【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.mzph.cn/news/656432.shtml

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

相关文章

Android studio打包apk比较大

1.遇到的问题 在集成linphone打包时发现有118m&#xff0c;为什么如此之大额。用studio打开后发现都是c不同的pu架构。 2.解决办法 增加ndk配置&#xff0c;不选配置那么多的cpu结构&#xff0c;根据自己需要调整。 defaultConfig { applicationId "com.matt.linphoneca…

备战蓝桥杯---数据结构与STL应用(基础3)

今天我们主要介绍的是pair,string,set,map pair:我们可以把它当作一个结构体&#xff1a; void solve(){pair<int int> a;//创建amake_pair(1,2);//添加元素cout<<a.first<<endl<<a.second<<endl;}//输出 当然&#xff0c;它也可以嵌套&#…

python笔记10

1、继承 继承是面向对象编程中的一个重要概念&#xff0c;它允许一个类&#xff08;子类&#xff09;继承另一个类&#xff08;父类&#xff09;的属性和方法。通过继承&#xff0c;子类可以重用父类的代码&#xff0c;并且有机会添加新的属性和方法&#xff0c;或者重写父类的…

使用PowerBI 基于Adventure Works案例分析

Adventure Works案例分析 前言 数据时代来临&#xff0c;但一个人要顺应时代的发展是真理。 数据分析的核心要素 那数分到底是什么&#xff1f; 显然DT 并不等同于 IT&#xff0c;我们需要的不仅仅是更快的服务器、更多的数据、更好用的工具。这些都是重要的组成部分&…

堆宝塔

L2-1 堆宝塔 分数 25 作者 陈越 单位 浙江大学 堆宝塔游戏是让小朋友根据抓到的彩虹圈的直径大小&#xff0c;按照从大到小的顺序堆起宝塔。但彩虹圈不一定是按照直径的大小顺序抓到的。聪明…

AI特训一:为什么要学习AI

我们先了解什么是AI AI&#xff08;人工智能&#xff09;是指计算机系统经过学习和推理能够模拟人类智能行为的一种技术。AI利用机器学习、深度学习、自然语言处理等技术&#xff0c;能够分析大量的数据、识别模式、做出决策和预测 AI有哪些强大之处 处理大量数据&#xff1a…

专栏:数据库、中间件的监控一网打尽

前言 对于数据库、中间件的监控&#xff0c;目前社区里最为完善的就是 Prometheus 生态的各个 Exporter&#xff0c;不过这些 Exporter 比较分散&#xff0c;不好管理&#xff0c;如果有很多目标实例需要监控&#xff0c;就要部署很多个 Exporter&#xff0c;要是能有一个大一…

蓝桥杯嵌入式——测量两路PWM频率和占空比

原理&#xff1a;在通道1&#xff0c;TIM_CH1上会输入PWM波&#xff0c;在每个上升沿来的时候会发生三个动作&#xff0c;第一个动作会触发一个中断&#xff0c;第二个动作会把CNT计数值捕获&#xff0c;第三个动作会把CNT的值清0&#xff0c; 要测量占空比则需要打开TI1FP2&a…

经济学基础入门,从《小岛经济学》看经济的演变

一直在学习怎么赚钱&#xff0c;没有专业的人指导。于是就想着先学习一下经济学相关的知识吧&#xff01;无意间看到大家推荐的这本书籍&#xff0c;一本很适合经济学入门的经济《小岛经济学》&#xff0c;这本书以故事的方式&#xff0c;讲解了经济演变过程。而且大约只需要2到…

对嵌入式系统、GCC、的理解

1、嵌入式系统通用硬件组成 2、Linux系统的嵌入式的开发流程 3、Linux系统的嵌入式的结构 4、文件IO和标准IO&#xff1f; 5、为什么需要交叉编译 1、嵌入式系统硬件的限制&#xff08;CPU处理能力不如电脑、存储空间小、网络带宽小不利于传输、安全性不如电脑、能耗问题&…

【计算机网络】——TCP协议

&#x1f4d1;前言 本文主要是【计算机网络】——传输层TCP协议的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句…

【学网攻】 第(16)节 -- 扩展ACL访问控制列表

系列文章目录 目录 系列文章目录 文章目录 前言 一、ACL(访问控制列表)是什么 ? 二、实验 1.引入 实验目标 实验步骤 实验设备 实验拓扑图 实验配置 配置ACL访问控制 实验验证 总结 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认识…

2024 年, Web 前端开发趋势

希腊哲学家赫拉克利特认为&#xff0c;变化是生命中唯一不变的东西。这句话适用于我们的个人生活、行业和职业领域。 尤其是前端开发领域&#xff0c;新技术、开发趋势、库和框架不断涌现&#xff0c;变化并不陌生。最近发生的一些事件正在改变开发人员构建网站和 Web 应用的方…

Python机器学习--简单清晰的说说K近邻算法的基本原理

K近邻算法的基本原理&#xff1a;首先通过所有的特征变量构筑起一个特征空间&#xff0c;特征空间的维数就是特征变量的个数&#xff0c;然后针对某个测试样本&#xff0c;按照参数K在特征空间内寻找与它最为近邻的K个训练样本观测值&#xff0c;最后依据这K个训练样本的响应变…

快速理解MoE模型

最近由于一些开源MoE模型的出现&#xff0c;带火了开源社区&#xff0c;为何&#xff1f;因为它开源了最有名气的GPT4的模型结构&#xff08;OPEN AI&#xff09;&#xff0c;GPT4为何那么强大呢&#xff1f;看看MoE模型的你就知道了。 MoE模型结构&#xff1a; 图中&#xff0…

Netty-ChannelHandle的业务处理

ChannelHandle结构 ChannelHandler基础接口 基础接口里面定义的基础通用方法。增加handler&#xff0c;移除handler&#xff0c;异常处理。 ChannelInboundHandler public interface ChannelInboundHandler extends ChannelHandler {/*** The {link Channel} of the {link Ch…

猫突然不吃东西没精神?性价比高可以迅速恢复精神的生骨肉冻干推荐

猫突然不吃东西没精神怎么办&#xff1f;当猫咪不吃东西、精神不振时&#xff0c;可能是由于健康问题、环境因素或食物原因所引起。首先应进行身体检查&#xff0c;观察是否有其他并发症&#xff0c;如无则可排除健康问题。为猫咪提供安全舒适的环境、给予关爱&#xff0c;可改…

亚信安慧AntDB:AntDB-M元数据锁(七)

5.4.5 慢路径锁的授予条件 当且仅当满足如下两个条件时&#xff0c;才可以授予锁。 1. 其他线程没有持有不兼容类型锁。 2. 当前申请的锁的优先级高于请求等待列表中的。 首先通过锁位图判断等待队列&#xff0c;不兼容则不能授予锁。再判断快速路径&#xff0c;不兼容则不…

状态码400以及状态码415

首先检查前端传递的参数是放在header里边还是放在body里边。 此图前端传参post请求&#xff0c;定义为’Content-Type’&#xff1a;‘application/x-www-form-urlencoded’ 此刻他的参数在FormData中。看下图 后端接参数应为&#xff08;此刻参数前边什么都不加默认为requestP…

Qt QScrollArea 不显示滚动条 不滚动

使用QScrollArea时&#xff0c;发现添加的控件超出QScrollArea 并没有显示&#xff0c;且没有滚动条效果 原因是 scrollArea指的是scrollArea控件本身的大小&#xff0c;肉眼能看到的外形尺寸。 scrollAreaWidgetContents指的是scrollArea控件内部的显示区域&#xff0c;里面可…