C++指针应用:从基础操作到链表归并排序
1. 指针基础:内存的导航系统
1.1 什么是指针?
指针是C++中一种特殊的变量,它存储的不是实际的数据值,而是另一个变量的内存地址。可以将指针理解为内存的导航系统或地址纸条。
int x = 10; // 实际存储数据的变量
int* ptr = &x; // 指针变量,存储x的地址

在 C 和 C++ 中,直接打印指针变量本身,输出的结果是否为 x 的地址,取决于你使用的编程语言以及打印方式——核心区别在于“是否指定了正确的格式化规则”。
ptr直接打印会是什么内容?
1)C 语言中:直接打印需用 %p 占位符
C 语言中没有“自动识别指针并打印地址”的机制,必须通过 printf 函数配合 %p 格式化占位符,才能正确输出指针存储的地址(即 x 的地址)。
1. 正确用法(输出 x 的地址)
使用 %p 明确告诉 printf:“待打印的变量是指针,需要输出它存储的内存地址”。
#include <stdio.h>
int main() {
int x = 10;
int* ptr = &x; // ptr 存储 x 的地址
// %p 占位符:输出 ptr 存储的地址(即 x 的地址)
printf("ptr 存储的地址(x 的地址):%p\n", ptr);
printf("直接取 x 的地址:%p\n", &x); // 与上面输出完全一致
return 0;
}
输出结果(地址是内存中的随机位置,仅作示例):
ptr 存储的地址(x 的地址):0x7ffd8b2b7a2c
直接取 x 的地址:0x7ffd8b2b7a2c
2) 错误用法(不会输出 x 的地址)
如果误用其他占位符(如 %d 打印整数、%c 打印字符),会导致“未定义行为”——输出的是无意义的数字,而非 x 的地址。
// 错误示例:用 %d 打印指针
printf("错误用法(%d):%d\n", ptr); // 输出随机数字,不是 x 的地址
3 )C++ 中:两种打印方式的区别
C++ 兼容 C 语言的 printf,同时提供了 cout 输出流。两种方式对指针的处理不同:
1. 用 cout 直接打印(输出 x 的地址)
C++ 的 cout 会自动识别指针类型,直接打印指针变量存储的内存地址(无需额外占位符)。
#include <iostream>
using namespace std;
int main() {
int x = 10;
int* ptr = &x;
// cout 直接打印指针:输出 x 的地址
cout << "ptr 存储的地址(x 的地址):" << ptr << endl;
cout << "直接取 x 的地址:" << &x << endl; // 与上面输出完全一致
return 0;
}
输出结果(示例):
ptr 存储的地址(x 的地址):0x7ffd8b2b7a2c
直接取 x 的地址:0x7ffd8b2b7a2c
2. 用 printf 打印(同 C 语言,需 %p)
如果在 C++ 中混用 printf,仍需遵循 C 语言的规则,用 %p 才能正确输出地址。
// C++ 中用 printf 打印指针
printf("ptr 存储的地址:%p\n", ptr); // 正确,输出 x 的地址
关键总结
| 编程语言 | 打印方式 | 是否输出 x 的地址? |
核心原因 |
|---|---|---|---|
| C | printf("%p", ptr) |
是 | %p 专门用于输出指针存储的地址 |
| C | printf("%d", ptr)等 |
否 | 占位符与指针类型不匹配,行为未定义 |
| C++ | cout << ptr |
是 | cout 自动识别指针,输出地址 |
| C++ | printf("%p", ptr) |
是 | 兼容 C 语言的格式化规则 |
简单说:在正确使用打印规则的前提下(C 用 %p,C++ 用 cout 或 %p),直接打印指针变量 ptr,输出的就是 x 的地址——因为 ptr 的本质就是“存储 x 地址的变量”。

