【C++】类与对象(二)特殊成员函数

在这里插入图片描述

前言
类与对象(二)


文章目录

  • 一、特殊成员函数
  • 二、构造函数
  • 三、析构函数
  • 四、拷贝构造函数
  • 五、拷贝赋值运算符

一、特殊成员函数

如果在类的声明中未显式提供某个成员函数的定义,编译器会自动生成一个默认实现。 这包括默认构造函数、默认析构函数、默认拷贝构造函数、默认拷贝赋值运算符以及默认移动构造函数和移动赋值运算符。

我们主要将讲解一下构造函数,析构函数,拷贝构造函数和默认拷贝赋值运算符。
在这里插入图片描述

二、构造函数

构造函数用来初始化对象的成员变量

主要特性:

  1. 与类同名

  2. 没有返回类型

  3. 在对象创建时自动调用

  4. 可以重载

    class Date{
    public:// 1.无参构造函数Date(){_year = 0;_month = 0;_day = 0;}// 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}
    private:int _year;int _month;int _day;
    };int main() {Date d1; // 调用无参构造函数,不需要跟括号Date d2(2015, 1, 1); // 调用带参的构造函数// 这不是在创建一个对象,而是声明一个函数 d3,该函数没有参数并返回一个//Date d3();  warning C4930 : “Date d3(void)” : 未调用原型函数(是否是有意用变量定义的 ? )return 0;
    }
    
  5. 类中没有显式定义构造函数,则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;return 0;
    }
    
  6. 编译器在遇到内置类型时,自动生成的默认构造函数(没有显式定义构造函数的情况下,这符合第五点)不对其初始化;遇到自定义类型时调用该类型中的显式定义构造函数,如果没有,也会像内置类型那样,自动生成默认构造函数并调用,但不对内置类型初始化。

    对于内置类型,编译器生成的默认构造函数通常不包含任何实际的初始化代码,这意味着内置类型的成员变量将包含未定义的值,即取决于存储它们的内存的初始状态(内置类型的成员变量是在栈上或堆上分配内存的,而这块内存的初始值是未定义的,即它们可能包含任意的数值)。

    对于自定义类型:

    1. 如果类中没有任何构造函数,编译器会生成一个默认构造函数,对所有成员变量执行它们各自的默认构造函数。对于基本数据类型成员,执行与内置类型相同的处理,即保留未初始化的值。

    2. 如果类显式声明了其他构造函数(无论是默认构造函数还是带参数的构造函数),编译器将不再生成默认构造函数。此时,如果你确实需要一个默认构造函数,你需要显式提供它。

    示例:

    #include <iostream>class Example {
    public:// 默认构造函数Example() {std::cout << "默认构造函数被调用" << std::endl;// 对于基本数据类型,保留未初始化的值}private:int intValue;double doubleValue;
    };int main() {// 对于自定义类型 Example,会调用默认构造函数Example obj;return 0;
    }
    

    在上述例子中,Example 类包含两个基本数据类型成员变量。默认构造函数将被调用,并且对于 intdouble 类型的成员变量,它们将包含未初始化的值。

    C++11 允许在类的声明中直接进行成员变量的初始化,这被称为默认成员初始化。

    在没有显式提供构造函数的情况下,成员变量 intValue 将会被默认初始化为 42。

    class Example {
    public:int intValue = 42;  // 默认成员初始化
    };
    
  7. 自定义的无参构造函数和全缺省构造函数,以及编译器自动生成的默认构造函数都是默认构造函数。
    默认构造函数的意思是,在创建对象时不需要任何参数。而无参构造函数和全缺省构造函数,以及编译器自动生成的默认构造函数,它们都不需要任何参数就可以创建对象,因此它们都是默认构造函数。
    但要注意,因为它们都不需要参数,所以它们不会同时出现,否则编译器不知道要调用哪个函数。

    class Date{
    public:Date(){_year = 1900;_month = 1;_day = 1;}Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
    };
    // 以下测试函数能通过编译吗?
    int main() {// “Date::Date” : 对重载函数的调用不明确//Date d1;
    }
    

