Re:从零开始的 C++ 入門篇(五)类和对象·第二篇:构造函数与析构函数

Re:从零开始的 C++ 入門篇(五)类和对象·第二篇:构造函数与析构函数

◆ 博主名称: 晓此方-ZEEKLOG博客

大家好,欢迎来到晓此方的博客。

⭐️C++系列个人专栏:

此方带你玩转C++_晓此方的博客-ZEEKLOG博客

 ⭐️踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰


0.1概述&前言

        从本文会开始,此方会为大家带来类的默认成员函数的内容。该方面是C/C++类和对象篇章最难以理解的部分,构造函数和析构函数分别取代了C语言的Init函数和destory函数,大大提升了运行效率。默认成员函数的学习将为后续内容打下深厚的基础,本文讲解深入骨髓,细节无微不至,希望看完后能让你对这两者有深入的认识。


一,类的默认成员函数

定义:

     默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。

一个类,我们不写的情况下编译器会默认生成以下6个默认成员函数:

       需要注意的是这6个中最重要的是前4个。最后两个取地址重载不重要,我们稍微了解一下即可。其次就是C++11以后还会增加两个默认成员函数,移动构造和移动赋值,这个我们后面再讲解。默认成员函数很重要,也比较复杂,我们要从两个方面去学习:

  • 第一:我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求。
  • 第二:编译器默认生成的函数不满足我们的需求,我们需要自己实现,那么如何自己实现?

二,构造函数

2.1构造函数的定义

构造函数是特殊的成员函数

