引言:C 语言的魅力与挑战
从操作系统内核到嵌入式系统,从高性能计算到网络编程,C 语言高效、灵活和贴近硬件的特性,始终占据着不可替代的地位。然而,C 语言的强大也伴随着较高的学习曲线,尤其是指针、内存管理和复杂数据结构的操作 经常搞得人晕.....
哥们这次特地基于 700 余行实战代码,系统梳理 C 语言的核心知识点,从基础语法到高级应用,从内存模型到算法实现,帮助读者建立完整的 C 语言知识体系。
第一部分:笔者搜集的 csdn牛客里扣github博客园开源中国等头部技术论坛的大厂考点实战:
一、基础概念与内存模型
1. 指针与数组的本质区别
题目:以下关于指针与数组的说法错误的是( )
A. 数组名在多数情况下会衰退为指针
B. sizeof(arr)
返回数组总字节数,sizeof(ptr)
返回指针字节数
C. 指针可以进行算术运算,数组名不能
D. 数组元素存储在堆上,指针变量存储在栈上
解析:
答案 D。数组元素存储位置取决于定义方式:局部数组在栈上,全局 / 静态数组在数据段,动态分配的数组在堆上。指针变量本身是变量,存储位置由定义位置决定(局部指针在栈,全局指针在数据段)。
关键点:
- 数组名衰退规则:除
sizeof(arr)
和&arr
外,数组名会衰退为指向首元素的指针 - 指针算术运算本质是地址偏移,步长由指向类型决定
2. 指针大小与平台相关性
题目:在 64 位 Linux 系统中,以下指针类型的大小分别是( )
int *p1;
void **p2;
char (*p3)[10];
int (*p4)(int, int);
A. 8,8,8,8 B. 4,8,8,4 C. 8,16,8,8 D. 4,4,4,4
解析:
答案 A。在 64 位系统中,所有指针类型(包括函数指针、多级指针、数组指针)的大小均为 8 字节,与指向类型无关。
关键点:
- 指针大小仅由操作系统位数决定:32 位 4 字节,64 位 8 字节
- 函数指针、数组指针本质仍是指针,遵循相同大小规则
3. 野指针成因与危害
题目:以下代码会产生野指针的是( )
A. int *p; *p = 10;
B. int *p = (int*)malloc(sizeof(int)); free(p); p = NULL;
C. int arr[5], *p = arr; p += 5;
D. int *p = &(int){10};
解析:
答案 A、C、D。
- A:未初始化的指针直接解引用,是典型野指针
- C:指针超出数组边界,指向无效内存
- D:临时变量地址在表达式结束后失效,形成野指针
关键点:
野指针常见成因:
- 未初始化的指针
- 释放后未置 NULL 的指针
- 越界访问的指针
- 指向临时变量的指针
二、指针与数组高级操作
4. 二维数组与指针运算
题目:对于二维数组int arr[3][4]
,以下表达式值为arr[1][2]
的是( )
A. *(arr + 1 + 2)
B. *(arr [1] + 2)
C. ((arr + 2) + 1)
D. arr[1] + 2
解析:
答案 B。
arr
是指向int[4]
的指针,arr[1]
等价于*(arr+1)
,类型为int*
arr[1]+2
是指向arr[1][2]
的指针,解引用后得到值
关键点:
二维数组在内存中按行存储,arr[i][j]
等价于*(*(arr+i)+j)
5. 指针数组与数组指针辨析
题目:定义int (*p)[5]
和int *p[5]
,以下说法正确的是( )
A. 两者都是指针数组,存储 5 个 int * 指针
B. 前者是数组指针,后者是指针数组
C. 前者指针指向 5 个 int,后者数组存储 5 个指针
D. 两者没有区别
解析:
答案 B。
int (*p)[5]
:数组指针,指向包含 5 个 int 的数组int *p[5]
:指针数组,包含 5 个 int * 指针
关键点:
- 括号优先级:
*p[5]
中[]
优先级高于*
,先成数组 (int (*)[5])
中括号改变优先级,先成指针
6. 字符串指针与数组的陷阱
题目:分析以下代码的输出:
void string_trap() {
char str[] = "hello";
char *ptr = "world";
str[0] = 'H';
ptr[0] = 'W';
}
A. 编译错误
B. 运行时错误(段错误)
C. 正常运行,str 变为 "Hello",ptr 变为 "World"
D. str 变为 "Hello",ptr 指向的字符串不变
解析:
答案 B。
str
是数组,存储在栈上,可以修改ptr
指向字符串常量,存储在代码段,修改会导致段错误
关键点:
- 字符串字面量存储在只读区,不能修改
- 数组名作为左值时可修改元素
三、内存管理与指针操作
7. 动态内存分配与指针
题目:以下代码存在的问题是( )
c
运行
void memory_bug() {
int *p = (int*)malloc(10 * sizeof(int));
for (int i=0; i<10; i++) p[i] = i;
int *q = (int*)realloc(p, 20 * sizeof(int));
free(p);
}
A. 没有问题
B. realloc 后未检查返回值
C. free (p) 释放了已重新分配的内存
D. 内存泄漏
解析:
答案 B、C。
- realloc 可能失败,需检查返回值
- realloc 成功时,p 的地址可能改变,原 p 被 q 覆盖后释放 q 才正确
关键点:
realloc 使用规范:
void *new_ptr = realloc(old_ptr, new_size);
if (new_ptr) {
old_ptr = new_ptr;
}
8. 指针与结构体对齐
题目:已知结构体:
struct Data {
char c;
int i;
double d;
};
在 64 位系统中,sizeof(struct Data)
的结果是( )
A. 13 B. 16 C. 24 D. 32
解析:
答案 B。
- 64 位系统默认对齐为 8 字节
char c
占 1,补 3 到 4 字节int i
占 4,累计 8 字节double d
占 8,累计 16 字节
关键点:
结构体对齐规则:
- 每个成员按自身大小和对齐参数取最小对齐
- 整体大小为最大对齐参数的整数倍
9. 多级指针操作
题目:执行以下代码后,**pp
的值是( )
int a = 10, b = 20;
int *p = &a, **pp = &p;
p = &b;
A. 10 B. 20 C. 编译错误 D. 运行时错误
解析:
答案 B。
pp
是指向p
的指针,p
先指向a
,后指向b
**pp
等价于*p
,即b
的值
关键点:
多级指针本质是指针的指针,解引用次数等于级别数
四、函数指针与回调
10. 函数指针数组实现计算器
题目:用函数指针数组实现四则运算计算器,要求支持+
、-
、*
、/
,并处理除零错误。
解析:
#include <stdio.h>
#include <stdlib.h>
// 函数指针类型
typedef int (*OpFunc)(int, int);
// 四则运算函数
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) {
if (b == 0) {
printf("错误:除数不能为零\n");
exit(1);
}
return a / b;
}
int main() {
// 函数指针数组
OpFunc ops[4] = {add, subtract, multiply, divide};
char op;
int a, b;
printf("输入运算(+ - * /): ");
scanf(" %c", &op);
printf("输入两个操作数: ");
scanf("%d %d", &a, &b);
// 根据操作符选择函数
int idx = -1;
switch (op) {
case '+': idx = 0; break;
case '-': idx = 1; break;
case '*': idx = 2; break;
case '/': idx = 3; break;
default: printf("不支持的操作符\n"); return 1;
}
printf("结果: %d\n", ops[idx](a, b));
return 0;
}
关键点:
- 函数指针数组实现多态操作
- 类型安全检查与错误处理
五、算法与数据结构中的指针应用
11. 链表逆序(指针操作)
题目:用指针操作实现单链表的逆序,要求时间复杂度 O (n),空间复杂度 O (1)。
解析:
struct Node {
int data;
struct Node *next;
};
struct Node* reverseList(struct Node* head) {
struct Node *prev = NULL;
struct Node *current = head;
struct Node *next = NULL;
while (current != NULL) {
next = current->next; // 保存下一个节点
current->next = prev; // 反转指针
prev = current; // 移动prev
current = next; // 移动current
}
return prev; // 新的头节点
}
关键点:
三指针法:prev
、current
、next
配合实现指针反转
12. 快速排序中的指针应用
题目:用指针操作实现快速排序,要求使用void*
指针实现通用排序。
解析:
#include <stdio.h>
#include <stdlib.h>
// 交换函数
void swap(void *a, void *b, size_t size) {
char temp[size];
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
}
// 分区函数
int partition(void *base, int low, int high, size_t size, int (*cmp)(const void*, const void*)) {
void *pivot = (char*)base + high * size;
int i = low - 1;
for (int j = low; j < high; j++) {
void *elem = (char*)base + j * size;
if (cmp(elem, pivot) <= 0) {
i++;
swap((char*)base + i * size, elem, size);
}
}
swap((char*)base + (i+1) * size, pivot, size);
return i + 1;
}
// 快速排序
void quickSort(void *base, int n, size_t size, int (*cmp)(const void*, const void*)) {
if (n > 1) {
int pi = partition(base, 0, n-1, size, cmp);
quickSort(base, pi, size, cmp);
quickSort((char*)base + (pi+1)*size, n - pi - 1, size, cmp);
}
}
// 测试
int intCmp(const void *a, const void *b) {
return *(int*)a - *(int*)b;
}
int main() {
int arr[] = {3, 1, 4, 1, 5, 9, 2};
int n = sizeof(arr)/sizeof(arr[0]);
quickSort(arr, n, sizeof(int), intCmp);
for (int i=0; i<n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
关键点:
void*
指针实现通用排序- 内存操作函数
memcpy
实现任意类型交换
六、综合应用与陷阱
13. 指针与数组传参陷阱
题目:以下函数中sizeof(arr)
的结果是( )
void func(int arr[10]) {
printf("%zu\n", sizeof(arr));
}
int main() {
int arr[5];
func(arr);
return 0;
}
A. 20 B. 8 C. 40 D. 编译错误
解析:
答案 B。数组作为函数参数时衰退为指针,sizeof(arr)
返回指针大小(64 位系统 8 字节)
关键点:
数组传参本质是传递指针,无法在函数内获取原始数组大小
14. 指针运算与类型转换
题目:计算以下表达式的值(64 位系统):
char *p = "hello";
int *q = (int*)p;
q += 1;
q - p
的值是( )
A. 1 B. 4 C. 8 D. 编译错误
解析:
答案 B。int*
指针 + 1 偏移 4 字节(int 大小),char*
指针偏移 1 字节,差值为 4
关键点:
指针算术运算的步长由指向类型决定
15. 内存泄漏检测
题目:找出以下代码中的内存泄漏:
void leak_demo() {
int *p1 = (int*)malloc(10*sizeof(int));
int *p2 = (int*)realloc(p1, 20*sizeof(int));
if (p2 == NULL) return;
free(p1);
}
解析:
realloc
成功时,p1
指向的内存已被重新分配,原p1
地址可能改变,直接free(p1)
会释放新分配的内存,导致原内存泄漏。应改为:
int *p2 = realloc(p1, 20*sizeof(int));
if (p2) {
p1 = p2; // 更新指针
}
七、编程题(大厂算法题)
16. 指针实现字符串拷贝(模拟 strcpy)
题目:用指针操作实现strcpy
函数,要求处理边界情况。
解析:
char* my_strcpy(char* dest, const char* src) {
char* res = dest;
if (dest == NULL || src == NULL) return NULL;
while (*src) {
*dest++ = *src++;
}
*dest = '\0';
return res;
}
17. 指针实现链表环检测(Floyd 判圈算法)
题目:用指针操作实现链表环检测,要求时间复杂度 O (n),空间复杂度 O (1)。
解析:
int hasCycle(struct Node *head) {
if (head == NULL || head->next == NULL) return 0;
struct Node *slow = head;
struct Node *fast = head->next;
while (slow != fast) {
if (fast == NULL || fast->next == NULL) return 0;
slow = slow->next;
fast = fast->next->next;
}
return 1;
}
18. 指针与内存池设计
题目:设计一个简单内存池,要求:
- 预分配一块大内存
- 实现内存分配与释放
- 避免碎片
解析:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define POOL_SIZE 1024*1024 // 1MB内存池
typedef struct {
char* memory; // 内存池起始地址
char* current; // 当前分配位置
int size; // 内存池大小
} MemoryPool;
// 初始化内存池
MemoryPool* initMemoryPool(int size) {
MemoryPool* pool = (MemoryPool*)malloc(sizeof(MemoryPool));
if (pool == NULL) return NULL;
pool->memory = (char*)malloc(size);
if (pool->memory == NULL) {
free(pool);
return NULL;
}
pool->current = pool->memory;
pool->size = size;
return pool;
}
// 从内存池分配内存
void* allocFromPool(MemoryPool* pool, int size) {
if (pool == NULL || size <= 0) return NULL;
if (pool->current + size > pool->memory + pool->size) {
printf("内存池不足\n");
return NULL;
}
void* res = pool->current;
pool->current += size;
return res;
}
// 释放内存池
void freeMemoryPool(MemoryPool* pool) {
if (pool == NULL) return;
free(pool->memory);
free(pool);
}
八、系统级指针操作
19. 指针与类型转换(内存复用)
题目:用指针操作实现 int 与 float 的内存互转,要求不使用联合。
解析:
20. 指针与位操作(内存映射)
题目:用指针操作实现将整数的第 3 位和第 7 位取反。
解析:
int flipBits(int num) {
// 第3位和第7位掩码
int mask = (1 << 3) | (1 << 7);
return num ^ mask;
}
// 指针版本
void flipBitsPtr(int *num) {
int mask = (1 << 3) | (1 << 7);
*num ^= mask;
}
面试题解析方法论
大厂指针题核心考点归纳:
- 指针本质:地址操作、类型系统、衰退规则
- 内存模型:栈堆数据段、对齐规则、生命周期
- 算法应用:链表 / 数组操作、排序 / 搜索中的指针技巧
- 系统编程:内存管理、函数指针、类型转换
- 安全问题:野指针、内存泄漏、越界访问
解题思路:
- 画图分析:指针操作时画出内存布局
- 类型推导:从定义推导指针类型(如
int (*)[5]
是数组指针) - 边界测试:空指针、越界、类型转换等边界情况
- 内存跟踪:动态分配时跟踪指针变化
这些题目覆盖了腾讯、阿里等大厂面试中指针相关的核心考点,从基础概念到系统级编程,结合算法与数据结构,适合进阶学习和面试准备。建议在理解原理的基础上,动手实现并调试代码,加深对指针本质的理解。
第二部分:相关知识点详解
一、C 语言基础数据结构与内存模型
1.1 结构体:数据组织的基石
结构体是 C 语言中组织复杂数据的核心机制,它允许我们将不同类型的数据组合成一个有机整体。在实际项目中,结构体的设计直接影响程序的效率和可维护性。
// 多用途节点结构体,适用于链表和树结构
struct Node {
int value; // 节点存储的值
struct Node *next; // 链表中的下一个节点
struct Node **children; // 树结构中的子节点数组
int child_count; // 子节点数量
};
// 人员信息结构体
struct Person {
char name[10]; // 姓名
int age; // 年龄
};
// 栈数据结构实现
struct stack {
int data[100]; // 栈存储数组
int top; // 栈顶指针
};
上述代码定义了三种常用结构体:多用途节点结构体Node
、人员信息结构体Person
和栈结构体stack
。其中Node
结构体的设计体现了 C 语言的灵活性 —— 通过next
指针可以构建链表,通过children
指针数组可以构建树结构,这种 "一结构多用途" 的设计在实际开发中非常常见。
结构体使用实战:栈的实现与应用
栈是计算机科学中最基础的数据结构之一,下面我们通过stack
结构体实现一个完整的栈,并演示其基本操作:
#define maxL 99 // 栈的最大容量
// 初始化栈
void initStack(struct stack *s) {
s->top = -1;
}
// 判断栈是否为空
int isEmpty(struct stack *s) {
return s->top == -1 ? 1 : -100;
}
// 判断栈是否已满
int isFull(struct stack *s) {
return s->top == maxL ? 1 : 0;
}
// 入栈操作
void pushStack(struct stack *s, int value) {
if (!isFull(s)) {
s->data[++(s->top)] = value;
}
}
// 出栈操作
int popStack(struct stack *s) {
if (!isEmpty(s)) {
return s->data[(s->top)--];
}
return -1;
}
// 获取栈顶元素
int peekTop(struct stack *s) {
return s->data[s->top];
}
// 栈操作测试
void stackTest() {
struct stack s;
initStack(&s);
printf("初始化后,栈是否为空: %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));
}
这段代码完整实现了栈的基本操作:初始化、判空、判满、入栈、出栈和获取栈顶元素。在stackTest
函数中,我们演示了栈的基本使用流程。需要注意的是,这里使用数组实现栈,属于顺序栈,其优点是访问效率高,缺点是容量固定。在实际项目中,当需要动态调整栈大小时,可以考虑使用链表实现栈结构。
1.2 指针:C 语言的灵魂
指针是 C 语言的核心特性,也是让许多初学者望而生畏的难点。理解指针,本质上是理解计算机内存的工作原理。
// 野指针成因演示
void wild_pointer_cause() {
int a = 10;
int *new_ptr = &a; // 合法指针,指向变量a
int *heap_ptr = (int *)malloc(sizeof(int)); // 在堆上分配内存
free(heap_ptr); // 释放内存,但指针未置为NULL
// heap_ptr现在成为野指针
int arr[5] = {1, 2, 3, 4, 5};
int *arr_ptr = arr;
arr_ptr = arr_ptr + 10; // 指针越界,成为野指针
}
// 指针关系运算演示
void pointer_relation() {
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = arr + 1; // 指向arr[1]
int *p2 = arr + 3; // 指向arr[3]
printf("p1 < p2: %s\n", (p1 < p2) ? "true" : "false"); // 指针地址比较
printf("p1 == arr+1: %s\n", (p1 == arr + 1) ? "true" : "false"); // 指针相等比较
}
// 指针算术运算演示
void pointer_arithmetic_application() {
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, *(p + i)); // 通过指针算术访问数组元素
}
}
指针与数组的爱恨情仇
在 C 语言中,指针与数组的关系极为密切,但又有着本质区别。许多初学者容易混淆两者,导致程序出现难以试的错误。
// 数组与指针区别演示
void array_pointer_question() {
int arr[5] = {1, 2, 3, 4, 5};
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 输出数组总字节数:5*4=20
printf("sizeof(arr+0) = %zu\n", sizeof(arr + 0)); // 输出指针字节数:8(64位系统)
}
// 数组指针应用:二维数组操作
void array_ptr_application() {
int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int (*p)[3] = arr; // 定义指向包含3个int的数组的指针
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
// 两种等价的访问方式
printf("1 the num: %d-%d:%d\n", i, j, *(p + i)[j]);
printf("2 the num: %d-%d:%d\n", i, j, p[i][j]);
}
}
}
1.3 C 语言内存模型:从栈到堆的全景图
理解 C 语言的内存分布是写出高效、稳定程序的关键。C 程序的内存通常分为以下几个区域:
// 内存分布演示
void memory_distribution_demo() {
// 栈区:存储局部变量
int localVar = 10;
char str[20] = "hello";
// 堆区:动态分配的内存
int *heapPtr = (int *)malloc(sizeof(int));
if (heapPtr == NULL) {
printf("内存分配失败\n");
return;
}
*heapPtr = 20;
// 数据段:存储全局变量和静态变量
static int staticVar = 30;
// 代码段:存储可执行代码和字符串常量
const char *strConst = "world";
// 释放堆内存
free(heapPtr);
}
二、字符串处理:从基础函数到自定义实现
字符串是 C 语言中最常用的数据类型之一,熟练掌握字符串处理是 C 程序员的基本素养。标准库提供了丰富的字符串函数,但了解其实现原理能帮助我们更好地使用它们。
2.1 字符串处理函数的自定义实现
c
// 自定义strstr函数:查找子字符串
char *my_strstr(const char *haystack, const char *needle) {
assert(haystack != NULL && needle != NULL);
// 空字符串特殊处理
if (*needle == '\0') {
return NULL;
}
while (*haystack) {
const char *h = haystack;
const char *n = needle;
// 逐个字符比较
while (*h && *n && *h == *n) {
h++;
n++;
}
// 找到匹配的子字符串
if (*n == '\0') {
return (char *)haystack;
}
haystack++;
}
return NULL;
}
// 自定义strncpy函数:复制指定长度的字符串
char *my_strncpy(char *dest, const char *src, size_t n) {
assert(dest != NULL && src != NULL);
size_t i = 0;
char *tempPtr = dest;
// 复制src中的字符,直到n个或遇到'\0'
while (i < n && src[i]) {
dest[i] = src[i];
i++;
}
// 填充剩余位置为'\0'
while (i < n) {
dest[i] = '\0';
i++;
}
return tempPtr;
}
// 自定义strncat函数:连接指定长度的字符串
char *my_strncat(char *dest, const char *src, size_t n) {
assert(dest != NULL && src != NULL);
size_t len = strlen(dest);
char *res = dest;
size_t i;
// 连接src中的字符,直到n个或遇到'\0'
for (i = 0; i < n && src[i] != '\0'; i++) {
dest[len + i] = src[i];
}
dest[i + len] = '\0';
return res;
}
// 自定义strncmp函数:比较指定长度的字符串
int my_strncmp(const char *s1, const char *s2, size_t n) {
assert(s1 != NULL && s2 != NULL);
for (size_t i = 0; i < n; i++) {
char c1 = s1[i] ? s1[i] : '\0';
char c2 = s2[i] ? s2[i] : '\0';
if (c1 != c2) {
return c1 - c2;
}
}
return 0;
}
// 自定义strchr函数:查找字符
char *my_strchr(const char *str, int c) {
assert(str != NULL);
const char *p = str;
while (*p) {
if (*p == (char)c) {
return (char *)p;
}
p++;
}
// 处理查找'\0'的情况
return (char)c == '\0' ? (char *)p : NULL;
}
2.2 字符串处理实战:从复制到拼接
c
// 字符串复制函数
char *str_copy(const char *src) {
// 分配内存,+1用于存储'\0'
char *dest = (char *)malloc(strlen(src) + 1);
assert(dest != NULL);
char *p = dest;
while (*src) {
*p++ = *src++;
}
*p = '\0';
return dest;
}
// 字符串拼接演示
void char_ptr_application() {
char str1[20] = "Hello";
char *str2 = ", World!";
char *p1 = str1 + strlen(str1);
char *p2 = str2;
// 手动拼接字符串
while (*p2) {
*p1++ = *p2++;
}
*p1 = '\0';
printf("拼接后的字符串: %s\n", str1);
}
// 字符串处理函数测试
void string_functions_test() {
printf("字符串复制测试:\n");
char *copy = str_copy("test");
printf("复制后的字符串: %s\n", copy);
free(copy);
printf("\nstrncpy测试:\n");
char dest[10] = {0};
my_strncpy(dest, "hello", 3);
printf("复制结果: %s\n", dest);
printf("\nstrncat测试:\n");
char dest_cat[10] = {'1', '2', '\0'};
char src_cat[] = {'3', '4', 'a', '\0'};
my_strncat(dest_cat, src_cat, 3);
printf("拼接结果: %s\n", dest_cat);
printf("\nstrncmp测试:\n");
char s1[] = {'1', '2', '3', '5'};
char s2[] = {'1', '2', '3'};
printf("比较结果: %d\n", my_strncmp(s1, s2, 4));
printf("\nstrchr测试:\n");
char test[] = {'1', '2', 'a', 'b', 'c', 'f', 'l', '6', '\0'};
char *res = my_strchr(test, 'a');
if (res) {
printf("找到字符 'a',位置: %ld\n", res - test);
} else {
printf("未找到字符\n");
}
printf("\nstrstr测试:\n");
char haystack[] = {'1', 'a', '2', 'b', 'c', '\0'};
char needle[] = {'b', 'c', '\0'};
char *strstr_res = my_strstr(haystack, needle);
if (strstr_res) {
printf("找到子字符串,位置: %ld\n", strstr_res - haystack);
} else {
printf("未找到子字符串\n");
}
}
三、算法与数据结构:从排序到递归
3.1 排序算法:快速排序的实现与优化
快速排序是实践中常用的高效排序算法,其平均时间复杂度为 O (n log n)。
c
// 交换函数
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]);
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 quick_sort_test() {
int arr[] = {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(arr) / sizeof(arr[0]);
printf("排序前数组:\n");
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
quick_Sort(arr, 0, len - 1);
printf("排序后数组:\n");
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
3.2 递归算法:斐波那契数列的实现
递归是一种强大的算法思想,常用于解决可以分解为相似子问题的场景。
c
// 斐波那契数列递归实现
int fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 斐波那契数列测试
void fibonacci_test() {
printf("fib(5) = %d\n", fibonacci(5)); // 输出5
printf("fib(10) = %d\n", fibonacci(10)); // 输出55
}
// 递归优化思路:记忆化搜索
int fibonacci_memo(int n, int *memo) {
if (n <= 1) {
return n;
}
// 如果已经计算过,直接返回结果
if (memo[n] != -1) {
return memo[n];
}
// 计算并存储结果
memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo);
return memo[n];
}
// 记忆化搜索测试
void fibonacci_memo_test() {
int n = 20;
int *memo = (int *)calloc(n + 1, sizeof(int));
for (int i = 0; i <= n; i++) {
memo[i] = -1;
}
printf("fib_memo(20) = %d\n", fibonacci_memo(20, memo));
free(memo);
}
四、高级主题:从函数指针到内存管理
4.1 函数指针:C 语言的回调机制
函数指针是 C 语言中实现回调机制的基础,也是许多高级特性的基石。
c
// 加法函数
int add(int a, int b) {
return a + b;
}
// 减法函数
int subtract(int a, int b) {
return a - b;
}
// 函数指针数组演示
void func_ptr_array_demo() {
// 定义函数指针类型
typedef int (*OpFunc)(int, int);
// 创建函数指针数组
OpFunc ops[] = {add, subtract};
printf("5+3=%d\n", ops[0](5, 3));
printf("5-3=%d\n", ops[1](5, 3));
}
// 回调函数示例:排序比较函数
int compare_asc(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
// qsort函数使用演示
void qsort_demo() {
int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 使用qsort排序,传入比较函数
qsort(arr, n, sizeof(int), compare_asc);
printf("排序后:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
4.2 内存管理:从 malloc 到野指针防护
c
// 动态内存分配演示
int *dy_alloc(int n) {
int *res = (int *)malloc(n * sizeof(int));
if (res == NULL) {
printf("内存分配失败\n");
exit(1);
}
// 初始化分配的内存
for (int i = 0; i < n; i++) {
res[i] = i;
}
return res;
}
// 内存管理最佳实践
void memory_management_best_practices() {
// 分配内存
int *ptr1 = (int *)malloc(10 * sizeof(int));
if (ptr1 == NULL) {
printf("内存分配失败\n");
return;
}
// 使用内存
for (int i = 0; i < 10; i++) {
ptr1[i] = i;
}
// 重新分配内存
int *ptr2 = (int *)realloc(ptr1, 20 * sizeof(int));
if (ptr2 == NULL) {
free(ptr1);
printf("内存重新分配失败\n");
return;
}
ptr1 = ptr2; // 更新指针
// 继续使用内存
for (int i = 10; i < 20; i++) {
ptr1[i] = i;
}
// 释放内存
free(ptr1);
ptr1 = NULL; // 置为NULL,避免野指针
}
// 野指针防护策略
void wild_pointer_prevention() {
int *ptr = NULL; // 初始化为NULL
// 分配内存
ptr = (int *)malloc(sizeof(int));
if (ptr == NULL) {
printf("内存分配失败\n");
return;
}
// 使用内存
*ptr = 10;
// 释放内存
free(ptr);
ptr = NULL; // 释放后立即置为NULL
// 安全检查
if (ptr != NULL) {
// 不会执行到这里
}
}
五、综合实战:从链表到矩阵操作
5.1 链表操作:从创建到遍历
c
// 在链表头部插入节点
struct Node *insert_atFirst(struct Node *head, int data) {
struct Node *n_node = (struct Node *)malloc(sizeof(struct Node));
if (n_node == NULL) {
printf("内存分配失败\n");
return head;
}
n_node->value = data;
n_node->next = head;
n_node->children = NULL;
n_node->child_count = 0;
return n_node;
}
// 打印链表
void print_List(struct Node *node) {
struct Node *l = node;
while (l != NULL) {
printf("链表节点值: %d\n", l->value);
l = l->next;
}
}
// 链表操作测试
void linked_list_test() {
struct Node *head_node = NULL;
head_node = insert_atFirst(head_node, 123);
printf("第一个节点值: %d\n", head_node->value);
head_node = insert_atFirst(head_node, 4);
printf("第二个节点值: %d\n", head_node->value);
print_List(head_node);
}
5.2 矩阵操作:转置与拼接
c
// 矩阵转置
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;
}
}
}
// 矩阵转置测试
void matrix_transpose_test() {
int arr[3][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 ", arr[i][j]);
}
printf("\n");
}
zhuanzhi(arr);
printf("转置后矩阵:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
// 字符串拼接
void connect_char(char *a, char *b) {
char *p1 = a + strlen(a);
while (*b) {
*p1 = *b;
p1++;
b++;
}
*p1 = '\0';
}
// 字符串拼接测试
void string_concatenation_test() {
char char1[] = {'1', '2', '3', '4', '5', 'a', '\0'};
char char2[] = {'b', '9', '\0'};
printf("拼接前char1: %s, char2: %s\n", char1, char2);
connect_char(char1, char2);
printf("拼接后char1: %s\n", char1);
}
六、C 语言编程最佳实践与常见陷阱
6.1 编程规范与最佳实践
c
// 命名规范演示
#define MAX_STACK_SIZE 100 // 宏定义使用全大写
typedef struct {
int data[MAX_STACK_SIZE];
int top;
} Stack; // 结构体类型使用大写开头
// 函数命名使用小写加下划线
Stack* stack_create() {
Stack *s = (Stack *)malloc(sizeof(Stack));
if (s == NULL) {
return NULL;
}
s->top = -1;
return s;
}
// 注释规范
int calculate_sum(int a, int b) {
// 计算两个整数的和
return a + b;
}
// 代码缩进与格式
if (n > 0) {
for (int i = 0; i < n; i++) {
if (arr[i] > 0) {
process(arr[i]);
}
}
} else {
printf("n 必须为正数\n");
}
6.2 常见陷阱与解决方案
c
// 数组越界陷阱
void array_overflow_trap() {
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
// 危险操作:越界访问
for (int i = 0; i < 10; i++) {
printf("%d ", ptr[i]); // 访问越界,行为未定义
}
}
// 内存泄漏陷阱
void memory_leak_trap() {
while (1) {
int *ptr = (int *)malloc(sizeof(int));
// 忘记调用free(ptr)
}
}
// 空指针解引用陷阱
void null_pointer_dereference() {
int *ptr = NULL;
*ptr = 10; // 空指针解引用,程序崩溃
}
// 解决方案:防御性编程
void defensive_programming() {
int *ptr = (int *)malloc(sizeof(int));
if (ptr == NULL) {
printf("内存分配失败\n");
return;
}
*ptr = 10;
// 使用指针前检查是否为NULL
if (ptr != NULL) {
printf("ptr的值: %d\n", *ptr);
}
free(ptr);
ptr = NULL; // 释放后置为NULL
}
七、总结+本人vscode本地编辑的源码:
1 附录1 指针知识代码源码:
2 附录代码2 :指针知识点总结
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
// 结构体定义
// 定义 Node 结构体,用于多级指针操作和链表
struct Node
{
int value; // 节点值
struct Node *next; // 指向下一个节点的指针(用于链表)
struct Node **children; // 子节点数组(用于多级指针)
int child_count; // 子节点数量
};
// 定义 Person 结构体,包含姓名和年龄
struct Person
{
char name[10];
int age;
};
// 定义栈结构体
struct stack
{
int data[100]; // 假设栈最大容量为 100
int top;
};
#define maxL 99 // 栈的最大容量
// 函数声明
// 栈操作函数
void initStack(struct stack *s);
int isEmpty(struct stack *s);
int isFull(struct stack *s);
void pushStack(struct stack *s, int value);
int popStack(struct stack *s);
int peekTop(struct stack *s);
// 链表操作函数
struct Node *insert_atFirst(struct Node *head, int data);
void print_List(struct Node *node);
// 排序函数
void quick_Sort(int arr[], int low, int high);
int partition(int arr[], int low, int high);
void swap(int *a, int *b);
// 字符串处理函数
char *my_strstr(const char *haystack, const char *needle);
char *my_strncpy(char *dest, const char *src, size_t n);
char *my_strncat(char *dest, const char *src, size_t n);
int my_strncmp(const char *s1, const char *s2, size_t n);
char *my_strchr(const char *str, int c);
char *str_copy(const char *src);
// 其他函数
void wild_pointer_cause();
void pointer_relation();
void pointer_arithmetic_application();
void array_pointer_question();
void array_ptr_application();
void char_ptr_application();
void const_pointer();
void value_pass_application();
void pass_row_ptr(int arr[][3], int rows);
void qsort_demo();
void func_ptr_array();
void testFuncPtr();
int fibonacci(int n);
void test_fibonacci();
void typedef_usage();
void dachangmianshi3();
void test_strncpy();
void test_str_cat();
void test_strncmp();
void test_strchar();
void test_strstr();
void arrSort(char *arr[], int n);
int *dy_alloc(int n);
void add_child(struct Node *parent, struct Node *child);
void *arrtoPtrInt(void *x);
int getStrLen(char *a);
char *strCpyFn(char *dest, char *src);
void zhuanzhi(int arr[3][3]);
void connect_char(char *a, char *b);
// 加法函数
int add(int a, int b);
// 减法函数
int subtract(int a, int b);
// 测试字符串复制函数
void test_str_copy();
// 栈操作函数实现
// 初始化栈
void initStack(struct stack *s)
{
s->top = -1;
}
// 判断栈是否为空
int isEmpty(struct stack *s)
{
return s->top == -1 ? 1 : -100;
}
// 判断栈是否已满
int isFull(struct stack *s)
{
return s->top == maxL ? 1 : 0;
}
// 入栈操作
void pushStack(struct stack *s, int value)
{
if (!isFull(s))
{
s->data[++(s->top)] = value;
}
}
// 出栈操作
int popStack(struct stack *s)
{
if (!isEmpty(s))
{
return s->data[(s->top)--];
}
return -1;
}
// 获取栈顶元素
int peekTop(struct stack *s)
{
return s->data[s->top];
}
// 链表操作函数实现
// 在链表头部插入节点
struct Node *insert_atFirst(struct Node *head, int data)
{
struct Node *n_node = (struct Node *)malloc(sizeof(struct Node));
n_node->value = data; // 修改为正确的成员名 value
n_node->next = head; // 使用正确的成员名 next
n_node->children = NULL; // 初始化子节点数组
n_node->child_count = 0; // 初始化子节点数量
return n_node;
}
// 打印链表节点数据
void print_List(struct Node *node)
{
struct Node *l = node;
while (l != NULL)
{
printf("当前打印的节点:数据为 %d \n", l->value); // 修改为正确的成员名 value
l = l->next; // 使用正确的成员名 next
}
}
// 排序函数实现
// 快速排序
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);
}
}
// 分区函数
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]);
return i + 1;
}
// 交换两个整数的值
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
// 字符串处理函数实现
// 自定义 strstr 函数,查找子字符串
char *my_strstr(const char *haystack, const char *needle)
{
assert(haystack != NULL && needle != NULL);
if (*needle == '\0')
{
return NULL;
}
while (*haystack)
{
const char *h = haystack;
const char *n = needle;
while (*h && *n && *h == *n)
{
h++;
n++;
}
if (*n == '\0')
{
return (char *)haystack;
}
haystack++;
}
return NULL;
}
// 自定义 strncpy 函数,复制指定长度的字符串
char *my_strncpy(char *dest, const char *src, size_t n)
{
assert(dest != NULL && src != NULL);
size_t i = 0;
char *tempPtr = dest;
while (i < n && src[i])
{
dest[i] = src[i];
i++;
}
while (i < n)
{
dest[i] = '\0';
i++;
}
return tempPtr;
}
// 自定义 strncat 函数,连接指定长度的字符串
char *my_strncat(char *dest, const char *src, size_t n)
{
assert(dest != NULL && src != NULL);
size_t len = strlen(dest);
char *res = dest;
size_t i;
for (i = 0; i < n && src[i] != '\0'; i++)
{
dest[len + i] = src[i];
}
dest[i + len] = '\0';
return res;
}
// 自定义 strncmp 函数,比较指定长度的字符串
int my_strncmp(const char *s1, const char *s2, size_t n)
{
assert(s1 != NULL && s2 != NULL);
for (size_t i = 0; i < n; i++)
{
char c1 = s1[i] ? s1[i] : '\0';
char c2 = s2[i] ? s2[i] : '\0';
if (c1 != c2)
{
return c1 - c2;
}
}
return 0;
}
// 自定义 strchr 函数,查找字符
char *my_strchr(const char *str, int c)
{
printf("\n___>>>>> in the strchar func!!\n");
assert(str != NULL);
const char *p = str;
while (*p)
{
if (*p == (char)c)
{
return (char *)p;
}
p++;
}
return (char)c == '\0' ? (char *)p : NULL;
}
// 自定义字符串复制函数
char *str_copy(const char *src)
{
char *dest = (char *)malloc(strlen(src) + 1);
assert(dest != NULL);
char *p = dest;
while (*src)
{
*p++ = *src++;
}
*p = '\0';
return dest;
}
// 其他函数实现
// 演示野指针的成因
void wild_pointer_cause()
{
int a = 10;
int *new_ptr = &a;
int *heap_ptr = (int *)malloc(sizeof(int));
free(heap_ptr);
int arr[5] = {1, 2, 3, 4, 5};
}
// 演示指针的关系运算
void pointer_relation()
{
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = arr + 1;
int *p2 = arr + 3;
printf("p1 < p2: %s\n", (p1 < p2) ? "true" : "false");
printf("p1 == arr+1: %s\n", (p1 == arr + 1) ? "true" : "false");
}
// 演示指针的算术运算
void pointer_arithmetic_application()
{
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
for (int i = 0; i < 5; i++)
{
printf("arr[%d] = %d\n", i, *(p + i));
}
}
// 演示数组指针相关问题
void array_pointer_question()
{
int arr[5] = {1, 2, 3, 4, 5};
printf("sizeof(arr) = %zu\n", sizeof(arr));
printf("sizeof(arr+0) = %zu\n", sizeof(arr + 0));
}
// 演示数组指针的应用
void array_ptr_application()
{
int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int (*p)[3] = arr;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("1 the numn :%d-%d:%d\n", i, j, *(p + i)[j]);
printf("2 the num: %d -%d : %d\n", i, j, p[i][j]);
}
}
printf("\n");
}
// 演示字符指针的应用
void char_ptr_application()
{
char str1[20] = "Hello";
char *str2 = ", World!";
char *p1 = str1 + strlen(str1);
char *p2 = str2;
while (*p2)
{
*p1++ = *p2++;
}
*p1 = '\0';
printf("拼接后的字符串: %s\n", str1);
}
// 演示 const 指针的使用
void const_pointer()
{
int val = 10;
const int *cp1 = &val;
int *const cp2 = &val;
}
// 演示值传递的应用
void value_pass_application()
{
printf("-->>\nin hte 21 value-pass-func:\n---->>\n");
int x = 5;
void value_pass(int *);
value_pass(&x);
printf("x的值不变: %d\n", x);
}
// 传递二维数组的行指针
void pass_row_ptr(int arr[][3], int rows)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
// 比较函数,用于 qsort
int compare_asc(const void *a, const void *b)
{
return *(int *)a - *(int *)b;
}
// 演示 qsort 函数的使用
void qsort_demo()
{
int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
int n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), compare_asc);
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
// 演示函数指针数组的使用
int add(int a, int b);
int subtract(int a, int b); // 修正函数声明
void func_ptr_array()
{
typedef int (*OpFunc)(int, int);
OpFunc ops[] = {add, subtract};
printf("5+3=%d\n", ops[0](5, 3));
printf("5-3=%d\n", ops[1](5, 3));
}
// 测试函数指针
void testFuncPtr()
{
// 示例代码可根据实际需求添加
}
// 斐波那契数列递归实现
int fibonacci(int n)
{
if (n <= 1)
return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 测试斐波那契数列
void test_fibonacci()
{
printf("fib(5) = %d\n", fibonacci(5));
}
// 演示 typedef 的使用
void typedef_usage()
{
typedef int IntArray[5];
IntArray arr;
arr[0] = 10;
printf("typedef 使用示例: %d\n", arr[0]);
}
// 大厂面试相关代码
void dachangmianshi3()
{
#define PTR_INT int *
typedef int *ptr_int;
PTR_INT a, b;
ptr_int x, y;
printf("--->>> %s %s \n", "int*", "int");
printf("--->>> %s %s \n", "int*", "int*");
}
// 测试 strncpy 函数
void test_strncpy()
{
char dest[10] = {0};
my_strncpy(dest, "hello", 3);
printf("strncpy---复制结果: %s\n", dest);
}
// 测试 strncat 函数
void test_str_cat()
{
printf("\n---->>>>>\n strcat 函数测试\n");
char dest[10] = {'1', '2', '\0'};
char src[] = {'3', '4', 'a', '\0'};
char *res = my_strncat(dest, src, 3);
printf("--->>> \n str cat func --->>> \n%s", res);
}
// 测试 strncmp 函数
void test_strncmp()
{
char s1[] = {'1', '2', '3', '5'};
char s2[] = {'1', '2', '3'};
printf("\n--->>>\nstrnCmp 函数测试结果:\n%d", my_strncmp(s1, s2, 4));
}
// 测试 strchr 函数
void test_strchar()
{
char test[] = {'1', '2', 'a', 'b', 'c', 'f', 'l', '6', '\0'};
char *res = my_strchr(test, '7');
if (!res)
{
printf("not found!!\n");
}
else
{
printf("founded !!!! res is %c \n\n", *res);
}
}
// 测试 strstr 函数
void test_strstr()
{
char haystack[] = {'1', 'a', '2', 'b', 'c', '\0'};
char needle[] = {'b', 'c', '\0'};
char *res = my_strstr(haystack, needle); // 使用自定义的 my_strstr 函数
if (res)
{
printf("-->>>>>>\n now in strstr func, res is :%c \n", *res);
}
else
{
printf("-->>>>>>\n now in strstr func, not found \n");
}
}
// 对字符串数组进行排序
void arrSort(char *arr[], int n)
{
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;
}
}
// 动态分配数组
int *dy_alloc(int n)
{
int *res = (int *)malloc(n * sizeof(int));
for (int i = 0; i < n; i++)
res[i] = i;
return res;
}
// 添加子节点
void add_child(struct Node *parent, struct Node *child)
{
parent->children = realloc(parent->children, (parent->child_count + 1) * sizeof(struct Node *));
parent->children[parent->child_count++] = child;
}
// void 指针通用类型转换
void *arrtoPtrInt(void *x)
{
return (char *)(x) + 2;
}
// 获取字符串长度
int getStrLen(char *a)
{
char *p = a;
while (*p)
p++;
return p - a;
}
// 字符串拷贝函数
char *strCpyFn(char *dest, char *src)
{
char *result = dest;
while (*src)
*dest++ = *src++;
*dest = '\0';
return result;
}
// 矩阵转置
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;
}
}
}
// 连接两个字符串
void connect_char(char *a, char *b)
{
char *p1 = a + strlen(a);
while (*b)
{
*p1 = *b;
p1++;
b++;
}
*p1 = '\0';
}
// 加法函数
int add(int a, int b)
{
return a + b;
}
// 减法函数
int subtract(int a, int b)
{
return a - b;
}
// 测试字符串复制函数
void test_str_copy()
{
char *copy = str_copy("test");
printf("复制后的字符串: %s\n", copy);
free(copy);
}
void value_pass(int *p)
{
printf("在 value_pass 函数中:原值 = %d\n", *p);
*p = *p + 10;
printf("在 value_pass 函数中:新值 = %d\n", *p);
}
// 主函数
int main()
{
printf("=== C 语言知识点综合测试 ===\n\n");
// 野指针相关测试
printf("野指针相关测试:\n");
wild_pointer_cause();
printf("\n");
// 指针关系和算术运算测试
printf("指针关系和算术运算测试:\n");
pointer_relation();
pointer_arithmetic_application();
printf("\n");
// 数组指针测试
printf("数组指针测试:\n");
array_pointer_question();
array_ptr_application();
printf("\n");
// 字符指针测试
printf("字符指针测试:\n");
char_ptr_application();
printf("\n");
// const 指针测试
printf("const 指针测试:\n");
const_pointer();
printf("\n");
// 值传递测试
printf("值传递测试:\n");
value_pass_application();
printf("\n");
// 二维数组行指针传递测试
printf("二维数组行指针传递测试:\n");
int arr2d[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
pass_row_ptr(arr2d, 3);
printf("\n");
// 字符串复制测试
printf("字符串复制测试:\n");
test_str_copy(); // 确保函数已声明和定义
printf("\n");
// qsort 函数测试
printf("qsort 函数测试:\n");
qsort_demo();
func_ptr_array();
testFuncPtr();
printf("\n");
// 斐波那契数列测试
printf("斐波那契数列测试:\n");
test_fibonacci();
printf("\n");
// typedef 使用测试
printf("typedef 使用测试:\n");
typedef_usage();
printf("\n");
// 大厂面试相关代码测试
printf("大厂面试相关代码测试:\n");
dachangmianshi3();
printf("\n");
// 字符串处理函数测试
printf("字符串处理函数测试:\n");
test_strncpy();
test_str_cat();
test_strncmp();
test_strchar();
test_strstr();
printf("\n");
// 栈操作测试
printf("栈操作测试:\n");
struct stack s;
initStack(&s);
printf("初始化后,栈是否为空: %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));
printf("\n");
// 链表操作测试
printf("链表操作测试:\n");
struct Node *head_node = NULL;
head_node = insert_atFirst(head_node, 123);
printf("第一个节点: %d\n", head_node->value); // 修改为正确的成员名 value
head_node = insert_atFirst(head_node, 4);
printf("第二个节点: %d\n", head_node->value); // 修改为正确的成员名 value
print_List(head_node);
printf("\n");
// 快速排序测试
printf("快速排序测试:\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]);
}
printf("\n");
printf("=== 测试结束 ===\n");
return 0;
}
第三部分:本人结合AI所总结的相关c语言知识点总结
知识点总结表格:
考点分类 | 具体知识点 | 常见题型 | 解题关键点 | 易错点 |
---|---|---|---|---|
基础概念 | 指针与数组的本质区别 | 选择题(如数组名衰退规则) | 数组名在sizeof 和& 操作外衰退为指针,数组存储位置由定义方式决定 | 混淆数组与指针的本质,认为数组名始终是指针 |
指针大小与平台相关性 | 选择题(64 位系统指针大小) | 指针大小仅由操作系统位数决定(32 位 4 字节,64 位 8 字节) | 误以为指针大小与指向类型有关 | |
野指针成因与危害 | 代码找错题 | 未初始化、释放未置 NULL、越界访问、指向临时变量是野指针主因 | 忽视临时变量地址失效问题 | |
数组与指针 | 二维数组与指针运算 | 表达式求值题(如arr[1][2] 的指针表示) | 二维数组按行存储,arr[i][j] 等价于*(*(arr+i)+j) | 错误计算指针偏移量 |
指针数组与数组指针辨析 | 定义辨析题 | int (*p)[5] 是数组指针,int *p[5] 是指针数组 | 混淆括号优先级导致类型判断错误 | |
字符串指针与数组陷阱 | 代码运行结果题 | 字符串字面量存储在只读区,数组可修改,指针指向不可修改 | 修改字符串常量导致段错误 | |
内存管理 | 动态内存分配与 realloc 陷阱 | 代码找错题 | realloc 需检查返回值,成功时原指针可能改变 | 直接释放原指针导致内存泄漏 |
指针与结构体对齐 | sizeof 计算题 | 结构体对齐规则:成员按自身大小对齐,整体为最大对齐参数整数倍 | 忽视对齐导致结构体大小计算错误 | |
多级指针操作 | 指针解引用题 | 多级指针解引用次数等于级别数,注意指针指向的指针层级 | 解引用次数不足或过多导致访问错误 | |
函数指针 | 函数指针数组实现计算器 | 编程题 | 定义函数指针类型,通过数组索引调用对应函数 | 函数指针类型定义错误 |
通用排序中的 void * 指针 | 算法实现题 | 使用void* 和memcpy 实现任意类型排序,需传入比较函数 | 类型转换时未考虑内存对齐问题 | |
算法应用 | 链表逆序(指针操作) | 数据结构题 | 三指针法:prev 、current 、next 配合反转指针 | 指针更新顺序错误导致链表断裂 |
快速排序中的指针应用 | 算法题 | 用指针偏移实现任意类型分区,void* 配合memcpy 交换元素 | 分区时偏移量计算错误 | |
系统级操作 | 指针与数组传参陷阱 | sizeof 计算题 | 数组作为参数衰退为指针,无法获取原始大小 | 在函数内用sizeof(arr) 获取数组大小 |
指针运算与类型转换 | 表达式求值题 | 指针算术步长由指向类型决定,不同类型指针相减得到元素个数 | 忽视指针类型不同导致偏移量错误 | |
内存泄漏检测与预防 | 代码分析题 | 动态内存分配后需正确释放,realloc 后更新指针 | 遗漏realloc 后的指针更新导致内存泄漏 | |
指针实现字符串拷贝 | 编程题 | 指针遍历源字符串,逐个字符复制,处理NULL 输入 | 未处理源字符串或目标字符串为NULL 的情况 | |
链表环检测(Floyd 判圈算法) | 算法题 | 快慢指针法:慢指针每次走 1 步,快指针每次走 2 步,相遇则有环 | 边界条件处理不当(空链表或单节点链表) | |
指针与内存池设计 | 系统编程题 | 预分配大内存,用指针跟踪分配位置,避免碎片 | 内存池边界检查缺失导致越界 | |
指针与类型转换(内存复用) | 底层编程题 | 通过强制类型转换实现不同类型内存复用,如int 与float 互转 | 违反类型安全原则导致未定义行为 | |
指针与位操作(内存映射) | 位运算题 | 用指针定位内存 |
逻辑关系图:
指针知识体系
│
├── 基础概念
│ ├── 指针本质(地址操作、类型系统)
│ ├── 指针与数组的关系
│ │ ├── 数组名衰退规则
│ │ ├── 二维数组内存布局
│ │ └── 指针数组 vs 数组指针
│ ├── 指针大小(平台相关性)
│ └── 野指针
│ ├── 成因(未初始化、释放未置NULL、越界、临时变量)
│ └── 危害(段错误、数据破坏)
│
├── 内存管理
│ ├── 动态分配(malloc/realloc/free)
│ │ ├── realloc陷阱(返回值检查、指针更新)
│ │ └── 内存泄漏检测
│ ├── 结构体对齐
│ │ ├── 对齐规则(成员对齐、整体对齐)
│ │ └── sizeof计算
│ └── 多级指针操作(解引用层级)
│
├── 函数指针
│ ├── 函数指针定义与调用
│ ├── 函数指针数组(回调机制)
│ └── 通用编程(void*指针+比较函数)
│
├── 算法与数据结构
│ ├── 链表操作
│ │ ├── 链表逆序(三指针法)
│ │ └── 环检测(Floyd算法)
│ ├── 排序算法
│ │ ├── 快速排序(指针分区)
│ │ └── 通用排序实现
│ └── 字符串处理(指针实现strcpy等)
│
└── 系统级编程
├── 指针传参与数组衰退
├── 指针运算(类型转换、偏移量)
├── 内存池设计(预分配与指针跟踪)
└── 底层操作(位运算、内存复用)
最后:
觉得我写得不错的,还请各位给一个点赞收藏关注!