1972年,丹尼斯·里奇(Dennis Ritch)在贝尔实验室和肯·汤普逊(Ken Tompson)开发 UNIX 操作系统时基于 B 语言(由肯·汤普逊开发)设计出 C 语言。
1987年,布莱恩·柯林汉(Brian Kernighan)和丹尼斯·里奇合著的 《The C Programming Language》(《C 语言程序设计》)第 1 版是公认的 C 标准,称之为 K&R C 或经典 C。
1989年,C 语言被美国国家标准协会 ANSI 标准化为 ANSI C,其定义了 C 语言和 C 标准库。这套 C 标准也被称为 C89。
1990年,国际标准化组织 ISO 采用了 ANSI 制定的 C 标准,改为 ISO C。它与 ANSI C 是完全相同的。ISO C 也称为 C90。
1994年,ANSI/ISO联合委员会发布 C99 标准。其修改如下:
iso646.h、wctype.h 和 wchar.h。printf/sprintf 函数一系列的格式代码。2011年,ISO 发布了 C11 标准。
C18
C2X
C 语言编写的源代码文件以 .c 结尾。

编译器(compiler)将源代码编译成中间代码或目标代码,最普遍的中间代码是机器语言代码,但是中间代码还不能直接运行,因为缺少启动代码和库代码。启动代码是程序与操作系统间的接口,不同操作系统的启动代码不同。
链接器最后把中间代码、系统的标准启动代码和库代码这三部分合并成一个文件:可执行文件。中间代码与可执行代码都由机器语言指令组成。
K&R C标准关键字:
| auto | extern | short | while |
| break | float | case | for |
| char | goto | static | if |
| struct | continue | switch | default |
| int | typedef | do | long |
| union | double | register | unsigned |
| else | restrict | void | return |
| sizeof |
C89/C90新增标准关键字:
C99新增标准关键字:
C11新增标准关键字:
_Alignas_Alignof_Atomic_Bool_Complex_Generic_Imaginary_Noreturn_Static_assert_Thread_local保留标识符包括那些以下划线字符开头的标识符和标准库函数名,如printf()、scanf()、malloc()等。
C语言的数据类型关键字:
| K&R C最初给出的 | C90新增 | C99新增 |
|---|---|---|
| int | signed | _Bool |
| short | void | _Complex |
| long | _Imaginary | |
| char | ||
| float | ||
| double | ||
| unsigned |
C语言针对不同使用情况,提供了多种整数类型。
int是常用的数据类型,被认为是计算机处理整型类型时最高效的类型。
int类型是有符号整型,可以是正负整数和零,第一位为符号位。
int类型的取值范围与计算机处理器位数相关:16位的处理器使用16位来存储一个int值,取值范围为 − 2 15 = 32768 -2^{15} = 32768 −215=32768到 2 15 − 1 = 32767 2^{15} -1 = 32767 215−1=32767,这也是ISO规定的int最小的取值范围;32位处理器则使用32位来存储一个int值;还有 64 位的。
声明并初始化int变量:

int a,b=1; // a并没有像b一样被初始化为1
表示八进制和十六进制:C语言默认数字常量是十进制,但在数字前添加0前缀表示8进制,添加0x或0X前缀表示16进制。
使用printf库函数在控制台打印int变量:

