指针入门:指针概念、取地址符、解引用、空指针与野指针
指针,是C++里绕不开的一个坎。说实话,我当年学指针的时候也懵了好一阵子。明明就是个变量,怎么还能存地址?地址又是什么鬼?别急,咱们一步步来拆解。
指针到底是什么?
指针,说白了就是一个存放内存地址的变量。你想想看,普通变量存的是值,比如 int a = 10; 那 a 里面存的就是数字 10。而指针变量呢?它存的是另一个变量在内存中的位置——也就是地址。
我习惯把内存想象成一排带编号的柜子。每个柜子有个编号(地址),里面可以放东西(数据)。普通变量就是直接往柜子里放东西,指针变量则是记下「哪个柜子有我要的东西」。
核心概念:
- 指针变量:存储内存地址的变量
- 地址:内存中每个字节的唯一编号
- 指向:指针变量中存储了某个变量的地址,就说它指向那个变量
取地址符 & 与解引用符 *
这两个符号是操作指针最基础的工具。嗯,这里要注意:& 和 * 在不同上下文里有不同含义,别搞混了。
取地址符 &
& 用在变量前面,作用是取出该变量在内存中的起始地址。我在项目中经常用它来调试,比如打印某个关键变量的地址,看看它是不是被意外改写了。
int age = 25;
cout << "age 的值: " << age << endl; // 输出 25
cout << "age 的地址: " << &age << endl; // 输出类似 0x7ffd5a3b4c 的地址
解引用符 *
* 用在指针变量前面,作用是通过地址访问该地址上存储的数据。说白了就是:顺着地址找到那个柜子,把里面的东西拿出来。
int age = 25;
int* ptr = &age; // ptr 存的是 age 的地址
cout << *ptr << endl; // 输出 25,因为 *ptr 就是 age 的值
*ptr = 30; // 通过指针修改 age 的值
cout << age << endl; // 输出 30
你看,通过 *ptr 我们不仅能读取 age,还能直接修改它。这就是指针的威力——间接访问。
我的小技巧:声明指针时,我习惯把 * 紧挨着变量名写,比如 int* ptr; 这样一眼就能看出「ptr 是一个指向 int 的指针」。但有些人喜欢 int *ptr; 写法,两种都行,保持统一就好。
空指针 nullptr
空指针,就是不指向任何有效对象的指针。C++11 之后推荐用 nullptr 来表示空指针,而不是老式的 NULL 或 0。
int* ptr = nullptr; // 明确表示 ptr 不指向任何东西
if (ptr == nullptr) {
cout << "ptr 是空指针,不能解引用" << endl;
}
// 错误做法:对空指针解引用会导致程序崩溃
// cout << *ptr << endl; // 千万别这么干!
为什么要用 nullptr 而不是 NULL?因为 nullptr 是类型安全的,它有自己的类型 std::nullptr_t,不会和整数 0 混淆。我记得有一次重构老代码,发现一堆用 NULL 的地方,结果重载函数匹配出了问题,排查了半天。从那以后,新代码一律用 nullptr。
避坑指南:我曾经在项目里见过这样的代码——声明指针后没有初始化,然后直接拿来用。结果程序时而正常时而崩溃,查了两天才发现是野指针的问题。记住:声明指针时立即初始化,要么给有效地址,要么给 nullptr。
野指针——程序员的噩梦
野指针,就是指向已释放内存或无效地址的指针。它比空指针危险得多,因为空指针至少还能检查,野指针你根本不知道它指向哪里。
野指针常见的三种来源:
- 未初始化:声明了指针但没有赋值
- 指向已释放的内存:用 delete 释放后没有置空
- 指向栈上已销毁的局部变量:函数返回后,局部变量的地址就失效了
// 野指针示例 1:未初始化
int* p1; // 野指针!p1 的值是随机的
// *p1 = 10; // 危险!可能写入非法内存
// 野指针示例 2:释放后未置空
int* p2 = new int(42);
delete p2; // 释放内存
// p2 现在成了野指针
p2 = nullptr; // 好习惯:释放后立即置空
// 野指针示例 3:返回局部变量地址
int* badFunc() {
int local = 100;
return &local; // 危险!local 在函数结束后就销毁了
}
黄金法则:
- 指针声明时一定要初始化
- delete 后立即将指针置为 nullptr
- 绝不返回局部变量的地址
- 解引用前先检查是否为空指针
指针与内存布局
为了让你更直观地理解指针的工作方式,我画了一张图。这张图展示了变量、地址和指针之间的关系。
指针的常见用法
指针在实际项目中用得最多的场景,我总结了一下:
| 场景 | 说明 | 示例 |
|---|---|---|
| 函数参数传递 | 通过指针修改函数外部的变量 | void swap(int* a, int* b) |
| 动态内存分配 | 用 new 在堆上创建对象 | int* p = new int(42); |
| 数组操作 | 数组名就是首元素指针 | int arr[5]; int* p = arr; |
| 多态与虚函数 | 基类指针指向派生类对象 | Base* b = new Derived(); |
我的建议:刚开始学指针,别急着搞复杂用法。先把 & 和 * 搞明白,多写几个小程序练手。我当年就是拿纸笔,把每个变量的地址和值画出来,慢慢就通了。记住一句话:指针就是地址,地址就是编号。
总结
指针是C++里最基础也最强大的工具之一。它让你能直接操作内存,但也带来了风险。空指针和野指针是新手最容易踩的坑,但只要养成好习惯——声明时初始化、释放后置空、解引用前检查——就能避开大部分问题。
嗯,指针这东西,说白了就是个地址。你把它想成门牌号,就简单多了。门牌号本身不稀奇,稀奇的是你能通过它找到那扇门,打开它,看到里面的东西。这就是指针的魅力。
公众号:蓝海资料掘金营,微信deep3321