C++ 入门教程(六):指针与引用

1869 字
9 分钟
C++ 入门教程(六):指针与引用

本篇目标#

指针是 C++ 的分水岭:理解了它,才算真正理解”程序如何在内存中运行”。本篇从内存模型讲起,覆盖裸指针、动态内存、引用对比与智能指针入门。请逐个示例亲手运行——这一篇没有捷径。

一、内存模型:变量住在哪里#

int age = 18;

这行代码发生的事:程序在栈内存里划出 4 字节,存入 18,并把这块空间标记为 age。

每块内存都有编号——地址(一串十六进制数)。& 取地址运算符可以拿到它:

#include <iostream>
int main() {
int age = 18;
std::cout << &age << "\n"; // 0x7ffee4c0a8bc(每次运行可能不同)
std::cout << sizeof(age); // 4 字节
}

类比:内存是一整条街的房间,变量是房间里的住户,地址是门牌号。

二、指针:存地址的变量#

定义与解引用#

int age = 18;
int* p = &age; // p 是指针,存的是 age 的地址
std::cout << p << "\n"; // 地址:0x7ffee4c0a8bc
std::cout << *p << "\n"; // 18:* 解引用——顺着地址找到值
std::cout << &p << "\n"; // p 自己的地址(指针也是变量)
*p = 20; // 通过地址改写 age!
std::cout << age << "\n"; // 20

两个符号记牢:

  • &x:取 x 的地址
  • *p:顺 p 存的地址取值(或写入)
int* p1, p2; // ⚠ 坑:p1 是指针,p2 是 int!
int *p3, *p4; // 两个都是指针

空指针与安全#

int* p = nullptr; // C++11 空指针(替代 NULL 与 0)
if (p != nullptr) {
std::cout << *p; // 用前判空
}
// *p; // ❌ 解引用空指针 = 段错误(Segfault),程序崩溃
Warning

永远不要解引用空指针或未初始化的指针(野指针)。现代写法:定义指针就初始化(给值或给 nullptr)。

指针作为函数参数#

C 风格的”修改实参”方式(C++ 更推荐引用,但必须会读):

void swap(int* a, int* b) { // 接收地址
int t = *a; // 通过地址取值
*a = *b; // 通过地址写值
*b = t;
}
int main() {
int x = 1, y = 2;
swap(&x, &y); // 传地址
std::cout << x << "," << y; // 2,1
}

指针与数组#

数组名本质是”首元素地址”:

int arr[] = {10, 20, 30, 40};
int* p = arr; // 等价于 &arr[0]
std::cout << *p << "\n"; // 10
std::cout << *(p + 1) << "\n"; // 20:指针算术,+1 移动一个 int
std::cout << p[2] << "\n"; // 30:指针也能用下标(等价 *(p+2))
// 遍历
for (int* q = arr; q != arr + 4; ++q)
std::cout << *q << " ";

指针算术:p + 1 移动的字节数 = 指向类型的大小(int* 移 4 字节,double* 移 8 字节)。

三、动态内存:new / delete#

栈上的变量函数结束自动销毁;堆内存由你手动管理——能跨函数存活、大小运行期决定。

// 单个变量
int* p = new int(42); // 堆上开 4 字节,初始化 42
std::cout << *p << "\n";
delete p; // 手动释放!
p = nullptr; // 置空好习惯
// 动态数组(大小可以是变量!)
int n;
std::cin >> n;
int* arr = new int[n]{}; // n 个 int 全 0
for (int i = 0; i < n; ++i) arr[i] = i * i;
delete[] arr; // 数组用 delete[]

两条铁律:

  1. 每个 new 必须对应一个 delete(数组 delete[])
  2. delete 后指针置 nullptr,防止”悬空指针”再次使用

内存泄漏:忘记 delete#

void leak() {
int* p = new int(100);
// 函数结束,p 这个"门牌号"没了,但堆上 4 字节还占着——永远无法释放
} // 💥 内存泄漏
void worse() {
int* p = new int(1);
p = new int(2); // 第一个内存块地址丢失,泄漏!
delete p;
}

长期运行的服务(服务器程序)泄漏累积会耗尽内存——这是裸 new/delete 逐渐被弃用的原因。

四、引用 vs 指针#

