类和对象(中)-- 类的六个默认成员函数

目录

1.构造函数

1.1构造函数的特性

1.2 默认构造函数

 1.3补丁

​2.析构函数

2.1析构函数的特性

2.2构造函数与析构函数的调用顺序

3.拷贝构造函数(复制构造函数)

3.1拷贝构造函数的特征

4.赋值运算符重载

4.1运算符重载

4.2类内重载运算符

4.3=赋值运算符重载

 4.4传值返回和引用返回

4.5其他运算符重载

4.6友元

 4.7Date类的实现

5.const成员函数

6.取地址及const取地址运算符重载


一个类中什么成员都没有,简称为空类,但空类中并非什么都没有,编译器会自动生成以下六种默认成员函数;
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

1.构造函数

对于上面的Date类,每次创建后都要调用init公有方法给实例化的对象进行初始化,有时候会有可能忘记初始化对象(如d2),所以c++有了构造函数,使对象在创建出来时就同时完成初始化:

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

1.1构造函数的特性

1.用于初始化对象,并不是开空间创建对象
2.函数名与类名相同
3.对象实例化时编译器自动调用对应的构造函数
4.构造函数可以重载
5.无返回值

 (注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,跟了括号就变成了函数声明了)

 6.如果类中没有显式定义构造函数,则c++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义构造函数编译器将不再生成。

 7.编译器生成的默认构造函数有什么用?
前面的例子可以看出调用了编译器自动生成的默认构造函数,似乎并没有对_year,_month,_day进行初始化,依然是随机值。
解释:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,编译器生成默认的构造函数会对自定类型成员调用的它的默认成员函数,内置类型会不会处理看编译器(不处理或者处理为0),内置类型没有规定要处理,对于自定义类型会调用的默认构造函数(注意是默认的),如果没有默认构造就会报错,所以要有默认构造,也可以没有默认构造,要使用初始化列表 

 自定义类型嵌套自定义类型,嵌套的尽头还是内置类型,内置类型要处理,一般情况下构造函数需要我们显式得去实现,只有少数情况可以让编译器自动生成(比如自定义类型里面只有自定义类型,没有内置类型)

1.2 默认构造函数

默认构造函数有三种:无参的、编译器自动生成的、全缺省的
总的来说,就是不传参数就可以调用的函数就是默认构造函数
注意:三个不能同时存在(存在调用歧义的问题)

 1.3补丁

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在
类中声明时可以给默认值,这个默认值用于初始化列表初始化。(并不是初始化,这里是声明)


2.析构函数

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

2.1析构函数的特性

1.析构函数名是在类名前面加上字符 ~ 
2.无参数无返回值类型
3.一个类只能有一个析构函数,若未显式定义,系统会自动生成默认的析构函数,需要注意:析构函数不能重载
4.对象生命周期结束时,c++编译系统会自动调用析构函数,析构函数可以手动调用,但这相当于两次调用了析构函数

5.析构函数没有显式定义,系统会自动生成默认析构函数,系统自动生成的默认析构函数会:
        a.内置类型不做处理
        b.自定义类型去调用它的析构函数
内置类型不做处理,这样就可能会造成内存泄露,但内存泄漏不会报错 ;

总结:
        a.有资源需要显式清理,就需要自己写析构;
        b.这两种场景可以不写析构,用编译器自动默认生成的就可以了:
                b1.没有资源需要清理
                b2.内置类型成员没有资源需要清理,且剩下的都是自定义类型成员

2.2构造函数与析构函数的调用顺序

 与压栈出栈的顺序有关。

3.拷贝构造函数(复制构造函数)

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

3.1拷贝构造函数的特征

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

c++存在规定:自定义类型传值传参需要调用拷贝构造:
假设存在Date类的对象d1,现在需要拷贝构造一个Date类的对象d2,将d1作为值传给拷贝构造函数,由于是自定义类型传值传参需要调用拷贝构造,所以d1值传给拷贝构造函数之前,我需要对d1调用拷贝构造函数……这样就引发了无穷递归调用了

 3.若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

 

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用
尽量使用引用

 

4.赋值运算符重载

4.1运算符重载

c++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值,函数名字以及参数列表,其返回值类型与普通的函数类似。
函数名字构成:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator符号 (参数列表)
作用:增强代码可读性

 .*运算符:        

例如:日期类>运算符重载,这是作为全局函数的实现方式
运算符重载可以显式调用,也可以转换调用,编译器会自动转换成显式调用

4.2类内重载运算符

将运算符重载成全局函数,无法访问类内私有成员变量,解决方法:
1.提供这些成员变量的get函数和set函数
2.设置成友元函数
3.类内重载运算符(一般使用这种)

重载成成员函数:

因为类内成员函数都有一个隐含的this指针参数在最前面,所以只需要写一个参数,转换调用格式相同。

注意:如果同类型的运算符重载,一个在类内,一个在全局,会优先调用类内的运算符重载函数

4.3=赋值运算符重载

参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回效率,有返回值目的是为了支持连续赋值
返回*this:符合连续赋值的含义
 

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

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

 4.4传值返回和引用返回

