2.2类和对象(中)

本篇博客来梳理C++中重要的默认成员函数

一、类的默认成员函数

用户没有显式实现,编译器自动生成的成员函数,一旦显式实现,编译器不会自动生成

1.学习时应该考虑的两个问题

(1)我们不写时,编译器默认生成的函数行为是什么,是否满足需求
(2)若不满足需求,就需要自己实现,如何自己实现?

2.6个默认成员函数

6个默认成员函数

二、构造函数

1.主要任务

对象实例化时初始化对象,类似Stack中的Init函数

2.特点

(1)函数名与类名相同
(2)无返回值(不用给返回值,也不用写void)
(3)对象实例化时系统会自动调用对应的构造函数
(4)构造函数可以重载
(5)若类中没有显式定义构造函数,则编译器会自动生成一个无参的默认构造函数,一旦用户显式定义则编译器不再生成
(6)总结一下:不传实参就可以调用的构造就是默认构造

class Date 
{ 
public: 
	// 1.⽆参构造函数 
	Date() 
	{ 
		_year = 1; 
		_month = 1; 
		_day = 1; 
	} 
	// 2.带参构造函数 
	Date(int year, int month, int day) 
	{ 
		_year = year; 
		_month = month; 
		_day = day; 
	} 
	// 3.全缺省构造函数 
	/*Date(int year = 1, int month = 1, int day = 1) 
	{ 
	_year = year; 
	_month = month; 
	_day = day; 
	}*/ 
private: 
	int _year; 
	int _month; 
	int _day; 
};
int main() 
{
	Date d1; // 调⽤默认构造函数 ,此时1和3只能存在一个
	Date d2(2025, 1, 1); // 调⽤带参的构造函数
	return 0; 
} 

注意:1.无参构造函数和3.全缺省构造函数构成函数重载,如果直接写Date d3(),会产生调用歧义

// 两个Stack实现队列 ,假设Stack的默认构造已经自己写好
class MyQueue 
{ 
public: 
	//编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化 
private: 
	Stack pushst; 
	Stack popst; 
}; 

(7)编译器默认生成的构造函数
①对内置类型成员变量:初始化无要求,看编译器
②对自定义类型成员变量:要求调用这个成员变量的默认构造函数初始化,若该成员没有默认构造函数就会报错,要初始化此成员变量需用到初始化列表,后续进行解析

三、析构函数

1.主要任务

完成对象中资源的清理释放工作,类似Stack中的Destroy函数

2.特点

(1)函数名:在类名之前加上~
(2)无参无返回值(跟构造函数类似)
(3)一个类只能有一个析构函数(不支持重载),若未显式定义,系统会自动生成默认的析构函数
(4)对象生命周期结束时,系统自动调用析构函数
(5)编译器默认生成的析构函数
①对内置类型成员:不做处理
②对自定义类型成员:会调用它对应的析构函数
(6)我们自己写析构,只需要管自定义类型中申请了资源
(7)对自定义类型成员,无论什么情况(比如自己显式写了)都会自动调用析构函数(比如下面两个栈实现队列)
(8)一个局部域的多个对象,C++规定后定义的先析构

class Stack 
{ 
public: 
	Stack(int n = 4) //构造函数
	{ 
		_a = (STDataType*)malloc(sizeof(STDataType) * n); 
		if (nullptr == _a) 
		{ 
		perror("malloc申请空间失败"); 
		return; 
	} 
	_capacity = n; 
	_top = 0; 
	} 
	~Stack() //析构函数
	{ 
		cout << "~Stack()" << endl; 
		free(_a); 
		_a = nullptr; 
		_top = _capacity = 0; 
	} 
private: 
	STDataType* _a; 
	size_t _capacity; 
	size_t _top; 
};

// 两个Stack实现队列 
class MyQueue 
{ 
public: 
	//编译器默认⽣成MyQueue的析构函数调用了Stack的析构,释放的Stack内部的资源 
	// 显式写析构,也会自动调用Stack的析构 
	/*~MyQueue() 
	{
		//...
	}*/ 
private: 
	Stack pushst; 
	Stack popst; 
};

注:在vs中调试代码时,在类里面想看成员变量,可以直接输入this
调试看成员变量

四、拷贝构造函数

1.定义

(1)是一个特殊的构造函数
(2)第一个参数是自身类类型的引用,且任何额外的参数都有默认值

2.特点

(1)拷贝构造函数是构造函数的一个重载
(2)拷贝构造函数的第一个参数必须是类类型对象的引用,若使用传值会引发无穷递归调用,编译器直接报错

