【C++】---类和对象(中)默认成员函数 和 操作符重载

前言:

假如一个类中既没有成员变量也没有成员函数,那么这个类就是空类,空类并不是什么都没有,因为所有类都会生成如下6个默认成员函数:
在这里插入图片描述

一、构造函数

1、构造函数的定义及其特性

对于日期类对象,我们可能会忘记调用Init函数进行初始化,C++为了解决这个问题,引入构造函数进行初始化。

#include<iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
};int main()
{Date d1;d1.Init(2024, 2, 11);d1.Print();return 0;
}

构造函数一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次
特性:
1.构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
2.其特征如下:

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

3.默认构造函数是我们不传参就可以调用的函数

  • 我们什么都没写,编译器自动生成的
  • 我们自己写的:无参的构造函数
  • 我们自己写的:全缺省构造函数
    这三类只能存在一个,注意后两个:不能同时存在的原因:当定义一个不带参数的类对象时,编译器不能确定到底要调用我们写的无参默认构造函数还是要调用我们写的带参全缺省默认构造函数,会报“对重载函数的调用不明确错误”。
    (1) 我们什么都没写,编译器自动生成的
#include<iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
};int main()
{Date d1;//调用编译器自动生成的默认构造函数return 0;
}

(2)我们自己写的:无参的构造函数

#include<iostream>
using namespace std;class Date
{
public://1.无参默认构造函数:初始化对象Date(){_year = 2024;_month = 2;_day = 12;}private:int _year;int _month;int _day;
};int main()
{Date d1;return 0;
}

(3)我们自己写的:全缺省构造函数

#include<iostream>
using namespace std;class Date
{
public://2.带参全缺省默认构造函数:初始化对象Date(int year = 2024, int month = 2, int day= 12){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};int main()
{Date d1;//调用带参默认构造函数return 0;
}

2、编译器自动生成的默认构造函数

关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??
解答: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;
}

二、析构函数

1、析构函数的定义及其特性

析构函数用来完成类的资源清理工作,编译器在销毁对象时,会自动调用析构函数。
特性:

(1)析构函数名是在类名前加上字符 ~。

(2)无参数无返回值。(析构函数不能重载,一个类有且仅有一个析构函数)

(3)一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数

(4)对象生命周期结束时,C++编译系统系统自动调用析构函数。

2、多对象的析构顺序

假如这个类有多个对象,那么析构的先后顺序是什么?
多对象的析构顺序 :局部对象(后定义先析构) -> 局部的静态(后定义先析构) ->全局对象(后定义先析构)

// 局部对象(后定义先析构) -> 局部的静态(后定义先析构) ->全局对象(后定义先析构)
class Date
{
private:int _year;int _month;int _day;
public:Date(int year){_year = year;}~Date(){// 调用一次 析构函数 我就打印一次cout << "~Date()->" << _year << endl;}
};void func()
{// 局部域Date d4(4);static Date d5(5);static Date d10(10);
}// 全局域
Date d6(6);
static Date d7(7);
Date d8(8);
static Date d9(9);int main()
{// 局部域Date d1(1);Date d2(2);static Date d3(3);func();return 0;
}

3、编译器自动生成的默认析构函数

当不写析构函数时,编译器会自动生成默认的析构函数,不过这个默认的析构函数什么也不做,不需要清理资源。那么编译器自动生成的默认析构函数到底有什么用呢?

同析构函数

(1)对于内置类型,不会处理

(2)对于自定义类型,会调用它的析构函数

三、拷贝构造函数

1、拷贝构造函数定义及特性

1.拷贝构造函数只有单个形参,该形参是对同类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
2.特征:
拷贝构造函数也是特殊的成员函数,其特征如下:

  • 拷贝构造函数是构造函数的一个重载形式。
  • 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错因为会引发无穷递归调用

思考:为什么使用传值方式会引发无穷递归调用?
C++规定:对自定义类型的函数传值传参时,都会调用拷贝构造函数!!!
在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS 1 
#include <stdio.h>
#include <iostream>using namespace std;class Date
{
private:int _year;int _month;int _day;
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}// 构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造函数//Date(Date d)// 错误写法 使用传值方式编译器直接报错,因为会引发无穷递归调用Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}// 析构函数~Date(){}
};int main()
{Date d1(2024,2,12);Date d2(d1);d1.Print();d2.Print();return 0;
}

拷贝构造函数也是构造函数,函数名和类型名相同,参数是同类型对象的引用,由编译器自动调用。
因此,对于自定义类型的对象,一般推荐使用传引用传参,虽然传值传参也可以,但是要调用拷贝构造

(1)对其拷贝构造函数的理解

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}Date(int year = 2024, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 析构函数~Date(){cout << "~Date()" << endl;//在析构函数内打印,调用一次就打印一次}//拷贝构造函数Date(const Date& d)  // Date (Date* this ,Date &d)// 因为d2调用拷贝构造函数,所以&d2=this, (d是d1的别名){_year = d._year;_month = d._month;_day = d._day;}};int main()
{Date d1(2024,2,16);// 调用构造函数Date d2(d1);// 调用拷贝构造函数,就是在定义对象的时候直接用拷贝构造函数// Date(&d2,Date& d1)d2.Print();return 0;
}

在这里插入图片描述

2、深浅拷贝

1.传值传参:是浅拷贝
浅拷贝的缺点:
两个数组的指针指向同一块空间,当进行free时只能 free一个,那么另外一个就会变成“野指针”!
动态开辟资源的,浅拷贝都不行,因为浅拷贝只会仅仅copy数据。
在这里插入图片描述
2.传引用传参:是深拷贝:深拷贝还需要我们自己去写,编译器不会自动生成。
在这里插入图片描述
在这里插入图片描述
在判断一个类里面需不要写拷贝构造函数,根据具体情况而定。如果没有动态开辟的程序,就不需要写深拷贝。

3、编译器自动生成的拷贝构造函数

若未显式定义,系统会生成默认拷贝构造函数。 同构造函数和析构函数不同:

(1)拷贝构造函数对内置类型依次按照字节序完成拷贝,即浅拷贝或值拷贝。

假如不写拷贝构造函数,照样正常打印:

#define _CRT_SECURE_NO_WARNINGS 1 
#include <stdio.h>
#include <iostream>using namespace std;class Date
{
private:int _year;int _month;int _day;
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}// 构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}拷贝构造函数Date(Date d)// 错误写法 使用传值方式编译器直接报错,因为会引发无穷递归调用//Date(const Date& d)//{//	_year = d._year;//	_month = d._month;//	_day = d._day;//}// 析构函数~Date(){}
};int main()
{Date d1(2024,2,12);Date d2(d1);d1.Print();d2.Print();return 0;
}

在这里插入图片描述

四、赋值运算符重载函数

1、运算符重载

1.定义:对于内置类型来说,语言层面本身就支持已经定义好的运算符,但对于自定义类型来说不行, C++中规定运算符重载的原因是:让自定义类型可以像内置类型一样使用运算符,想重载哪个运算符就重载哪个运算符。
运算符重载的语法:
函数原型:返回值类型 operator操作符(参数列表)
2.特性:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针
  5. (.*) (::) (sizeof) (?;) (.) 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

运算符重载实例:

(1)==运算符重载

#include<iostream>
using namespace std;class Date
{
public://构造函数Date(int year = 2024, int month = 2, int day = 16){_year = year;_month = month;_day = day;}//成员变量公有
public:int _year;int _month;int _day;
};//operator==运算符重载
bool operator==(Date x1, Date x2)
{return x1._year == x2._year&& x1._month == x2._month&& x1._day == x2._day;
}int main()
{Date d1(2024, 2, 15);Date d2(2024, 2, 16);//两种调用方式://1.可读性不强operator==(d1, d2);//2.当编译器看到==自定义类型,会去检查日期类有没有==的重载运算符,如果有重载会转换成operator==(d1, d2)去调用operator==函数d1 == d2;return 0;
}

2、运算符重载和函数重载的区别:

运算符重载和函数重载,虽然都使用了重载,但是两者之间没有关联:

(1)函数重载时支持定义同名函数

(2)运算符重载是为了让自定义类型可以像内置类型一样去使用运算符。

3、赋值运算符重载

1.赋值运算符重载语法格式:

  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义

赋值运算符重载和拷贝构造的区别:

(1)拷贝构造函数:对即将要创建的新对象进行初始化(不过初始化的内容是将一个已经存在的对象拷贝给他)

(2)赋值运算符重载:两个已经存在的对象进行赋值

2.赋值运算符重载代码 以及如何调用!

class Date
{
private:int _year;int _month;int _day;
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}Date(int year = 2024, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 析构函数~Date(){cout << "~Date()" << endl;//在析构函数内打印,调用一次就打印一次}//拷贝构造函数Date(const Date& d)  // Date (Date* this ,Date &d)// 因为d2调用拷贝构造函数,所以&d2=this, (d是d1的别名){_year = d._year;_month = d._month;_day = d._day;}//赋值运算符重载// d1=d2   //d1.operator=(&d1,d2)Date& operator=(const Date& d) // void Date& operator=(&d1,const Date& d){if (this != &d) // 对d取地址,判断this的值和d的地址是否相同,如果不是自己给自己赋值,才需要拷{_year = d._year;_month = d._month;_day = d._day;}return *this;}};int main()
{//Date d1(2024,2,16);// 调用构造函数//Date d2(d1);// 调用拷贝构造函数,就是在定义对象的时候直接用拷贝构造函数 Date(&d2,Date& d1)//d2.Print();Date d1(2024, 2, 16);d1.Print();Date d2;d2.Print();d2 = d1;d2.Print();return 0;
}

在这里插入图片描述

4、const修饰类的成员函数

定义:将const修饰的类成员函数称为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

比如有如下场景:假如把Date类的operator==运算符重载函数写错了

bool operator==(const Date& d)
{return (_year == d._year)&& (_month == d._month)&& (_day == d._day);
}

将其中的一个"==“错写成”=" :

bool operator==(const Date& d) //bool operator==(Date* this,const Date& d)
{return (_year == d._year)&& (_month = d._month)&& (_day == d._day);
}

虽然编译没有问题,但是这会导致this的值被修改了,并且执行结果也错误:

int main()
{Date d1(2024, 2, 16);Date d2(2024, 3, 16);cout << (d1 == d2) << endl;d1.Print();d2.Print();return 0;
}

这不符合要求,仅仅是比较而已,但是被比较对象的值却被修改了。const最大的作用是保护对象和变量,d2传给了d,d是d2的别名,const已经保护了d,那d1如何保护呢?由于this是隐含的,那么const为了保护this,应该如何加?把const加在成员函数的后面,叫做const修饰成员函数

bool operator==(const Date& d) const
{return (_year == d._year)&& (_month = d._month)&& (_day == d._day);
}

总结:
1.const引用:我是你的别名,但我不能修改你(是之前:权限的缩小)
比如:const int& c = a; // c是a的别名,但由于const修饰c所以说不能通过c来修a的值。
2.如果有const的修饰,那么说明这个变量是只能读的!如果没有const修饰,普通的变量它是可读可写的!
在这里插入图片描述

五、总结:

1.构造函数和析构函数
如果我们不写,编译器对内置类型不做处理,自定义类型会调用它的构造函数和析构函数进行处理

2.拷贝构造和赋值运算符重载
如果我们不写,内置类型会完成浅拷贝,自定义类型会调用它的拷贝构造函数和赋值运算符重载函数。

3.取地址操作符重载和const取地址操作符重载
一般不需要重载,编译器默认生成的已经够用,重载没有价值。


好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!
在这里插入图片描述

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

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

相关文章

VBA技术资料MF119:数据验证的添加与删除

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

如何在没有第三个变量的情况下进行两个变量交换大小

昨天我看到了一个很有意思的一个面试题&#xff1a;如何在没有第三个变量的情况下进行两个变量交换大小 大家再在平时进行两个变量交换大小的时候通常会进行将其中的一个赋值给第三个变量&#xff0c;借助第三个变量进行两个变量交换&#xff0c;如下&#xff1a; #include &…

java8-用流收集数据-6

本章内容口用co1lectors类创建和使用收集器 口将数据流归约为一个值 口汇总:归约的特殊情况 数据分组和分区口 口 开发自己的自定义收集器 我们在前一章中学到&#xff0c;流可以用类似于数据库的操作帮助你处理集合。你可以把Java8的流看作花哨又懒惰的数据集迭代器。它们…

Nuxt3+Vue3(Composition API)+TS+Vite+Ant Design Vue 搭建

最近官网搭建选择了nuxtjs&#xff0c;由于框架更新了&#xff0c;其中语法也有很多变化&#xff0c;中间遇到了一些问题点做下总结。 nuxt3官方文档地址&#xff1a;https://nuxt.com/docs/getting-started/installation 安装 在安装Nuxt3之前&#xff0c;你需要保证你的nod…

中科星图——LANDSAT_8/02/T1/RAW的Landsat8_C2_RAW类数据集

数据名称&#xff1a; Landsat8_C2_RAW 数据来源&#xff1a; USGS 时空范围&#xff1a; 2020年1月-2023年3月 空间范围&#xff1a; 全国 数据简介&#xff1a; Landsat8_C2_RAW数据集是经过缩放和校准的辐射亮度产品&#xff0c;按照数据质量划分为T1和T2。数据质量…

进程地址空间

虚拟地址&#xff1a; 引子&#xff1a; 我们在Linux中使用fork函数创建子进程时是否会产生一下几个疑惑。 为什么一个变量id具有两个值&#xff1f; 学习C语言后&#xff0c;我们都知道一个函数是不会返回两个值的&#xff0c;那么我们只能猜测是fork函数return了两次。 事实…

(16)Hive——企业调优经验

前言 本篇文章主要整理hive-3.1.2版本的企业调优经验&#xff0c;有误请指出~ 一、性能评估和优化 1.1 Explain查询计划 使用explain命令可以分析查询计划&#xff0c;查看计划中的资源消耗情况&#xff0c;定位潜在的性能问题&#xff0c;并进行相应的优化。 explain执行计划…

基于Python实现的元宵节猜灯谜兑奖软件源码,输入灯谜序号,获取谜面及谜底

基于Python实现的元宵节猜灯谜兑奖软件源码&#xff0c;输入灯谜序号&#xff0c;获取谜面及谜底 核心代码&#xff1a; import sys from time import sleep import xlrd import os import tkintertable_listA "" table_listB ""filename os.getcwd()&…

一个简单的链接脚本,用来将两个以上的.o文件合并在一起,以及如何看机器码的含义

下面是一个简单的lds链接脚本&#xff0c;用来将两个以上的.o文件合并在一起&#xff0c;并且用来指定链接后的代码加载到内存中的起始地址。 SECTIONS 说明这是段的描述脚本 {. 0xD0020010; 加载代码到内存中的起始地址为0xD0020010.text : { 代码段mystart.o mystar…

Java图形化界面编程——五子棋游戏 笔记

2.8.5 五子棋 接下来&#xff0c;我们使用之前学习的绘图技术&#xff0c;做一个五子棋的游戏。 注意&#xff0c;这个代码只实现了五子棋的落子、删除棋子和动画等逻辑实现&#xff0c;并没有把五子棋的游戏逻辑编写完整&#xff0c;比较简单易上手。 图片素材 package…

[嵌入式系统-14]:常见实时嵌入式操作系统比较:RT-Thread、uC/OS-II和FreeRTOS、Linux

目录 一、实时嵌入式操作系统 1.1 概述 1.2 什么“实时” 1.3 什么是硬实时和软实时 1.4 什么是嵌入式 1.5 什么操作系统 二、常见重量级操作系统 三、常见轻量级嵌入式操作系统 3.1 概述 3.2 FreeRTOS 3.3 uC/OS-II 3.4 RT-Thread 3.5 RT-Thread、uC/OS-II、Free…

智能汽车行业产业研究报告:毫米波雷达优势明显,核心壁垒是芯片、天线阵列、波形设计

今天分享的是智能汽车系列深度研究报告&#xff1a;《智能汽车行业产业研究报告&#xff1a;毫米波雷达优势明显&#xff0c;核心壁垒是芯片、天线阵列、波形设计》。 &#xff08;报告出品方&#xff1a;国泰君安证券&#xff09; 报告共计&#xff1a;67页 毫米波雷达被广泛…