【C++】类中的“默认成员函数“--构造函数、析构函数、拷贝构造、赋值运算符重载

目录

"默认"成员函数 概念引入:

一、构造函数

问题引入:

1)构造函数的概念

2)构造函数实例

3)构造函数的特性

4)关于默认生成的构造函数     (默认构造函数)

默认构造函数未完成初始化工作实例:

二、析构函数

1)析构函数概念

2)析构函数特性

三、拷贝构造函数

1)拷贝构造函数概念

示例代码:

2)深拷贝

3)拷贝构造函数特性

四、赋值运算符重载

运算符重载

赋值运算符重载

"默认"成员函数 概念引入:

C++中的默认成员函数是系统自动生成的,如果没有手动编写该类的成员函数,编译器就会自动为该类生成默认成员函数。默认成员函数包括默认构造函数、默认析构函数和默认拷贝构造函数等。

  1. 默认构造函数:当创建对象时,如果没有显式地调用构造函数,系统会自动调用默认构造函数来初始化对象。默认构造函数不接受任何参数,也不返回任何值。
  2. 默认析构函数:当对象被销毁时,系统会自动调用析构函数来清理对象。默认析构函数不接受任何参数,也不返回任何值。
  3. 默认拷贝构造函数:当将一个对象赋值给另一个对象时,系统会自动调用拷贝构造函数来完成对象的复制。默认拷贝构造函数会将原对象的所有成员变量逐个复制给新对象。

除了以上三种默认成员函数外,还有默认赋值运算符、取地址运算符等。这些默认成员函数可以让我们更方便、更高效地使用C++语言进行面向对象编程。

以上有六个默认成员函数,但是我们今天只探讨前4个,后面两个实际操作中我们很少直接编写,一般都是使用默认生成式的。

一、构造函数

问题引入:

        在C++编程语言中,构造函数和析构函数的出现与对象及对象的生命周期管理密切相关。在现实世界中,每个事物都有其生命周期,会在某个时候出现也会在另外一个时候消亡。类似地,程序是对现实世界的反映,其中的对象就代表了现实世界的各种事物,自然也就具有生命周期,也会被创建和销毁。

        因此,为了恰当地管理对象的生命周期,特别是对象的初始化和清理工作,C++引入了构造函数和析构函数这两个特殊的成员函数。每一个类都有一个默认的构造函数和析构函数;构造函数在类定义时由系统自动调用,析构函数在类被销毁时由系统自动调用。

具体来说,构造函数主要用于完成对象的初始化工作,它的名字和类名相同,一个类可以有多个构造函数。如果程序员没有手动编写构造函数,编译器会默认生成一个构造函数。另一方面,析构函数则用于完成对象的清理工作,它的名字是类名前面加一个~符号。当对象的生命期结束时,会自动执行析构函数。

        总的来说,构造函数和析构函数的出现,让程序员可以更加方便、准确地管理对象的生命周期,这是C++面向对象编程特性的一个重要体现。

1)构造函数的概念

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

2)构造函数实例

而且可以结合我们前面学过的知识,构造函数也是可以重载的:

//普通版
Date()//无参的
{m_year = 2023;m_month = 10;m_day = 17;
}
Date(int year,int month,int day)//带参且无缺省参数
{m_year = year;m_month = month;m_day = day;  
}//融合版
Date(int year = 2023,int month = 10,int day = 17)//全缺省的
{m_year = year;m_month = month;m_day = day;
}//普通版容易在使用时造成二义性问题,所以推荐后面的用法

3)构造函数的特性

不允许出现这正形式的调用:!!!

Date d1();

这种类型的调用,因为它可以被认为成函数的声明,就好比:

Date fun1();

所以,如果想定义一个日期类,则不能加括号,不然括号内需要加参数,

  1. 函数名与类名相同
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成

4)关于默认生成的构造函数     (默认构造函数)

关于编译器生成的默认成员函数,很多同学会有疑惑:不手动实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来它生成的默认构造函数又没什么用?我们在定义对象时调用了编译器生成的默认构造函数,但是对象的成员变量_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数没有实质性作用吗?

默认构造函数未完成初始化工作实例:

class Date
{
public:void Print(){std::cout << _year << _month << _day << std::endl;}
private:int _year;int _month;int _day;
};int main(void)
{Date date;date.Print();return 0;
}

对比手动添加构造函数的结果:
 

class Date
{
public:void Print(){std::cout << _year << _month << _day << std::endl;}Date(int year = 2024, int month = 11, int day = 13)//全缺省的{_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main(void)
{Date date;date.Print();return 0;
}

解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,编译器生成默认的构造函数会对自定类型成员调用的它的默认成员函数,而内置类型则不会,这就是为什么全是int类型的成员变量的日期类我们观察不到其初始化的原因。

 如果我们用两个栈实现一个队列类就可以体会到默认构造的作用了:

class MyQueue
{
private:Stack _pushst;Stack _popst;
};

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值,自动生成的构造函数会根据此初始化。

class Date
{
private:int _year = 2000;//在类中定义的默认值可以用作构造函数初始化的参考int _month = 1;int _day = 1;
}

 其实关于默认构造函数,只有在我们所有成员变量全是自定义类型的情况下,我们才会去省略掉自己编写的过程去使用编译器默认生成的,大部分情况下构造函数都是要我们自己编写的。

二、析构函数

1)析构函数概念

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么清除的呢?析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

~Date() {  // 在这里可以执行任何必要的清理工作  std::cout << "Date destructor called for " << _year << "-" << _month << "-" << _day << std::endl;  }  

2)析构函数特性

  1. 析构函数名是在类名前加上字符 “~”。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
  5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数
  6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类
  7. 默认生成的析构函数不会对内置类型进行操作,不然释放掉不该释放的内容将会出问题。

析构函数是类的一种特殊的成员函数,其名称与类名相同但增加一个波浪线符号(~)。当对象超出范围或通过调用delete显式销毁对象时,析构函数会自动被调用。析构函数往往用来做“清理善后”的工作,例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存。此外,析构函数也可以有多个,如果没有手动写析构函数,编译器会生成一个默认的析构函数并自动调用。

析构函数的调用时机主要有以下几种:

  1. 对象生命周期结束:当对象超出作用域或被显式销毁时,析构函数会自动被调用,用来释放对象占用的内存空间。
  2. delete操作符:当使用delete删除指针类对象时,会直接调用析构函数来清理内存。
  3. 包含关系:如果对象Dog是对象Person的成员,那么在Person的析构函数被调用时,Dog对象的析构函数也会被自动调用。

三、拷贝构造函数

1)拷贝构造函数概念

        拷贝构造函数,也称为复制构造函数,是一种特殊的构造函数。它是当创建新对象时,使用同一类中之前已创建的对象来初始化新创建的对象。这种构造函数由编译器自动调用,用于一些基于同一类的其他对象的构建及初始化。

拷贝构造函数的形式参数必须是引用,通常为const引用,以便能够处理常量对象和非常量对象的复制。这样,它既能以常量对象(初始化后值不能改变的对象)作为参数,也能以非常量对象作为参数。

        在编程实践中,拷贝构造函数常常用于以下情况:通过使用另一个同类型的对象来初始化新创建的对象;复制对象把它作为参数传递给函数;以及从函数返回一个对象时复制该对象。

        值得注意的是,如果程序员没有显式定义拷贝构造函数,那么编译器会自动生成一个默认的拷贝构造函数。这个默认的拷贝构造函数会将原对象的成员变量值赋值给新对象的相应成员变量。

拷贝构造函数的使用加场景类似于:

//使用a创建b的过程
int a = 1;
int b = a;
  • 直接传值传参(浅拷贝):由于C++的特性会出现析构两次的问题
  • 引用拷贝:但是对于复制对象的操作会影响到原对象(因为引用底层是指针,所以引用拷贝使用时没有问题,但是实际会影响原来的对象)
  • C++规定,自定义类型的传参就需要调用拷贝构造函数

