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
2
3
4
5
6
#include <stdio.h>

int main(void) {
printf("Hello, World!\n");
return 0;
}

代码解析

  • #include <stdio.h>:包含标准输入输出头文件,引入 printf 等函数的声明
  • int main(void):主函数,程序的入口点,int 表示返回类型为整数,void 表示不接受参数
  • printf("Hello, World!\n"):调用标准库函数 printf,输出字符串到控制台,\n 表示换行符
  • return 0:返回值 0,表示程序正常结束,非零值表示错误或异常情况

编译与执行过程

  1. 预处理:处理 #include 指令,将 stdio.h 的内容插入到源文件中
  2. 编译:将预处理后的源代码编译为目标代码(.o 文件)
  3. 链接:将目标代码与标准库链接,生成可执行文件
  4. 执行:操作系统加载可执行文件到内存,开始执行 main 函数

内存布局

  • 代码段:存储可执行指令,包括 main 函数和 printf 函数的代码
  • 数据段:存储全局变量和静态变量
  • :存储局部变量和函数调用信息,包括 main 函数的栈帧
  • :动态内存分配区域,本程序未使用

执行流程

  1. 操作系统调用 main 函数
  2. main 函数调用 printf 函数
  3. printf 函数输出字符串 “Hello, World!\n” 到标准输出
  4. printf 函数返回,main 函数继续执行
  5. main 函数执行 return 0,返回操作系统
  6. 操作系统接收返回值,结束程序执行

第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); }
    • 控制语句ifforwhiledo-whileswitchbreakcontinuegotoreturn
    • 声明语句:用于声明变量或函数,如 int x;
  • 注释

    • 块注释/* 注释内容 */,可跨行
    • 行注释// 注释内容,仅单行(C99 及以上)
    • 文档注释:用于生成文档的特殊注释格式,如 /** 文档注释 */

2.2 C语言的基本语法

  • 标识符:变量、函数、常量、类型等的名称

    • 命名规则:只能由字母、数字和下划线组成,不能以数字开头,区分大小写
    • 命名约定
      • 变量和函数:小写字母加下划线(snake_case),如 int user_id;
      • 常量:全部大写加下划线,如 #define MAX_BUFFER_SIZE 1024
      • 类型名:首字母大写或全部大写,如 typedef struct Node Node;#define BOOL int
    • 保留标识符:以下划线开头的标识符通常为系统保留,应避免使用
  • 关键字:C语言保留的特殊单词,具有特定的语法含义

    • 存储类别关键字autoregisterstaticexterntypedef
    • 类型关键字voidcharshortintlongfloatdouble_Bool_Complex_Imaginary
    • 控制流关键字ifelseswitchcasedefaultforwhiledobreakcontinuegotoreturn
    • 其他关键字constrestrictvolatileinline_Noreturn_Thread_local
  • 运算符

    • 算术运算符+-*/%++--
    • 关系运算符==!=<><=>=
    • 逻辑运算符&&||!
    • 位运算符&|^~<<>>
    • 赋值运算符=+=-=*=/=%=&=|=^=<<=>>=
    • 条件运算符?:
    • 逗号运算符,
    • 指针运算符*&
    • 下标运算符[]
    • 成员运算符.->
    • 括号运算符()
    • 大小运算符sizeof_Alignof
  • 分隔符:用于分隔语法元素

    • {}:用于定义块和复合语句
    • ():用于函数调用和表达式分组
    • []:用于数组下标
    • ,:用于分隔参数和表达式
    • ;:用于结束语句
    • ::用于标签和 case 语句

