C语言学习知识点总结(适合新手/考试复习)
C语言学习知识点总结(适合新手/考试复习)
🎯 写在前面:这份学习笔记是我在学习C语言过程中的心得体会。作为一名曾经的编程小白,我深知学习编程的不易。因此,我尽量用通俗易懂的语言,结合实际例子,帮助大家更好地理解C语言。
C语言知识点总结
- C语言学习知识点总结(适合新手/考试复习)
- 第一部分:C语言基础
- 1. 初识C语言
- 1.1 第一个C程序
- 1.2 基本数据类型
- 1.3 格式化输入输出
- 2. 运算符与表达式
- 2.1 基本运算符
- 2.2 关系运算符和逻辑运算符
- 2.3 赋值运算符和条件运算符
- 3. 程序流程控制
- 3.1 条件语句
- 3.2 循环语句
- 4. 数组
- 4.1 一维数组基础
- 4.2 二维数组
- 4.3 数组的高级应用
- 5. 函数
- 5.1 函数基础
- 5.2 函数高级特性
- 5.3 函数优化技术
- 6. 指针
- 6.1 指针基础
- 6.2 指针的高级应用
- 6.3 指针的安全性和优化
- 7. 结构体和共用体
- 7.1 结构体基础
- 7.2 结构体的高级特性
- 7.3 共用体和枚举
- 8. 文件操作
- 8.1 文件的基本操作
- 8.2 文本文件操作
- 8.3 二进制文件操作
- 8.4 高级文件操作
- 第二部分:进阶知识点
- 1. 预处理指令
- 1.1 预处理器基础
- 1.2 高级预处理技术
- 1.3 预处理器最佳实践
- 2. 动态内存分配
- 2.1 内存分配基础
- 2.2 高级内存管理
- 2.3 内存管理最佳实践
- 3. 位运算
- 3.1 基本位运算
- 3.2 位运算技巧
- 3.3 位运算高级应用
- 4. 排序算法
- 4.1 基本排序算法
- 4.2 高级排序算法
- 4.3 排序算法的比较和应用
- 5. 查找算法
- 5.1 静态查找
- 5.2 动态查找
- 5.3 哈希查找
- 6. 字符串算法
- 6.1 字符串反转
- 6.2 KMP字符串匹配
- 7. 数学算法
- 7.1 最大公约数(GCD)
- 7.2 素数筛选(埃氏筛)
- 8. 图算法
- 8.1 深度优先搜索(DFS)
- 8.2 广度优先搜索(BFS)
- 9. 动态规划
- 9.1 斐波那契数列
- 9.2 背包问题
第一部分:C语言基础
在开始学习C语言之前,我们先要理解:为什么要学习C语言?C语言是一门"古老"但强大的编程语言,它是很多其他编程语言的基础,学好C语言就像打好地基一样重要。它的特点是:
- 语言简洁,执行效率高
- 贴近硬件,可以直接操作内存
- 应用广泛,从操作系统到单片机都有它的身影
1. 初识C语言
让我们从最基础的开始,就像学习任何一门新语言一样,先从"你好,世界"开始。
1.1 第一个C程序
下面这个程序可能看起来很简单,但它包含了C语言最基本的要素:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
让我们一行一行地解释这个程序:
#include <stdio.h>
:这是一个预处理指令,告诉编译器我们需要使用输入输出函数。就像要用微信,首先得安装它一样。int main()
:这是程序的入口,所有C程序都从这里开始执行。就像一本书必须有目录一样。printf()
:这是一个输出函数,用来在屏幕上显示文字。return 0
:告诉系统程序正常结束了。
🌟 初学者常见问题:
- 忘记写分号(;)
- main函数名写错(比如写成Main)
- 忘记引入stdio.h就使用printf
- 字符串忘记加双引号
1.2 基本数据类型
在C语言中,数据类型就像是不同的容器,用来存放不同类型的数据。就像我们生活中用不同的容器来存放不同的东西:水要用水杯,饭要用饭盒。
#include <stdio.h>
int main() {
// 整型:用来存储整数
int a = 10; // 最常用的整数类型,比如年龄
short b = 20; // 短整数,节省内存空间
long c = 30L; // 长整数,可以存储更大的数
// 浮点型:用来存储小数
float d = 3.14f; // 单精度浮点数,比如身高
double e = 3.14159; // 双精度浮点数,比如圆周率
// 字符型:用来存储单个字符
char f = 'A'; // 字符,比如等级(A、B、C)
// 输出各种类型的值
printf("整数:%d\n", a);
printf("浮点数:%f\n", d);
printf("字符:%c\n", f);
return 0;
}
🎯 数据类型的选择建议:
- 存储普通整数,用
int
就够了 - 需要小数点,用
float
或double
- 存储单个字符,用
char
- 需要很大的数,才考虑用
long
💡 生活中的例子:
- 年龄:用
int
(不会有小数的年龄) - 体重:用
float
(需要小数点) - 成绩等级:用
char
(A、B、C、D) - 银行存款:用
double
(需要精确到分)
1.3 格式化输入输出
在C语言中,格式化输入输出就像是我们日常生活中的对话。printf是"说话",scanf是"听别人说"。让我们来看看如何让程序和用户进行友好的对话:
#include <stdio.h>
int main() {
// 格式化输出
int num = 123;
float pi = 3.14159;
char str[] = "Hello";
printf("整数格式:\n");
printf("默认右对齐:%8d\n", num); // " 123"
printf("左对齐:%-8d\n", num); // "123 "
printf("零填充:%08d\n", num); // "00000123"
printf("\n浮点数格式:\n");
printf("默认小数点后6位:%f\n", pi); // "3.141590"
printf("指定精度:%.2f\n", pi); // "3.14"
printf("科学计数法:%e\n", pi); // "3.141590e+00"
printf("\n字符串格式:\n");
printf("默认:%s\n", str); // "Hello"
printf("限制宽度:%8s\n", str); // " Hello"
printf("限制长度:%.3s\n", str); // "Hel"
}
🎨 格式控制符的妙用:
-
整数格式化:
%d
:最基本的整数显示,就像写数字一样%8d
:预留8个空格,就像在表格中对齐数字%08d
:用0填充空位,就像银行账号的显示方式
-
小数格式化:
%f
:显示小数,默认保留6位小数%.2f
:保留2位小数,适合显示价格%e
:科学计数法,适合表示很大或很小的数
-
字符串格式化:
%s
:显示字符串,就像写一段文字%8s
:控制显示宽度,适合制作表格%.3s
:限制显示长度,类似微博字数限制
💡 实际应用场景:
- 显示商品价格:
%.2f
- 对齐账单数据:
%8d
- 显示百分比:
%.1f%%
- 格式化日期时间:
%02d:%02d:%02d
🚫 常见错误提醒:
-
格式符和实际类型不匹配:
int num = 10; printf("%f", num); // 错误!整数用%d
-
忘记取地址符&:
int age; scanf("%d", age); // 错误!应该是&age
✨ 小技巧:
-
制作表格时,可以这样对齐:
printf("姓名 年龄 成绩\n"); printf("%-8s%4d%8.1f\n", "张三", 18, 92.5); printf("%-8s%4d%8.1f\n", "李四", 19, 88.0);
-
读取带空格的字符串:
char name[50]; printf("请输入姓名:"); gets(name); // 可以读取带空格的字符串
2. 运算符与表达式
在C语言中,运算符就像是数学中的运算符号,但功能更加强大。让我们通过生活中的例子来理解它们。
2.1 基本运算符
就像我们在小学学习的加减乘除一样,C语言中的基本运算符也很容易理解:
#include <stdio.h>
int main() {
int a = 10, b = 3;
// 算术运算符
printf("基本运算:\n");
printf("%d + %d = %d\n", a, b, a + b); // 就像买东西算总价
printf("%d - %d = %d\n", a, b, a - b); // 就像找零钱
printf("%d × %d = %d\n", a, b, a * b); // 就像买3件10元的商品
printf("%d ÷ %d = %d\n", a, b, a / b); // 整数除法,结果取整
printf("%d 除以 %d 的余数是:%d\n", a, b, a % b); // 就像分苹果剩下的
// 自增自减(就像计数器)
int count = 5;
printf("\n计数器示例:\n");
printf("当前人数:%d\n", count);
printf("一个人进来:%d\n", ++count); // 先加1再使用
printf("一个人出去:%d\n", --count); // 先减1再使用
return 0;
}
💡 生活中的例子:
- 加法(+):购物时计算总价
- 减法(-):找零钱、计算年龄差
- 乘法(*):计算商品总价(单价×数量)
- 除法(/):平均分配(10个苹果分给3个人)
- 取余(%):判断奇偶、计算剩余部分
⚠️ 需要注意的点:
- 整数除法会直接舍弃小数部分
- 除数不能为0
- 取余运算只能用于整数
2.2 关系运算符和逻辑运算符
这些运算符就像是我们生活中的判断,帮助我们做出决策:
#include <stdio.h>
int main() {
int age = 18;
int score = 85;
// 关系运算符(就像比较大小)
printf("年龄判断:\n");
printf("是否成年:%d\n", age >= 18); // 1表示是,0表示否
printf("是否到退休年龄:%d\n", age >= 60);
// 逻辑运算符(就像组合条件)
printf("\n考试成绩评估:\n");
// 同时满足多个条件(&&)
printf("是否及格且表现优秀:%d\n", score >= 60 && score < 90);
// 满足任意一个条件(||)
printf("是否需要补考或者重修:%d\n", score < 60 || score > 100);
return 0;
}
🌟 实际应用场景:
-
关系运算符:
- 判断年龄是否达到标准
- 比较成绩高低
- 检查库存是否充足
-
逻辑运算符:
- 判断是否满足多个条件(&&)
- 判断是否满足任意条件(||)
- 判断条件是否不成立(!)
2.3 赋值运算符和条件运算符
这些是C语言中的"效率工具",可以让我们的代码更简洁:
#include <stdio.h>
int main() {
// 赋值运算符(简化写法)
int money = 100;
printf("初始金额:%d\n", money);
money += 50; // 等同于 money = money + 50
printf("存入50元后:%d\n", money);
money -= 30; // 等同于 money = money - 30
printf("取出30元后:%d\n", money);
// 条件运算符(三目运算符)- 简化if-else
int score = 75;
printf("\n成绩评估:%s\n",
score >= 60 ? "及格" : "不及格");
// 实际应用组合
int age = 20;
int ticket = 100;
int final_price = ticket * (age < 12 ? 0.5 : 1.0);
printf("票价:%d元\n", final_price);
return 0;
}
✨ 使用技巧:
-
复合赋值运算符:
+=
:累加(比如积分增加)-=
:递减(比如库存减少)*=
:倍数增长(比如利息计算)
-
条件运算符:
- 适合简单的条件选择
- 可以用在表达式中
- 代码更简单
3. 程序流程控制
让我们把程序流程控制想象成生活中的决策和重复动作。每个人每天都在不知不觉中做出很多决定,执行很多重复的动作,这些就是程序中的流程控制。
3.1 条件语句
条件语句就像我们每天都要面对的选择,比如:
- 如果下雨了,就带伞
- 如果饿了,就吃饭
- 如果累了,就休息
#include <stdio.h>
int main() {
// if-else语句示例:模拟考试成绩评级系统
int score = 85;
printf("=== 成绩评估系统 ===\n");
if (score >= 90) {
printf("🌟 优秀!继续保持!\n");
} else if (score >= 80) {
printf("👍 良好!还可以更好!\n");
} else if (score >= 60) {
printf("😊 及格!需要继续努力!\n");
} else {
printf("💪 加油!不要灰心!\n");
}
// switch语句示例:模拟简单点餐系统
int choice = 1;
printf("\n=== 快餐点餐系统 ===\n");
switch (choice) {
case 1:
printf("🍔 您选择了汉堡套餐\n");
break;
case 2:
printf("🍗 您选择了炸鸡套餐\n");
break;
case 3:
printf("🍝 您选择了意面套餐\n");
break;
default:
printf("❓ 对不起,没有这个选项\n");
}
return 0;
}
💡 实际应用场景:
-
if-else适用场景:
- 根据年龄判断票价优惠
- 根据消费金额计算折扣
- 根据天气选择出行方式
-
switch适用场景:
- 菜单选择系统
- 简单的状态机
- 等级评定系统
⚠️ 易错点提醒:
-
if-else中的常见错误:
- 忘记写大括号{}
- 判断条件写成=(赋值)而不是==(比较)
- 条件的范围重叠或遗漏
-
switch中的常见错误:
- 忘记写break导致执行多个case
- case中的值必须是常量
- default位置可以放在任意位置(但建议放在最后)
3.2 循环语句
循环就像我们生活中的:
- 每天刷牙
- 每周健身
- 每月查看账单
#include <stdio.h>
int main() {
// for循环示例:模拟健身计划
printf("=== 健身计划追踪 ===\n");
for (int day = 1; day <= 7; day++) {
printf("第%d天:完成%d个俯卧撑 💪\n",
day, day * 5); // 每天增加5个
}
// while循环示例:模拟存钱计划
printf("\n=== 存钱计划追踪 ===\n");
int savings = 0;
int week = 1;
while (savings < 1000) {
savings += 200; // 每周存200
printf("第%d周:已存%d元 💰\n",
week++, savings);
}
// do-while循环示例:模拟游戏菜单
int choice;
do {
printf("\n=== 游戏菜单 ===\n");
printf("1. 开始游戏 🎮\n");
printf("2. 设置 ⚙️\n");
printf("3. 退出 🚪\n");
printf("请选择(1-3):");
scanf("%d", &choice);
} while (choice < 1 || choice > 3);
return 0;
}
🎯 实践建议:
-
选择合适的循环:
- for:知道确切循环次数
- while:不确定循环次数,但知道结束条件
- do-while:至少需要执行一次的情况
-
循环优化技巧:
- 使用break提前结束循环
- 使用continue跳过当前循环
- 避免死循环(一定要有正确的终止条件)
4. 数组
4.1 一维数组基础
#include <stdio.h>
int main() {
// 数组的多种定义和初始化方式
int arr1[5] = {1, 2, 3, 4, 5}; // 完整初始化
int arr2[5] = {1, 2}; // 部分初始化,其余为0
int arr3[] = {1, 2, 3}; // 自动确定大小
int arr4[5] = {0}; // 全部初始化为0
// 数组基本操作
printf("=== 数组基本操作 ===\n");
// 1. 访问元素
printf("第一个元素:%d\n", arr1[0]);
printf("最后一个元素:%d\n", arr1[4]);
// 2. 修改元素
arr1[0] = 10;
// 3. 计算数组大小
int size = sizeof(arr1) / sizeof(arr1[0]);
printf("数组大小:%d\n", size);
// 4. 数组遍历的多种方式
// 4.1 下标遍历
for (int i = 0; i < size; i++) {
printf("%d ", arr1[i]);
}
printf("\n");
// 4.2 指针遍历
int *p = arr1;
for (int i = 0; i < size; i++) {
printf("%d ", *(p + i));
}
printf("\n");
return 0;
}
⚠️ 关键技术点:
-
数组定义规则:
- 数组大小必须是常量表达式
- 数组名代表数组首地址
- 下标从0开始
-
内存特性:
- 连续内存空间
- 固定大小,不可变
- 随机访问,O(1)时间复杂度
-
数组越界问题:
- 访问越界可能导致程序崩溃
- 编译器可能不会检查越界
- 需要手动确保下标合法
4.2 二维数组
#include <stdio.h>
int main() {
// 1. 二维数组的多种定义方式
int arr1[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int arr2[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; // 按行展开初始化
// 2. 二维数组的访问
printf("=== 二维数组访问 ===\n");
// 2.1 使用行列下标
printf("arr1[1][2] = %d\n", arr1[1][2]);
// 2.2 使用指针
printf("*(*arr1 + 1) = %d\n", *(*arr1 + 1));
// 3. 二维数组的遍历
int rows = sizeof(arr1) / sizeof(arr1[0]);
int cols = sizeof(arr1[0]) / sizeof(arr1[0][0]);
// 3.1 按行优先遍历
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d ", arr1[i][j]);
}
printf("\n");
}
// 3.2 按列优先遍历
for (int j = 0; j < cols; j++) {
for (int i = 0; i < rows; i++) {
printf("%3d ", arr1[i][j]);
}
printf("\n");
}
return 0;
}
🔍 二维数组核心要点:
-
内存布局:
- 行优先存储
- 连续内存空间
- 可以通过一维数组方式访问
-
地址计算:
- 元素地址 = 基地址 + (i * 列数 + j) * sizeof(元素类型)
- arr[i][j] 等价于 ((arr + i) + j)
-
参数传递:
- 作为函数参数时必须指定列数
- 可以省略行数,但不能省略列数
4.3 数组的高级应用
#include <stdio.h>
// 1. 数组作为函数参数
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// 2. 返回数组的函数(通过指针)
int* findElement(int arr[], int size, int target) {
for (int i = 0; i < size; i++) {
if (arr[i] == target) {
return &arr[i];
}
}
return NULL;
}
// 3. 二维数组作为函数参数
void process2DArray(int arr[][4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
arr[i][j] *= 2;
}
}
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
// 数组作为参数传递
printf("原始数组:");
printArray(arr, size);
// 查找元素
int target = 3;
int *found = findElement(arr, size, target);
if (found != NULL) {
printf("找到元素 %d 在位置:%ld\n", target, found - arr);
}
// 二维数组操作
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
process2DArray(matrix, 3);
return 0;
}
📝 高级特性总结:
-
数组与指针:
- 数组名可以作为指针使用
- 数组作为参数会退化为指针
- 指针算术运算与数组索引等价
-
内存管理:
- 栈上分配的数组大小固定
- 需要可变大小时使用动态内存分配
- 注意避免内存泄漏
-
性能优化:
- 合理利用CPU缓存
- 避免频繁的数组拷贝
- 使用合适的遍历方式
5. 函数
5.1 函数基础
#include <stdio.h>
// 1. 函数声明
int add(int a, int b);
void swap(int *x, int *y);
int getMax(int arr[], int size);
// 2. 函数定义
int add(int a, int b) {
return a + b;
}
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
int getMax(int arr[], int size) {
int max = arr[0];
for(int i = 1; i < size; i++) {
if(arr[i] > max) max = arr[i];
}
return max;
}
int main() {
// 3. 函数调用示例
printf("=== 函数调用演示 ===\n");
// 3.1 基本函数调用
int sum = add(5, 3);
printf("add(5, 3) = %d\n", sum);
// 3.2 传递指针参数
int x = 10, y = 20;
printf("交换前: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("交换后: x = %d, y = %d\n", x, y);
// 3.3 传递数组参数
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int size = sizeof(arr) / sizeof(arr[0]);
printf("数组最大值: %d\n", getMax(arr, size));
return 0;
}
📝 函数基础要点:
-
函数声明:
- 函数原型声明
- 参数列表
- 返回值类型
-
参数传递:
- 值传递:参数为基本数据类型
- 指针传递:需要修改原值
- 数组传递:退化为指针
-
返回值:
- void:无返回值
- 基本数据类型
- 指针类型
5.2 函数高级特性
#include <stdio.h>
// 1. 内联函数
inline int square(int x) {
return x * x;
}
// 2. 函数指针
int operate(int x, int y, int (*operation)(int, int)) {
return operation(x, y);
}
int multiply(int x, int y) { return x * y; }
int divide(int x, int y) { return x / y; }
// 3. 可变参数函数
#include <stdarg.h>
int sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for(int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
// 4. 递归函数
unsigned long long factorial(int n) {
if(n <= 1) return 1;
return n * factorial(n - 1);
}
int main() {
printf("=== 函数高级特性演示 ===\n");
// 1. 内联函数调用
printf("square(5) = %d\n", square(5));
// 2. 函数指针使用
int (*operation)(int, int);
operation = multiply;
printf("multiply(6, 3) = %d\n", operation(6, 3));
operation = divide;
printf("divide(6, 3) = %d\n", operation(6, 3));
// 3. 可变参数函数调用
printf("sum(4, 1,2,3,4) = %d\n", sum(4, 1,2,3,4));
// 4. 递归函数调用
printf("factorial(5) = %llu\n", factorial(5));
return 0;
}
🔍 高级特性要点:
-
内联函数:
- 减少函数调用开销
- 编译器优化
- 适用于简短、频繁调用的函数
-
函数指针:
- 指向函数的指针
- 回调函数实现
- 函数表实现
-
可变参数:
- va_list 类型
- va_start/va_arg/va_end 宏
- 参数个数的传递
-
递归函数:
- 基本情况处理
- 递归调用
- 栈空间使用
5.3 函数优化技术
#include <stdio.h>
// 1. 参数优化
void processArray(const int *arr, int size) { // 使用const防止修改
// 只读操作
}
// 2. 返回值优化
typedef struct {
int x;
int y;
} Point;
Point* createPoint(int x, int y) { // 返回堆内存中的对象
Point* p = (Point*)malloc(sizeof(Point));
if(p != NULL) {
p->x = x;
p->y = y;
}
return p;
}
// 3. 尾递归优化
int factorial_tail(int n, int acc) {
if(n <= 1) return acc;
return factorial_tail(n - 1, n * acc);
}
int main() {
// 函数优化示例
int arr[] = {1, 2, 3, 4, 5};
processArray(arr, 5);
Point* p = createPoint(10, 20);
if(p != NULL) {
printf("Point: (%d, %d)\n", p->x, p->y);
free(p);
}
printf("Factorial(5) = %d\n", factorial_tail(5, 1));
return 0;
}
⚡ 优化技术要点:
-
参数传递优化:
- 使用const修饰只读参数
- 大对象使用指针传递
- 小对象使用值传递
-
返回值优化:
- 返回指针而非大对象
- 使用输出参数
- 考虑内存管理
-
递归优化:
- 尾递归转换
- 递归深度控制
- 内存使用优化
6. 指针
6.1 指针基础
#include <stdio.h>
int main() {
// 1. 指针的声明和初始化
int num = 42;
int *ptr = # // 基本指针
const int *ptr1 = # // 指向常量的指针
int * const ptr2 = # // 常量指针
// 2. 指针的基本操作
printf("=== 指针基本操作 ===\n");
printf("变量值: %d\n", num);
printf("变量地址: %p\n", (void*)&num);
printf("指针存储的地址: %p\n", (void*)ptr);
printf("指针指向的值: %d\n", *ptr);
// 3. 指针的算术运算
int array[5] = {1, 2, 3, 4, 5};
int *p = array;
printf("\n=== 指针算术运算 ===\n");
printf("初始值: %d\n", *p);
p++; // 指针递增
printf("递增后: %d\n", *p);
printf("偏移访问: %d\n", *(p + 2));
return 0;
}
📝 指针基础要点:
-
指针类型:
- 基本指针:可读写指向的值
- 指向常量的指针:不能通过指针修改值
- 常量指针:指针本身不能修改
- void指针:通用指针类型
-
内存操作:
- &:取地址运算符
- *:解引用运算符
- 指针大小:取决于系统架构
-
指针运算:
- 加减运算:按照指针类型的大小进行
- 比较运算:比较地址大小
- NULL指针:表示不指向任何地址
6.2 指针的高级应用
#include <stdio.h>
#include <stdlib.h>
// 1. 函数指针
typedef int (*Operation)(int, int);
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
// 2. 回调函数
void processArray(int *arr, int size, int (*process)(int)) {
for(int i = 0; i < size; i++) {
arr[i] = process(arr[i]);
}
}
int square(int x) { return x * x; }
int double_value(int x) { return x * 2; }
// 3. 多级指针应用
void allocateMatrix(int ***matrix, int rows, int cols) {
*matrix = (int**)malloc(rows * sizeof(int*));
for(int i = 0; i < rows; i++) {
(*matrix)[i] = (int*)malloc(cols * sizeof(int));
}
}
int main() {
// 1. 函数指针使用
Operation ops[] = {add, subtract};
printf("10 + 5 = %d\n", ops[0](10, 5));
printf("10 - 5 = %d\n", ops[1](10, 5));
// 2. 回调函数示例
int arr[] = {1, 2, 3, 4, 5};
printf("\n原始数组: ");
for(int i = 0; i < 5; i++) printf("%d ", arr[i]);
processArray(arr, 5, square);
printf("\n平方后: ");
for(int i = 0; i < 5; i++) printf("%d ", arr[i]);
// 3. 多级指针示例
int **matrix;
allocateMatrix(&matrix, 3, 4);
// 使用矩阵
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 4; j++) {
matrix[i][j] = i * 4 + j;
printf("%2d ", matrix[i][j]);
}
printf("\n");
}
// 释放内存
for(int i = 0; i < 3; i++) {
free(matrix[i]);
}
free(matrix);
return 0;
}
🔍 高级应用要点:
-
函数指针:
- 存储函数地址
- 实现回调机制
- 函数表实现
-
多级指针:
- 动态分配多维数组
- 指针的间接引用
- 参数传递优化
-
内存管理:
- 动态内存分配
- 内存泄漏预防
- 指针安全性
6.3 指针的安全性和优化
#include <stdio.h>
#include <stdlib.h>
// 1. 安全的内存分配
void* safeAlloc(size_t size) {
void* ptr = malloc(size);
if(ptr == NULL) {
fprintf(stderr, "内存分配失败\n");
exit(1);
}
return ptr;
}
// 2. 智能指针模拟
typedef struct {
int* ptr;
size_t* ref_count;
} SmartPtr;
SmartPtr createSmartPtr(int value) {
SmartPtr sp;
sp.ptr = (int*)safeAlloc(sizeof(int));
sp.ref_count = (size_t*)safeAlloc(sizeof(size_t));
*sp.ptr = value;
*sp.ref_count = 1;
return sp;
}
void addRef(SmartPtr* sp) {
(*sp->ref_count)++;
}
void release(SmartPtr* sp) {
(*sp->ref_count)--;
if(*sp->ref_count == 0) {
free(sp->ptr);
free(sp->ref_count);
sp->ptr = NULL;
sp->ref_count = NULL;
}
}
int main() {
// 1. 安全的指针使用
int* ptr = (int*)safeAlloc(sizeof(int));
*ptr = 42;
printf("安全分配的值: %d\n", *ptr);
free(ptr);
ptr = NULL; // 避免悬挂指针
// 2. 智能指针示例
SmartPtr sp1 = createSmartPtr(100);
printf("智能指针值: %d\n", *sp1.ptr);
printf("引用计数: %zu\n", *sp1.ref_count);
// 复制智能指针
SmartPtr sp2 = sp1;
addRef(&sp2);
printf("复制后引用计数: %zu\n", *sp1.ref_count);
// 释放智能指针
release(&sp1);
release(&sp2);
return 0;
}
⚡ 安全性和优化要点:
-
内存安全:
- 空指针检查
- 边界检查
- 类型安全
-
资源管理:
- RAII原则
- 引用计数
- 自动内存管理
-
性能优化:
- 指针对齐
- 缓存友好访问
- 避免指针追踪
7. 结构体和共用体
7.1 结构体基础
#include <stdio.h>
#include <string.h>
// 1. 结构体定义
struct Point {
int x;
int y;
};
// 2. 结构体对齐和压缩
#pragma pack(1) // 设置1字节对齐
struct CompactData {
char a; // 1字节
int b; // 4字节
short c; // 2字节
};
#pragma pack() // 恢复默认对齐
// 3. 结构体嵌套
struct Rectangle {
struct Point topLeft;
struct Point bottomRight;
char *label;
};
int main() {
// 1. 结构体初始化
struct Point p1 = {10, 20}; // 常规初始化
struct Point p2 = {.y = 30, .x = 40}; // 指定成员初始化
// 2. 内存对齐演示
printf("=== 内存对齐 ===\n");
printf("Point大小: %zu字节\n", sizeof(struct Point));
printf("CompactData大小: %zu字节\n", sizeof(struct CompactData));
// 3. 结构体操作
struct Rectangle rect;
rect.topLeft = p1;
rect.bottomRight = p2;
rect.label = "Rectangle1";
printf("\n=== 结构体访问 ===\n");
printf("矩形: (%d,%d) to (%d,%d)\n",
rect.topLeft.x, rect.topLeft.y,
rect.bottomRight.x, rect.bottomRight.y);
return 0;
}
📝 结构体基础要点:
-
内存布局:
- 成员按声明顺序存储
- 考虑内存对齐
- 可能存在填充字节
-
对齐规则:
- 默认对齐:按系统字长
- 自定义对齐:使用pragma pack
- 对齐优化:成员排序
-
初始化方式:
- 顺序初始化
- 指定成员初始化
- 动态分配初始化
7.2 结构体的高级特性
#include <stdio.h>
#include <stdlib.h>
// 1. 结构体封装
typedef struct {
char *data;
size_t length;
size_t capacity;
} String;
// 构造函数
String* String_create(const char* str) {
String* s = (String*)malloc(sizeof(String));
if (s != NULL) {
s->capacity = strlen(str) + 1;
s->data = (char*)malloc(s->capacity);
if (s->data != NULL) {
strcpy(s->data, str);
s->length = strlen(str);
} else {
free(s);
return NULL;
}
}
return s;
}
// 析构函数
void String_destroy(String* s) {
if (s != NULL) {
free(s->data);
free(s);
}
}
// 2. 柔性数组
typedef struct {
int length;
int data[]; // 柔性数组成员
} Array;
Array* Array_create(int length) {
Array* arr = (Array*)malloc(sizeof(Array) + length * sizeof(int));
if (arr != NULL) {
arr->length = length;
}
return arr;
}
// 3. 位域结构
struct Flags {
unsigned int read: 1; // 1位
unsigned int write: 1; // 1位
unsigned int exec: 1; // 1位
unsigned int reserved: 5; // 5位
};
int main() {
// 1. 封装示例
String* str = String_create("Hello");
if (str != NULL) {
printf("字符串: %s, 长度: %zu\n", str->data, str->length);
String_destroy(str);
}
// 2. 柔性数组示例
Array* arr = Array_create(5);
if (arr != NULL) {
for (int i = 0; i < arr->length; i++) {
arr->data[i] = i * i;
printf("%d ", arr->data[i]);
}
printf("\n");
free(arr);
}
// 3. 位域示例
struct Flags flags = {1, 1, 0, 0}; // 读写权限
printf("Flags大小: %zu字节\n", sizeof(struct Flags));
printf("权限: r%c w%c x%c\n",
flags.read ? 'w' : '-',
flags.write ? 'w' : '-',
flags.exec ? 'x' : '-');
return 0;
}
🔍 高级特性要点:
-
封装技术:
- 构造和析构函数
- 资源管理
- 接口设计
-
柔性数组:
- 必须是最后一个成员
- 不占用结构体大小
- 动态内存分配
-
位域使用:
- 节省内存空间
- 位级操作
- 标志位管理
7.3 共用体和枚举
#include <stdio.h>
// 1. 共用体定义
union Data {
int i; // 4字节
float f; // 4字节
char str[8]; // 8字节
};
// 2. 枚举定义
enum DayOfWeek {
SUNDAY = 0,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
};
// 3. 类型标记联合
typedef enum {
TYPE_INT,
TYPE_FLOAT,
TYPE_STRING
} ValueType;
typedef struct {
ValueType type;
union {
int i;
float f;
char *s;
} value;
} TypedValue;
// 值打印函数
void printValue(TypedValue *v) {
switch (v->type) {
case TYPE_INT:
printf("整数: %d\n", v->value.i);
break;
case TYPE_FLOAT:
printf("浮点数: %f\n", v->value.f);
break;
case TYPE_STRING:
printf("字符串: %s\n", v->value.s);
break;
}
}
int main() {
// 1. 共用体示例
union Data data;
printf("Data大小: %zu字节\n", sizeof(union Data));
data.i = 42;
printf("整数: %d\n", data.i);
data.f = 3.14f;
printf("浮点数: %f\n", data.f);
strcpy(data.str, "Hello");
printf("字符串: %s\n", data.str);
// 2. 枚举示例
enum DayOfWeek today = WEDNESDAY;
printf("今天是周%d\n", today + 1);
// 3. 类型标记联合示例
TypedValue val1 = {TYPE_INT, {.i = 42}};
TypedValue val2 = {TYPE_FLOAT, {.f = 3.14f}};
TypedValue val3 = {TYPE_STRING, {.s = "Hello"}};
printValue(&val1);
printValue(&val2);
printValue(&val3);
return 0;
}
⚡ 共用体和枚举要点:
-
共用体特性:
- 成员共享内存
- 大小为最大成员
- 内存覆盖规则
-
枚举特性:
- 常量定义
- 自动递增
- 类型安全
-
应用场景:
- 数据类型转换
- 内存节省
- 状态管理
8. 文件操作
8.1 文件的基本操作
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
// 1. 文件打开模式
FILE *fp;
const char *modes[] = {"r", "w", "a", "r+", "w+", "a+"};
// 2. 错误处理示例
if ((fp = fopen("test.txt", "r")) == NULL) {
fprintf(stderr, "错误码: %d\n", errno);
fprintf(stderr, "错误信息: %s\n", strerror(errno));
return 1;
}
// 3. 文件指针操作
fseek(fp, 0, SEEK_SET); // 移到文件开头
fseek(fp, 0, SEEK_END); // 移到文件末尾
long size = ftell(fp); // 获取文件大小
rewind(fp); // 回到文件开头
// 4. 文件关闭
fclose(fp);
return 0;
}
8.2 文本文件操作
#include <stdio.h>
int main() {
FILE *fp;
char buffer[1024];
// 1. 写入文本文件
fp = fopen("text.txt", "w");
if (fp != NULL) {
// 单字符写入
fputc('H', fp);
// 字符串写入
fputs("ello, World!\n", fp);
// 格式化写入
fprintf(fp, "Count: %d\n", 42);
fclose(fp);
}
// 2. 读取文本文件
fp = fopen("text.txt", "r");
if (fp != NULL) {
// 单字符读取
int ch = fgetc(fp);
// 行读取
if (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("读取的行: %s", buffer);
}
// 格式化读取
int count;
if (fscanf(fp, "Count: %d", &count) == 1) {
printf("读取的数字: %d\n", count);
}
fclose(fp);
}
return 0;
}
8.3 二进制文件操作
#include <stdio.h>
#include <stdlib.h>
// 定义数据结构
typedef struct {
int id;
char name[50];
double score;
} Student;
int main() {
FILE *fp;
Student students[3] = {
{1, "张三", 85.5},
{2, "李四", 92.0},
{3, "王五", 78.5}
};
// 1. 写入二进制文件
fp = fopen("students.dat", "wb");
if (fp != NULL) {
// 整块写入
fwrite(students, sizeof(Student), 3, fp);
// 单个写入
Student newStudent = {4, "赵六", 88.5};
fwrite(&newStudent, sizeof(Student), 1, fp);
fclose(fp);
}
// 2. 读取二进制文件
fp = fopen("students.dat", "rb");
if (fp != NULL) {
Student readStudent;
// 随机访问
fseek(fp, sizeof(Student) * 2, SEEK_SET); // 定位到第三个记录
fread(&readStudent, sizeof(Student), 1, fp);
printf("ID: %d, 姓名: %s, 分数: %.1f\n",
readStudent.id, readStudent.name, readStudent.score);
fclose(fp);
}
return 0;
}
8.4 高级文件操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 1. 文件缓冲区管理
void bufferDemo() {
FILE *fp = fopen("test.txt", "w");
if (fp != NULL) {
// 设置缓冲区
char buffer[1024];
setvbuf(fp, buffer, _IOFBF, sizeof(buffer));
// 写入数据
fprintf(fp, "测试数据\n");
// 强制刷新缓冲区
fflush(fp);
fclose(fp);
}
}
// 2. 临时文件操作
void tempFileDemo() {
FILE *temp = tmpfile();
if (temp != NULL) {
fprintf(temp, "临时数据\n");
rewind(temp);
char buffer[100];
if (fgets(buffer, sizeof(buffer), temp) != NULL) {
printf("临时文件内容: %s", buffer);
}
fclose(temp); // 关闭时自动删除
}
}
// 3. 文件锁定示例
void fileLockDemo() {
FILE *fp = fopen("shared.txt", "r+");
if (fp != NULL) {
#ifdef _WIN32
_flock(fileno(fp), _LK_NBLCK); // Windows
#else
flock(fileno(fp), LOCK_EX); // UNIX
#endif
// 对文件进行操作
fprintf(fp, "加锁写入\n");
#ifdef _WIN32
_funlock(fileno(fp));
#else
flock(fileno(fp), LOCK_UN);
#endif
fclose(fp);
}
}
int main() {
bufferDemo();
tempFileDemo();
fileLockDemo();
return 0;
}
【核心要点】
-
文件操作基础:
- 文件打开模式的选择
- 错误处理机制
- 文件指针操作
- 资源管理
-
文本文件操作:
- 字符级操作
- 行级操作
- 格式化IO
- 缓冲区管理
-
二进制文件操作:
- 结构体的读写
- 随机访问
- 数据对齐
- 字节序考虑
-
高级特性:
- 缓冲区管理
- 临时文件
- 文件锁定
- 异常处理
⚠️ 注意事项:
- 始终检查文件操作的返回值
- 正确关闭文件以释放资源
- 考虑文件访问权限
- 处理平台差异性
- 注意数据的字节对齐
第二部分:进阶知识点
1. 预处理指令
1.1 预处理器基础
#include <stdio.h>
// 1. 宏定义
#define PI 3.14159
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
// 2. 条件编译
#define DEBUG 1
#if DEBUG
#define LOG(msg) printf("[DEBUG] %s\n", msg)
#else
#define LOG(msg) // 空定义
#endif
// 3. 预定义宏
void printInfo() {
printf("文件:%s\n", __FILE__);
printf("行号:%d\n", __LINE__);
printf("编译时间:%s %s\n", __DATE__, __TIME__);
printf("ANSI C:%d\n", __STDC__);
}
int main() {
// 宏的使用
printf("PI = %.5f\n", PI);
printf("最大值:%d\n", MAX(10, 20));
printf("平方值:%d\n", SQUARE(5));
// 条件编译的使用
LOG("测试消息");
// 预定义宏的使用
printInfo();
return 0;
}
1.2 高级预处理技术
#include <stdio.h>
// 1. 字符串化运算符 #
#define PRINT_VAR(var) printf(#var " = %d\n", var)
// 2. 标记连接运算符 ##
#define CONCAT(x, y) x##y
// 3. 可变参数宏
#define DEBUG_LOG(format, ...) \
printf("[DEBUG] " format "\n", ##__VA_ARGS__)
// 4. 条件编译的高级用法
#ifdef _WIN32
#define OS "Windows"
#elif defined(__linux__)
#define OS "Linux"
#elif defined(__APPLE__)
#define OS "macOS"
#else
#define OS "Unknown"
#endif
// 5. 防止头文件重复包含
#ifndef MY_HEADER_H
#define MY_HEADER_H
typedef struct {
int x;
int y;
} Point;
#endif
int main() {
// 字符串化示例
int count = 42;
PRINT_VAR(count); // 输出:count = 42
// 标记连接示例
int value12 = 100;
printf("%d\n", CONCAT(value, 12)); // 访问value12
// 可变参数宏示例
DEBUG_LOG("测试 %s", "消息");
DEBUG_LOG("简单消息");
// 条件编译结果
printf("当前操作系统:%s\n", OS);
return 0;
}
1.3 预处理器最佳实践
// 1. 头文件保护
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
// 2. 版本控制
#define MATH_UTILS_VERSION 100 // 1.0.0
// 3. 功能开关
#define ENABLE_ADVANCED_MATH 1
// 4. 条件检查
#if defined(_MSC_VER) && _MSC_VER < 1400
#error "需要 Visual C++ 2005 或更高版本"
#endif
// 5. 平台特定代码
#ifdef _WIN32
#define PATH_SEPARATOR '\\'
#else
#define PATH_SEPARATOR '/'
#endif
// 6. 安全的宏定义
#define SAFE_DELETE(p) do { if(p) { delete (p); (p) = NULL; } } while(0)
#define SAFE_ARRAY_DELETE(p) do { if(p) { delete[] (p); (p) = NULL; } } while(0)
// 7. 条件编译的嵌套
#if defined(DEBUG)
#if defined(_WIN32)
#define DEBUG_BREAK() __debugbreak()
#else
#define DEBUG_BREAK() __builtin_trap()
#endif
#else
#define DEBUG_BREAK()
#endif
#endif // MATH_UTILS_H
【核心要点】
-
预处理器基础:
- 宏定义与函数的区别
- 条件编译的使用场景
- 预定义宏的应用
-
高级预处理技术:
- 字符串化运算符(#)
- 标记连接运算符(##)
- 可变参数宏
- 条件编译的高级应用
-
最佳实践:
- 头文件保护机制
- 版本控制和功能开关
- 平台特定代码处理
- 安全的宏定义方式
⚠️ 注意事项:
- 宏定义中的括号使用
- 避免宏的副作用
- 合理使用条件编译
- 注意宏的命名规范
- 预处理器的执行顺序
🔍 常见应用场景:
- 调试信息控制
- 平台特定代码
- 版本兼容性处理
- 代码优化开关
- 资源管理宏
2. 动态内存分配
2.1 内存分配基础
#include <stdio.h>
#include <stdlib.h>
int main() {
// 1. 基本内存分配
int *p1 = (int*)malloc(sizeof(int)); // 分配单个整数
int *p2 = (int*)calloc(1, sizeof(int)); // 分配并初始化为0
int *p3 = (int*)realloc(NULL, sizeof(int)); // 类似malloc
// 2. 数组内存分配
int *arr = (int*)malloc(5 * sizeof(int));
if (arr != NULL) {
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
}
}
// 3. 内存释放
free(p1);
free(p2);
free(p3);
free(arr);
// 4. 避免内存泄漏
p1 = p2 = p3 = arr = NULL;
return 0;
}
2.2 高级内存管理
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 1. 安全的内存分配函数
void* safeMalloc(size_t size) {
void* ptr = malloc(size);
if (ptr == NULL) {
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
return ptr;
}
// 2. 二维数组动态分配
int** create2DArray(int rows, int cols) {
int** array = (int**)safeMalloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
array[i] = (int*)safeMalloc(cols * sizeof(int));
}
return array;
}
// 3. 二维数组释放
void free2DArray(int** array, int rows) {
if (array) {
for (int i = 0; i < rows; i++) {
free(array[i]);
}
free(array);
}
}
// 4. 内存池实现
typedef struct MemoryPool {
void* memory;
size_t size;
size_t used;
} MemoryPool;
MemoryPool* createMemoryPool(size_t size) {
MemoryPool* pool = (MemoryPool*)safeMalloc(sizeof(MemoryPool));
pool->memory = safeMalloc(size);
pool->size = size;
pool->used = 0;
return pool;
}
void* poolAlloc(MemoryPool* pool, size_t size) {
if (pool->used + size > pool->size) {
return NULL; // 内存池已满
}
void* ptr = (char*)pool->memory + pool->used;
pool->used += size;
return ptr;
}
void destroyMemoryPool(MemoryPool* pool) {
if (pool) {
free(pool->memory);
free(pool);
}
}
int main() {
// 1. 二维数组示例
int rows = 3, cols = 4;
int** matrix = create2DArray(rows, cols);
// 初始化矩阵
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j;
printf("%2d ", matrix[i][j]);
}
printf("\n");
}
// 2. 内存池示例
MemoryPool* pool = createMemoryPool(1024); // 1KB内存池
// 从内存池分配内存
int* numbers = (int*)poolAlloc(pool, 5 * sizeof(int));
char* text = (char*)poolAlloc(pool, 20);
if (numbers && text) {
// 使用分配的内存
for (int i = 0; i < 5; i++) {
numbers[i] = i;
}
strcpy(text, "Hello, Memory Pool!");
printf("Text: %s\n", text);
}
// 清理资源
free2DArray(matrix, rows);
destroyMemoryPool(pool);
return 0;
}
2.3 内存管理最佳实践
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 1. 内存跟踪结构
typedef struct {
void* ptr;
size_t size;
const char* file;
int line;
} MemoryBlock;
#define MAX_BLOCKS 1000
MemoryBlock g_blocks[MAX_BLOCKS];
int g_blockCount = 0;
// 2. 内存分配跟踪
#define TRACK_MALLOC(size) trackMalloc(size, __FILE__, __LINE__)
void* trackMalloc(size_t size, const char* file, int line) {
void* ptr = malloc(size);
if (ptr && g_blockCount < MAX_BLOCKS) {
g_blocks[g_blockCount].ptr = ptr;
g_blocks[g_blockCount].size = size;
g_blocks[g_blockCount].file = file;
g_blocks[g_blockCount].line = line;
g_blockCount++;
}
return ptr;
}
// 3. 内存释放跟踪
void trackFree(void* ptr) {
for (int i = 0; i < g_blockCount; i++) {
if (g_blocks[i].ptr == ptr) {
memmove(&g_blocks[i], &g_blocks[i + 1],
(g_blockCount - i - 1) * sizeof(MemoryBlock));
g_blockCount--;
break;
}
}
free(ptr);
}
// 4. 内存泄漏检测
void checkMemoryLeaks() {
if (g_blockCount > 0) {
printf("检测到内存泄漏!\n");
for (int i = 0; i < g_blockCount; i++) {
printf("泄漏: %zu bytes at %p (%s:%d)\n",
g_blocks[i].size,
g_blocks[i].ptr,
g_blocks[i].file,
g_blocks[i].line);
}
} else {
printf("未检测到内存泄漏\n");
}
}
// 5. 对齐内存分配
void* alignedMalloc(size_t size, size_t alignment) {
void* ptr = NULL;
#ifdef _WIN32
ptr = _aligned_malloc(size, alignment);
#else
if (posix_memalign(&ptr, alignment, size) != 0) {
ptr = NULL;
}
#endif
return ptr;
}
void alignedFree(void* ptr) {
#ifdef _WIN32
_aligned_free(ptr);
#else
free(ptr);
#endif
}
int main() {
// 内存跟踪示例
int* numbers = (int*)TRACK_MALLOC(5 * sizeof(int));
char* text = (char*)TRACK_MALLOC(20);
// 使用内存
if (numbers && text) {
for (int i = 0; i < 5; i++) {
numbers[i] = i;
}
strcpy(text, "Hello");
}
// 释放部分内存
trackFree(numbers);
// 检查内存泄漏
checkMemoryLeaks(); // 应该显示text未释放
// 对齐内存分配示例
float* aligned_data = (float*)alignedMalloc(256, 16);
if (aligned_data) {
// 使用对齐的内存
alignedFree(aligned_data);
}
// 清理剩余资源
trackFree(text);
checkMemoryLeaks(); // 应该显示无泄漏
return 0;
}
【核心要点】
-
内存分配基础:
- malloc/calloc/realloc的使用
- 内存释放和空指针处理
- 内存分配失败处理
- 避免内存泄漏
-
高级内存管理:
- 安全的内存分配封装
- 多维数组的动态分配
- 内存池实现
- 资源管理策略
-
最佳实践:
- 内存跟踪和调试
- 内存泄漏检测
- 内存对齐处理
- 平台兼容性考虑
⚠️ 注意事项:
- 检查内存分配返回值
- 避免内存泄漏和重复释放
- 注意内存对齐要求
- 考虑内存分配效率
- 处理内存碎片问题
🔍 应用场景:
- 动态数组实现
- 图像处理缓冲区
- 数据库缓存管理
- 游戏资源管理
- 大数据处理
3. 位运算
3.1 基本位运算
#include <stdio.h>
int main() {
// 1. 基本位运算操作
unsigned int a = 60; // 二进制:00111100
unsigned int b = 13; // 二进制:00001101
printf("=== 基本位运算 ===\n");
printf("a = %u (%08X)\n", a, a);
printf("b = %u (%08X)\n", b, b);
printf("按位与 (AND): %u (%08X)\n", a & b, a & b);
printf("按位或 (OR): %u (%08X)\n", a | b, a | b);
printf("按位异或 (XOR): %u (%08X)\n", a ^ b, a ^ b);
printf("按位取反 (NOT): %u (%08X)\n", ~a, ~a);
// 2. 移位运算
printf("\n=== 移位运算 ===\n");
printf("左移2位 (a << 2): %u (%08X)\n", a << 2, a << 2);
printf("右移2位 (a >> 2): %u (%08X)\n", a >> 2, a >> 2);
// 3. 带符号数右移
int negative = -60; // 负数的右移
printf("\n=== 带符号右移 ===\n");
printf("负数右移1位: %d (%08X)\n", negative >> 1, negative >> 1);
return 0;
}
3.2 位运算技巧
#include <stdio.h>
// 1. 判断奇偶性
#define IS_EVEN(n) (((n) & 1) == 0)
// 2. 交换两数
void swapBits(int *a, int *b) {
if (a != b) { // 检查是否为同一地址
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
}
// 3. 计算二进制中1的个数
int countSetBits(unsigned int n) {
int count = 0;
while (n) {
count += n & 1;
n >>= 1;
}
return count;
}
// 4. 快速计算2的幂
#define IS_POWER_OF_TWO(n) (((n) & ((n) - 1)) == 0)
// 5. 获取最低位的1
#define LOWEST_SET_BIT(n) ((n) & -(n))
int main() {
// 1. 奇偶性判断
int num = 7;
printf("数字 %d 是%s数\n", num, IS_EVEN(num) ? "偶" : "奇");
// 2. 位交换示例
int x = 5, y = 10;
printf("交换前: x = %d, y = %d\n", x, y);
swapBits(&x, &y);
printf("交换后: x = %d, y = %d\n", x, y);
// 3. 计算置位数
unsigned int n = 0xFF; // 二进制:11111111
printf("数字 %u 中1的个数: %d\n", n, countSetBits(n));
// 4. 2的幂检查
int powerOfTwo = 16;
printf("%d %s 2的幂\n", powerOfTwo,
IS_POWER_OF_TWO(powerOfTwo) ? "是" : "不是");
// 5. 最低位1
int number = 12; // 二进制:1100
printf("%d 的最低位1值为: %d\n", number, LOWEST_SET_BIT(number));
return 0;
}
3.3 位运算高级应用
#include <stdio.h>
// 1. 位域结构
typedef struct {
unsigned int ready: 1; // 1位,状态标志
unsigned int busy: 1; // 1位,忙标志
unsigned int error: 2; // 2位,错误代码
unsigned int command: 4; // 4位,命令
unsigned int reserved: 24; // 24位,保留
} StatusRegister;
// 2. 位掩码操作
#define SET_BIT(x, pos) ((x) |= (1U << (pos)))
#define CLEAR_BIT(x, pos) ((x) &= ~(1U << (pos)))
#define TOGGLE_BIT(x, pos) ((x) ^= (1U << (pos)))
#define GET_BIT(x, pos) (((x) >> (pos)) & 1U)
// 3. 循环移位
unsigned int rotateLeft(unsigned int value, int shift) {
if ((shift &= sizeof(value) * 8 - 1) == 0)
return value;
return (value << shift) | (value >> (sizeof(value) * 8 - shift));
}
unsigned int rotateRight(unsigned int value, int shift) {
if ((shift &= sizeof(value) * 8 - 1) == 0)
return value;
return (value >> shift) | (value << (sizeof(value) * 8 - shift));
}
// 4. 位图实现
#define BITMAP_SIZE 128
typedef struct {
unsigned int bits[BITMAP_SIZE / 32];
} Bitmap;
void setBit(Bitmap* bitmap, int pos) {
bitmap->bits[pos / 32] |= (1U << (pos % 32));
}
void clearBit(Bitmap* bitmap, int pos) {
bitmap->bits[pos / 32] &= ~(1U << (pos % 32));
}
int testBit(Bitmap* bitmap, int pos) {
return (bitmap->bits[pos / 32] >> (pos % 32)) & 1U;
}
int main() {
// 1. 位域示例
StatusRegister status = {0};
status.ready = 1;
status.command = 5;
printf("状态寄存器: ready=%d, command=%d\n",
status.ready, status.command);
// 2. 位掩码操作示例
unsigned int flags = 0;
SET_BIT(flags, 3); // 设置第3位
TOGGLE_BIT(flags, 5); // 切换第5位
printf("标志位: %08X\n", flags);
printf("第3位: %d\n", GET_BIT(flags, 3));
// 3. 循环移位示例
unsigned int value = 0x12345678;
printf("原值: %08X\n", value);
printf("左移4位: %08X\n", rotateLeft(value, 4));
printf("右移4位: %08X\n", rotateRight(value, 4));
// 4. 位图示例
Bitmap bitmap = {0};
setBit(&bitmap, 100); // 设置第100位
setBit(&bitmap, 101); // 设置第101位
printf("第100位: %d\n", testBit(&bitmap, 100));
printf("第99位: %d\n", testBit(&bitmap, 99));
return 0;
}
【核心要点】
-
基本位运算:
- 按位与、或、异或、取反
- 左移和右移操作
- 带符号数和无符号数的区别
- 位运算优先级
-
位运算技巧:
- 奇偶性判断
- 无临时变量交换
- 计算置位数
- 2的幂检查
- 最低位1提取
-
高级应用:
- 位域结构定义
- 位掩码操作
- 循环移位实现
- 位图数据结构
⚠️ 注意事项:
- 位运算的优先级
- 带符号数右移的符号扩展
- 位域的对齐和移植性
- 位运算的性能考虑
- 代码可读性平衡
🔍 应用场景:
- 状态标志管理
- 权限控制
- 硬件寄存器操作
- 数据压缩
- 加密算法
4. 排序算法
4.1 基本排序算法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 1. 冒泡排序 - 时间复杂度O(n²)
void bubbleSort(int arr[], int n) {
int i, j;
int flag; // 优化标志
for (i = 0; i < n - 1; i++) {
flag = 0;
for (j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = 1;
}
}
if (!flag) break; // 如果一轮未发生交换,说明已经有序
}
}
// 2. 选择排序 - 时间复杂度O(n²)
void selectionSort(int arr[], int n) {
int i, j, min_idx;
for (i = 0; i < n - 1; i++) {
min_idx = i;
for (j = i + 1; j < n; j++) {
if (arr[j] < arr[min_idx])
min_idx = j;
}
if (min_idx != i) {
int temp = arr[i];
arr[i] = arr[min_idx];
arr[min_idx] = temp;
}
}
}
// 3. 插入排序 - 时间复杂度O(n²)
void insertionSort(int arr[], int n) {
int i, key, j;
for (i = 1; i < n; i++) {
key = arr[i];
j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
// 4. 希尔排序 - 时间复杂度O(n^1.3)
void shellSort(int arr[], int n) {
for (int gap = n/2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j;
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap)
arr[j] = arr[j - gap];
arr[j] = temp;
}
}
}
4.2 高级排序算法
// 1. 快速排序 - 时间复杂度O(nlogn)
void quickSort(int arr[], int low, int high) {
if (low < high) {
// 三数取中法选择基准
int mid = low + (high - low) / 2;
if (arr[low] > arr[high])
swap(&arr[low], &arr[high]);
if (arr[mid] > arr[high])
swap(&arr[mid], &arr[high]);
if (arr[low] > arr[mid])
swap(&arr[low], &arr[mid]);
int pivot = arr[mid];
int i = low - 1;
int j = high + 1;
while (1) {
do {
i++;
} while (arr[i] < pivot);
do {
j--;
} while (arr[j] > pivot);
if (i >= j)
break;
swap(&arr[i], &arr[j]);
}
quickSort(arr, low, j);
quickSort(arr, j + 1, high);
}
}
// 2. 归并排序 - 时间复杂度O(nlogn)
void merge(int arr[], int left, int mid, int right) {
int i, j, k;
int n1 = mid - left + 1;
int n2 = right - mid;
int *L = (int*)malloc(n1 * sizeof(int));
int *R = (int*)malloc(n2 * sizeof(int));
for (i = 0; i < n1; i++)
L[i] = arr[left + i];
for (j = 0; j < n2; j++)
R[j] = arr[mid + 1 + j];
i = 0;
j = 0;
k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
free(L);
free(R);
}
void mergeSort(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
// 3. 堆排序 - 时间复杂度O(nlogn)
void heapify(int arr[], int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[largest])
largest = left;
if (right < n && arr[right] > arr[largest])
largest = right;
if (largest != i) {
swap(&arr[i], &arr[largest]);
heapify(arr, n, largest);
}
}
void heapSort(int arr[], int n) {
// 构建最大堆
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// 逐个从堆中取出元素
for (int i = n - 1; i > 0; i--) {
swap(&arr[0], &arr[i]);
heapify(arr, i, 0);
}
}
4.3 排序算法的比较和应用
// 1. 排序算法性能测试
void testSortingAlgorithm(void (*sortFunc)(int[], int), const char* name, int arr[], int n) {
int *testArr = (int*)malloc(n * sizeof(int));
memcpy(testArr, arr, n * sizeof(int));
clock_t start = clock();
sortFunc(testArr, n);
clock_t end = clock();
double time_spent = (double)(end - start) / CLOCKS_PER_SEC;
printf("%s 用时: %f秒\n", name, time_spent);
// 验证排序结果
int isSorted = 1;
for (int i = 1; i < n; i++) {
if (testArr[i] < testArr[i-1]) {
isSorted = 0;
break;
}
}
printf("排序%s\n", isSorted ? "正确" : "错误");
free(testArr);
}
// 2. 排序算法选择指南
/*
选择排序算法的考虑因素:
1. 数据规模
- 小规模数据(n < 50):插入排序
- 中等规模(50 <= n < 10000):快速排序
- 大规模数据(n >= 10000):归并排序或堆排序
2. 稳定性要求
- 需要稳定排序:归并排序、插入排序、冒泡排序
- 不需要稳定性:快速排序、堆排序、希尔排序
3. 空间复杂度要求
- O(1)空间:堆排序、快速排序(平均)
- O(n)空间:归并排序
4. 数据特征
- 接近有序:插入排序
- 完全随机:快速排序
- 大量重复元素:三路快排
*/
// 3. 实际应用示例
void sortExample() {
// 生成测试数据
int n = 10000;
int *arr = (int*)malloc(n * sizeof(int));
for (int i = 0; i < n; i++)
arr[i] = rand() % 10000;
// 测试各种排序算法
printf("=== 排序算法性能比较 ===\n");
testSortingAlgorithm(bubbleSort, "冒泡排序", arr, n);
testSortingAlgorithm(insertionSort, "插入排序", arr, n);
testSortingAlgorithm(quickSort, "快速排序", arr, n);
testSortingAlgorithm(mergeSort, "归并排序", arr, n);
testSortingAlgorithm(heapSort, "堆排序", arr, n);
free(arr);
}
【核心要点】
-
基本排序算法:
- 冒泡排序:相邻元素比较交换
- 选择排序:每次选择最小元素
- 插入排序:构建有序序列
- 希尔排序:改进的插入排序
-
高级排序算法:
- 快速排序:分治法,基准选择关键
- 归并排序:分治法,稳定排序
- 堆排序:利用堆数据结构
-
算法选择:
- 考虑数据规模
- 考虑稳定性要求
- 考虑空间复杂度
- 考虑数据特征
⚠️ 注意事项:
- 基准元素的选择对快排性能影响大
- 递归深度可能导致栈溢出
- 原地排序vs额外空间
- 稳定性的保证
- 特殊输入的处理
5. 查找算法
5.1 静态查找
#include <stdio.h>
// 1. 顺序查找
int sequentialSearch(int arr[], int n, int target) {
for (int i = 0; i < n; i++) {
if (arr[i] == target)
return i;
}
return -1;
}
// 2. 二分查找(迭代版本)
int binarySearch(int arr[], int n, int target) {
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出
if (arr[mid] == target)
return mid;
if (arr[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1;
}
// 3. 二分查找(递归版本)
int binarySearchRecursive(int arr[], int left, int right, int target) {
if (right >= left) {
int mid = left + (right - left) / 2;
if (arr[mid] == target)
return mid;
if (arr[mid] > target)
return binarySearchRecursive(arr, left, mid - 1, target);
return binarySearchRecursive(arr, mid + 1, right, target);
}
return -1;
}
// 4. 插值查找
int interpolationSearch(int arr[], int n, int target) {
int left = 0;
int right = n - 1;
while (left <= right && target >= arr[left] && target <= arr[right]) {
if (left == right) {
if (arr[left] == target) return left;
return -1;
}
// 使用插值公式计算探测点
int pos = left + (((double)(right - left) * (target - arr[left])) /
(arr[right] - arr[left]));
if (arr[pos] == target)
return pos;
if (arr[pos] < target)
left = pos + 1;
else
right = pos - 1;
}
return -1;
}
5.2 动态查找
// 1. 二叉搜索树节点定义
typedef struct TreeNode {
int key;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
// 创建新节点
TreeNode* createNode(int key) {
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
node->key = key;
node->left = node->right = NULL;
return node;
}
// 插入节点
TreeNode* insert(TreeNode* root, int key) {
if (root == NULL)
return createNode(key);
if (key < root->key)
root->left = insert(root->left, key);
else if (key > root->key)
root->right = insert(root->right, key);
return root;
}
// 查找节点
TreeNode* search(TreeNode* root, int key) {
if (root == NULL || root->key == key)
return root;
if (key < root->key)
return search(root->left, key);
return search(root->right, key);
}
// 2. 平衡二叉树(AVL树)节点
typedef struct AVLNode {
int key;
struct AVLNode *left;
struct AVLNode *right;
int height;
} AVLNode;
// 获取节点高度
int height(AVLNode *N) {
if (N == NULL)
return 0;
return N->height;
}
// 获取平衡因子
int getBalance(AVLNode *N) {
if (N == NULL)
return 0;
return height(N->left) - height(N->right);
}
// AVL树的插入和查找操作(略)
5.3 哈希查找
#define TABLE_SIZE 10
// 1. 链地址法
typedef struct HashNode {
int key;
int value;
struct HashNode* next;
} HashNode;
typedef struct {
HashNode* table[TABLE_SIZE];
} HashMap;
// 初始化哈希表
void initHashMap(HashMap* map) {
for (int i = 0; i < TABLE_SIZE; i++)
map->table[i] = NULL;
}
// 哈希函数
int hash(int key) {
return key % TABLE_SIZE;
}
// 插入键值对
void insert(HashMap* map, int key, int value) {
int index = hash(key);
HashNode* newNode = (HashNode*)malloc(sizeof(HashNode));
newNode->key = key;
newNode->value = value;
newNode->next = map->table[index];
map->table[index] = newNode;
}
// 查找值
HashNode* search(HashMap* map, int key) {
int index = hash(key);
HashNode* current = map->table[index];
while (current != NULL) {
if (current->key == key)
return current;
current = current->next;
}
return NULL;
}
// 2. 开放地址法
typedef struct {
int key;
int value;
int isOccupied;
} HashEntry;
typedef struct {
HashEntry* table;
int size;
} HashTable;
// 初始化开放地址哈希表
HashTable* createHashTable(int size) {
HashTable* ht = (HashTable*)malloc(sizeof(HashTable));
ht->table = (HashEntry*)calloc(size, sizeof(HashEntry));
ht->size = size;
return ht;
}
// 线性探测
int linearProbe(HashTable* ht, int key) {
int index = hash(key);
int i = 0;
while (ht->table[(index + i) % ht->size].isOccupied &&
ht->table[(index + i) % ht->size].key != key) {
i++;
if (i == ht->size) return -1; // 表满
}
return (index + i) % ht->size;
}
【核心要点】
-
静态查找:
- 顺序查找:O(n),适用于小规模无序数据
- 二分查找:O(logn),要求有序数据
- 插值查找:O(loglogn),适用于均匀分布数据
-
动态查找:
- 二叉搜索树:平均O(logn),最差O(n)
- AVL树:严格O(logn),自平衡
- 红黑树:O(logn),实际应用广泛
-
哈希查找:
- 链地址法:处理冲突,适用于动态数据
- 开放地址法:节省空间,适用于静态数据
- 时间复杂度:平均O(1)
⚠️ 注意事项:
-
选择合适的查找算法要考虑:
- 数据规模
- 数据是否有序
- 数据分布特征
- 空间限制
- 查找频率
-
实现细节:
- 边界条件处理
- 溢出处理
- 错误处理
- 资源管理
🔍 应用场景:
-
静态查找:
- 有序数组查找
- 数据库索引
- 文件检索
-
动态查找:
- 符号表实现
- 路由表查找
- 文件系统
-
哈希查找:
- 缓存系统
- 数据库索引
- 字符串匹配
6. 字符串算法
6.1 字符串反转
void reverseString(char* str) {
int length = strlen(str);
for (int i = 0; i < length/2; i++) {
char temp = str[i];
str[i] = str[length-1-i];
str[length-1-i] = temp;
}
}
6.2 KMP字符串匹配
void computeLPSArray(char* pattern, int M, int* lps) {
int len = 0;
lps[0] = 0;
int i = 1;
while (i < M) {
if (pattern[i] == pattern[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len-1];
} else {
lps[i] = 0;
i++;
}
}
}
}
int KMPSearch(char* text, char* pattern) {
int M = strlen(pattern);
int N = strlen(text);
int lps[M];
computeLPSArray(pattern, M, lps);
int i = 0;
int j = 0;
while (i < N) {
if (pattern[j] == text[i]) {
j++;
i++;
}
if (j == M) {
return i-j; // 找到匹配
} else if (i < N && pattern[j] != text[i]) {
if (j != 0)
j = lps[j-1];
else
i++;
}
}
return -1; // 未找到匹配
}
7. 数学算法
7.1 最大公约数(GCD)
int gcd(int a, int b) {
if (b == 0)
return a;
return gcd(b, a % b);
}
// 扩展欧几里得算法
void extendedGCD(int a, int b, int *x, int *y) {
if (b == 0) {
*x = 1;
*y = 0;
return;
}
int x1, y1;
extendedGCD(b, a % b, &x1, &y1);
*x = y1;
*y = x1 - (a/b) * y1;
}
7.2 素数筛选(埃氏筛)
void sieveOfEratosthenes(int n) {
int prime[n+1];
memset(prime, 1, sizeof(prime));
for (int p = 2; p * p <= n; p++) {
if (prime[p]) {
// 将p的倍数标记为非素数
for (int i = p * p; i <= n; i += p)
prime[i] = 0;
}
}
// 打印素数
for (int p = 2; p <= n; p++)
if (prime[p])
printf("%d ", p);
}
8. 图算法
8.1 深度优先搜索(DFS)
#define MAX_VERTICES 100
void DFS(int graph[MAX_VERTICES][MAX_VERTICES], int vertex, int visited[], int V) {
printf("%d ", vertex);
visited[vertex] = 1;
for (int i = 0; i < V; i++) {
if (graph[vertex][i] && !visited[i]) {
DFS(graph, i, visited, V);
}
}
}
8.2 广度优先搜索(BFS)
void BFS(int graph[MAX_VERTICES][MAX_VERTICES], int start, int V) {
int visited[MAX_VERTICES] = {0};
int queue[MAX_VERTICES];
int front = 0, rear = 0;
visited[start] = 1;
queue[rear++] = start;
while (front < rear) {
int vertex = queue[front++];
printf("%d ", vertex);
for (int i = 0; i < V; i++) {
if (graph[vertex][i] && !visited[i]) {
visited[i] = 1;
queue[rear++] = i;
}
}
}
}
9. 动态规划
9.1 斐波那契数列
int fibonacci(int n) {
if (n <= 1) return n;
int dp[n+1];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++)
dp[i] = dp[i-1] + dp[i-2];
return dp[n];
}
9.2 背包问题
int knapsack(int W, int wt[], int val[], int n) {
int dp[n+1][W+1];
for (int i = 0; i <= n; i++) {
for (int w = 0; w <= W; w++) {
if (i == 0 || w == 0)
dp[i][w] = 0;
else if (wt[i-1] <= w)
dp[i][w] = max(val[i-1] + dp[i-1][w-wt[i-1]],
dp[i-1][w]);
else
dp[i][w] = dp[i-1][w];
}
}
return dp[n][W];
}
以上就是全部的内容。如果各位大佬有疑问,欢迎在评论区留言,你的点赞收藏是我创作的动力!