指针(上):从概念到实战,我带你一步步吃透
指针,是C++里绕不开的一道坎。很多初学者一听到指针就头疼,觉得它又抽象又难懂。其实说白了,指针就是一个存放地址的变量。你想想看,我们平时用的普通变量,存的是值;指针变量呢,存的是另一个变量的门牌号。今天这一讲,我们就从最基础的概念开始,把指针的方方面面掰开揉碎了讲清楚。
一、指针到底是什么?
我个人习惯把指针比作遥控器。你手里的遥控器本身不发光不发热,但它能控制电视、空调。指针也一样,它本身不存数据,但它知道数据在内存的哪个位置,通过它就能操作那个数据。
举个例子:
int a = 10; // 普通变量,存的是10
int* p = &a; // 指针变量,存的是a的地址
这里 p 就是指针,它里面装的是 a 在内存中的地址。通过 *p 就能拿到 a 的值,也就是10。
& 和 *。& 是取地址,* 是解引用(取地址里的值)。记住一句话:& 是找门牌号,* 是进门拿东西。
二、指针的定义与初始化
定义指针的语法很简单:
类型* 指针名;
比如:
int* p; // 定义一个指向int的指针
double* dp; // 指向double的指针
char* cp; // 指向char的指针
但这里有个坑——未初始化的指针。我刚开始学的时候,就犯过这个错:
int* p; // 只定义,没初始化
*p = 100; // 危险!p指向哪里?不知道!
这种未初始化的指针,我们叫它野指针。它指向的内存地址是随机的,往里面写数据,轻则程序崩溃,重则破坏系统数据。
nullptr。
正确的做法:
int a = 10;
int* p = &a; // 初始化指向a
int* q = nullptr; // 暂时不指向任何对象
三、空指针 nullptr
C++11 引入了 nullptr,专门表示空指针。在它之前,大家用 NULL 或 0,但这两个有歧义。比如:
void func(int);
void func(int*);
func(NULL); // 到底调用哪个?有歧义!
func(nullptr); // 明确调用指针版本
所以,我个人建议:永远用 nullptr,别再用 NULL 了。
使用空指针时要注意:
int* p = nullptr;
if (p == nullptr) {
// 安全,p不指向任何对象
}
// *p = 10; // 错误!解引用空指针是未定义行为
nullptr。这是避免程序崩溃的第一道防线。
四、野指针——我踩过的坑
野指针,就是指向无效内存的指针。产生野指针的常见场景:
- 未初始化:定义后直接使用
- 指向已释放的内存:比如
delete后没置空 - 指向栈上已销毁的变量:函数返回后,局部变量的地址就失效了
我曾经在项目里遇到过这样一个bug:
int* getPtr() {
int x = 100;
return &x; // 危险!x是局部变量,函数结束就销毁了
}
int* p = getPtr();
cout << *p; // 未定义行为!可能输出100,也可能崩溃
这个bug让我排查了整整一个下午。后来我养成了一个习惯:函数返回指针时,一定要确保指针指向的生命周期足够长。要么用 new 在堆上分配,要么传引用参数。
delete 后的指针没置 nullptr。从那以后,我每次 delete 完都立刻写 ptr = nullptr;。
五、指针运算
指针也能做加减运算,但和普通数字的加减不一样。指针加减的是指向类型的大小。
int arr[] = {10, 20, 30, 40};
int* p = arr; // 指向arr[0]
cout << *p; // 输出10
p++; // 移动一个int大小(通常是4字节)
cout << *p; // 输出20
p += 2; // 移动两个int大小
cout << *p; // 输出40
你想想看,为什么 p++ 不是加1个字节,而是加4个字节?因为指针知道它指向的是 int,所以移动一个单位就是移动一个 int 的大小。这就是指针的类型安全。
指针还可以相减:
int* p1 = &arr[0];
int* p2 = &arr[3];
ptrdiff_t diff = p2 - p1; // 结果是3,表示相差3个元素
六、const 与指针
const 和指针的组合,是很多人的噩梦。其实只要记住一句话:const 修饰谁,谁就不能变。
1. 指向常量的指针(pointer to const)
const int a = 10;
const int* p = &a; // p可以改指向,但不能通过p修改a的值
int b = 20;
p = &b; // 可以,p改指向b
// *p = 30; // 错误!不能通过p修改b的值
这种指针,说白了就是只读视角。你可以换着看不同的对象,但不能通过这个指针去修改对象。
2. 常量指针(const pointer)
int a = 10;
int* const p = &a; // p的指向不能变,但可以通过p修改a的值
*p = 20; // 可以,a变成20
// p = &b; // 错误!p是常量,不能改指向
这种指针,就是固定视角。你只能盯着一个对象看,但可以随意修改它。
3. 指向常量的常量指针
const int a = 10;
const int* const p = &a; // 既不能改指向,也不能修改值
这个组合最严格,啥都不能动。
const 在 * 的哪边。const int* — const 在 * 左边,修饰的是指向的对象(对象不可变)int* const — const 在 * 右边,修饰的是指针本身(指针不可变)
七、知识体系总览
下面这张图,把今天讲的内容串了起来。你可以把它当作一个思维导图来用:
八、总结
指针这一块,说白了就是三个核心:
- 地址:指针存的是地址,不是值
- 类型:指针知道它指向什么类型,所以加减运算按类型大小移动
- const:看 const 在 * 的哪边,决定谁不可变
嗯,今天的内容就到这里。指针的下半部分,我们会聊指针和数组的关系、指针作为函数参数、以及更高级的指针用法。但今天这些基础,你一定要亲手敲代码练一练。光看是学不会指针的,真的。