2.3 数据类型

  • 基本数据类型

    • 整数类型
      • char:通常为 1 字节,可表示字符或小整数
      • short:通常为 2 字节
      • int:通常为 4 字节
      • long:至少 4 字节
      • long long:至少 8 字节(C99 及以上)
      • 无符号变体unsigned charunsigned shortunsigned intunsigned longunsigned long long
    • 浮点类型
      • float:单精度浮点数,通常为 4 字节
      • double:双精度浮点数,通常为 8 字节
      • long double:扩展精度浮点数,通常为 12 或 16 字节
    • 布尔类型
      • _Bool:C99 引入的布尔类型,只能存储 0 或 1
      • stdbool.h:定义了 booltruefalse
    • 复数类型
      • float _Complexdouble _Complexlong double _Complex(C99 及以上)
      • float _Imaginarydouble _Imaginarylong 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 }; 中的 REDGREENBLUE
    • 宏常量 vs const 常量
      • 宏常量:在预处理阶段替换,无类型检查,可能导致副作用
      • const 常量:在编译阶段处理,有类型检查,更安全

2.5 输入输出

  • 标准输入输出

    • 输出函数
      • printf():格式化输出函数,返回输出的字符数
      • putchar():输出单个字符,返回输出的字符
      • puts():输出字符串并自动添加换行符,返回非负值或 EOF
    • 输入函数
      • scanf():格式化输入函数,返回成功读取的项目数
      • getchar():读取单个字符,返回读取的字符或 EOF
      • gets():读取一行字符串(不安全,已被弃用)
      • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>

#define PI 3.14159265358979323846 // 更精确的圆周率

/**
* 计算圆的面积
* @param radius 圆的半径
* @return 圆的面积
*/
double calculate_circle_area(double radius) {
return PI * radius * radius;
}

int main(void) {
double radius, area;
int result;

// 提示用户输入
printf("请输入圆的半径:");

// 读取用户输入并验证
result = scanf("%lf", &radius);
if (result != 1) {
fprintf(stderr, "错误:输入无效!\n");
return 1;
}

// 验证半径是否为正数
if (radius < 0) {
fprintf(stderr, "错误:半径不能为负数!\n");
return 1;
}

// 计算面积
area = calculate_circle_area(radius);

// 输出结果,保留 6 位小数
printf("圆的面积是:%.6f\n", area);

return 0;
}

代码分析

  • 使用了更精确的圆周率定义
  • 添加了函数注释,遵循文档注释规范
  • 增加了输入验证,确保输入有效
  • 使用 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 int
    • unsigned short
    • unsigned long
    • unsigned 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 提供的头文件,定义了 booltruefalse

