C++ 指针

在 C++ 中,普通变量直接保存整数、小数或字符等具体数值。而指针(pointer)是一种特殊的变量,它里面保存的是一个 “内存地址”。

如果我们在程序中不仅需要知道某个变量存放在内存的哪个位置,后续还需要随时更换关联的目标变量,就要用到指针。

C++ 指针是什么?

计算机运行程序时,会把变量存放在内存中,并为每一块内存空间分配一个唯一的编号,这个编号就叫做 “内存地址”。

我们可以把内存想象成一排带门牌号的房间:普通变量是住在房间里的 “人”,内存地址是房间门上的 “门牌号”,而指针变量则是专门用来记录门牌号的 “便签纸”,如下图所示。

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 个字节。小伙伴们不要把 “指针变量自身的大小” 和 “目标变量的大小” 搞混了。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号