C/C++ 指针完全指南 - 从基础到进阶
指针是 C/C++ 中最强大但也最容易让人困惑的特性。本文系统讲解指针的概念、用法和注意事项,帮助你真正理解指针。
一、指针基础概念
1.1 核心概念
| 概念 |
说明 |
| 内存块 |
存储单位,1 字节 = 8 位,是最小可寻址单位 |
| 内存首地址 |
内存块的起始地址,可用数字表示 |
| 指针 |
内存首地址的数值表示 |
| 指针变量 |
存储指针(内存首地址)的变量 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ┌─────────────────────────────────────────────────────────────────────────┐ │ 内存与指针概念图 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 内存空间 │ │ ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐ │ │ │0x00│0x01│0x02│0x03│0x04│0x05│0x06│0x07│0x08│0x09│0x0A│0x0B│... │ │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤ │ │ │ │ │ 20 │ │ │ │'a' │'b' │'c' │'\0'│ │ │ │ │ └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘ │ │ ▲ ▲ │ │ │ │ │ │ int a = 20 char str[] = "abc" │ │ &a = 0x02 &str[0] = 0x06 │ │ │ └─────────────────────────────────────────────────────────────────────────┘
|
1.2 指针大小
| 机器架构 |
指针大小 |
说明 |
| 32 位 |
4 字节 |
可寻址 4GB 内存 |
| 64 位 |
8 字节 |
可寻址更大内存空间 |
1.3 类型与内存分配
1 2 3 4
| char ch; int a; float f; double d;
|
💡 关键:变量定义时即分配内存,内存首地址由编译器分配。
二、* 号与 & 号
2.1 * 号的含义
| 上下文 |
含义 |
定义时 int* p |
声明 p 是指针类型 |
使用时 *p |
解引用,获取指针指向内存块的值 |
等号左边 *p = |
向内存块写入值 |
等号右边 x = *p |
从内存块读取值 |
1 2 3 4 5
| int a = 10; int* p = &a;
*p = 20; int b = *p;
|
2.2 & 号的含义
| 上下文 |
含义 |
&变量 |
取变量的内存首地址 |
&指针 |
取指针变量的地址(二级指针) |
1 2 3
| int a = 10; int* p = &a; int** pp = &p;
|
2.3 优先级
⚠️ 注意:int* 类型的优先级高于 int 类型。
三、指针类型
3.1 指针分类
| 类型 |
说明 |
| 空指针 |
值为 NULL (0) 的指针 |
| 野指针 |
指向非法内存的指针 |
| 万能指针 |
void* 类型,可转换为任意指针类型 |
3.2 空指针与野指针
1 2 3 4 5 6 7 8 9
| int* p = NULL; if (p != NULL) { *p = 10; }
int* p; *p = 10;
|
💡 最佳实践:指针定义时务必初始化为 NULL。
3.3 万能指针
1 2 3 4 5 6 7
| void* p; int a = 10; p = &a;
int* ip = (int*)p; *ip = 20;
|
3.4 指针运算
1 2 3 4 5 6 7 8
| int arr[5] = {1, 2, 3, 4, 5}; int* p = arr;
p + 1;
char* cp = (char*)arr; cp + 1;
|
四、数组与指针
4.1 数组名即指针
1 2 3 4 5
| int arr[10]; int* p = arr;
int* p2 = &arr[0];
|
4.2 指针与数组的关系
1 2 3 4 5 6 7 8
| int arr[5] = {1, 2, 3, 4, 5}; int* p = arr;
arr[2] *(arr + 2) p[2] *(p + 2)
|
💡 核心:*(p + i) 与 p[i] 是完全等价的。
五、多级指针(套娃)
5.1 多级指针概念
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ┌─────────────────────────────────────────────────────────────────────────┐ │ 多级指针结构 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 一级指针 │ │ ┌─────────┐ ┌─────────┐ │ │ │ int a │◄─────│ int* p │ │ │ │ value=10│ │ &a │ │ │ └─────────┘ └─────────┘ │ │ │ │ 二级指针 │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ int a │◄─────│ int* p │◄─────│ int** q │ │ │ │ value=10│ │ &a │ │ &p │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ 三级指针 │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ int a │◄─────│ int* p │◄─────│ int** q │◄─────│int*** w │ │ │ │ value=10│ │ &a │ │ &p │ │ &q │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘
|
5.2 多级指针代码
1 2 3 4 5 6 7 8 9 10
| int a = 10; int* p = &a; int** q = &p; int*** w = &q;
a *p **q ***w
|
六、函数传参
6.1 值传递
1 2 3 4 5 6 7 8
| void print_Array(int a[]) { for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++) { printf("%d,", a[i]); } }
|
⚠️ 陷阱:形参数组退化为指针,sizeof(a) = 指针大小(4 或 8),不是数组大小。
6.2 正确的数组传参
1 2 3 4 5 6 7 8 9 10 11
| void print_Array(int* array, int length) { for (int i = 0; i < length; i++) { printf("%d,", array[i]); } printf("\n"); }
int array[] = {7, 79, 465, 65, -345, -346, 798, 1, 0, 45}; print_Array(array, sizeof(array) / sizeof(array[0]));
|
6.3 形参数组等价性
| 写法 |
编译器理解 |
void func(int a[100]) |
void func(int* a) |
void func(int a[]) |
void func(int* a) |
void func(int* a) |
void func(int* a) |
💡 结论:形参中的数组只是语法糖,本质上都是指针。
七、指针数组与数组指针
7.1 指针数组
指针数组:是一个数组,每个元素是指针。
7.2 数组指针
数组指针:是一个指针,指向整个数组。
7.3 区分技巧
| 声明 |
含义 |
记忆技巧 |
int* p[10] |
指针数组 |
[] 优先级高,先是个数组 |
int (*p)[10] |
数组指针 |
() 优先级高,先是个指针 |
1 2 3 4 5 6 7
| char* arr[3] = {"hello", "world", "pointer"};
int (*p)[3]; int nums[3] = {1, 2, 3}; p = &nums;
|
八、指针函数与函数指针
8.1 指针函数
指针函数:返回指针的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int* pointer_Function() { int a = 200; return &a; }
int* pointer_Function() { static int a = 200; return &a; }
int* pointer_Function() { int* p = (int*)malloc(sizeof(int)); *p = 200; return p; }
|
8.2 函数指针
函数指针:指向函数的指针。
1 2 3 4 5 6 7 8 9 10 11
| int (*func_ptr)(int, int);
int add(int a, int b) { return a + b; }
func_ptr = add;
int result = func_ptr(3, 5);
|
8.3 函数指针数组
1 2 3 4 5 6 7 8 9 10 11 12 13
| int (*funcs[3])(int, int);
int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; }
funcs[0] = add; funcs[1] = sub; funcs[2] = mul;
int result = funcs[0](3, 5);
|
九、字符串与指针
9.1 字符串结束符
| 表示 |
ASCII |
说明 |
0 |
48 |
字符 ‘0’ |
'\0' |
0 |
字符串结束符 |
1 2 3 4 5 6 7 8 9 10 11
| char a[] = {'a', 'b'}; printf("%s", a);
char a[10] = {'a', 'b'}; printf("%s", a);
char a[] = {'a', 'b', '\0'}; printf("%s", a);
char a[] = "ab"; printf("%s", a);
|
9.2 字符指针的特殊性
1 2 3 4 5 6 7 8 9
| char str[] = "hello"; printf("%s\n", str);
int i = 0; while (str[i] != '\0') { printf("%c", str[i]); i++; }
|
9.3 字符常量区
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| printf("%p\n", "aaa"); printf("%p\n", "aaa");
char buf[] = "aaa";
char* p1 = "hello";
char buf[] = "hello"; buf[0] = 'H';
|
十、结构体与指针
10.1 结构体指针的三种操作方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct Student { char name[20]; int age; float score; };
struct Student s[3] = {};
s[0].age = 15; strcpy(s[0].name, "mike"); s[0].score = 90.5f;
(*(s + 1)).age = 16; strcpy((*(s + 1)).name, "john"); (*(s + 1)).score = 85.0f;
(s + 2)->age = 17; strcpy((s + 2)->name, "tom"); (s + 2)->score = 88.0f;
|
10.2 指针访问结构体
1 2 3 4 5 6 7 8 9 10
| struct Student* p = s;
p[0].age = 15;
(*(p + 1)).age = 16;
(p + 2)->age = 17;
|
十一、内存管理
11.1 内存分配时机
1 2 3 4 5 6 7
| int a; int* p;
int* p = (int*)malloc(sizeof(int)); free(p);
|
11.2 变量与内存
| 代码 |
内存分配 |
int a; |
分配内存首地址 + 内存块 |
int* p; |
分配指针变量的内存首地址和内存块 |
💡 理解:指针变量也是变量,也有自己的内存地址和内存块,只是它的内存块专门用来存储地址。
十二、常见陷阱
12.1 返回局部变量地址
1 2 3 4 5 6 7 8 9 10 11
| int* func() { int a = 10; return &a; }
int* func() { static int a = 10; return &a; }
|
12.2 数组传参陷阱
1 2 3
| void func(int arr[100]) { printf("%zu", sizeof(arr)); }
|
12.3 字符串修改陷阱
1 2 3 4 5
| char* p = "hello"; p[0] = 'H';
char arr[] = "hello"; arr[0] = 'H';
|
十三、总结
| 主题 |
核心要点 |
| 基础概念 |
内存块 + 内存首地址 = 指针 |
| * 号 |
定义时表示类型,使用时表示解引用 |
| & 号 |
取地址操作符 |
| 数组与指针 |
数组名即首地址,*(p+i) 等价 p[i] |
| 多级指针 |
每一级 * 都需要解引用一次 |
| 函数传参 |
数组退化为指针,需传递长度 |
| 字符串 |
自动添加 '\0' 结束符 |
| 常量区 |
字符串字面量只读,需拷贝后修改 |
💡 学习建议:
- 理解内存模型是关键
- 多画图,多思考内存布局
- 注意数组传参的退化问题
- 警惕野指针和返回局部地址
- 使用工具(如 Valgrind)检测内存错误
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1487842110@qq.com