3.6 类型转换

  • 隐式转换:编译器自动进行的转换
    • 小类型转换为大类型(如 intdouble
    • 赋值时,右侧转换为左侧类型
    • 运算时,操作数会转换为共同类型
  • 显式转换:使用强制类型转换运算符
    1
    2
    int a = 10;
    double b = (double)a;

3.7 sizeof 运算符

  • sizeof:返回操作数的大小(以字节为单位)
    1
    2
    printf("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
    #include <string.h>

    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
    2
    char buffer[100];
    fgets(buffer, sizeof(buffer), stdin);
  • puts():输出字符串并自动添加换行符
    1
    puts("Hello, World!");

4.6 程序示例:字符串处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>

int main(void) {
char name[50];
char greeting[100];

printf("请输入您的名字:");
fgets(name, sizeof(name), stdin);

// 移除换行符
name[strcspn(name, "\n")] = '\0';

sprintf(greeting, "你好,%s!欢迎学习C语言。", name);
puts(greeting);

printf("您的名字长度是:%zu\n", strlen(name));

return 0;
}

第5章 运算符、表达式和语句

5.1 运算符概述

  • 算术运算符+-*/%
  • 关系运算符==!=<><=>=
  • 逻辑运算符&&(与)、||(或)、!(非)
  • 赋值运算符=+=-=*=/=%=
  • 自增自减运算符++--
  • 条件运算符?:
  • 位运算符&|^~<<>>

5.2 表达式

  • 表达式:由运算符和操作数组成的式子
  • 表达式的值:表达式计算的结果
  • 表达式的类型:根据操作数和运算符确定

5.3 运算符的优先级和结合性

  • 优先级:决定运算符的执行顺序
  • 结合性:决定同优先级运算符的执行顺序(从左到右或从右到左)

5.4 语句

  • 表达式语句:由表达式加分号组成
    1
    2
    x = 5; // 赋值语句
    printf("Hello"); // 函数调用语句
  • 复合语句:用花括号括起来的语句块
    1
    2
    3
    4
    {
    int x = 5;
    printf("x = %d\n", x);
    }
  • 空语句:只有一个分号的语句
    1
    ; // 空语句

5.5 类型转换表达式

  • 隐式类型转换:编译器自动进行的转换
  • 显式类型转换:使用强制类型转换
    1
    2
    int a = 10;
    double b = (double)a / 3; // 结果为 3.333...

5.6 程序示例:计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>

int main(void) {
double num1, num2, result;
char op;

printf("请输入表达式(如 1+2):");
scanf("%lf%c%lf", &num1, &op, &num2);

switch (op) {
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
if (num2 != 0) {
result = num1 / num2;
} else {
printf("错误:除数不能为零!\n");
return 1;
}
break;
default:
printf("错误:无效的运算符!\n");
return 1;
}

printf("%.2lf %c %.2lf = %.2lf\n", num1, op, num2, result);

return 0;
}

第6章 C控制语句:循环

6.1 循环的概念

  • 循环:重复执行一段代码的结构
  • 循环三要素:初始化、条件判断、更新

6.2 while 循环

  • 基本语法
    1
    2
    3
    while (条件) {
    // 循环体
    }
  • 执行流程
    1. 检查条件是否为真
    2. 如果为真,执行循环体
    3. 重复步骤 1 和 2
    4. 如果为假,退出循环

6.3 for 循环

  • 基本语法
    1
    2
    3
    for (初始化; 条件; 更新) {
    // 循环体
    }
  • 执行流程
    1. 执行初始化
    2. 检查条件是否为真
    3. 如果为真,执行循环体
    4. 执行更新
    5. 重复步骤 2 到 4
    6. 如果为假,退出循环

6.4 do-while 循环

  • 基本语法
    1
    2
    3
    do {
    // 循环体
    } while (条件);
  • 执行流程
    1. 执行循环体
    2. 检查条件是否为真
    3. 如果为真,重复步骤 1 和 2
    4. 如果为假,退出循环
  • 特点:循环体至少执行一次

6.5 循环的嵌套

  • 嵌套循环:一个循环包含另一个循环
    1
    2
    3
    4
    5
    6
    for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= i; j++) {
    printf("* ");
    }
    printf("\n");
    }

6.6 循环控制语句

  • break:跳出当前循环
    1
    2
    3
    4
    5
    6
    for (int i = 1; i <= 10; i++) {
    if (i == 5) {
    break; // 当 i=5 时跳出循环
    }
    printf("%d ", i);
    }
  • continue:跳过当前循环的剩余部分,进入下一次循环
    1
    2
    3
    4
    5
    6
    for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) {
    continue; // 跳过偶数
    }
    printf("%d ", i);
    }
  • goto:跳转到指定标签(尽量避免使用)
    1
    2
    3
    4
    5
    6
    7
    int i = 1;
    loop:
    printf("%d ", i);
    i++;
    if (i <= 5) {
    goto loop;
    }

6.7 程序示例:猜数字游戏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
int secret_number, guess, attempts = 0;

// 初始化随机数生成器
srand(time(NULL));

// 生成 1-100 之间的随机数
secret_number = rand() % 100 + 1;

printf("猜数字游戏!\n");
printf("我想了一个 1-100 之间的数字,你能猜出来吗?\n\n");

do {
printf("请输入你的猜测:");
scanf("%d", &guess);
attempts++;

if (guess < secret_number) {
printf("太小了!再试试。\n\n");
} else if (guess > secret_number) {
printf("太大了!再试试。\n\n");
} else {
printf("恭喜你猜对了!答案是 %d。\n", secret_number);
printf("你用了 %d 次尝试。\n", attempts);
}
} while (guess != secret_number);