三、析构函数

析构函数是在对象生命周期结束时被调用的特殊成员函数。它的主要作用是进行对象的清理和资源释放工作。

在C++中,每个类都可以有一个析构函数,其名称与类名相同,前面加上波浪号(~)。

无参数无返回值类型。

一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。

对象生命周期结束时自动调用析构函数。

class Date {
public:Date() {_year = 1900;_month = 1;_day = 1;}~Date() {cout <<"对象生命周期结束时自动调用"<< "\n";}private:int _year;int _month;int _day;
};int main() {Date d1;
}

在这里插入图片描述


当一个类没有显式定义析构函数时,C++编译器会自动生成一个默认的析构函数。这个默认的析构函数会执行基本的清理工作,但对于动态分配的内存或其他资源的释放,它可能不会进行额外的操作。

#include <iostream>class Example {
public:// 没有显式定义析构函数// 其他成员函数和变量void someFunction() {std::cout << "Executing some function\n";}
};int main() {// 创建对象Example obj;// 调用成员函数obj.someFunction();// 对象超出作用域,析构函数被调用return 0;
}

默认的析构函数通常足够处理大多数情况,尤其是对于没有动态资源管理的简单类。然而,如果类涉及到动态分配的内存等复杂的操作,通常建议显式定义析构函数以确保这些资源能够被正确释放。


四、拷贝构造函数

拷贝构造函数是一种特殊的构造函数,用于创建一个对象,该对象是已有对象的精确副本。

拷贝构造函数通常在以下情况下调用:

  1. 通过一个对象初始化另一个对象。
  2. 将对象作为函数参数传递给函数。
  3. 从函数返回对象。

拷贝构造函数的基本语法如下:

class MyClass {
public:// 拷贝构造函数MyClass(const MyClass& other) {// 执行拷贝操作,创建一个对象的副本}// 其他成员函数和变量
};

可以将拷贝构造函数看作构造函数的重载。


拷贝构造函数的参数是一个对同类型对象的引用,并且通常是 const 引用,以确保不修改原始对象。在函数体内,你需要编写适当的代码来实现对象的拷贝。

同时拷贝构造函数不能通过传值的方式定义,因为这样会引发无限递归的拷贝构造函数调用。

class MyClass {
public:// 错误的拷贝构造函数,传值方式MyClass(MyClass another) {// 这里的传值方式将调用拷贝构造函数,导致无限循环}//正确方式 MyClass(const MyClass& another)
};

在这里插入图片描述

通过值传递方式定义拷贝构造函数时,传递的对象 another 会触发拷贝构造函数,而这个拷贝构造函数又传递了一个值,然后再次触发拷贝构造函数,导致无限递归调用。


如果你没有显式提供拷贝构造函数,C++ 编译器会为你生成一个默认的拷贝构造函数。 这个默认的拷贝构造函数执行的操作是按位拷贝(浅拷贝),即将一个对象的每个成员变量的值复制给另一个对象的对应成员变量。

class Date{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print() {cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main(){Date d1;Date d2(d1);// 调用默认拷贝构造函数,创建对象d2d1.Print();d2.Print();return 0;
}

如果类中包含了动态分配的资源(比如使用 new 分配的内存),默认的拷贝构造函数执行的是浅拷贝,这可能导致两个对象共享相同的资源,而不是创建资源的副本。

而且当对象的生命周期结束时,析构函数清理动态分配的资源,但由于两个对象共享相同的资源,会将已经清理的空间再次清理,导致程序崩溃。


所以当中包含了动态分配的资源时,拷贝构造函数要由我们自己定义。这就是深拷贝。

class Date {
public:// 构造函数Date(const char* dateString) {// 假设 dateString 是通过 new 分配的内存data = new char[strlen(dateString) + 1];strcpy(data, dateString);}// 自定义的深拷贝构造函数Date(const Date& other) {// 分配新的内存data = new char[strlen(other.data) + 1];// 复制原始对象的数据到新分配的内存中strcpy(data, other.data);}// 析构函数~Date() {// 释放动态分配的内存delete[] data;}// 打印日期void printDate() const {std::cout << "Date: " << data << std::endl;}private:char* data;
};int main() {// 创建日期对象Date date1("2022-01-01");// 使用深拷贝创建另一个日期对象Date date2 = date1;// 打印两个日期对象date1.printDate();date2.printDate();return 0;
}

在这里插入图片描述


调用拷贝构造函数的三种情况