示例代码:

#include <iostream>  
#include <cstring>  class MyClass {  
public:  char* data;  // 构造函数  MyClass(const char* str) {  data = new char[strlen(str) + 1];  strcpy(data, str);  }  // 拷贝构造函数  MyClass(const MyClass& other) {  data = new char[strlen(other.data) + 1]; // 重新分配内存  strcpy(data, other.data); // 深拷贝  }  // 析构函数  ~MyClass() {  delete[] data;  }  // 用于展示数据  void show() const {  std::cout << "Data: " << data << std::endl;  }  
};  // 按值传递  
void byValue(MyClass obj) {  obj.show();  
}  // 按引用传递  
void byReference(MyClass& obj) {  obj.show();  
}  int main() {  MyClass original("Hello, World!");  // 按值传递  std::cout << "Calling byValue:" << std::endl;  byValue(original);  std::cout << "After byValue call, original:" << std::endl;  original.show(); // original 的数据没有改变  // 按引用传递  std::cout << "Calling byReference:" << std::endl;  byReference(original);  std::cout << "After byReference call, original:" << std::endl;  original.show(); // original 的数据没有改变  return 0;  
}

按值传递可能出现的问题

在 byValue 函数中,传递对象会触发拷贝构造函数。若拷贝构造函数不正确(如仅进行浅拷贝),则会导致多个对象指向同一内存。在一个对象被销毁时,另一个对象访问已被释放的内存,进而引发未定义行为。

按引用传递的安全性

在 byReference 函数中,使用引用传递,因此不会发生对象的拷贝。原始对象的状态保持不变,不会出现内存访问的问题。

2)深拷贝

深拷贝,是指源对象与拷贝对象互相独立其中任何一个对象的改动都不会对另外一个对象造成影响。深拷贝会复制所有字段,并复制字段所指向的动态分配内存。深拷贝发生在对象及其引用的对象被复制时。对于基本数据类型,如预定义类型Int32,Double等,深拷贝复制所有基本数据类型的成员变量的值。对于引用数据类型的成员变量,深拷贝申请新的存储空间,并复制该引用对象所引用的对象。

深拷贝是一种特殊的拷贝方式,它不仅复制了对象的基本数据类型成员变量的值,还为引用类型的成员变量申请了新的存储空间,并递归复制了这些引用对象所引用的其他对象。这样,源对象与拷贝对象就完全独立任一对象的修改都不会影响到另一个对象

需要注意的是,在C++中,对于基本类型的数据以及简单的对象,它们之间的拷贝非常简单,通常是按位复制内存。但对于复杂对象和包含指针或动态内存分配的对象来说,需要进行深拷贝来确保两个对象不会相互影响。 默认情况下,基本数据类型(number,string,null,undefined,boolean)的操作都是深拷贝。

3)拷贝构造函数特性

  • 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
  • 拷贝构造函数典型调用场景:使用已存在对象创建新对象函数参数类型为类类型对象函数返回值类型为类类型对象

四、赋值运算符重载

运算符重载

  1. C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字operator后面接需要重载的运算符符号。函数原型:返回值类型 operator操作符(参数列表)
  2. 不能通过连接其他符号来创建新的操作符:比如operator@
  3. 重载操作符必须有一个类类型参数
  4. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  5. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  6. 当然有特殊情况,以下五个运算符不可以重载!!!!
 *     ::    sizeof      ?     : 

赋值运算符重载

我们可以重载赋值运算符。不论形参的类型是什么,赋值运算符都必须定义为成员函数。

                                                                                                                --《C++ Prime》

  1. 赋值运算符重载格式参数类型:const T&,传递引用可以提高传参效率返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值检测是否自己给自己赋值返回*this :要复合连续赋值的含义
  2. 赋值运算符只能重载成类的成员函数不能重载成全局函数//:原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
  3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

在实际编码中,建议在需要动态资源管理的类中实现拷贝构造函数、赋值操作符、析构函数,以遵循“遵循Rule of Three”的原则,确保资源管理正确无误。

如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