1.2 基本指针操作
| 操作 | 语法 | 含义 | 比喻 |
|---|---|---|---|
| 取地址 | &var |
获取变量地址 | 记录房子门牌号 |
| 声明指针 | Type* ptr |
声明指向Type的指针 | 准备一张地址纸条 |
| 解引用 | *ptr |
访问指针指向的值 | 按地址找到房子 |
| 成员访问 | ptr->member |
访问指针所指对象的成员 | 进入房子查看房间 |
1.3 指针与对象的关系
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
ListNode node(10); // 创建节点对象
ListNode* ptr = &node; // 指针指向该节点
// 三种等价的访问方式:
node.val = 20; // 直接访问
ptr->val = 20; // 通过指针访问(推荐)
(*ptr).val = 20; // 解引用后访问
关键理解:无论用哪种方式,操作的都是同一个内存位置!
2. 链表归并排序中的指针应用
2.1 算法框架
自底向上的归并排序是理解指针应用的完美案例:
ListNode* sortList(ListNode* head) {
if (!head || !head->next) return head;
// 计算链表长度
int length = 0;
ListNode* curr = head;
while (curr) {
length++;
curr = curr->next;
}
ListNode dummy(0); // 虚拟头节点
dummy.next = head;
// 自底向上归并
for (int step = 1; step < length; step *= 2) {
ListNode* prev = &dummy; // 连接指针
curr = dummy.next; // 处理指针
while (curr) {
// 分割和合并操作
ListNode* left = curr;
ListNode* right = split(left, step);
curr = split(right, step);
ListNode* merged = mergeTwoLists(left, right);
prev->next = merged;
// 移动prev到末尾
while (prev->next) {
prev = prev->next;
}
}
}
return dummy.next;
}
2.2 三大关键指针的作用
| 指针 | 作用 | 比喻 |
|---|---|---|
dummy |
固定的头节点锚点 | 大地基,永远标记起点 |
prev |
移动的连接器 | 胶水,负责连接各个段落 |
curr |
移动的分割器 | 剪刀,负责切割待处理段落 |
3. 核心子函数:指针操作的精华
3.1 分割函数 split()
ListNode* split(ListNode* head, int step) {
if (!head) return nullptr;
ListNode* curr = head;
// 移动到第step个节点(或链表末尾)
for (int i = 1; i < step && curr->next; i++) {
curr = curr->next;
}
ListNode* rest = curr->next; // 保存剩余部分
curr->next = nullptr; // 切断链表
return rest;
}
指针操作解析:
curr遍历链表,找到分割点curr->next = nullptr执行实际的分割操作- 返回剩余部分的头指针
示例:
原链表: 1→2→3→4→5→null
调用 split(head, 2) 后:
left部分: 1→2→null
返回: 3→4→5→null
3.2 合并函数 mergeTwoLists()
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode dummy(0); // 虚拟头节点
ListNode* curr = &dummy; // 移动的构建指针
while (l1 && l2) {
if (l1->val < l2->val) {
curr->next = l1; // 连接l1节点
l1 = l1->next; // 移动l1指针
} else {
curr->next = l2; // 连接l2节点
l2 = l2->next; // 移动l2指针
}
curr = curr->next; // 移动构建指针
}
curr->next = l1 ? l1 : l2; // 连接剩余部分
return dummy.next; // 返回实际头节点
}
指针操作解析:
dummy提供稳定的起始点curr逐步构建新链表l1和l2遍历两个输入链表- 最后返回
dummy.next获取真正的头节点
关键技巧:curr 开始时指向 dummy,通过 curr->next 修改 dummy.next,然后移动 curr 继续构建。
4. 常见问题与调试技巧
4.1 常见指针错误
-
野指针:未初始化的指针
ListNode* ptr; // ❌ 危险! ptr->val = 10; // 💥 段错误 -
空指针解引用:
ListNode* ptr = nullptr; cout << ptr->val; // 💥 运行时错误 -
内存泄漏:
ListNode* ptr = new ListNode(10); // 忘记 delete ptr;
4.2 调试建议
- 可视化绘图:在纸上画出链表和指针位置
- 使用调试器:单步执行,观察指针值的变化
- 添加打印语句:
cout << "curr at: " << curr << ", value: " << (curr ? curr->val : -1) << endl;
5. 总结与最佳实践
5.1 指针使用原则
-
总是初始化指针:
ListNode* ptr = nullptr; // ✅ 安全 -
优先使用栈对象:
ListNode dummy(0); // ✅ 自动内存管理 -
明确指针所有权:谁创建,谁释放
5.2 链表操作模式
// 模式1:虚拟头节点 + 移动指针
ListNode dummy(0);
ListNode* curr = &dummy;
// ... 构建操作 ...
return dummy.next;
// 模式2:双指针技巧
ListNode* slow = head;
ListNode* fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
参考文献:【指针】内含超多图解!通俗易懂!(入门必看!)
参考文献:关于指针的基本知识,什么是指针?(小白版)
更多推荐

所有评论(0)