8. 指针基础:指针概念、指针声明与初始化、指针运算、指针与数组、空指针与野指针

指针,是 C++ 里绕不开的一个坎。很多人觉得它难,其实说白了,它就是地址的搬运工。我当年刚学的时候也被绕得晕头转向,后来在项目里被指针坑过几次,才真正摸透了它的脾气。

这一章,咱们就把指针的底裤扒干净。从概念到实战,从声明到运算,一步到位。

8.1 指针到底是什么?

指针,就是一个变量,存的是另一个变量的内存地址。你想想看,我们平时用变量名直接访问数据,那是“直接访问”。而指针呢,它先找到地址,再通过地址找到数据,这叫“间接访问”。

打个比方:你家住在“幸福路 100 号”。你的朋友想来找你,他可以直接导航到“幸福路 100 号”——这就是直接访问。但如果他先找到你邻居,邻居告诉他你家地址,他再过来——这就是指针。

指针本身也有地址,它也是一个变量。只不过它存的内容比较特殊,是别人的地址。

核心概念:

  • 指针是一个变量,存储的是内存地址
  • 通过指针可以间接访问目标数据
  • 指针本身也有自己的内存地址

8.2 指针的声明与初始化

声明指针很简单,用 * 符号。比如:

int* p;      // 声明一个指向 int 的指针
char* cp;    // 声明一个指向 char 的指针
double* dp;  // 声明一个指向 double 的指针

我个人习惯把 * 紧挨着类型名写,比如 int* p。这样读起来就是“int 指针 p”,语义更清晰。当然也有人喜欢 int *p,这都行,团队统一就好。

声明了指针,还得初始化。不然它就是野指针,指向哪里你根本不知道。初始化有两种方式:

int a = 42;
int* p = &a;   // 方式一:取变量 a 的地址

int* q = nullptr;  // 方式二:初始化为空指针

& 是取地址运算符。它能把变量的内存地址取出来,赋值给指针。

小技巧: 我建议你每次声明指针时都初始化,哪怕暂时不知道指向哪里,也先给个 nullptr。这能避免很多莫名其妙的崩溃。

8.3 指针运算

指针也能做运算,但跟普通变量不一样。指针的加减法,是以“指向的类型大小”为步长的。

举个例子:

int arr[] = {10, 20, 30, 40};
int* p = arr;  // p 指向 arr[0]

p++;           // p 现在指向 arr[1]
cout << *p;   // 输出 20

p += 2;        // p 现在指向 arr[3]
cout << *p;   // 输出 40

为什么 p++ 不是加 1 个字节,而是加 4 个字节?因为 int 占 4 个字节。指针知道它指向的是什么类型,所以它会自动跳到下一个元素的位置。

我在项目中遇到过一个问题:有人用 void* 指针做运算,结果编译报错。因为 void 类型的大小是未知的,编译器不知道步长是多少。所以 void* 不能做加减法。

注意: 指针运算只能在数组范围内玩。如果你指向了数组之外的内存,再去做加减法,那就是未定义行为。程序可能崩溃,也可能不崩溃,但结果一定是错的。

8.4 指针与数组

数组名,本质上就是一个指针常量。它指向数组的第一个元素。

int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;   // 等价于 int* p = &arr[0];

cout << arr[2];   // 输出 3
cout << *(p + 2); // 也输出 3

你看,arr[2]*(p + 2) 是完全等价的。编译器会把 arr[2] 翻译成 *(arr + 2)。这就是指针和数组的底层关系。

但有一点要注意:数组名是常量,不能修改。你不能写 arr++,但可以写 p++。因为 p 是变量,arr 是常量。

我曾经在代码里看到有人这么写:

void func(int arr[]) {
    arr++;  // 这其实是可以的,因为函数参数里的 arr 退化成指针了
}

嗯,这里要注意:当数组作为函数参数时,它会退化成指针。所以 int arr[]int* arr 在函数参数里是完全一样的。

8.5 空指针与野指针

这两个概念,是新手最容易踩的坑。我当年也踩过,而且踩得挺惨的。

空指针

空指针就是指向 nullptr 的指针。它明确表示“我指向空”。

int* p = nullptr;
if (p != nullptr) {
    *p = 42;  // 安全:只有 p 非空时才解引用
}

使用空指针前一定要判空。这是好习惯,也是保命符。

野指针

野指针更可怕。它指向的内存是随机的、无效的、或者已经被释放的。你根本不知道它指向哪里。

int* p;        // 未初始化,野指针
*p = 42;       // 危险!不知道写到哪里去了

int* q = new int(10);
delete q;
*q = 20;       // 危险!q 指向的内存已经被释放了

我曾经在项目里遇到过一个 bug:程序运行一段时间后随机崩溃,查了两天才发现是野指针的问题。一个指针被 delete 之后,又被其他地方解引用了。这种 bug 最难定位,因为它不是每次都复现。

避坑指南:

  • 指针声明时一定要初始化,哪怕给 nullptr
  • delete 指针后,立即把它置为 nullptr
  • 解引用前,先判空
  • 不要返回局部变量的地址

8.6 本章知识体系

下面这张图,把指针的核心知识点串起来了。你可以对照着复习:

指针基础 指针概念 存储地址的变量 间接访问数据 声明与初始化 int* p = &a; int* p = nullptr; 指针运算 p++ 步长为 sizeof(类型) p + n 跳 n 个元素 指针与数组 数组名 = 指针常量 arr[i] = *(arr + i) 空指针 nullptr 解引用前必须判空 野指针 未初始化 / 已释放 随机崩溃的元凶 核心:初始化 + 判空 + 不越界

指针这东西,说白了就是一把双刃剑。用好了,能写出高效灵活的代码;用不好,就是给自己挖坑。我建议你多写多练,尤其是多写那些会崩溃的代码,崩溃几次你就记住了。

嗯,这一章就到这里。记住:指针不是洪水猛兽,它只是地址的搬运工。


公众号:蓝海资料掘金营,微信deep3321