C++规定:传值传参(函数的传值调用)和传值返回要调用拷贝构造函数
正常的拷贝构造调用:
正常的拷贝构造调用
如果拷贝构造不写传引用调用,采用传值调用,那么就会产生无穷递归
无穷递归
(3)C++规定:自定义类型对象进行拷贝行为必须调用拷贝构造,因此函数调用时,自定义类型传值调用和传值返回都会调用拷贝构造=>自定义类型建议传引用调用
(4)编译器默认生成的拷贝构造函数:
①对内置类型成员:完成值拷贝/浅拷贝(一个字节一个字节的拷贝)
②对自定义类型成员:会调用它对应的拷贝构造
(5)我们自己写拷贝构造:关注类有没有申请资源,如果类显式实现了析构并释放资源,就需要显式写拷贝构造
Date这样的类,成员变量全是内置类型=>不用自己写拷贝构造
Stack这样的类,_a指向了资源,编译器默认生成的拷贝构造不符合需求,因为程序只对栈实现了浅拷贝,会导致st1和st2两个对象指向同一块数组空间,那么在这两个对象生命周期结束的时候,析构函数调用两次就会出现程序崩溃。所以Stack这样的类需要自己写拷贝构造函数,先申请另一块空间再进行拷贝
MyQueue(两个栈实现队列)这样的类,内部主要是自定义类型Stack,编译器默认生成的拷贝构造会调用栈的拷贝构造,也不需要自己显式写,是“躺赢玩家”

Stack要自己写拷贝构造
MyQueue不用自己写拷贝构造
(6)关于函数的传值返回:会产生一个临时对象,因此会调用拷贝构造,返回的是值拷贝。对传引用返回,要小心返回“野引用”
小心“野引用”

五、赋值运算符重载函数

运算符重载可以给运算符指定新的含义

1.运算符重载(跟函数重载无关)

(1)C++规定:类类型对象使用运算符,必须调用对应的运算符重载,如果没有则编译报错
(2)运算符重载函数是名字比较特殊的函数,函数名:operator运算符
如:

bool operator==(const Date& d1, const Date& d2) 

(3)运算符重载函数的参数个数=运算符作用的运算对象数量,一元运算符有一个参数,二元运算符有两个参数,二元运算符左侧算子传给第一个参数,右侧算子传给第二个参数
(4)当运算符重载函数是成员函数:第一个运算对象默认传给隐式的this指针=>这种情况下函数参数比运算对象少一个
(5)不能用语法中没有的符号来创建新的运算符,如operator@
(6).* :: sizeof ?: .这五个运算符不能重载
(7)重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义
如:

int operator+(int x, int y)//参数全是内置类型,不能重载
class Date 
{ 
public: 
	Date(int year = 1, 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; 
};

重载成成员函数
注释:

  • 成员放公有:把private注释掉即可
  • 友元函数:在类里面添加友元声明
friend bool operator==(const Date& d1, const Date& d2);

重载为成员函数
(8)前置++和后置++重载:后置++重载时增加一个int形参,跟前置++构成函数重载,方便区分
前置++和后置++重载
(9)重载<<和>>时,需要重载为全局函数
内置类型已经重载好了
内置类型的流插入和流提取已经重载好了,需要自己实现的是自定义类型的流插入和流提取
① 重载流插入<<
如果重载为成员函数,this指针默认抢占了第一个形参的位置,第一个形参是对象,第二个形参才是cout,写起来就是对象<<cout,简直倒反天罡,不符合使用习惯和可读性,因此应该重载为全局函数,把ostream/istream放到第一个形参的位置,第二个形参的位置放对象

重载为成员函数改进:重载为全局函数
友元声明的优势:不用改变成员变量的private性质,也可以让全局函数访问到对象的成员变量
② 重载流提取>>
重载流提取>>

2.赋值运算符重载函数(跟函数重载无关)

(1)主要任务:完成两个已经存在的对象的直接拷贝赋值(与拷贝构造不同,拷贝构造用来拷贝初始化给另一个要创建的对象
与拷贝构造区分

(2)特点
①规定必须重载为成员函数,参数建议写成const的类类型引用,减少拷贝同时防止实参被改
②有返回值,且建议写成类类型引用,有返回值的目的是为了支持连续赋值
有返回值的目的
③编译器默认生成的赋值运算符重载函数:对内置类型完成值拷贝/浅拷贝,对自定义类型成员变量会调用他的赋值重载
④我们自己写赋值运算符重载函数:关注类有没有申请资源,如果类显式实现了析构并释放资源,就需要显式写赋值运算符重载函数

六、取地址运算符重载函数

1.const成员函数

(1)定义:用const修饰的成员函数,const修饰成员函数放到成员函数参数列表的后面
(2)const实际修饰:隐含的this指针,表明在这个成员函数中不能对类的任何成员进行修改
像这样写编译器就会报错
在这里插入图片描述
正确的写法

void Date::Print() const
{
	cout<<_year<<"/"<<_month<<"/"<<_day<<endl;

写成const成员函数才能顺利访问const成员对象,对于不需要修改成员变量的成员函数都建议加上const

2.取地址运算符重载函数

分为普通取地址运算符重载const取地址运算符重载,一般编译器实现的足够用,很少自己实现

class Date 
{ 
public : 
	Date* operator&() 
	{ 
	return this; 
	// return nullptr; 
	} 
	const Date* operator&()const 
	{ 
	return this; 
	// return nullptr; 
	} 
private : 
	int _year ; // 年 
	int _month ; // ⽉ 
	int _day ; // ⽇ 
}; 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值