C Primer Plus 第6版
C Primer Plus 第6版
前言
《C Primer Plus》是一本经典的C语言入门教材,由Stephen Prata编写,第6版于2012年出版。这本书以清晰、系统的方式介绍了C语言的各个方面,从基础概念到高级特性,适合初学者和有一定编程经验的开发者。
本学习笔记基于《C Primer Plus 第6版》,但在原有基础上进行了深度扩展,融入了更多底层原理、高级编程技巧和工程实践经验。笔记内容不仅包括各章节的主要知识点、代码示例、学习重点和注意事项,还增加了底层实现细节、性能优化策略、内存管理深入分析、实际项目案例以及C语言标准的最新发展和特性。
适用人群
- 高级C语言开发者:希望深入理解C语言底层原理和实现细节的专业人士
- 系统软件工程师:需要编写高性能、高可靠性系统代码的工程师
- 嵌入式系统开发者:在资源受限环境中工作的嵌入式开发人员
- 计算机科学专业学生:希望从理论到实践全面掌握C语言的学生
- 代码审查人员:需要快速识别C代码中潜在问题的专业人士
核心价值
- 深度解析:从编译器实现、内存模型等底层视角解析C语言特性
- 实战导向:提供大量实际项目中的代码示例和优化案例
- 性能优化:详细介绍C语言性能优化的原理和实践方法
- 内存管理:深入探讨C语言内存管理的最佳实践和常见陷阱
- 标准演进:涵盖C89/C90、C99、C11、C17等标准的重要变化
本笔记的目标是帮助读者不仅掌握C语言的语法和基本用法,更能理解其设计原理和实现机制,从而能够编写出更高效、更可靠、更易于维护的C语言代码。
第1章 初步认识C语言
1.1 C语言的起源与发展
起源:C语言由 Dennis Ritchie 于 1972 年在贝尔实验室开发,用于重写 UNIX 操作系统。C语言的设计灵感来源于 B 语言和 BCPL 语言,但在其基础上进行了重大改进,增加了数据类型、结构体等特性,使其更适合系统编程。
标准演进:
- ANSI C (C89/C90):1989年发布的第一个官方标准,奠定了C语言的基本语法和特性
- C99:1999年发布的标准,增加了许多现代编程语言特性,如内联函数、可变长度数组、复合字面量等
- C11:2011年发布的标准,增加了线程支持、原子操作、泛型选择表达式等
- C17:2017年发布的标准,主要是对C11的修正和完善,没有引入新特性
- C23:计划中的新标准,将引入更多现代特性
设计哲学:
- 接近硬件:提供直接访问内存和硬件的能力,使程序员能够编写高效的代码
- 简洁明了:语法简洁,关键字少,规则明确,易于理解和实现
- 可移植性:通过标准库和预处理指令,使代码能够在不同平台上编译运行
- 表达能力强:支持低级操作和高级抽象,能够表达复杂的算法和数据结构
- 信任程序员:给予程序员极大的自由,同时也要求程序员对自己的代码负责
1.2 编程的本质
编程:是将人类的意图转换为计算机可执行的指令序列的过程,涉及问题分析、算法设计、代码实现、测试调试等多个环节
算法:解决问题的步骤集合,具有确定性、有限性、可行性、输入和输出等特征
程序:用编程语言表达的算法,是算法在计算机上的具体实现
编译器:将源代码转换为机器语言的工具,是连接人类思维和计算机执行的桥梁
- 编译过程:预处理、词法分析、语法分析、语义分析、中间代码生成、优化、目标代码生成、链接
- 编译优化:常量折叠、死代码消除、循环展开、内联函数、寄存器分配等
程序执行模型:
- 存储模型:代码段、数据段、堆、栈
- 执行流程:从 main 函数开始,顺序执行语句,处理函数调用,直到 main 函数返回
1.3 C语言的应用领域
系统软件:操作系统内核、编译器、汇编器、链接器、数据库管理系统
- 案例:Linux 内核、GCC 编译器、SQLite 数据库
嵌入式系统:微控制器、智能家居设备、汽车电子系统、医疗设备
- 特点:资源受限,需要高效、可靠的代码
游戏开发:游戏引擎、物理模拟、图形渲染、AI 逻辑
- 案例:Unity 游戏引擎的 C/C++ 核心、Unreal Engine
科学计算:数值分析、模拟仿真、信号处理、图像处理
- 优势:执行速度快,内存使用效率高
网络编程:服务器、网络协议实现、网络安全工具
- 案例:Apache HTTP Server、OpenSSL 库
高性能计算:超级计算机应用、分布式计算、并行处理
- 优势:可以直接控制硬件资源,充分发挥硬件性能
1.4 学习C语言的准备
编译器工具链:
- GCC (GNU Compiler Collection):开源编译器,支持多种平台和语言
- Clang/LLVM:现代化编译器,具有良好的错误提示和静态分析能力
- Visual Studio:集成开发环境,提供丰富的调试和分析工具
- ICC (Intel C++ Compiler):针对 Intel 处理器优化的编译器
开发工具:
- 编辑器:VS Code、Sublime Text、Vim、Emacs
- 调试器:GDB、LLDB、Visual Studio Debugger
- 性能分析工具:Valgrind、Perf、Intel VTune
- 静态分析工具:Clang Static Analyzer、Coverity
构建系统:
- Make:传统的构建工具
- CMake:跨平台构建系统
- Ninja:高速构建系统
操作系统:Windows、Linux、macOS 均可,但 Linux 平台更适合深入学习C语言的底层特性
1.5 第一个C程序
1 |
|
代码解析:
#include <stdio.h>:包含标准输入输出头文件,引入 printf 等函数的声明int main(void):主函数,程序的入口点,int表示返回类型为整数,void表示不接受参数printf("Hello, World!\n"):调用标准库函数 printf,输出字符串到控制台,\n表示换行符return 0:返回值 0,表示程序正常结束,非零值表示错误或异常情况
编译与执行过程:
- 预处理:处理 #include 指令,将 stdio.h 的内容插入到源文件中
- 编译:将预处理后的源代码编译为目标代码(.o 文件)
- 链接:将目标代码与标准库链接,生成可执行文件
- 执行:操作系统加载可执行文件到内存,开始执行 main 函数
内存布局:
- 代码段:存储可执行指令,包括 main 函数和 printf 函数的代码
- 数据段:存储全局变量和静态变量
- 栈:存储局部变量和函数调用信息,包括 main 函数的栈帧
- 堆:动态内存分配区域,本程序未使用
执行流程:
- 操作系统调用 main 函数
- main 函数调用 printf 函数
- printf 函数输出字符串 “Hello, World!\n” 到标准输出
- printf 函数返回,main 函数继续执行
- main 函数执行 return 0,返回操作系统
- 操作系统接收返回值,结束程序执行
第2章 C语言概述
2.1 C程序的结构
预处理指令:以
#开头的指令,在编译前由预处理器处理- 文件包含:
#include <header.h>或#include "header.h" - 宏定义:
#define MACRO value,可带参数 - 条件编译:
#if、#ifdef、#ifndef、#else、#elif、#endif - 其他指令:
#undef、#line、#error、#pragma
- 文件包含:
函数:程序的基本执行单元,由函数声明、函数定义和函数调用组成
- 主函数:
int main(void)或int main(int argc, char *argv[]),程序的入口点 - 用户定义函数:由返回类型、函数名、参数列表和函数体组成
- 库函数:由标准库或第三方库提供的函数
- 主函数:
语句:
- 表达式语句:由表达式加分号组成,如
x = 5; - 复合语句:用花括号括起来的语句块,如
{ int x = 5; printf("%d\n", x); } - 控制语句:
if、for、while、do-while、switch、break、continue、goto、return - 声明语句:用于声明变量或函数,如
int x;
- 表达式语句:由表达式加分号组成,如
注释:
- 块注释:
/* 注释内容 */,可跨行 - 行注释:
// 注释内容,仅单行(C99 及以上) - 文档注释:用于生成文档的特殊注释格式,如
/** 文档注释 */
- 块注释:
2.2 C语言的基本语法
标识符:变量、函数、常量、类型等的名称
- 命名规则:只能由字母、数字和下划线组成,不能以数字开头,区分大小写
- 命名约定:
- 变量和函数:小写字母加下划线(snake_case),如
int user_id; - 常量:全部大写加下划线,如
#define MAX_BUFFER_SIZE 1024 - 类型名:首字母大写或全部大写,如
typedef struct Node Node;或#define BOOL int
- 变量和函数:小写字母加下划线(snake_case),如
- 保留标识符:以下划线开头的标识符通常为系统保留,应避免使用
关键字:C语言保留的特殊单词,具有特定的语法含义
- 存储类别关键字:
auto、register、static、extern、typedef - 类型关键字:
void、char、short、int、long、float、double、_Bool、_Complex、_Imaginary - 控制流关键字:
if、else、switch、case、default、for、while、do、break、continue、goto、return - 其他关键字:
const、restrict、volatile、inline、_Noreturn、_Thread_local
- 存储类别关键字:
运算符:
- 算术运算符:
+、-、*、/、%、++、-- - 关系运算符:
==、!=、<、>、<=、>= - 逻辑运算符:
&&、||、! - 位运算符:
&、|、^、~、<<、>> - 赋值运算符:
=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>= - 条件运算符:
?: - 逗号运算符:
, - 指针运算符:
*、& - 下标运算符:
[] - 成员运算符:
.、-> - 括号运算符:
() - 大小运算符:
sizeof、_Alignof
- 算术运算符:
分隔符:用于分隔语法元素
{}:用于定义块和复合语句():用于函数调用和表达式分组[]:用于数组下标,:用于分隔参数和表达式;:用于结束语句::用于标签和 case 语句
2.3 数据类型
基本数据类型:
- 整数类型:
char:通常为 1 字节,可表示字符或小整数short:通常为 2 字节int:通常为 4 字节long:至少 4 字节long long:至少 8 字节(C99 及以上)- 无符号变体:
unsigned char、unsigned short、unsigned int、unsigned long、unsigned long long
- 浮点类型:
float:单精度浮点数,通常为 4 字节double:双精度浮点数,通常为 8 字节long double:扩展精度浮点数,通常为 12 或 16 字节
- 布尔类型:
_Bool:C99 引入的布尔类型,只能存储 0 或 1- stdbool.h:定义了
bool、true、false宏
- 复数类型:
float _Complex、double _Complex、long double _Complex(C99 及以上)float _Imaginary、double _Imaginary、long double _Imaginary(C99 及以上)
- 整数类型:
派生数据类型:
- 数组:相同类型元素的集合,如
int arr[10]; - 指针:存储内存地址的变量,如
int *p; - 结构体:不同类型成员的集合,如
struct Person { char name[50]; int age; }; - 联合体:所有成员共享同一块内存的类型,如
union Data { int i; float f; char c; }; - 枚举:一组命名的整数常量,如
enum Color { RED, GREEN, BLUE }; - 函数类型:由返回类型和参数列表决定的类型
- 数组:相同类型元素的集合,如
类型修饰符:
const:修饰的对象不可修改restrict:修饰的指针是访问其指向对象的唯一方式(C99 及以上)volatile:修饰的对象可能被外部因素修改,禁止编译器优化_Atomic:修饰的对象支持原子操作(C11 及以上)
2.4 变量和常量
变量:
- 声明:
int age;,告诉编译器变量的类型和名称 - 定义:为变量分配内存空间,如
int age;(声明并定义)或extern int age;(仅声明) - 初始化:在定义时赋予初始值,如
int age = 18; - 存储类别:
auto:自动存储类别,默认,存储在栈中register:寄存器存储类别,建议编译器将变量存储在寄存器中static:静态存储类别,存储在静态存储区,生命周期为整个程序extern:外部存储类别,用于声明在其他文件中定义的变量
- 声明:
常量:
- 字面常量:直接出现在代码中的值,如
100(整型)、3.14(浮点型)、'A'(字符型)、"Hello"(字符串型) - 符号常量:用
#define定义的常量,如#define PI 3.14159 - 常量变量:用
const修饰的变量,如const int MAX_AGE = 100; - 枚举常量:枚举类型的成员,如
enum Color { RED, GREEN, BLUE };中的RED、GREEN、BLUE - 宏常量 vs const 常量:
- 宏常量:在预处理阶段替换,无类型检查,可能导致副作用
- const 常量:在编译阶段处理,有类型检查,更安全
- 字面常量:直接出现在代码中的值,如
2.5 输入输出
标准输入输出:
- 输出函数:
printf():格式化输出函数,返回输出的字符数putchar():输出单个字符,返回输出的字符puts():输出字符串并自动添加换行符,返回非负值或 EOF
- 输入函数:
scanf():格式化输入函数,返回成功读取的项目数getchar():读取单个字符,返回读取的字符或 EOFgets():读取一行字符串(不安全,已被弃用)fgets():读取一行字符串,可指定最大读取长度,更安全
- 输出函数:
格式化说明符:
- 整数:
%d(十进制)、%o(八进制)、%x/%X(十六进制)、%u(无符号十进制) - 浮点数:
%f(十进制)、%e/%E(科学计数法)、%g/%G(自动选择格式) - 字符:
%c(单个字符) - 字符串:
%s(字符串) - 指针:
%p(指针地址) - 修饰符:
- 宽度:
%5d(至少 5 个字符宽度) - 精度:
%.2f(保留 2 位小数) - 长度:
%ld(长整型)、%lld(长长整型)、%lf(双精度浮点型) - 标志:
%-5d(左对齐)、%+d(显示正负号)、%05d(前导零)、% d(空格替代正号)
- 宽度:
- 整数:
输入输出的底层实现:
- 缓冲区:输入输出操作通常使用缓冲区,提高效率
- 行缓冲:遇到换行符时刷新缓冲区,如标准输入输出
- 全缓冲:缓冲区满时刷新,如文件操作
- 无缓冲:立即输出,如标准错误
- 刷新缓冲区:使用
fflush()函数手动刷新
2.6 程序示例:计算圆的面积
1 |
|
代码分析:
- 使用了更精确的圆周率定义
- 添加了函数注释,遵循文档注释规范
- 增加了输入验证,确保输入有效
- 使用
fprintf(stderr, ...)输出错误信息,区分标准输出和标准错误 - 函数化设计,提高代码复用性和可维护性
- 清晰的错误处理,增强程序健壮性
性能优化:
- 宏定义
PI避免了运行时计算 - 函数
calculate_circle_area简洁高效,适合被编译器内联 - 避免了不必要的变量和计算,减少内存使用和执行时间
可移植性考虑:
- 使用标准 C 语法和库函数,确保在不同平台上的兼容性
- 避免了平台特定的特性和依赖
- 错误处理方式符合标准 C 规范
第3章 数据和C
3.1 数据类型概述
- 位(bit):最小的存储单位,只能存储 0 或 1
- 字节(byte):通常由 8 位组成,是计算机存储的基本单位
- 字(word):计算机一次处理的位数,与处理器架构有关
3.2 整数类型
- 有符号整数:可以表示正数、负数和零
int:通常为 4 字节short:通常为 2 字节long:通常为 4 字节或 8 字节long long:通常为 8 字节(C99 及以上)
- 无符号整数:只能表示非负数
unsigned intunsigned shortunsigned longunsigned long long
3.3 字符类型
- char:通常为 1 字节,用于存储字符
- 有符号字符:范围通常为 -128 到 127
- 无符号字符:范围通常为 0 到 255
- 转义序列:如
\n(换行)、\t(制表符)、\\(反斜杠)等
3.4 浮点类型
- float:单精度浮点数,通常为 4 字节
- double:双精度浮点数,通常为 8 字节
- long double:扩展精度浮点数,通常为 12 或 16 字节
3.5 布尔类型
- _Bool:C99 引入的布尔类型,只能存储 0(假)或 1(真)
- stdbool.h:C99 提供的头文件,定义了
bool、true、false
3.6 类型转换
- 隐式转换:编译器自动进行的转换
- 小类型转换为大类型(如
int到double) - 赋值时,右侧转换为左侧类型
- 运算时,操作数会转换为共同类型
- 小类型转换为大类型(如
- 显式转换:使用强制类型转换运算符
1
2int a = 10;
double b = (double)a;
3.7 sizeof 运算符
- sizeof:返回操作数的大小(以字节为单位)
1
2printf("int 的大小:%zu 字节\n", sizeof(int));
printf("double 的大小:%zu 字节\n", sizeof(double));
第4章 字符串和格式化输入/输出
4.1 字符串的概念
- 字符串:是由零个或多个字符组成的序列,以空字符
'\0'结尾 - 字符串字面量:用双引号括起来的字符序列,如
"Hello"
4.2 字符串的存储
- 字符数组:用于存储字符串的数组
1
char name[20] = "John";
- 字符串长度:不包括空字符的字符个数
1
2
3
4
char name[] = "John";
printf("字符串长度:%zu\n", strlen(name)); // 输出 4
4.3 格式化输出函数 printf()
- 基本语法:
printf(格式字符串, 参数1, 参数2, ...) - 格式说明符:
%d:十进制整数%o:八进制整数%x:十六进制整数%f:浮点数%e:科学计数法%c:字符%s:字符串%p:指针
- 修饰符:
- 宽度:
%5d(至少占 5 个字符宽度) - 精度:
%.2f(保留 2 位小数) - 标志:
%-5d(左对齐)、%+d(显示正负号)等
- 宽度:
4.4 格式化输入函数 scanf()
- 基本语法:
scanf(格式字符串, &变量1, &变量2, ...) - 格式说明符:与
printf()类似 - 注意事项:
- 需要使用地址运算符
&(除了字符串数组) - 会跳过空白字符(空格、制表符、换行符)
- 遇到非匹配字符时停止
- 需要使用地址运算符
4.5 字符串输入函数
- gets():读取一行字符串(不安全,已被弃用)
- fgets():读取一行字符串(安全)
1
2char buffer[100];
fgets(buffer, sizeof(buffer), stdin); - puts():输出字符串并自动添加换行符
1
puts("Hello, World!");
4.6 程序示例:字符串处理
1 |
|
第5章 运算符、表达式和语句
5.1 运算符概述
- 算术运算符:
+、-、*、/、% - 关系运算符:
==、!=、<、>、<=、>= - 逻辑运算符:
&&(与)、||(或)、!(非) - 赋值运算符:
=、+=、-=、*=、/=、%=等 - 自增自减运算符:
++、-- - 条件运算符:
?: - 位运算符:
&、|、^、~、<<、>>
5.2 表达式
- 表达式:由运算符和操作数组成的式子
- 表达式的值:表达式计算的结果
- 表达式的类型:根据操作数和运算符确定
5.3 运算符的优先级和结合性
- 优先级:决定运算符的执行顺序
- 结合性:决定同优先级运算符的执行顺序(从左到右或从右到左)
5.4 语句
- 表达式语句:由表达式加分号组成
1
2x = 5; // 赋值语句
printf("Hello"); // 函数调用语句 - 复合语句:用花括号括起来的语句块
1
2
3
4{
int x = 5;
printf("x = %d\n", x);
} - 空语句:只有一个分号的语句
1
; // 空语句
5.5 类型转换表达式
- 隐式类型转换:编译器自动进行的转换
- 显式类型转换:使用强制类型转换
1
2int a = 10;
double b = (double)a / 3; // 结果为 3.333...
5.6 程序示例:计算器
1 |
|
第6章 C控制语句:循环
6.1 循环的概念
- 循环:重复执行一段代码的结构
- 循环三要素:初始化、条件判断、更新
6.2 while 循环
- 基本语法:
1
2
3while (条件) {
// 循环体
} - 执行流程:
- 检查条件是否为真
- 如果为真,执行循环体
- 重复步骤 1 和 2
- 如果为假,退出循环
6.3 for 循环
- 基本语法:
1
2
3for (初始化; 条件; 更新) {
// 循环体
} - 执行流程:
- 执行初始化
- 检查条件是否为真
- 如果为真,执行循环体
- 执行更新
- 重复步骤 2 到 4
- 如果为假,退出循环
6.4 do-while 循环
- 基本语法:
1
2
3do {
// 循环体
} while (条件); - 执行流程:
- 执行循环体
- 检查条件是否为真
- 如果为真,重复步骤 1 和 2
- 如果为假,退出循环
- 特点:循环体至少执行一次
6.5 循环的嵌套
- 嵌套循环:一个循环包含另一个循环
1
2
3
4
5
6for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= i; j++) {
printf("* ");
}
printf("\n");
}
6.6 循环控制语句
- break:跳出当前循环
1
2
3
4
5
6for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // 当 i=5 时跳出循环
}
printf("%d ", i);
} - continue:跳过当前循环的剩余部分,进入下一次循环
1
2
3
4
5
6for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
printf("%d ", i);
} - goto:跳转到指定标签(尽量避免使用)
1
2
3
4
5
6
7int i = 1;
loop:
printf("%d ", i);
i++;
if (i <= 5) {
goto loop;
}
6.7 程序示例:猜数字游戏
1 |
|
第7章 C控制语句:分支和跳转
7.1 if 语句
- 基本语法:
1
2
3if (条件) {
// 条件为真时执行
} - if-else 语句:
1
2
3
4
5if (条件) {
// 条件为真时执行
} else {
// 条件为假时执行
} - 嵌套 if 语句:
1
2
3
4
5
6
7if (条件1) {
// 条件1为真时执行
} else if (条件2) {
// 条件2为真时执行
} else {
// 所有条件都为假时执行
}
7.2 逻辑运算符
- &&(逻辑与):当且仅当两个操作数都为真时,结果为真
- ||(逻辑或):当至少一个操作数为真时,结果为真
- !(逻辑非):取反操作数的逻辑值
7.3 条件运算符
- 基本语法:
条件 ? 表达式1 : 表达式2 - 执行流程:如果条件为真,返回表达式1的值,否则返回表达式2的值
1
2
3int a = 10, b = 20;
int max = (a > b) ? a : b;
printf("最大值:%d\n", max); // 输出 20
7.4 switch 语句
- 基本语法:
1
2
3
4
5
6
7
8
9
10
11
12switch (表达式) {
case 常量1:
// 代码
break;
case 常量2:
// 代码
break;
// 更多 case
default:
// 代码
break;
} - 注意事项:
- 表达式的值必须是整数类型或枚举类型
- 每个 case 后必须有 break,否则会继续执行下一个 case
- default 是可选的
7.5 程序示例:成绩等级评定
1 |
|
第8章 字符输入/输出和输入验证
8.1 字符输入函数
- getchar():从标准输入读取一个字符
1
2char ch;
ch = getchar(); - scanf():可以读取字符
1
2char ch;
scanf("%c", &ch);
8.2 字符输出函数
- putchar():向标准输出写入一个字符
1
2char ch = 'A';
putchar(ch); - printf():可以输出字符
1
2char ch = 'A';
printf("%c\n", ch);
8.3 缓冲区
- 行缓冲:输入输出会先存储在缓冲区,遇到换行符时才实际读写
- 刷新缓冲区:
- 遇到换行符
- 缓冲区满
- 使用 fflush() 函数
- 程序结束
8.4 输入验证
- 输入验证:确保用户输入的数据符合要求
- 常见验证:
- 数值范围验证
- 数据类型验证
- 格式验证
8.5 程序示例:字符统计
1 |
|
第9章 函数
9.1 函数的概念
- 函数:是完成特定任务的代码块
- 函数的优点:
- 模块化:将复杂任务分解为小任务
- 代码重用:避免重复代码
- 可维护性:便于修改和调试
- 可读性:使代码结构更清晰
9.2 函数的声明和定义
- 函数声明:告诉编译器函数的存在和签名
1
int add(int, int); // 函数声明
- 函数定义:实现函数的具体功能
1
2
3int add(int a, int b) { // 函数定义
return a + b;
}
9.3 函数的参数和返回值
- 参数:函数接收的输入值
- 形式参数(形参):函数定义中的参数
- 实际参数(实参):函数调用时传递的参数
- 返回值:函数执行后返回的结果
- 使用
return语句返回值 - 如果函数没有返回值,使用
void类型
- 使用
9.4 函数调用
- 函数调用:执行函数的代码
1
int result = add(5, 3); // 调用 add 函数,传入实参 5 和 3
9.5 函数的作用域
- 局部变量:在函数内部定义的变量,只在函数内部可见
- 全局变量:在函数外部定义的变量,在整个程序中可见
9.6 递归函数
- 递归:函数调用自身的过程
- 递归的条件:
- 终止条件:避免无限递归
- 递归步骤:将问题分解为更小的子问题
- 示例:计算阶乘
1
2
3
4
5
6int factorial(int n) {
if (n <= 1) {
return 1; // 终止条件
}
return n * factorial(n - 1); // 递归调用
}
9.7 函数的存储类别
- auto:自动变量,默认存储类别
- static:静态变量,在函数调用之间保持值
- extern:外部变量,声明在其他文件中定义的全局变量
- register:寄存器变量,建议编译器将变量存储在寄存器中
9.8 程序示例:斐波那契数列
1 |
|
第10章 数组和指针
10.1 数组的概念
- 数组:是相同类型元素的集合
- 数组的声明:
1
int numbers[5]; // 声明一个包含 5 个整数的数组
- 数组的初始化:
1
2int numbers[5] = {1, 2, 3, 4, 5}; // 初始化数组
int numbers[] = {1, 2, 3, 4, 5}; // 编译器自动计算大小
10.2 数组的访问
- 数组元素:通过索引访问,索引从 0 开始
1
2
3int numbers[5] = {1, 2, 3, 4, 5};
printf("第一个元素:%d\n", numbers[0]); // 输出 1
printf("最后一个元素:%d\n", numbers[4]); // 输出 5 - 数组的边界:C语言不检查数组边界,访问越界会导致未定义行为
10.3 多维数组
- 二维数组:数组的数组
1
2
3
4
5int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
}; - 访问二维数组元素:
1
printf("matrix[1][2] = %d\n", matrix[1][2]); // 输出 7
10.4 指针的概念
- 指针:是存储内存地址的变量
- 指针的声明:
1
int *p; // 声明一个指向整数的指针
- 指针的初始化:
1
2int x = 10;
int *p = &x; // 初始化指针 p,使其指向变量 x
10.5 指针的运算
- 解引用运算符:
*,用于获取指针指向的值1
2
3int x = 10;
int *p = &x;
printf("x 的值:%d\n", *p); // 输出 10 - 取地址运算符:
&,用于获取变量的地址1
2int x = 10;
printf("x 的地址:%p\n", &x); - 指针算术:
- 指针加减整数:根据指针类型移动相应的字节数
- 指针比较:比较指针指向的地址
10.6 数组和指针的关系
- 数组名:是指向数组第一个元素的指针常量
1
2int numbers[] = {1, 2, 3, 4, 5};
int *p = numbers; // 等同于 p = &numbers[0] - 通过指针访问数组元素:
1
2
3
4int numbers[] = {1, 2, 3, 4, 5};
int *p = numbers;
printf("第一个元素:%d\n", *p); // 输出 1
printf("第二个元素:%d\n", *(p + 1)); // 输出 2
10.7 指针数组
- 指针数组:是存储指针的数组
1
2
3
4
5char *names[] = {
"John",
"Mary",
"Tom"
};
10.8 函数与数组
- 数组作为函数参数:
1
2
3
4
5
6void print_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
} - 注意:数组作为参数时,会退化为指针,失去大小信息
10.9 程序示例:数组排序
1 |
|
第11章 字符和字符串
11.1 字符的表示
- ASCII 码:美国信息交换标准代码,用 7 位二进制表示字符
- 字符常量:用单引号括起来的单个字符,如
'A' - 转义序列:用于表示特殊字符,如
'\n'(换行)
11.2 字符串的表示
- 字符串:由零个或多个字符组成的序列,以空字符
'\0'结尾 - 字符串字面量:用双引号括起来的字符序列,如
"Hello"
11.3 字符串的处理函数
- string.h:提供字符串处理函数的头文件
- 常见函数:
strlen():计算字符串长度strcmp():比较两个字符串strcpy():复制字符串strcat():连接字符串strchr():查找字符在字符串中的位置strstr():查找子字符串在字符串中的位置
11.4 字符串的输入和输出
- 输入字符串:
scanf("%s", str):读取字符串,遇到空白字符停止fgets(str, size, stdin):读取一行字符串
- 输出字符串:
printf("%s", str):输出字符串puts(str):输出字符串并自动添加换行符
11.5 程序示例:字符串反转
1 |
|
第12章 存储类别、链接和内存管理
12.1 存储类别
- 存储类别:决定变量的生命周期、作用域和链接
- C语言的存储类别:
- auto:自动存储类别,默认
- static:静态存储类别
- register:寄存器存储类别
- extern:外部存储类别
12.2 作用域
- 块作用域:变量在定义它的块内可见
- 函数作用域:标签在函数内可见
- 文件作用域:变量在定义它的文件内可见
- 函数原型作用域:函数原型中的参数在原型内可见
12.3 链接
- 内部链接:变量只在定义它的文件内可见
- 外部链接:变量在多个文件中可见
12.4 内存管理
- 内存的分配方式:
- 静态分配:编译时分配内存
- 自动分配:函数调用时分配内存,函数返回时释放
- 动态分配:运行时分配内存
12.5 动态内存管理函数
- stdlib.h:提供动态内存管理函数的头文件
- 常见函数:
malloc():分配指定大小的内存calloc():分配指定数量和大小的内存,并初始化为零realloc():调整已分配内存的大小free():释放已分配的内存
12.6 程序示例:动态内存分配
1 |
|
第13章 文件输入/输出
13.1 文件的概念
- 文件:存储在磁盘上的数据集合
- 文件指针:指向文件结构的指针,用于操作文件
13.2 文件的打开和关闭
- fopen():打开文件
1
FILE *fp = fopen("filename.txt", "r"); // 以只读模式打开文件
- 文件模式:
r:只读w:只写(覆盖现有文件)a:追加(在文件末尾写入)r+:读写w+:读写(覆盖现有文件)a+:读写(在文件末尾写入)
- fclose():关闭文件
1
fclose(fp);
13.3 文件的读写
- 字符读写:
fgetc():从文件读取一个字符fputc():向文件写入一个字符
- 行读写:
fgets():从文件读取一行fputs():向文件写入一行
- 格式化读写:
fscanf():从文件读取格式化数据fprintf():向文件写入格式化数据
- 二进制读写:
fread():从文件读取二进制数据fwrite():向文件写入二进制数据
13.4 文件的定位
- fseek():移动文件指针到指定位置
1
2
3fseek(fp, 0, SEEK_SET); // 移动到文件开头
fseek(fp, 0, SEEK_END); // 移动到文件末尾
fseek(fp, 10, SEEK_CUR); // 从当前位置向后移动 10 字节 - ftell():获取当前文件指针的位置
1
long position = ftell(fp);
- rewind():移动文件指针到文件开头
1
rewind(fp);
13.5 文件的错误处理
- ferror():检查文件操作是否发生错误
1
2
3if (ferror(fp)) {
printf("文件操作错误!\n");
} - clearerr():清除文件错误标志
1
clearerr(fp);
13.6 程序示例:文件复制
1 |
|
第14章 结构和其他数据形式
14.1 结构的概念
- 结构:是一种复合数据类型,可以包含不同类型的成员
- 结构的声明:
1
2
3
4
5struct Person {
char name[50];
int age;
float height;
};
14.2 结构变量的定义和初始化
- 结构变量的定义:
1
struct Person p1;
- 结构变量的初始化:
1
struct Person p1 = {"John", 30, 1.75};
14.3 结构成员的访问
- 点运算符:用于访问结构变量的成员
1
2
3
4struct Person p1 = {"John", 30, 1.75};
printf("姓名:%s\n", p1.name);
printf("年龄:%d\n", p1.age);
printf("身高:%.2f\n", p1.height); - 箭头运算符:用于通过指针访问结构成员
1
2
3
4
5struct Person p1 = {"John", 30, 1.75};
struct Person *pp = &p1;
printf("姓名:%s\n", pp->name);
printf("年龄:%d\n", pp->age);
printf("身高:%.2f\n", pp->height);
14.4 结构数组
- 结构数组:存储结构变量的数组
1
2
3
4
5struct Person people[3] = {
{"John", 30, 1.75},
{"Mary", 25, 1.68},
{"Tom", 35, 1.80}
};
14.5 嵌套结构
- 嵌套结构:结构中包含其他结构
1
2
3
4
5
6
7
8
9
10
11struct Date {
int day;
int month;
int year;
};
struct Person {
char name[50];
struct Date birthdate;
float height;
};
14.6 联合体
- 联合体:是一种复合数据类型,所有成员共享同一块内存
1
2
3
4
5union Data {
int i;
float f;
char c;
}; - 特点:同一时间只能存储一个成员的值
14.7 枚举
- 枚举:是一种整数类型,其值为一组命名的常量
1
2
3
4
5
6
7
8
9enum Weekday {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}; - 默认值:从 0 开始,依次递增
- 自定义值:
1
2
3
4
5
6
7
8
9enum Weekday {
MONDAY = 1,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
};
14.8 typedef
- typedef:用于为类型创建别名
1
2typedef struct Person Person;
Person p1; // 等同于 struct Person p1;
14.9 程序示例:学生成绩管理
1 |
|
第15章 位操作
15.1 位的概念
- 位:最小的存储单位,只能存储 0 或 1
- 字节:通常由 8 位组成
- 字:计算机一次处理的位数
15.2 位运算符
- 按位与:
&,当且仅当两个对应位都为 1 时,结果位为 1 - 按位或:
|,当至少一个对应位为 1 时,结果位为 1 - 按位异或:
^,当对应位不同时,结果位为 1 - 按位取反:
~,反转所有位 - 左移:
<<,将位向左移动指定的位数 - 右移:
>>,将位向右移动指定的位数
15.3 位操作的应用
- 设置位:使用按位或
1
2int a = 0b0000;
a |= 0b0010; // 设置第 2 位(从 0 开始) - 清除位:使用按位与和按位取反
1
2int a = 0b1111;
a &= ~0b0010; // 清除第 2 位 - 切换位:使用按位异或
1
2int a = 0b0000;
a ^= 0b0010; // 切换第 2 位 - 检查位:使用按位与
1
2
3
4int a = 0b0010;
if (a & 0b0010) {
printf("第 2 位是 1\n");
}
15.4 位字段
- 位字段:是结构中的成员,指定了占用的位数
1
2
3
4
5
6struct Flags {
unsigned int flag1 : 1;
unsigned int flag2 : 1;
unsigned int flag3 : 1;
unsigned int reserved : 5;
};
15.5 程序示例:位操作
1 |
|
第16章 C预处理器和C库
16.1 C预处理器
- 预处理器:在编译之前处理源代码的程序
- 预处理指令:以
#开头的指令
16.2 宏定义
- #define:定义宏
1
2 - #undef:取消宏定义
1
16.3 文件包含
- #include:包含头文件
1
2
16.4 条件编译
- #if、#elif、#else、#endif:条件编译指令
1
2
3
printf("调试信息:x = %d\n", x); - #ifdef、#ifndef:检查宏是否定义
1
2
3
printf("调试信息:x = %d\n", x);
16.5 其他预处理指令
- #line:改变行号和文件名
- #error:产生编译错误
- #pragma:向编译器发送特定指令
16.6 C标准库
- 标准库:C语言提供的函数集合
- 常见头文件:
stdio.h:输入输出函数stdlib.h:通用工具函数string.h:字符串处理函数math.h:数学函数time.h:时间和日期函数ctype.h:字符处理函数assert.h:断言函数
16.7 程序示例:使用预处理器
1 |
|
第17章 高级数据表示
17.1 抽象数据类型
- 抽象数据类型(ADT):是一种数据类型,定义了数据的结构和操作,但隐藏了实现细节
- 常见ADT:
- 栈(Stack)
- 队列(Queue)
- 链表(Linked List)
- 树(Tree)
- 图(Graph)
17.2 链表
- 链表:是一种线性数据结构,元素通过指针链接
- 单链表:每个节点包含数据和指向下一个节点的指针
1
2
3
4struct Node {
int data;
struct Node *next;
}; - 链表的操作:
- 创建节点
- 插入节点
- 删除节点
- 遍历链表
- 反转链表
17.3 栈
- 栈:是一种后进先出(LIFO)的数据结构
- 栈的操作:
- 压栈(push):将元素添加到栈顶
- 弹栈(pop):从栈顶移除元素
- 查看栈顶(peek):查看栈顶元素
- 检查栈是否为空(isEmpty)
17.4 队列
- 队列:是一种先进先出(FIFO)的数据结构
- 队列的操作:
- 入队(enqueue):将元素添加到队尾
- 出队(dequeue):从队首移除元素
- 查看队首(front):查看队首元素
- 检查队列是否为空(isEmpty)
17.5 树
- 树:是一种层次数据结构,由节点组成
- 二叉树:每个节点最多有两个子节点
1
2
3
4
5struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
}; - 二叉树的遍历:
- 前序遍历:根 -> 左 -> 右
- 中序遍历:左 -> 根 -> 右
- 后序遍历:左 -> 右 -> 根
17.6 程序示例:链表
1 |
|
总结
《C Primer Plus 第6版》是一本全面、系统的C语言入门教材,涵盖了从基础概念到高级特性的所有内容。通过学习本书,读者可以掌握C语言的核心概念和编程技巧,为进一步学习其他编程语言和技术打下坚实的基础。
本学习笔记总结了《C Primer Plus 第6版》的主要内容,包括:
- C语言基础:数据类型、变量、常量、运算符、表达式
- 控制结构:条件语句、循环语句、跳转语句
- 函数:函数的声明、定义、调用、递归
- 数组和指针:数组的使用、指针的概念、数组与指针的关系
- 字符串:字符串的存储、处理函数
- 内存管理:动态内存分配、存储类别
- 文件操作:文件的打开、关闭、读写
- 复合数据类型:结构、联合体、枚举
- 位操作:位运算符、位字段
- 预处理器:宏定义、条件编译
- 高级数据结构:链表、栈、队列、树
学习C语言需要不断练习,通过编写实际程序来巩固所学知识。建议读者在学习过程中多动手编程,尝试解决实际问题,这样才能真正掌握C语言的精髓。
参考资料
- 《C Primer Plus 第6版》- Stephen Prata
- C语言标准文档(C11)
- GCC 编译器文档
- 各种C语言在线教程和资源
学习建议:
- 按章节顺序学习,不要跳过基础内容
- 每学完一章,编写相应的练习题
- 尝试解决实际问题,如小型工具、游戏等
- 阅读优秀的C语言代码,学习编程风格和技巧
- 参加编程社区,与其他学习者交流经验
注意事项:
- 注意数组边界,避免越界访问
- 注意内存管理,及时释放动态分配的内存
- 注意指针的使用,避免空指针和野指针
- 注意输入验证,避免恶意输入导致的问题
- 注意代码风格,保持代码的可读性和可维护性
希望本学习笔记对您的C语言学习有所帮助!



