学而时习之:C++中的指针

101 阅读6分钟

C++ 指针

指针是一种特殊变量,它保存的是“另一个变量的内存地址”,而不是直接保存数据值。借助指针,程序可以高效地访问和操作内存,是系统级编程与动态内存管理的关键工具。
直接访问指针时,得到的是地址;要拿到地址上的数据,必须用解引用运算符 *

示例代码:

#include <iostream>
using namespace std;

int main() {
    int var = 10;

    // 声明指针并保存 var 的地址
    int* ptr = &var;

    // 打印值与地址
    cout << "变量值: " << var << endl;           // 10
    cout << "变量地址: " << &var << endl;        // 0x7fffa0757dd4
    cout << "指针存储的地址: " << ptr << endl;   // 同上
    cout << "指针指向的值: " << *ptr << endl;    // 10

    return 0;
}
Value of x: 10
Address of x: 0x7fffa0757dd4
Value stored in pointer ptr: 0x7fffa0757dd4
Value pointed to by ptr: 10

1.创建指针(Create Pointer)

指针的声明方式与普通变量几乎一样,只要在变量名前加上星号 * 即可:

语法

data_type* name;
  • data_type —— 指针所指向的数据类型
  • name —— 指针变量的名字
  • * —— 也叫“解引用运算符”

示例

int* ptr;

这条语句创建了一个名为 ptr 的指针,它可以存放一个 int 类型变量的地址,读作“指向整型的指针”或“整型指针”。

2.赋值地址(Assign Address)

取地址运算符 & 可以获得任意变量的内存地址。把这个地址赋给指针变量,就完成了指针的初始化。

示例:

int val = 22;
int* ptr = &val;

上面这条语句把 val 的地址存到了指针 ptr 中。
注意:指针的类型必须与被指向的变量类型一致,否则会出现类型不匹配错误。

3.解引用(Dereferencing)

通过指针里保存的地址去“读取或修改”那块内存的值,称为解引用。使用解引用运算符 * 完成。

示例 1:读取指向的值

#include <iostream>
using namespace std;

int main() {
    int var = 10;
    int* ptr = &var;   // ptr 保存 var 的地址

    cout << *ptr;      // 输出 10(解引用拿到值)
    return 0;
}

示例 2:直接访问指针本身

#include <iostream>
using namespace std;

int main() {
    int var = 10;
    int* ptr = &var;

    cout << ptr;       // 输出 0x7fffa0757dd4(指针变量里存的地址)
    return 0;
}

总结

  • ptr → 看到地址
  • *ptr → 看到地址里的值

4.修改指针的指向

只要类型相同,就可以让指针改去指向另一块内存地址。

示例:

#include <iostream>
using namespace std;

int main() {
    int a = 10;
    int b = 99;

    int* ptr = &a;     // 先指向 a
    cout << *ptr << endl;  // 输出 10

    ptr = &b;          // 改指向 b
    cout << *ptr;          // 输出 99

    return 0;
}

输出:

10
99

要点:

  • 指针变量 ptr 里存的地址可以被重新赋值
  • 新地址对应的变量类型必须与指针声明类型一致

5.指针的大小

无论指针指向什么类型的数据,同一系统中所有指针变量的大小都相同,与类型无关,只取决于操作系统和 CPU 架构:

  • 64 位系统:8 字节
  • 32 位系统:4 字节

原因很简单:指针里存的是“内存地址”,而地址的位数由 CPU 架构决定。例如 64 位 CPU 的地址就是 64 位宽。可用 sizeof 验证:

#include <iostream>
using namespace std;

int main() {
    int  *ptr1;
    char *ptr2;

    cout << sizeof(ptr1) << endl;  // 8
    cout << sizeof(ptr2);          // 8
    return 0;
}

输出:

8
8

(当前为 64 位系统,因此两者都是 8 字节。)

特殊类型的指针

