在 C++ 中,普通变量直接保存整数、小数或字符等具体数值。而指针(pointer)是一种特殊的变量,它里面保存的是一个 “内存地址”。
如果我们在程序中不仅需要知道某个变量存放在内存的哪个位置,后续还需要随时更换关联的目标变量,就要用到指针。
C++ 指针是什么?
计算机运行程序时,会把变量存放在内存中,并为每一块内存空间分配一个唯一的编号,这个编号就叫做 “内存地址”。
我们可以把内存想象成一排带门牌号的房间:普通变量是住在房间里的 “人”,内存地址是房间门上的 “门牌号”,而指针变量则是专门用来记录门牌号的 “便签纸”,如下图所示。

语法:
类型名* 指针变量名 = &普通变量名;说明:
在使用指针时,我们最常接触到的是 & 和 * 这 2 个运算符:
- 取地址运算符(&):放在普通变量名前面(如
&a),用来获取该变量在内存中的地址。 - 解引用运算符(*):也叫 “间接访问运算符”,放在指针变量名前面(如
*p)。所谓 “解引用”,意思就是顺着指针保存的地址找过去,直接读写对应的目标变量。
这里小伙伴们要特别留意:同一个符号出现在 “声明变量” 和 “使用变量” 时,代表的含义是完全不同的。比如在定义引用时,int& ref = a 里的 & 跟在类型名后面表示声明引用,而 &a 里的 & 放在变量名前面则表示取地址。同样地,int* p 里的 * 表示声明指针变量,后续写 *p 则是顺着地址访问目标变量。
示例 1:定义并使用指针
#include <iostream>
int main() {
int a = 10; // 普通变量
int* p = &a; // 保存 a 的地址
std::cout << "&a = " << &a << '\n';
std::cout << "p = " << p << '\n';
std::cout << "*p = " << *p << '\n';
return 0;
}运行结果如下。
&a = 0x7ffdf3a37cc4
p = 0x7ffdf3a37cc4
*p = 10分析:
int* p = &a; 表示定义了一个整型指针变量 p,并把变量 a 的内存地址赋值给 p。直接访问 p 和使用 &a 拿到的都是变量 a 的地址,因此前两行输出的结果完全相同。第 3 行通过 *p 解引用,顺着地址找到了变量 a,输出它的值 10。
打印出来的 0x7ffdf3a37cc4 是一串以 0x 开头的十六进制数,这是内存地址的常见显示格式。由于内存地址是由操作系统在程序运行时动态分配的,你在自己电脑上运行时看到的具体编号可能与上面不同,但 &a 和 p 输出的地址一定是相同的。
此外,给指针赋值时要注意以下 2 点:① 指针变量的名字是 p 而不是 *p,保存地址时应该赋给 p;② 指针类型必须与目标变量的类型一致。我们可以对比一下正确写法与常见的错误写法。
int a = 10;
double d = 3.14;
// 正确方式
int* p;
p = &a;
// 错误方式 1
int* p1;
*p1 = &a;
// 错误方式 2
int* p2 = a;
// 错误方式 3
int* p3 = &d;在上面的代码中,错误方式 1 误把地址赋给了 *p1,错误方式 2 尝试把普通整数 a 直接当成地址赋给指针,错误方式 3 则尝试使用 int* 指针去保存 double 变量的地址。这 3 种写法都会直接导致编译报错。同时也要记住,在指针尚未指向具体变量之前,千万不能直接写 *p = 20 去修改值。
“指针” 与 “指针变量” 的区别
从严格的概念上区分,“指针” 指的是一个具体的内存地址(如 &a),而 “指针变量” 指的是用来保存这个地址的变量(如 p)。这就好比定义 int year = 2026 时,2026 是整数,而 year 是整型变量。
在日常代码中,我们通常把指针变量命名为 p 或 ptr,其中 ptr 就是英文 “pointer(指针)” 的缩写。同时在实际开发和大多数资料里,大家一般不会刻意区分这两者,而是直接把 “指针变量” 简称为 “指针”,我们根据上下文自然理解即可。
C++ 指针的定义风格
在 C++ 中,指针本身属于一种复合类型。比如 int* 是由 int 和 * 组合而成的类型,表示 “整型指针类型”。我们在定义指针变量时,星号既可以紧贴类型名写成 int* p = &a,也可以紧贴变量名写成 int *p = &a,这两种书写风格在语法上都是合法的。
平时阅读别人的代码时,这两种写法我们都要认得。但在自己编写 C++ 代码时,我们更推荐使用 int* p = &a 这种把星号紧贴类型名的写法。因为写成 int *p = &a 时,初学者容易混淆到底 p 是指针还是 *p 是指针。而写成 int* p = &a,一眼就能看出变量名是 p,它的类型是 int*。
不过采用 int* p 风格时,建议大家养成 “一行只定义一个指针变量” 的习惯。如果在同一行里连续声明多个变量,星号 * 只会作用于第 1 个变量。我们可以对比一下下面这两种写法。
// 推荐
int* p1;
int* p2;
// 不推荐
int* p1, p2;在上面的 “不推荐” 写法中,只有 p1 是 int* 指针,后面的 p2 会被编译器当成普通的 int 整数,并不是指针。把每个指针变量单独写在一行,代码会清晰得多。
C++ 指针的常见操作
操作指针时,我们只需要记住一句话:p 保存的是 “去哪里找(地址)”,而 *p 才是 “顺着地址找到的那个变量”。接下来,我们通过 3 个例子来看看如何通过指针读写变量、更换指针的指向,以及让多个指针指向同一个变量。
示例 2:通过指针读写变量
#include <iostream>
int main() {
int a = 10;
int* p = &a;
*p = 20; // 通过指针修改 a
std::cout << "a = " << a << ", *p = " << *p << '\n';
a = 30; // 直接修改原变量 a
std::cout << "a = " << a << ", *p = " << *p << '\n';
return 0;
}运行结果如下。
a = 20, *p = 20
a = 30, *p = 30分析:
*p = 20; 表示顺着指针 p 保存的地址找到目标变量 a,并将它的值修改为 20。
只要指针 p 指向变量 a,*p 代表的就是变量 a 本身,写 *p = 20 就相当于直接执行 a = 20。同样地,后面执行 a = 30 修改原变量后,再次通过 *p 读取到的结果也会同步变成 30。
示例 3:修改指针的指向
#include <iostream>
int main() {
int a = 10;
int b = 20;
int* p = &a; // 先指向变量 a
std::cout << *p << '\n';
p = &b; // 改为指向变量 b
std::cout << *p << '\n';
return 0;
}运行结果如下。
10
20分析:
在这个例子中,p = &b; 左边没有加星号 *,因此修改的是指针变量 p 自己保存的内存地址。
指针 p 一开始保存的是变量 a 的地址,输出 *p 得到 10。执行 p = &b 后,p 里面保存的地址换成了变量 b 的地址。此时 *p 代表的就是变量 b 了,第 2 次输出也就变成了 20。
注意 我们在定义指针变量时,如果暂时还不确定要让它指向哪个变量,千万不要只写 int* p; 让它空着,而应该先将它初始化为空指针 nullptr(即 int* p = nullptr;)。
示例 4:多个指针指向同一个变量
#include <iostream>
int main() {
int a = 10;
int* p = &a;
int* q = p; // 复制 p 保存的地址
*q = 20; // 通过 q 修改 a 的值
std::cout << "a = " << a << '\n';
std::cout << "*p = " << *p << '\n';
std::cout << "*q = " << *q << '\n';
return 0;
}运行结果如下。
a = 20
*p = 20
*q = 20分析:
int* q = p; 表示把指针 p 里面保存的地址复制一份赋给另一个指针 q,让 p 和 q 同时指向变量 a。执行 *q = 20; 修改数据后,无论是直接访问 a,还是通过 *p、*q 访问,拿到的结果都是 20。
下面代码中,a 的最终值是 10,b 的最终值是 50,小伙伴们可以思考一下为什么。
int a = 10;
int b = 20;
int* p = &a;
p = &b;
*p = 50;C++ 指针变量的大小
我们知道,int 变量通常占用 4 个字节,double 变量占用 8 个字节。那么指向 double 的指针 double*,会不会比 int* 占用更多的内存呢?我们可以使用 sizeof 运算符来测量一下。
示例 5:测量不同类型指针的大小
#include <iostream>
int main() {
char c = 'A';
int n = 10;
double d = 3.14;
char* pc = &c;
int* pn = &n;
double* pd = &d;
std::cout << "sizeof(pc) = " << sizeof(pc) << '\n';
std::cout << "sizeof(pn) = " << sizeof(pn) << '\n';
std::cout << "sizeof(pd) = " << sizeof(pd) << '\n';
return 0;
}运行结果如下。
sizeof(pc) = 8
sizeof(pn) = 8
sizeof(pd) = 8分析:
从运行结果可以看出,无论是 char*、int* 还是 double*,使用 sizeof 测出的大小全部都是 8 个字节。
这是因为不管目标变量本身占用多少个字节,指针变量里面保存的都只是一个 “内存地址编号”。在同一个操作系统环境下,所有内存地址编号的长度都是固定的,比如 64 位系统下固定占 8 个字节,32 位系统下固定占 4 个字节。小伙伴们不要把 “指针变量自身的大小” 和 “目标变量的大小” 搞混了。
