C++ 指针与引用:底层内存访问核心

一、引言:为什么指针是 C++ 的 "灵魂"

在前面四篇文章中,我们已经掌握了 C++ 的基础语法、数据类型、流程控制与函数模块化。我们能够写出结构清晰、功能完整的程序,但这一切都停留在变量层面------ 我们只知道 "变量可以存数据",却不知道数据在内存中究竟如何存放、如何访问、如何传递。

指针,正是打开计算机底层世界的钥匙。

指针是 C/C++ 最强大、最核心、最具魅力,也最让初学者头疼的概念。可以说: 不懂指针,就不算真正掌握 C++。 不会指针,就无法理解内存、数组、函数、对象、STL、动态内存......

指针的作用极其强大:

  1. 直接访问内存地址,操控硬件底层;
  2. 高效传递数据,避免拷贝,提高程序性能;
  3. 实现动态内存管理(new/delete);
  4. 构建复杂数据结构:链表、树、图;
  5. 在函数中修改变量本身(而非副本);
  6. 操作数组、字符串、二进制数据;
  7. 实现高级特性:多态、回调、泛型。

本篇文章将从零开始,由浅入深,彻底讲透 C++ 指针与引用:

  • 内存地址与指针的本质;
  • 指针的定义、赋值、解引用;
  • 空指针、野指针、迷途指针;
  • 指针与数组、指针与字符串;
  • 指针与函数:地址传递、指针参数;
  • 引用的本质、用法、与指针的区别;
  • 指针的 const 用法;
  • 指针常见错误、安全规范、实战案例。

本篇内容偏底层、偏难,但极其重要。请静下心阅读、动手练习,一旦掌握指针,你的 C++ 水平将直接跃升到新的高度。


二、内存地址:一切指针的基础

计算机的内存可以看作由无数个连续小房间组成的巨大仓库 ,每个小房间是 1 字节(8 位),每个房间都有一个唯一编号,这个编号就是内存地址

在 C++ 中:

  • 变量存储在内存中;
  • 每个变量都有地址
  • 地址用十六进制表示,如:0x7ffeefb2c8ac。

我们可以用 & 符号获取变量地址:

cpp

运行

复制代码
int a = 10;
cout << &a << endl;  // 输出 a 的地址

指针 = 专门存放内存地址的变量。 指针变量存储的不是普通数值,而是另一个变量的地址。


三、指针的定义与基本使用

(一)指针定义格式

cpp

运行

复制代码
类型 *指针变量名;

含义:

  • 类型:指针指向的变量的数据类型;
  • *:表示这是一个指针变量;
  • 指针变量名:存储地址。

示例:

cpp

运行

复制代码
int a = 10;
int *p = &a;   // p 指向 a 的地址

(二)解引用:*p 访问指针指向的值

*p 表示:取指针 p 所指向地址里的数据。

cpp

运行

复制代码
int a = 10;
int *p = &a;

cout << p;    // 输出 a 的地址
cout << *p;   // 输出 a 的值:10

(三)通过指针修改变量值

cpp

运行

复制代码
*p = 20;   // 相当于 a = 20;

指针让我们通过地址直接修改变量本身


四、指针的四个核心概念

1. 指针本身是变量,占内存空间

32 位系统:指针占 4 字节 64 位系统:指针占 8 字节

cpp

运行

来源:ixn2014.com/BKDTY

来源:ixn2014.com/XRVDG

来源:ixn2014.com/BPZXW

来源:ixn2014.com/GLUIK

来源:ixn2014.com/LDNDB

来源:ixn2014.com/PHAMA

来源:ixn2014.com/LDNRX

来源:ixn2014.com/VOXCD

来源:ixn2014.com/GEQRK

来源:ixn2014.com/FMBMC

复制代码
cout << sizeof(p);  // 输出 8(64位)

2. 指针必须指向有效地址

未初始化的指针非常危险!

3. 空指针 nullptr(C++11 推荐)

表示指针 "不指向任何地方"。

cpp

运行

复制代码
int *p = nullptr;

访问空指针会直接崩溃!

4. 野指针(最危险)

指向未知内存地址的指针。 产生原因:

  • 指针未初始化;
  • 指针指向的变量已销毁;
  • 指针被错误赋值。

野指针是程序崩溃的第一大元凶!


五、指针与数组

数组名本质上是指向数组首元素的常量指针

cpp

运行

复制代码
int arr[5] = {10,20,30,40,50};
int *p = arr;   // p 指向 arr[0]

数组访问两种方式

  1. 下标法:arr[i]
  2. 指针法:*(p+i)

cpp

运行

复制代码
cout << *(arr+2);   // 输出 arr[2] = 30

这就是为什么数组下标从 0 开始:因为偏移量从 0 计算。


六、指针与字符串

C 风格字符串本质是char 指针

cpp

运行

复制代码
const char *str = "Hello";
cout << str;    // 输出 Hello
cout << *str;   // 输出 H

字符串以 \0 结尾。


七、指针与函数:地址传递(重点)

C++ 默认函数参数是值传递:拷贝一份,不影响原变量。

如果希望在函数内部修改外部变量 ,必须使用指针传递(地址传递)

