C语言学习笔记¶
本笔记内容主要来源于《C Primer Plus》。
00.前言¶
本节为学习动机、语言背景等内容,与具体语法无关,若想快速上手可跳过。
0.0 作者的话¶
很感谢你能在茫茫互联网之中看到我的这篇笔记,记录笔记同时也是我学习的过程。我之前自学过Python,JavaScript,PHP等编程语言,大多在自学编程中的问题我也遇到过,我会用尽量简洁高效的语言梳理C语言的知识点,如果你有问题,可以在主页找到我的联系方式。
m310ct
2025 年 6 月 11 日 00:41
0.1 C语言的起源¶
C语言由贝尔实验室的丹尼斯·里奇(Dennis Ritchie)和肯·汤普逊(Ken Thompson)于1971年在开发 UNIX 操作系统时设计。
它是在 B 语言的基础上发展而来,是现代多数编程语言的祖先之一。
0.2 为何选择C¶
- 高效性:相对于如 Python 这类解释型语言,C语言会直接编译为机器码,运行效率更高。
- 可移植性:使用标准C编写的程序可以在多种操作系统和硬件平台上编译运行。
- 底层控制力强:C语言既可以操作硬件、管理内存,又比汇编语言更易读、易写,学习成本低。
0.3 学习C语言之后能做什么¶
- 编写系统级软件:如操作系统内核、驱动程序、嵌入式系统等。
- 参与底层开发:如网络协议实现、编译器、虚拟机等。
- 开发嵌入式设备程序:如智能家电、物联网设备上的固件。
- 开发游戏引擎核心模块:许多高性能游戏底层使用 C 或 C++ 编写。
- 刷算法题/参加编程竞赛:C 是高效、紧凑的算法实现语言。
- 逆向工程与漏洞挖掘:C 是理解内存布局、二进制安全的基础。
- 学习操作系统与计算机组成原理:深入理解内存、指针、汇编。
- 移植开源项目或开发高性能库:如参与 Git、Redis、Nginx 等项目。
- 作为跳板语言:C 是理解 C++、Rust、Go 等现代系统语言的基础。
在开始学习之前,希望你已经充满热情。
如果你已经准备好了,那就让我们一起踏上 C 语言的学习之旅吧!
01.Hello World¶
本章内容为C语言的环境配置,包括windows、MacOS、Linux平台,以及C语言程序从代码的编译过程
1.1 你的第一个程序¶
# include <stdio.h>
int main(){
printf("Hello World");
return 0;
}
现在你不需要知道这个程序中代码的具体意思,你只需要在你的电脑上编译并且运行这个程序。
1.2 C语言程序编译过程¶
C语言程序的编译分为四个阶段:
- 预处理:执行宏替换、头文件展开、条件编译等操作,
#include
,#define
,#ifdef
等指令会被先执行。 - 编译:把C语法(如变量定义、函数调用)翻译成汇编指令,不涉及CPU具体编码,只是人类可读的汇编语言。
- 汇编:把汇编代码翻译成机器码,生成与 CPU 架构对应的二进制指令。
- 链接:把目标文件与库函数链接成最终可执行程序。
1.3 配置开发环境¶
MacOS/Linux已经预安装gcc,打开终端即可使用。
Windows¶
CLion/VSCode¶
-
下载CLion:CLion: A Cross-Platform IDE for C and C++ by JetBrains
-
下载并安装vscode:Visual Studio Code - Code Editing. Redefined,代码编辑器,下载后默认安装。建议关闭AI代码补全。
-
CLion和VScode二选一,但是都需要配置C环境,电脑性能不好建议选择VScode,强烈不建议 VScode Studio,太笨重了
配置C环境¶
-
下载并安装 MSYS2:https://www.msys2.org/,类 UNIX 环境开发平台,下载后默认安装,安装时可修改安装目录,记录默认目录,后续要用
-
打开MSYS2,输入以下命令:
pacman -Syu # 初始更新(可能需重启)
pacman -S mingw-w64-ucrt-x86_64-gcc gdb make
-
将MSYS2添加到环境变量。win+s,搜索
编辑系统环境变量
,点击环境变量
,选中系统变量
中的Path
,编辑
,新建
,输入MSYS2的路径下/ucrt64/bin的完整路径,比如我的是E:\SDK\MSYS2\ucrt64\bin
,确定
。 -
打开CMD,输入
gcc --version
,显示版本号即为成功。
1.4 gcc编译程序¶
单文件编译¶
gcc hello.c -o hello
./hello # Windows: hello.exe
-
gcc
:调用编译器 -
hello.c
:源文件 -
-o hello
:输出文件名(不加会默认生成a.out
)
请直接再cmd或终端中运行程序,不然程序可能会一闪而过!
编译多个文件¶
gcc main.c utils.c -o app
./app
添加调试信息¶
gcc -g main.c -o debug_app
gdb ./debug_app
-g
:包含调试信息,用于 GDB/LLDB 调试器
常用编译选项¶
选项 | 含义 |
---|---|
-o <file> |
指定输出文件名 |
-g |
添加调试信息 |
-Wall |
打开所有警告 |
-Werror |
把警告当成错误 |
-O2 / -O3 |
启用优化 |
-std=c99 / -std=c11 |
指定 C 标准版本 |
-I <dir> |
添加头文件搜索路径 |
-L <dir> / -l<lib> |
添加库搜索路径 / 链接库 |
现在你已经成功完成了 C 语言的第一个程序,并了解了编译原理和开发环境的配置!
接下来,将正式进入语法与实战阶段的学习。
02.C语言程序的基本构成¶
本章通过一个极简示例介绍C程序的基本组成部分——头文件、
main()
函数、变量声明、printf()
、格式控制符%d
、转义字符\n
。
/*我的C语言练习程序
自我介绍程序*/
#include <stdio.h>
int main(){
int age;//声明age
age = 17; //age变量表示年龄
printf("你好我是m310ct\n");
printf("我今年%d岁了",age);
return 0;
}
这是一段简单输出自我介绍的代码,你可以先编译在终端中运行看看效果。代码运行成功你将看到如下图的结果。下文将详细解释这段代码。
⚠️乱码问题¶
如果在cmd中运行出现乱码,主要引起的原因是vscode自动以utf-8保存,而windows的默认编码是gbk。在运行程序前输入chcp 65001
将cmd的编码改成utf-8。
CLion中出现乱码,请将右下角UTF-8转换为gbk
2.1 引入头文件¶
#include <stdio.h>
这行代码的用途就是引入头文件。
#include
- 预处理器指令。这个指令放在代码的最前面,在编译器的预处理步骤中执行。除了引入头文件,还可以宏定义,条件编译等。类似的预处理器指令还有
#define
(宏定义)、#ifdef
(条件编译)等。
stdio.h
-
这里用尖括号包裹的就是头文件。头文件以.h作为文件后缀,用来声明函数、变量、结构体等接口的,帮助编译器知道你用的东西“长什么样”。
-
stdio的全称是标准输入输出(standar input output)其中存放了常用的一些函数,比如打印用到的函数
printf()
就存放在其中,打印的本质就是输出。如果你引入stdio.h你就无法在程序中使用打印函数。
这里你可能想问:为什么不内置输出函数呢?答:并非所有程序都需要打印功能,这体现出C语言“轻装上阵”的哲学。
2.2 main()函数¶
int main(){
/*skip*/
return 0;
}
-
main()
函数是C语言程序的入口函数,程序从main()
函数开始执行,以return 0;
结尾。 -
int:C语言中的数据类型,代表整数,后文会讲解。
-
大括号:大括号表示其中的内容为这个函数的代码。
return 0¶
-
return
表示程序运行完后将结果返回给操作系统。 -
return 0;
:表示程序正常结束(成功)。 -
return 1;
或其他非零值:表示程序出错或异常退出。 -
这个返回值叫做“退出码”,可以被操作系统或其他程序读取,用来判断程序是否运行成功。
2.3 变量¶
int age;//声明age
age = 17; //age变量表示年龄
声明¶
int age;
就是一个声明语句。声明就是告诉电脑要在内存中开辟一块空间,为之后存取数据所用。声明时要明确数据类型, 以后变量只能存放对应数据类型的数据。
关于数据类型的知识在下一章。
赋值¶
使用赋值运算符=
来给变量进行赋值。简而言之,就是将=
右边的值给到=
左边的变量。
初始化变量¶
初始化就是在声明变量的同时给予变量一个初始值。
int age = 17;
2.4 字符串打印¶
printf("你好我是m310ct\n");
printf("我今年%d岁了",age);
printf函数¶
printf函数的作用是格式化输出各种数据,以字符串的形式打印到终端
注意 :printf打印字符串时需要用英文状态的双引号包裹,这是新手常见错误!
03. 数据类型¶
本章将介绍 C 语言中的数据类型、
sizeof
函数与scanf
输入函数。
3.1 数据类型的作用¶
在之前的代码中,我们已经在定义函数时接触过了数据类型——int
。
数据类型的作用是告诉计算机:这个值是什么类型、可以进行哪些操作、以及应该如何在内存中存储它。
3.2 int
类型¶
int
是有符号整数类型,可以存储正整数、负整数或 0。
八进制和十六进制¶
- 八进制:逢 8 进 1,比如十进制下的 9 转换为八进制是
11
。 - 十六进制:逢 16 进 1,超过 9 的部分用字母
a-f
表示,例如十进制的 15 转为十六进制是f
。
在 C 语言中,要输出不同进制的数,只需将 %d
(十进制)替换为:
- %o
:八进制输出
- %x
:十六进制输出
3.3 其他整数类型¶
C 语言提供 short
、long
、unsigned
等关键字来修饰 int
类型,表示不同的存储大小或取值范围:
short int
或short
:占用的存储空间通常比int
少。long int
或long
:用于存储更大的整数。long long int
或long long
:通常占用至少 64 位空间,用于极大数值。unsigned int
或unsigned
:只能存储非负整数(0 及正数)。- 同样也有
unsigned long
、unsigned long long
等组合形式。
(具体大小依平台而定,见后续表格)
3.4 打印整数类型¶
下面是各种整数类型的输出格式说明:
类型 | 十进制 | 十六进制 | 八进制 | 无符号输出 |
---|---|---|---|---|
int |
%d |
%x |
%o |
%u |
long |
%ld |
%lx |
%lo |
%lu |
short |
%hd |
%hx |
%ho |
%hu |
long long |
%lld |
%llx |
%llo |
%llu |
示例代码:¶
#include <stdio.h>
int main(){
short a = 1;
int b = 100;
long c = 1000000;
long long d = 10000000000000;
printf("我是 a,十进制:%hd,十六进制:%hx,八进制:%ho\n", a, a, a);
printf("我是 b,十进制:%d,十六进制:%x,八进制:%o\n", b, b, b);
printf("我是 c,十进制:%ld,十六进制:%lx,八进制:%lo\n", c, c, c);
printf("我是 d,十进制:%lld,十六进制:%llx,八进制:%llo\n", d, d, d);
return 0;
}
⚠️ 注意:输出结果依赖于平台(如 32 位或 64 位系统),不同平台下类型大小可能略有差异。
运行结果:
我是 a,十进制:1,十六进制:1,八进制:1
我是 b,十进制:100,十六进制:64,八进制:144
我是 c,十进制:1000000,十六进制:f4240,八进制:3641100
我是 d,十进制:10000000000000,十六进制:9184e72a000,八进制:221411634520000
3.5 char
字符类型¶
char
类型用于表示单个字符,但本质上它是一种整数类型,每个字符都有一个整数编码值(ASCII)。
ASCII 简介¶
在 C 语言中,字符会被转换为对应的 ASCII 码值。通过%c
打印字符,例如:
char a = 'A'; // 实际存储的是整数 65
printf("%c",a);
完整的 ASCII 表如下:
你可以通过字符直接赋值,无需记忆码表:
char ch1 = 'A'; // 存储 ASCII 码 65
char ch2 = 66; // 直接存储整数 66,输出时为 'B'
转义字符¶
有些字符(比如换行、引号等)不能直接写在字符串中,需要使用转义字符(以 \
开头):
转义字符 | 含义 | 示例说明 |
---|---|---|
\n |
换行 | 输出后光标跳到下一行 |
\t |
制表符(Tab) | 相当于按了一个 Tab 键 |
\\ |
反斜杠字符 \ |
输出一个反斜杠 |
\' |
单引号 ' |
输出一个单引号 |
\" |
双引号 " |
输出一个双引号 |
\r |
回车符(光标回到行首) | 通常配合 \n 用于 Windows 换行 |
\b |
退格符(删除前一个字符) | 有些终端有效,等价于 Backspace |
\a |
响铃(beep) | 有些终端会响一声或闪烁 |
\0 |
空字符(null terminator) | 表示字符串结束,用于字符串结尾标志 |
3.6 浮点类型¶
小数或科学计数法的类型就是浮点型,浮点型包括float
,double
和long double
。
科学计数法¶
当一个数过大或过小,位数过多难以直接表达时便使用科学计数法。比如在数学中,10000可表示为 $1 \times 10^5$,0.00321则表示为 $3.21 \times 10^-3$
在C语言中,用e代表$10^n$,比如$1 \times 10^5$就是1e5
,$3.21 \times 10^-3$ 就是3.21e-3。
各种浮点类型¶
- float:单精度浮点数,至少表示 6 位有效数字,打印时使用 %f(普通浮点数格式)或 %e(科学计数法格式)。
- double:双精度浮点数,至少表示 15 位有效数字,打印时同样使用 %f 或 %e
- long double:扩展精度浮点数,至少表示 18 位有效数字,打印时使用 %Lf(普通格式)或 %Le(科学计数法格式)。
浮点数溢出¶
当浮点数的值超出其类型所能表示的最大限度(比如 float
的最大值约为 ( 3.4 \times 10^{38} ))时,计算结果会变成 inf
,这称为 上溢(Overflow)。
值得注意的是:只有当计算结果本身超出最大范围才会溢出,例如将 3.0e38f
乘以 2
会得到 inf
。但如果只是对一个非常大的浮点数加上一个相对较小的值(如 3.4e38f + 1000
),由于 float
精度有限,这个加数可能被忽略,因此 不会造成溢出,只是“精度丢失”。
类似地,如果浮点数的结果非常接近 0
,小于其最小的非零正数(对 float
而言约为 ( 1.4 \times 10^{-45} )),就会变成 0.000000
,这称为 下溢(Underflow)。
例如将 1.0e-40f
连续除以很大的数,最终结果可能过小到无法表示,就会发生下溢。
float large = 3e38f;
float overflow = large * 2;
// 浮点下溢:小于 float 最小非零正数约为 1.4e-45
float small = 1.0e-40f;
float underflow = small / 1.0e10f;
printf("上溢示例:%f\n", overflow); // 输出 inf
printf("下溢示例:%f\n", underflow); // 输出 0.000000
return 0;
浮点数精度误差¶
浮点数不能进行完全精确的计算:
double a = 0.1;
double b = 0.2;
double c = a + b;
printf("%.20f",c);//输出:0.30000000000000004441,这里.20是保留20位小数的意思
由于 float 使用有限的 23 位二进制尾数(mantissa)表示有效数字,当数值过大时,它的最小可区分单位也变大,导致较小的加数(如 +1)无法被表示,从而被舍弃。举一个例子:你用一个只能显示 8位数 的计算器,它能显示 99999999,那你再按 +1 它还是 99999999,因为已经“装不下”更小的差别了。