  1. 对象的初始化: 当一个对象通过另一个对象进行初始化时,拷贝构造函数会被调用。

    MyClass obj1;          // 调用默认构造函数
    MyClass obj2 = obj1;   // 调用拷贝构造函数
    //或者 MyClass obj2(obj1);
    
  2. 传递对象给函数: 当对象作为参数传递给函数时,拷贝构造函数会被调用。

    void someFunction(MyClass param) {// 在函数体内使用 param
    }MyClass obj3;
    someFunction(obj3);    // 调用拷贝构造函数
    
  3. 从函数返回对象: 当一个函数返回一个对象时,拷贝构造函数会被调用,用于创建返回对象的副本。

    MyClass createObject() {MyClass obj;return obj; // 调用拷贝构造函数
    }MyClass obj4 = createObject(); // 调用拷贝构造函数
    

五、拷贝赋值运算符

拷贝赋值运算符用于将一个已经存在的对象的值赋给另一个已经存在的对象。这个运算符通常用于确保对象之间的深度拷贝,特别是在涉及到动态分配的资源时。

拷贝赋值运算符的一般形式如下:

class MyClass {
public:// 拷贝赋值运算符MyClass& operator=(const MyClass& other) {// 检查是否是自赋值if (this != &other) {// 执行深拷贝操作,复制资源// 注意:需要释放当前对象可能持有的资源}return *this; // 返回当前对象的引用}// 其他成员函数和变量
};

拷贝赋值运算符返回一个对当前对象的引用,这样可以支持链式赋值操作(例如 a = b = c。在实现拷贝赋值运算符时,需要注意避免自赋值,以免在释放资源时导致错误。


如果在类中没有显式定义拷贝赋值运算符,编译器会自动生成一个默认的拷贝赋值运算符。这个默认生成的版本会按字节拷贝对象的每个成员变量,即浅拷贝。
例子:

class MyClass {
public:// 构造函数,成员初始化列表MyClass(int val) : value(val) {}// 打印数据void printData() const {std::cout << "Value: " << value << std::endl;}private:int value;
};int main() {// 创建对象MyClass obj1(42);// 使用默认生成的拷贝赋值运算符进行赋值MyClass obj2(0);obj2 = obj1;// 打印两个对象的数据obj1.printData();obj2.printData();return 0;
}

在这里插入图片描述
同样的,如果类需要管理动态分配的资源,需要显式提供拷贝构造函数和拷贝赋值运算符以确保正确的资源复制。


拷贝构造函数和拷贝赋值运算符的一些区别:

  • 时机不同: 拷贝构造函数在对象的创建和复制时被调用,而拷贝赋值运算符在对象已经存在的情况下进行赋值时被调用。

  • 用途不同: 拷贝构造函数通常用于对象的初始化和创建副本(类类型传参和函数返回类类型时),而拷贝赋值运算符用于对象的赋值操作。

  • 返回类型不同: 拷贝构造函数没有返回类型,而拷贝赋值运算符返回当前对象的引用(链式赋值操作)。


在这里插入图片描述
如果你喜欢这篇文章,点赞👍+评论+关注⭐️哦!
欢迎大家提出疑问,以及不同的见解。

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

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

相关文章

Redis的数据类型

目录 string 1.编码方式 2.应用场景 3.常用命令 hash 1.编码方式 2.应用场景 3.常用命令 list 1.编码方式 2.应用场景 3.常用命令 set 1.编码方式 2.应用场景 3.常用命令 zset 1.编码方式 2.应用场景 3.常用命令 如何理解Redis的编码方式 embs…

【Python】03快速上手爬虫案例三:搞定药师帮

文章目录 前言1、破解验证码2、获取数据 前言 提示&#xff1a;通过用户名、密码、搞定验证码&#xff0c;登录进药师帮网站&#xff0c;然后抓取想要的数据。 爬取数据&#xff0c;最终效果图&#xff1a; 1、破解验证码 使用药师帮测试系统&#xff1a;https://dianrc.ysb…

【保驾护航】HarmonyOS应用开发者基础认证-题库-2024

通过系统化的课程学习&#xff0c;熟练掌握DevEco Studio&#xff0c;ArkTS&#xff0c;ArkUI&#xff0c;预览器&#xff0c;模拟器&#xff0c;SDK等HarmonyOS应用开发的关键概念&#xff0c;具备基础的应用开发能力。 考试说明 1、考试需实名认证&#xff0c;请在考前于个…

有哪些ssl证书推荐

SSL证书是由CA认证机构颁发的数字证书&#xff0c;可以用来实现网站的身份验证和数据加密&#xff0c;保障用户与网站之间的通信安全。现在市场上知名的CA认证机构Digicert、Sectigo、Certum等&#xff0c;都已经成立了几十年&#xff0c;每年都要进行WebTrust安全审计&#xf…

HTTPS 之fiddler抓包--jmeter请求

一、浅谈HTTPS 我们都知道HTTP并非是安全传输&#xff0c;在HTTPS基础上使用SSL协议进行加密构成的HTTPS协议是相对安全的。目前越来越多的企业选择使用HTTPS协议与用户进行通信&#xff0c;如百度、谷歌等。HTTPS在传输数据之前需要客户端&#xff08;浏览器&#xff09;与服…

【Python笔记-设计模式】抽象工厂模式

一、说明 (一) 解决问题 抽象工厂是一种创建型设计模式&#xff0c;主要解决接口选择的问题。能够创建一系列相关的对象&#xff0c;而无需指定其具体类。 (二) 使用场景 系统中有多于一个的产品族&#xff0c;且这些产品族类的产品需实现同样的接口。 例如&#xff1a;有…

数字人解决方案VividTalk——音频驱动单张照片实现人物头像说话的效果

前言 VividTalk是一项由南京大学、阿里巴巴、字节跳动和南开大学共同开发的创新项目。该项目通过结合单张人物静态照片和一段语音录音&#xff0c;能够制作出一个看起来仿佛实际说话的人物视频。项目的特点包括自然的面部表情和头部动作&#xff0c;口型能够同步&#xff0c;同…

HarmonyOS 鸿蒙驱动消息机制管理

驱动消息机制管理 使用场景 当用户态应用和内核态驱动需要交互时&#xff0c;可以使用HDF框架的消息机制来实现。 接口说明 消息机制的功能主要有以下两种&#xff1a; 用户态应用发送消息到驱动。 用户态应用接收驱动主动上报事件。 表1 消息机制接口 方法描述struct …

防御保护笔记02

防火墙 防火墙的主要职责在于&#xff1a;控制和防护 ---- 安全策略 --- 防火墙可以根据安全策略来抓取流量 防火墙分类 按物理特性划分 软件防火墙 硬件防火墙 按性能划分 百兆级防火墙 吞吐量&#xff1a;指对网络、设备、端口、虚电路或其他设施&#xff0c;单位时间内成…

架构整洁之道-价值维度与编程范式

1 设计与架构究竟是什么 结论&#xff1a;二者没有任何区别&#xff0c;一丁点区别都没有。 架构图里实际上包含了所有底层设计细节&#xff0c;这些细节信息共同支撑了顶层的架构设计&#xff0c;底层设计信息和顶层架构设计共同组成了整个架构文档。底层设计细节和高层架构信…

最新GPT4.0使用教程,AI绘画-Midjourney绘画,GPT语音对话使用,DALL-E3文生图+思维导图一站式解决

一、前言 ChatGPT3.5、GPT4.0、GPT语音对话、Midjourney绘画&#xff0c;文档对话总结DALL-E3文生图&#xff0c;相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚至也可以和…

【HarmonyOS应用开发】ArkUI 开发框架-基础篇-第一部分(七)

常用基础组件 一、组件介绍 组件&#xff08;Component&#xff09;是界面搭建与显示的最小单位&#xff0c;HarmonyOS ArkUI声明式开发范式为开发者提供了丰富多样的UI组件&#xff0c;我们可以使用这些组件轻松的编写出更加丰富、漂亮的界面。组件根据功能可以分为以下五大类…

深入理解Redis:如何设置缓存数据的过期时间及其背后的机制

目录 Redis 给缓存数据设置过期时间 Redis是如何判断数据是否过期的呢&#xff1f; 过期的数据的删除策略 Redis 内存淘汰机制 Redis 给缓存数据设置过期时间 一般情况下&#xff0c;我们设置保存的缓存数据的时候都会设置一个过期时间。为什么呢&#xff1f; 因为内存是有…

Django模型(一)

一、介绍 模型&#xff0c;就是python中的类对应数据库中的表 1.1、ORM ORM 就是通过实例对象的语法&#xff0c;完成关系型数据库的操作的技术&#xff0c;是"对象-关系映射"&#xff08;Object/Relational Mapping&#xff09; 的缩写 ORM 把数据库映射成对象 1.…

【Linux】压缩脚本、报警脚本

一、压缩搅拌 要求&#xff1a; 写一个脚本&#xff0c;完成如下功能 传递一个参数给脚本&#xff0c;此参数为gzip、bzip2或者xz三者之一&#xff1b; (1) 如果参数1的值为gzip&#xff0c;则使用tar和gzip归档压缩/etc目录至/backups目录中&#xff0c;并命名为/backups/etc…

Java-并发高频面试题

1.说一下你对Java内存模型&#xff08;JMM&#xff09;的理解&#xff1f; 其实java内存模型是一种抽象的模型&#xff0c;具体来看可以分为工作内存和主内存。 JMM规定所有的变量都会存储再主内存当中&#xff0c;再操作的时候需要从主内存中复制一份到本地内存&#xff08;c…

C++(6) 继承

文章目录 继承1. 继承1.1 什么是继承1.2 C 继承方式1.2.1 基本案例1.2.2 继承权限组合1.2.3 继承中构造函数的说法1.2.4 继承中析构函数的执行顺序1.2.5 继承中变量名称冲突问题1.2.6 继承中函数【重写】 继承 1. 继承 1.1 什么是继承 面向对象程序设计中最重要的一个概念是继…

【linux】复制cp和硬连接、软连接的区别? innode 关系?

1.命令&#xff1a; cp -r [源文件或目录] [目的目录] #复制 ln -s [被链接的文件] [链接的目录/名称] #软连接 ln [被链接的文件] [链接的目录/名称] #硬连接 注&#xff1a;cp -r 会把所有source当作普通文件&#xff08;regular文件&#xff09;&#x…

把批量M3U8网络视频地址转为MP4视频

在数字媒体时代&#xff0c;视频格式的转换已成为一项常见的需求。尤其对于那些经常处理网络视频的用户来说&#xff0c;将M3U8格式的视频转换为更常见的MP4格式是一项必备技能。幸运的是&#xff0c;现在有了固乔剪辑助手这款强大的工具&#xff0c;这一过程变得异常简单。下面…

03:华为云管理|云主机管理|云项目实战

华为云管理&#xff5c;云主机管理&#xff5c;云项目实战 安全组配置部署跳板机配置yum源&#xff0c;安装软件包优化系统服务安装配置ansible管理主机 模版镜像配置配置yum源&#xff0c;安装软件包优化系统 网站云平台部署实战华为云的负载均衡 安全组配置 设置安全组 云…