2.1.1构造函数的主要任务:

        构造函数的主要任务并不是开空间创建对象(我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象

2.1.2构造函数的本质:

        构造函数的本质是要替代我们以前写的Init函数的功能,构造函数自动调用的特点就完美的替代了Init。

2.2特性与创建

2.2.1构造函数的特性函数名与类名相同。无返回值。(返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)构造函数可以重载

2.2.2构造函数的创建

class Date { public: Date () { _year=0; _month=0; _day=0; } Date (int year,int month,int day) { _year=year; _month=month; _day=day; } private: int _year; int _month; int _day; } 
  1. 在类Date中创建了一个构造函数Date,构造函数的名称和类的名称一致。
  2. 构造函数Date没有返回类型也没有返回值。
  3. 构造函数Date()和Date (int year,int month,int day)构成重载。

2.3构造函数的调用

对象实例化的时候,构造函数会自动调用对应的构造函数

1,调用无参数的构造函数,直接创建Date类的时候自动调用了构造函数

2,调用有参的构造函数后面要加括号。和传递的参数值。

2.3.1以上面的的类举例

int mian() { Date d1; Date d2(2025,12,21); return 0; }
  1. Date 类型实例化一个对象d1。此时自动调用第一个无参构造函数。
  2. Date类型实例化一个对象d2.此时实自动传递擦参数2025、12、21.调用第二个构造函数。

Tips:无参数构造函数的调用不可以在后面加括号,原因:为了防止与函数声明产生歧义。

Date Func();

    2.4默认构造函数

    2.4.1全缺省构造函数

    Date (int year=2025,int month=2,int day=20) { _year=year; _month=month; _day=day; }
    1. 全缺省构造函数的调用不需要括号
    2. 全缺省构造函数不能和无参构造函数同时存在,会产生调用歧义
    3. 全缺省构造函数的使用一定程度上取代了上面的两个构造函数

    2.4.2自动生成默认构造函数

            如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

    定义:

            无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。

    Tips:常见错误写法与报错类型:

     Date (int year,int month,int day) { _year=year; _month=month; _day=day; } //省略class等代码 int main() { Date d1; return 0; }

    手动添加了一个非默认构造函数,防止了编译器自动生成默认构造函数。引发报错:

    总结一下:不传递实参的构造函数就是默认构造函数。(这个概念特别容易混淆。

    2.5构造函数对不同的类型

    说明:

            C++把类型分成内置类型和自定义类型。内置类型就是语言提供的原生数据类型,如:int/char/double/指针等,自定义类型就是我们使用class/struct等关键字自己定义的类型。

    2.5.1对内置类型

    先说结论:

            我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求。也就是说是否初始化是不确定的,看编译器。(实际上这里很乱。C++标准没有规定)
    • 早期的编译器是都不初始化的(也就是对内置类型的成员,生成的是一个随机值)
    • 后期的编译器有些会初始化,或者在有些场景下会初始化。

    既然如此,为了提升代码的可移植性:我们都把他们当成内置类型不处理的情况。

    测试:VS2019,VS2022:其他编译器也可以尝试一下。

    2.5.2对自定义类型

    先说结论:

            对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错。

    举例:这里有一个栈

    class Stack{ public: Stack(int n = 4){ _a = (STDataType*)malloc(sizeof(STDataType) * n); _capacity = n; _top = 0; } // ... private: STDataType* _a; size_t _capacity; size_t _top; }; 

    双栈实现队列:

    class MyQueue{// 两个Stack实现队列 public: //编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化 private: Stack pushst; Stack popst; }; int main(){ MyQueue mq; return 0; }

            这里就不需要显示的写MyQueue的构造函数,创建mq对象时对 pushst和popst先后调用Stack的默认构造函数。实现初始化:

           如果stack的默认构造函数不存在,则会报错:现在我们没办法解决这个问题,在后面的文章中再讲:初始化列表。【必须注意:这里是指默认构造函数,也就是说,自定义类型对象有非默认构造函数的时候一样会报错】

    2.5.3混合特殊情况

            这里让内置类型和自定义类型混合,初始化了内置类型。这是C++标准和编译器的问题。所以这个size是一个大坑,因为C++标准没有规定,有的编译器不会处理,问题就大了。因此size的初始化构造函数得自己写。

    2.6总结

            大多数情况下,构造函数都要我们自己写,少数情况下,类似Myqueue,构造函数会自动调用。

    二,析构函数

    2.1析构函数定义

            析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,

            C++规定对象在销毁时(声明周期结束)会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比我们之前Stack实现的Destroy功能。

    2.1.1为什么析构函数不是完成对对象本身的销毁

    对象存在栈帧,函数结束栈帧销毁并释放

    int main() { Myqueue mq; Stack ST; return 0; }

           对象本身的空间在main函数的栈帧里面。main结束后栈帧一块儿销毁,所以对象本身的空间的释放和销毁不是析构函数完成的。

    2.1.2没有空间申请的类不需要析构函数

    如日期类:

    class Date { public: //………………… private: int _year; int _month; int _day; } 

    成员变量都是内置类型,不需要手写析构函数,同时编译器自动生成的析构函数也没什么用

    2.2析构函数的创建

    1. 在构造函数的前面加上“~”:“~”在C语言中是按位取反,有“与构造函数相反”的意味。
    2. 无参数无返回值。(也不需要void)

    2.2.1以栈举例

    int main() { Stack st1; Stack st2; return 0; }
    ~Stack() { free(_a); _a=nullptr; _top=_capacity=0; }

    2.3析构函数的特性

    相对于destory,最大的特性:自动

    1. 对象声明周期结束,系统会自动调用默认的析构函数。
    2. 有资源申请的类需要写析构函数。
    3. 有多个对象需要析构的时候后创建的先析构。因为栈帧后进先出。

    2.3.1以上面的栈类型析构举例

    ~Stack() { free(_a); _a=nullptr; _top=_capacity=0; }

            执行到达return语句,声明周期结束,开始调用析构函数。析构函数先后调用两次,为st2和st1析构。

    调试结果:

    Tips:调试技巧,在类里面想要看成员变量,可以直接输入this。

    2.4编译器的默认操作

    1. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。默认生成的析构函数不会管内置类型。
    2. 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理自定类型成员会调用他的析构函数(如果没有找到会报错)。

    2.5对自定义类型的特殊规定

            需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。

    // 两个Stack实现队列 class MyQueue { public: ~MyQueue() { free(str); str=nullptr; } private: Stack pushst; Stack popst; int *str; }

            首先,编译器会去调用析构函数    ~MyQueue()。(调用    ~MyQueue()并不是因为有str指针)然后再会去调用栈类型的析构函数。

            所以对于自定义类型的成员,不写也好,写也好,他都会去调用这个自定义成员的自己的析构函数。目的是防止内存泄漏。

    2.4总结

               如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;如果默认生成的析构就可以用,也就不需要显示写析构,如MyQueue;但是有资源申请时,一定要自己写析构,否则会造成资源泄漏,如Stack。

    Read more

    “现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

    “现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

    大模型仍未对上商业的齿轮? 编译 | 王启隆 来源 | youtu.be/aWqfH0aSGKI 出品丨AI 科技大本营(ID:rgznai100) 现在的硅谷,空气里都飘着一股“再不上车就晚了”的焦躁感。 最近 OpenClaw 风头正旺,强势登顶 GitHub,终结了 React 神话,许多人更是觉得“AI 自己干活赚钱”的日子就在明天了。 特别是在斯坦福商学院(GSB)这种地方,台下坐着的都是成天琢磨怎么用下一个技术风口搞个独角兽出来的狠人。 微软的首席科学官(CSO)Eric Horvitz 被请到了这个几乎全美最想用 AI 变现的礼堂里。作为从上世纪 80 年代就开始搞 AI 的绝对老炮、也是微软技术底座的“扫地僧”,这位老哥并没有顺着台下的胃口,去吹捧下个月大模型又要颠覆什么行业,而是兜头给大家浇了一盆带点学术味的冷水。 他讲了一个挺有画面感的比喻:大家都在聊

    By Ne0inhk
    Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

    Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

    整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 当大模型能在几秒钟内生成一段“看起来像那么回事”的补丁时,开源社区却开始付出另一种代价。 最近,开源游戏引擎 Godot 的核心维护团队公开吐槽:他们正被大量“AI 生成的低质量代码”淹没。那些代码往往结构完整、注释齐全、描述洋洋洒洒,但真正的问题是——提交者可能并不理解自己交上来的内容。 这件事,并不是简单的“有人偷懒用 AI 写代码”。它正在触及开源协作最核心的东西:信任。 一场悄无声息的“AI 洪水” 事情的导火索来自一条 Bluesky 讨论帖。 Godot 主要维护者之一、同时也是 Godot 商业支持公司 W4 Games 联合创始人的 Rémi Verschelde 表示,所谓的“AI slop”

    By Ne0inhk
    诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

    诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

    当宇宙级的“嘴炮”遇到降维打击。 编译 | 王启隆 来源 | youtu.be/l6ZcFa8pybE 出品丨AI 科技大本营(ID:rgznai100) 打开最新一期知名播客 StarTalk 的 YouTube 评论区,最高赞的一条留言是这样写的: “我长这么大,第一次看到尼尔·德葛司·泰森(Neil deGrasse Tyson)在一档节目里几乎全程闭嘴,像个手足无措的小学生一样乖乖听讲。” 作为全美最知名的天体物理学家,泰森平时的画风是充满激情、喋喋不休、用宇宙的宏大来震撼嘉宾。但这一次,坐在他对面的那位满头银发、带着温和英音的英国老人,仅仅用最平淡的语气,就让整个演播室陷入了数次令人窒息的沉默。 这位老人是 Geoffrey Hinton。深度学习三巨头之一,2024 年诺贝尔物理学奖得主,被公认为“AI 教父”。 对经常阅读 Hinton 演讲的我来说,这也是比较新奇的一幕—

    By Ne0inhk
    48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

    48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

    整理 | 苏宓 出品 | ZEEKLOG(ID:ZEEKLOGnews) 「仅过了 48 小时,一笔 8.2 万美元的天价费用凭空出现,较这家小型初创公司的正常月费暴涨近 46000%。」 这不是假设的虚幻故事,而是一家墨西哥初创公司正在经历的真实危机。 近日,一位名为 RatonVaquero 的开发者在 Reddit 发帖求助称,由于他的 Gemini API 密钥被盗用,原本每月仅约 180 美元(约 1242 元)的费用,在短短 48 小时内暴涨到 82,314.44 美元(约 56.8 万元)。对于这家只有三名开发者的小型创业团队来说,这笔突如其来的账单,几乎等同于灭顶之灾。 “我现在整个人都处在震惊和恐慌之中。”RatonVaquero

    By Ne0inhk