1 基本数据类型
1.1 存储单位
C 语言提供了多种数据类型,不同的数据类型占据的空间大小不同,一般表示数据大小的单位,我们通常使用字、字节来计算。
计算机底层使用 0 和 1 来存储数据,比如我们存储 10 进制的 4,其二进制格式为 100 ,此时占用 3 个 bit 位,而 8 个 bit 代表一个字节(B, byte),2 个 字节 等于 1 个字。
8 bit = 1 B,1024 B = 1KB, 1024 KB = 1 MB, 1024 MB = 1 GB,以此类推 TB, PB..., 但是大多数硬盘厂商生产硬盘时单位是按照 1000 的比例计算的。
1.2 原码
正数的原码就是其二进制本身,比如使用 4 个 bit 位存储正数 4, 其原码就是 0110,那么负数怎么办呢? 可以使用第一个 bit 位表示符号,比如 0 代表正数,1 代表负数。
原码表示虽然简单,但是计算场景使用原码会导致出错,比如 1 + (-1) = 0001 + 1001 = 1010 显然结果不对。
1.3 反码
正数的反码还是其本身,负数的反码是在其原码的基础上,符号位不变,其余位取反,比如 -1 的原码是 1001, 那么其反码就会变成 1110,那么此时再来看上面的计算公式。
1 + (-1) = 0001 + 1110 = 1111,得出最终的反码,然后将反码转换成原码,就变成了 1000,代表 -0,问题来了,0 还区分正负?
1.4 补码
为了解决上述的问题,我们引入了补码的概念。
正数的补码还是其本身,负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后再 +1,表示在其反码的基础上+1,我们再来计算上面的公式。
1 + (-1) = 0001 + (1110 + 0001) = 0000,因为我们使用的是 4 个 bit 位来计算,因此最后一个进位被丢弃,计算结果正确。
1.5 整数类型
整数就是不包含小数点的数据,比如 8, 88, 888 等,整数分为以下三种类型:
- short 占用 2 个字节,16 个 bit 位
- int 占用 4 个字节, 32 个 bit 位
- long 占用 8 个字节,64 个 bit 位
1.6 浮点类型
浮点类型一般用于保存小数,浮点类型将小数分为整数部分和小数部分,用一部分 bit 位表示整数部分,另一部分 bit 位表示小数部分,各自占多少 bit 位是浮动的,不固定,因此叫做浮点类型。浮点类型分为以下两种类型:
- float 单精度浮点,占用 4 个字节,32 个 bit 位
- double 双精度浮点,占用 8 个字节, 64 个 bit 位
1.7 字符类型
除了数字之外,每一个字符都可以使用字符类型来表示。char 占用 1 个字节,可表示所有的 ASCII 码,每一个数字其实就是一个 ASCII 码表中的一个字符。
控制字符
十进制 | 字符 | CTRL | 代码 | 字符解释 |
---|---|---|---|---|
0 | NUL | ^@ | \x00 | 空字符 |
1 | SOH | ^A | \x01 | 标题开始 |
2 | STX | ^B | \x02 | 正文开始 |
3 | ETX | ^C | \x03 | 正文结束 |
4 | EOT | ^D | \x04 | 传输结束 |
5 | ENQ | ^E | \x05 | 询问 |
6 | ACK | ^F | \x06 | 确认 |
7 | BEL | ^G | \x07 | 响铃 |
8 | BS | ^H | \x08 | 退格 |
9 | HT | ^I | \x09 | 水平制表符 |
10 | LF | ^J | \x0A | 换行 |
11 | VT | ^K | \x0B | 垂直制表符 |
12 | FF | ^L | \x0C | 换页 |
13 | CR | ^M | \x0D | 回车 |
14 | SO | ^N | \x0E | 移动到活动的输出 |
15 | SI | ^O | \x0F | 移动到活动输入 |
16 | DLE | ^P | \x10 | 数据链路转义 |
17 | DC1 | ^Q | \x11 | 设备控制1 |
18 | DC2 | ^R | \x12 | 设备控制2 |
19 | DC3 | ^S | \x13 | 设备控制3 |
20 | DC4 | ^T | \x14 | 设备控制4 |
21 | NAK | ^U | \x15 | 否定应答 |
22 | SYN | ^V | \x16 | 同步空闲 |
23 | ETB | ^W | \x17 | 结束传输块 |
24 | CAN | ^X | \x18 | 取消 |
25 | EM | ^Y | \x19 | 结束介质访问 |
26 | SUB | ^Z | \x1A | 替代 |
27 | ESC | ^[ | \x1B | 转义 |
28 | FS | ^\ | \x1C | 文件分隔符 |
29 | GS | ^] | \x1D | 组分隔符 |
30 | RS | ^^ | \x1E | 记录分隔符 |
31 | US | ^_ | \x1F | 单元分隔符 |
打印字符
数字 32–126 分配给了能在键盘上找到的字符,当您查看或打印文档时就会出现。数字 127 代表 DELETE 命令。
十进制 | 字符 | CTRL | 代码 | 字符解释 |
---|---|---|---|---|
32 | \x20 | 空格 | ||
33 | ! | ^! | \x21 | 感叹号 |
34 | " | ^" | \x22 | 双引号 |
35 | # | ^# | \x23 | 井号 |
36 | $ | ^$ | \x24 | 美元符号 |
37 | % | ^% | \x25 | 百分号 |
38 | & | ^& | \x26 | 与 |
39 | ' | ^' | \x27 | 单引号 |
40 | ( | ^( | \x28 | 左圆括号 |
41 | ) | ^) | \x29 | 右圆括号 |
42 | * | ^* | \x2A | 星号 |
43 | + | ^+ | \x2B | 加号 |
44 | , | ^, | \x2C | 逗号 |
45 | - | ^- | \x2D | 减号 |
46 | . | ^. | \x2E | 句点 |
47 | / | ^/ | \x2F | 斜杠 |
48-57 | 0-9 | 数字0-9 | ||
58 | : | ^: | \x3A | 冒号 |
59 | ; | ^; | \x3B | 分号 |
60 | < | ^< | \x3C | 小于号 |
61 | = | ^= | \x3D | 等号 |
62 | > | ^> | \x3E | 大于号 |
63 | ? | ^? | \x3F | 问号 |
64 | @ | ^@ | \x40 | 电子邮件符号 |
65-90 | A-Z | 大写字母A-Z | ||
91 | [ | ^[ | \x5B | 左方括号 |
92 | \ | ^\ | \x5C | 反斜线 |
93 | ] | ^] | \x5D | 右方括号 |
94 | ^ | ^^ | \x5E | 插入符号 |
95 | _ | ^_ | \x5F | 下划线 |
96 | ` | ^` | \x60 | 反引号 |
97-122 | a-z | 小写字母a-z | ||
123 | { | ^{ | \x7B | 左花括号 |
124 | | | ^| | \x7C | 竖线 |
125 | } | ^} | \x7D | 右花括号 |
126 | ~ | ^~ | \x7E | 波浪号 |
127 | DEL | ^? | \x7F | 删除符号 |
表中只有运算符号,数字,大小写字母等,不存在中文和其他语言的字符,也没有中文的标点符号。
转义ASCII字符
某些无法显示的字符如换行、退格、换页、响铃等需要使用转义字符来表示。以下是需要进行转义的ASCII字符列表,包含转义字符、含义和对应的ASCII码值:
转义序列 | 含义 | 十进制值 | ASCII码值 |
---|---|---|---|
\0 | 空字符 | 0 | 0 |
\a | 响铃符 | 7 | 7 |
\b | 退格符 | 8 | 8 |
\t | 制表符 | 9 | 9 |
\n | 换行符 | 10 | 10 |
\v | 垂直制表符 | 11 | 11 |
\f | 换页符 | 12 | 12 |
\r | 回车符 | 13 | 13 |
\" | 双引号 | 34 | 34 |
\' | 单引号 | 39 | 39 |
\? | 问号 | 63 | 63 |
\\ | 反斜杠 | 92 | 92 |
\nnn | 八进制数 | 可变 | 可变 |
\xhh | 十六进制数 | 可变 | 可变 |
转义序列 \nnn 和 \xhh 的十进制值是可变的,取决于所指定的八进制数或十六进制数。ASCII码值是转义序列对应的ASCII字符的码值。
2 变量
以上的数据类型如何使用,这就用到了变量,变量可以存储不同的数据类型,使用变量必须先要声明,声明时需要指定要存储的数据类型
变量类型 变量名称 = 初始值; // 初始值可以不指定
int age = 18;
变量的名称需要符合以下规则:
- 不可重复使用其他已经定义过的变量名称
- 智能包含英文字母、数字、下划线、严格区分大小写
- 不能以数字开头
- 不能是关键字
建议使用有意义的英文单词,不要使用拼音,风格符合驼峰格式或者下划线连接格式进行命名。
现在我们来计算两个数值相加,程序如下
#include <stdio.h>
int main() {
int a = 10;
int b = 10;
int c = a + b;
printf("c is %d", c);
return 0;
}
可以能看到打印出的结果 20,这里需要使用到 printf 函数进行格式化 %d 输出 c 的值,直接打印 c 是看不到值的,那么除了数字意外的其他类型的数据如何打印呢?
格式化参数打印
格式化控制符 | 说明 |
---|---|
%hd 、%d 、%ld | 用于打印有符号、 十进制的short、int、long整数 |
%hu 、%u 、%lu | 用于打印无符号、 十进制的short、int、long整数 |
%ho 、%o 、%lo | 用于打印无符号、不带前缀、八进制的short、int、long整数 |
%#ho 、%#o 、%#lo | 用于打印无符号、 带前缀、八进制的short、int、long整数 |
%hx 、%x 、%lx | 用于打印无符号、不带前缀、十六进制的short、int、long整数 |
%hX 、%X 、%lX | 用于打印无符号、不带前缀、十六进制的short、int、long整数,X大写 |
%#hx 、%#x 、%#lx | 用于打印无符号、 带前缀、十六进制的short、int、long整数 |
%#hX 、%#X 、%#lX | 用于打印无符号、 带前缀、十六进制的short、int、long整数,X大写 |
%e 、%le | 用科学计数法打印浮点数float、double,e小写 |
%E 、%lE | 用科学计数法打印浮点数float、double,e大写 |
%f 、%lf | 用十进制表示法打印浮点数float、double |
%g 、%lg 、 | 用于打印浮点数,并根据具体情况选择以定点表示法或科学计数法来显示。打印时会自动切换为最短的方式,避免不必要的零和小数点。当指数小于-4或大于等于精度(默认为6)时,会使用科学计数法。如果浮点数的小数部分全为0,则会省略小数部分。 |
%G 、%lG | 同上,科学计数法显示时,E大写 |
%.xG 、%.xlG | 同上,.x 中的 x 表示要自定义的保留精度位数 |
%s | 用于打印字符串 |
%c | 用于打印单个字符 |
%% | 用于打印百分号 |
变量定义字符类型
char a = 'a';
printf("a:%d", a); // a: 97
3 常量
定义常量和变量基本类似,多了一个 const 关键字,格式如下
const double PI = 3.14;
4 无符号数
计算机底层使用二进制保存数据,第一位是符号位,如果不考虑符号位,那么所有的数都是按照正数表示,比如 char 的范围从原来的 -128 ~ 127 变成了 0 ~ 255。
表示无符号的数据,可以使用无符号关键字 unsigned
int main() {
unsigned char c = -65;
printf("%u", c);
}
// 191
why? 我们首先明确下信:char 占用 1 个字节,也就是 8 个 bit 位,那么 -65 的补码形式是 原码(11000001) -> 反码(10111110) --> 补码(10111111),由于无符号关键字加持,导致现在无符号位,那么现在就是一个整数,计算后得出 10111111 = 128 + 32 + 16 + 8 + 4 + 2 + 1 = 191。
5 类型转换
类型转换:一种类型的数据可以转换为其他类型的数据,类型转换分为自动类型转换和强制类型转换。
5.1 自动类型转换
自动类型转换就是编译器隐式地进行的数据类型转换,比如 char 转换成 int, int 转换成 long
short a = 10;
int b = a;
也可以把 int 转换成 char,我们来看看打印结果
#include <stdio.h>
int main() {
int a = 128;
char b = a;
printf("%d", b);
}
// -128
why? 还是根据上面的无符号数的推断方式我们来逐步梳理下。
int 类型占用 4 个字节,32 个 bit 位,那么 128 的补码就是 00000000 00000000 00000000 10000000,此时将 int 转换成 char, 那么 char 只占用 1 个字节即 8 个 bit 位,发生截断,导致新的 char 类型的变量 b 的补码变为了 10000000, 计算得出:-(2^7) = -128。
整数转换成小数,小数位补 0 即可
#include <stdio.h>
int main() {
int a = 100;
double b = a;
printf("%f", b);
}
// 100.000000
小数变整数,发生截断,小数点及后面的小数直接被丢弃
#include <stdio.h>
int main() {
double b = 3.14;
int a = b;
printf("%d", a);
}
// 3
除了赋值操作会发生自动类型转换,运算也会发生转换
#include <stdio.h>
int main() {
float a = 2;
int b = 3;
double c = b / a;
printf("%f", c);
}
// 1.500000
那么转换的规则是怎样的?
char --->
|---> int ---> unsigned int ---> long ---> double <--- float
short --->
- char 和 short 参与计算时,先转换成 int 后再计算
- 浮点类型默认按照双精度进行计算,float 会首先转换成 double 后再计算
- 低优先级和高优先级混合运算,会统一转换成高优先级运算
5.2 强制类型转换
强制类型转换,即手动转换,转换格式如下
(强制转换类型) 变量、常量或表达式;
比如我们将小数强制转换成整数
#include <stdio.h>
int main() {
int a = (int) 3.14;
printf("a is %d", a);
}
// a is 3
在某些计算场景下,强制转换会显得很有用
#include <stdio.h>
int main() {
int a = 10, b = 4;
double c = a / b; // 先计算出结果 2,再转换类型。其实可以看做是 (double) (a / b)
double d = (double) a / b; // 先将 a 转换成 double, 然后 double a 和 int b 运算,int b 会被转换成 double b
printf("c: %f, d: %f", c, d);
}
// c: 2.000000, d: 2.5000000