且不能改变操作符的操作数。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

中仕公考怎么样?事业编面试不去有影响吗?

事业编考试笔试已经通过&#xff0c;但是面试不去参加会有影响吗&#xff1f; 1. 自动放弃面试资格&#xff1a;未能按时出席事业单位的面试将被视为主动放弃该岗位的竞争机会。 2. 个人信誉问题&#xff1a;面试作为招聘流程的关键步骤&#xff0c;无故缺席可能被解释为诚信…

websocket初始化

websocket初始化 前言 上一集我们HTTP的ping操作就可以跑通了&#xff0c;那么我们还有一个协议---websocket&#xff0c;我们在这一集就要去完成我们websocket的初始化。 分析 我们在初始化websocket的之前&#xff0c;我们考虑一下&#xff0c;我们什么时候就要初始化我们…

Unity中HDRP设置抗锯齿

一、以前抗锯齿的设置方式 【Edit】——>【Project Settings】——>【Quality】——>【Anti-aliasing】 二、HDRP项目中抗锯齿的设置方式 在Hierarchy中——>找到Camera对象——>在Inspector面板上——>【Camera组件】——>【Rendering】——>【Pos…

Linux系统任务管理

文章目录 系统任务管理atcron &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Linux专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年11月14日11点20分 系统任务管理 任务管理 计划执行&#xff1a;特定时间运行一次&#xff1a;at 定期执…

(附项目源码)Java开发语言,springboot 民宿管理系统的设计与实现 57,计算机毕设程序开发+文案(LW+PPT)

目 录 摘 要 1 绪论 1.1 研究背景 1.2 研究意义 1.3 主要研究内容 1.4 论文章节安排 2 相关技术介绍 2.1 Java编程语言 2.2 MySQL数据库 2.3 springboot框架 3 系统分析 3.1 可行性分析 3.1.1 技术可行性分析 3.1.2 经济可行性分析 3.1.3 操作可行性分析 3.2 …

IDEA热部署(简单死了!)

真的很简单很简单&#xff0c;我之前看别的博主&#xff0c;很多都讲的很复杂&#xff0c;我哭 步骤一&#xff1a; 步骤二&#xff1a; 步骤三&#xff1a; 步骤四&#xff1a; 到这里就结束啦~ 最后很重的是&#xff1a; 1.启动项目时候&#xff0c;必须使用debug方式启…

基于java+springboot+layui的流浪动物交流信息平台设计实现

基于javaspringbootlayui的流浪动物交流信息平台设计实现 &#x1f345; 作者主页 网顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各种定制系…

Spring Boot框架:构建可扩展的网上商城

4 系统设计 网上商城系统的设计方案比如功能框架的设计&#xff0c;比如数据库的设计的好坏也就决定了该系统在开发层面是否高效&#xff0c;以及在系统维护层面是否容易维护和升级&#xff0c;因为在系统实现阶段是需要考虑用户的所有需求&#xff0c;要是在设计阶段没有经过全…

「Py」模块篇 之 PyAutoGUI库自动化图形用户界面库

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「Py」Python程序设计&#x1f4da;全部专栏「Win」Windows程序设计「IDE」集成开发环境「UG/NX」BlockUI集合「C/C」C/C程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「UG/NX」NX定…

打造透明、高效的分布式系统:通过 EMQX ECP 集成实现链路追踪功能

链路追踪作为一种用于监控和观察分布式系统中请求流动和性能的技术&#xff0c;在现代微服务架构中扮演着重要角色。 在复杂的分布式环境中&#xff0c;它可以记录并可视化跨多个服务与组件的完整请求路径&#xff0c;并提供每个服务节点上的执行时间&#xff0c;帮助开发人员…

sql数据库-聚合函数-DQL(类似Excel函数)

目录 聚合函数介绍 语法 举例 统计表中的所有女性员工 统计表中工作地点在北京的员工 聚合函数介绍 常用的聚合函数 函数功能count统计字段数量max最大值min最小值avg平均值sum求和 语法 SELECT 聚合函数(字段列表) FROM 表名; 举例 统计表中的所有女性员工 sele…

