在解决 LeetCode.707 设计链表 时,我们实现了一个链表。但在这个过程中,很多 C++ 的基础概念可能会让人感到困惑:
struct到底怎么写才规范?C 语言里的typedef去哪了?LinkNode* dummy;只是一个定义,它如何变成一个真正的节点?- 为什么在函数里可以直接用
dummy,不需要重新定义? - 为什么用了
new,就必须配上一个~MyLinkedList()析构函数?
本文将围绕这些问题,简单学习一下相关概念。
一、 为什么不用 typedef 了?
在 C 语言中,我们通常这样写:
// C 语言风格
typedef struct Node {
int val;
struct Node* next;
} Node;
这是因为在 C 中,struct Node 是一种类型说明,要直接使用 Node 作为类型名,需要用 typedef 创建一个别名。
但在 C++ 中,这个过程被大大简化了!
当定义一个 struct 或 class 时,它的名字直接就成为了一个新的类型名。
// C++ 风格
struct LinkNode {
int val;
LinkNode* next;
// ... 构造函数 ...
};
// 现在可以直接使用 LinkNode 作为类型,无需 typedef
LinkNode* my_node;
这就是为什么在现代 C++ 代码中,很少再看到 C 风格的 typedef struct 写法。
二、从指针定义到实体节点
在 MyLinkedList 类中,我们首先定义了一个成员变量:LinkNode* dummy;。
理解这一步至关重要:这只是一个指针!
你可以把它想象成一个**“门牌号地址簿”**。dummy 就是地址簿里的一个条目,它现在是空的,没有记录任何地址。它本身不是一个“房子”(节点),只是一个准备用来记录房子地址的变量。
那么,“房子”是在哪里建的呢? 答案就在 MyLinkedList 的构造函数里:
MyLinkedList() {
dummy = new LinkNode(-1, nullptr);
}
这行代码做了两件大事:
new LinkNode(-1, nullptr):new关键字就像一个“建筑队”。它会在一块叫做**“堆 (heap)”**的特殊内存区域中,建造一个新的LinkNode房子。dummy = ...: 建筑队建好房子后,会返回这栋房子的地址。我们用赋值操作符=,将这个地址郑重地记录在我们的dummy地址簿里。
从此刻起,dummy 这个指针就指向了一个真实存在的、活生生的节点对象。
三、构造函数 —— 对象的“出厂设置”
new LinkNode(-1, nullptr) 不仅创建了对象,还调用了 LinkNode 的构造函数。
构造函数是 struct 或 class 的一个特殊函数,负责在对象创建时进行初始化,也就是进行“出厂设置”。
struct LinkNode{
LinkNode* next;
int val;
// 这就是构造函数
LinkNode(int v, LinkNode* n) : val(v), next(n) {}
};
让我们来解读这个构造函数 LinkNode(int v, LinkNode* n) : val(v), next(n) {}:
LinkNode(int v, LinkNode* n): 这是函数签名,表示它接受一个int和一个LinkNode*作为参数。: val(v), next(n): 这是成员初始化列表,是 C++ 中最高效、最标准的初始化方式。它的意思是:“请直接用参数v来初始化成员val,用参数n来初始化成员next”。{}: 这是构造函数的函数体,因为所有初始化工作都在列表中完成了,所以这里是空的。
所以,new LinkNode(-1, nullptr) 的完整含义是:“在堆上创建一个 LinkNode 对象,并调用它的构造函数,将其 val 初始化为 -1,将其 next 指针初始化为 nullptr”。
四、为什么在函数里可以直接用 dummy?
因为 dummy 是 MyLinkedList 的一个成员变量,它属于这个类的每一个实例对象。当你在 addAtHead 等成员函数中写下 dummy->next 时,你是在说:“请拿出本对象的 dummy 地址簿,找到它记录的那个房子的地址,然后访问那个房子的 next 房间”。
你不需要在每个函数里重新定义它。dummy 节点作为整个链表的“导航起点”或“哨兵”,自始至终都存在于 MyLinkedList 对象的生命周期中。
五、为什么用了 new 就必须配上析构函数?
回到我们的比喻:new 是在“堆”这块公共土地上盖的房子。这块地是“三不管”地带,系统不会自动帮你拆除。如果你盖了房子(new),却在离开时(对象销毁时)不告诉建筑队去拆掉它(delete),这个房子就会永远地、废弃地立在那里,占用土地。这就是内存泄漏 (Memory Leak)。
析构函数 ~MyLinkedList() 就是你与 C++ 签订的“拆迁协议”。
~MyLinkedList(): 这个函数会在MyLinkedList对象被销毁前自动被调用。这是你最后的机会去清理你手动申请的资源。delete curr;: 这就是“拆迁”指令。它会释放curr指针指向的那块堆内存,把土地还给系统。
~MyLinkedList() {
LinkNode* curr = dummy;
while (curr) {
LinkNode* next = curr->next; // 1. 先保存下一栋房子的地址
delete curr; // 2. 拆掉当前的房子
curr = next; // 3. 移动到下一栋房子
}
}
这个循环确保了从 dummy 开始,整个链表上的每一个节点都被安全地拆除,完美地履行了“有借有还”(new/delete)的C++ 契约。
总结
structin C++: 直接定义新类型,无需typedef。- 指针 vs. 实体:
LinkNode* dummy是地址簿,new LinkNode()才是盖房子。 - 构造函数: 对象的出厂设置,推荐使用成员初始化列表。
- 成员变量: 属于类的每一个对象,在所有成员函数中共享。
- 内存契约: 每一次
new都必须有一次delete与之对应,而析构函数正是履行这个契约的最佳场所。

被折叠的 条评论
为什么被折叠?



