这是 C++ 最核心、也最让新手头疼的一章。但别怕,我们用"门牌号"的类比,一步步讲到彻底理解。掌握它,你就跨过了 C++ 最大的门槛。
8.1 内存地址:数据的"门牌号"
第 3 章说过,内存像一个大储物柜,每个格子有地址(门牌号)。
intx=42;这句做了两件事:找一个空格子,把 42 放进去。这个格子的门牌号(地址)就是&x能取到的值。
取地址运算符&可以得到变量的地址:
intx=42;std::cout<<&x<<std::endl;// 打印 x 的地址,形如 0x7ffd3a2b...地址是一串十六进制数,指向内存中的某个位置。
8.2 指针:专门存地址的变量
指针(pointer)就是一个存地址的变量。
intx=42;int*p=&x;// p 是"指向 int 的指针",存的是 x 的地址拆解int* p = &x;:
int*:类型,表示"指向 int 的指针"p:变量名&x:取出 x 的地址
现在p里存着 x 的门牌号。指针就是门牌号的副本。
解引用*:通过门牌号找到房间
有了门牌号,怎么进去看/改房间里的东西?用解引用运算符*:
intx=42;int*p=&x;std::cout<<*p<<std::endl;// 42,*p 就是"p 指向的那个值"*p=100;// 通过指针修改 xstd::cout<<x<<std::endl;// 100,x 被改了*p读作"p 指向的内容"。通过*p,可以读写 x,就像直接操作 x 一样。
&和*是一对反操作:
&x:拿到 x 的地址(问"x 住哪")*p:拿到地址 p 指向的值(问"这个门牌号住的是谁")
一张图记住
变量 x ── 存着 42 变量 p ── 存着 x 的地址 ──→ 指向 x *p === x 的值(42)8.3 为什么需要指针
你可能想问:直接用变量不就行了,为什么要绕一圈用指针?原因有几个:
- 在函数里修改外部的值(见 8.5)
- 避免复制大数据:把一个巨大的数组/矩阵传给函数时,传地址比复制整个数据快得多
- 动态分配内存(第 11 章)
- 实现复杂数据结构(链表、树等)
这些场景,后面都会遇到。现在先掌握指针的基本操作。
8.4 空指针 nullptr
指针如果不指向任何东西,应该给它赋nullptr(空指针),表示"不指向任何地方":
int*p=nullptr;// 空指针访问空指针是严重错误(程序崩溃)。所以解引用前要检查:
if(p!=nullptr){std::cout<<*p<<std::endl;// 安全}8.5 指针与函数:真正修改外部变量
第 6 章说值传递改不了外部变量。用指针就能改:
#include<iostream>voidaddOne(int*p){*p=*p+1;// 通过指针,修改 p 指向的那个变量}intmain(){intx=5;addOne(&x);// 传入 x 的地址std::cout<<x;// 6,x 被真正修改了}8.6 引用:更优雅的"别名"
指针好用但*、&写起来繁琐易错。C++ 提供了引用(reference)——给变量起别名:
intx=42;int&r=x;// r 是 x 的引用(别名),r 和 x 是同一个东西r=100;std::cout<<x;// 100,通过 r 改 x引用和指针的区别:
| 指针 | 引用 | |
|---|---|---|
| 语法 | int* p = &x;用*p访问 | int& r = x;直接用r |
| 能否为空 | 可以为nullptr | 不能为空,必须绑定一个变量 |
| 能否改指向 | 能指向别的变量 | 一旦绑定,终身不改 |
| 可读性 | 繁琐 | 更简洁 |
引用的本质:就是变量的另一个名字,操作r就是操作x。
8.7 引用参数:函数里改外部变量(推荐写法)
用引用传参,函数里就能改外部变量,而且语法干净:
#include<iostream>voidswap(int&a,int&b){// 引用参数inttmp=a;a=b;b=tmp;}intmain(){intx=1,y=2;swap(x,y);std::cout<<x<<" "<<y;// 2 1,交换成功}8.8 常量引用:只读 + 高效
传大对象时,既想"不复制"(高效),又不想让函数修改它,就用常量引用:
voidprint(conststd::string&s){// const + &// s 可以读,但不能改std::cout<<s<<std::endl;}const std::string&读作:“一个字符串的只读引用”。这是 C++ 里极其常见的写法,Eigen 里到处都是(如const MatrixXd&),务必熟悉。
记忆口诀:看到
const X&,就理解为"传了个地址进去,但保证不改它,既快又安全"。
8.9 指针的指针、指针与数组(了解)
指针可以指向指针(多级指针),数组名其实是指向首元素的指针:
inta[3]={10,20,30};int*p=a;// 数组名 a 就是首元素地址std::cout<<*p;// 10std::cout<<p[1];// 20,指针也能用下标这些是底层细节,初学了解即可,后面用std::vector会更省心。
8.10 小结
- 指针
int* p存地址;&x取地址,*p解引用。 - 指针能改外部变量、避免复制、动态分配内存。
- 引用
int& r是别名,不能为空、不能改绑。 - 常量引用
const T&:只读 + 高效,Eigen 里极常用。 - 空指针
nullptr,解引用前先判空。
下一章,学习结构体与类——把相关的数据打包成一个整体。
练习题
- 声明
int x = 10; int* p = &x;,通过*p把 x 改成 20,打印验证。 - 写函数
increment(int& n)用引用参数让外部变量加 1。 - 写函数
print(const std::string& s)打印字符串,体会常量引用。 - 用指针写
swap,交换两个 int 的值。 - 说说引用和指针的三点区别。