十进制、八进制和十六进制的显示:
/*
* 以指定格式显示整型
*/
printf("%d %d\n", 010, 0x10); // 8 16
printf("%o %o\n", 8, 0x10); // 10 20
printf("%x %x\n", 16, 0x20); // 10 20
/*
* 以原型显示整型,即保留0、0x或0X前缀
*/
printf("%#o %#o\n", 010, 0x10); // 010 020
printf("%#x %#x\n", 010, 0x10); // 0x8 0x10
printf("%#X %#X\n", 010, 0x10); // 0X8 0X10
转换说明
%d —转为—> 十进制整数%o —转为—> 八进制整数%x —转为—> 十六进制整数%#o —显示—> 八进制整数%#x —显示—> 0x开头十六进制整数%#X —显示—> 0X开头十六进制整数不被捕获的输出错误:
printf("%d %d",1); // 第二个%d没有提供值,它会打印出内存中的任意值
上面这种错误不会被编译器捕获,因为printf函数无法预知使用者传参数量。
short 是 short int 类型的简写,其占用存储空间不会比 int 多,可能相等,一般为 16 位。在某些情况下可以节省空间。
short 的打印可以使用 %hd(十进制)、%ho(八进制)和 %hx(十六进制)。
long 是 long int 的简写,占用空间不比 int 少,一般为 32 位。适用于数值大的场合。
long 的打印可以使用 %ld(十进制)、%lo(八进制)和 %lx(十六进制)。
对于 long 类型常量,后缀要加 l 或 L。
long long int 的简写,占用空间不比 long 少,至少占用 64 位。
long long 的打印可以使用 %lld(十进制)、%llo(八进制)和 %llx(十六进制)。
long long 类型常量后缀要加 ll 或 LL。
unsigned int的简写,表示无符号整型。
C90 中添加了 unsigned long 或 unsigned long int ,以及 unsigned short 和 unsigned short int。
C99 中添加了 unsigned long long 或 unsigned long long int。
有符号类型前面都省略了signed。加上也无妨。
常量后缀:
| 类型 | 后缀 |
|---|---|
| unsigned | %u |
| unsigned long | %lu、%ul、%UL、%LU … |
| unsigned long long | %llu … |
...后面显而易见了。
在C语言中,字符类型主要用于表示单个字符。char 变量通常占用一个字节的内存空间,即8位,但其确切大小和编码方式可能因不同的编译器和平台而异。
从技术层面看,char是整数类型,因为它实际上存储的是整数而非字符,特定整数用于表示特定的字符,这也是字符的编码,美国最常用的编码是ASCII(American Standard Code for Information Interchange),标准ASCII码使用7位二进制数表示0-9、a-z、A-Z、标点符号、美式英语中的特殊控制字符,剩余1位为0,这一位也使用在了扩展ASCII码。
字符常量和初始化:
char a = 'A'; // 使用单引号括起来的单个字符是字符常量
也可以使用数字初始化char变量,'A'的ASCII码是65:
char a = 65; // 只适合使用ASCII编码的系统
C语言将字符常量视为int类型而非char类型,在int为32位、char为8位的系统中,它会把字符常量存储在32位的存储单元,但只取后8位:
char x = 'abcd'; // x 实际为 d
C语言中还有一些有趣的非打印字符,比如退格、换行、蜂鸣或响铃,它们可以用如下称为转义序列的字符表示。
| 转义序列 | 含义 |
|---|---|
| \a | 警报(ANSI C) |
| \b | 退格 |
| \f | 换页 |
| \n | 换行 |
| \r | 回车 |
| \t | 水平制表符 |
| \v | 垂直制表符 |
| \\ | 反斜杠 |
| \’ | 单引号 |
| \" | 双引号 |
| \? | 问号 |
| \0xx | 八进制值 |
| \xhh | 十六进制值 |
接下来是字符类型的打印了,printf函数使用%c作为字符类型的转换说明。如果使用%d打印字符,则会打印一个整数。
printf函数中转换说明决定了数据的显示方式,而数据的声明决定了数据存储形式。
C语言提供了两种主要的字符类型:char 和 wchar_t。
wchar_t 是C语言中的宽字符类型,用于表示更广范围的字符,包括国际化字符集中的字符。
wchar_t 变量的大小和编码方式因平台而异,通常会比 char 大。
在C语言中,wchar_t 类型的变量可以用L前缀和单引号括起来,例如:wchar_t wideChar = L'宽';。
_Bool类型用于表示布尔值,即true或false。C99标准引入了这个数据类型,以提供对布尔逻辑的原生支持。
_Bool类型原则上只占用1位存储空间,用0表示false,用1表示true。
要使用 _Bool 类型,需要包含 头文件。
为了更方便地使用 _Bool,C标准库还定义了 bool 作为 _Bool 的别名。
为了提高可移植性,C99 新增了两个头文件 stdint.h 和 inttypes.h。
stdint.h提供精确宽度整数类型(exact-width integer type)。例如 int32_t 表示 32 位的有符号整数,在 32 位 int 的系统中会将其作为 int 别名,而在 16 位 int、32 位 long 的系统中则会将其作为 long 的别名。但是,如果系统中并没有 32 位这么短的类型,那么这时就有问题了。
因此,C99和 C11提供了最小宽度类型(minimum width type)来解决该情况。例如,int_least8_t 是可容纳 8 位有符号整数值的类型中宽度最小的类型的一个别名。如果系统最小数据类型是 16 位,那么int_least8_t可能被实现为 16 位。
如果你更关心数据类型的速度而非空间,那么 C99 和 C11 也定义一组可使计算达到最快的类型集合,称为最快最小宽度类型。例如 int_fast8_t。
最后,还有最大整数类型 intmax_t 和 uintmax_t。
这些类型都被包含在 stdint.h 中。
inttypes.h 定义了一系列的宏,用于在格式化字符串中指定不同大小和带符号性质的整数类型。这些宏通常以PRI和SCN开头,后面跟随整数类型的缩写,例如 PRId32 用于表示带符号32位整数的格式宏,PRIu64 用于表示无符号64位整数的格式宏。这些宏可用于 printf 和 scanf 等函数,以确保在不同平台上正确处理整数类型的输入和输出。
printf("%"PRId32"",x);
// PRId32被定义在inttypes.h中的d替换
// 因此,上行代码等价于:
printf("%"d"",x); => printf("%d",x);
C语言中的浮点类型有float、double和long double。浮点类型用于金融和数学领域的场合。
一般计数法:120.55
科学计数法:1.2055 x 10^2
指数计数法:1.2055e2
float 类型至少表示小数点后 6 位有效数字,取值范围为 1 0 − 37 至 1 0 37 10^{-37} 至 10^{37} 10−37至1037。
float 类型通常占据 32 位,采用 IEEE 754 标准来表示浮点数,其中 8 位用于表示指数的符号和值,另外 24 位用于表示尾数及其符号。
尾数范围是-1~1,指数范围是-128~128,底数是2,所以float类型的范围是 − 2 − 128 − 2 128 -2^{-128} - 2^{128} −2−128−2128。
IEEE 754 是一个用于浮点数表示和计算的标准,它定义了浮点数的二进制表示方法、舍入规则以及基本算术运算规则。
让我们考虑一个单精度浮点数:0 10000010 10010000000000000000000。
IEEE 754 单精度浮点数由三个部分组成:符号位(Sign Bit)、指数部分(Exponent)、小数部分(Fraction/Mantissa)。让我们一步步来解剖这个例子:
- 符号位(Sign Bit):第一个位是符号位。0 表示正数,1 表示负数。在这个例子中,符号位是0,所以这是一个正数。
- 指数部分(Exponent):接下来的 8 位是指数部分。这个部分用于表示浮点数的指数。在 IEEE 754 中,指数采用偏移表示法,即需要减去一个偏移值来得到真实的指数。偏移值为127(单精度浮点数的情况)。所以,在这个例子中,指数部分是 10000010。要计算真实的指数,需要将它解释为二进制,然后减去偏移值:
10000010 (二进制) = 130 (十进制) 真实的指数 = 130 - 127 = 3- 小数部分(Fraction/Mantissa):剩下的位数(23 位)组成小数部分。这个部分用于表示浮点数的精度和小数部分。在这个例子中,小数部分是 10010000000000000000000。
现在,我们有了这些部分的值,我们可以计算出这个浮点数的实际值。根据 IEEE 754 的规则,实际值可以计算为:
实际值 = (-1)^符号位 × 1.10010000000000000000000(二进制) × 2^真实的指数
在这个例子中:
- 符号位是 0,表示正数。
- 小数部分是 1.10010000000000000000000(二进制)。
- 真实的指数是 3。
现在,我们可以将这些值代入公式中来计算实际值:
实际值 = (-1)^0 × 1.10010000000000000000000 × 2^3
实际值 = 12.5(十进制)
double 是双精度浮点型,与 float 最小取值范围相同,但是最小有效数字必须至少为 10 位,double 一般占用 64 位。实际上,double类型的值至少有13位有效数字。
long double 至少与 double 精度相同。
有效的浮点型常量的示例:
3.14
.2
2e2
.2E-2
10.
默认情况下,浮点型常量被认为是double类型的精度。在浮点数后加上f或F后缀可以覆盖这种行为,被认为是float类型。加上l或L后缀则会使得数字成为long double类型。
打印浮点数:printf函数使用%f打印十进制记数法的float、double类型浮点数,使用%e打印指数记数法的浮点数,使用Lf或Le打印long double类型。
浮点数的上溢(overflow)和下溢(underflow):
上溢指计算结果超出当前类型能表示最大范围:
float toobig = 1e30 * 1e30f; // toobig上溢,会被赋值为一个表示无穷的值,如inf、infinity
下溢指损失了类型全精度的计算,得到的结果称为低于正常的(subnormal)浮点值,通常发生在尾数部分非常小,以至于无法用规格化的方式表示时。在这种情况下,尾数部分会被右移,以适应更小的指数值。比如将最小的正浮点数除以2。
还有一个特殊的浮点值NaN(Not a Number),比如给asin函数传递一个大于1的参数就会返回NaN。