指针入门:指针概念、指针声明与初始化、指针与数组、指针与函数、空指针与野指针

指针,是C++里绕不开的一个坎。说实话,我当年学指针的时候也懵了好一阵子。但等你真正理解了,你会发现它其实没那么玄乎。说白了,指针就是一个存放地址的变量。你想想看,我们平时用的普通变量存的是值,而指针变量存的是另一个变量的地址。就这么简单。

指针的概念

每个变量在内存中都有个门牌号,也就是地址。指针就是用来记录这个门牌号的。通过指针,我们可以间接地访问和修改那个地址里存的数据。嗯,这里要注意:指针本身也有自己的地址,它存的内容是别人的地址。

核心理解: 指针就是地址的载体。它让你能绕过程序中变量的名字,直接通过地址去操作内存。

指针的声明与初始化

声明指针很简单,在类型后面加个星号就行。比如 int* p; 表示 p 是一个指向整型数据的指针。我个人习惯把星号紧挨着类型名写,这样读起来更像“这是一个整型指针”。当然也有人喜欢挨着变量名写,这都行,关键是团队里统一。

初始化指针,就是给它一个合法的地址。千万别用未初始化的指针,那是给自己挖坑。

int a = 42;
int* p = &a;   // p 现在存的是变量 a 的地址

// 也可以先声明,再赋值
int* q;
q = &a;        // 这样也行,但注意 q 之前是未初始化的

我在项目中遇到过有人忘了初始化指针就直接用,结果程序跑起来莫名其妙地崩溃。查了半天才发现是野指针的问题。所以,声明指针的同时就给它赋值,这是个好习惯。

指针与数组

数组和指针的关系非常紧密。数组名其实就是一个指向数组首元素的指针常量。你可以用指针来遍历数组,这比用下标有时候更灵活。

int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;   // 等价于 int* p = &arr[0];

// 用指针访问数组元素
for (int i = 0; i < 5; ++i) {
    // *(p + i) 等价于 p[i],也等价于 arr[i]
    std::cout << *(p + i) << " ";
}

你想想看,p[i]*(p + i) 在编译器眼里其实是一回事。但用指针加减的方式,能让你更清楚地感受到内存地址的连续排列。我曾经用指针操作数组时,不小心把指针加过头了,访问到了数组边界之外的数据,结果读出了一堆乱码。嗯,这就是越界,要小心。

小技巧: 用指针遍历数组时,可以定义一个指向数组末尾的指针作为哨兵,这样循环条件更清晰。

int* end = arr + 5;
for (int* p = arr; p != end; ++p) {
    std::cout << *p << " ";
}

指针与函数

指针可以作为函数参数传递。这样做的好处是,函数内部可以直接修改外部变量的值,而不需要返回值。说白了,就是传地址而不是传值。

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5, y = 10;
    swap(&x, &y);
    // 现在 x=10, y=5
    return 0;
}

我建议你在需要修改外部变量时,优先考虑用指针或引用。引用是C++里更安全的替代方案,但指针在某些场景下(比如动态数组、回调函数)仍然不可替代。

另外,函数也可以返回指针。但要注意:永远不要返回局部变量的地址!因为局部变量在函数结束后就被销毁了,返回的指针会成为野指针。

警告: 不要返回指向局部变量的指针。如果你需要返回指针,请确保它指向的是动态分配的内存(用 new 申请的),或者静态/全局变量。

空指针与野指针

空指针就是指向地址0的指针。在C++11之后,我们通常用 nullptr 来表示空指针。它明确表示“这个指针不指向任何有效对象”。

int* p = nullptr;  // 现代C++推荐写法
if (p != nullptr) {
    // 安全地使用 p
}

野指针就麻烦多了。它指向的内存地址是随机的、无效的,或者已经被释放的。使用野指针会导致未定义行为,程序可能崩溃,也可能产生奇怪的结果。

我曾经在维护一个老项目时,发现一个bug:程序运行一段时间后随机崩溃。查了两天才定位到,原来是一个指针在释放后没有置为nullptr,后续代码又用它写了数据。嗯,从那以后我每次释放内存后,都会立刻把指针置为nullptr。

避坑指南: 我曾经因为野指针问题熬过夜。记住三条铁律:

  • 指针声明时立即初始化,要么给有效地址,要么给nullptr。
  • 释放内存后,立即将指针置为nullptr。
  • 使用指针前,总是检查它是否为nullptr。

知识体系图:指针的核心脉络

指针 (Pointer) 概念:地址的载体 声明与初始化 指针与数组 指针与函数 空指针与野指针 int* p = &a; 数组名即首地址 传地址,改外部变量 nullptr 与 未定义行为

上面这张图把指针的核心知识点串了起来。你可以看到,指针的概念是基础,然后衍生出声明初始化、与数组结合、与函数结合,以及最需要警惕的空指针和野指针问题。我个人觉得,把这五个点吃透了,指针这块就算入门了。

总结一下

指针没那么可怕。它就是一个存地址的变量,仅此而已。但因为它直接操作内存,所以用起来要格外小心。记住:初始化、判空、释放后置空。这三步做好了,野指针就离你远远的。

我在实际项目中,经常用指针来操作动态数组、实现多态、或者作为函数参数传递大对象(避免拷贝开销)。你慢慢也会发现,指针用好了,代码会变得非常灵活高效。

个人建议: 刚开始学指针时,多画内存布局图。把每个变量、每个指针的地址和值画出来,你就不会晕了。我当年就是这么过来的。


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