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

news2024/9/24 9:24:37

在这里插入图片描述

前言
类与对象(二)


文章目录

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

一、特殊成员函数

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

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

二、构造函数

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

主要特性:

  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);// 调用默认拷贝构造函数,创建对象d2

    d1.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.coloradmin.cn/o/1420812.html

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

相关文章

Flutter 开发3:创建第一个Flutter应用

Step 1: 安装Flutter 1.1 下载Flutter SDK 首先&#xff0c;你需要访问Flutter官方网站下载最新的Flutter SDK。选择适合你操作系统的安装包。 $ cd ~/development $ unzip ~/Downloads/flutter_macos_2.2.3-stable.zip1.2 更新环境变量 接下来&#xff0c;你需要将Flutter…

笔记本电脑系统Win10重装教程

当前很多用户都会使用笔记本电脑办公&#xff0c;如果笔记本电脑携带的操作系统不好用&#xff0c;就会影响到用户的办公效率&#xff0c;这时候可以给笔记本电脑重新安装一款好用的系统。以下小编带来笔记本电脑系统Win10重装教程&#xff0c;让用户们轻松给笔记本电脑重新安装…

张维迎《博弈与社会》纳什均衡与囚徒困境博弈(2)囚徒困境博弈

囚徒困境大家应该都比较熟悉了&#xff0c;我觉得这篇的意义大概在与&#xff0c;经济学术语的运用&#xff1f; 囚徒困境&#xff1a;个人理性与集体理性的矛盾 假定有两个犯罪嫌疑人共同作案。警察抓住他们以后&#xff0c;分开拘押&#xff0c;并告诉他们&#xff1a;可以选…

GWIT 和GWFI

关于燃烧的历史&#xff1a; -UL request needle flame (open fire) test to rate flammability per UL-94 Vxx UL 要求针焰&#xff08;明火&#xff09;试验以评定UL-94的易燃性。 - industry recognized that glowing wires ( caused by electrical overload) may put …

《幻兽帕鲁》游戏公司如何打造全球爆款 《幻兽帕鲁Palworld》怎么在Mac上玩?

玩法融合之外&#xff0c;《幻兽帕鲁》设计的成功和难点其实是把大部分系统及玩法结合得更紧密&#xff0c;做到多个系统之间互相强化。 “下班&#xff0c;该当帕鲁训练家了。”近日&#xff0c;记者从多个游戏群中看到&#xff0c;《幻兽帕鲁》正在取代其他游戏&#xff0c;成…

蓝桥杯-常用STL(一)

常用STL &#x1f388;1.动态数组&#x1f388;2.vector的基础使用&#x1f52d;2.1引入库&#x1f52d;2.2构造一个动态数组&#x1f52d;2.3插入元素&#x1f52d;2.4获取长度并且访问元素&#x1f52d;2.5修改元素&#x1f52d;2.6删除元素&#x1f52d;2.7清空 &#x1f38…

抽象类(Java)、模板方法设计模式

一、概念 在Java中有abstract关键字&#xff0c;就是抽象的意思&#xff0c;可用来修饰类和成员方法。 用abstract来修饰类&#xff0c;那这个类就是抽象类&#xff1b;修饰方法&#xff0c;那这个方法就是抽象方法。 修饰符 abstract class 类名{修饰符 abstract 返回值类型…

知识库是什么 产品经理必须知道的行业知识

现如今&#xff0c;我们生活在一个知识爆炸的时代。对于产品经理来说&#xff0c;信息不再是稀缺资源&#xff0c;如何高效地管理和利用这些信息&#xff0c;是他们面临的重要问题。这时&#xff0c;知识库便悄然成为产品经理必备的工具。所以&#xff0c;什么是知识库呢&#…

Python网络拓扑库之mininet使用详解

概要 网络工程师、研究人员和开发人员需要进行各种网络实验和测试&#xff0c;以评估网络应用和协议的性能&#xff0c;以及解决网络问题。Python Mininet是一个功能强大的工具&#xff0c;它允许用户创建、配置和仿真复杂的网络拓扑&#xff0c;以满足各种实际应用场景。本文…

2024美赛备战--六大题型常用模型简要分析