【C语言刷力扣】58.最后一个单词的长度

题目&#xff1a; 解题思路; 倒序遍历&#xff0c;先将末尾的空格过滤&#xff0c;再统计至第一个空格。 条件i > 0 放在前面先判断&#xff0c;条件s[i] ! 放后面&#xff0c;反之遇到单字符会溢出。 时间复杂度&#xff1a; 空间复杂度&#xff1a; int lengthOfLas…

【数据运营】数据资产私域运营:探索并实现数据价值变现的新途径

随着数字化浪潮的席卷&#xff0c;数据已成为现代企业的核心竞争力之一。然而&#xff0c;仅仅拥有数据并不足以在激烈的市场竞争中脱颖而出&#xff0c;关键在于如何有效地管理和运营这些数据资产&#xff0c;将其转化为实实在在的商业价值。本文将从数据资产私域运营的定义、…

360天擎终端安全管理 远程控制客户端终端进行的安全防护/终端管理:病毒查杀/插件管理/系统修复/漏洞管理等操作

文章目录 目录 文章目录 使用流程 小结 概要使用流程技术细节小结 概要 如果首页上出现只有5台。但是公司实际上有20台电脑。还有很多未进行安装360天擎的用户主机。我们下发指示通告内容。这个的话需要一个一个排查才能知道谁没有安装。可以查看终端管理页面看到主机IP知道已…

数字人直播骗局大起底!源码部署究竟有哪些优势?

随着数字人直播的应用频率不断上升&#xff0c;越来越多的人开始关注到了它所蕴含着的广阔前景和巨大收益潜力&#xff0c;于是&#xff0c;纷纷打听起了入局相关的事宜。而这也就让许多不法分子盯上了这一项目&#xff0c;并炮制出了各式各样的数字人直播骗局来收割韭菜。 其中…

OpenAI官方发布:利用ChatGPT提升写作的12条指南

近日&#xff0c;OpenAI官方发布了学生如何利用ChatGPT提升写作的12条指南&#xff0c;值得深入研究学习。 在如今AIGC应用爆发增长的时间点&#xff0c;如何充分利用生成式AI工具&#xff0c;如ChatGPT&#xff0c;有效切快速的提升写作和学习能力&#xff0c;成为每个学生、…

探索大型语言模型(LLMs)能否在不泄露私人信息的情况下联合其他大型语言模型共同解决问题

概述 谷歌的 Gemini Ultra&#xff08;2023 年&#xff09;和 OpenAI 的 GPT-4 &#xff08;2023 年&#xff09;等大规模语言模型在许多任务中都表现出了令人印象深刻的性能。然而&#xff0c;这些模型不仅推理成本高昂&#xff0c;而且运行于数据中心&#xff0c;而数据中心…

CloudDM Team Docker 版安装指南

CloudDM Team 是一款全新的国产自研数据库管理工具&#xff0c;在《全新的企业级数据库数据安全管控平台》 一文中全面介绍了其核心功能和特点。本文将会介绍如何在 Ubuntu Linux 中安装并初步使用这款数据库管理工具。 准备工作 安装 Docker CloudDM Team 安装过程中需要用…

第22天Linux下常用工具

目录 第 1 章 vim 编辑器 1.1 vim 安装 1.2 vim 的使用 1.3 vim 的 4 种工作模式 第 2 章 gcc 编译器 2.1 编译流程&#xff08;以 main.c 为例&#xff09; 2.2 gcc 的常用参数 2.3 进行多模块编译 第 3 章 动态库静态库的制作 3.1 库的作用 3.2 库的分类与特点 …

/// ts中的三斜线指令 | 前端

第一次看到注意到这行代码&#xff0c;不知道的还以为是注释呢&#xff0c;查了资料才知道这是typescript中的三斜线指令&#xff0c;那有什么作用呢&#xff1f; 1. 这行代码是TypeScript中的一个三斜线指令&#xff08;Triple-Slash Directive&#xff09;&#xff0c;用于…