return 0;
}

第7章 C控制语句:分支和跳转

7.1 if 语句

  • 基本语法
    1
    2
    3
    if (条件) {
    // 条件为真时执行
    }
  • if-else 语句
    1
    2
    3
    4
    5
    if (条件) {
    // 条件为真时执行
    } else {
    // 条件为假时执行
    }
  • 嵌套 if 语句
    1
    2
    3
    4
    5
    6
    7
    if (条件1) {
    // 条件1为真时执行
    } else if (条件2) {
    // 条件2为真时执行
    } else {
    // 所有条件都为假时执行
    }

7.2 逻辑运算符

  • &&(逻辑与):当且仅当两个操作数都为真时,结果为真
  • ||(逻辑或):当至少一个操作数为真时,结果为真
  • !(逻辑非):取反操作数的逻辑值

7.3 条件运算符

  • 基本语法条件 ? 表达式1 : 表达式2
  • 执行流程:如果条件为真,返回表达式1的值,否则返回表达式2的值
    1
    2
    3
    int 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
    12
    switch (表达式) {
    case 常量1:
    // 代码
    break;
    case 常量2:
    // 代码
    break;
    // 更多 case
    default:
    // 代码
    break;
    }
  • 注意事项
    • 表达式的值必须是整数类型或枚举类型
    • 每个 case 后必须有 break,否则会继续执行下一个 case
    • default 是可选的

7.5 程序示例:成绩等级评定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>

int main(void) {
int score;
char grade;

printf("请输入分数(0-100):");
scanf("%d", &score);

if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else if (score >= 60) {
grade = 'D';
} else {
grade = 'F';
}

printf("等级:%c\n", grade);

return 0;
}

第8章 字符输入/输出和输入验证

8.1 字符输入函数

  • getchar():从标准输入读取一个字符
    1
    2
    char ch;
    ch = getchar();
  • scanf():可以读取字符
    1
    2
    char ch;
    scanf("%c", &ch);

8.2 字符输出函数

  • putchar():向标准输出写入一个字符
    1
    2
    char ch = 'A';
    putchar(ch);
  • printf():可以输出字符
    1
    2
    char ch = 'A';
    printf("%c\n", ch);

8.3 缓冲区

  • 行缓冲:输入输出会先存储在缓冲区,遇到换行符时才实际读写
  • 刷新缓冲区
    • 遇到换行符
    • 缓冲区满
    • 使用 fflush() 函数
    • 程序结束

8.4 输入验证

  • 输入验证:确保用户输入的数据符合要求
  • 常见验证
    • 数值范围验证
    • 数据类型验证
    • 格式验证

8.5 程序示例:字符统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

int main(void) {
char ch;
int letters = 0, digits = 0, spaces = 0, others = 0;

printf("请输入一些字符(按 Ctrl+D 或 Ctrl+Z 结束):\n");

while ((ch = getchar()) != EOF) {
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
letters++;
} else if (ch >= '0' && ch <= '9') {
digits++;
} else if (ch == ' ' || ch == '\t' || ch == '\n') {
spaces++;
} else {
others++;
}
}

printf("\n统计结果:\n");
printf("字母:%d\n", letters);
printf("数字:%d\n", digits);
printf("空白字符:%d\n", spaces);
printf("其他字符:%d\n", others);

return 0;
}

第9章 函数

9.1 函数的概念

  • 函数:是完成特定任务的代码块
  • 函数的优点
    • 模块化:将复杂任务分解为小任务
    • 代码重用:避免重复代码
    • 可维护性:便于修改和调试
    • 可读性:使代码结构更清晰

9.2 函数的声明和定义

  • 函数声明:告诉编译器函数的存在和签名
    1
    int add(int, int); // 函数声明
  • 函数定义:实现函数的具体功能
    1
    2
    3
    int 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
    6
    int factorial(int n) {
    if (n <= 1) {
    return 1; // 终止条件
    }
    return n * factorial(n - 1); // 递归调用
    }

9.7 函数的存储类别

  • auto:自动变量,默认存储类别
  • static:静态变量,在函数调用之间保持值
  • extern:外部变量,声明在其他文件中定义的全局变量
  • register:寄存器变量,建议编译器将变量存储在寄存器中

9.8 程序示例:斐波那契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>

// 递归实现
int fibonacci_recursive(int n) {
if (n <= 1) {
return n;
}
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2);
}

// 迭代实现
int fibonacci_iterative(int n) {
if (n <= 1) {
return n;
}

int a = 0, b = 1, c;
for (int i = 2; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return b;
}

int main(void) {
int n;

printf("请输入一个整数:");
scanf("%d", &n);

printf("递归实现:第 %d 个斐波那契数是 %d\n", n, fibonacci_recursive(n));
printf("迭代实现:第 %d 个斐波那契数是 %d\n", n, fibonacci_iterative(n));

return 0;
}

第10章 数组和指针

10.1 数组的概念

  • 数组:是相同类型元素的集合
  • 数组的声明
    1
    int numbers[5]; // 声明一个包含 5 个整数的数组
  • 数组的初始化
    1
    2
    int numbers[5] = {1, 2, 3, 4, 5}; // 初始化数组
    int numbers[] = {1, 2, 3, 4, 5}; // 编译器自动计算大小

10.2 数组的访问

  • 数组元素:通过索引访问,索引从 0 开始
    1
    2
    3
    int 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
    5
    int 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
    2
    int x = 10;
    int *p = &x; // 初始化指针 p,使其指向变量 x

10.5 指针的运算

  • 解引用运算符*,用于获取指针指向的值
    1
    2
    3
    int x = 10;
    int *p = &x;
    printf("x 的值:%d\n", *p); // 输出 10
  • 取地址运算符&,用于获取变量的地址
    1
    2
    int x = 10;
    printf("x 的地址:%p\n", &x);
  • 指针算术
    • 指针加减整数:根据指针类型移动相应的字节数
    • 指针比较:比较指针指向的地址

10.6 数组和指针的关系

  • 数组名:是指向数组第一个元素的指针常量
    1
    2
    int numbers[] = {1, 2, 3, 4, 5};
    int *p = numbers; // 等同于 p = &numbers[0]
  • 通过指针访问数组元素
    1
    2
    3
    4
    int 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
    5
    char *names[] = {
    "John",
    "Mary",
    "Tom"
    };

10.8 函数与数组

  • 数组作为函数参数
    1
    2
    3
    4
    5
    6
    void print_array(int arr[], int size) {
    for (int i = 0; i < size; i++) {
    printf("%d ", arr[i]);
    }
    printf("\n");
    }
  • 注意:数组作为参数时,会退化为指针,失去大小信息

10.9 程序示例:数组排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>

// 冒泡排序
void bubble_sort(int arr[], int size) {
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}

// 打印数组
void print_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}

int main(void) {
int numbers[] = {64, 34, 25, 12, 22, 11, 90};
int size = sizeof(numbers) / sizeof(numbers[0]);

printf("排序前:");
print_array(numbers, size);

bubble_sort(numbers, size);

printf("排序后:");
print_array(numbers, size);

return 0;
}

第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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <string.h>

// 反转字符串
void reverse_string(char str[]) {
int length = strlen(str);
int start = 0;
int end = length - 1;

while (start < end) {
// 交换字符
char temp = str[start];
str[start] = str[end];
str[end] = temp;

start++;
end--;
}
}

int main(void) {
char str[100];

printf("请输入一个字符串:");
fgets(str, sizeof(str), stdin);

// 移除换行符
str[strcspn(str, "\n")] = '\0';

printf("原字符串:%s\n", str);

reverse_string(str);

printf("反转后:%s\n", str);

return 0;
}

