目录
1 数据存储单位
bit(b):位,计算机中的最小存储单位,表示一个二进制位。
Byte(B):字节,计算机中的基本存储单元,1 Byte = 8 bit。
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 级别的存储主要用于超大规模的数据存储需求,如全球互联网数据的存储、大型科研项目的数据存储等。
2 数据类型的分类
程序的主要目的就是对数据进行计算和处理,为了方便数据的管理,通常会对数据进行分类。就像根据容器的容量和形状不同,可以分成大杯中杯、大碗小碗等,不同的容器适合装不同的东西。
C 语言根据数据的特性和用途进行了类型划分,不同类型的数据在内存中占据不同大小的空间。
注意:
从 C99 标准开始,C 语言通过包含头文件 <stdbool.h> 引入了布尔类型 _Bool(以及宏定义 bool 作为 _Bool 的别名,以及 true 和 false 作为宏定义的两个值)。虽然 _Bool 和 bool 在 <stdbool.h> 中定义,但它们的行为类似于基本数据类型,用于表示真(非零)和假(零)。因此,从 C99 开始,可以认为布尔类型(通过 bool)是 C 语言中的一种基本数据类型(或至少是作为基本数据类型使用的)。
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 int 或 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 类型。如果这个整数的值超出了 int 类型能表示的范围,编译器会自动将其提升为 long 类型,以便能够容纳这个较大的值。
unsigned short 类型的整数字面量并没有专门的后缀来表示。通常,整数字面量的后缀是用来指定较长或特定类型的整数,如 long,long long 以及它们的无符号版本。
如果需要表示 long 类型字面量,需要添加后缀 l 或 L,为了和数字 1 区分开,建议使用大写 L。
如果需要表示 long long 类型字面量,需要添加后缀 ll 或 LL,为了和数字 1 区分开,建议使用大写 L。
如果需要表示无符号整数字面量,需要添加后缀 u 或 U, 注意, u 和 l 可以结合使用,不分先后,如 ul 或 lu 表示 unsigned long 类型、LLU 或 ULL 表示 unsigned long 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 |
注意:
对于 long long 类型的支持,在 C99 标准之前可能不是所有编译器都支持 %lld 和 %llu 这样的格式化输出占位符,因为 long long 类型是在 C99 中正式引入的。对于早期的 C 标准,可能需要使用编译器特定的扩展或避免使用 long long 类型。
在整数字面量中,后缀的顺序可以是 UL 或 LU,但在格式化占位符中必须严格遵守顺序,例如 %lld 或 %llu,不能颠倒。
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=20000
return 0;
}
输出结果如下所示:
3.7 案例:格式化输入输出多种整型数据
#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=45933945
printf("sizeof(x32)=%zu\n", sizeof(x32)); // sizeof(x32)=4
printf("sizeof(int8_t)=%zu\n", sizeof(int8_t)); // sizeof(int8_t)=1
printf("sizeof(int16_t)=%zu\n", sizeof(int16_t)); // sizeof(int16_t)=2
printf("sizeof(int32_t)=%zu\n", sizeof(int32_t)); // sizeof(int32_t)=4
printf("sizeof(int64_t)=%zu\n", sizeof(int64_t)); // sizeof(int64_t)=8
printf("sizeof(uint8_t)=%zu\n", sizeof(uint8_t)); // sizeof(uint8_t)=1
printf("sizeof(uint16_t)=%zu\n", sizeof(uint16_t)); // sizeof(uint16_t)=2
printf("sizeof(uint32_t)=%zu\n", sizeof(uint32_t)); // sizeof(uint32_t)=4
printf("sizeof(uint64_t)=%zu\n", sizeof(uint64_t)); // sizeof(uint64_t)=8
return 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 浮点型注意事项
各类型的存储大小和精度受到操作系统、编译器、硬件平台的影响。
开发中用到浮点型数字,建议使用 double 型,如精度要求更高可以使用 long double 型。
浮点数(如 float 和 double 类型)没有符号(unsigned / signed)之分。它们可以表示正数、负数和零。浮点数类型的设计初衷就是为了能够存储和表示带有小数部分的数值,而这些数值可以是正的、负的或者零。所以不需要(也不能)在声明浮点数类型的变量时使用 signed 或 unsigned 关键字,不然编译器会报错。
4.4 科学计数法
十进制数形式:如:5.12、512.0f、.512(0.512, 可以省略 0)
科学计数法形式:如:5.12e2、5.12E-2
小数的指数形式(也称为科学记数法)是一种用于表示非常大或非常小的浮点数的方法。科学记数法通过将一个浮点数分解为基数(mantissa,也称为尾数或有效数字)和指数(exponent)两部分来表示。
有效数字部分:
字符 e(或 E) 之前的数是一个非零的数,可以是整数或小数,但不能为空(至少包含一位有效数字)。
指数部分:
指数部分由字符 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 区分。
提示:
在 C 语言中,long double 和 long int 类型的字面量都可以使用 l 或 L 作为后缀。但是,编译器是如何区分这两个类型的呢?
在 C 语言中,编译器通过上下文来确定字面量的类型。具体来说:
整数类型:
如果一个整数字面量后面加上 l 或 L 后缀,它将被解释为 long int 类型。
如果一个整数字面量没有后缀,默认为 int 类型,但如果它的值超出了 int 的范围,它会被自动提升为 long int。
浮点数类型:
浮点数字面量默认为 double 类型。
如果浮点数字面量后面加上 l 或 L 后缀,它将被解释为 long double 类型。
4.6 格式占位符
%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。这样不仅减少了输入字符的数量,也更符合编程惯例。
占位符 | 作用类型 | 用途 | 备注 |
---|---|---|---|
%f | float 或 double | 在 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 | float 或 double | 用于输出科学计数法表示的 float 或 double 浮点数,默认尾数保留 6 位小数 | 可以指定尾数部分的小数位数,如 %.2e |
%le 或 %LE | long double | 用于输出科学计数法表示的 long double 浮点数,默认尾数保留 6 位小数 | 可以指定尾数部分的小数位数,如 %.2LE |
注意:
- 对于 printf,通常使用 %f 来输出 float 或 double 类型的浮点数。
- 对于 scanf,使用 %f 读取 float 类型的输入,使用 %lf 读取 double 类型的输入。
- %Lf 是专门用于 long double 类型的,无论是输出还是输入。
- %e 和 %E 或 %le 和 %LE 用于输出科学计数法表示的浮点数,其中 e/E 表示使用字母 e/E 来表示指数部分,l/L针对 long double 类型的。
- 默认情况下,这些占位符都保留 6 位小数,但可以通过在 % 和 f、lf、Lf、e 或 E 之间添加 . 和数字来指定小数位数,这样得到的结果是四舍五入的形式。
4.7 案例:格式化输出多种浮点型数据
#include <stdio.h>
int main()
{
// 1. double 类型
// 初始化double类型的变量
double a1 = 3.1415; // 标准的 double 类型浮点数
double a2 = .12345678; // 省略整数部分(0)的 double 类型浮点数
double a3 = -2e12; // 使用科学记数法表示的 double 类型浮点数
double a4 = 1.9823e2; // 另一个科学记数法表示的 double 类型浮点数
// 1.1 使用 %f 和 %lf 格式化输出
// 习惯上就用 %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
// 1.2 使用 %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
// 2.float 类型
// 初始化 float 类型的变量,注意 f/F 后缀表示 float 字面量
float f = 3.1415; // 不加后缀也行,隐式转换 double -> float
float b1 = 3.1415f; // float 类型的浮点数
float b2 = .123456f; // 省略整数部分(0)的 float 类型浮点数
float b3 = -2e4F; // 使用科学记数法表示的 float 类型浮点数,注意精度损失
float b4 = 1.9823e2F; // 另一个科学记数法表示的 float 类型浮点数
// 2. 使用 %f 或 %lf 格式化输出
printf("b1=%f, b2=%f, b3=%lf, b4=%lf\n", b1, b2, b3, b4);
// b1 = 3.141500, b2 = 0.123456, b3 = -20000.000000, b4 = 198.229996
// 2.2 使用 %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+04, b4=1.982300e+02
// 3.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 主要是为了说明意图
// 3.1 使用 %Lf 格式化输出 long double
printf("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
// 3.2 使用 %e 或 %E 以科学记数法输出 long double,可能导致不正确的输出
printf("数据可能错误:c1=%E, c2=%.2E, c3=%e, c4=%e\n", c1, c2, c3, c4);
// 数据可能错误:c1=3.934572E-312, c2=3.93E-312, c3=3.934572e-312, c4=3.934572e-312
// 3.3 使用 %Le 或 %LE 以科学记数法输出 long double
printf("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+02
return 0;
}
输出结果如下所示:
4 字符类型
4.1 介绍
字符类型 char 可以表示单个字符,如一个数字、一个字母、一个符号。
char 类型的字面量是用单引号括起来的单个字符。
多个字符称为字符串,在 C 语言中使用 char 数组表示,数组不是基本数据类型,而是构造类型,我们后续专门讲解。
4.2 转义字符
可以使用转义字符 \(反斜杠) 表示特殊含义的字符。
转义字符 | 说明 |
---|---|
\n | 换行符,用于在输出中换到下一行的开始。 |
\t | 水平制表符,用于在输出中插入一个水平制表符(通常等价于几个空格)。 |
\\ | 反斜杠字符本身,因为反斜杠在 C 语言中用作转义字符的引导,所以表示它自身时需要两个反斜杠。 |
\' | 单引号字符,因为在 C 语言中字符串是由双引号括起来的,所以要在字符串中表示单引号,就需要使用转义字符。 |
\" | 双引号字符,因为 C 语言中的字符串是由双引号括起来的,所以要在字符串中表示双引号,就需要使用转义字符。 |
\a | 响铃(BEL)字符,用于产生声音或可视信号(具体效果取决于系统)。 |
\b | 退格符,用于将光标向左移动一个位置,在某些情况下可以删除光标左侧的一个字符。 |
\f | 换页符,用于将光标移动到下一个页面的开始,这在打印输出中可能更常见。 |
\r | 回车符,在某些系统中用于将光标移动到当前行的开头,而不是新行的开始。 |
\v | 垂直制表符,其效果可能因系统而异,通常用于在垂直方向上移动光标。 |
\ooo | 八进制转义序列,这里的 o 表示八进制数字(0-7),并且这个序列可以是 1 到 3 位长。如果序列的位数少于 3,则在其前面填充 0 以达到三位 |
\xhh | 十六进制转义序列,这里的 h 表示十六进制数字(0-9, A-F, a-f),并且这个序列通常是两位长。 |
示例:
#include <stdio.h>
int main()
{
// 使用 \n 换行符
printf("这是第一行\n这是第二行\n");
// 使用 \t 制表符
printf("姓名\t年龄\t职业\n"); // 姓名 年龄 职业
printf("张三\t30\t程序员\n"); // 张三 30 程序员
// 使用 \\ 反斜杠
printf("文件路径是C:\\Users\\Public\\Documents\\file.txt\n");
// 文件路径是C:\Users\Public\Documents\file.txt
// 使用 \' 单引号
printf("字符\'A\'的ASCII码是65\n"); // 字符'A'的 ASCII 码是 65
// 可以直接在双引号里面嵌套单引号
printf("字符'A'的ASCII码是65\n"); // 字符'A'的 ASCII 是 65
// 使用 \" 双引号
printf("他说:\"你好,世界!\"\n"); // 他说:"你好,世界!"
// 不可以在双引号里面嵌套使用双引号,不然会报错
// printf("他说:"你好,世界!"\n"); // 他说:"你好,世界!"
// 使用 \a 警报(响铃)符
printf("警报!\a\n"); // 可能会听到计算机发出的哔哔声,也可能不会
// 使用 \b 退格符
printf("Hello\b \bWorld!\n"); // 第一个 \b 删除了'o',第二个 \b 删除了空格,所以输出是 HellWorld!
// 使用 \f 换页符(通常用于打印或显示设备)
printf("这是第一页\f这是第二页\n"); // 在某些环境中可能不会立即看到效果
// 使用 \r 回车符(注意:在早期的系统中,\r\n 通常一起使用表示换行,但这里仅展示 \r 的效果)
printf("使用\r此行前面的字符会被覆盖,即光标会移回行首: Hello World!\n");
// 这将输出"将光标移回行首: Hello World!",因为 \r 将光标移回行首,覆盖了“使用”
// 使用 \v 垂直制表符(不太常用)
printf("垂直制表符示例1:\v垂直制表符示例2:\v"); // 在某些环境中可能效果不明显
// 使用 \ooo 八进制转义序列(其中 o 是八进制数字)
printf("八进制转义示例:\141\142\143\n"); // 输出: abc
// 使用 \xhh 十六进制转义序列(其中 h 是十六进制数字)
printf("十六进制转义示例:\x41\x42\x43\n"); // 输出: ABC
return 0;
}
输出结果如下所示:
提示:
在所有的 C 语言字符串字面量中,可以直接在双引号包围的字符串内部使用单引号来界定字符常量。因为单引号界定的字符常量是字符串字面量的一部分,而不是一个独立的字符串。如:"This is a 'character' inside a string.\n"
但是,不能直接在双引号界定的字符串内部使用另一个未转义的双引号,因为双引号用于界定字符串的开始和结束。如果需要在字符串中包含一个双引号字符,必须使用转义字符(
\
)来转义它。这样,紧随转义字符的双引号就被视为字符串的一部分,而不是字符串的结束。如:"She said, \"Hello, world!\".\n"
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 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 b1 = 'b';
char b2 = 101; // ASCII 码为'e'
// 输出 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 类型取值范围
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 表示一个负数的补码,即 -56
return 0;
}
输出结果如下所示:
4.7 案例:字母大小写转换
#include <stdio.h>
int main()
{
char ch = 'A'; // 大写字母 A
char lowerCh = ch + 32; // 将大写转换为小写
printf("Original: %c, Lowercase: %c\n", ch, lowerCh);
// Original: A, Lowercase: a
ch = 'a'; // 小写字母 a
char upperCh = ch - 32; // 将小写转换为大写
printf("Original: %c, Uppercase: %c\n", ch, upperCh);
// Original: a, Uppercase: A
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 0
int main()
{
// 1. 使用整型表示真假两种状态
// int isPass = 0;
// int isOk = 1;
// 2. 使用宏定义的 BOOL 类型来表示真假两种状态
// 这里通过宏定义避免了直接在代码中使用 int 来表示布尔值,使得代码更易于理解和维护
BOOL isPass = FALSE;
BOOL isOk = TRUE;
printf("isPass=%d, isOk=%d \n", isPass, isOk); // isPass=0, isOk=1
// 使用 if 语句判断 isPass 的值
// 由于 isPass 被初始化为 FALSE(即0),所以这个条件不满足,不会执行大括号内的代码
if (isPass)
{
printf("不会执行"); // 不会执行
}
// 使用 if 语句判断 isOk 的值
// 由于 isOk 被初始化为 TRUE(即1),所以这个条件满足,会执行大括号内的代码
if (isOk)
{
printf("会执行"); // 会执行
}
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;
printf("isPass=%d, isOk=%d \n", isPass, isOk); // 输出: isPass=0, isOk=1
// 判断 isPass 的值
// 由于 isPass 为假(0),这个条件不满足,不会执行大括号内的代码
if (isPass)
{
printf("不会执行"); // 不会执行
}
// 判断 isOk 的值
// 由于 isOk 在这里被隐式转换为真(1),这个条件满足,会执行大括号内的代码
if (isOk)
{
printf("会执行"); // ok
}
return 0;
}
输出结果如下所示:
5.2.3 bool 类型
C99 标准还提供了一个头文件 <stdbool.h> 定义了 bool 类型作为 _Bool 的别名,并且定义了 true 和 false 两个宏,分别代表整数值 1 和 0。
#include <stdio.h> // 引入标准输入输出库,用于 printf 函数
#include <stdbool.h> // 引入布尔类型库,提供 bool、true 和 false 的定义
int main()
{
// 使用 bool 类型定义布尔变量
// bool 是 C99 及以后版本中定义的布尔类型
bool isPass = false; // 显示的初始化 isPass 为假
bool isOk = true; // 显示的初始化 isOk 为真
printf("isPass=%d, isOk=%d \n", isPass, isOk); // isPass=0, isOk=1
// 使用 if 语句判断 isPass 的值
// 由于 isPass 为假(false),这个条件不满足,不会执行大括号内的代码
if (isPass)
{
printf("不会执行"); // 不会执行
}
// 使用 if 语句判断 isOk 的值
// 由于 isOk 为真(true),这个条件满足,会执行大括号内的代码
if (isOk)
{
printf("会执行"); // 会执行
}
return 0;
}
输出结果如下所示:
bool 类型在 C 语言中实际上是一个能够存储 0 或非 0 值的整数类型。这意味着,虽然 true 和 false 是最自然的选择来表示布尔值,但也可以将其他非零值赋给 bool 类型的变量,它们都将被视为 true。只有赋值 0(或显式地赋值 false)时,bool 类型的变量才会被视为 false。
#include <stdio.h>
#include <stdbool.h>
int main()
{
bool a = true; // 明确设置为 true
bool b = 1; // 也被视为 true,因为 1 是非零值
bool c = 0; // 设置为 false
bool d = false; // 明确设置为 false
// 输出测试结果
printf("a: %d\n", a); // 输出 a: 1
printf("b: %d\n", b); // 输出 b: 1
printf("c: %d\n", c); // 输出 c: 0
printf("d: %d\n", d); // 输出 d: 0
return 0;
}
可以通过按住键盘 Ctrl 键并点击鼠标左键来查看 stdbool.h 的源代码。源代码片段内容如下:
6 sizeof 获取数据的存储大小
使用 sizeof 可以获取数据类型或变量、字面量的存储大小,单位是字节。sizeof 返回一个 size_t 类型的无符号整数值,格式占位符是 %zu。
size_t 通常是 unsigned int 或 unsigned long 的别名,具体是哪个类型的别名,由系统和编译器决定。
6.1 括号的可选性
计算基本数据类型的大小,必须使用括号将数据类型关键字包裹起来。
对于字面量和变量,sizeof 运算符可以直接作用于它们,括号是可选的,可以省略括号。
建议:
为了保持代码风格的一致性和直观性,建议在复杂表达式或为了提高代码可读性时,即使可以省略括号也习惯性地加上它们(如 sizeof(变量名) 或 sizeof('a') )
6.2 查看基本数据类型、字面量和变量的大小
字符字面量(如 'a'):在大多数情况下被视为 int 类型,sizeof 通常返回 4 字节。
整数和浮点数字面量:其大小取决于它们被解释为的具体类型。
#include <stdio.h>
int main()
{
// 计算基本数据类型的大小,必须使用括号将数据类型关键字包裹起来
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,因为字符字面量被提升为 int
printf("%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。