【C++】类和对象之构造函数、析构函数、拷贝构造函数(二)

前言:在上一篇我们对于C++中类和对象有了一个初步的了解,今天我们将进一步的学习,今天我们目标是对构造函数、析构函数、拷贝构造函数进行一个初步学习在后面也会进一步的学习,一起加油呐!

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


目录标题

  • 类和对象的默认成员函数
    • 构造函数
      • 默认构造函数(无参构造函数)
      • 有参构造函数
    • 析构函数
      • 析构函数的销毁顺序
    • 拷贝构造函数
        • 默认拷贝函数


类和对象的默认成员函数

构造函数

构造函数的概念:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

构造函数的特点
1.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
2. 名字与类名相同,可以有参数,但是不能有返回值(连void也不行)
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
5. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
6. C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

默认构造函数(无参构造函数)

构造函数的引入
我们在C语言数据结构阶段像学习链表还有栈和队列的时候,每次手撕的时候总是要自己对它初始化一次,我们甚至经常的会忘记去初始化,因此我们的默认构造函数就来了。
就像下面这个例子,我们在每次使用的时候,总是要自己主动的对其进行初始化,在写代码的时候尤其代码非常长的时候我们总是会忘记对其进行初始化
话不多说,我们直接来一个默认构造函数的代码演示:

class Date
{
public:Date()//无参默认构造{_year = 2024;_month = 1;_day = 31;}void Print(){cout << "year = " << this->_year << " month = " << this->_month << " day = " << this->_day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Print();return 0;
}

在这里插入图片描述

看了上面这个例子,我们可以知道如果我们定义了一个构造函数,那么我们就不必在对其进行初始化了。


我们在来看下面这个例子,我们不自己定义一个默认构造函数,让编译器自己定义,看看运行结果会是什么样的。

class Date
{
public:void Print(){cout << "year = " << this->_year << " month = " << this->_month << " day = " << this->_day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Print();return 0;
}

在这里插入图片描述

看到这里怎么全是一个随机值,这里很多同学就会有疑问了?:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的
默认构造函数并没有什么用??这里我就要给大家解释解释了:
C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

这里可以给大家总结成几句话:

  1. 对于基本类型(int/char…)默认函数是不会对它进行初始化
  2. 对于自定义类型(class/struct/union)
  3. C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

例子演示

class Date
{
public:void Print(){cout << "year = " << this->_year << " month = " << this->_month << " day = " << this->_day << endl;}
private:int _year = 2024;//在声明的时候给予默认值int _month = 1;int _day = 31;
};int main()
{Date d1;d1.Print();return 0;
}

在这里插入图片描述


注意事项:
调用无参初始化时不能加上括号:加上后与函数声明无法区分开如:d1.Date()报错


有参构造函数

有参构造函数概念:C++中的有参构造函数是一种特殊的构造函数,它接收参数并用这些参数来初始化对象的成员变量。有参构造函数可以使对象在创建时就具有初始值,而不必在创建后再进行赋值操作。
实例演示:

class Date
{
public:void Print(){cout << "year = " << this->_year << " month = " << this->_month << " day = " << this->_day << endl;}Date(int year,int month = 1,int day = 30)//有参默认构造{_year = year;//对其进行初始化_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024);//像缺省参数一样使用d1.Print();return 0;
}

在这里插入图片描述


注意事项:
对有参构造函数使用全缺省可以代替无参构造函数,此时虽然二者依然构成重载,但是初始化对象不知道调用哪一个。此时只需要一个全缺省构造函数就可以。虽然语法上允许它们们两个可以同时存在,但是如果有对象定义去调用就会报错,所以我们就留下一个就好。
在这里插入图片描述作者这里强烈推荐各位用全缺省参数和半缺省参数,因为是真的非常非常好用!


析构函数

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?正如我们很多人在学习数据结构的时候哪怕我们都记得经常的去初始化一个栈、队列、链表,但是我们很多朋友都会忘记去销毁这个队列、栈、链表等等,因此我们的C++祖师爷就引入了析构函数这个概念。
析构函数概念:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特性