第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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdlib.h>

int main(void) {
int *arr;
int size;

printf("请输入数组大小:");
scanf("%d", &size);

// 分配内存
arr = (int *)malloc(size * sizeof(int));
if (arr == NULL) {
printf("内存分配失败!\n");
return 1;
}

// 填充数组
for (int i = 0; i < size; i++) {
arr[i] = i * 2;
}

// 打印数组
printf("数组元素:");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");

// 释放内存
free(arr);

return 0;
}

第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
    3
    fseek(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
    3
    if (ferror(fp)) {
    printf("文件操作错误!\n");
    }
  • clearerr():清除文件错误标志
    1
    clearerr(fp);

13.6 程序示例:文件复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>

int main(void) {
FILE *source, *dest;
char ch;
char source_file[100], dest_file[100];

printf("请输入源文件名:");
scanf("%s", source_file);

printf("请输入目标文件名:");
scanf("%s", dest_file);

// 打开源文件
source = fopen(source_file, "r");
if (source == NULL) {
printf("无法打开源文件!\n");
return 1;
}

// 打开目标文件
dest = fopen(dest_file, "w");
if (dest == NULL) {
printf("无法打开目标文件!\n");
fclose(source);
return 1;
}

// 复制文件内容
while ((ch = fgetc(source)) != EOF) {
fputc(ch, dest);
}

printf("文件复制成功!\n");

// 关闭文件
fclose(source);
fclose(dest);

return 0;
}

第14章 结构和其他数据形式

14.1 结构的概念

  • 结构:是一种复合数据类型,可以包含不同类型的成员
  • 结构的声明
    1
    2
    3
    4
    5
    struct 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
    4
    struct 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
    5
    struct 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
    5
    struct 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
    11
    struct Date {
    int day;
    int month;
    int year;
    };

    struct Person {
    char name[50];
    struct Date birthdate;
    float height;
    };

14.6 联合体

  • 联合体:是一种复合数据类型,所有成员共享同一块内存
    1
    2
    3
    4
    5
    union Data {
    int i;
    float f;
    char c;
    };
  • 特点:同一时间只能存储一个成员的值

14.7 枚举

  • 枚举:是一种整数类型,其值为一组命名的常量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    enum Weekday {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
    };
  • 默认值:从 0 开始,依次递增
  • 自定义值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    enum Weekday {
    MONDAY = 1,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
    };

14.8 typedef

  • typedef:用于为类型创建别名
    1
    2
    typedef struct Person Person;
    Person p1; // 等同于 struct Person p1;

14.9 程序示例:学生成绩管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>

// 定义学生结构
typedef struct {
char name[50];
int id;
float scores[3]; // 三门课程的成绩
float average;
} Student;

// 计算平均成绩
void calculate_average(Student *s) {
float sum = 0;
for (int i = 0; i < 3; i++) {
sum += s->scores[i];
}
s->average = sum / 3;
}

// 打印学生信息
void print_student(Student s) {
printf("学号:%d\n", s.id);
printf("姓名:%s\n", s.name);
printf("成绩:%.2f %.2f %.2f\n", s.scores[0], s.scores[1], s.scores[2]);
printf("平均成绩:%.2f\n\n", s.average);
}

int main(void) {
Student students[3];

// 输入学生信息
for (int i = 0; i < 3; i++) {
printf("请输入第 %d 个学生的信息:\n", i + 1);
printf("姓名:");
scanf("%s", students[i].name);
printf("学号:");
scanf("%d", &students[i].id);
printf("三门课程的成绩:");
scanf("%f %f %f", &students[i].scores[0], &students[i].scores[1], &students[i].scores[2]);

// 计算平均成绩
calculate_average(&students[i]);
printf("\n");
}

// 打印学生信息
printf("学生信息:\n\n");
for (int i = 0; i < 3; i++) {
print_student(students[i]);
}

return 0;
}

第15章 位操作

15.1 位的概念

  • :最小的存储单位,只能存储 0 或 1
  • 字节:通常由 8 位组成
  • :计算机一次处理的位数

15.2 位运算符

  • 按位与&,当且仅当两个对应位都为 1 时,结果位为 1
  • 按位或|,当至少一个对应位为 1 时,结果位为 1
  • 按位异或^,当对应位不同时,结果位为 1
  • 按位取反~,反转所有位
  • 左移<<,将位向左移动指定的位数
  • 右移>>,将位向右移动指定的位数

15.3 位操作的应用

  • 设置位:使用按位或
    1
    2
    int a = 0b0000;
    a |= 0b0010; // 设置第 2 位(从 0 开始)
  • 清除位:使用按位与和按位取反
    1
    2
    int a = 0b1111;
    a &= ~0b0010; // 清除第 2 位
  • 切换位:使用按位异或
    1
    2
    int a = 0b0000;
    a ^= 0b0010; // 切换第 2 位
  • 检查位:使用按位与
    1
    2
    3
    4
    int a = 0b0010;
    if (a & 0b0010) {
    printf("第 2 位是 1\n");
    }

15.4 位字段

  • 位字段:是结构中的成员,指定了占用的位数
    1
    2
    3
    4
    5
    6
    struct Flags {
    unsigned int flag1 : 1;
    unsigned int flag2 : 1;
    unsigned int flag3 : 1;
    unsigned int reserved : 5;
    };

15.5 程序示例:位操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>

// 打印二进制表示
void print_binary(unsigned int n) {
for (int i = 31; i >= 0; i--) {
printf("%d", (n >> i) & 1);
if (i % 4 == 0) {
printf(" ");
}
}
printf("\n");
}

int main(void) {
unsigned int a = 0b00001111;
unsigned int b = 0b00110011;

printf("a = ");
print_binary(a);
printf("b = ");
print_binary(b);

printf("a & b = ");
print_binary(a & b);

printf("a | b = ");
print_binary(a | b);

printf("a ^ b = ");
print_binary(a ^ b);

printf("~a = ");
print_binary(~a);

printf("a << 2 = ");
print_binary(a << 2);

printf("a >> 2 = ");
print_binary(a >> 2);

return 0;
}

第16章 C预处理器和C库

16.1 C预处理器

  • 预处理器:在编译之前处理源代码的程序
  • 预处理指令:以 # 开头的指令

16.2 宏定义

  • #define:定义宏
    1
    2
    #define PI 3.14159
    #define MAX(a, b) ((a) > (b) ? (a) : (b))
  • #undef:取消宏定义
    1
    #undef PI

16.3 文件包含

  • #include:包含头文件
    1
    2
    #include <stdio.h> // 包含标准库头文件
    #include "myheader.h" // 包含自定义头文件

16.4 条件编译

  • #if#elif#else#endif:条件编译指令
    1
    2
    3
    #if defined(DEBUG)
    printf("调试信息:x = %d\n", x);
    #endif
  • #ifdef#ifndef:检查宏是否定义
    1
    2
    3
    #ifdef DEBUG
    printf("调试信息:x = %d\n", x);
    #endif

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

// 定义宏
#define DEBUG 1
#define MAX_ARRAY_SIZE 100
#define PRINT_ARRAY(arr, size) \n for (int i = 0; i < size; i++) { \n printf("%d ", arr[i]); \n } \n printf("\n")

int main(void) {
int arr[MAX_ARRAY_SIZE] = {1, 2, 3, 4, 5};
int size = 5;

#ifdef DEBUG
printf("调试模式:数组大小 = %d\n", size);
#endif

printf("数组元素:");
PRINT_ARRAY(arr, size);

return 0;
}

第17章 高级数据表示

17.1 抽象数据类型

  • 抽象数据类型(ADT):是一种数据类型,定义了数据的结构和操作,但隐藏了实现细节
  • 常见ADT
    • 栈(Stack)
    • 队列(Queue)
    • 链表(Linked List)
    • 树(Tree)
    • 图(Graph)

17.2 链表

  • 链表:是一种线性数据结构,元素通过指针链接
  • 单链表:每个节点包含数据和指向下一个节点的指针
    1
    2
    3
    4
    struct 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
    5
    struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
    };
  • 二叉树的遍历
    • 前序遍历:根 -> 左 -> 右
    • 中序遍历:左 -> 根 -> 右
    • 后序遍历:左 -> 右 -> 根

