DEV Community

qiaqiabuqia
qiaqiabuqia

Posted on AI-assisted

C语言--初步理解C语言

理解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 结尾的文本文件里。文本文件本身不能执行我们想要的操作,要经过两道处理:

  1. 编译:编译器(VS 里是 cl.exe)把每个 .c 文件编译成对应的 .obj 目标文件;
  2. 链接:链接器(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;
}
Enter fullscreen mode Exit fullscreen mode

据说,这段代码会给每一个程序员带来好运。初学看不懂很正常,照着敲就好。实际上,当你安装完程序,除了所谓的传统外,敲这样一段代码也是为了检验你的集成环境是否正常。

写的时候记得把输入法切到英文——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;
Enter fullscreen mode Exit fullscreen mode

它的意思是:把整数 10 存放到整型变量 a 中。以等号为界——

  • 等号左边是 int a,表示定义了一个变量,名字是 a,数据类型是 int;
  • 等号右边是数字 10,显然是一个整数;
  • 结合等号的含义,就是说整型变量 a 的值是 10。

这里提到的“存放数据”“赋值”“变量的值是……”,其实都是一个意思,不必过于深究。最规范的说法是将 10 赋值给变量 a;为了接收整数 10,我们把 a 的类型规定为整型,其他说法都只是便于接受。

也可以拆成两句来写:

int a;
a = 10;
Enter fullscreen mode Exit fullscreen mode

这两句和上面的 int a = 10; 在 C 语言中是等价的,一般我们使用第一种。

3. 字符型与浮点型的定义

仿照整型,也可以定义字符型和浮点型变量:

char c = 'A';        // 定义字符型变量 c,存放字符 'A'
float f = 3.14f;     // 定义浮点型变量 f,存放小数 3.14
Enter fullscreen mode Exit fullscreen mode

[!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
Enter fullscreen mode Exit fullscreen mode

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;    // 无符号更长整型
Enter fullscreen mode Exit fullscreen mode

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;  // 无符号字符型
Enter fullscreen mode Exit fullscreen mode

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;   // 长双精度
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

这样写的好处是:即使换到 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
Enter fullscreen mode Exit fullscreen mode

所以,只要包含了 <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;
}
Enter fullscreen mode Exit fullscreen mode

十一、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;
}
Enter fullscreen mode Exit fullscreen mode

[!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;
}
Enter fullscreen mode Exit fullscreen mode

在 Windows 64 位平台 + VS2026 下,预期输出为:

char        : 1 字节
short       : 2 字节
int         : 4 字节
long        : 4 字节
long long   : 8 字节
float       : 4 字节
double      : 8 字节
long double : 8 字节
Enter fullscreen mode Exit fullscreen mode

结果与前两节表格里的“典型大小”完全对得上:

类型 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)