Skip to content

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代码字符解释
0NUL^@\x00空字符
1SOH^A\x01标题开始
2STX^B\x02正文开始
3ETX^C\x03正文结束
4EOT^D\x04传输结束
5ENQ^E\x05询问
6ACK^F\x06确认
7BEL^G\x07响铃
8BS^H\x08退格
9HT^I\x09水平制表符
10LF^J\x0A换行
11VT^K\x0B垂直制表符
12FF^L\x0C换页
13CR^M\x0D回车
14SO^N\x0E移动到活动的输出
15SI^O\x0F移动到活动输入
16DLE^P\x10数据链路转义
17DC1^Q\x11设备控制1
18DC2^R\x12设备控制2
19DC3^S\x13设备控制3
20DC4^T\x14设备控制4
21NAK^U\x15否定应答
22SYN^V\x16同步空闲
23ETB^W\x17结束传输块
24CAN^X\x18取消
25EM^Y\x19结束介质访问
26SUB^Z\x1A替代
27ESC^[\x1B转义
28FS^\\x1C文件分隔符
29GS^]\x1D组分隔符
30RS^^\x1E记录分隔符
31US^_\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-570-9数字0-9
58:^:\x3A冒号
59;^;\x3B分号
60<^<\x3C小于号
61=^=\x3D等号
62>^>\x3E大于号
63?^?\x3F问号
64@^@\x40电子邮件符号
65-90A-Z大写字母A-Z
91[^[\x5B左方括号
92\^\\x5C反斜线
93]^]\x5D右方括号
94^^^\x5E插入符号
95_^_\x5F下划线
96`^`\x60反引号
97-122a-z小写字母a-z
123{^{\x7B左花括号
124|^|\x7C竖线
125}^}\x7D右花括号
126~^~\x7E波浪号
127DEL^?\x7F删除符号

表中只有运算符号,数字,大小写字母等,不存在中文和其他语言的字符,也没有中文的标点符号。

转义ASCII字符

某些无法显示的字符如换行、退格、换页、响铃等需要使用转义字符来表示。以下是需要进行转义的ASCII字符列表,包含转义字符、含义和对应的ASCII码值:

转义序列含义十进制值ASCII码值
\0空字符00
\a响铃符77
\b退格符88
\t制表符99
\n换行符1010
\v垂直制表符1111
\f换页符1212
\r回车符1313
\"双引号3434
\'单引号3939
\?问号6363
\\反斜杠9292
\nnn八进制数可变可变
\xhh十六进制数可变可变

转义序列 \nnn 和 \xhh 的十进制值是可变的,取决于所指定的八进制数或十六进制数。ASCII码值是转义序列对应的ASCII字符的码值。

2 变量

以上的数据类型如何使用,这就用到了变量,变量可以存储不同的数据类型,使用变量必须先要声明,声明时需要指定要存储的数据类型

c
变量类型 变量名称 = 初始值; // 初始值可以不指定
int age = 18;

变量的名称需要符合以下规则:

  • 不可重复使用其他已经定义过的变量名称
  • 智能包含英文字母、数字、下划线、严格区分大小写
  • 不能以数字开头
  • 不能是关键字

建议使用有意义的英文单词,不要使用拼音,风格符合驼峰格式或者下划线连接格式进行命名。

现在我们来计算两个数值相加,程序如下

c
#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用于打印单个字符
%%用于打印百分号

变量定义字符类型

c
char a = 'a';
printf("a:%d", a); // a: 97

3 常量

定义常量和变量基本类似,多了一个 const 关键字,格式如下

c
const double PI = 3.14;

4 无符号数

计算机底层使用二进制保存数据,第一位是符号位,如果不考虑符号位,那么所有的数都是按照正数表示,比如 char 的范围从原来的 -128 ~ 127 变成了 0 ~ 255。

表示无符号的数据,可以使用无符号关键字 unsigned

c
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

c
short a = 10;
int b = a;

也可以把 int 转换成 char,我们来看看打印结果

c
#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 即可

c
#include <stdio.h>

int main() {
    int a = 100;
    double b = a;
    printf("%f", b);
}

// 100.000000

小数变整数,发生截断,小数点及后面的小数直接被丢弃

c
#include <stdio.h>

int main() {
    double b = 3.14;
    int a = b;
    printf("%d", a);
}

// 3

除了赋值操作会发生自动类型转换,运算也会发生转换

c
#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 强制类型转换

强制类型转换,即手动转换,转换格式如下

c
(强制转换类型) 变量、常量或表达式;

比如我们将小数强制转换成整数

c
#include <stdio.h>

int main() {
 int a = (int) 3.14;
 printf("a is %d", a);
}

// a is 3

在某些计算场景下,强制转换会显得很有用

c
#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

Released under the MIT License.