理解C语言:第一个程序
一、C语言定义
通俗地讲,C语言就是一门人跟计算机交流的语言。
现实里不同地区的人说不同的话,人和机器之间的“语言差异”则更大:人说的是自然语言,例如汉语、英语、日语,计算机则使用二进制语言。为了实现人和计算机的“交流”,就有了像 C 语言这样的计算机语言。C++、Java、Go、Python 其实也都是。
C 语言最早是作为 Unix 系统的开发工具被发明出来的,设计者是贝尔实验室的 Dennis Ritchie(丹尼斯·里奇)。后来他和 Brian Kernighan(布莱恩·柯林汉)合写了一本《The C Programming Language》。
到今天,Linux 内核、Redis、MySQL 这些偏底层的项目,都还是用 C 写的。这也是 C 经久不衰的原因。下图是 TIOBE 编程社区 9 月公布的相关数据:
二、程序编写准备
就像大学生期末考试写高数前得先买一支笔,写 C 程序也得先有工具:编译器和链接器,它们是用来对代码进行编译和链接的工具。
1. 编译和链接
C 是编译型语言,代码都写在以 .c 结尾的文本文件里。文本文件本身不能执行我们想要的操作,要经过两道处理:
-
编译:编译器(VS 里是
cl.exe)把每个.c文件编译成对应的.obj目标文件; -
链接:链接器(
link.exe)把多个目标文件和库文件拼到一起,生成.exe可执行文件。
这两步对初学者较难理解,可以先做简单了解,后面会展开讲解。
2. 编译器与 IDE
编译器有好几种,常见的有 GCC、Clang、MSVC,但直接拿来用都得自己配置环境,学习成本偏高。
所以一般用集成开发环境(IDE)——它把编辑器、编译器、调试器都打包好了,开箱即用。常见的有 VS2022 / VS2026、Xcode、Code::Blocks、Dev-C++、CLion。
这一系列文章使用的是 VS2026,功能全、界面是中文的(自己选择),装完基本不用额外配置,对新手比较友好。
相应的 IDE 下载步骤可以在 B 站上查找教程,比较简单。一般地,下载好安装程序、执行,根据引导进行相关设定即可;也可以考虑询问 AI,要求指导。
三、第一个好运程序
建好项目和源文件(该过程同样可以询问 AI),就可以写第一段代码了,这是来自 C 语言之父的“传统”:
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
据说,这段代码会给每一个程序员带来好运。初学看不懂很正常,照着敲就好。实际上,当你安装完程序,除了所谓的传统外,敲这样一段代码也是为了检验你的集成环境是否正常。
写的时候记得把输入法切到英文——C 语言里必须用英文半角符号,也就是 Win11 右下角应该如图所示:
四、代码解析(可跳过)
[!note] 提示
以下四点看不懂很正常,因为初学者缺乏了很多前置知识。如果你的代码运行正常,这一段建议跳过,等学完基础知识后再回过头来阅读即可。
1. #include <stdio.h>
这是一句指令,作用是把 stdio 这个头文件包含进来。
可以这么理解:想用一把剪刀,前提是装有剪刀的工具箱在你手上。这句话就是把 stdio 这个“工具箱”放到你手上,里面装着 printf 这个工具。
stdio 是 standard input / output 的缩写,也就是“标准输入输出”,只要跟输入输出有关的操作,都要包含它。printf 是 print 和 format 的结合,表示格式化输出。
2. int main()
main 是程序的入口,每个 C 程序都从 main 开始执行,所以它也叫主函数。一个项目里可以有多个 .c 文件,但 main 有且只有一个。
前面的 int 是 integer 的缩写,意思是整型,也就是说 main 结束时会返回一个整数;后面那对大括号 {} 里,才是它真正要执行的代码。
3. printf("hello, world\n");
这是一次函数调用,把引号里的内容交给 printf 打印出来。
- 引号里的东西叫字符串,注意是双引号,单引号括起来的单个字符才叫字符;
-
\n是换行,不写的话光标不会跳到下一行; - 末尾的分号不能少,C 语言里每条语句都得用它收尾。
4. return 0;
表示程序正常结束,把 0 交还给操作系统,正好和 main 前面的 int 呼应上。
五、再谈主函数
C 语言最重要的是 main() 这六个字符,一般地,我们把这个整体称为主函数,也就是 C 程序中最主要的函数。
main() 是程序执行的入口,这句话需要这样理解:main 只能有一个,无论有多少个 C 文件。因为 C 程序执行的时候需要从一端开始,而不是多处、多端开始,这是这门程序语言设定好的规则,需要每一个程序员遵守,具体的原因需要不断深入后才能理解。
花括号包括了主函数的内容,return 0 是主函数结束的标志,这些东西都是设定好的,它们的根源需要我们不断学习才能掌握,当前阶段记住就好。
所以回过头来看最开始的程序,我们是在主函数中使用了一个工具,进而实现了我们的目的。这个工具也很有意思,叫做 printf,也就是格式化输出的含义:print 是输出,f 是 format 的缩写,表示格式化。它是我们常用的一个输出工具,用来在控制台打印我们需要的信息(也就是那个黑色的窗口)。
六、常见错误
以下是初学者可能犯的错误,会导致代码出错、程序无法正常运行:
- main 写成了 mian;
- main 后面的
()漏掉了; - 用了中文符号,比如括号、分号;
- 一条语句结束忘了写分号。
刚开始犯错很正常,练熟了就好了。
七、从主函数到函数
既然有了主函数,我们不难猜想还有别的函数,毕竟主函数之所以叫主函数,就是为了区别于其他函数。
实际上我们一直称为“工具”的 printf 就是一个函数。至此,我们得到 C 语言里面的一个概念——函数。它是用来处理一些复杂操作的:比如我们要把“你好”打印到电脑的控制台,实际上背后如何做到的我们一无所知,我们只要使用 printf 这个操作就可以实现,细节就是把我们要输出的信息放到里面。
这也就涉及到如何实现函数的调用了,是一个复杂的部分,每个函数都有所不同。而且为了了解这一部分,我们需要了解 C 语言的数据类型。
八、关键字(保留字)
人类为了描述自然世界,在数学中加入了各种类型,包括描述重量的千克和描述长度的米,以及一些纯粹的数字。而 C 语言同样具备类似的“单位”,而且相对于自然语言,C 语言的“单位”更加抽象,我们一般称为数据类型。
在了解数据类型前,需要先了解一个概念,只有理解它们,才能辨别 C 语言中的数据类型以及其他关键代码。这个概念就是关键字,或者称为保留字。
还是用最简单的那个 hello world 程序,我们已经了解到什么是主函数 main() 了,但是主函数使用前有一串字符,叫做 int,它就是一个关键字,用来表示整型的概念,此处不妨将整型理解为数学中的整数。
类似的关键字还有很多,例如:
| 类别 | 关键字 |
|---|---|
| 数据类型 |
int、char、float、double、void、long、short、signed、unsigned
|
| 流程控制 |
if、else、switch、case、default、for、while、do、break、continue、goto、return
|
| 存储与限定 |
static、extern、register、const、volatile、auto
|
| 构造与运算 |
struct、union、enum、typedef、sizeof
|
它们是 C 语言保留的字符串,也就是说,程序员不可以在程序中将其另作他用,只能用于固定的用途。以上关键字都会在后续讲解到,此处作了解即可。
九、数据类型与变量
1. 三种基本数据类型
前面提到,只有了解数据类型,才能理解函数的调用。先看最常用的三种基本数据类型:
| 数据类型 | 关键字 | 含义 | 举例 |
|---|---|---|---|
| 整型 | int |
数学上的整数 |
10、-3
|
| 浮点型 | float |
数学上的小数 |
3.14、-0.5
|
| 字符型 | char |
英文字母、中文等字符 |
'A'、'中'
|
我们常用 int 来表示数学上的整数,用浮点型表示小数,用字符型表示字母、中文等字符。
这里要特别记住一点:字符常量用单引号 '' 标识,字符串才用双引号 "",这样就能把字符和字符串区分开来。
2. 变量与赋值
变量就是用来存放数据的容器。前面说的“定义”这个操作,实际上就是使用关键字(int、float、char)结合变量名、赋值符号,把数据存进去。
看这样一句代码:
int a = 10;
它的意思是:把整数 10 存放到整型变量 a 中。以等号为界——
- 等号左边是
int a,表示定义了一个变量,名字是 a,数据类型是 int; - 等号右边是数字 10,显然是一个整数;
- 结合等号的含义,就是说整型变量 a 的值是 10。
这里提到的“存放数据”“赋值”“变量的值是……”,其实都是一个意思,不必过于深究。最规范的说法是将 10 赋值给变量 a;为了接收整数 10,我们把 a 的类型规定为整型,其他说法都只是便于接受。
也可以拆成两句来写:
int a;
a = 10;
这两句和上面的 int a = 10; 在 C 语言中是等价的,一般我们使用第一种。
3. 字符型与浮点型的定义
仿照整型,也可以定义字符型和浮点型变量:
char c = 'A'; // 定义字符型变量 c,存放字符 'A'
float f = 3.14f; // 定义浮点型变量 f,存放小数 3.14
[!tip] 关于
3.14f后面的 f
直接写3.14会被当作双精度浮点型(double),后面的f用来把它明确成单精度浮点型(float)。入门阶段先照着写即可。
4. 三种类型的关键字记忆
三种基本类型的关键字比较难记,可以结合英文单词来记:
- 整数的英文单词是 integer,C 语言把它缩写为关键字
int; - 字符的英文单词是 character,关键字是
char; - 浮点型较为特殊,主要关注它“浮动”的特性,float 本身也有“漂浮”的意思。
为什么把小数称为浮点型?
因为这类数的小数点位置不是固定的,而是可以“浮动”的。计算机正是依靠这种“小数点位置可变”的方式来表示小数,所以称为浮点型:
float f = 3.14f; // 相当于 3.14 × 10^0
double d = 314.0; // 也可以理解为 3.14 × 10^2
5. 深入整型
整型分为四种,分别是短整型、整型、长整型、更长的整型。它们都用来表示整数,区别在于能存放的数据大小不同,对应的关键字分别是:
- 短整型:
short int - 整型:
int - 长整型:
long int - 更长的整型:
long long int
再结合 signed(有符号)和 unsigned(无符号)两个关键字,我们就得到了 8 种整型。下表列出它们的典型大小和取值范围(以常见的 64 位平台为例,实际大小与平台相关):
| 类型 | 关键字 | 典型大小 | 取值范围 |
|---|---|---|---|
| 短整型 | short int |
2 字节 | -32768 ~ 32767 |
| 无符号短整型 | unsigned short int |
2 字节 | 0 ~ 65535 |
| 整型 | int |
4 字节 | -2147483648 ~ 2147483647 |
| 无符号整型 | unsigned int |
4 字节 | 0 ~ 4294967295 |
| 长整型 | long int |
4 或 8 字节 | -2147483648 ~ 2147483647(Windows 下为 4 字节) |
| 无符号长整型 | unsigned long int |
4 或 8 字节 | 0 ~ 4294967295(Windows 下) |
| 更长整型 | long long int |
8 字节 | -9223372036854775808 ~ 9223372036854775807 |
| 无符号更长整型 | unsigned long long int |
8 字节 | 0 ~ 18446744073709551615 |
之所以这样设定,是为了在写 C 程序时,能对电脑内存达到最高效的利用。打个比方:你的饭量决定你点的菜量——数据有多大,就选刚好够用的类型。
8 种整型的定义示例:
short int a = 10; // 短整型
unsigned short int b = 20; // 无符号短整型
int c = 30; // 整型
unsigned int d = 40; // 无符号整型
long int e = 50; // 长整型
unsigned long int f = 60; // 无符号长整型
long long int g = 70; // 更长整型
unsigned long long int h = 80; // 无符号更长整型
6. 深入字符型
字符型只有一种:char。如果考虑符号,就分两种——signed char(有符号)和 unsigned char(无符号)。
| 类型 | 关键字 | 典型大小 | 取值范围 |
|---|---|---|---|
| 字符型(有符号) | signed char |
1 字节 | -128 ~ 127 |
| 字符型(无符号) | unsigned char |
1 字节 | 0 ~ 255 |
[!note] 提示
直接写char时,它到底是有符号还是无符号,C 标准没有强制规定,由具体的编译器决定。入门阶段先掌握“一个 char 占 1 字节”即可。
定义示例:
char c = 'A'; // 普通字符型
signed char sc = -1; // 有符号字符型
unsigned char uc = 200; // 无符号字符型
7. 深入浮点型
浮点型分为三种:float、double、long double。
| 类型 | 关键字 | 典型大小 | 说明 |
|---|---|---|---|
| 单精度浮点型 | float |
4 字节 | 精度约 6~7 位有效数字 |
| 双精度浮点型 | double |
8 字节 | 精度约 15~16 位有效数字 |
| 长双精度浮点型 | long double |
8 或 16 字节 | 精度更高,大小与平台相关 |
定义示例:
float f = 3.14f; // 单精度
double d = 3.1415926535; // 双精度
long double ld = 3.14159265358979323846L; // 长双精度
8. 数据的存储单位
上面给范围时,我们故意没有给出具体单位,而是用“大小”笼统带过。接下来讲清楚数据存储的单位——这些单位知识不只对 C 语言有用,对以后学习 Python 等语言同样有帮助。
- bit(位 / 比特):最小的数据单位,只能取 0 或 1,是二进制的根本。
- byte(字节):计算机存储的基本单位,1 字节 = 8 位,即 1 Byte = 8 bit
常用换算关系:
| 单位 | 换算 |
|---|---|
| 1 Byte | 8 bit |
| 1 KB | 1024 Byte |
| 1 MB | 1024 KB |
| 1 GB | 1024 MB |
| 1 TB | 1024 GB |
计算机内部采用二进制存储数据。以 1 个字节(8 位)为例:
- 若按无符号理解,8 位能表示 $2^8 = 256$ 个数,即 0 ~ 255;
- 若按有符号理解,则划分到 -128 ~ 127。
这也正好解释了前面 unsigned char 的范围是 0 ~ 255、signed char 的范围是 -128 ~ 127 的原因。
9. 整型的极限值常量
为了代码的可移植性,当我们需要用到某种整数类型的极限值时,一般不会直接写死数字,而是使用标准头文件 <limits.h> 中预先定义好的常量,比如 INT_MIN、INT_MAX。常用的有:
| 常量 | 含义 | 典型值(64 位 / VS2026) |
|---|---|---|
INT_MIN |
int 的最小值 |
-2147483648 |
INT_MAX |
int 的最大值 |
2147483647 |
UINT_MAX |
unsigned int 的最大值 |
4294967295 |
LONG_MIN / LONG_MAX
|
long 的最小 / 最大值 |
-2147483648 / 2147483647 |
ULLONG_MAX |
unsigned long long 的最大值 |
18446744073709551615 |
这些常量本质上都是宏,取值正好对应前面表格里的取值范围。使用前要先包含头文件:
#include <limits.h>
#include <stdio.h>
int main()
{
printf("%d\n", INT_MAX); // 2147483647
printf("%d\n", INT_MIN); // -2147483648
return 0;
}
这样写的好处是:即使换到 int 宽度不同的平台,常量也会自动跟着变化,代码不必改动,也就更不容易出错。
十、布尔类型
1. 布尔类型的由来
布尔类型用来表示“真”与“假”两种结果,名字来自英国数学家 George Boole(乔治·布尔)。
在早期的 C 语言(C89)里,其实并没有布尔类型,人们通常用整型 int 来代替:0 表示假,非 0 表示真。
直到 C99 标准,才正式引入了布尔类型。不过它的关键字并不是 bool,而是 _Bool(前面有一个下划线,且 B 大写)。为了书写方便,标准头文件 <stdbool.h> 又把它包装成了 bool,并定义了 true(真)和 false(假)两个宏。
为什么 C99 不直接使用 bool? 核心原因是为了兼容大量已有的 C 代码,避免“破坏性”的改动。
在 C99 之前,bool 并不是关键字,而是程序员可以自由使用的普通标识符。很多人会自己写一句 typedef int bool;,或者干脆拿 bool 当变量名、函数名、结构体名。假如 C99 直接规定 bool 是关键字,这些老代码里的 bool 就会全部变成语法错误,无法再编译——对一门非常强调向后兼容的语言来说,这是不可接受的。
于是标准挑了一个“几乎不可能撞名”的名字:_Bool。因为 C 标准规定,凡是以“下划线 + 大写字母”开头(或以双下划线开头)的标识符,都是保留给编译器实现使用的,普通程序不会也不应该使用它们。拿这样一个名字来当关键字,就能最大程度避免与现存代码冲突。
真正的关键字只有 _Bool 这一个,bool、true、false 都只是 <stdbool.h> 里用宏“顺手”提供的别名:
// <stdbool.h> 大致相当于:
#define bool _Bool
#define true 1
#define false 0
所以,只要包含了 <stdbool.h>,就可以像 C++ 那样直接写 bool;而没有包含它的老代码则完全不受影响——bool 依旧可以当普通名字用。正是这种“新关键字加下划线、再靠头文件用宏包装”的做法,让 C 语言能在不破坏老代码的前提下不断演进。
[!note] 补充
从 C23 标准开始,bool、true、false已经成为语言自带的关键字,不再需要包含<stdbool.h>;但在使用旧标准的代码里,仍然要显式包含它。
2. 布尔类型的大小与取值
| 类型 | 关键字 | 典型大小 | 取值 |
|---|---|---|---|
| 布尔类型 |
_Bool / bool
|
1 字节 | 0(假)、1(真) |
布尔类型通常只占 1 字节。需要注意的是:把任意非 0 的整数赋给布尔变量时,它会被自动“规整”成 1,只有赋 0 才会得到 0。
3. 定义与使用示例
#include <stdbool.h>
#include <stdio.h>
int main()
{
bool flag = true; // 等价于 _Bool flag = 1;
bool empty = false; // 等价于 _Bool empty = 0;
printf("%d\n", flag); // 输出 1
printf("%d\n", empty); // 输出 0
return 0;
}
十一、sizeof 关键字
1. sizeof 是什么
sizeof 用来求一个类型或一个变量“占多少字节”。这里要特别强调:sizeof 是关键字(运算符),不是像 printf 那样的库函数。
它长得像函数、用起来也带小括号,所以很容易被误认,但两者有本质区别:
| 对比项 | sizeof |
printf(函数) |
|---|---|---|
| 身份 | 关键字 / 运算符 | 库函数 |
| 是否需要包含头文件 | 不需要 | 需要 #include <stdio.h>
|
| 何时起作用 | 编译阶段就求出结果 | 运行阶段才被调用 |
| 能否接类型名 | 能,如 sizeof(int)
|
不能 |
| 能否取地址 | 不能 | 可以 |
正因为它是运算符,所以既可以写成 sizeof(类型),也可以写成 sizeof(变量);而后一种形式里的括号其实可以省略。
2. 两种写法
#include <stdio.h>
int main()
{
int a = 10;
printf("%zu\n", sizeof(int)); // 4:求“类型” int 的大小
printf("%zu\n", sizeof(a)); // 4:求“变量” a 的大小
printf("%zu\n", sizeof a); // 4:变量时可以省略括号
return 0;
}
[!tip] 关于
%zu
sizeof求出的结果类型是size_t,这是一种无符号整型,正确的输出格式是%zu。现代 VS 都支持;如果遇到不支持的旧编译器,可以写成(int)sizeof(int)并配合%d。[!note] 小知识
对普通变量使用sizeof时,括号里的表达式不会被真正执行。例如在int a = 1;之后写sizeof(a++),得到的仍然是4,而a并不会自增。
3. 用 sizeof 验证各类型大小
接下来对照前面的内容,用 sizeof 亲自验证每种类型到底占几个字节:
#include <stdio.h>
int main()
{
printf("char : %zu 字节\n", sizeof(char));
printf("short : %zu 字节\n", sizeof(short));
printf("int : %zu 字节\n", sizeof(int));
printf("long : %zu 字节\n", sizeof(long));
printf("long long : %zu 字节\n", sizeof(long long));
printf("float : %zu 字节\n", sizeof(float));
printf("double : %zu 字节\n", sizeof(double));
printf("long double : %zu 字节\n", sizeof(long double));
return 0;
}
在 Windows 64 位平台 + VS2026 下,预期输出为:
char : 1 字节
short : 2 字节
int : 4 字节
long : 4 字节
long long : 8 字节
float : 4 字节
double : 8 字节
long double : 8 字节
结果与前两节表格里的“典型大小”完全对得上:
| 类型 |
sizeof 结果 |
与前面表格对照 |
|---|---|---|
char |
1 字节 | 一致 |
short |
2 字节 | 一致 |
int |
4 字节 | 一致 |
long |
4 字节 | 一致(Windows 下为 4 字节) |
long long |
8 字节 | 一致 |
float |
4 字节 | 一致 |
double |
8 字节 | 一致 |
long double |
8 字节 | 一致(MSVC 下与 double 相同;Linux 上常为 16 字节) |
[!warning] 注意平台差异
上表的输出是 Windows + MSVC 下的结果。换到 Linux(GCC)时,long通常会变成 8 字节、long double通常会变成 16 字节。所以,“某类型占几个字节”并不是 C 语言规定的绝对死数,必要时用sizeof量一量最可靠。
十二、C 标准的发展及其影响
1. C 语言的标准版本
C 语言有一个特点:它最初并没有官方标准,只是由 Kernighan 与 Ritchie 的书和实现“约定俗成”。为了让不同厂商写出的编译器彼此兼容,美国国家标准学会(ANSI)和国际标准化组织(ISO)先后制定了多个版本的标准。下面按时间顺序列出主要的几版:
| 标准 | 俗称 | 发布年份 | 主要新增内容 |
|---|---|---|---|
| K&R C | — | 1978 | 《The C Programming Language》所定义的原始版本 |
| C89 / C90 | ANSI C | 1989(ISO 于 1990 采纳) | 第一个官方标准,统一了语法与标准库 |
| C95 | — | 1995 | 对 C89 的补充修订,加入宽字符、双字符组(digraph)等 |
| C99 | — | 1999 |
long long、_Bool / stdbool.h、变长数组、// 单行注释、stdint.h、inline、指定初始化器等 |
| C11 | — | 2011 |
_Generic、_Static_assert、多线程与原子操作、匿名结构体/联合体等 |
| C17 / C18 | — | 2018 | 主要是缺陷修补,几乎没有新特性 |
| C23 | — | 2024 | 属性 [[…]]、nullptr、constexpr、typeof、二进制字面量 0b、bool / true / false 成为关键字等 |
[!note] 关于版本号
标准年份有两种叫法:ANSI 常用发布年份(如 C89),ISO 常用正式采纳年份(如 C90),它们指的是同一版本,不必纠结。
2. 对 C 编程的影响
(1)可移植性更强。 只要代码遵守标准,同一份源码就更容易在 Windows、Linux、macOS 等不同平台间迁移——编译器负责把标准语法翻译成对应平台能执行的机器码。
(2)新标准是“增量”,不是“推翻”。 语言的核心部分——变量、数据类型、函数、指针、流程控制——在所有标准里都保持一致。新版本大多是“加菜”,很少删掉旧语法,所以老代码通常还能继续编译。
(3)实际能不能用,取决于编译器。 标准是“纸面规定”,真正把它落地的是各家编译器(GCC、Clang、MSVC)。同一个新特性,有的编译器支持得快,有的支持得慢,写代码时要留意当前用的是哪个标准版本。
(4)初学者会遇到的现实差异。 很多看起来“理所当然”的写法,其实是较新标准才有的:
- 用
//写单行注释,是 C99 才正式加入的(C89 只能用/* */); - 布尔类型
bool,是 C99 才有的; - 在
for循环的括号里直接声明变量(for (int i = 0; ...)),也是 C99 才允许的。
所以,在 VS 中可以通过项目属性设置语言标准(对应编译选项 /std:c11、/std:c17 等);阅读别人的代码时,也可以从它用到的语法反推它依赖的是哪一版标准。
十三、对 short、long、signed、unsigned 的补充
前文为了让行文连贯,直接带出了 short、long、unsigned、signed 这几个关键字。严格来说,它们并不是独立的“数据类型”,而是类型修饰符——附加在基本类型前面,用来改变它的存储长度或取值范围。它们的具体作用前文已经讲过,这里只作几点补充。
1. 谁能被修饰
short 和 long 用来调整整型的“长度”,因此主要用于整型:
-
short只能修饰int:short int,通常简写为short; -
long可以修饰int,也可以修饰double:long int、long double。
也就是说,short、long 都不会用来修饰 char;但要注意“long 只修饰整型”并不准确——它是唯一能修饰浮点型的长度关键字(long double)。而 signed / unsigned 与它们不同,可以修饰 char(signed char、unsigned char)和各种整型。
2. 长度关系
-
long int的长度大于等于int,不一定大于——例如 64 位 Windows 上两者都是 4 字节; -
long、long long以及指针类型的长度,与平台采用的数据模型有关; -
long double的长度更是随平台而变:MSVC 下与double同为 8 字节,x86 架构的 Linux / GCC 下常为 16 字节。
3. 什么是数据模型
数据模型(data model) 指的是:在特定的操作系统、CPU 架构和 ABI(应用程序二进制接口)下,对 C/C++ 中基本整数类型(short、int、long、long long)以及指针类型位宽(字节数)的一套约定。它的名字通常由各类型的宽度缩写拼成,例如 LP64 表示 long 与 pointer 是 64 位、int 是 32 位。常见的有:
| 数据模型 | int | long | long long | 指针 | 典型平台 |
|---|---|---|---|---|---|
| LP64 | 4 | 8 | 8 | 8 | 64 位 Linux、macOS |
| LLP64 | 4 | 4 | 8 | 8 | 64 位 Windows |
| ILP32 | 4 | 4 | 8 | 4 | 32 位系统 |
(单位:字节)
这就解释了为什么同一句 sizeof(long),在 Windows 下得 4、在 Linux 下得 8——不是编译器“任性”,而是它们各自遵循的数据模型不同。写跨平台代码时,不要对 long 的具体宽度做假设;确实需要固定宽度时,可用 <stdint.h> 里的 int32_t、int64_t 等定长类型,或直接用 sizeof 量一量。
这一部分内容作了解。
4. sizeof 的返回值类型:size_t
sizeof 的返回值,C 语言只规定它是一个无符号整数,并没有规定具体是哪种类型,而是留给系统自己决定:可能是 unsigned int,可能是 unsigned long,也可能是 unsigned long long,对应的 printf 占位符分别是 %u、%lu、%llu。这就给程序的可移植性带来了麻烦。
为此,C 语言引入了一个类型别名 size_t,用它来统一表示 sizeof 的返回值类型:在当前系统上,size_t 究竟是 unsigned int 还是 unsigned long long,由系统自行决定,程序员只要写 size_t 即可。size_t 类型的值,打印时使用的占位符是 %zu。
十四、ASCII 字符集
接下来介绍一个很重要的概念——ASCII 字符集。
在前文的叙述中,代码直接使用了 0、9、a、z 这些字符来定义变量,显然没有报错;可我们又在最开始提到,计算机使用的是二进制语言。按道理,这样的代码应该出错才对。这个疑问,就是靠 ASCII 字符集解决的。
在 ASCII 字符集中,规定了 128 个字符各自对应的数值(完整对照表如下图),后来 ASCII 码常被扩展为 256 个字符,目的是兼容更多国家语言中的字符。
这些字符大致可以分成三段:
| 范围 | 内容 |
|---|---|
| 0 ~ 31、127 | 控制字符(如换行 \n 对应 10) |
| 32 ~ 126 | 可显示字符(空格、数字、字母、标点符号) |
| 128 ~ 255 | 扩展 ASCII,由各国自行定义 |
说到底,这个字符集就是一种特殊的映射关系——它把每个字符都映射成了一个整数。
到了这里,也不难发现:既然字符都能对应一个整数,那么字符类型其实也是一种整型。换句话说,char 本质上就是一个(通常占 1 字节的)小整数,'A' 只是 65 的一种“写法”而已。这也解释了为什么 char 可以参与整数运算,以及为什么前文说 signed char 的范围是 -128 ~ 127。
我们不需要记住所有映射对应关系,使用时查表即可,不过最好记住数字和大小写字母这几组的规律:
| 字符 | ASCII 值 |
|---|---|
'0' ~ '9'
|
48 ~ 57 |
'A' ~ 'Z'
|
65 ~ 90 |
'a' ~ 'z'
|
97 ~ 122 |
三组都是连续排列的:数字最小、大写字母居中、小写字母最大;并且 'a' 恰好比 'A' 大 32。



Top comments (0)