一、知识点全景表格(覆盖我这3千行代码的全部内容!
知识领域 | 具体知识点 | 代码示例 | 工业实践建议 |
---|---|---|---|
数组基础 | 一维数组初始化与遍历 | 里面的testArrayInitialization() 函数,演示显式初始化、隐式补零、自动计算长度 | 初始化时避免数组越界,建议用sizeof 计算长度,如ARRAY_LEN(arr) 宏 |
数组越界风险与调试 | demonstrateArrayOutOfBounds() 故意越界访问,触发未定义行为 | 用-fsanitize=address 编译选项检测越界,配合gdb 追踪地址访问错误 | |
二维数组内存布局与转置 | zhuanzhi() 函数实现矩阵转置,通过行优先遍历交换元素 | 二维数组传参时用行指针int arr[][col] ,避免数组退化为一级指针导致的列数丢失 | |
排序算法 | 冒泡排序优化(含有序标记) | bubbleSort() 基础实现,可优化添加isSorted 标志提前终止循环 | 大规模数据用qsort 库函数,小规模用插入排序,冒泡仅用于教学场景 |
选择排序的原地交换逻辑 | selectionSort() 通过最小索引交换元素,时间复杂度 O (n²) | 不稳定排序,适合不要求稳定性的场景(如哈希表冲突处理) | |
快速排序与分区策略 | quick_Sort() 结合partition() 实现降序分区,输出每次分区结果 | 三数取中法优化枢轴选择,避免最坏情况 O (n²),建议用shuffle 预处理数组 | |
指针深度 | 指针与数组的等价性 | arr1_ptr() 用int* 指针遍历数组,验证arr[i] == *(ptr+i) | 数组名作为参数时退化为指针,务必同时传递长度参数,如printArray(arr, len) |
多级指针与结构体指针数组 | Node 结构体中children 为struct Node** 二级指针,动态管理子节点数组 | 多级指针初始化时需parent->children = NULL ,扩容用realloc 并校验返回值 | |
void 指针的通用转换 | arrtoPtrInt() 用char* 偏移实现任意类型指针运算,返回void* | 避免void* 直接解引用,必须强转类型并确保对齐,如*(int*)ptr | |
内存管理 | 动态内存分配与性能测试 | dy_alloc_mem() 分配 100 万元素数组,用clock() 测试初始化耗时 | 大块内存用mmap 替代malloc ,用valgrind --tool=massif 分析内存峰值 |
柔性数组与结构体设计 | 代码未显式实现,但可扩展struct Node 添加柔性数组children[] 节省内存 | 柔性数组需保证最后一个元素为变长部分,如struct { int len; int data[]; } Array; | |
数据结构 | 单向链表的头插法实现 | insert_atFirst() 创建新节点并更新头指针,print_List() 遍历链表 | 链表节点用dummy head 简化边界处理,销毁时需递归释放节点 |
顺序栈的临界条件处理 | stack 结构体实现入栈pushStack 、出栈popStack ,包含空栈 / 满栈判断 | 栈顶索引从 - 1 开始,入栈先自增再赋值,出栈先取值再自减,避免竞态条件 | |
结构体嵌套与内存对齐 | Person_16 嵌套Address 结构体,用#pragma pack(1) 取消对齐验证内存占用 | 工业代码用__attribute__((packed)) 声明紧凑结构体,避免对齐导致的空间浪费 | |
字符串处理 | 指针遍历实现字符串逆序 | string_reverse() 用双指针交换字符,时间复杂度 O (n) | 处理多字节字符(如 UTF-8)需按编码单元操作,避免截断字符 |
安全字符串拼接 | connect_char() 用strlen 计算长度后追加,避免缓冲区溢出 | 生产环境用snprintf 替代手动拼接,指定目标缓冲区大小如connect_char(a, b, sizeof(a)) | |
预处理与宏 | 数组长度计算宏 | ARRAY_LEN(arr) 用sizeof 计算数组元素个数,避免硬编码长度 | 宏定义加括号(sizeof(arr)/sizeof((arr)[0])) ,防止表达式优先级问题 |
类型别名简化结构体声明 | typedef struct Node 简化链表节点声明,相比struct Node 减少代码冗余 | 复杂结构体用typedef 封装,如typedef struct { ... } ListNode; | |
函数与指针 | 函数指针实现计算器 | 代码注释中保留的计算器逻辑,通过int (*func_ptr)(int, int) 指向add/substract | 用函数指针数组实现命令分发,如int (*ops[])(int, int) = {add, sub, mul, div}; |
可变参数与日志系统 | 代码注释中的DEBUG(fmt, ...) 宏,可扩展为带文件 / 行号的日志输出 | 用va_list 实现可变参数函数,如void log(const char* fmt, ...) |
二、核心模块深度解析
1. 数组与指针:内存操作的双刃剑
1.1 数组越界的致命陷阱
void demonstrateArrayOutOfBounds() {
int arr[6] = {1, 2, 3, 4, 5, 6};
for (int i = 0; i <= 8; i++) { // 访问arr[6]~arr[8]触发越界
printf("arr[%d]=%d\n", i, arr[i]); // 可能输出随机值或崩溃
}
}
- 风险分析:越界访问会覆盖相邻内存,可能导致:
- 栈帧破坏引发段错误(如覆盖函数返回地址)
- 堆内存元数据损坏导致
malloc
崩溃 - 静默数据错误(如误改其他变量值)
- 工业方案:
- 用
-fsanitize=address
编译,运行时检测越界 - 封装安全数组结构体:
typedef struct { int *data; size_t len; size_t cap; } SafeArray; int safe_array_get(SafeArray *arr, size_t index, int *val) { if (index >= arr->len) return -1; // 越界返回错误码 *val = arr->data[index]; return 0; }
- 用
1.2 二维数组转置的指针优化
void zhuanzhi(int arr[3][3]) {
for (int i = 0; i < 3; i++) {
for (int j = i + 1; j < 3; j++) {
int temp = arr[i][j];
arr[i][j] = arr[j][i];
arr[j][i] = temp;
}
}
}
- 内存布局:二维数组在内存中按行优先存储,
arr[i][j]
等价于*(arr[i] + j)
- 行指针传参:函数参数
int arr[3][3]
等价于int (*arr)[3]
,表示指向含 3 个元素的一维数组的指针 - 扩展应用:动态二维数组用指针数组实现:
int **alloc_2dArr(int row, int col) { int **a = malloc(row * sizeof(int*)); for (int i=0; i<row; i++) { a[i] = malloc(col * sizeof(int)); } return a; // 调用后需逐层free }
2. 排序算法:从 O (n²) 到 O (n log n) 的性能跃迁
2.1 快速排序的分区与递归优化
int partition(int arr[], int left, int right) {
int pivot = arr[right]; // 选择最后一个元素为枢轴
int i = left - 1;
for (int j = left; j < right; j++) {
if (arr[j] > pivot) { // 降序分区:左侧元素 > 枢轴
swap(&arr[++i], &arr[j]); // 交换到左侧
}
}
swap(&arr[i+1], &arr[right]); // 枢轴归位
return i + 1; // 枢轴索引即第i+1大元素位置
}
- 时间复杂度:平均 O (n log n),最坏 O (n²)(有序数组需三数取中法优化)
- 递归深度控制:对大区间用迭代实现,小区间用插入排序:
void quick_sort_iterative(int arr[], int left, int right) { struct Stack { int l, r; } stack[100]; // 手动模拟栈 int top = -1; push(&stack, &top, left, right); while (top >= 0) { struct Stack node = pop(&stack, &top); if (node.l < node.r) { int pi = partition(arr, node.l, node.r); // 先压入小区间,后压入大区间,减少栈深度 if (pi - node.l < node.r - pi) { push(&stack, &top, pi+1, node.r); push(&stack, &top, node.l, pi-1); } else { push(&stack, &top, node.l, pi-1); push(&stack, &top, pi+1, node.r); } } } }
3. 动态内存与数据结构:工业级设计要点
3.1 链表节点的安全管理
struct Node *insert_atFirst(struct Node *head, int data) {
struct Node *n_node = malloc(sizeof(struct Node)); // 分配节点
if (!n_node) { perror("malloc failed"); exit(1); } // 错误处理
n_node->data = data;
n_node->next = head; // 新节点指向原头节点
return n_node; // 返回新头节点
}
- 内存泄漏预防:
- 插入失败时确保已分配内存被释放
- 销毁链表时递归释放:
void free_list(struct Node *head) { while (head) { struct Node *temp = head; head = head->next; free(temp); } }
- 线程安全:多线程环境需加锁,如用
pthread_mutex_t
保护链表操作
3.2 顺序栈的临界条件测试
int popStack(struct stack *s) {
if (isEmpty(s)) {
return -1; // 空栈返回特殊值
}
return s->data[s->top--]; // 先取值,后减栈顶索引
}
- 测试用例:
- 空栈弹出:验证返回值是否为 - 1
- 满栈压入:调用
isFull
后验证是否拒绝 - 边界值测试:栈满时
top == maxL-1
,而非maxL
(原代码 bug)
- 扩展功能:支持动态扩容栈:
void resize_stack(struct stack *s, int new_size) { int *new_data = realloc(s->data, new_size * sizeof(int)); if (new_data) { s->data = new_data; s->cap = new_size; // 新增容量字段 } }
4. 字符串与指针:从底层到应用
4.1 安全字符串拼接的工业实现
void connect_char(char *a, char *b) {
size_t a_len = strlen(a);
size_t b_len = strlen(b);
if (a_len + b_len + 1 > sizeof(a)) { // 假设a为固定大小数组
fprintf(stderr, "buffer overflow risk!\n");
return;
}
strcpy(a + a_len, b); // 等价于手动遍历
}
- 缺陷分析:原代码未校验目标缓冲区大小,可能导致溢出
- 正确实现:
void safe_strcat(char *dest, const char *src, size_t dest_size) { size_t dest_len = strlen(dest); size_t src_len = strlen(src); if (dest_len + src_len + 1 >= dest_size) { errno = ERANGE; // 设置错误码 return; } memcpy(dest + dest_len, src, src_len + 1); }
4.2 void 指针的跨类型应用
void *arrtoPtrInt(void *x) {
return (char *)x + 2; // 偏移2字节,适用于char*指针
}
- 应用场景:
- 协议解析:跳过协议头 2 字节,如
uint8_t header[2]; uint8_t data = *(uint8_t*)arrtoPtrInt(header);
- 内存对齐:确保指针偏移后仍对齐,如
(int*)((char*)ptr + (sizeof(int)-1)) & ~(sizeof(int)-1)
- 协议解析:跳过协议头 2 字节,如
三、未覆盖知识点与扩展方向(基于代码注释)
知识领域 | 缺失知识点 | 工业实现建议 |
---|---|---|
指针进阶 | const 限定符与指针权限 | 用const int* 防止指针修改数据,用int* const 防止指针重定向 |
函数指针数组与回调机制 | 实现命令模式,如int (*ops[])(int, int) = {add, sub, mul}; | |
内存管理 | 内存池设计 | 预分配大块内存,用链表管理空闲块,减少malloc/free 系统调用开销 |
数据结构 | 二叉树与哈希表 | 用平衡二叉树实现快速查找,哈希表处理键值对存储(如用户信息管理) |
预处理 | 条件编译与带副作用宏 | #ifdef DEBUG 包裹调试代码,宏定义避免副作用:#define MIN(a,b) ((a)<(b)?(a):(b)) |
标准库扩展 | qsort/bsearch 与安全字符串函数 | 用qsort(arr, len, sizeof(int), cmp) 排序,strncpy 替代strcpy 防止溢出 |
四、我这4千行代码的核心函数解析 :我自己写的15 个典型案例
1. testArrayInitialization()
- 数组初始化与内存布局
void testArrayInitialization() {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5] = {1, 2, 3}; // 自动补0
int arr3[] = {1, 3, 4, 5, 6}; // 自动计算长度
printf("一维数组初始化测试:\n");
for (int i = 0; i < 5; i++) {
printf("arr1[%d]=%d, arr2[%d]=%d, arr3[%d]=%d\n",
i, arr1[i], i, arr2[i], i, arr3[i]);
}
}
知识点剖析:
- 显式初始化:
arr1
完全初始化,内存布局为连续的 5 个整数 - 隐式补零:
arr2
部分初始化,未显式赋值的元素自动初始化为 0 - 自动长度计算:
arr3
省略长度,编译器根据初始化列表推断长度为 5 - 内存对齐:数组元素按类型自然对齐,
int
通常占 4 字节(取决于平台)
工业实践:
- 避免越界访问:初始化时明确长度,或用
sizeof(arr)/sizeof(arr[0])
计算 - 全局数组默认初始化为 0,局部数组需显式初始化
- 多维数组初始化:
int arr[2][3] = {{1,2,3},{4,5,6}}
等价于按行展开的一维数组
2. demonstrateArrayOutOfBounds()
- 数组越界的未定义行为
void demonstrateArrayOutOfBounds() {
int arr[6] = {1, 2, 3, 4, 5, 6};
printf("越界访问示例(危险!):\n");
for (int i = 0; i <= 8; i++) { // 注意这里会访问arr[6]导致越界
printf("arr[%d]=%d\n", i, arr[i]);
}
}
风险分析:
- 栈内存布局:数组
arr
后的内存可能存储函数返回地址、局部变量 - 越界后果:
- 读取随机值(访问未初始化内存)
- 覆盖相邻变量(如
i
被修改导致死循环) - 段错误(访问非法地址,如
arr[1000]
)
调试技巧:
- 编译选项:
-fsanitize=address
(AddressSanitizer)在运行时检测越界 - GDB 断点:在循环内设置断点,观察
i
和arr[i]
的值变化 - Valgrind:用
--tool=memcheck
检测内存访问错误
3. test2DArray()
- 二维数组的内存布局与访问
void test2DArray() {
int arr[2][3] = {{12, 3, 4}, {12, 22, 33}};
printf("\n二维数组演示:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("arr[%d][%d]=%d\t", i, j, arr[i][j]);
}
printf("\n");
}
}
内存模型:
- 二维数组在内存中按行优先存储,等价于一维数组
{12,3,4,12,22,33}
arr[i][j]
的地址计算:&arr[0][0] + i*3 + j
- 行指针与列指针:
arr
是指向第一行的指针(类型为int (*)[3]
)arr[i]
是指向第 i 行首元素的指针(类型为int*
)
传参陷阱:
- 函数参数必须指定列数:
void func(int arr[][3], int rows)
- 若列数不确定,需用动态二维数组(二级指针或数组指针)
4. printArray(int arr[], int size)
- 数组作为函数参数
void printArray(int arr[], int size) {
printf("\n数组作为参数传递:\n");
for (int i = 0; i < size; i++) {
printf("arr[%d]=%d\n", i, arr[i]);
}
}
参数退化:
int arr[]
等价于int* arr
,数组名退化为指针- 丢失数组长度信息,必须显式传递
size
参数 - 与
sizeof(arr)
的区别:函数内sizeof(arr)
返回指针大小(通常 8 字节)
替代方案:
- 用结构体封装数组和长度:
typedef struct { int *data; size_t size; } Array; void printArray(Array arr) { ... }
- 用宏计算长度:
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
5. bubbleSort(int arr[], int size)
- 冒泡排序与优化
void bubbleSort(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;
}
}
}
}
算法分析:
- 时间复杂度:O (n²),稳定排序
- 优化点:添加有序标记,提前终止循环
void optimizedBubbleSort(int arr[], int size) { int swapped; for (int i = 0; i < size - 1; i++) { swapped = 0; for (int j = 0; j < size - i - 1; j++) { if (arr[j] > arr[j + 1]) { swap(&arr[j], &arr[j + 1]); swapped = 1; } } if (!swapped) break; // 已排序,提前退出 } }
工业应用:
- 小规模数据排序(n<100)
- 部分有序数据的适应性排序
6. selectionSort(int arr[], int size)
- 选择排序的实现
void selectionSort(int arr[], int size) {
for (int i = 0; i < size - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < size; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
算法特性:
- 时间复杂度:O (n²),不稳定排序
- 比较次数:固定为 n (n-1)/2 次,交换次数最多 n-1 次
- 内存访问模式:每次选择最小值需遍历未排序部分,缓存不友好
适用场景:
- 交换成本高的场景(如链表排序)
- 数据规模较小且对稳定性无要求的排序
7. reverseArray(int arr[], int size)
- 数组逆序的双指针技巧
void reverseArray(int arr[], int size) {
printf("\n数组逆序前:\n");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
int start = 0;
int end = size - 1;
while (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
printf("\n数组逆序后:\n");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
算法分析:
- 时间复杂度:O (n/2),空间复杂度 O (1)
- 双指针技巧:首尾指针向中间移动,每次交换元素
- 边界条件:奇数长度数组中间元素无需交换(
start < end
而非start <= end
)
扩展应用:
- 字符串反转:
reverseString(char* str)
- 链表反转:需修改指针指向关系
- 旋转数组:先整体反转,再局部反转
8. zhuanzhi(int arr[3][3])
- 矩阵转置的内存操作
void zhuanzhi(int arr[3][3]) {
for (int i = 0; i < 3; i++) {
for (int j = i + 1; j < 3; j++) {
int temp = arr[i][j];
arr[i][j] = arr[j][i];
arr[j][i] = temp;
}
}
}
实现要点:
- 只遍历上三角:避免重复交换(如
arr[0][1]
和arr[1][0]
被交换两次) - 原地转置:无需额外空间,时间复杂度 O (n²)
- 非方阵转置:需创建新矩阵,无法原地操作
内存优化:
- 缓存优化:按行优先遍历,减少缓存失效
- 分块转置:大规模矩阵按块处理,提高缓存命中率
9. dy_alloc_mem()
- 动态内存分配与性能测试
static void dy_alloc_mem() {
int *arr1 = (int *)malloc(1000000 * sizeof(int));
if (arr1 == NULL) {
printf("内存分配失败!\n");
return;
}
for (int i = 0; i < 1000000; i++) {
*(arr1 + i) = i;
}
clock_t start, end;
start = clock();
int i;
for (i = 0; i < 100; i++) {
printf("%d \n", *(arr1 + i));
}
end = clock();
double t = (double)(end - start);
printf("打印这些%d用了%10f毫秒\n", i, t);
}
内存管理:
malloc
返回 void*,需显式转换类型(C++ 要求,C 可选)- 内存分配失败返回 NULL,必须检查返回值
clock()
计算 CPU 时间,gettimeofday()
获取更精确的挂钟时间
性能分析:
- 内存访问模式:顺序访问,缓存友好
- 预取优化:现代 CPU 自动预取连续内存,加速遍历
- 内存碎片:频繁分配释放可能导致堆碎片,可用内存池解决
10. string_reverse(char *str, int len)
- 字符串反转的指针操作
void string_reverse(char *str, int len) {
char *start = str;
char *end = start + len - 1;
while (start < end) {
char temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
}
指针技巧:
- 指针运算:
end = start + len - 1
定位到字符串末尾 - 解引用操作:
*start
和*end
交换字符 - 双指针相向移动:避免使用索引,提高效率
安全考虑:
- 传入长度参数避免
strlen
重复计算 - 需确保
len
不超过字符串实际长度(不含 '\0') - 处理多字节字符(如 UTF-8)需特殊处理,避免截断
11. insert_atFirst(struct Node *head, int data)
- 链表头插法
struct Node *insert_atFirst(struct Node *head, int data) {
struct Node *n_node = (struct Node *)malloc(sizeof(struct Node));
n_node->data = data;
n_node->next = head;
return n_node;
}
数据结构设计:
- 节点包含数据域和指向下一节点的指针
- 头插法时间复杂度 O (1),无需遍历链表
- 返回新头指针,调用者需更新头指针
内存管理:
- 节点创建必须用
malloc
动态分配 - 避免内存泄漏:不再使用的节点需
free
- 线程安全:多线程环境需加锁保护插入操作
12. quick_Sort(int arr[], int low, int high)
- 快速排序的递归实现
void quick_Sort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quick_Sort(arr, low, pi - 1);
quick_Sort(arr, pi + 1, high);
}
}
算法分析:
- 分治策略:通过分区将数组分为两部分,递归排序
- 时间复杂度:平均 O (n log n),最坏 O (n²)(有序数组)
- 空间复杂度:递归栈深度 O (log n)(平均)
优化点:
- 三数取中法选择枢轴:
int pivot = median(arr[low], arr[mid], arr[high])
- 小规模数据用插入排序(n<10)
- 尾递归优化:先处理较小分区,减少栈深度
13. connect_char(char* a, char* b)
- 字符串拼接的指针遍历
void connect_char(char* a, char* b) {
char* p1 = a + strlen(a);
printf("这是实验18里面的callback,a的长度是:%d ,b的长度是:%d\n", strlen(a), strlen(b));
while(*b!='\0'){
*p1 = *b;
p1++;b++;
}
*p1 = '\0';
}
实现细节:
strlen(a)
计算原字符串长度,O (n) 时间复杂度p1
指向a
的末尾,逐字符复制b
- 手动添加终止符
\0
安全隐患:
- 未检查目标缓冲区大小,可能导致溢出
- 若
a
空间不足,建议返回错误码而非静默失败 - 改进版本:
int connect_safe(char* a, char* b, size_t max_len)
14. initStack(struct stack* s)
- 栈的初始化
void initStack(struct stack* s) {
s->top = -1;
}
数据结构:
- 顺序栈用数组实现,
top
为栈顶索引 - 初始化为 - 1 表示空栈
- 入栈操作:
s->data[++(s->top)] = data
- 出栈操作:
return s->data[(s->top)--]
边界条件:
- 空栈判断:
top == -1
- 栈满判断:
top == maxL - 1
(原代码错误为top == maxL
)
15. findKthlarge(int a[], int left, int right, int k)
- 第 k 大元素查找
int findKthlarge(int a[], int left, int right, int k) {
if (left == right)
return a[left];
int pivot = partition(a, left, right);
int rank = pivot - left + 1;
if (rank == k)
return a[pivot];
else if (rank > k)
return findKthlarge(a, left, pivot - 1, k);
else
return findKthlarge(a, pivot + 1, right, k - rank);
}
算法思想:
- 基于快速排序的分区思想,每次排除一半元素
- 时间复杂度:平均 O (n),最坏 O (n²)
- 空间复杂度:递归栈深度 O (log n)
优化方向:
- 随机化分区:随机选择枢轴,减少最坏情况概率
- 尾递归优化:用迭代替代递归,降低栈溢出风险
- 应用场景:Top-K 问题、中位数查找
五、我自己的一点小总结-献丑了
1 核心知识点与易错点总结表格
知识模块 | 核心知识点 | 典型代码示例 | 易错点 / 关键提醒 |
---|---|---|---|
数组基础 | 一维 / 二维数组初始化、越界风险 | testArrayInitialization() | - 隐式补零规则:未初始化元素默认 0,但局部数组需显式初始化 - 二维数组传参必须指定列数,否则退化为一级指针 |
指针深度 | 指针与数组等价性、多级指针、void 指针 | arr1_ptr() 、Node.children | - 数组名作为参数时退化为指针,必须搭配长度参数 - void 指针需强转类型解引用,避免内存对齐错误 |
动态内存 | malloc/free 配对、内存性能测试 | dy_alloc_mem() | - 忘记检查 malloc 返回 NULL 导致程序崩溃 - 大块内存分配用 realloc 扩容,避免重复申请释放 |
排序算法 | 冒泡 / 选择 / 快排实现与优化 | bubbleSort() 、quick_Sort() | - 快排枢轴选择不当导致 O (n²),建议用三数取中法 - 冒泡排序优化:添加有序标记提前终止循环 |
数据结构 | 链表头插法、顺序栈临界条件处理 | insert_atFirst() 、popStack() | - 链表插入后未更新头指针导致内存泄漏 - 栈满判断应为 top == maxL-1 ,而非maxL |
字符串处理 | 指针遍历反转、安全拼接 | string_reverse() 、connect_char() | - 手动拼接未校验缓冲区大小导致溢出,需用snprintf 替代- 多字节字符反转需按编码单元处理,避免截断 |
调试工具 | AddressSanitizer、Valgrind、GDB | 无(贯穿代码优化) | - 数组越界用-fsanitize=address 检测- 内存泄漏用 valgrind --tool=memcheck 定位 |
2、我的真实感悟与给新手的肺腑之言
记得刚开始学指针的时候,总觉得这玩意儿既神秘又酷炫。看着老师在黑板上写int *p = &a;
,觉得指针不过是存个地址而已,能有多难?直到自己写第一个链表程序,因为没给新节点的next
指针赋NULL
,程序一运行就卡在遍历那里,Debug 了整整 3 个小时 —— 当时对着黑屏的终端疯狂敲键盘,差点把笔记本砸了的心都有。后来才明白,野指针就像埋在代码里的地雷,你以为只是忘了初始化一个指针,结果它能让整个程序炸得粉碎。现在我写代码时,只要定义指针,第一反应就是给它赋值NULL
或者指向合法内存,这习惯都是当年被段错误逼出来的。
3、给新手的 5 条实操建议
-
动手比看视频更重要:
别光看华清老师演示代码,自己敲一遍并故意制造错误(如越界、野指针),观察程序反应。我在学习指针时,故意写了int *p = NULL; *p = 1;
,程序直接崩溃,从此记住空指针解引用的危害。 -
用项目驱动学习:
别停留在写单个函数,尝试做个小项目(如学生管理系统):- 用数组存储学生信息
- 用链表实现课程选修关系
- 用快排对成绩排序
遇到问题再查资料,比单纯刷题印象深 10 倍。
-
掌握 3 个调试工具:
gcc -Wall -Wextra
:开启警告,很多隐藏错误会暴露(如未初始化变量)gdb
:学会设置断点、查看变量、回溯栈帧valgrind
:每周至少用一次检测内存问题,养成良好习惯
-
建立错题本:
把每次遇到的错误分类记录,比如:- 野指针:5 次(链表插入未初始化 next)
- 内存泄漏:3 次(动态数组忘记 free)
- 越界访问:7 次(循环条件写反)
每周复习一次,避免重复踩坑。
-
别怕问问题,但先学会自己解决:
遇到问题先自己查资料、调试,卡 3 小时再问人。我曾被链表反转卡住,查了 2 小时资料,尝试了 3 种写法后终于成功,从此再也没忘记双指针反转的逻辑。
4 给新手的真心话:多练多试错!
C 语言难吗?难。别怕写烂代码,别怕遇到 bug。每一个指针错误都是提醒你更深入理解内存的机会,每一次内存泄漏都是让你学会对资源负责的教训。
记得多动手敲代码,多调试,多记录自己的错误就完事儿!
附录:5千行手写代码合集
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// D9:多级指针、void指针、const
typedef struct Node
{
int value;
struct Node **children; // #self 多级指针:指向结构体指针的数组
int child_count;
} Node;
void add_child(Node *parent, Node *child)
{
// realloc函数,动态扩展函数 !!!vip 节省空间
parent->children = realloc(parent->children, (parent->child_count + 1) * sizeof(Node *)); // #self vip 动态扩展二级指针数组
parent->children[parent->child_count++] = child;
}
void *arrtoPtrInt(void *x)
{ // #self vip void指针通用类型转换
return (char *)(x) + 2; // #self 指针运算:char*偏移2字节
}
// D10:函数的基本用法及传参
void printArr(int arr[], int len)
{ // #self 数组传参退化为指针
for (int i = 0; i < len; i++)
printf("元素%d的值: %d\n", i, arr[i]);
}
// D11:函数的传参
void arrSort(char *arr[], int n)
{ // #self 指针数组排序:字符指针比较
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - 1 - i; j++)
if (strcmp(arr[j], arr[j + 1]) > 0)
{
char *temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
// D12:指针函数
int *dy_alloc(int n)
{ // #self vip 指针函数返回动态数组
int *res = (int *)malloc(n * sizeof(int));
for (int i = 0; i < n; i++)
res[i] = i;
return res;
}
// D14:#define和typedef
#define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0])) // #self 宏定义计算数组长度
// D15:变量的作用域和生命周期
// (原代码未体现,保留为示例位置)
// D16:字符串处理函数
int getStrLen(char *a)
{ // #self 字符指针遍历
char *p = a;
while (*p != '\0')
p++;
return p - a; // #self 指针差值计算长度
}
char *strCpyFn(char *dest, char *src)
{ // #self 指针函数:返回目标字符串指针
char *result = dest;
while (*src)
*dest++ = *src++;
*dest = '\0';
return result;
}
int main()
{
// D9:多级指针测试
Node root = {1, NULL, 0};
Node child1 = {2, NULL, 0};
add_child(&root, &child1); // #self 多级指针添加子节点
printf("节点%d的子节点值: %d\n", root.value, root.children[0]->value);
// D9:void指针测试
char arr[] = {'a', 'b', 'c', 'd'};
char *ptr = arrtoPtrInt(arr);
printf("void指针偏移后的值: %c\n", *ptr);
// D10:函数传参测试
int arr1[] = {1, 2, 3, 4, 5};
printArr(arr1, ARRAY_LEN(arr1)); // #self 使用宏定义计算数组长度
// D11:指针数组排序
char *arr6[] = {"apple", "banana", "cherry"};
arrSort(arr6, 3);
printf("排序后的字符串: %s, %s, %s\n", arr6[0], arr6[1], arr6[2]);
// D12:指针函数测试
int *dyn_arr = dy_alloc(5);
printf("动态分配数组: %d, %d, %d, %d, %d\n", dyn_arr[0], dyn_arr[1], dyn_arr[2], dyn_arr[3], dyn_arr[4]);
free(dyn_arr);
// D16:字符串处理测试
char src[] = "Hello";
char dest[10];
strCpyFn(dest, src);
printf("字符串拷贝结果: %s (长度: %d)\n", dest, getStrLen(dest));
// #self ai分析 !!!vip 进度:截至25年5月20号
/* 目录逻辑:基础→指针→函数/数据→预处理/内存→字符串/算法
* D1-D7:二维数组、指针基础、字符串操作
* D8-D9:指针数组、多级指针、void/const
* D10-D13:函数传参、指针函数、递归/回调
* D14-D15:宏/类型定义、变量作用域
* D16:字符串算法(KMP等)
*/
/* 已覆盖:
* - 指针操作(一/二级指针、动态数组)
* - 内存管理(malloc/realloc)
* - 数据结构(链表、栈、快排)
* - 函数传参(指针传参、简单函数指针)
*/
/* 未覆盖但必需:
* 1. 指针:三维数组、void指针、const限定
* 2. 回调:函数指针数组、异步回调
* 3. 内存:Valgrind检测、内存池设计
* 4. 预处理:带副作用宏、条件编译
* 5. 数据结构:二叉树、哈希表
* 6. 算法:递归转迭代、双指针技巧
* 7. 标准库:qsort/bsearch、安全字符串函数
*/
/* 大厂高频考点:
* - 野指针、内存对齐
* - 缓冲区溢出防范
* - 段错误调试技巧
*/
// #self
// 所有知识点详细目录:
// 1二维数组的定义及初始化试听
// 二维数组的应用案例试听
// 二维数组应用-矩阵转置试听
// 二维数组应用-杨辉三角形试听
// 今日作业
// 2字符数组和字符串试听
// 字符数组和字符串-应用案例试听
// 二维字符数组试听
// 字符串输入输出函数
// 3指针的基本用法
// 指针的作用及学习方法05'24"
// 内存、地址、变量08'27"
// 指针和指针变量13'11"
// 指针的目标和“解引用”08'45"
// 指针的赋值14'25"
// 指针的大小11'36"
// 空指针10'05"
// 野指针及成因16'12"
// 野指针的危害是什么11'28"
// 如何避免野指针
// 4指针的运算
// 指针的算术运算17'48"
// 指针的算术运算-自增自减08'31"
// 指针算术运算符-自增自减应用109'07"
// 指针算术运算符-自增自减应用204'00"
// 指针的算术运算-应用08'32"
// 指针的算术运算-注意事项12'22"
// 指针的关系运算
// 5指针与数组
// 指针与数组基本用法11'57"
// 指针与数组使用注意事项09'49"
// 指针与数组相关笔试题
// 6指针与二维数组
// 一级指针与二维数组07'57"
// 数组指针与二维数组10'06"
// 数组指针应用12'33"
// 数组指针笔试题
// 7字符指针与字符串
// 字符指针与字符串基本用法09'15"
// 字符指针与字符串常量08'34"
// 字符指针与字符串应用
// 8指针数组
// 指针数组的基本用法14'06"
// 指针数组与二维数组11'41"
// 数组指针和指针数组的区别
// 9多级指针 void指针 const
// 多级指针基本用法10'36"
// 多级指针的运算13'45"
// 多级指针和指针数组06'09"
// void指针及用法11'38"
// const变量、const指针14'31"
// main函数的参数
// 10函数的基本用法及传参
// 函数的基本用法10'44"
// 函数的调用07'29"
// 函数的声明07'16"
// 函数的应用08'34"
// 函数传参-全局变量06'01"
// 函数的参数传递-值传递07'34"
// 函数的参数传递-值传递 -应用12'40"
// 函数的参数传递-指针传递
// 11函数的传参
// 函数传参-应用-字符串统计14'17"
// 一维数组在函数间传参14'26"
// 一维数组在函数间传参-总结04'00"
// 字符数组传参09'28"
// 二维数组在函数间传参-一级指针05'47"
// 二维数组在函数间传参 -行指针05'39"
// 二维数组在函数间传参 -
// 12指针函数
// 指针函数及用法11'44"
// 指针函数的返回值08'30"
// 指针函数-编写字符串拷贝函数13'17"
// 指针函数-编写字符串连接函数11'44"
// 指针函数案例
// 13递归函数和函数指针
// 递归函数及用法09'54"
// 递归函数应用04'37"
// 函数指针的基本用法16'07"
// 函数指针数组04'35"
// qsort函数的用法
// 14#define 和 typedef
// 预处理指令#define09'58"
// 没有值的宏定义15'34"
// 宏定义和 const 常量区别04'57"
// typedef的用法15'45"
// #define和typedef的比较06'27"
// 宏函数的使用
// 15变量的作用域和生命周期
// 变量存储类型-auto04'03"
// 变量存储类型-register07'03"
// 变量存储类型-static05'40"
// 变量存储类型-extern
// 16字符串处理函数
// 字符串函数上11'28"
// 字符串函数下08'48"
// strncpy函数09'12"
// strncat函数04'38"
// strncmp函数04'46"
// strchr函数04'09"
// strstr函数
return 0;
}
#include <stdio.h>
#include <stdlib.h>
// 转置 动态分配内存、k大算法->脱胎与快排法
// 转置矩阵函数
void zhuanzhi()
{
// 定义并初始化一个3x3的矩阵
int a[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
// 输出原始矩阵
printf("原始矩阵:\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
// 转置矩阵
printf("现在开始转置:\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < i; j++)
{
int temp = a[i][j];
a[i][j] = a[j][i];
a[j][i] = temp;
}
}
// 输出转置后的矩阵
printf("转置之后:\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
}
// 动态分配一维数组函数
int *alloc_1dArr()
{
// 分配100个整数的内存空间
int *arr = (int *)malloc(100 * sizeof(int));
// 初始化数组元素
for (int i = 0; i < 100; i++)
{
arr[i] = i;
}
return arr;
}
// 动态分配二维数组函数
int **alloc_2dArr(int row, int col)
{
// 分配row个指针的内存空间,用于存储每一行的地址
int **a1 = (int **)malloc(row * sizeof(int *));
// 为每一行分配col个整数的内存空间,并初始化元素
for (int i = 0; i < row; i++)
{
a1[i] = (int *)malloc(col * sizeof(int));
for (int j = 0; j < col; j++)
{
a1[i][j] = i * col + j;
}
}
// 输出二维数组
printf("动态分配的二维数组:\n");
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%4d ", a1[i][j]);
}
printf("\n");
}
return a1;
}
// 交换两个整数的函数
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
// 分区函数(移除冗余的k参数)
int partition(int a[], int left, int right)
{
int pivot = a[right]; // 选择最右元素作为枢轴
int i = left - 1; // 标记大于枢轴区域的末尾
for (int j = left; j <= right - 1; j++)
{
if (a[j] > pivot)
{ // 降序分区:左侧元素 > 枢轴
i++; // 扩大左侧区域
swap(&a[i], &a[j]); // 交换到正确位置
}
}
swap(&a[i + 1], &a[right]); // 枢轴归位
return i + 1; // 返回枢轴索引(第rank大元素的位置)
}
// 查找第k大元素函数
int findKthlarge(int a[], int left, int right, int k)
{
if (left == right)
return a[left];
int pivot = partition(a, left, right);
int rank = pivot - left + 1; // 计算枢轴排名
if (rank == k)
{ // 找到第k大
return a[pivot];
}
else if (rank > k)
{ // 左半部分查找
return findKthlarge(a, left, pivot - 1, k);
}
else
{ // 右半部分查找,调整k
return findKthlarge(a, pivot + 1, right, k - rank);
}
}
int main(void)
{
// 标记当前是实验1
printf("实验1:矩阵转置\n");
zhuanzhi();
// 标记当前是实验2
printf("\n实验2:动态分配一维数组\n");
int *a1 = alloc_1dArr();
for (int i = 0; i < 100; i++)
{
printf("%d ", a1[i]);
}
printf("\n");
// 标记当前是实验3
printf("\n实验3:动态分配二维数组\n");
int m = 10, n = 15;
int **a2 = alloc_2dArr(m, n);
// 标记当前是实验4
printf("\n实验4:找到第k大的数字\n");
int arr4[] = {34, 78, 12, 56, 89, 23, 45, 67, 90, 11};
int len4 = sizeof(arr4) / sizeof(arr4[0]);
printf("这里的arr4的长度是:%d\n", len4);
int k = 8;
int res4 = findKthlarge(arr4, 0, len4 - 1, k);
printf("数组arr4的元素:\n");
for (int i = 0; i < len4; i++)
{
printf("%d ", arr4[i]);
}
printf("\n第k大(第4大)的数字就是:%d\n\n", res4);
// 实验5:指针遍历二维数组
// 这里可以添加一个新的函数来演示指针遍历二维数组
// 实验6
// 实验7
return 0;
}
// #self init statement
// 这里说明清楚:原来的代码有五百行
// 自己根据AI和华清园圈的课程写的现在底薪的一部分是AI重新给我优化生成的但是
// 之前写的我也不会给他删掉做一个备份?
// #include <stdio.h>
// #include <string.h>
// #include <stdlib.h>
// #include <time.h>
// // 指针访问数组中的元素
// void arr1_ptr()
// {
// int arr[] = {1, 2, 4, 5, 6, 56, 67, 8, 9};
// int *ptr = arr;
// int len = sizeof(arr) / sizeof(arr[0]);
// for (int i = 0; i < len; i++)
// {
// printf("指针访问:%d,数组访问:%d\n ", *(ptr + i), arr[i]);
// }
// }
// // 转置操作
// // #self 数组没办法获取数组大小 :sizeof(arr)这个会失效!!!
// void zhuanzhi(int arr[3][3])
// {
// for (int i = 0; i < 3; i++)
// {
// for (int j = i + 1; j < 3; j++)
// {
// int temp = arr[i][j];
// arr[i][j] = arr[j][i];
// arr[j][i] = temp;
// }
// }
// }
// static void dy_alloc_mem()
// {
// int *arr1 = (int *)malloc(1000000 * sizeof(int));
// if (arr1 == NULL)
// {
// printf("内存分配失败!\n");
// return;
// }
// for (int i = 0; i < 1000000; i++)
// {
// *(arr1 + i) = i;
// }
// // 时间戳1
// clock_t start, end;
// start = clock();
// int i;
// for (i = 0; i < 100; i++)
// {
// printf("%d \n", *(arr1 + i));
// }
// end = clock();
// double t = (double)(end - start);
// printf("打印这些%d用了%10f毫秒\n", i, t);
// //
// // 时间戳2
// }
// void string_reverse(char *str, int len)
// {
// char *start = str;
// char *end = start + len - 1;
// while (start < end)
// {
// char temp = *start;
// *start = *end;
// *end = temp;
// start++;
// end--;
// }
// }
// int add(int a, int b)
// {
// return a + b;
// }
// int substract(int a, int b)
// {
// return a - b;
// }
// struct Person
// {
// char name[10];
// int age;
// };
// struct Node
// {
// int data;
// struct Node *next;
// };
// struct Node *insert_atFirst(struct Node *head, int data)
// {
// // self
// // vip!!!
// // 在末尾插入!
// struct Node *n_node = (struct Node *)malloc(sizeof(struct Node));
// n_node->data = data;
// n_node->next = head;
// return n_node;
// }
// void print_List(struct Node *node)
// {
// struct Node *l = node;
// while (l != NULL)
// {
// printf("这是打印的节点:!!! %d \n", l->data);
// l = l->next;
// }
// return;
// }
// void swap(int *a, int *b)
// {
// int temp = *a;
// *a = *b;
// *b = temp;
// }
// int cnt11 = 0;
// int partition(int arr[], int low, int high)
// {
// int pi = arr[high];
// int i = low - 1;
// for (int j = low; j <= high - 1; j++)
// {
// if (arr[j] <= pi)
// {
// i++;
// swap(&arr[i], &arr[j]);
// }
// }
// swap(&arr[i + 1], &arr[high]);
// printf("\n\n---现在是partition%d次调用\n,pi是%d\n", cnt11++, pi);
// for (int i = 0; i < high; i++)
// {
// printf(" %d-", arr[i]);
// }
// printf("---现在是partition%d次调用\n\n", cnt11++);
// return i + 1;
// }
// void quick_Sort(int arr[], int low, int high)
// {
// if (low < high)
// {
// int pi = partition(arr, low, high);
// quick_Sort(arr, low, pi - 1);
// quick_Sort(arr, pi + 1, high);
// }
// }
// struct Address{
// char street[50];
// int house_number;
// };
// struct Person_16{
// char name[20];
// struct Address address;
// };
// void Print_address(struct Person_16* p){
// printf("%s \n",p->name);
// printf("%s \n",p->address.street);
// printf("%d \n",p->address.house_number);
// }
// //#self vip !!!!
// //不自己写 根本就不知道有这么多不懂的内容!
// void connect_char(char* a,char* b){
// char* p1 = a + strlen(a);
// printf("这是实验18里面的callback,a的长度是:%d ,b的长度是:%d\n",strlen(a),strlen(b));
// // for(int i=0;i<strlen(b);i++){
// // a[i] = b[i];
// // }
// while(*b!='\0'){
// *p1 = *b;
// p1++;b++;
// }
// *p1 = '\0';
// }
// #define maxL 100
// struct stack{
// int data[maxL] ;
// int top ;
// };
// void initStack(struct stack* s ){
// s->top=-1;
// }
// int isEmpty(struct stack* s ){
// if(s->top==-1){
// return 1;
// }
// else{
// return -100;
// }
// }
// int isFull(struct stack* s ){
// if(s->top==maxL){
// return 1;
// }
// else{
// return 0;
// }
// }
// void pushStack(struct stack* s ,int data ){
// //判断栈是不是满了?
// if(s->top==maxL){
// return ;
// }
// else{
// s->data[++(s->top)] = data;
// }
// }
// int popStack(struct stack* s ){
// //判断是不是空?空了就返回-1
// if(isEmpty(s)){
// return -1;
// }
// else{
// int res = s->data[s->top--];
// }
// }
// int peekTop(struct stack* s ){
// return s->data[s->top];
// }
// int main()
// {
// // 访问数组元素
// // arr1_ptr();
// // printf("1 访问数组元素结束!\n");
// // // 多维数组的内存布局
// // // 把一个二维数组转置操作一下
// // // #self 数组没办法获取数组大小 :sizeof(arr)这个会失效!!!
// // int arr[3][3] = {
// // {1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
// // zhuanzhi(arr);
// // for (int i = 0; i < 3; i++)
// // {
// // for (int j = 0; j < 3; j++)
// // {
// // printf("%d ", arr[i][j]);
// // }
// // printf("\n");
// // }
// // printf("\n\n实验转置操作\n");
// // // 动态分配内存打印
// // printf("第3实验开始!\n");
// // dy_alloc_mem();
// // printf("第3实验结束!\n");
// // printf("\n\n第四个实验开始\n\n");
// // char charS[100] = {'0', '1', '2', '3', '4', '5', '\0'};
// // int char_len = strlen(charS);
// // printf("这个字符数组的长度:%d \n", char_len);
// // printf("但是强行从0-100打印就是:\n");
// // for (int i = 0; i < 5; i++)
// // {
// // printf("%c \n", charS[i]);
// // }
// // string_reverse(charS, char_len);
// // for (int i = 0; i < char_len; i++)
// // {
// // printf("%c \n", charS[i]);
// // }
// // printf("第4个实验结束\n\n");
// // // 第5个实验:函数指针
// // // 手动实现一个计算器
// // printf("\n第5个实验开始\n\n");
// // int a = 100, b = 50;
// // int (*func_ptr)(int a, int b);
// // printf("告诉我你要的操作:\n");
// // char op;
// // scanf("%c", &op);
// // switch (op)
// // {
// // case '+':
// // func_ptr = add;
// // {
// // int res = func_ptr(a, b);
// // printf("%d\n", res);
// // break;
// // }
// // case '-':
// // func_ptr = substract;
// // {
// // int res = func_ptr(a, b);
// // printf("%d\n", res);
// // break;
// // }
// // }
// // printf("\n第5个实验结束\n\n");
// // printf("\n第六个实验,二维数组动态分配\n");
// // int m =3,n =4 ;
// // int **arr_6 =(int**)malloc(m*sizeof(int*));
// // for(int i= 0;i<m;i++){
// // arr_6[i] = (int*)malloc(n*sizeof(int));
// // }
// // for(int i =0;i<m;i++){
// // for(int j =0;j<n;j++){
// // arr_6[i][j] = i*n+j;
// // printf("\n%d = %d 行 %d列",arr_6[i][j],i,j);
// // }
// // printf("\n");
// // }
// // printf("\n第六个实验,二维数组动态分配\n");
// printf("实验7 指针数组用法\n");
// char *string[] = {"jibamao", "abc", "123", "why", "你是神秘嘉宾", "\0"};
// int len7 = sizeof(string) / sizeof(string[0]);
// for (int i = 0; i < len7; i++)
// {
// printf("%s\n", string[i]);
// }
// char string7[] = "123123";
// printf("%d\n", sizeof(string7));
// for (int i = 0; i < sizeof(string7); i++)
// {
// printf("%c", string7[i]);
// }
// // #self vip
// // 特别重要 字符串数组和字符指针数组
// // 结构体指针8 实验8 结构体修改结构体内值
// //
// struct Person person = {"xiaomin", 18};
// printf("\n%s-%d\n", person.name, person.age);
// struct Person *p = &person;
// strcpy(p->name, "jibamao");
// // p->name = "jibamao";
// printf("改过之后:%s\n", p->name);
// // #self vip!
// // 结构体指针的作用
// // #self
// // 实验9 链表节点的插入
// printf("\n\n实验9 链表的插入\n");
// struct Node *head_node = NULL;
// // struct Node node = {123,NULL};
// head_node = insert_atFirst(head_node, 123);
// printf("\n第一个节点:%d\n ", head_node->data);
// head_node = insert_atFirst(head_node, 4);
// printf("\n第二个节点 : %d\n", head_node->data);
// print_List(head_node);
// // #self vip!!!
// // 实验10 冒泡排序优化
// printf("实验10 冒泡排序优化!\n");
// // 11 快排法
// // #self vip!!!
// printf("\n\n实验11 快速排序!!开始!!!\n");
// int arr11[] = {4, 5, 6, 7, 15, 234, 46, 698, 238, 258, 45, 2, 36, 26, 123, 77, 5, 48, 45, 2, 5, 325, 32, 1, 6};
// int len = sizeof(arr11) / sizeof(arr11[0]);
// quick_Sort(arr11, 0, len - 1);
// for (int i = 0; i < len; i++)
// {
// printf("快排打印的结果是");
// printf("%d\n", arr11[i]);
// }
// printf("\n\n实验11 快排结束!!!\n");
// // 13实验 文件读写操作
// //
// // 14整数阶乘
// // 15指针数组与二维数组区别:
// // #self vip
// printf("\n\n实验15开始 \n\n");
// char *str15[] = {"ab", "123", "efg", "jkl"};
// char str15new[3][10] = {"baname", "apple", "milk"};
// int len1 = sizeof(strlen(str15))/sizeof(str15[0]);
// for (int i = 0; i <len1; i++)
// {
// printf("%s \n", str15[i]);
// }
// printf("\n\n");
// for (int i = 0; i < strlen(str15new); i++)
// {
// printf("%s \n", str15new[i]);
// }
// //实验16 结构体嵌套
// printf("\n\n实验16开始 \n\n");
// struct Person_16 p16 = {"xiaom",{"123abc",100}};
// Print_address(&p16);
// printf("\n\n实验16结束 \n\n");
// //实验17 位运算实现假发
// //shyan 18 指针实现字符串拼接
// //告诉两个指针的地址,直接把连个地址相加即可
// printf("\n\n实验18开始 ---------\n\n");
// char char1 []= {'1','2','3','4','5','a','\0'};
// char char2 []= {'b','9','\0'};
// connect_char(char1,char2);
// int len18 = strlen(char1);
// printf("\n18实验的长度是:%d \n\n",len18);
// for(int i =0;i<len18;i++){
// printf("%c \n",char1[i]);
// }
// printf("\n\n实验18结束 ---------------\n\n");
// //实验19
// //later
// //检测是否链表有环形结构
// //实验20
// //自己实现一个栈结构
// //#self vip!!!
// //栈要实现哪些功能?
// //1 初始化 2入栈 3出栈 4查看栈顶元素 5空了? 6满了?
// struct stack s;
// initStack(&s);
// printf("初始了,是不是空?%d\n",isEmpty(&s));
// pushStack(&s,1);
// printf("插入了1,是不是空?\n",isEmpty(&s));
// pushStack(&s,2);
// pushStack(&s,3);
// printf("栈顶是:%d\n",peekTop(&s));
// pushStack(&s,4);
// printf("栈顶是:%d\n",peekTop(&s));
// popStack(&s);
// printf("栈顶是:%d\n",peekTop(&s));
// printf("栈顶是:%d\n",peekTop(&s));
// return 0;
// }
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
// 指针数组、结构体指针、链表插入、快排、栈
int cnt11 = 0;
// ====================== 提前结构体定义 ======================
struct Person
{
char name[10];
int age;
};
struct Node
{
int data;
struct Node *next;
};
#define maxL 100
struct stack
{
int data[maxL];
int top;
};
// ====================== 函数前置声明 ======================
void arr1_ptr();
void zhuanzhi(int arr[3][3]);
static void dy_alloc_mem();
void string_reverse(char *str, int len);
int add(int a, int b);
int substract(int a, int b);
struct Node *insert_atFirst(struct Node *head, int data);
void print_List(struct Node *node);
void swap(int *a, int *b);
int partition(int arr[], int low, int high);
void quick_Sort(int arr[], int low, int high);
void connect_char(char *a, char *b);
void initStack(struct stack *s);
int isEmpty(struct stack *s);
int isFull(struct stack *s);
void pushStack(struct stack *s, int data);
int popStack(struct stack *s);
int peekTop(struct stack *s);
// ====================== 原始函数实现 ======================
// 指针访问数组中的元素
void arr1_ptr()
{
int arr[] = {1, 2, 4, 5, 6, 56, 67, 8, 9};
int *ptr = arr;
int len = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < len; i++)
{
printf("指针访问:%d,数组访问:%d\n ", *(ptr + i), arr[i]);
}
}
// 转置操作
void zhuanzhi(int arr[3][3])
{
for (int i = 0; i < 3; i++)
{
for (int j = i + 1; j < 3; j++)
{
int temp = arr[i][j];
arr[i][j] = arr[j][i];
arr[j][i] = temp;
}
}
}
static void dy_alloc_mem()
{
int *arr1 = (int *)malloc(1000000 * sizeof(int));
if (arr1 == NULL)
{
printf("内存分配失败!\n");
return;
}
for (int i = 0; i < 1000000; i++)
{
*(arr1 + i) = i;
}
clock_t start, end;
start = clock();
int i;
for (i = 0; i < 100; i++)
{
printf("%d \n", *(arr1 + i));
}
end = clock();
double t = (double)(end - start);
printf("打印这些%d用了%10f毫秒\n", i, t);
}
void string_reverse(char *str, int len)
{
char *start = str;
char *end = start + len - 1;
while (start < end)
{
char temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
}
int add(int a, int b)
{
return a + b;
}
int substract(int a, int b)
{
return a - b;
}
struct Node *insert_atFirst(struct Node *head, int data)
{
struct Node *n_node = (struct Node *)malloc(sizeof(struct Node));
n_node->data = data;
n_node->next = head;
return n_node;
}
void print_List(struct Node *node)
{
struct Node *l = node;
while (l != NULL)
{
printf("这是打印的节点:!!! %d \n", l->data);
l = l->next;
}
}
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int partition(int arr[], int low, int high)
{
int pi = arr[high];
int i = low - 1;
for (int j = low; j <= high - 1; j++)
{
if (arr[j] <= pi)
{
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
printf("\n\n---现在是partition%d次调用\n,pi是%d\n", cnt11++, pi);
for (int i = 0; i < high; i++)
{
printf(" %d-", arr[i]);
}
printf("---现在是partition%d次调用\n\n", cnt11++);
return i + 1;
}
void quick_Sort(int arr[], int low, int high)
{
if (low < high)
{
int pi = partition(arr, low, high);
quick_Sort(arr, low, pi - 1);
quick_Sort(arr, pi + 1, high);
}
}
void connect_char(char *a, char *b)
{
char *p1 = a + strlen(a);
printf("这是实验18里面的callback,a的长度是:%d ,b的长度是:%d\n", strlen(a), strlen(b));
while (*b != '\0')
{
*p1 = *b;
p1++;
b++;
}
*p1 = '\0';
}
void initStack(struct stack *s)
{
s->top = -1;
}
int isEmpty(struct stack *s)
{
if (s->top == -1)
{
return 1;
}
else
{
return -100;
}
}
int isFull(struct stack *s)
{
if (s->top == maxL)
{ // 注意:这里可能存在越界风险,原始逻辑未修正
return 1;
}
else
{
return 0;
}
}
void pushStack(struct stack *s, int data)
{
if (s->top == maxL)
{
return;
}
else
{
s->data[++(s->top)] = data;
}
}
int popStack(struct stack *s)
{
if (isEmpty(s))
{
return -1;
}
else
{
int res = s->data[s->top--];
return res; // 原始代码遗漏return
}
}
int peekTop(struct stack *s)
{
return s->data[s->top];
}
// 指针数组、结构体指针、链表插入、快排、栈
// ====================== 主函数 ======================
int main()
{
// 实验7 指针数组用法
printf("实验7 指针数组用法\n");
char *string[] = {"qibamao", "abc", "123", "why", "你是神秘嘉宾", "\0"};
int len7 = sizeof(string) / sizeof(string[0]);
for (int i = 0; i < len7; i++)
{
printf("%s\n", string[i]);
}
char string7[] = "123123";
printf("%d\n", sizeof(string7));
for (int i = 0; i < sizeof(string7); i++)
{
printf("%c", string7[i]);
}
// 结构体指针实验
struct Person person = {"xiaomin", 18};
printf("\n%s-%d\n", person.name, person.age);
struct Person *p = &person;
strcpy(p->name, "jibamao");
printf("改过之后:%s\n", p->name);
// 链表实验
printf("\n\n实验9 链表的插入\n");
struct Node *head_node = NULL;
head_node = insert_atFirst(head_node, 123);
printf("\n第一个节点:%d\n ", head_node->data);
head_node = insert_atFirst(head_node, 4);
printf("\n第二个节点 : %d\n", head_node->data);
print_List(head_node);
// 快速排序实验
printf("\n\n实验11 快速排序!!开始!!!\n");
int arr11[] = {4, 5, 6, 7, 15, 234, 46, 698, 238, 258, 45, 2, 36, 26, 123, 77, 5, 48, 45, 2, 5, 325, 32, 1, 6};
int len = sizeof(arr11) / sizeof(arr11[0]);
quick_Sort(arr11, 0, len - 1);
for (int i = 0; i < len; i++)
{
printf("快排打印的结果是%d\n", arr11[i]);
}
// 栈结构实验
struct stack s;
initStack(&s);
printf("\n初始了,是不是空?%d\n", isEmpty(&s));
pushStack(&s, 1);
printf("插入了1,是不是空?%d\n", isEmpty(&s));
pushStack(&s, 2);
pushStack(&s, 3);
printf("栈顶是:%d\n", peekTop(&s));
pushStack(&s, 4);
printf("栈顶是:%d\n", peekTop(&s));
popStack(&s);
printf("弹出后栈顶是:%d\n", peekTop(&s));
return 0;
}
#include <stdio.h>
//数组 、二维数组 、排序算法
// 1. 一维数组初始化与遍历
void testArrayInitialization() {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5] = {1, 2, 3}; // 自动补0
int arr3[] = {1, 3, 4, 5, 6}; // 自动计算长度
printf("一维数组初始化测试:\n");
for (int i = 0; i < 5; i++) {
printf("arr1[%d]=%d, arr2[%d]=%d, arr3[%d]=%d\n",
i, arr1[i], i, arr2[i], i, arr3[i]);
}
}
// 2. 数组越界演示(危险操作,仅用于教学)
void demonstrateArrayOutOfBounds() {
int arr[6] = {1, 2, 3, 4, 5, 6};
printf("越界访问示例(危险!):\n");
for (int i = 0; i <= 8; i++) { // 注意这里会访问arr[6]导致越界
printf("arr[%d]=%d\n", i, arr[i]);
}
}
// 3. 二维数组操作
void test2DArray() {
int arr[2][3] = {{12, 3, 4}, {12, 22, 33}};
printf("\n二维数组演示:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("arr[%d][%d]=%d\t", i, j, arr[i][j]);
}
printf("\n");
}
}
// 4. 数组作为函数参数
void printArray(int arr[], int size) {
printf("\n数组作为参数传递:\n");
for (int i = 0; i < size; i++) {
printf("arr[%d]=%d\n", i, arr[i]);
}
}
// 5. 冒泡排序
void bubbleSort(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;
}
}
}
}
// 6. 选择排序
void selectionSort(int arr[], int size) {
for (int i = 0; i < size - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < size; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
// 7. 数组逆序
void reverseArray(int arr[], int size) {
printf("\n数组逆序前:\n");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
int start = 0;
int end = size - 1;
while (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
printf("\n数组逆序后:\n");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
// 测试数组初始化
testArrayInitialization();
// 测试数组越界(注意:实际编程中应避免!)
demonstrateArrayOutOfBounds();
// 测试二维数组
test2DArray();
// 测试数组作为参数
int arr[] = {1, 3, 5, 7, 9};
printArray(arr, 5);
// 测试排序算法
int sortArr[] = {5, 3, 8, 4, 2};
int size = sizeof(sortArr) / sizeof(sortArr[0]);
printf("\n选择排序前:\n");
printArray(sortArr, size);
selectionSort(sortArr, size);
printf("\n选择排序后:\n");
printArray(sortArr, size);
// 测试数组逆序
reverseArray(sortArr, size);
return 0;
}