C++ 结构体、构造函数与内存管理简析

在解决 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++ 中,这个过程被大大简化了!

当定义一个 structclass 时,它的名字直接就成为了一个新的类型名

    // 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);
    }

这行代码做了两件大事:

  1. new LinkNode(-1, nullptr): new 关键字就像一个“建筑队”。它会在一块叫做**“堆 (heap)”**的特殊内存区域中,建造一个新的 LinkNode 房子。
  2. dummy = ...: 建筑队建好房子后,会返回这栋房子的地址。我们用赋值操作符 =,将这个地址郑重地记录在我们的 dummy 地址簿里。

从此刻起,dummy 这个指针就指向了一个真实存在的、活生生的节点对象。

三、构造函数 —— 对象的“出厂设置”

new LinkNode(-1, nullptr) 不仅创建了对象,还调用了 LinkNode构造函数

构造函数是 structclass 的一个特殊函数,负责在对象创建时进行初始化,也就是进行“出厂设置”。

    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

因为 dummyMyLinkedList 的一个成员变量,它属于这个类的每一个实例对象。当你在 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++ 契约。

总结

  • struct in C++: 直接定义新类型,无需 typedef
  • 指针 vs. 实体: LinkNode* dummy 是地址簿,new LinkNode() 才是盖房子。
  • 构造函数: 对象的出厂设置,推荐使用成员初始化列表
  • 成员变量: 属于类的每一个对象,在所有成员函数中共享。
  • 内存契约: 每一次 new 都必须有一次 delete 与之对应,而析构函数正是履行这个契约的最佳场所。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值