在 C++ 中,有 4 种在不同场景下会提到的特殊指针,第一种是:

1.野指针(Wild Pointer)

当指针变量被创建后没有被初始化,它里面保存的是一个随机的、可能无效的地址,这种指针就叫“野指针”。

示例:

#include <iostream>
using namespace std;

int main() {
    int *ptr;   // 野指针:未初始化,值随机
    return 0;
}

对野指针进行解引用(*ptr)很可能导致运行时错误,如段错误(segmentation fault)。
因此,定义指针后应立即将其初始化为有效地址或置为空指针

2.空指针(NULL Pointer)

NULL 指针是一个不指向任何有效内存的特殊指针,其值为 NULL。当你暂时不想让它指向任何对象时,可用 NULL 来初始化。

示例:

#include <iostream>
using namespace std;

int main() {
    int *ptr = NULL;  // 空指针:不指向任何有效地址
    return 0;
}

对空指针解引用(*ptr)会引发未定义行为,使用前应先判断其是否为 NULL

3.void 指针(Void Pointer)

void* 是一种特殊指针,没有关联任何具体数据类型,因此可以保存任意类型的地址,常用于通用编程。
由于编译器不知道它指向的数据到底占多少字节、该如何解释,所以不能直接解引用
必须先通过显式类型转换把它变成具体类型的指针,再解引用。

示例:

#include <iostream>
using namespace std;

int main() {
    int x = 42;

    void* ptr = &x;          // void 指针保存 int 的地址

    // cout << *ptr;         // ❌ 错误:不能解引用 void*

    // 先强制类型转换再解引用
    cout << "void 指针指向的值: " << *(static_cast<int*>(ptr)) << endl;  // 输出 42
    return 0;
}

4.悬空指针(Dangling Pointer)

悬空指针是指向“已被释放或不再有效”内存的指针。常见产生场景:

  1. 指针指向的局部变量已离开作用域被销毁
  2. 动态分配的内存已被 delete,但指针仍保存原地址

解引用悬空指针会导致未定义行为,且这类 bug 往往难以定位。

示例:

#include <iostream>
using namespace std;

int* getPointer() {
    int x = 10;        // 局部变量
    return &x;         // ❌ 返回局部地址
}

int main() {
    int* ptr = getPointer(); // ptr 变成悬空指针
    // cout << *ptr;         // 未定义行为,可能崩溃
    return 0;
}

指向函数的指针(函数指针)

C++ 中的函数指针用来保存“函数入口地址”,就像普通指针保存变量地址一样。
借助函数指针可以:

  • 间接调用函数
  • 把函数作为参数传给另一个函数,实现运行时的“动态绑定”与灵活调度。

智能指针(Smart Pointers)

智能指针是对普通指针的封装类,重载了 *-> 等运算符,用起来像原生指针,但额外提供了自动内存管理功能:当对象不再需要时,智能指针会自动释放所占用的堆内存,避免忘记 delete 造成的泄漏。

指针 vs 引用(Pointer vs Reference)

理解 C++ 中指针与引用的区别。两者都能“指代”其他变量,但工作方式和行为截然不同。

方面指针引用
初始化指针可以先声明,以后再赋值。引用必须在声明的同时就绑定对象。
空值指针可以赋 NULL / nullptr引用不能为空,必须始终绑定有效对象。
重新绑定指针可随时改指别的对象。引用一旦绑定,终生不可更改。

指针的用途

指针是 C++ 中一项强大机制,通过直接操作内存地址,为内存和数据管理提供更高掌控力。主要用途如下:

  1. 动态内存分配
    new / delete 在运行时按需申请或释放对象/数组,大小不必在编译期确定。

  2. 实现数据结构
    构造链表、树、图等复杂结构时,依靠指针把动态分配的节点按需链接起来。

  3. 按指针传参
    把实参的地址传进函数,函数内部通过指针修改,即可改变原始变量的值(效果类似引用,但语法更底层)。