  1. 析构函数的命名规则与类的名称相同,前面加上一个波浪号(~)例如:类名为:Date 则析构函数的名称为~Date
  2. 析构函数没有返回值,也没有参数。
  3. 析构函数的定义在类的声明中,通常放在公有部分的最后。
  4. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
  5. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

实例演示

class Date
{
public:void Print(){cout << "year = " << this->_year << " month = " << this->_month << " day = " << this->_day << endl;}Date(int year,int month = 1,int day = 30)//有参默认构造{_year = year;//对其进行初始化_month = month;_day = day;}~Date()//定义一个析构函数{cout << "hello destruction func" << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024);d1.Print();return 0;
}

在这里插入图片描述

在这里我们会发现,我们并没有去主动的调用这个析构函数,但是函数结束的时候依然去主动调用了这个函数。对象本身的销毁是在销毁函数栈帧的时候随之销毁,不是析构函数的事,析构函数只是在这个销毁的时候被调用去做一些资源清理工作。Date类没有资源需要清理,所以它不实现析构函数都是可以的。但是上述代码实现了它还是会调用,因此我们可以知道当对象的生命周期结束时,析构函数会自动调用。


析构函数的销毁顺序

当一个类的对象被销毁时,会先调用该类的析构函数,然后再依次调用该对象中成员变量的析构函数。具体的销毁顺序如下:

  1. 静态成员变量的析构函数按照它们的声明顺序的逆序进行销毁。
  2. 非静态成员变量按照它们在类中声明的顺序的逆序进行销毁。

我们来看下面着例子,是先析构的d1还是d2

class Date
{
public:void Print(){cout << "year = " << this->_year << " month = " << this->_month << " day = " << this->_day << endl;}Date(int year,int month = 1,int day = 30)//有参默认构造{_year = year;//对其进行初始化_month = month;_day = day;}~Date()//定义一个析构函数{cout << "hello destruction func" << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024);Date d2(2023);//d1.Print();return 0;
}

根据我们刚刚总结的销毁顺序可以知道,先定义的后销毁,因此d2先销毁,然后才是d1
在这里插入图片描述


我们再来看一个例题,来帮助我们理解:请问下面这个程序是有输出结果还是没有?还是报错呢?

class Time
{
public:
~Time()
{cout << "~Time()" << endl;
}
private:int _hour;int _minute;int _second;
};
class Date
{private:
// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

程序运行结果:
在这里插入图片描述
代码分析:

  1. 因为main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,_day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。
  2. 但是main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁。
  3. main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数。
  4. 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

最后我们再来看一个关于析构函数的例题展示了析构函数销毁顺序的特点:请问该程序的输出结果是什么呢?

#include <iostream>class Base {
public:Base() {std::cout << "Base constructor" << std::endl;}~Base() {std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:Derived() {std::cout << "Derived constructor" << std::endl;}~Derived() {std::cout << "Derived destructor" << std::endl;}
};int main() {Derived d;return 0;
}

运行结果:在这里插入图片描述
代码分析
在运行的时候,我们先去调用了Base这个对象,然后编译器默认调用了Base的构造函数因此先输出Base的,此时我们需要注意Base的析构函数同时也会随着Base函数的声明周期结束而结束,因此我们是属于先定义了Base的析构函数,然后再去同理运行了Derived对象的构造函数和析构函数,又可以利用析构函数的特点,先定义的后销毁因此就有了我们所看到的结果。


拷贝构造函数

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎,那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?因此就有了拷贝构造的出现。
概念:C++中的拷贝构造函数(Copy Constructor)是一种特殊的构造函数,用于创建一个新对象并将其初始化为已存在的对象的副本。
拷贝构造函数的主要作用是:

  1. 创建一个新对象时,将已存在的对象的值复制到新对象中。
  2. 在函数调用中,将对象作为参数传递时,创建原始对象的副本。

在这里插入图片描述这里先直接给大家看看用法:

Date d1(2024);
Date d2(d1);//直接对着d1做出来了一个一模一样的d2

拷贝构造函数的特征:

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。(关于这一点作者得好好的给各位讲解一下,如下所示)
    在这里插入图片描述
  3. . 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。这里我们又要讲解一下什么叫默认拷贝了。
默认拷贝函数

来我们看下面这个代码,我们并没有写拷贝函数但是它还是拷贝成功了,在这里我们可以知道和构造函数一样拷贝函数一样会有一个默认拷贝函数(也叫浅拷贝),因此一般在写的过程中我们并不需要特别的去写一个拷贝函数。注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝(例如栈的时候如果是浅拷贝那么一块空间就会出现析构两次的情况)。

class Date
{
public:void Print(){cout << "year = " << this->_year << " month = " << this->_month << " day = " << this->_day << endl;}Date(int year,int month = 1,int day = 30)//有参默认构造{_year = year;//对其进行初始化_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024);Date d2(d1);d1.Print();d2.Print();return 0;
}

在这里插入图片描述

