数据类型
数据类型
苏丙榅1. 数据类型
1.1 整型
和数学的概念一样,在C语言中,整数是没有小数部分的数。计算机以二进制数字储存整数,例如,整数 7
以二进制写是 111
。因此,要在8位字节中储存该数字,需要把前 5 位都设置成 0,后 3 位设置成 1。
1.1.1 整形家族
关于变量的定义方式前面已经详细讲过了,把具体的数据类型放到变量名前面即可,关于整形其实是一个很宽泛的概念,我们还可以再次进行细分,如下表:
数据类型 | 占用空间 |
---|---|
short (有符号短整型) |
2字节 |
int (有符号整形) |
4字节 |
long (有符号长整形) |
Windows 为 4 字节,Linux 为 4 字节(32位),8字节(64位) |
long long (有符号长长整形) |
8字节 |
unsigned short (无符号短整型) |
2字节 |
unsigned int (无符号整形) |
4字节 |
unsigned long (无符号长整形) |
同 long 类型 |
unsigned long long (无符号长长整形) |
8字节 |
C语言提供 3个附属关键字
修饰基本整数类型:short
、long
和unsigned
。我们应记住以下几点:
short int
类型(简写为short
)占用的存储空间可能比 int 类型少,常用于较小数值的场合以节省空间。
long int
(简写为long
)占用的存储空间可能比 int 多,适用于较大数值的场合。long long int
(简写为long long
,C99标准加入)占用的储存空间可能比long
多,适用于更大数值的场合,该类型至少占64位
。以
unsigned
为前缀的整数只适用于非负值的场合。当一个小的数据类型赋值给一个大的数据类型,不会出错,因为编译器会自动转化。但当一个大的类型赋值给一个小的数据类型,那么就可能丢失高位。
对于长整形来说它所对应的数值常量和非长整形是有区别的,如下表:
整型常量 | 所需类型 |
---|---|
10 | 代表short/int 类型 |
10l,10L | 代表long 类型 |
10ll,10LL | 代表long long 类型 |
10u,10U | 代表unsigned short/int 类型 |
10ul,10UL | 代表unsigned long 类型 |
10ull,10ULL | 代表unsigned long long 类型 |
对于长整形数值来说在描述的时候需要再后面添加L(或者小写的l)
后缀,如果是无符号类型需要添加后缀U(u)
,这样看起来就非常直观了。
关于上述整形在使用 printf()
函数打印的时候对应的占位符如下:
占位符 | 描述 |
---|---|
%d/%i |
十进制int 类型 |
%o |
八进制整数 |
%x/%X |
十六进制整数 |
%u |
十进制unsigned int 类型 |
%hd |
十进制short 类型 |
%ho |
八进制 short int 类型 |
%hx |
十六进制 short int 类型 |
%hu |
十进制unsigned short 类型 |
%ld |
十进制long 类型 |
%lo |
八进制 long int 类型 |
%lx |
十六进制 long int 类型 |
%lu |
十进制unsigned long 类型 |
%lld |
十进制long long 类型 |
%llo |
八进制 long long int 类型 |
%llx |
十六进制 long long int 类型 |
%llu |
十进制unsigned long long 类型 |
%% |
输出一个百分号 |
1.1.2 sizeof() 关键字
sizeof不是函数
,所以不需要包含任何头文件,它的功能是计算一个数据类型的大小,单位为字节
。
- sizeof 的返回值为
size_t
size_t
类型在 32 位操作系统下是unsigned int
,是一个无符号的整数
占位符 | 描述 |
---|---|
%zd |
使用 printf 函数打印size_t 类型的数据 |
C99 和 C11 提供%zd
转换说明匹配sizeof
的返回类型。一些不支持C99和C11的编译器可用%u
或%lu
代替%zd
。
sizeof 的操作方式一共有两种:
- 获取特定
数据类型的大小
,语法为:sizeof(类型)
- 获取给定变量的大小,语法为:
sizeof(变量名)
或者sizeof 变量名
当 sizeof 运算符运算对象是类型时,圆括号必不可少,但是对于特定量(变量或常量)可有可无。
1 |
|
程序输出的结果如下:
1 | sizeof(short) = 2 |
1.1.3 有符号和无符号区别
1.1.3.1 有符号整数
有符号整数
是一种整数数据类型,可以表示正数
、负数
和零
。在C/C++中,常见的有符号整数类型有:
signed short int
:它是一个有符号的短整数类型,占用两个字节
的存储空间,可简写为short int
或者short
。signed int
:它是一个有符号的整数类型,占用四个字节
的存储空间,可简写为int
。signed long int
:它是一个有符号的长整数类型,占用四个字节或八个字节
的存储空间,根据编译器和平台的不同,表示的范围也会有所变化。可简写为long int
或者long
。signed long long int
:它是一个有符号的长长整数类型,占用八个字节
的存储空间,表示的范围是很大的。可简写为long long int
或者long long
。
这些有符号整数类型可以表示负数,因为它们的最高位(最左边的位)用于表示符号位,0表示正数,1表示负数。接下来举例说明:
现有一个十进制
数-1089474374
,通过程序打印它对应的十六进制
数值:
1 |
|
输出的结果为:
1 | BF0FF0BA |
接下来我们将这个十六进制数转换为二进制进行分析:
十六进制 | BF0FF0BA |
---|---|
补码 | 1011 1111 0000 1111 1111 0000 1011 1010 |
反码 | 1011 1111 0000 1111 1111 0000 1011 1001 |
原码 | 1100 0000 1111 0000 0000 1111 0100 0110 |
前面已经反复强调了,数值在计算机中一律都是采用补码来存储的
,对于有符号整数来说,它们左侧的最高位是符号位,得到的结果符号位为1,证明这个整数是负数。
如果忽略最高位的符号位,得到就是一个正整数,其对应的十六进制数值应该是40F00F46
,也就是十进制的1089474374
正整数的补码和原码相同,得到的二进制数和负数的二进制数相比较也只是最高位不同,正整数最高位为0
,负整数最高位为1
。
1.1.3.2 无符号整数
无符号整数
是一种整数数据类型,它只能表示非负数(包括零)
,无符号数最高位不是符号位,而就是数的一部分
,无符号数不可能是负数。
在C/C++中,无符号整数需要使用关键字unsigned
对整型进行修饰,常见的无符号整数类型有:
unsigned short int
:它是一个无符号的短整数类型,占用两个字节
的存储空间,可写简写为unsigned short
unsigned int
:它是一个无符号的整数类型,占用四个字节
的存储空间,可写简写为unsigned
unsigned long int
:它是一个无符号的长整数类型,占用四个字节或八个字节
的存储空间,根据编译器和平台的不同,表示的范围也会有所变化。可写简写为unsigned long
unsigned long long int
:它是一个无符号的长长整数类型,占用八个字节
的存储空间,表示的范围很大。可写简写为unsigned long long
现有一个十进制
数3236958022
,通过程序打印它对应的十六进制
数值:
1 |
|
程序输出的结果如下:
1 | C0F00F46 |
接下来我们将这个十六进制数转换为二进制进行分析:
十六进制 | C0F00F46 |
---|---|
补码 | 1100 0000 1111 0000 0000 1111 0100 0110 |
反码 | 1100 0000 1111 0000 0000 1111 0100 0110 |
原码 | 1100 0000 1111 0000 0000 1111 0100 0110 |
通过验证可以得知,无符号整形的左侧最高位不是符号位,而是数值范围的上限。因此,无符号整数具有更大的正数范围
,但不能表示负数。
最后,通过一个表格来说明有符号和无符号整型的取值范围:
数据类型 | 占用空间 | 取值范围 |
---|---|---|
short |
2 字节 | -32768 到 32767 (-215 ~ 215-1) |
int |
4 字节 | -2147483648 到 2147483647 (-231 ~ 231-1) |
long |
4 字节 | -2147483648 到 2147483647 (-231~ 231-1) |
unsigned short |
2 字节 | 0 到 65535 (0 ~ 216-1) |
unsigned int |
4 字节 | 0 到 4294967295 (0 ~ 232-1) |
unsigned long |
4 字节 | 0 到 4294967295 (0 ~ 232-1) |
1.2 布尔类型
C99标准添加了_Bool类型
,用于表示布尔值,即逻辑值true
和false
。因为C语言用值 1 表示 true,值 0 表示 false,所以 _Bool 类型实际上也是一种整数类型。但原则上它仅占用1位存储空间
,因为对 0 和 1 而言,1位的存储空间足够了。关于布尔类型,其对应的头文件为stdbool.h
,该头文件内容如下:
1 | // stdbool.h |
通过阅读上述代码(初学者不用读),可以得到如下信息:
- 数据类型
bool
和_Bool
是等价的,二者可以替换彼此 - 布尔类型有两个值:
0
或者1
0
值对应的关键字是false
1
值对应的关键字是true
1 |
|
1.3 字符型
1.3.1 ASCII 对照表
字符型变量用于存储一个单一字符,在 C 语言中用 char
表示,其中每个字符变量都会占用 1 个字节
。在给字符型变量赋值时,需要用一对英文半角格式的单引号 (''
) 把字符括起来。
从技术层面看,char 的本质就是一个1字节大小的整型
。因为 char 类型实际上储存的是整数而不是字符。计算机使用数字编码来处理字符,即用特定的整数表示特定的字符,最常用的编码就是ASCII 编码。
下面是 ASCII 对照表:
ASCII值 | 控制字符 | ASCII值 | 字符 | ASCII值 | 字符 | ASCII值 | 字符 |
---|---|---|---|---|---|---|---|
0 | NUT | 32 | (space) | 64 | @ | 96 | 、 |
1 | SOH | 33 | ! | 65 | A | 97 | a |
2 | STX | 34 | “ | 66 | B | 98 | b |
3 | ETX | 35 | # | 67 | C | 99 | c |
4 | EOT | 36 | $ | 68 | D | 100 | d |
5 | ENQ | 37 | % | 69 | E | 101 | e |
6 | ACK | 38 | & | 70 | F | 102 | f |
7 | BEL | 39 | , | 71 | G | 103 | g |
8 | BS | 40 | ( | 72 | H | 104 | h |
9 | HT | 41 | ) | 73 | I | 105 | i |
10 | LF | 42 | * | 74 | J | 106 | j |
11 | VT | 43 | + | 75 | K | 107 | k |
12 | FF | 44 | , | 76 | L | 108 | l |
13 | CR | 45 | - | 77 | M | 109 | m |
14 | SO | 46 | . | 78 | N | 110 | n |
15 | SI | 47 | / | 79 | O | 111 | o |
16 | DLE | 48 | 0 | 80 | P | 112 | p |
17 | DCI | 49 | 1 | 81 | Q | 113 | q |
18 | DC2 | 50 | 2 | 82 | R | 114 | r |
19 | DC3 | 51 | 3 | 83 | S | 115 | s |
20 | DC4 | 52 | 4 | 84 | T | 116 | t |
21 | NAK | 53 | 5 | 85 | U | 117 | u |
22 | SYN | 54 | 6 | 86 | V | 118 | v |
23 | TB | 55 | 7 | 87 | W | 119 | w |
24 | CAN | 56 | 8 | 88 | X | 120 | x |
25 | EM | 57 | 9 | 89 | Y | 121 | y |
26 | SUB | 58 | : | 90 | Z | 122 | z |
27 | ESC | 59 | ; | 91 | [ | 123 | { |
28 | FS | 60 | < | 92 | / | 124 | | |
29 | GS | 61 | = | 93 | ] | 125 | } |
30 | RS | 62 | > | 94 | ^ | 126 | ` |
31 | US | 63 | ? | 95 | _ | 127 | DEL |
标准ASCII码的范围是0~127,只需7位二进制数即可表示。通常,char 类型被定义为8位的存储单元,因此容纳标准 ASCII 码绰绰有余。
ASCII 码大致由以下两部分组成:
- 非打印控制字符:
- ASCII 表上的数字
0 ~ 31
分配给了控制字符,用于控制像打印机等一些外围设备。 - 数字
127
代表 Del 命令。
- ASCII 表上的数字
- 打印字符:数字
32 ~ 126
分配给了能在键盘上找到的字符,当查看或打印文档时就会出现。
1 |
|
1.3.2 转义字符
转义字符是一种特殊的字符序列,用于表示在代码中无法直接表示或输入的字符。在 C/C++ 和许多其他编程语言中,转义字符以反斜杠(\
)开头,后跟一个或多个字符。
下表中是一些常见的C/C++转义字符:
转义字符 | 含义 | ASCII码值(十进制) |
---|---|---|
\a | 警报 | 007 |
\b | 退格(BS) ,将当前位置移到前一列 | 008 |
\f | 换页(FF),将当前位置移到下页开头 | 012 |
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\r | 回车(CR) ,将当前位置移到本行开头 | 013 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
\\ |
代表一个反斜线字符”" | 092 |
\' |
代表一个单引号(撇号)字符 | 039 |
\" |
代表一个双引号字符 | 034 |
\? |
代表一个问号 | 063 |
\0 |
数字0 | 000 |
\ddd |
8进制转义字符,d范围0~7 | 3位8进制 |
\xhh |
16进制转义字符,h范围0-9,a-f,A~F | 3位16进制 |
温馨提示:注意:绿色字体标注的为不可打印字符。
1 |
|
程序输出的结果如下:
1 | efgxyz |
- 第 5 行:输出一个字符串,但是并没有换行
- 第 6 行:转义字符
\r
是将位置切换到行首,\n
为换行,因此发生了数据覆盖 - 第 7 行:输出一个字符串,但是并没有换行
- 第 8 行:转义字符
\b
作用为退格,因此会删掉一个字符,\n
为换行 - 第 9 行:
\123
为8进制转义字符
,0123 对应 10 进制数为 83 - 第 10 行:
\x23
为16进制转义字符
,0x23 对应 10 进制数为 35
1.3.3 数值溢出
当超过一个数据类型能够存放最大的范围时,数值会溢出。有符号位最高位溢出会导致数的正负发生改变,但最高位的溢出会导致最高位丢失。
数据类型 | 占用空间 | 取值范围 |
---|---|---|
char |
1字节 | -128 到 127 (-27 ~ 27-1) |
unsigned char |
1字节 | 0 到 255 (0 ~ 28-1) |
1 |
|
程序执行的结果如下:
1 | ch = -127 |
- 有符号字符变量
ch
的最大值是127
,也就是二进制的0111 1111
,如果在这个数值的基础上加 2,二进制数值会变成1000 0001
,最高位符号位值是 1,也就意味着这是一个负数,并且计算机是基于补码进行数据运算的,所以这个二进制数应该是补码,我们需要计算原码:- 原码 = 补码的数据位取反 + 1,即:
1111 1111
1111 1111
去掉符号位就是127
,所以结果是-127
- 原码 = 补码的数据位取反 + 1,即:
- 无符号字符变量
ch1
的最大值是255
,也就是二进制的1111 1111
,如果在这个数值的基础上加 1,二进制数值会变成1 0000 0000
,最高位溢出,剩余的8个二进制位都为0,所以得到的结果为0
- 无符号字符变量
ch1
的最大值是255
,也就是二进制的1111 1111
,如果在这个数值的基础上加 2,二进制数值会变成1 0000 0001
,最高位溢出,剩余的8个二进制位值为1,所以得到的结果为1
1.4 浮点型
浮点型变量是用来存储小数数值的
。在C语言中, 浮点型变量分为两种: 单精度浮点数(float)、 双精度浮点数(double)
, 从语义上就可以得知 double 型变量所表示的浮点数比 float 型变量更精确。
数据类型 | 占用空间 | 有效数字范围 |
---|---|---|
float |
4 字节 | 7 位有效数字 |
double |
8 字节 | 15~16 位有效数字 |
由于浮点型变量是由有限的存储单元组成的,因此只能提供有限的有效数字。有效位以外的数字将被舍去,这样可能会产生一些误差。
在使用过程中不以 f/F
结尾的常量是 double 类型,以 f/F
结尾的常量(如3.14f/3.14F)是 float 类型。
我们在使用一些浮点值的时候,可能会遇到一个数值非常大或者非常小的情形,为了将它们更加情形的描述出来,就可以使用科学计数法。
科学计数法(Scientific Notation)是一种表示非常大或非常小的数的常用方法
。它基于数字的指数形式,由两部分组成:一个基数(在1到10之间)和一个指数(整数或有理数)
。通常使用科学计数法来简化表示具有大量零的数或极大/极小的物理量。
科学计数法的一般形式如下:
a x 10b
其中,a 是一个在 1 到 10 之间的基数,10 是底数,b 是指数。例如:
- 1,000,000 可以用科学计数法表示为 1 x 106
- 0.000001 可以用科学计数法表示为 1 x 10-6
科学计数法的优点是简洁、易于阅读和表达极大/极小的数。它也便于进行数学运算,特别是在涉及非常大或非常小的数时。在编程语言中,科学计数法也被广泛使用。例如在C/C++中,可以使用科学计数法表示浮点数:
1 | double num = 8e6; // 表示 8 x 10^6,等同于 8000000.0 |
在此示例中,变量 num 被赋值为 8e6
,它表示 8 x 10^6
,等同于数值 8000000.0。8是基数,e代表的就是底数10,6是指数, 此处的e和E可以互换
关于上述整形在使用 printf()
函数打印的时候对应的占位符如下:
占位符 | 描述 |
---|---|
%f |
小数(包含float 类型和double 类型)默认精度为6位 |
%g |
6个有效数字的浮点数。整数部分一旦超过6位,就会自动转为科学计数法,指数部分的e 为小写 |
%G |
等同于%g ,唯一的区别是指数部分的E 为大写 |
%le |
科学计数法表示的 long double 类型浮点数 |
%lf |
long double 类型浮点数 |
%.2f |
打印浮点数,保留小数点后两位 |
%a |
十六进制浮点数,字母输出为小写 |
%A |
十六进制浮点数,字母输出为大写 |
1 |
|
程序输出的结果如下:
1 | a = 3.140000e+06 // 损失了精度 |
2. 数据的输入输出
2.1 字符串常量
字符串是内存中一段连续的char
空间,以'\0'(数字0)结尾
。字符串常量是由双引号括起来
的字符序列,如 “china”
、“C program”
,“$12.5”
等都是合法的字符串常量。字符串常量与字符常量的不同:
每个字符串的结尾,编译器会自动的添加一个结束标志位'\0'
,即字符串 "a"
包含两个字符'a'
和’\0’
。
2.2 printf 和 putchar 函数
printf()
和 putchar()
都是标准C的库函数,可以直接拿过来使用,它们都可以进行数据的输出,但二者还是有区别的:
- printf 是输出一个字符串(char*)
- putchar 输出一个字符(char)
这两个函数的函数原型如下:
1 |
|
下面的表格是使用 printf 函数进行数据输出的时候常用
的一下占位符:
占位符 | 数据类型 | 描述 |
---|---|---|
%d |
int | 接受整数值并将它表示为有符号的十进制整数 |
%hd |
short int | 短整数 |
%hu |
unsigned short | 无符号短整数 |
%o |
unsigned int | 无符号8进制整数 |
%u |
unsigned int | 无符号10进制整数 |
%x,%X |
unsigned int | 无符号16进制整数,x 对应的是 abcdef,X 对应的是 ABCDEF |
%f |
float/double | 浮点数 |
%lf |
long double | 长双精度浮点数 |
%e,%E |
double | 科学计数法表示的数,此处”e”的大小写代表在输出时用的”e”的大小写 |
%c |
char | 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符 |
%s |
char * | 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符) |
%p |
void * | 以16进制形式输出指针 |
%% |
% | 输出一个百分号 |
printf 函数附加格式(下表中的字符可以一并添加到上面的占位符中):
字符 | 含义 |
---|---|
l(字母l) | 附加在d,u,x,o前面,表示长整数 |
- | 左对齐 |
m(代表一个整数) | 数据最小宽度 |
0(数字0) | 将输出的前面补上0直到占满指定列宽为止不可以搭配使用- |
m.n(代表一个整数) | m指域宽,即对应的输出项在输出设备上所占的字符数。n指精度,用于说明输出的实型数的小数位数。对数值型的来说,未指定n时,隐含的精度为n=6位。 |
1 |
|
程序输出的结果如下:
1 | a = 100 |
2.3 scanf 与 getchar 函数
通过使用 scanf 函数与 getchar 函数都可以从标准输入设备读取数据,它们都是标准C的库函数,二者的区别在于:
- getchar 是从标准输入设备读取一个字符(char)
- scanf 通过%转义的方式可以得到用户通过标准输入设备输入的数据
两个函数的函数原型如下:
1 |
|
需要注意的是,getchar
函数每次只能读取一个字符。如果你输入了多个字符,getchar
函数只会读取第一个字符,而将其他字符留在输入缓冲区中。你可以在需要的时候多次调用getchar
函数来读取多个字符。
1 |
|
在上面的程序中有一下几点需要额外说明:
- 通过
getchar()
接收一个字符输出,输入完毕之后会按下回车健表示输入完毕,所以一共输入了两个字符,但是getchar()
一次只能读一个字符,想要读出这个回车就必须再调用一次getchar()
,程序中第13,18行
的作用就是如此。 - 在使用
scanf
接收一个输入的时候,会把用户输入存储到一个变量中,所以需要对这个变量取地址(在变量名前加 &
)也就是找到变量的家,这样才能够完成变量数据的写入,在后续的学习过程中会频繁接触到类似的操作。
2.4 缓冲区
缓冲区(Buffer)是指用于临时存储数据的一块内存区域。缓冲区在不同的上下文中有不同的用途,以下是一些常见的缓冲区的应用场景:
输入缓冲区
:用于在从输入设备(如键盘、鼠标)读取数据之前,临时存储输入的数据。输入缓冲区可以收集并存储一定数量的输入数据,等待程序逐个处理。输出缓冲区
:用于在将数据发送到输出设备(如显示器、打印机)之前,临时存储要输出的数据。输出缓冲区可以收集一定数量的数据,然后一次性发送到输出设备,从而提高输出效率。文件缓冲区
:用于在文件的读取和写入过程中作为数据的中转站。文件缓冲区可以减少对物理文件的读写操作,通过在缓冲区内进行数据处理,提高文件操作的效率。网络缓冲区
:用于在网络通信中存储数据,以便发送和接收数据包。网络缓冲区可以帮助管理数据包的传输,并提供对数据包的排序、丢失检测和重传等处理。
在前面章节中为大家介绍了输入和输出函数,对于这些函数而言都是对应一块缓冲区,其好处在上文中已经提及了。虽然缓冲输入好处很多,但是某些交互式程序也需要无缓冲输入
。例如,在游戏
中,你希望按下一个键就执行相应的指令。因此,缓冲输入和无缓冲输入都有用武之地。
关于缓冲区我们可以将其分为两类:
完全缓冲I/O
- 当缓冲区被填满时才刷新缓冲区(内容被发送至目的地),通常出现在文件输入中
- 缓冲区的大小取决于系统,常见的大小是 512 字节和 4096字节
行缓冲I/O
- 在出现换行符时刷新缓冲区
- 键盘输入通常是行缓冲输入,所以在按下Enter键后才刷新缓冲区
在C语言中,完全缓冲函数在后讲文件 I/O 的时候会接触到,有一个常用的行缓冲函数是printf
。它用于将格式化的数据输出到标准输出(通常是显示器)并自动刷新缓冲区。当使用printf
函数在屏幕上打印内容时,内容会被缓冲并在换行符\n
出现时刷新缓冲区,将缓冲区中的内容显示到屏幕上。
通常情况下,当我们使用printf
函数输出内容后,内容并非立即显示在屏幕上,而是先存储在缓冲区中,当以下条件之一满足时,缓冲区的内容才会被刷新到屏幕上:
缓冲区已满
:当缓冲区已经达到一定大小时,会自动刷新缓冲区。遇到换行符 \n
:当printf
函数的格式化字符串中包含换行符时,会触发缓冲区的刷新。输入输出流的关闭
:当程序结束时,缓冲区中的内容会被自动刷新。
除了printf
函数,还有其他用于行缓冲的函数,例如puts
函数和fputs
函数。这些函数会自动在输出内容末尾添加换行符,以触发缓冲区的刷新。