美国大学生数学建模竞赛&#xff08;MCM&#xff09;是全球知名的数学建模比赛之一&#xff0c;每年都吸引了来自世界各地的学生参加。在这场充满挑战的竞赛中&#xff0c;参赛者将面对多种题目&#xff0c;需要利用他们的数学建模技能来解决实际问题。下面&#xff0c;建模忠哥…

深入了解Yum:Linux系统的软件包管理利器

目录 软件包 软件包的来源 关于yum yum是什么 yum的相关操作 介绍rzsz rz&#xff08;从Windows本地传到Linux服务器&#xff09; sz&#xff08;从Linux服务器传到Windows本地&#xff09; 注意事项 查看软件包 安装软件 卸载软件 yum的本地配置 为什么要进行配置…

Redis学习——高级篇④

Redis学习——高级篇④ Redis7高级之Redis与Mysql数据双写一致性工程案例&#xff08;四&#xff09; 4.1 MySQL主从复制原理4.2 canal 工作原理4.3 mySQL->canal->redis 双写一致性1.环境2.配置Mysql3.配置canal4. Canal客户端&#xff08;Java编写&#xff0…

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

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

Whatsapp 相关(七) -网络请求

本篇主要用来完善上篇文章 frida 监测网络请求的. whatsapp相关(五)- frida监测网络请求 1: 脚本 本次的脚本与上次的区别是,之前只能输出请求的地址,本次优化后,可输出请求参数,结果等. 代码如下: Java.perform(function () {var HttpURLConnection Java.use(java.net.H…

PyTorch][chapter 12][李宏毅深度学习][Semi-supervised Linear Methods-1]

这里面介绍半监督学习里面一些常用的方案&#xff1a; K-means ,HAC, PCA 等 目录&#xff1a; K-means HAC PCA 一 K-means 【预置条件】 N 个样本分成k 个 簇 step1: 初始化簇中心点 (随机从X中抽取k个样本点作为&#xff09; Repeat: For all in X: 根据其到 &…

MP4格式视频怎么提取gif?一招教你在线做

MP4是一种常见的数字多媒体容器格式&#xff0c;它是一种使用最广泛的视频文件格式之一。MP4文件可以包含音频、视频和字幕等多种媒体数据&#xff0c;并且可以通过各种播放器和设备进行播放和共享。它是一种压缩格式&#xff0c;可以在保持相对较小文件大小的同时提供较高的视…

数学建模学习笔记||灰色关联分析

灰色系统 信息绝对透明的是白色系统&#xff0c;信息绝对秘密的是黑色系统&#xff0c;灰色系统介于两者之间 关联分析 即系统的分析因素 包含多种因素的系统中&#xff0c;哪些因素是主要的&#xff0c;哪些因素是次要的&#xff0c;哪些因素影响大&#xff0c;哪些因素影响小…

Vue3下载WEBAPI导出的Excel文件

webApi查询数据保存为Excel /// <summary>/// 获取LMI3D相机涂胶测量数据/// </summary>/// <returns></returns>[HttpPost(Name "GetLMI3DGlueDataToExcel")]public async Task<IActionResult> GetLMI3DGlueDataToExcel(QueryGlueM…

搜维尔科技:「简报」元宇宙数字人赛道,优秀作品《黛妮卡》赏析

黛妮卡是宇宙航星局所特聘来研究纪录地球科技的星际种子。宇宙航星局汇集各星球的菁英并称他们为星际种子&#xff0c;星际种子有各自的任务&#xff0c;完成任务是他们的使命。 学校&#xff1a; 私立长荣高级中学(台湾) 选手&#xff1a; 郭伊珍 角色姓名&#xff1a; 黛妮…

Docker-Cgroup资源限制

目录 一、Cgroup 1.CGROUP概念 2.CGROUP 子系统 3.CGROUP 示例 二、内存资源限制 1.内存资源限制概念 2.内存资源限制参数 1 3.内存资源限制参数 2 三、CPU 资源限制 1.容器中 CPU 使用 2.容器 CPU 限制参数 四、实验演示 一、Cgroup 1.CGROUP概念 在linux内核里提…