17.6 程序示例:链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <stdio.h>
#include <stdlib.h>

// 定义链表节点结构
struct Node {
int data;
struct Node *next;
};

// 创建新节点
struct Node *create_node(int data) {
struct Node *new_node = (struct Node *)malloc(sizeof(struct Node));
if (new_node == NULL) {
printf("内存分配失败!\n");
exit(1);
}
new_node->data = data;
new_node->next = NULL;
return new_node;
}

// 在链表末尾添加节点
void append(struct Node **head, int data) {
struct Node *new_node = create_node(data);

// 如果链表为空,新节点作为头节点
if (*head == NULL) {
*head = new_node;
return;
}

// 找到链表末尾
struct Node *current = *head;
while (current->next != NULL) {
current = current->next;
}

// 添加新节点
current->next = new_node;
}

// 遍历链表
void print_list(struct Node *head) {
struct Node *current = head;

while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}

// 释放链表内存
void free_list(struct Node *head) {
struct Node *temp;

while (head != NULL) {
temp = head;
head = head->next;
free(temp);
}
}

int main(void) {
struct Node *head = NULL;

// 添加节点
append(&head, 10);
append(&head, 20);
append(&head, 30);
append(&head, 40);

// 打印链表
printf("链表内容:");
print_list(head);

// 释放内存
free_list(head);

return 0;
}

