目录
1 数据类型的分类
2 数据存储单位
2.1 位
2.2 字节
2.3 其余单位
3 整数类型
3.1 基本介绍
3.2 整型的类型
3.2.1 整数类型多样性的原因
3.2.2 整型类型之间的相对大小关系
3.3 整型注意事项
3.4 字面量后缀
3.5 格式占位符
3.6 案例:声明并输出多种字面量整型数据
3.7 案例:printf 输出 scanf 输入的多种整型数据
3.8 精确宽度类型
4 浮点类型
4.1 基本介绍
4.2 浮点数的类型
4.3 浮点型注意事项
4.4 浮点型数据的两种表现形式
4.4.1 科学计数法
4.5 字面量后缀
4.6 格式占位符
4.7 案例:声明并输出多种字面量浮点型数据
4 字符类型
4.1 基本介绍
4.2 转义字符
4.3 格式占位符
4.4 字符类型本质
4.5 ASCII 码
4.6 案例:字符型数据的输出、计算与溢出
4.7 案例:字母大小写转换
5 布尔类型
5.1 基本介绍
5.2 声明布尔类型的三种方式
5.2.1 宏定义
5.2.2 _Bool 类型
5.2.3 bool 类型
6 sizeof 获取数据的存储大小
6.1 注意事项
6.2 查看基本数据类型、字面量和变量的大小
7 测试题
1 数据类型的分类
程序的主要目的就是对数据进行计算和处理,为了方便数据的管理,通常会对数据进行分类。就像根据容器的容量和形状不同,可以分成大杯中杯、大碗小碗等,不同的容器适合装不同的东西。
C 语言根据数据的特性和用途进行了类型划分,不同类型的数据在内存中占据不同大小的空间。
注意:字符型 char 也其实可以划分到【基本类型 - 整型】里边。
2 数据存储单位
2.1 位
bit(b):位,计算机中的最小存储单位,表示一个二进制位。
2.2 字节
Byte(B):字节,计算机中基本存储单元,1 Byte = 8 bit。
2.3 其余单位
Kilobyte(KB):千字节,是字节的更大单位。1 KB = 1024 Bytes(注意,在计算机科学中,KB、MB、GB 等通常使用 2 的幂次方作为定义,即 1024,而不是传统的 1000)。KB 常用于表示文件大小、内存使用量等。
Megabyte(MB):兆字节,是更大的数据存储单位。1 MB = 1024 KB。MB 常用于描述较大的文件大小,如软件安装包、音乐文件、视频文件等。
Gigabyte(GB):吉字节,是更大的数据存储单位。1 GB = 1024 MB。GB 常用于描述硬盘驱动器、固态硬盘、USB 闪存盘等的存储容量,以及大型软件、高清视频等的文件大小。
Terabyte(TB):太字节,是更大的数据存储单位。1 TB = 1024 GB。TB 级别的存储通常用于企业级存储解决方案、数据中心、大型数据库以及个人用户的大量数据备份等。
Petabyte(PB):拍字节,是更大的数据存储单位。1 PB = 1024 TB。PB 级别的存储通常用于超大规模的数据中心、云计算平台、大数据分析等领域。
Exabyte(EB):艾字节,是更大的数据存储单位。1 EB = 1024 PB。EB 级别的存储主要用于超大规模的数据存储需求,如全球互联网数据的存储、大型科研项目的数据存储等。
3 整数类型
3.1 基本介绍
整数类型简称整型,就是用于存放整数值的,比如:12,345,666,888,999 等。
3.2 整型的类型
类型 | 存储大小 | 值范围 |
---|---|---|
short signed short | 2 字节 | -32,768 (-2^15) 到 32,767 (2^15 - 1) |
unsigned short | 2 字节 | 0 到 65,535 (2^16 - 1) |
int signed int | 16 位:2 字节 32 位:4 字节 | 16 位:-32,768 (- 2^15 ) 到 32,767 (2^15-1) 32 位:-2,147,483,648 (- 2^31) 到 2,147,483,647 (2^31 -1) |
unsigned unsigned int | 16 位:2 字节 32 位:4 字节 | 16 位:0 到 65,535 (2^16-1) 32 位:0 到 4,294,967,295 (2^32 -1) |
long signed long | 32 位:4 字节 64 位:8 字节 (一般同 int 一样的大小) | 32 位:-2,147,483,648 (- 2^31) 到 2,147,483,647 (2^31 - 1) 64 位:9223372036854775808(-2^63) 到9223372036854775807 (2^63-1) |
unsigned long | 4 字节 或 8 字节 | 32 位:0 到 4,294,967,295 (2^32 - 1) 64 位:0 ~ 18446744073709551615 (2^64 - 1) |
long long signed long long | 8 字节 | 9223372036854775808(-2^63) 到 9223372036854775807 (2^63-1) |
unsigned long long | 8 字节 | 0 ~ 18446744073709551615 (2^64 - 1) |
3.2.1 整数类型多样性的原因
内存效率:
不同的数据类型占用不同的内存空间。例如,short 和 unsigned short 类型仅占用 2 字节,比 int 类型(通常是 4 字节)节省一倍的空间。在资源受限的系统中,比如嵌入式设备,使用较小的数据类型可以有效减少内存消耗。
性能优化:
处理器通常针对不同大小的数据类型进行了优化。使用与处理器寄存器大小相匹配的数据类型可以提高代码的执行效率。例如,在 32 位系统上使用 int 或 unsigned int 可能比使用 long 更快,因为 int 的大小与寄存器大小一致。
数值范围:
不同的数据类型可以表示不同的数值范围。例如,int 类型通常可以表示从 -2,147,483,648 到 2,147,483,647 的值,而 unsigned int 类型可以表示从 0 到 4,294,967,295 的非负数。选择适当的数据类型可以确保程序能够正确地存储和处理所需范围内的数值。
跨平台兼容性:
C 语言标准规定了一些类型的基本行为,但具体实现(如 int, long, long long 的大小)可能因平台而异。例如,int 在某些系统上可能是 16 位,而在其他系统上可能是 32 位。提供多种类型有助于编写可在不同硬件和操作系统上运行的代码。
类型安全:
使用适当的数据类型有助于防止溢出和其他类型错误。例如,如果知道一个计数器永远不会超过 65535,使用 unsigned short 可以确保不会发生意外的溢出,从而增加代码的健壮性和安全性。
代码清晰性:
使用正确的数据类型可以使代码更具可读性。例如,使用 unsigned 类型来存储已知为非负的值可以清楚地表达意图,使代码更容易理解和维护。
3.2.2 整型类型之间的相对大小关系
C 语言标准定义了不同整数类型之间的相对大小关系,但并没有严格规定每个类型的精确位数,以便于在不同的硬件平台上保持灵活性和可移植性。
根据 C99 标准,整数类型的相对大小关系如下:
- char 的大小是最小的,通常为 1 字节(8位)。
- short int 或简写为 short 至少与 char 同样大,但通常比 char 大。
- int 至少与 short 同样大。
- long int 或简写为 long 至少与 int 同样大。
- long long int 或简写为 long long 至少与 long 同样大。
尽管 C 标准允许实现有一定程度的灵活性,但在 C99 标准中,它规定了某些最小值:
- short 至少是 16 位。
- int 至少是 16 位。
- long 至少是 32 位。
- long long 至少是 64 位。
要确定特定编译器和平台上的确切大小,可以使用 sizeof 运算符(后文讲解)。
提示:大多数情况下,long 的大小和 int 相同。所以如果需要小的存储空间就用 short,正常的就用 int,需要大的存储空间就用 long long。
3.3 整型注意事项
各类型存储大小受到操作系统、编译器、硬件平台的影响。
整型分为有符号 signed 和无符号 unsigned 两种,默认是 signed,signed 可省略。
开发中使用整型一般用 int 型,如果不足以表示大数,可以使用 long long。
3.4 字面量后缀
字面量是源代码中一个固定值的表示法,用于直接表示数据,如图所示:
一个整数字面量(不加后缀)默认是 int 类型。
如果需要表示 long 类型字面量,需要添加后缀 l 或 L,为了和数字 1 区分开,建议使用大写 L。
如果需要表示 long long 类型字面量,需要添加后缀 ll 或 LL,为了和数字 1 区分开,建议使用大写 L。
如果需要表示无符号整数字面量,需要添加后缀 u 或 U, 注意, u 和 l 可以结合使用,不分先后,如 ul(无符号 long 类型)、ULL(无符号 long long 类型),lu(无符号 long 类型) 等。
注意:如果一个整数字面量没有后缀,那么它默认是 int 类型,除非它的值太大以至于不能被 int 类型容纳,这时它会被视为 long 类型。
3.5 格式占位符
%hd 对应 short 类型,%hu 对应 unsigned short 类型。
%d 对应 int 类型,%u 对应 unsigned int 类型。
%ld 对应 long 类型,%lu 对应 unsigned long 类型。
%lld 对应 long long 类型,%llu 对应 unsigned long long 类型。
格式说明符 | 对应的整型类型 |
---|---|
%hd | short |
%hu | unsigned short |
%d | int |
%u | unsigned int |
%ld | long |
%lu | unsigned long |
%lld | long long |
%llu | unsigned long long |
注意:在 C99 标准之前,%lld 和 %llu 可能不被所有编译器支持,因为它们是为 long long 类型引入的,而 long long 是在 C99 中标准化的。对于早期的 C 标准,可能需要使用编译器特定的扩展或避免使用 long long 类型。
前面提到字面量的后缀 UL 也可以写成 LU,不分先后,但是在格式占位符中就不可以这样啦,要严格遵守顺序!
3.6 案例:声明并输出多种字面量整型数据
#include <stdio.h>int main()
{// short 类型short a1 = 10; // 等同于 signed short a1 = 10;signed short a2 = -10;unsigned short a3 = 20;printf("short a1=%hd; signed short a2=%hd; unsigned short a3=%hu\n", a1, a2, a3);// 输出:short a1=10; signed short a2=-10; unsigned short a3=20// int 类型int b1 = 100; // 等同于 signed int b1 = 100;signed int b2 = -100;unsigned int b3 = 200u;unsigned b4 = 300U; // 等同于 unsigned int b4 = 300U;printf("int b1=%d; signed int b2=%d; unsigned int b3=%u; unsigned b4=%u\n", b1, b2, b3, b4);// 输出:int b1=100; signed int b2=-100; unsigned int b3=200; unsigned b4=30// long 类型long c1 = 1000l; // 等同于 signed long c1 = 1000l;signed long c2 = -1000L; // 推荐大写后缀,以此和数字 1 区别unsigned long c3 = 2000UL; // 推荐大写后缀printf("long c1=%ld; signed long c2=%ld; unsigned long c3=%lu\n", c1, c2, c3);// 输出:long c1=1000; signed long c2=-1000; unsigned long c3=2000// long long 类型(C99及以后)long long d1 = 10000ll; // 等同于 signed long long d1 = 10000ll;signed long long d2 = -10000LL; // 推荐大写后缀,以此和数字 1 区别unsigned long long d3 = 20000ULL; // 推荐大写后缀printf("long long d1=%lld; signed long long d2=%lld; unsigned long long d3=%llu\n", d1, d2, d3);// 输出:long long d1=10000; signed long long d2=-10000; unsigned long long d3=20000return 0;
}
3.7 案例:printf 输出 scanf 输入的多种整型数据
#include <stdio.h>int main()
{short s;unsigned short us;int i;unsigned int ui;long l;unsigned long ul;long long ll;unsigned long long ull;printf("Enter a short integer: ");scanf("%hd", &s);printf("Enter an unsigned short integer: ");scanf("%hu", &us);printf("Enter an integer: ");scanf("%d", &i);printf("Enter an unsigned integer: ");scanf("%u", &ui);printf("Enter a long integer: ");scanf("%ld", &l);printf("Enter an unsigned long integer: ");scanf("%lu", &ul);printf("Enter a long long integer: ");scanf("%lld", &ll);printf("Enter an unsigned long long integer: ");scanf("%llu", &ull);// 打印输入的值以验证printf("Values entered:\n");printf("Short: %hd\n", s);printf("Unsigned Short: %hu\n", us);printf("Int: %d\n", i);printf("Unsigned Int: %u\n", ui);printf("Long: %ld\n", l);printf("Unsigned Long: %lu\n", ul);printf("Long Long: %lld\n", ll);printf("Unsigned Long Long: %llu\n", ull);return 0;
}
输出结果:
3.8 精确宽度类型
C 语言的整数类型(short、int、long)在不同计算机上,占用的字节宽度可能是不一样的。程序员有时需要精准的字节宽度,以提高代码的可移植性,尤其是嵌入式开发中,使用精确宽度类型可以确保代码在各种平台上的一致性。
标准库的头文件 <stdint.h> 中定义了一些新的类型别名,如下:
类型名称 | 含义 |
---|---|
int8_t | 8 位有符号整数 |
int16_t | 16 位有符号整数 |
int32_t | 32 位有符号整数 |
int64_t | 64 位有符号整数 |
uint8_t | 8 位无符号整数 |
uint16_t | 16 位无符号整数 |
uint32_t | 32 位无符号整数 |
uint64_t | 64 位无符号整数 |
上面这些都是类型别名,编译器会指定它们指向的底层类型。比如,某个系统中,如果 int 类型为 32 位, int32_t 就会指向 int ;如果 long 类型为 32 位, int32_t 则会指向 long 。
示例代码:
#include <stdio.h>
#include <stdint.h>int main()
{// 变量 x32 声明为 int32_t 类型,可以保证是 32 位(4 个字节)的宽度。int32_t x32 = 45933945;printf("x32=%d\n", x32); // x32=45933945printf("sizeof(x32)=%zu\n", sizeof(x32)); // sizeof(x32)=4printf("sizeof(int8_t)=%zu\n", sizeof(int8_t)); // sizeof(int8_t)=1printf("sizeof(int16_t)=%zu\n", sizeof(int16_t)); // sizeof(int16_t)=2printf("sizeof(int32_t)=%zu\n", sizeof(int32_t)); // sizeof(int32_t)=4printf("sizeof(int64_t)=%zu\n", sizeof(int64_t)); // sizeof(int64_t)=8printf("sizeof(uint8_t)=%zu\n", sizeof(uint8_t)); // sizeof(uint8_t)=1printf("sizeof(uint16_t)=%zu\n", sizeof(uint16_t)); // sizeof(uint16_t)=2printf("sizeof(uint32_t)=%zu\n", sizeof(uint32_t)); // sizeof(uint32_t)=4printf("sizeof(uint64_t)=%zu\n", sizeof(uint64_t)); // sizeof(uint64_t)=8return 0;
}
我们可以通过按住【ctrl 键 + 鼠标左键单击】的方式查看头文件的源代码:
头文件源代码中如下所示:
可以看出:这段代码定义了一系列类型别名,用于表示不同位宽的整数类型。这些别名在跨平台编程中特别有用,因为它们提供了一种标准化的方式来指定数据类型的位宽,而不需要担心不同平台(如 32 位和 64 位系统)上基本数据类型(如 int、long 等)的位宽可能不同的问题。
4 浮点类型
4.1 基本介绍
浮点类型可以表示一个小数,比如:123.4,7.8,0.12 等。
4.2 浮点数的类型
类型 | 存储大小 | 值范围 | 有效小数位数 |
---|---|---|---|
float 单精度 | 4 字节 | 负数:-3.4E+38 到 -1.18E-38 正数:1.18E-38 到 3.4E+38 | 6 ~ 9 |
double 双精度 | 8 字节 | 负数:-1.8E+308 到 2.23E-308 正数:2.23E-308 到 1.8E+308 | 15 ~18 |
long double 长双精度 | 32 位:10字节 64 位:16字节 | 32位:与 double 相同或更大 64位: 负数:-1.2E+4932 到 -3.4E-4932 正数:3.4E-4932 到 1.2E+4932 | 18 或更多 |
4.3 浮点型注意事项
各类型的存储大小和精度受到操作系统、编译器、硬件平台的影响。
浮点数(如 float 和 double 类型)没有符号(unsigned / signed)之分。它们可以表示正数、负数和零。浮点数类型的设计初衷就是为了能够存储和表示带有小数部分的数值,而这些数值可以是正的、负的或者零。所以不需要(也不能)在声明浮点数类型的变量时使用 signed 或 unsigned 关键字,不然编译器会报错。
4.4 浮点型数据的两种表现形式
十进制数形式:如:5.12、512.0f、.512(0.512, 可以省略 0)
科学计数法形式:如:5.12e2、5.12E-2
开发中用到浮点型数字,建议使用 double 型,如精度要求更高可以使用 long double 型。
4.4.1 科学计数法
小数的指数形式(也称为科学记数法)是一种用于表示非常大或非常小的浮点数的方法。科学记数法通过将一个浮点数分解为基数(mantissa,也称为尾数或有效数字)和指数(exponent)两部分来表示。
基数部分:
是一个非零的数,可以是整数或小数,但不能为空(至少包含一位有效数字)。
指数部分:
指数部分由字符 e(或 E,不区分大小写)后跟一个整数组成,用于表示基数需要乘以 10 的多少次方来得到原始数值。
指数可以是正数、负数或零。正数指数表示基数需要向右移动小数点相应位数,负数指数表示基数需要向左移动小数点相应位数。当指数为正时,前面的加号可以省略;当指数为负时,前面的减号必须明确写出。
总结:
字母 e(或 E) 之前必须有数字,且 e(或 E)后面的指数必须为整数。
正确示例:
- 1e-3 表示 1×10 的 -3 次方,即 0.001
- 1.8e-3 表示 1.8×10 的 −3 次方
- -123e-6 表示 −123×10 的 −6 次方
- -.1e-3 表示 −0.1×10 的 −3 次方,也可以写成 -1e-4
错误示例及原因:
- e3:错误,因为 e 前没有数字。
- 2.1e3.5:错误,因为 e 后面的指数的是小数。
- .e3:错误,因为没有有效的数字在 e 之前。
- e:错误,没有有效的基数和指数。
4.5 字面量后缀
浮点数字面量(不加后缀)默认是 double 型。
如果需要表示 float 类型字面量,须加后缀 f 或 F。
如果需要表示 long double 类型字面量,需加后缀 l 或 L,推荐大写 L 以此和数字 1 区分。
4.6 格式占位符
在 C 语言中,占位符是一种用于格式化输出的特殊字符,通常用于 printf() 等输出函数中,用于指定输出的格式和内容。
%f 是浮点类型的格式占位符,在 printf 中对应 double 类型(float 类型会转换成 double 来处理);默认会保留 6 位小数,可以指定小数位数,如:%.2f 表示保留 2 位小数。
%lf 在 printf 中和 %f 意思相同(C99 标准加入),也对应 double 类型,默认保留 6 位小数,可以指定小数位数,如:%.2lf 表示保留 2 位小数。但需要注意的是,在 scanf 中 %lf 和 %f 含义不同:输入一个 float类型数据时使用 %f;而输入 double 类型时必须使用 %lf。
%Lf 对应的是 long double 类型,默认保留 6 位小数,可以指定小数位数,如: %.2Lf 表示保留 2 位小数。需要注意的是,输入输出 long double 类型都必须使用 %Lf 占位符。
%e 或 %E 对应科学计数法表示的浮点数,可以指定尾数部分所保留的小数位数,如 %.2e 表示尾数部分保留 2 位小数。
提示:对于 double 类型变量,习惯上用 %f 进行输出,这样相比 %lf 少打一个字符。
占位符 | 类型 | 用途 | 备注 |
---|---|---|---|
%f | double(或 float) | 在 printf 中用于输出 double 或 float 类型, float 会被提升为 double 处理,默认保留 6 位小数 | 在 scanf 中用于输入 float 类型 |
%lf | double | 在 printf 中与 %f 相同,用于输出 double 类型 | 在 scanf 中必须用于输入 double 类型 |
%Lf | long double | 用于输出和输入 long double 类型,默认保留 6 位小数 | 无论输入输出,long double 类型都必须使用 %Lf |
%e 或 %E | - | 用于输出科学计数法表示的浮点数,默认尾数保留 6 位小数 | 可以指定尾数部分的小数位数,如 %.2e |
注意:
- %f 和 %lf 在 printf 函数中可互换使用,因为 printf 会自动处理 float 到 double 的提升。但在 scanf 中,它们的使用是有区别的。
- %Lf 是专门用于 long double 类型的,无论是输出还是输入。
- %e 和 %E 用于输出科学计数法表示的浮点数,其中 %E 使用大写字母 E 来表示指数部分。
- 默认情况下,这些占位符都只保留 6 位小数,但可以通过在 % 和 f、lf、Lf、e 或 E 之间添加 . 和数字来指定小数位数,这样得到的结果是四舍五入的形式。
- 有的编译器可能需要使用 %LE 或 %Le 以科学记数法输出,但这不是 C 标准规定的,慎用!
4.7 案例:声明并输出多种字面量浮点型数据
#include <stdio.h>int main()
{// double 类型// 初始化double类型的变量double a1 = 3.1415; // 标准的 double 类型浮点数double a2 = .12345678; // 省略整数部分(0)的 double 类型浮点数double a3 = -2e12; // 使用科学记数法表示的 double 类型浮点数double a4 = 1.9823e2; // 另一个科学记数法表示的 double 类型浮点数// 使用 %f 和 %lf(尽管 %f 通常也用于 double)格式化输出// 习惯上就用 %f 输出 double类型,这样相比 %lf 少打一个字符printf("a1=%f, a2=%.10f, a3=%.2lf, a4=%lf\n", a1, a2, a3, a4);// a1=3.141500, a2=0.1234567800, a3=-2000000000000.00, a4=198.230000// 使用 %e 以科学记数法输出printf("a1=%e, a2=%.2e, a3=%e, a4=%e\n", a1, a2, a3, a4);// a1=3.141500e+00, a2=1.23e-01, a3=-2.000000e+12, a4=1.982300e+02// float 类型// 初始化 float 类型的变量,注意 f/F 后缀表示 float 字面量float f = 3.1415; // 不加后缀也行,隐式转换 double -> floatfloat b1 = 3.1415f; // float 类型的浮点数float b2 = .123456f; // 省略整数部分(0)的 float 类型浮点数float b3 = -2e12F; // 使用科学记数法表示的 float 类型浮点数,注意精度损失float b4 = 1.9823e2F; // 另一个科学记数法表示的 float 类型浮点数// 使用 %f 格式化输出printf("b1=%f, b2=%f, b3=.0%f, b4=%f\n", b1, b2, b3, b4);// b1=3.141500, b2=0.123456, b3=-1999999991808, b4=198.229996// 使用 %e 以科学记数法输出printf("b1=%e, b2=%.2e, b3=%e, b4=%e\n", b1, b2, b3, b4);// b1=3.141500e+00, b2=1.23e-01, b3=-2.000000e+12, b4=1.982300e+02// long double 类型// 初始化 long double 类型的变量,注意 l/L 后缀表示 long double 字面量long double c1 = 3.14159265358979323846L; // long double 类型的浮点数,使用 L 后缀(如果编译器支持)long double c2 = .12345678901234567890L; // 省略整数部分(0)的 long double 类型浮点数long double c3 = -2e12L; // 使用科学记数法表示的 long double 类型浮点数long double c4 = 1.9823e2L; // 另一个科学记数法表示的 long double 类型浮点数// 注意:不是所有编译器都支持 L 后缀用于浮点数字面量,这里使用 L 主要是为了说明意图// 使用 %Lf 格式化输出 long doubleprintf("c1=%Lf, c2=%.20Lf, c3=%.2Lf, c4=%Lf\n", c1, c2, c3, c4);// c1=3.141593, c2=0.12345678901234567890, c3=-2000000000000.00, c4=198.230000// 使用 %Le 或 %LE 以科学记数法输出 long doubleprintf("c1=%LE, c2=%.2LE, c3=%Le, c4=%Le\n", c1, c2, c3, c4);// c1=3.141593E+00, c2=1.23E-01, c3=-2.000000e+12, c4=1.982300e+02return 0;
}
4 字符类型
4.1 基本介绍
字符类型 char 可以表示单个字符,如一个数字、一个字母、一个符号。
char 类型的字面量是用单引号括起来的单个字符。
多个字符称为字符串,在 C 语言中使用 char 数组表示,数组不是基本数据类型,而是构造类型,我们后续专门讲解。
4.2 转义字符
可以使用转义字符 \(反斜杠) 表示特殊含义的字符。
转义字符 | 说明 |
---|---|
\b | 退格 |
\n | 换行符 |
\r | 回车符 |
\t | 制表符 |
\" | 双引号 |
\' | 单引号 |
\\ | 反斜杠 |
如下代码所示:
#include <stdio.h>int main()
{// 使用 \b 退格符printf("Hello\b \bWorld!\n"); // 注意这里使用了两个 \b,第一个 \b 删除了'o',第二个 \b 删除了空格,所以输出是 HellWorld!// 使用 \n 换行符printf("这是第一行\n这是第二行\n");// 使用 \r 回车符(注意:在早期的系统中,\r\n 通常一起使用表示换行,但这里仅展示 \r 的效果)printf("使用\r将光标移回行首: Hello World!\n"); // 这将输出"将光标移回行首: Hello World!",因为 \r 将光标移回行首,覆盖了“使用”// 使用 \t 制表符printf("姓名\t年龄\t职业\n"); // 姓名 年龄 职业printf("张三\t30\t程序员\n"); // 张三 30 程序员// 使用 \" 双引号printf("他说:\"你好,世界!\"\n"); // 他说:"你好,世界!"// 直接在双引号里面输入单引号也是可以的printf("字符'A'的ASCII码是65\n"); // 字符'A'的 ASCII 是 65// 使用 \' 单引号printf("字符\'A\'的ASCII码是65\n"); // 字符'A'的 ASCII 码是 65// 使用 \\ 反斜杠printf("文件路径是C:\\Users\\Public\\Documents\\file.txt\n"); // 文件路径是C:\Users\Public\Documents\file.txtreturn 0;
}
输出结果:
4.3 格式占位符
使用 %c 表示 char 类型,输出字符。使用 %d 输出对应的 ASCII 码值。
4.4 字符类型本质
C 语言中,char 类型本质是一个整数,是 ASCII 码中对应的数字,存储长度是 1 个字节, char 类型也可以进行数学运算(计算时当做整型计算)。
字符型同样分为 signed char(无符号)和 unsigned char(有符号),其中 signed char 取值范围 -128 ~ 127,unsigned char 取值范围 0 ~ 255。默认是否带符号取决于当前运行环境,或者一开始就显示的声明符号。
字符型数据在计算中存储和读取的过程:
4.5 ASCII 码
ASCII(American Standard Code for Information Interchange)码是一种用于表示文本字符的字符编码标准,一共规定了128 个字符的编码,比如常见的空格 “SPACE” 是 32(二进制 0010 0000),数字 0 是 48(二进制 0011 0000)大写的字母 A 是 65(二进制 0100 0001),大写的字母 a 是 97(二进制 0110 0001)。
4.6 案例:字符型数据的输出、计算与溢出
#include <stdio.h>int main()
{// char 类型字面量需要使用单引号包裹// 定义并初始化三个 char 类型的变量,分别存储字符'A'、'9'和制表符'\t'char a1 = 'A';char a2 = '9';char a3 = '\t';printf("a1=%c, a3=%c, a2=%c \n", a1, a3, a2); // 输出:a1=A, a3= , a2=9// char 类型本质上整数可以进行运算// 定义并初始化两个 char 类型的变量,一个存储字符 'b',另一个存储整数 101(ASCII 码为'e')char b1 = 'b';char b2 = 101;// 输出 b1 的字符形式和整数值printf("%c->%d \n", b1, b1); // 输出:b->98// 输出 b2 的字符形式和整数值printf("%c->%d \n", b2, b2); // 输出:e->101// 输出 b1 和 b2 的字符相加后的整数值('b' + 'e' = 98 + 101 = 199)printf("%c+%c=%d \n", b1, b2, b1 + b2); // 输出:b+e=199// char 类型取值范围// 定义并初始化三个 char 类型的变量,分别测试无符号、有符号 char 以及默认 char(通常是有符号)unsigned char c1 = 200; // 无符号 char 取值范围 0 ~255,200 是有效值signed char c2 = 200; // 有符号 char 取值范围 -128~127,200 会溢出,但这里不会报错,只是值会被截断或按二进制方式表示char c3 = 200; // 当前系统,char 默认是 signed char,同样会溢出// 输出 c1, c2, c3 的值,注意它们都是按整数形式输出的printf("c1=%d, c2=%d, c3=%d", c1, c2, c3); // 输出:c1=200, c2=-56, c3=-56// 底层原理还是补码// 200 的 32 位二进制是(0000 0000 0000 0000 0000 0000 1100 1000)// 低八位 1100 1000 赋值给了 c2 和 c3// c2 和 c3 表示有符号类型:所以 1100 1000 表示一个负数的补码,即 -56return 0;
}
4.7 案例:字母大小写转换
#include <stdio.h>int main()
{char ch = 'A'; // 大写字母 Achar lowerCh = ch + 32; // 将大写转换为小写printf("Original: %c, Lowercase: %c\n", ch, lowerCh);// Original: A, Lowercase: ach = 'a'; // 小写字母 achar upperCh = ch - 32; // 将小写转换为大写printf("Original: %c, Uppercase: %c\n", ch, upperCh);// Original: a, Uppercase: A// 注意:如果 ch 不是字母,这种方法会产生非预期的字符// 例如:ch = '1'; // 数字 1char unexpected = ch - 32; // 这将产生一个非预期的字符,因为'1'的ASCII值减去32不是有效的打印字符printf("Original: %c, Unexpected: %c\n", ch, unexpected);return 0;
}
输出结果:
5 布尔类型
5.1 基本介绍
布尔值用于表示真、假两种状态,通常用于逻辑运算和条件判断。
5.2 声明布尔类型的三种方式
5.2.1 宏定义
C89 标准没有定义布尔类型,判断真假时以 0 为假,非 0 为真 ,但这种做法不直观,我们一般需要借助 C 语言的宏定义。
使用宏定义来表示布尔类型是一种在 C 语言中模拟布尔类型的常见做法,因为 C 语言标准在 C99 之前并没有原生的布尔类型。
#include <stdio.h>// 宏定义部分
// 定义 BOOL 为 int 类型,用于表示布尔值(真或假)
#define BOOL int
// 定义 TRUE 为 1,表示真
#define TRUE 1
// 定义 FALSE 为 0,表示假
#define FALSE 0int main()
{// 使用整型表示真假两种状态// int isPass = 0;// int isOk = 1;// 使用宏定义的 BOOL 类型来表示真假两种状态// 这里通过宏定义避免了直接在代码中使用 int 来表示布尔值,使得代码更易于理解和维护// 定义一个 BOOL 类型的变量 isPass,并使用 FALSE 宏初始化为假BOOL isPass = FALSE;// 定义一个 BOOL 类型的变量 isOk,并使用 TRUE 宏初始化为真BOOL isOk = TRUE;// 输出 isPass 和 isOk 的值printf("isPass=%d, isOk=%d \n", isPass, isOk); // isPass=0, isOk=1// 使用 if 语句判断 isPass 的值// 由于 isPass 被初始化为 FALSE(即0),所以这个条件不满足,不会执行大括号内的代码if (isPass){printf("Pass"); // 不会执行}// 使用 if 语句判断 isOk 的值// 由于 isOk 被初始化为 TRUE(即1),所以这个条件满足,会执行大括号内的代码if (isOk){printf("Ok"); // Ok}return 0;
}
5.2.2 _Bool 类型
C99 标准提供了_Bool 型,_Bool 仍是整数类型,但与一般整型不同的是,_Bool 变量只能赋值为 0 或 1,非 0 的值都会被存储为 1。
#include <stdio.h>int main()
{// 使用 _Bool 类型定义布尔变量// _Bool 是 C99 引入的用于布尔值的类型,其值只能是 0 或 1_Bool isPass = 0; // 初始化 isPass 为假// 尽管这里尝试将 -4 赋给 isOk,但 _Bool 类型会将其隐式转换为 1(真)// 因为在布尔上下文中,任何非 0 值都被视为真_Bool isOk = -4;// 打印 isPass 和 isOk 的值,但注意这里使用 %d 格式化字符串来打印 _Bool 类型的值// 实际上,_Bool 在大多数编译器中是以 int 的形式处理的,所以 %d 是可行的,但最好使用 %d 对应的布尔版本(如果有的话)// 然而,标准 C 并没有直接为 _Bool 提供特定的格式化字符串,所以 %d 是一个常见的做法printf("isPass=%d, isOk=%d \n", isPass, isOk); // 输出: isPass=0, isOk=1// 判断 isPass 的值// 由于 isPass 为假(0),这个条件不满足,不会执行大括号内的代码if (isPass){printf("Pass"); // 不会执行}// 判断 isOk 的值// 由于 isOk 在这里被隐式转换为真(1),这个条件满足,会执行大括号内的代码if (isOk){printf("Ok"); // ok}return 0;
}
5.2.3 bool 类型
C99 标准还提供了一个头文件 <stdbool.h> 定义了 bool 代表_Bool,true 代表 1,false 代表 0。
#include <stdio.h> // 引入标准输入输出库,用于 printf 函数
#include <stdbool.h> // 引入布尔类型库,提供 bool、true 和 false 的定义int main()
{// 使用 bool 类型定义布尔变量// bool 是 C99 及以后版本中定义的布尔类型,其值只能是 true 或 falsebool isPass = false; // 初始化 isPass 为假bool isOk = true; // 初始化 isOk 为真// 使用 printf 函数打印 isPass 和 isOk的值// 注意:虽然 bool 类型在 C 中,但 printf 的 %d 格式化字符串仍然可以工作,因为 bool 在大多数实现中是以 int 的形式处理的// 然而,为了代码的清晰性和可移植性,建议使用 stdbool.h 中定义的宏来明确转换 bool 类型到 int// 例如:printf("isPass=%d, isOk=%d \n", (int)isPass, (int)isOk);// 但这里为了简单起见,直接使用 %dprintf("isPass=%d, isOk=%d \n", isPass, isOk); // isPass=0, isOk=1// 使用 if 语句判断 isPass 的值// 由于 isPass 为假(false),这个条件不满足,不会执行大括号内的代码if (isPass){printf("Pass"); // 不会执行}// 使用 if 语句判断 isOk 的值// 由于 isOk 为真(true),这个条件满足,会执行大括号内的代码if (isOk){printf("Ok"); // Ok}// 程序正常结束return 0;
}
6 sizeof 获取数据的存储大小
使用 sizeof 可以获取数据类型或变量、字面量的存储大小,单位是字节。sizeof 返回一个 size_t 类型的无符号整数值,格式占位符是 %zu。
size_t 通常是 unsigned int 或 unsigned long 的别名,具体是哪个类型的别名,由系统和编译器决定。
6.1 注意事项
计算基本数据类型的大小,必须使用括号将数据类型关键字包裹起来。
对于字面量和变量,sizeof 运算符可以直接作用于它们,括号是可选的,可以省略括号。
提示:习惯上都加上括号,看着更直观。
6.2 查看基本数据类型、字面量和变量的大小
#include <stdio.h>int main()
{// 计算基本数据类型的大小,必须使用括号将数据类型关键字包裹起来// 使用 sizeof 运算符,并通过 %zu(无符号大小类型)格式化字符串打印结果// 注意:sizeof 运算符的结果类型为 size_t,在 printf 中使用 %zu 是 C99 及以后版本的标准printf("char:%zu \n", sizeof(char)); // char:1,表示 char 类型占用 1 个字节printf("short:%zu \n", sizeof(short)); // short:2 ,表示 short 类型占用 2 个字节printf("int:%zu \n", sizeof(int)); // int:4 ,但这也可能因编译器和平台而异printf("long:%zu \n", sizeof(long)); // long:4,但这也可能因编译器和平台而异printf("long long:%zu \n", sizeof(long long)); // long long:8 8,表示 long long 类型占用 8 个字节printf("float:%zu \n", sizeof(float)); // float:4 ,表示 float 类型占用 4 个字节printf("double:%zu \n", sizeof(double)); // double:8 ,表示 double 类型占用 8 个字节printf("long double:%zu \n", sizeof(long double)); // long double:16 ,但这也可能因编译器和平台而异printf("\n");// 计算字面量数据的大小// 对于字面量,sizeof 运算符可以直接作用于它们,括号是可选的,可以省略括号// 字符字面量(如'a')在大多数情况下被视为 int 类型(但 sizeof 会返回其实际存储所需的大小)// 整数和浮点数字面量的大小取决于它们被解释为的类型printf("%zu \n", sizeof('a')); // 输出为 4,因为字符字面量被提升为 intprintf("%zu \n", sizeof(431)); // 输出为 4,因为 431 是 int 类型的字面量printf("%zu \n", sizeof 4.31); // 输出为 8,因为 4.31 默认为 double 类型的字面量printf("\n");// 计算变量的大小// 对于变量,sizeof 运算符同样可以直接作用于它们,括号是可选的,可以省略括号char a = 'A';int b = 90;long long c = 100;double d = 10.8;printf("a: %zu \n", sizeof(a)); // 输出为1,表示 char 变量 a 占用 1 个字节printf("b: %zu \n", sizeof b); // 输出为4,表示 int 变量 b 占用 4 个字节printf("c: %zu \n", sizeof(c)); // 输出为8,表示 long long 变量 c 占用 8 个字节printf("d: %zu \n", sizeof(d)); // 输出为8,表示 double 变量 d 占用 8 个字节return 0;
}
7 测试题
1. 请将下列数据类型按照所占字节多少从小到大排序。
long long、char、int、short
【答案】char、short、int、long long
【解析】char 类型占 1 个字节,short 类型占 2 个字节,int 类型占 4 个字节,long long 类型占 8 个字节。
2. 写出下面代码的运行结果。
#include <stdio.h>
int main()
{printf("%zu", sizeof(12.5));return 0;
}
【答案】8
【解析】字面量 12.5 会被识别为 double 类型,double 类型占 8 个字节。
3. 写出下面代码的运行结果。
#include <stdio.h>
int main()
{int a = 10;_Bool b1 = -4;printf("%d", a + b1);return 0;
}
【答案】11
【解析】C99 标准提供了_Bool 型,_Bool 仍是整数类型,但与一般整型不同的是,_Bool变量只能赋值为 0 或 1,非 0 的值都会被存储为 1。