维度引用 int& r = x指针 int* p = &x
本质变量的别名存地址的变量
初始化必须初始化,不可换绑可空、可改指向
空值不存在nullptr
用法直接当原变量用需 * 解引用
安全性高低(野指针/泄漏)
int x = 10;
int& r = x; // r 就是 x 的第二个名字
r = 99; // x 变 99
// int& bad; // ❌ 引用必须初始化
int* p = &x; // p 指向 x
p = nullptr; // ✓ 指针可以为空
int y = 5;
p = &y; // ✓ 指针可换指向

选择:能用引用就用引用(安全简洁);需要”可能为空”或”改指向”才用指针。

五、智能指针(现代 C++ 的答案)#

C++11 用智能指针把 new/delete 交给对象自动管理——RAII:资源获取即初始化,析构自动释放。

unique_ptr:独占所有权#

#include <memory>
#include <iostream>
int main() {
// make_unique 创建(C++14)
std::unique_ptr<int> p = std::make_unique<int>(42);
std::cout << *p << "\n"; // 像普通指针一样用
// 离开作用域自动 delete,无需也无法手动 delete
// auto q = p; // ❌ unique 不可拷贝(独占)
auto q = std::move(p); // ✓ 只能转移所有权(p 变空)
if (!p) std::cout << "p 已交出\n";
std::cout << *q << "\n";
} // q 析构 → 自动释放内存

shared_ptr:共享所有权(计数引用)#

#include <memory>
auto a = std::make_shared<int>(100); // 引用计数 = 1
{
auto b = a; // 计数 = 2,可以拷贝
std::cout << *b << "\n";
} // b 销毁,计数 = 1
std::cout << a.use_count() << "\n"; // 1
// a 销毁时计数归零 → 内存自动释放

选型:默认 unique_ptr;确实多方持有才 shared_ptr。

六、实战:链表节点(指针的第一次真实应用)#

#include <iostream>
struct Node { // 结构体第八篇详讲,先看用法
int data;
Node* next; // 指向下一个节点——链表的骨架
};
int main() {
// 用 new 造三个节点
Node* head = new Node{1, nullptr};
head->next = new Node{2, nullptr}; // -> 通过指针访问成员
head->next->next = new Node{3, nullptr};
// 遍历
for (Node* cur = head; cur != nullptr; cur = cur->next)
std::cout << cur->data << " -> ";
std::cout << "null\n";
// 逐个释放(防泄漏)
Node* cur = head;
while (cur != nullptr) {
Node* next = cur->next; // 先记住下一个
delete cur; // 再释放当前
cur = next;
}
}

这个例子同时展示了:堆分配、-> 运算符、指针串联结构、手动释放顺序。以后用 std::unique_ptr<Node> next 重写它,就是现代 C++ 风格。

小练习#

  1. 写 int* find_max(int* arr, int n) 返回最大元素的地址(不是值)
  2. 用 new 申请一个 double 数组存 10 个成绩,输出平均分后正确 delete
  3. 验证”值传递不改原值、指针与引用传递改原值”:同一功能写三个版本对比
  4. 用 unique_ptr 管理一个动态 int 数组(std::unique_ptr<int[]> arr{new int[5]{}}),体会自动释放
  5. (概念题)int* p = new int(5); delete p; std::cout << *p; 会发生什么?(悬空指针,未定义行为——说说为什么不能这么写)

本篇小结#

  • & 取地址、*p 解引用;指针存地址,是”携带门牌号的变量”
  • 数组名≈首元素地址,指针算术按类型大小移动
  • new/delete 手动管理堆:一一配对、delete 后置空、防泄漏
  • 引用是必须初始化的别名,优先用引用;可为空/换指向才用指针
  • 现代 C++ 用 unique_ptr/shared_ptr 自动管理内存(RAII)
  • -> 是”指针访问成员”的语法糖

下一篇:结构体与枚举——把相关数据打包成自定义类型。


本系列为 Cyber先锋社原创教程。指针是 C++ 的门槛也是魅力:内存模型想通了,后面豁然开朗。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

C++ 入门教程(六):指针与引用
https://www.cyberpioneer.club/posts/cpp-tutorial-06/
作者
Cyber先锋社
发布于
2026-10-29
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Cyber先锋社
探索信息技术的先锋阵地
公告
欢迎来到 Cyber先锋社 官方博客!本站刚刚创建上线,内容持续更新中,欢迎收藏关注。
分类
标签
最新动态
站点统计
文章
39
分类
6
标签
30
总字数
34,248
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Cloudflare Workers
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录