  1. 注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

拷贝构造函数典型调用场景:

  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象

好啦,今天的内容就到这里啦,下期内容预告类和对象(三)赋值运算符重载等


结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。


🌏🗺️ 这里祝各位寒假愉快 💞💞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/658247.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

幻兽帕鲁服务器多少钱——幻兽帕鲁服务器价格介绍

2024年幻兽帕鲁服务器价格表更新&#xff0c;阿里云、腾讯云和华为云Palworld服务器报价大全&#xff0c;4核16G幻兽帕鲁专用服务器阿里云26元、腾讯云32元、华为云26元&#xff0c;阿腾云atengyun.com分享幻兽帕鲁服务器优惠价格表&#xff0c;多配置报价&#xff1a; 幻兽帕鲁…

Windows Service 2008虚拟机安装mysql service 5.7并实现远程连接

安装前必读 mysql好像在5.7.20版本之后的绿色压缩包版解压都没有my.ini或者my-default.ini配置文件了&#xff0c;需要自己添加配置。 也没有data数据库文件夹&#xff0c;data文件夹不能自己新建&#xff0c;需要命令initialize初始化建立。 my-default.ini文件存不存在不重要…

JavaWeb学习|Session

学习材料声明 所有知识点都来自互联网&#xff0c;进行总结和梳理&#xff0c;侵权必删。 引用来源&#xff1a;尚硅谷最新版JavaWeb全套教程,java web零基础入门完整版 Session 1、Session 就一个接口&#xff08;HttpSession&#xff09;。 2、Session 就是会话。它是用来…

[ESP32]在Thonny IDE中,如何將MicroPython firmware燒錄到ESP32開發板中?

[ESP32 I MicroPython] Flash Firmware by Thonny(4.1.4) IDE 正常安裝流程&#xff0c;可參考上述影片。然而&#xff0c;本篇文章主要是紀錄安裝過程遇到的bug, 供未來查詢用&#xff0c;也一併供有需要的同好參考。 問題:安裝後&#xff0c;Thonny互動介面顯示一堆亂碼和co…

【lesson26】学习MySQL事务前的基础知识

文章目录 CURD不加控制&#xff0c;会有什么问题&#xff1f;CURD满足什么属性&#xff0c;能解决上述问题&#xff1f;什么是事务&#xff1f;为什么会出现事务事务的版本支持 CURD不加控制&#xff0c;会有什么问题&#xff1f; CURD满足什么属性&#xff0c;能解决上述问题&…

文本生成高清、连贯视频,谷歌推出时空扩散模型

谷歌研究人员推出了创新性文本生成视频模型——Lumiere。 与传统模型不同的是&#xff0c;Lumiere采用了一种时空扩散&#xff08;Space-time&#xff09;U-Net架构&#xff0c;可以在单次推理中生成整个视频的所有时间段&#xff0c;能明显增强生成视频的动作连贯性&#xff…

挑选合适的板式家具生产线:满足加工需求的要素解析

板式家具生产线是现代家具制造业中不可或缺的重要工具。然而&#xff0c;选择一条适合自身加工需求的板式家具生产线并不容易&#xff0c;需要考虑多方面因素。本文将深入探讨如何挑选合适的板式家具生产线&#xff0c;从而让读者更好地理解生产线的特点和选择要点。 一、需求分…

nginx+nginx-rtmp-module+ffmpeg进行局域网推流rtmp\m3u8

局域网推流的简单方式 这里以ubuntu为例 一、先下载安装包 nginx、nginx-rtmp-module&#xff0c;再一起安装 # 下载nginx # 这里我安装的是 nginx-1.10.3 版本 cd /usr/software wget http://nginx.org/download/nginx-1.25.0.tar.gz tar -zxvf nginx-1.25.0.tar.gz# 下载ng…

Prometheus---图形化界面grafana(二进制)

前言 Prometheus是一个开源的监控以及报警系统。整合zabbix的功能&#xff0c;系统&#xff0c;网络&#xff0c;设备。 proetheus可以兼容网络&#xff0c;设备。容器的监控。告警系统。因为他和k8s是一个项目基金开发的产品&#xff0c;天生匹配k8s的原生系统。容器化和云原…

详解OpenHarmony各部分文件在XR806上的编译顺序

大家好&#xff0c;今天我们来谈一谈编程时一个很有趣的话题——编译顺序。我知道&#xff0c;一提到编译可能大家会感到有点儿头疼&#xff0c;但请放心&#xff0c;我不会让大家头疼的。我们要明白&#xff0c;在开始写代码之前&#xff0c;了解整个程序的编译路径是十分有必…

Linux进程间通信(IPC)机制之一:共享内存

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;Nonsense—Sabrina Carpenter 0:50━━━━━━️&#x1f49f;──────── 2:43 &#x1f504; ◀️ ⏸ ▶️ …

智能小程序事件系统——SJS响应事件实现方案

背景信息 如有频繁用户交互&#xff0c;在小程序上表现是比较卡顿的。例如&#xff0c;页面有 2 个元素 A 和 B&#xff0c;用户在 A 上做 touchmove 手势&#xff0c;要求 B 也跟随移动&#xff0c;movable-view 就是一个典型的例子。一次 touchmove 事件的响应过程为&#x…

可以举一反三的动态规划问题(最短编辑问题)

给定两个字符串 A 和 B&#xff0c;现在要将 A经过若干操作变为 B&#xff0c;可进行的操作有&#xff1a; 删除–将字符串 A 中的某个字符删除。插入–在字符串 A 的某个位置插入某个字符。替换–将字符串 A 中的某个字符替换为另一个字符。 现在请你求出&#xff0c;将 A 变…

第9章 安全漏洞、威胁和对策(9.1-9.2)

9.1 共担责任(shared responsibility) 共担责任是安全设计的原则&#xff0c;表明任何机构都不是孤立运行的。 相反&#xff0c;它们与世界有着千丝万缕的联系。我们使用相同的基本技术&#xff0c;遵循相同的通信协议规范&#xff0c;在同一个互联网上漫游&#xff0c;共用操…

Shell脚本——免交互

目录 一、Here Document免交互 1、免交互概述 2、语法格式 2.1示例&#xff1a;免交互方式实现对行数的统计&#xff0c;将要统计的内容置于标记EOF之间&#xff0c;直接将内容传给wc-l来统计 3、变量设定 ①变量图换成实际值 ②整行内容作为变量并输出结果 ③使输出内…

基于深度学习的鸟类识别系统matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 卷积神经网络基础 4.2 GoogLeNet模型 4.3 鸟类识别系统 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ............................…

STM32 IIC电量计LTC2944

1 描述 LTC2944 可在便携式产品应用中测量电池充电状态、电池电压、电池电流及其自身温度。宽输入电压范围允许使用高达 60V 的多节电池。精密库仑反向积分电流通过电池正极端子与负载或充电器之间的检测电阻器。 电压、电流和温度由内部 14 位无延迟 ΔΣ™ ADC 测量。测量结…

Linux:理解信号量以及内核中的三种通信方式

文章目录 共享内存的通信速度消息队列msggetmsgsndmsgrcvmsgctl 信号量semgetsemctl 内核看待ipc资源单独设计的模块ipc资源的维护 理解信号量总结 本篇主要是基于共享内存&#xff0c;延伸出对于消息队列和信号量&#xff0c;再从内核的角度去看这三个模块实现进程间通信 共享…

【教学类-44-04】20240130 print dashed(虚线字体)制作的数字描字帖

作品展示&#xff1a;背景需求&#xff1a; 制作绿色数字的数字描字帖 选用字体&#xff1a;print dashed&#xff08;虚线字体&#xff09; 【教学类-44-03】20240111阿拉伯数字字帖的字体&#xff08;三&#xff09;——德彪钢笔行书&#xff08;实线字体&#xff09;和pri…

如何使用Docker部署JSON Crack

文章目录 1. 在Linux上使用Docker安装JSONCrack2. 安装Cpolar内网穿透工具3. 配置JSON Crack界面公网地址4. 远程访问 JSONCrack 界面5. 固定 JSONCrack公网地址 JSON Crack 是一款免费的开源数据可视化应用程序&#xff0c;能够将 JSON、YAML、XML、CSV 等数据格式可视化为交互…