C++ 指针
指针是一种特殊变量,它保存的是“另一个变量的内存地址”,而不是直接保存数据值。借助指针,程序可以高效地访问和操作内存,是系统级编程与动态内存管理的关键工具。
直接访问指针时,得到的是地址;要拿到地址上的数据,必须用解引用运算符 *。
示例代码:
#include <iostream>
using namespace std;
int main() {
int var = 10;
// 声明指针并保存 var 的地址
int* ptr = &var;
// 打印值与地址
cout << "变量值: " << var << endl; // 10
cout << "变量地址: " << &var << endl; // 0x7fffa0757dd4
cout << "指针存储的地址: " << ptr << endl; // 同上
cout << "指针指向的值: " << *ptr << endl; // 10
return 0;
}
Value of x: 10
Address of x: 0x7fffa0757dd4
Value stored in pointer ptr: 0x7fffa0757dd4
Value pointed to by ptr: 10
1.创建指针(Create Pointer)
指针的声明方式与普通变量几乎一样,只要在变量名前加上星号 * 即可:
语法
data_type* name;
data_type—— 指针所指向的数据类型name—— 指针变量的名字*—— 也叫“解引用运算符”
示例
int* ptr;
这条语句创建了一个名为 ptr 的指针,它可以存放一个 int 类型变量的地址,读作“指向整型的指针”或“整型指针”。
2.赋值地址(Assign Address)
取地址运算符 & 可以获得任意变量的内存地址。把这个地址赋给指针变量,就完成了指针的初始化。
示例:
int val = 22;
int* ptr = &val;
上面这条语句把 val 的地址存到了指针 ptr 中。
注意:指针的类型必须与被指向的变量类型一致,否则会出现类型不匹配错误。
3.解引用(Dereferencing)
通过指针里保存的地址去“读取或修改”那块内存的值,称为解引用。使用解引用运算符 * 完成。
示例 1:读取指向的值
#include <iostream>
using namespace std;
int main() {
int var = 10;
int* ptr = &var; // ptr 保存 var 的地址
cout << *ptr; // 输出 10(解引用拿到值)
return 0;
}
示例 2:直接访问指针本身
#include <iostream>
using namespace std;
int main() {
int var = 10;
int* ptr = &var;
cout << ptr; // 输出 0x7fffa0757dd4(指针变量里存的地址)
return 0;
}
总结
ptr→ 看到地址*ptr→ 看到地址里的值
4.修改指针的指向
只要类型相同,就可以让指针改去指向另一块内存地址。
示例:
#include <iostream>
using namespace std;
int main() {
int a = 10;
int b = 99;
int* ptr = &a; // 先指向 a
cout << *ptr << endl; // 输出 10
ptr = &b; // 改指向 b
cout << *ptr; // 输出 99
return 0;
}
输出:
10
99
要点:
- 指针变量
ptr里存的地址可以被重新赋值 - 新地址对应的变量类型必须与指针声明类型一致
5.指针的大小
无论指针指向什么类型的数据,同一系统中所有指针变量的大小都相同,与类型无关,只取决于操作系统和 CPU 架构:
- 64 位系统:8 字节
- 32 位系统:4 字节
原因很简单:指针里存的是“内存地址”,而地址的位数由 CPU 架构决定。例如 64 位 CPU 的地址就是 64 位宽。可用 sizeof 验证:
#include <iostream>
using namespace std;
int main() {
int *ptr1;
char *ptr2;
cout << sizeof(ptr1) << endl; // 8
cout << sizeof(ptr2); // 8
return 0;
}
输出:
8
8
(当前为 64 位系统,因此两者都是 8 字节。)
特殊类型的指针
在 C++ 中,有 4 种在不同场景下会提到的特殊指针,第一种是:
1.野指针(Wild Pointer)
当指针变量被创建后没有被初始化,它里面保存的是一个随机的、可能无效的地址,这种指针就叫“野指针”。
示例:
#include <iostream>
using namespace std;
int main() {
int *ptr; // 野指针:未初始化,值随机
return 0;
}
对野指针进行解引用(*ptr)很可能导致运行时错误,如段错误(segmentation fault)。
因此,定义指针后应立即将其初始化为有效地址或置为空指针。
2.空指针(NULL Pointer)
NULL 指针是一个不指向任何有效内存的特殊指针,其值为 NULL。当你暂时不想让它指向任何对象时,可用 NULL 来初始化。
示例:
#include <iostream>
using namespace std;
int main() {
int *ptr = NULL; // 空指针:不指向任何有效地址
return 0;
}
对空指针解引用(*ptr)会引发未定义行为,使用前应先判断其是否为 NULL。
3.void 指针(Void Pointer)
void* 是一种特殊指针,没有关联任何具体数据类型,因此可以保存任意类型的地址,常用于通用编程。
由于编译器不知道它指向的数据到底占多少字节、该如何解释,所以不能直接解引用。
必须先通过显式类型转换把它变成具体类型的指针,再解引用。
示例:
#include <iostream>
using namespace std;
int main() {
int x = 42;
void* ptr = &x; // void 指针保存 int 的地址
// cout << *ptr; // ❌ 错误:不能解引用 void*
// 先强制类型转换再解引用
cout << "void 指针指向的值: " << *(static_cast<int*>(ptr)) << endl; // 输出 42
return 0;
}
4.悬空指针(Dangling Pointer)
悬空指针是指向“已被释放或不再有效”内存的指针。常见产生场景:
- 指针指向的局部变量已离开作用域被销毁
- 动态分配的内存已被
delete,但指针仍保存原地址
解引用悬空指针会导致未定义行为,且这类 bug 往往难以定位。
示例:
#include <iostream>
using namespace std;
int* getPointer() {
int x = 10; // 局部变量
return &x; // ❌ 返回局部地址
}
int main() {
int* ptr = getPointer(); // ptr 变成悬空指针
// cout << *ptr; // 未定义行为,可能崩溃
return 0;
}
指向函数的指针(函数指针)
C++ 中的函数指针用来保存“函数入口地址”,就像普通指针保存变量地址一样。
借助函数指针可以:
- 间接调用函数
- 把函数作为参数传给另一个函数,实现运行时的“动态绑定”与灵活调度。
智能指针(Smart Pointers)
智能指针是对普通指针的封装类,重载了 * 和 -> 等运算符,用起来像原生指针,但额外提供了自动内存管理功能:当对象不再需要时,智能指针会自动释放所占用的堆内存,避免忘记 delete 造成的泄漏。
指针 vs 引用(Pointer vs Reference)
理解 C++ 中指针与引用的区别。两者都能“指代”其他变量,但工作方式和行为截然不同。
| 方面 | 指针 | 引用 |
|---|---|---|
| 初始化 | 指针可以先声明,以后再赋值。 | 引用必须在声明的同时就绑定对象。 |
| 空值 | 指针可以赋 NULL / nullptr。 | 引用不能为空,必须始终绑定有效对象。 |
| 重新绑定 | 指针可随时改指别的对象。 | 引用一旦绑定,终生不可更改。 |
指针的用途
指针是 C++ 中一项强大机制,通过直接操作内存地址,为内存和数据管理提供更高掌控力。主要用途如下:
-
动态内存分配
用new/delete在运行时按需申请或释放对象/数组,大小不必在编译期确定。 -
实现数据结构
构造链表、树、图等复杂结构时,依靠指针把动态分配的节点按需链接起来。 -
按指针传参
把实参的地址传进函数,函数内部通过指针修改,即可改变原始变量的值(效果类似引用,但语法更底层)。