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

💗个人主页💗
⭐个人专栏——C++学习⭐
💫点击关注🤩一起学习C语言💯💫

目录

  • 导读
  • 1. 默认成员函数
  • 2. 构造函数
    • 2.1 引入
    • 2.2 特性
    • 2.3 默认构造函数
  • 3. 析构函数
    • 3.1 概念
    • 3.2 特性
    • 3.3 默认析构函数
  • 4. 拷贝构造函数
    • 4.1 概念
    • 4.2 特性
    • 4.3 默认拷贝构造函数

导读

我们上次讲了类和对象的一些定义相关的知识,今天我们进一步的来讲构造函数、析构函数和拷贝构造函数。

1. 默认成员函数

如果一个类中什么成员都没有,简称为空类。
但是!
空类中并不是什么都没有。
类有六个默认的成员函数,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
在这里插入图片描述

2. 构造函数

2.1 引入

为什么要有构造函数?
我们引入下述代码来看:

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Init(2024, 1, 30);d1.Print();Date d2;d2.Init(2024, 1, 31);d2.Print();return 0;
}

对于Date类,我们可以使用成员函数Init()给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦。

那有没有什么办法能在创建对象时,自动将我们要传递的内容放置进去呢?
那就需要我们的构造函数了。

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有
一个合适的初始值,并且在对象整个生命周期内只调用一次。

2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

构造函数的特点包括:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
class Date
{
public:Date() //无参构造函数{_year = 1;_month = 1;_day = 1;}Date(int year, int month, int day)//有参构造函数  ,二者构成重载{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;     // 年int _month;    // 月int _day;      // 日
};int main()
{Date d1;//调用无参构造函数d1.Print();Date d2(2024, 1, 31);//调用带参构造函数d2.Print();return 0;
}

在这里插入图片描述
不给参数时就会调用 无参构造函数,给参数则会调用 带参构造函数。
注意事项:

  • 如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明。
class Date
{
public:Date() //无参构造函数{_year = 1;_month = 1;_day = 1;}Date(int year, int month, int day)//有参构造函数  ,二者构成重载{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;     // 年int _month;    // 月int _day;      // 日
};int main()
{Date d1();  //这样不能调用无参初始化return 0;
}
  • 这里如果调用带参构造函数,我们需要传递三个参数

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

class Date
{
public:/*// 如果用户显式定义了构造函数,编译器将不再生成Date(int year, int month, int day){_year = year;_month = month;_day = day;}*/void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函Date d1;d1.Print();return 0;
}

在这里插入图片描述
为什么是随机值呢?我们接着引入下一特性。

2.3 默认构造函数

我们上述使用默认构造函数时,生成的默认值似乎并没有什么用处。

C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

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

注意:
C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

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

在这里插入图片描述
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

**注意:**无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

3. 析构函数

3.1 概念

析构函数是一种特殊的成员函数,用于在对象销毁时执行清理工作。它的名称与类名相同,前面加上一个波浪线(~)。在C++中,每个类都可以有一个析构函数,它会在对象的生命周期结束时被自动调用。

3.2 特性

