一、指针的本质与内存模型
1.1 内存地址与指针变量
-
内存的物理结构:内存由连续的字节组成,每个字节有唯一地址(如
0x7ffeeb0d1234)。 -
指针变量的核心作用:存储地址的容器。我们可以简单把指针和地址等价起来。
int a = 10; int* p = &a; // p存储a的地址 printf("%p", p); // 输出类似0x7ffeeb0d12341.2 指针类型与操作权限
- 类型决定访问范围:
char* pc = (char*)&a; // 仅访问a的第一个字节
printf("%d", *pc); // 若a=0x12345678,输出0x78(小端模式)
- 指针运算的步长(单个指针的大小):
double arr[5]; double* p = arr; p++; // 移动8字节(sizeof(double*))二、复杂指针类型解析
2.1 右左法则:拆解复杂声明
-
核心原则:从变量名出发,结合运算符优先级逐步解析。
-
经典示例:
int (*(*func)[5])(char);
// 解析:
// 1. func是指针
// 2. 指向一个数组,数组元素是函数指针
// 3. 函数接受char参数,返回int
2.2 指针数组 vs 数组指针
| 类型 | 声明 | 本质 | 应用场景 |
|---|---|---|---|
| 指针数组 | int* arr[5] | 存储指针的数组 | 管理多个字符串 |
| 数组指针 | int (*p)[5] | 指向数组的指针 | 二维数组传参 |
反正要记住的是,数组结尾就是数组,指针结尾就是指针。
示例1:指针数组管理字符串
char* languages[] = {"C", "Python", "Java"};
printf("%s\n", languages[1]); // 输出"Python"
示例2:数组指针遍历二维数组
int matrix[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
int (*p)[4] = matrix; // 指向第一行
printf("%d\n", p[1][2]); // 输出7
三、函数指针与高阶应用
3.1 函数指针的定义与调用
int add(int a, int b) { return a + b; }
int (*funcPtr)(int, int) = add; // 定义函数指针
printf("%d\n", funcPtr(3, 5)); // 输出8
3.2 回调函数与策略模式
场景:实现可扩展的排序算法
typedef int (*Comparator)(int, int);
void sort(int arr[], int n, Comparator cmp) {
for(int i=0; i<n-1; i++) {
for(int j=0; j<n-i-1; j++) {
if(cmp(arr[j], arr[j+1]) > 0) {
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
// 升序比较函数
int ascending(int a, int b) { return a - b; }
// 降序比较函数
int descending(int a, int b) { return b - a; }
// 使用示例
int main() {
int arr[] = {5, 2, 9, 1};
sort(arr, 4, ascending); // 结果:1, 2, 5, 9
}
3.3 函数指针数组:实现计算器核心
double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double (*ops[])(double, double) = {add, sub};
int main() {
printf("%.2f\n", ops[0](3.5, 2.1)); // 输出5.60
printf("%.2f\n", ops[1](3.5, 2.1)); // 输出1.40
}
四、指针与内存安全
4.1 野指针的成因与防范
- 常见成因:
- 未初始化的指针:
int* p;(指向随机地址) - 指针越界访问:
int arr[5]; int* p = &arr[5]; - 指向已释放的内存:
free(p);后继续使用p
-
防御策略:
int* p = NULL; // 初始化时赋NULL
if (p != NULL) { // 使用前检查有效性
*p = 10;
}
4.2 内存操作函数详解
memcpy与memmove的区别:
int src[] = {1,2,3,4,5};
int dest[5];
// 非重叠拷贝
memcpy(dest, src, 5 * sizeof(int));
// 重叠区域拷贝(必须用memmove)
memmove(src + 1, src, 3 * sizeof(int)); // 结果:1,1,2,3,5
五、指针与结构体的高级交互
5.1 结构体指针与对齐规则
struct Student {
char name[20];
int age;
float score;
};
struct Student s = {"Alice", 20, 95.5};
struct Student* p = &s;
printf("%s\n", p->name); // 输出"Alice"
5.2 结构体内存对齐原理
- 对齐规则:(这里我认为光看规则是不容易理解的,最好就是多看看例子,还有就是要多画图)
- 结构体成员按自身大小和对齐数的最小值对齐。
- 结构体总大小为最大对齐数的整数倍。
struct Example {
char c; // 1字节,对齐到0
int i; // 4字节,对齐到4(填充3字节)
}; // 总大小 = 8字节
六、指针的陷阱
6.1 类型强制转换的风险
float f = 12.3;
int* p = (int*)&f; // 强制转换可能导致数据解释错误
printf("%d\n", *p); // 输出不可预测的整数值
6.2 多级指针的深度解引用
不管是几级指针,只要是指针存储的就是地址,n级指针存储n - 1级指针的地址,n = 1时就是存储变量的地址。
int a = 10;
int* p = &a;
int** pp = &p;
printf("%d\n", **pp); // 输出10
关键学习建议:
调试观察:使用调试器查看指针地址和内存变化。调试是非常重要的技能,若要发展成程序员,一定要好好学习如何去调试,否则以后再找bug时会吃大亏。指针的灵活性与风险并存,只有透彻理解其底层原理,才能编写出高效可靠的C程序。
希望文章对读者有所帮助,如果文章有什么不妥之处,欢迎指正。
1412

被折叠的 条评论
为什么被折叠?