总结

《C Primer Plus 第6版》是一本全面、系统的C语言入门教材,涵盖了从基础概念到高级特性的所有内容。通过学习本书,读者可以掌握C语言的核心概念和编程技巧,为进一步学习其他编程语言和技术打下坚实的基础。

本学习笔记总结了《C Primer Plus 第6版》的主要内容,包括:

  1. C语言基础:数据类型、变量、常量、运算符、表达式
  2. 控制结构:条件语句、循环语句、跳转语句
  3. 函数:函数的声明、定义、调用、递归
  4. 数组和指针:数组的使用、指针的概念、数组与指针的关系
  5. 字符串:字符串的存储、处理函数
  6. 内存管理:动态内存分配、存储类别
  7. 文件操作:文件的打开、关闭、读写
  8. 复合数据类型:结构、联合体、枚举
  9. 位操作:位运算符、位字段
  10. 预处理器:宏定义、条件编译
  11. 高级数据结构:链表、栈、队列、树

学习C语言需要不断练习,通过编写实际程序来巩固所学知识。建议读者在学习过程中多动手编程,尝试解决实际问题,这样才能真正掌握C语言的精髓。

参考资料

  • 《C Primer Plus 第6版》- Stephen Prata
  • C语言标准文档(C11)
  • GCC 编译器文档
  • 各种C语言在线教程和资源

学习建议

  1. 按章节顺序学习,不要跳过基础内容
  2. 每学完一章,编写相应的练习题
  3. 尝试解决实际问题,如小型工具、游戏等
  4. 阅读优秀的C语言代码,学习编程风格和技巧
  5. 参加编程社区,与其他学习者交流经验

注意事项

  1. 注意数组边界,避免越界访问
  2. 注意内存管理,及时释放动态分配的内存
  3. 注意指针的使用,避免空指针和野指针
  4. 注意输入验证,避免恶意输入导致的问题
  5. 注意代码风格,保持代码的可读性和可维护性

希望本学习笔记对您的C语言学习有所帮助!