示例:交换两个数(经典)

cpp

运行

复制代码
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(&x, &y);   // 传地址
    cout << x << " " << y; // 20 10
    return 0;
}

只有传地址,才能修改原变量! 这是指针最常用、最重要的功能之一。


八、引用:C++ 对指针的安全封装

(一)什么是引用

引用是变量的别名。 一旦绑定,不能再改变指向。

定义格式:

cpp

运行

复制代码
类型 &别名 = 原变量;

示例:

cpp

运行

复制代码
int a = 10;
int &b = a;  // b 是 a 的别名

b = 20;      // a 也变成 20

(二)引用的特点

  1. 必须初始化
  2. 不能为空
  3. 不能重新绑定到其他变量
  4. 底层是指针,使用更安全、更简单

(三)引用做函数参数(推荐)

引用可以实现地址传递的效果,但语法更简洁

cpp

运行

复制代码
void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x=10,y=20;
    swap(x,y);
}

无需传地址,代码更清晰! 现代 C++ 优先使用引用,少用指针。


九、指针 vs 引用:最重要对比

表格

特性 指针 引用
能否为空 能(nullptr) 不能
能否重新指向 不能
语法 需要 * 直接使用
底层实现 地址变量 编译器优化的指针
安全性 较低(易崩溃)
效率 同样高

结论:能用引用,就不用指针。 需要动态分配、空值、复杂操作时,才用指针。


十、const 与指针(三种情况)

1. 指向常量的指针

cpp

运行

复制代码
const int *p = &a;
// *p 不能改,p 可以改

2. 常量指针

cpp

运行

复制代码
int *const p = &a;
// p 不能改,*p 可以改

3. 指向常量的常量指针

cpp

运行

复制代码
const int *const p = &a;
// 都不能改

口诀: const 右边的东西不能改。


十一、多级指针(指针的指针)

指针可以存储另一个指针的地址。

cpp

运行

复制代码
int a = 10;
int *p = &a;
int **pp = &p;   // 二级指针

cout << **pp;  // 输出 10

多级指针常用于:

  • 二维数组
  • 链表、树
  • 函数参数修改指针

十二、指针常见错误(崩溃重灾区)

1. 操作空指针

cpp

运行

复制代码
int *p = nullptr;
*p = 10;  // 崩溃!

2. 野指针

cpp

运行

复制代码
int *p;   // 未初始化
*p = 10;  // 崩溃!

3. 指针越界

cpp

运行

复制代码
int arr[5];
int *p = arr+10;
*p = 10; // 越界!

4. 重复释放、释放栈内存

cpp

运行

复制代码
int *p = new int(10);
delete p;
delete p;  // 崩溃!

5. 混淆 * 和 &

cpp

运行

复制代码
int a=10;
int *p = a;  // 错误!应该是 &a

十三、指针安全使用规范

  1. 指针初始化优先使用 nullptr
  2. 每次使用指针前判断是否为空;
  3. 避免野指针;
  4. 动态内存必须配对:new/delete;
  5. 尽量用引用代替指针;
  6. 少用多级指针;
  7. 数组不要越界;
  8. 现代 C++ 尽量使用智能指针(后续讲解)。

十四、经典实战案例

案例 1:指针实现数组求和

cpp

运行

来源:sz12xx.cn/XCNHZ

来源:sz12xx.cn/HVECU

来源:sz12xx.cn/PQMUU

来源:sz12xx.cn/VYXGC

来源:sz12xx.cn/ATZDS

来源:sz12xx.cn/XWWGN

来源:sz12xx.cn/GGLEH

来源:sz12xx.cn/WMJJP

来源:sz12xx.cn/INNWT

来源:sz12xx.cn/HDILA

复制代码
int sum(int *arr, int len) {
    int s = 0;
    for (int i=0; i<len; i++) {
        s += *(arr+i);
    }
    return s;
}

案例 2:引用实现成绩修改

cpp

运行

复制代码
void setScore(int &score) {
    score = 90;
}

案例 3:指针实现字符串长度

cpp

运行

复制代码
int strLen(const char *s) {
    int len = 0;
    while (*s != '\0') {
        len++;
        s++;
    }
    return len;
}

十五、本章总结

本篇文章彻底、系统、深入讲解了 C++指针与引用,这是 C++ 最核心、最底层、最关键的知识点:

  1. 内存地址是指针的基础;
  2. 指针是存储地址的变量;
  3. *p 解引用访问地址中的值;
  4. 空指针、野指针是崩溃主要原因;
  5. 指针与数组、字符串本质相同;
  6. 指针传递可修改外部变量;
  7. 引用是安全别名,现代 C++ 优先使用;
  8. const 指针、多级指针、安全规范。

掌握指针,你就真正理解了 C++ 的内存模型。 掌握指针,你才能真正进入高级 C++ 开发。

指针是 C++ 从 "入门" 走向 "精通" 的必经之路。 学完本篇,你已经具备学习面向对象、动态内存、STL、数据结构的基础。


下一篇预告

第 6 篇《C++ 面向对象:类与对象、封装、构造与析构》(4000 字以上) 我们将正式进入 C++ 最核心的编程范式:面向对象编程(OOP)。