传值返回:以临时对象充当返回值(拷贝构造成临时对象)

 引用返回:返回对象的别名(注意返回对象出函数时生命周期是否结束)

虽然引用返回可以减少一次拷贝,但出了函数作用,返回对象还在,才能用引用返回

 总结:如果返回对象生命周期到了,会析构,用传值返回;返回对象生命周期没到,并不会析构,用传引用返回。

4.5其他运算符重载

前置++--,后置++--
后置++--运算符重载,为了区分前置++--,并构成重载,给后置++--强行增加一个int形参,int形参只是用来区分前置后置,没有实际意义


<<运算符重载和>>运算符重载

由于运算符重载中,参数顺序和操作顺序是一致的,<<(或者>>) 定义在类内,第一个形参是隐含的this指针,导致调用方式与常见的cout和int调用顺序不同。

 可以在类内成为成员函数,但调用形式不符合正常逻辑,所以不建议在类内重载,可以重载成全局函数

 返回值是ostream&是为了连续操作,如:cout << d1 << d2 << endl;
&是因为ostream(和istream)禁止了拷贝构造;
返回的对象是out(就是cout,out是cout的引用)
cin是istream类的全局对象
cout是ostream类的全局对象

以上操作都是在成员变量在public权限下的情况,但成员变量一般都在private权限下,那上述操作将无法访问,在类外访问私有需要友元

4.6友元

关键字:friend
友元函数不属于类内成员,只提供了访问类内私有成员的权限

 也可以类内定义:

 4.7Date类的实现

#pragma once

#include <iostream>
#include <cassert>
using namespace std;

class Date
{
	friend ostream& operator<<(ostream& out, const Date& d)
	{
		cout << d._year << '-' << d._month << '-' << d._day;
		return out;
	}
	friend istream& operator>>(istream& in, Date& d)
	{
		cin >> d._year >> d._month >> d._day;
		return in;
	}
		
public:
	Date(int year = 2025, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "Date()" << endl;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "Date(const Date& d)" << endl;
	}
	~Date()
	{
		_year = 0;
		_month = 0;
		_day = 0;
		cout << "~Date()" << endl;
	}
	//判断
	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}
	bool operator<(const Date& d)
	{
		//复用>=
		return !(*this >= d);
	}
	bool operator<=(const Date& d)
	{
		//复用>
		return !(*this > d);
	}
	bool operator>(const Date& d)
	{
		if (_year > d._year)
		{
			return true;
		}
		else if (_year == d._year)
		{
			if (_month > d._month)
			{
				return true;
			}
			else if (_month == d._month)
			{
				return _day > d._day;
			}
		}
		return false;
	}
	bool operator>=(const Date& d)
	{
		//复用>和==
		return (*this > d) || (*this == d);
	}
	bool operator==(const Date& d)
	{
		return (_year == d._year) && (_month == d._month) && (_day == d._day);
	}
	bool operator!=(const Date& d)
	{
		//复用==
		return !(*this == d);
	}
	//增减天数
	int GetMonthDay(int year, int month)
	{
		//默认内联-频繁调用
		assert(month > 0 && month < 13);
		static int monthDayArray[13] = { -1, 31, 28,31,30,31,30,31,31,30,31,30,31 };
		if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
			return 29;
		else
			return monthDayArray[month];
	}
	Date& operator+=(int day)
	{
		if (day < 0)
		{
			return *this -= -day;
		}
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			++_month;
			if (_month == 13)
			{
				++_year;
				_month = 1;
			}
		}
		return *this;
	}
	Date operator+(int day)
	{
		Date tmp(*this);
		return tmp += day;
	}
	Date& operator-=(int day)
	{
		if (day < 0)
		{
			return *this += -day;
		}
		_day -= day;
		while (_day <= 0)
		{
			--_month;
			if (_month == 0)
			{
				--_year;
				_month = 12;
			}
			_day += GetMonthDay(_year, _month);
		}
		return *this;
	}
	Date operator-(int day)
	{
		Date tmp(*this);
		return tmp -= day;
	}
	int operator-(const Date& d)
	{
		int count = 0;
		int flag = 1;
		Date greater(*this);
		Date smaller(d);
		if (greater < smaller)
		{
			greater = d;
			smaller = *this;
			flag = -1;
		}
		while (greater != smaller)
		{
			smaller += 1;
			++count;
		}
		return count * flag;
	}
	Date& operator++()//++a
	{
		return *this += 1;
	}
	Date operator++(int)//a++
	{
		Date tmp(*this);
		*this += 1;
		return tmp;
	}
	Date& operator--()//--a
	{
		return *this -= 1;
	}
	Date operator--(int)//a--
	{
		Date tmp(*this);
		*this -= 1;
		return tmp;
	}


	void Print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

5.const成员函数

将const修饰的“成员函数”称为const成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改(原来的隐含this指针参数是Date* const this)

 const对象不能调用非const成员函数
非const对象可以调用const成员函数
const成员函数内可以调用其他的非const成员函数
非const成员函数内可以调用其他的const成员函数

总结:如果没有修改的必要,可以在函数后面都加上const

6.取地址及const取地址运算符重载

这两个默认成员函数一般不用重新定义,编译器会默认生成

  • 28
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饼干烧饼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值