本文最后更新于136 天前,其中的信息可能已经过时,如有错误请发送邮件至 2641805259@qq.com
1. 指针的本质
指针是存储内存地址的变量。
int a = 1;
float b = 0.1;
指针 p = 地址
普通变量存储数据本身;指针变量存储的是某个数据在内存中的位置。
内存中每个字节都有唯一的地址,如果内存中有个字节,其字节地址可编号为
示例:
int a = 10;
int *p = &a; // p 中存放 a 的地址
2. 指针的声明与初始化
2.1 声明
类型 *指针名;
示例:
int *p; // 指向 int 的指针
double *q; // 指向 double 的指针
2.2 初始化
int a = 5;
int *p = &a;
&:表示取变量的地址
未初始化的指针属于“野指针”,危险且不可使用。
3. 指针的解引用(dereference)
解引用指通过指针访问指向的变量。
int a = 10;
int *p = &a;
*p == a
printf("%d\n", *p); // 输出 10
*p = 20; // 修改 a 的值为 20
4. 指针与数组
4.1 数组名是不可修改的指针常量(指向的内存不变)
int arr[3] = {1,2,3};
int *p = arr; // 等价于 &arr[0]
printf("%d\n", *(p+1)); // 输出 2 移动了 1 * sizeof(int) = 4
指针 + n 实际移动 n × sizeof(类型) 字节位置。
4.2 指针运算
++优先级大于*
| * p++ | 取当前指针 p 指向的元素的值。表达式结束后,p 自增 1,指向下一个元素。(对当前元素解引用,然后指针后移。) |
|---|---|
| *(p++) | 取当前指针 p 指向的元素的值。表达式结束后,p 自增 1,指向下一个元素。(对当前元素解引用,然后指针后移。) |
| (*p)++ | 返回:原来的 *p, 副作用:*p = *p + 1,p 不移动 |
| ++*p | 副作用:*p = *p + 1 ,返回:新的 *p,p 不移动 |
| ++(*p) | 同上 |
| *++P | p 先递增,指向下一个元素。解引用新位置的值。 |
| *(++p) | 同上 |
5. 指针与函数
5.1 通过指针实现“传引用”
用于函数内部修改外部变量。
void set(int *x) {
*x = 100;
}
int main() {
int a = 5;
set(&a);
}
5.2 指针作为数组参数
避免复制大数组。
void print(int *arr, int n) {
for(int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
}
*(arr + i) == arr[i]
6. 多级指针
表示指向指针的指针。
int a = 10;
int *p = &a;
int **pp = &p;
(int *) *pp = &p;
*pp = p
**pp = *(*pp) = *p = a
printf("%d\n", **pp); // 输出 10
典型用例:
- 管理二维动态数组
- 修改函数内部的指针(如
malloc封装)
7. 动态内存与指针
使用 malloc / calloc / realloc 动态申请内存后通过指针访问。
int *p = malloc(sizeof(int) * 5);
p[0] = 1;
free(p);
p = malloc(sizeof(int) * 10);
注意事项:
- 申请后必须检查是否为
NULL - 使用完毕必须
free free后将指针置为NULL,避免悬挂指针
8. 指针的赋值与类型转换
不同类型指针不可直接赋值
C 语言中,指针类型必须与所指向对象的类型一致;不同类型的指针不能在未转换的情况下相互赋值。
错误示例:
int *p;
char *q;
q = p; // 类型不兼容,编译器将给出警告或错误
原因: 不同指针类型解读内存的方式不同(访问粒度、对齐要求、步进方式都不同)。直接赋值会导致行为错误。
void* 指针
1 含义
void * 表示“指向未知类型”的指针,即通用指针类型。
特点:
- 不知道指向的对象的类型
- 不支持解引用 (
*q非法) - 不支持指针算术运算 (
q+1非法) - 可以接收“任意类型指针”的赋值
- 可以赋值给“任意类型指针”,但需要显式转换
示例:
int *p = &i;
void *q = p; // 正确,int* -> void*
指针类型转换
1 显式转换
指针可以通过显式类型转换改变“访问内存的视角”。
示例:
int i = 0x12345678;
int *p = &i;
void *q = (void *)p; // p 给 q,类型变为 void*
此时:
p按 int 视角解读内存(通常 4 字节)q不知道类型,无法直接解读- 若将
q转换为char*,则内存变成按字节解读
示例:
char *c = (char *)q; // 现在按 1 字节读内存
printf("%02x\n", c[0]); // i 的低字节
2 “通过 q 解读内存的视角变了”
这句的技术意义是:
指针类型决定了解释内存的单位与方式
int*→ 每次解引用读取sizeof(int)字节char*→ 每次解引用读取 1 字节void*→ 无法解引用,必须先转换类型double*→ 每次解引用读取sizeof(double)字节
因此,改变指针类型即改变查看内存的方式。
这是 C 语言中非常重要且需要谨慎使用的机制。
4. 示例:同一地址,不同视角
int i = 0x11223344;
int *p = &i; // 按4字节整数读取
char *c = (char*)&i; // 按字节读取
内存(假设小端机):
| 地址 | 内容 |
|---|---|
| &i+0 | 44 |
| &i+1 | 33 |
| &i+2 | 22 |
| &i+3 | 11 |
访问差异:
printf("%x\n", *p); // 11223344
printf("%x\n", c[0]); // 44
printf("%x\n", c[1]); // 33
5. 合法但危险的行为
C 的类型转换功能强大,但必须注意:
- 若按错误类型访问内存,可能破坏对齐要求
- 可能导致未定义行为(UB)
- 用于解析二进制协议、内存分析、字节序转换时需要精确控制
8. 函数指针
用于指向函数入口地址。
声明方式:
返回类型 (*指针名)(参数类型列表)
示例:
int add(int a, int b) { return a+b; }
int (*fp)(int,int) = add;
int r = fp(3,4); // 调用 add
应用场景:
- 回调机制
- 状态机
- 函数表
9. 指针常见错误与风险
- 野指针(未初始化)
- 悬挂指针(free 后继续使用)
- 越界访问
- 指针类型不匹配
- 重复释放导致崩溃
示例(错误):
int *p;
*p = 10; // 未初始化直接使用
10. 指针与 const
10.1 指向常量的指针
const int *p; // 不允许 *p 被修改
10.2 指针本身为常量
int * const p; // p 不可被重新赋地址
10.3 都为常量
const int * const p;