第 2 讲:数据类型初步
我们先要掌握一个概念:数据类型(Data Type)。世间万物都有自己独特的表示方式。例如人的性别只有男、女、其它三种方式就可以完整表达出来,再比如身高只需要是一个正整数来表示就比较合适了,又比如说话的文字可以用语音的形式呈现,也可以用文字形式呈现。在电脑里,每一种想要表示出来的数据都有他们自己的数据表示机制,这种机制就称为数据类型。

布尔型数据在 C 语言委员会在 1999 年才提出该类型的文案,所以 C99 标准才开始支持布尔类型,而现在大多教材使用的还是 ANSI C(C89)标准,即 1989 年的标准,所以教材上给的 C 语言是不支持布尔类型的。
头晕是吧,我们先来说说,什么叫无符号、什么叫带符号(有符号)、什么叫字符、什么叫字符串、什么叫布尔、什么叫二进制小数、什么叫浮点小数、什么又叫精度。
无符号(Unsigned):非负的;
带符号(或有符号,Signed):可以表示负数的;
字符(Character):可以表示出一个普通的英文文本信息的最小单元。这样说不好理解,你可以认为是“英文所有大小写字母 + 数字 + 各种英文标点符号”的集合;
字符串(简称串,String):一系列字符;
布尔(Boolean):只用来表示真假情况的;
二进制小数(Binary Decimal):使用二进制格式表达出来的小数;
浮点小数(Floating Point Decimal):二进制小数存储在电脑里的一种存储机制,类似于科学计数法,不过是表达为 a \times 2^b 的格式;
精度(Accuracy):表示表达出一个完整数据和原始数据比较起来的完全精确的程度,在 C 语言里特指小数转为二进制小数后,在存放为浮点小数时,表达出来的数值和原始数值相差多少个小数位。
下面我们来说一下精度和二进制小数的东西。
精度和二进制小数
一般在学校里,老师都会讲解如何把一个数字利用二进制表达出来。可它们并没有讲解,如何表示一个小数,以及一个负数。不过,我会带领大家认识一下,如何表示一个二进制小数,使用 0 和 1。首先,我们举个例子。
将 0.15625 转为 0 和 1
表示小数的方式很简单。既然表示整数利用的是除2取整的模式,那么小数自然就是乘2取余的模式。
比如这个示例里,我们把 0.15625 乘以 2,得到 0.3125,整数部分为 0,把它提取出来,做减法得到 0.3125(因为是减去 0,所以数字并没变化),然后把这个数继续乘以 2,得到 0.625,整数部分为 0,继续提取出来,然后用 0.625 乘以 2,得到 1.25,提取整数部分 1,再将 0.25 乘以 2,继续往下做,直到我们得到 0 结束计算。
最终,我们得到的数字 0、0、1、0、1 即是二进制小数表达的小数部分,由于这个数的整数部分本来就是 0,所以整数部分无需我们作更多的二进制表达的转换,所以这个数的二进制表示就是 0.00101_2。
能遇到十进制表示里的循环小数类似的情况吗?
如果我们尝试把 0.3 按上述逻辑转换为二进制小数的时候,就会发现异样:
可以看到,在计算 0.3 的时候,运算总能得到 0.3、0.6、0.2、0.4、0.8、0.6、0.2、0.4、……的序列,后面无穷无尽。所以,0.3 的二进制表示就是 0.0\dot100\dot1_2。
现在再来说精度的问题
正是因为数字并不能完整转换为准确的二进制表示,比如 0.3,所以表示上一定会存在精度的偏差。比如 0.3 在二进制转换后,由于无穷无尽的关系,我们只能在 float
、double
或 long double
类型表示完整的二进制位数后,舍弃掉后面的部分,于是这个数肯定就不等于原始数值了。
不过,二进制表达是很复杂的,所以这里我不打算展开讲解,有兴趣的小伙伴可以看下网上有关 IEEE 754 的标准规范,这个就是专门规定如何把小数表示为二进制表示,存储到程序里的规范。
这里告诉大家的是,经过这种精心转换的模式后,我们有了如下的结论:
float
类型的小数,精度是表示为十进制数的情况下的 6 或 7 位有效数字(6 或 7 的其一,详细情况则取决于二进制表示,这里不展开说明);double
类型的小数,精度是表示为十进制数的情况下的 14 或 15 位有效数字;long double
类型取决于系统实现,所以这里无法给出精确的范围,大约精确到 19 位有效数字。
所占字节大小
大家都知道,数据存储到电脑里都是以一个叫做“字节”的单位作为衡量依据的,那么这些数据类型都对应了一个固定的、绝对的字节数,它就表示元素存进去时,用这个类型就需要占据多大的内存空间的容量。表格如下:

如果你有时候记不住它们的大小,可以用代码写成这样来得到结果:
我们把上面提到的类型代号写到这里,就可以输出对应的结果了。比如
因为这些所占字节数是可以测出来的,所以一般不要求记住它们,但考试会考,所以这些数值还是希望你记住,就好像化学元素的相对原子质量,常见的是需要作出记忆的。
然后,先不要管这个代码里的
%d
是啥,稍后会作出解释。
使用
变量的定义和声明
在数学里,表示一个可以变化的数据信息是通过变量来表达的,比如 f(x) = 3x^2 + 5x + 7 这个表达式里,x 就是所谓的自变量,而 f(x) 称为函数,如果写成 y 则又称为因变量。在 C 语言里,我们不区分所谓的自变量和因变量,只要能写成一个数值的表示形式,就称为变量。
变量的形式一般是这样的:
数据类型 变量名;
(简单声明)数据类型 变量名 = 数值;
(赋值数值常量)数据类型 变量名 = 计算表达式;
(赋值表达式)数据类型 变量1, 变量2, 变量3;
(不赋值)数据类型 变量1, 变量2 = 多少, 变量3;
(不全赋值)数据类型 变量1 = 多少, 变量2 = 多少, 变量3 = 多少;
(全赋值)
比如,一个程序写成这样
将得到
其中的 \'
在前文提到过,以 \
开头的、写在双引号里的内容可以表示一个另外意义的表达结果,这里 \'` 表示输出一个 `'` 符号。然后 `%d` 则在这里表示 `printf` 语句小括号里第一个逗号后的变量的信息结果。这里就表示 `age` 这个变量的数值(23),然后把 23 替换到双引号的 `m` 和 `y
之间。
顺带一说,双引号的内容就称为一个字符串,而每一个字母(包括空格、\'
等等符号,都表示一个字符)。
变量的自定义输入
再拿出 int
作为数据类型举例。既然有输出就应该由输入。如果需要我们自己输进去一个数字,然后程序输出这个结果,就需要用到新的写法:scanf
语句。
这样就可以了。注意 scanf
语句的写法,逗号后跟的 age
变量必须要在前面加前缀符号 &
,这一点在后面再说。
可以发现,C 语言是一种比较奇特的语言,它的大多数语法的耦合度较高,即前面的知识点要用超纲的内容做铺垫,而后面的知识点却又依赖于前面的知识点,所以……写教程真的很伤脑筋啊……
当然,scanf
语句也可以同时为多个变量赋值。比如
&
前缀符号,而且在运行程序的时候,你必须得跟着这个字符串里写的格式写,比如这个字符串 %d, %d
scanf
语句甚至不以换行作为分割,所以你可以这么输入:
这样的书写格式里所有的换行都是不被读取的,所以你的所有换行都会被“抹掉”,故这种格式最终还是
但如果没有严格按照格式书写,比如
这些格式都是错的,最终只有 a
会正确得到 12,而 b
依旧是没有数值的。
当然,你也可以分写两句:
切记,字符串里带有空格的
scanf
语句,在运行程序时,你也要在输入完第一个数据时,空一格输入第二个数据。当然,后者这种格式,在输入的时候可以用回车的方式分割两个输入数据。
格式化字符串的定义和使用
下面还要来看看,什么是格式化字符串(Formatting String)。其实,说白了,前文出现的 %d
就是格式化字符串的一种。但请注意,它只是其中一种,如果你把它认为是输出数值结果的话,那就错了。所有的格式化字符串如下。
要把数据输入到电脑里面去,我们需要用到的是C语言自带的输入功能,写法也有两种不同的类型:
&
了,这个符号就是专门用来找位置的。
另需要你记忆一些格式化字符串的写法:

大家只需要记住前面四种和最后两种就可以了。
如果我又定义了一个变量,是 int
,就用 %d
转为整数存放到电脑之中。
比如
就将整数变量 a
输入到电脑里面用整数规范存储了。如果格式化字符串用成其他的,比如 %f
,结果就很怪异。所以一定要配套。
输入两次的话,可以简写成一次输入:
注意,scanf
语句的字符串里中间最好要有字符。说白了就是不要写成 %d%d
挨在一起的写法。如果不给空格虽然程序不会出错,但是这样让容易出现安全漏洞,诸如缓存区溢出等。
格式化字符串的排版控制
因为是在控制台界面运行的,所以空格就可以用来排版了,不过有些时候,比如我们想让某个数输出的时候有对齐的效果,就会按情况来计算占据的显示的格数。比如一个两位数一定是占两格的,如果我们强制让所有输出的数值都占三格呢?
我们可以在 %
和 d
之间添加数值以控制占的格数。比如 %2d
表示占两格,%6d
输出的样子就是
%占格数.小数位格数f
将输出
特别要注意前面的空格。
如果是输入里插入这样的格式化字符串的话,则会严格按照这样的标准执行输入,如果超出范围的部分会被直接舍弃掉,或是拿给下一个部分使用。比如
那么提示输入的时候,如果输入的是 12345
的话,由于前面 %3d
的控制,后面的 45 会被切掉,而 b
恰好要两位数,所以 a
为 123,b
是 45,输出也是一样。
但如果超出了范围,比如输入了 123456,则依然结果是 123 和 45,6 会被舍弃。
最后讲一下这些语句的写法规范。
规范
代码格式规范
其实呢,编译器这位翻译官将你的 C 语言代码给它转变为系统能够识别和操作的语言之前,会给你的代码进行排版,这个时候,分号这些就显得额外重要。
一句话一定要用分号结尾,表示你这个操作做完了。不过换行不是必须有的,你可以把代码全写在一行,也可以换行写,只要有分号怎么写都无所谓,但是为了代码好看整洁,建议换行。
这样当然可以了,不过肯定不好看!
标识符命名规范
在前文里我们提到了标签和变量,它们都遵循一个命名规则。这个规则保证了程序正确的执行。命名规范其实在前文已经说到了。
数字、字母、下划线
_
三者的组合;第一个字符不能是数字;
不能是前文用到过的那些 C 语言自带的字符组合,如
int
等(注意,此时scanf
还不是 C 语言固有的东西,也就是说你可以把scanf
、printf
、main
这种东西定义为变量名,但完全不建议这么做)。
只要满足这 3 点就可以了。比如 hello
、age
、test_01
、T_T
、o_O
都是正确的标识符,而 2_4
、^_^
则不行。但建议名字取得有意义,因为下文可能会用到它们,取一个有意义的名字对语义理解有帮助。
类型规范
可以在上文看到很多有关变量的定义的语句。但可以发现到的是,大多整数类型的变量全部被定义为了 int
,而在后面的很多内容里也会这么干。你可能会问,虽然 int
的范围较大,且包含大多我们常见的元素的量,但 int
存储的字节数(占的空间大小)显然比 short
这些类型要大上一倍,甚至比 char
大上三倍,那为什么像是年龄这种东西不用 short
类型,甚至是更精确的 unsigned short
类型存储呢?年龄一般在 150 以下,而且 short
的范围最大到 32767,显然是够用的。
原因在于,int
是非常常用的数据类型,这其实算作一种习惯性使用。上文提到的说法是正确而且合理的,但一般我们出于计算和兼容性而言,用 int
居多。所以,如果你觉得确实需要降低内存使用,或者 int
不够用的话,才确定具体的使用类型,而一般都尽量使用 int
类型。