Skip to content

Latest commit

 

History

History

variable-types

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

变量与类型

不论是 GUI 还是命令行,计算机程序的处理的内容总是数据。在编程语言中,我们可以通过变量的概念来存取数据。

对 JavaScript 这样灵活的脚本语言,编写形如 var x = 123var y = 'abc' 的变量赋值代码是非常简单而直观的。但这也带来了潜在的问题:脚本语言用户在定义并使用变量时,往往只关注变量名,而忽略了变量的类型

类型有多重要呢?类型系统可以成为划分编程语言的主要维度之一。字符串、整数、浮点数、布尔值、集合、字典……类型的背后是编程语言的种种设计决策,它直接关系到一门语言所写出代码的:

  • 安全性 - 完善的静态类型检查能避开空指针等暗坑。
  • 抽象能力 - 抽象能力强的语言通常能够支持更复杂的类型。
  • 可维护性 - 动态类型一时爽,重构代码火葬场。
  • 运行效率 - 能够静态推导出的类型更利于性能优化。

正是因为类型系统如此重要,因而在跑通 Hello World 之后,我们希望首先从类型系统出发来重新了解 C。

一等公民的类型

我们经常能够听见这样的说法:“JavaScript 中函数是一等公民。”那么一等公民有何定义,这样的划分在 C 语言中又是如何体现的呢?

可以宽泛地认为,一等公民相当于一门编程语言中这样的实体:

  • 可以被存入变量。
  • 可以作为函数参数传递。
  • 可以作为函数返回值返回。
  • 可以在运行时创建,无需编译期静态声明。
  • 可以匿名存在。

面向对象语言中的对象类型满足上面的每一条规则,因而我们可以认为这些语言中对象是一等公民。类似地,JavaScript 的函数也满足这些规则,故而有 JavaScript 中函数一等公民的说法。而在 C 中,四种基本的运算类型 charintfloatdouble 都属于一等公民,至于剩下的布尔值、数组、指针、结构体和函数呢?它们都有着各自的局限:

  • C 没有原生的布尔值类型,TRUE 和 FALSE 不过是 0 和 1 的语法糖。
  • C 不支持对数组重新赋值,只能通过指针间接操作。
  • C 没有匿名指针。
  • C 的结构体、枚举和联合类型不能够动态创建,只能静态指定。
  • C 的函数不能像 new Function() 那样动态创建。

现在我们已经知道了 C 有哪些类型,其中又有哪些是一等公民。但要根据类型来快速了解一门编程语言,其维度显然不止这一点。下面我们不妨从动态类型和静态类型的区别,来看看 C 与 JavaScript 的异同。

动态类型与静态类型

在 JavaScript 这样的脚本语言中,我们通常习惯只提供变量名来声明变量:

let x = 123;
let y = 'Hello World'
let z = [1, 2, 3]

而在 C 里,我们需要同时为变量指定变量名类型

int x = 42;
float y = 1.5;

这种区别的背后,实际上涉及一门编程语言是动态类型语言还是静态类型语言,这对其使用体验会产生很大的影响:

  • 静态语言 - 变量类型在编译期指定,类型问题会造成编译失败。它的开发效率相对较低,但运行时更不容易出现类型错误。
  • 动态语言 - 变量类型在运行时才能判断。它的开发效率相对较高,但更有可能出现运行时的类型错误。

很显然,C 属于静态语言,而 JavaScript 属于动态语言。静态语言能够避免什么错误呢?譬如这样的代码:

let fn // undefined
fn()

将任意的变量当做函数调用,这样的语法在 JavaScript 中都是合法的。但正如上面所看到的,被当做函数调用的变量,其值完全有可能是一个未定义的 undefined,这所带来的报错相信前端同学一定不会陌生:

TypeError: undefined is not a function

那么,C 这样的静态语言就能通过编译期检查来杜绝运行时的类型错误了吗?这也是不准确的。在这方面,编程语言的强类型和弱类型之分是另一个重要的影响因素。

强类型与弱类型

静态类型和动态类型的区别可以说泾渭分明,但强类型和弱类型则没有一个非常明确的边界。记得化学中熵的概念吗?熵没有单位,但可以比较。类似地,编程语言没有绝对的强类型和弱类型,只有相对的强弱之分。

在类型的强弱方面,有两个非常普遍的错误观点:

  • Python 是脚本语言,所以它是弱类型的。
  • C 是静态语言,所以它是强类型的。

我们可以用一行代码的实验,来分辨出 Python、JavaScript 和 C 中类型的强弱:

  • 终端运行 Python,输入 1 + '1' 查看结果。
  • 终端运行 Node,输入 1 + '1' 查看结果。
  • 编译 implicit-conversion.c 并运行,查看 C 中 1 + '1' 的结果。

为什么同样是 1 + '1',在 JavaScript 中得到 '11',在 C 中得到 50,而在 Python 中会报错呢?

一门语言的类型越强,则意味着它越不容忍隐式类型转换;反之类型越弱,则越倾向于容忍隐式类型转换。作为例子,我们可以解析这三门语言在上面这个场景(将整数和字符串两种类型相加)下的处理策略:

  • Python 没有隐式转换,解释器认为这是一个类型错误。作为替代,你可以选择 str(1) + 11 + int('1')
  • JavaScript 选择将数字隐式转换成字符串,然后执行字符串的相加操作,相当于 '1' + '1'
  • C 的设计是先将 char 类型隐式转换为整数,再执行相加操作。C 的 char 类型是基于 ASCII 码表示的,这个编码中每个字符使用 0~256 中的一个数字表示,'1' 对应十进制的 49,故而我们得到了 49 + 1 = 50 的整数。

所以,C 和 JavaScript 其实都属于弱类型语言,而常常被认为非常灵活的 Python 却是一门正经的强类型语言。对 C 来说,在后面的篇幅中我们会介绍的指针,甚至可以任意指定其所指的类型,这也是 C 中最灵活和最不容易掌握的特性了。

代码示例

对于上文中提及的几种 C 基本数据类型,可以在 basic-types.c 中查看它们的定义和使用方式,注意 printf 中用于打印不同类型变量的标识符哦。

在明白了变量的类型后,在 C 中定义并使用它们相信不会是一件难事。但如何处理程序逻辑的复杂度呢?接下来让我们看看 C 中的控制流语句吧。