一、指针的本质:内存与地址
1.1 内存的管理方式
计算机内存被划分为一个个 1 字节的内存单元,每个单元都有唯一的编号(地址)。这就像宿舍楼的房间编号,通过地址能快速定位到内存中的数据。
内存单位换算:
- 1 字节(byte)= 8 比特位(bit)
- 1KB = 1024byte,1MB = 1024KB,1GB = 1024MB(二进制计数)
1.2 地址与指针的关系
- 地址:内存单元的编号,如 0x006FFD70
- 指针:C 语言中对地址的称呼,是同一个概念的不同说法
- 32 位机器支持 2³² 个地址(约 4GB 内存),64 位机器支持 2⁶⁴个地址
硬件层面:地址通过地址总线传输,32 位机器有 32 根地址线,每根线表示 0/1 两种状态,共同组成 2³² 个地址。
二、指针变量的核心概念
2.1 指针变量的定义与使用
指针变量专门用于存储地址,语法格式:类型* 变量名
int a = 10;
int* pa = &a; // &是取地址操作符,获取a的地址
*pa = 20; // *是解引用操作符,通过地址修改a的值
int*表示指向整型变量的指针类型- 指针变量存储的是地址,解引用时通过地址找到目标变量
2.2 指针变量的大小
指针变量的大小取决于平台位数:
- 32 位平台:4 字节(可表示 2³² 个地址)
- 64 位平台:8 字节(可表示 2⁶⁴个地址)
printf("%zd\n", sizeof(char*)); // 4或8
printf("%zd\n", sizeof(int*)); // 4或8(与类型无关)
三、指针类型的意义
指针的类型决定了其操作权限和步长:
3.1 解引用的权限
char*指针解引用:访问 1 字节int*指针解引用:访问 4 字节(32 位 int)
int n = 0x11223344;
char* pc = (char*)&n;
*pc = 0; //仅修改低地址1字节,n变为0x11223300
3.2 指针 ± 整数的步长
指针 ± 整数时,偏移量由类型决定:
char*±1:偏移 1 字节int*±1:偏移 4 字节
int arr[3] = {1,2,3};
int* p = arr;
p++; //指向arr[1](偏移4字节)
3.3 特殊类型:void * 指针
- 可接收任意类型地址,但不能直接解引用或 ± 整数
- 用于实现泛型函数(如
memcpy、qsort)
void* p = &a; //正确:接收int类型地址
*p = 10; //错误:void*不能直接解引用
四、const 与指针:限制访问权限
const 修饰指针有三种场景,关键看 const 与*的位置关系:
-
const 在 * 左边:限制指针指向的内容不可修改
const int* p; //*p不可改,p可改
-
const 在 * 右边:限制指针变量本身不可修改
int* const p; //p不可改,*p可改
-
两边都有 const:指针和指向内容都不可修改
const int* const p; //p和*p都不可改
五、指针运算:三种基本操作
5.1 指针 ± 整数
常用于数组遍历,通过指针移动访问元素:
int arr[5] = {1,2,3,4,5};
int* p = arr;
for(int i=0; i<5; i++)
{
printf("%d ", *(p+i)); //等价于arr[i]
}
5.2 指针 - 指针
计算两个指针之间的元素个数(需指向同一数组):
int len = &arr[4] - &arr[0]; //结果为4(元素个数)
5.3 指针的关系运算
比较指针地址大小,常用于数组边界判断:
int* p = arr;
while(p < arr + 5)
{ //未越界时循环
*p = 0;
p++;
}
六、野指针:危险的陷阱
野指针是指向未知内存的指针,会导致程序崩溃或数据错误,常见成因:
-
指针未初始化:局部指针默认值随机
int* p; //野指针
*p = 10; //危险!
-
指针越界访问:超出数组范围
int arr[5];
for(int i=0; i<=5; i++)
{
arr[i] = i; //i=5时越界
}
-
指向已释放的内存:返回局部变量地址
int* func()
{
int a = 10;
return &a; //函数结束后a被释放
}
规避方法:
- 指针初始化时赋值
NULL - 访问前检查有效性(
if(p != NULL)) - 避免返回局部变量地址
- 使用
assert断言辅助调试
七、指针与数组:密不可分的关系
7.1 数组名的本质
数组名通常表示首元素地址,但有两个例外:
sizeof(数组名):表示整个数组大小&数组名:表示整个数组的地址
int arr[5] = {1,2,3,4,5};
printf("%p\n", arr); //首元素地址(0x0012ff40)
printf("%p\n", &arr); //整个数组地址(0x0012ff40,与首地址值相同)
printf("%p\n", &arr + 1); //偏移整个数组大小(0x0012ff54)
7.2 指针访问数组的原理
arr[i]本质是*(arr + i),编译器会将数组访问转换为指针运算:
int* p = arr;
p[2] = 10; //等价于*(p+2) = 10
7.3 数组传参的本质
数组传参时实际传递的是首元素地址,函数内无法通过sizeof获取数组长度:
void func(int arr[])
{ //等价于int* arr
printf("%d\n", sizeof(arr)); //4/8(指针大小)
}
八、高级指针:从二级指针到函数指针
8.1 二级指针
指向指针的指针,用于存储指针变量的地址:
int a = 10;
int* p = &a;
int** pp = &p; //二级指针
**pp = 20; //等价于a = 20
8.2 指针数组
存储指针的数组,常用于模拟二维数组或管理多个字符串:
char* strs[] = {"apple", "banana", "cherry"};
printf("%s\n", strs[1]); //输出"banana"
8.3 函数指针
指向函数的指针,可将函数作为参数传递(回调函数的基础):
int add(int a, int b){ return a + b; }
int main()
{
int(*pf)(int, int) = add; //函数指针
printf("%d\n", pf(2,3)); //输出5
return 0;
}
8.4 函数指针数组
存储函数指针的数组,用于实现 "转移表"(减少分支判断):
//计算器示例
int(*calc[])(int, int) = {0, add, sub, mul, div};
int result = calc[1](2,3); //调用add(2,3)
九、实战应用:回调函数与 qsort
回调函数是通过函数指针调用的函数,典型应用是qsort(快速排序函数):
//比较int类型的回调函数
int cmp_int(const void* a, const void* b)
{
return *(int*)a - *(int*)b;
}
int main()
{
int arr[] = {3,1,4,2};
qsort(arr, 4, sizeof(int), cmp_int); //排序
return 0;
}
qsort可排序任意类型数据,只需提供对应的比较函数,体现了指针的灵活性。
十、常见陷阱:sizeof 与 strlen 的区别
| 特性 | sizeof | strlen |
|---|---|---|
| 本质 | 操作符(编译期计算) | 库函数(运行期计算) |
| 功能 | 计算内存大小(字节) | 计算字符串长度(到 \0) |
| 处理对象 | 变量 / 类型 | 字符串首地址 |
| 越界风险 | 无(不访问数据) | 有(未找到 \0 时) |
总结
指针是 C 语言的灵魂,其核心是对内存地址的直接操作。从基础的指针变量、类型意义,到高级的函数指针、回调函数,指针的知识点环环相扣。掌握指针需理解:
- 内存地址是指针的物理基础;
- 指针类型决定操作权限和步长;
- 数组与指针的本质关联(
arr[i] = *(arr+i)); - 高级指针(函数指针、二级指针等)是实现复杂逻辑的关键。
通过大量实践(尤其是调试内存地址),才能真正理解指针的精髓,为后续学习数据结构、操作系统等打下坚实基础。

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