析构函数是特殊的成员函数,其特征如下:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class Date {
public:Date(int year = 1, int month = 0, int day = 0) {_year = year;_month = month;_day = day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}~Date() {// Date 类没有资源需要清理,所以Date不实现析构函都是可以的cout << "~Date()  " << endl;  // 测试一下,让他吱一声}private:int _year;int _month;int _day;
};int main(void)
{Date d1;d1.Print();Date d2(2024, 1, 31);d2.Print();return 0;
}

在这里插入图片描述
我们在之前学习数据结构时,多用malloc()等函数来开辟空间,最后也要写个函数释放空间,但有时候我们会忘记释放,这时就需要我们的析构函数了,我们不用特意的去调用,系统会自己帮我们调用。

typedef int StackDataType;
class Stack {
public:/* 构造函数 - StackInit */Stack(int capacity = 4) {  // 这里只需要一个capacity就够了,默认给4(利用缺省参数)_array = (StackDataType*)malloc(sizeof(StackDataType) * capacity);if (_array == NULL) {cout << "Malloc Failed!" << endl;exit(-1);}_top = 0;_capacity = capacity;}/* 析构函数 - StackDestroy */~Stack() {  free(_array);_array = nullptr;_top = _capacity = 0;}private:int* _array;size_t _top;size_t _capacity;
};int main(void)
{Stack s1;Stack s2(2); return 0;
}

3.3 默认析构函数

  1. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
    和默认构造函数一样:
  • 对于 “内置类型” 的成员变量:不作处理
  • 对于 “自定义类型” 的成员变量:会调用它对应的析构函数。
class Time
{
public:~Time(){cout << "调用了time的析构函数" << endl;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year = 1;int _month = 1;int _day = 1;// 自定义类型Time _time;
};int main()
{Date d1;return 0;
}

在这里插入图片描述

  1. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
    Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

4. 拷贝构造函数

4.1 概念

拷贝构造函数是一种特殊的构造函数,用于创建对象的拷贝。它接受与对象类型相同的另一个对象作为参数,并使用该参数的值来初始化新对象。拷贝构造函数通常用于实现深拷贝,即创建一个对象的独立副本,而不是仅复制指针或引用。
拷贝构造函数的语法如下:

类名(const 类名& 另一个对象)
{// 初始化新对象的成员变量
}

4.2 特性

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
class Date
{
public:Date(int year = 2024, int month = 1, int day = 31){_year = year;_month = month;_day = day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);d1.Print();d2.Print();return 0;
}

在这里插入图片描述
为什么要用引用呢?
如果拷贝构造函数使用了对象作为参数而不是引用,那么在调用拷贝构造函数时又需要创建一个新的对象,这会引起一次不必要的拷贝构造操作。而如果使用引用作为参数,就可以直接引用原对象,避免了对象的拷贝。

此外,如果拷贝构造函数使用了对象作为参数,在进行传递时会调用拷贝构造函数,而拷贝构造函数又需要调用拷贝构造函数,这会导致无限递归的问题。而使用引用作为参数,可以避免这种无限递归的情况。

4.3 默认拷贝构造函数

若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

class Date {
public:Date(int year = 0, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}// Date(Date& d) {//     _year = d._year;//     _month = d._month;//     _day = d._day;// }void Print() {printf("%d-%d-%d\n", _year, _month, _day);}private:int _year;int _month;int _day;
};int main(void)
{Date d1(2024, 1, 31);Date d2(d1);d1.Print();d2.Print();return 0;
}

在这里插入图片描述
默认拷贝构造函数似乎和前面的默认构造函数以及默认析构函数不太一样,它能够解决我们的需求。
但是!
这并不意味着我们不用写拷贝构造函数,
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

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

  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象
class Date
{
public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}
private:int _year;int _month;int _day;
};
Date Test(Date d)
{Date temp(d);return temp;
}
int main()
{Date d1(2024, 1, 31);Test(d1);return 0;
}

在这里插入图片描述

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

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

相关文章

C语言------函数

C语言的两个重要关键字&#xff1a;static&&extern 会报错&#xff0c;是a未定义&#xff0c;说明局部变量a只能够在内部的括号里面使用&#xff1b; 如果a是全局变量&#xff0c;那么a就全部可以使用&#xff1b; 在不同的文件中&#xff0c;如果想要使用的某个变量没…

万户 ezOFFICE DocumentEdit_unite.jsp SQL注入漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品,统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日…

【Java-JDK】JDK 的安装与环境变量的配置:Windows Linux

【Java-JDK】JDK的安装与环境变量的配置&#xff1a;Windows & Linux 1&#xff09;Windows安装JDK1.1.下载JDK1.1.安装JDK1.2.JDK环境配置1.3.验证环境变量是否配置成功 2&#xff09;Linux安装JDK2.1.下载JDK2.2.安装JDK2.3.JDK环境配置2.4.验证环境变量配置是否成功 1&a…

如何有效避免市场恐慌性抛售?

布雷特斯坦伯格是一位备受尊敬的交易心理导师&#xff0c;曾担任华尔街多家顶级培训机构的心理导师&#xff0c;指导交易员们如何应对心理挑战。作为一名心理学教授和资深交易员&#xff0c;他对交易心理的理解远超常人。人们普遍认为&#xff0c;要想在交易领域取得成功&#…

Zynq UltraScale+使用记录-----配置和程序固化

新手使用记录 芯片&#xff1a;xczu3eg-sfvc784-2-i 开发板&#xff1a;米联客8X-3EG &#xff08;B&#xff09; 开发环境&#xff1a;vavido2019.2 1. create block design 主要配置了UART0&#xff08;输出信息&#xff09;\SD1&#xff08;SD卡启动&#xff09;\BANK电平\…

qt学习:Table widget控件

目录 头文件 实战 重新配置ui界面 添加头文件 在构造函数中添加初始化 显示方法 该实例是在sqlite项目上添加qt学习&#xff1a;QTSQL连接sqlite数据库增删改查-CSDN博客 头文件 #include <QTableWidgetItem> 实战 重新配置ui界面 用法介绍&#xff0c;可以双击…

Redis核心技术与实战【学习笔记】 - 7.Redis GEO类型 - 面向 LBS 应用的数据类型

前言 前面&#xff0c;介绍了 Redis 的 5 大基本数据类型&#xff1a;String、List、Hash、Set、Sorted Set&#xff0c;它们可以满足绝大多数的数据存储需求&#xff0c;但是在面对海里数据统计时&#xff0c;它们的内存开销很大。所以对于一些特殊的场景&#xff0c;它们是无…

解析Kubernets pod DNS域名

k8s dns理解 这个博主讲的很详细 我的这篇文章主要是演示测试 k8s的dns nslookup怎么解析到k8spod域名 创建一个busybox的pod&#xff0c;测试一下pod内是否可以解析 1、流程验证 cat >dns-Deployment.yaml<<EOF apiVersion: apps/v1 kind: Deployment metadata:nam…

文本三剑客之grep

目录 一、正则表达式 1、什么是正则表达式 2、元字符 3、扩展正则表达式元字符 二、grep 一、正则表达式 1、什么是正则表达式 REGEXP&#xff1a; Regular Expressions&#xff0c;由一类特殊字符及文本字符所编写的模式&#xff0c;其中有些字符&#xff08;元字符&#…

tidb节点重启后,服务无法重连

大家好&#xff0c;我是烤鸭&#xff1a; 前几天遇到tidb节点重启后服务无法重连&#xff0c;确切地说是两个服务&#xff0c;一个可以正常重连&#xff0c;一个不行。 问题复现 由于线上执行慢SQL&#xff0c;导致TiDB 单个节点宕机重启。 其中A服务的3个节点和B服务的1个节…

STM32——USART

一、通信 1.1通信是什么&#xff1b; 通信是将一个设备的数据发送到另一个设备中&#xff0c;从而实现硬件的扩展&#xff1b; 1.2通信的目的是什么&#xff1b; 实现硬件的扩展-在STM32中集成了很多功能&#xff0c;例如PWM输出&#xff0c;AD采集&#xff0c;定时器等&am…

springboot142新冠病毒密接者跟踪系统

新冠病毒密接者跟踪系统设计与实现 摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff…