【类与对象】②认识类的六个默认函数

news2024/11/18 9:23:26

文章目录

  • 1.类的六个默认函数
  • 2.构造函数
  • 3.析构函数
  • 4.拷贝构造函数
  • 5.赋值运算符重载
  • 6.const成员

在这里插入图片描述

1.类的六个默认函数

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员
函数。默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
在这里插入图片描述

2.构造函数

class Date
{
public:
void Init(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 d1;
d1.Init(2022, 7, 5);
d1.Print(); Date d2;
d2.Init(2022, 7, 6);
d2.Print();
return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置 信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
特征如下:

  • 函数名与类名相同。
  • 无返回值。
  • 对象实例化时编译器自动调用对应的构造函数。
  • 构造函数可以重载。
    例如:
class Date
{
public:
// 1.无参构造函数
Date()
{}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month; _day = day;
}
private:
int _year; int _month; int _day;
};
void TestDate()
{
Date d1; // 调用无参构造函数 
Date d2(2015, 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
Date d3(); //声明了d3函数,该函数无参,返回一个日期类型的对象
}
  • 如果类中没有显式定义构造函数,则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类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
Date d1;// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
return 0;
}
  • 关于编译器生成的默认成员函数,可能会有很多疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d1对象调用了编译器生成的默认构造函数,但是d1对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的 默认构造函数并没有什么用??

解:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是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;
}

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

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 = 1970;  //此处为C++的补丁,这不是初始化,而是缺省值
	int _month = 1; 
	int _day = 1;
	
	Time _t;// 自定义类型
};
int main()
{
Date d;
return 0;
}
  • 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

3.析构函数

问:通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

特性

  • 析构函数名是在类名前加上字符 ~。
  • 无参数无返回值类型。
  • 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
  • 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class Time
{
public:
~Time()
{
	cout << "~Time()" << endl;
}
private:
	int _hour; int _minute; int _second;
};
class Date
{
private:
	int _year = 1970; int _month = 1; int _day = 1;// 基本类型(内置类型)
	Time _t;// 自定义类型
};
int main()
{
Date d;
return 0;
}

解析:
程序运行结束后输出:~Time(),在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对 象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date 类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁 ,main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数。

注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

4.拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

特性

  • 拷贝构造函数是构造函数的一个重载形式。
  • 拷贝构造函数的参数只有一个且必须是类类型对象的引用,若使用传值方式则编译器直接报错,因为会引发无穷递归调用。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
	_year = year;
	 _month = month; 
	 _day = day;
}
// Date(const Date d)   // 错误写法 :编译报错,会引发无穷递归
Date(const Date& d)   // 拷贝构造正确写法
{
	_year = d._year;
	 _month = d._month; 
	 _day = d._day;
}
private:
int _year; int _month; int _day;
};
int main()
{
Date d1; Date d2(d1);
return 0;
}
  • 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

浅拷贝:一个对象修改会影响另一个对象,会析构两次,导致程序崩溃。

class Time
{
public:
Time()
{
	_hour = 1;
	 _minute = 1; 
	 _second = 1;
	 }
Time(const Time& t)
{
	_hour = t._hour; 
	_minute = t._minute;
	 _second = t._second;
	cout << "Time::Time(const Time&)" << endl;
}
private:
	int _hour;
	 int _minute; 
	 int _second;
};
class Date
{
private:
	int _year = 1970;// 基本类型(内置类型)
	int _month = 1; 
	int _day = 1;
	Time _t;// 自定义类型
};
int main()
{
Date d1;
Date d2(d1);// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数,但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数.
return 0;

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

拷贝构造函数典型调用场景:

  • 使用已存在对象创建新对象。
  • 函数参数类型为类类型对象。
  • 函数返回值类型为类类型对象。

5.赋值运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

区分运算符重载与函数重载:

  • 运算符重载:让自定义类型对象可以用运算符,转换成调用这个重载函数。
  • 函数重载:支持函数名相同的函数同时存在。

运算符重载和函数重载虽然都用了重载这个词,但是他们之间没有必然联系。

运算符重载格式:
函数名字:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表) 。

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数。
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .* :: sizeof ?: . 注意这5个运算符不能重载
// 全局的operator==
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
	_year = year; 
	_month = month; 
	_day = day;
}
//private:
	int _year; 
	int _month; 
	int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year 
		&& d1._month == d2._month 
		&& d1._day == d2._day;
}
void Test ()
{
	Date d1(2018, 9, 26);
	Date d2(2018, 9, 27);
	cout<<(d1 == d2)<<endl;
}
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
	_year = year; 
	_month = month; 
	_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 这里需要注意的是,左操作数是this,指向调用函数的对象
bool operator==(const Date& d2)
{
return _year == d2._year; 
		&& _month == d2._month;
		&& _day == d2._day;
}
private:
	int _year; 
	int _month; 
	int _day;
};

赋值运算符重载格式

  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义
class Date
{
public :
Date(int year = 1900, int month = 1, int day = 1)
{
	_year = year; 
	_month = month; 
	_day = day;
}
Date (const Date& d)
{
	_year = d._year; 
	_month = d._month; 
	_day = d._day;
}
Date& operator=(const Date& d)
{
if(this != &d)
{
	_year = d._year; 
	_month = d._month; 
	_day = d._day;
}
return *this;
}
private:
	int _year ; 
	int _month ; 
	int _day ;
};

赋值运算符只能重载成类的成员函数不能重载成全局函数

class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
	_year = year; 
	_month = month; 
	_day = day;
}
	int _year; 
	int _month; 
	int _day;
};
Date& operator=(Date& left, const Date& right)  // 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
{
if (&left != &right)
{
	left._year = right._year; 
	left._month = right._month; 
	left._day = right._day;
}
return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

  • 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

前置++和后置++重载

class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
	_year = year; 
	_month = month; 
	_day = day;
}
// 前置++:返回+1之后的结果
// 注意:this指向的对象在函数结束后不会销毁,故以引用方式返回提高效率
Date& operator++()
{
	_day += 1;
	return *this;
}
// 后置++:
// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器 自动传递
// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
	Date temp(*this); 
	_day += 1;
	return temp;
}
private:
	int _year; 
	int _month; 
	int _day;
};
int main()
{
	Date d;
	Date d1(2022, 1, 13);
	d = d1++;    // d: 2022,1,13   d1:2022,1,14 d = ++d1;    // d: 2022,1,15   d1:2022,1,15
	return 0;
}

6.const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针所指向的内容,表明在该成员函数中不能对类的任何成员进行修改,从而保证了成员函数内部不会修改成员变量。
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

算法通关村14关 | 数据流中位数问题

1. 数据流中位数问题 题目 LeetCode295: 中位数是有序列表中间的数&#xff0c;如果列表长度是偶数&#xff0c;中位数是中间两个数的平均值&#xff0c; 例如:[2,3,4]的中位数是3&#xff0c; [2,3]中位数是&#xff08;23&#xff09;/ 2 2.5 设计一个数据结构&#xff1a; …

使用python对光谱进行lorentz峰值拟合并作图(标注峰值点位)

承接&#xff1a; 使用python对光谱进行lorentz峰值拟合 接下来是对图象的处理&#xff0c;即作图。 import numpy as np import matplotlib.pyplot as plt from scipy.optimize import curve_fit# 定义Lorentzian函数 def lorentzian(x, x0, A, gamma):return A * gamma**2…

外贸企业如何借助CRM提升企业发展?

外贸企业竞争激烈&#xff0c;提高自身竞争力&#xff0c;扩大海外业务市场&#xff0c;是每个外贸企业的目标。为了实现这一目标&#xff0c;不少外贸企业借助CRM系统&#xff0c;优化业务流程&#xff0c;管理维护客户&#xff0c;从而实现可持续发展。那么&#xff0c;外贸企…

git 忽略已经提交的文件或文件夹 (修改.gitignore文件无效)

场景描述&#xff1a;项目开发到一半&#xff0c;追加了模块&#xff0c;提交的时候未注意将不需要提交的文件或者目录提交到.gitignore&#xff0c;然后提交后发现再修改git配置文件已无法阻拦更新&#xff0c;查阅官方资料&#xff1a; 核心点&#xff1a;.gitignore 之前&a…

大数据之hadoop入门

大数据概念 大数据&#xff1a;无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合&#xff0c;是需要新处理模式才能具有更强的决策力、洞察发现李和流程优化能力的海量、高增长率和多样化的信息资产。 大的概念是相对来说的&#xff1a;目前来说&#xff0…

基于51单片机的SHT11温湿度上下限LCD12864显示报警仿真设计( proteus仿真+程序+原理图+报告+讲解视频)

51单片机SHT11温湿度上下限LCD12864显示报警仿真设计( proteus仿真程序原理图报告讲解视频&#xff09; 讲解视频1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&下载链接 51单片机SHT11温湿度上下限LCD12864显示报警仿真设计( proteus仿…

实现跨境电商测评和采退、LU卡、LU货最安全的系统方案

首先你要有一个稳定的测评环境系统&#xff0c;这个是做自养号退款、撸货、撸卡的基础。测评环境系统有很多&#xff0c;从早期的虚拟机&#xff0c;模拟机&#xff0c;云手机&#xff0c;VPS等等。这些系统方案先不说成本高&#xff0c;最重要的是成功率很低&#xff0c;所以一…

Git和Github的基本用法

目录 背景 下载安装 安装 git for windows 安装 tortoise git 使用 Github 创建项目 注册账号 创建项目 下载项目到本地 Git 操作的三板斧 放入代码 三板斧第一招: git add 三板斧第二招: git commit 三板斧第三招: git push 小结 &#x1f388;个人主页&#xf…

基于单片机的串行通信发射机设计

一、项目介绍 串行通信是一种常见的数据传输方式&#xff0c;允许将数据以比特流的形式在发送端和接收端之间传输。当前实现基于STC89C52单片机的串行通信发射机&#xff0c;通过红外发射管和接收头实现自定义协议的数据无线传输。 二、系统设计 2.1 单片机选择 在本设计中&…

黑马 大事件项目 笔记

学习视频&#xff1a;黑马 Vue23 课程 后台数据管理系统 - 项目架构设计 在线演示&#xff1a;https://fe-bigevent-web.itheima.net/login 接口文档: https://apifox.com/apidoc/shared-26c67aee-0233-4d23-aab7-08448fdf95ff/api-93850835 接口根路径&#xff1a; http:/…

系统架构技能之设计模式-抽象工厂模式

一、上篇回顾 上篇我们主要讲述了简单工厂模式和工厂模式。并且分析了每种模式的应用场景和一些优缺点&#xff0c;我们现在来回顾一下&#xff1a; 简单工厂模式&#xff1a;一个工厂负责所有类型对象的创建&#xff0c;不支持无缝的新增新的类型对象的创建。 工厂模式&…

系统架构技能之设计模式-工厂模式

一、开篇 本文主要是讲述设计模式中最经典的创建型模式-工厂模式&#xff0c;本文将会从以下几点对工厂模式进行阐述。 本文将会从上面的四个方面进行详细的讲解和说明&#xff0c;当然会的朋友可以之处我的不足之处&#xff0c;不会的朋友也请我们能够相互学习讨论。 二、摘…

学习心得06:Halcon

据说实际场景中&#xff0c;使用Halcon的多。功能上跟OpenCV差不多&#xff0c;轮廓&#xff0c;线条&#xff0c;边缘&#xff0c;连通&#xff0c;滤波&#xff0c;深度学习等。 需要了解的是&#xff0c;如何与其他语言对接。这当然也不难&#xff0c;人家早就考虑好了。 看…

企业架构LNMP学习笔记4

企业服务器LNMP环境搭建&#xff1a; 常见的软件架构&#xff1a; 1&#xff09;C/S: client/server 2&#xff09;B/S: browser/server 不管是C还是B&#xff0c;都是属于客户端属于前端。那么运维人员主要是负责和管理的Server端&#xff0c;也统称为服务器端。为了快速的…

[标注工具]rolabelImg旋转框标注工具安装和使用教程

rolabelImg是一个专门用于标注旋转框的工具&#xff0c;目前最新版本是3.0版本&#xff0c;rolabelImg是python编写的&#xff0c;为了避免安装python环境和配置。我们可以直接使用安装包安装&#xff0c;首先我们打开安装包 双击打开安装包 选择安装路径&#xff0c;不要是中文…

Linux-Centos7安装Docker

文章目录 一、前言二、Docker安装1、Docker及系统版本2、Docker的自动化安装3、Docker手动安装3.1、卸载Docker&#xff08;可选&#xff09;3.2、设置源仓库3.3、Docker安装3.4、Docker启动3.5、验证是否安装成功3.5.1、拉取镜像3.5.2、查看镜像3.5.3、运行镜像 3.6、删除Dock…

Spring-Cloud-Openfeign如何传递用户信息?

用户信息传递 微服务系统中&#xff0c;前端会携带登录生成的token访问后端接口&#xff0c;请求会首先到达网关&#xff0c;网关一般会做token解析&#xff0c;然后把解析出来的用户ID放到http的请求头中继续传递给后端的微服务&#xff0c;微服务中会有拦截器来做用户信息的…

一,表单标签

一,表单标签 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>常用标签</title></head><body><!-- 1.表单标签:比表格多了数据提交的功能1.1, 要求:必须使用form标签 必须有提交按钮 必须配置name属…

springcloud3 注册中心以及cloud启动原理总结(含面试)

一 Springcloud微服务面试题 1.1 为何使用注册中心 1)问题描述 在多个单体微服务之间&#xff0c;可以直接通过http请求进行通信&#xff0c;但是存在以下问题&#xff1a; 1.调用服务提供者时需要写ip和端口&#xff0c;如果出现ip和端口进行了修改&#xff0c;没有及时告…

SAP_ABAP_接口技术_RFC远程函数实践总结

SAP ABAP顾问能力模型梳理_企业数字化建设者的博客-CSDN博客SAP Abap顾问能力模型&#xff0c;ALV/REPORT|SMARTFROM|SCREEN|OLE|BAPI|BDC|PI|IDOC|RFC|API|WEBSERVICE|Enhancement|UserExits|Badi|Debughttps://blog.csdn.net/java_zhong1990/article/details/132469977 SAP接…