C++——类和对象(Ⅱ)

类的默认成员函数

默认成员函数就是用户没有显示实现,编译器会自动生成的成员函数。一个类中,我们不写的情况下,编译器会默认生成下面几个默认成员函数。

1 构造函数

构造函数是在对象实例化时初始化对象,其本质相当于Stack中的Init函数的功能。

特点:

●函数名与类名相同

●无返回值,也不需要写void

●对象实例化时,系统会自动调用对应的构造函数

●可以重载

●如果类中没有显示定义构造函数,则编译器会自动生成一个无参的默认构造函数

●无参构造函数、全缺省构造函数、编译器默认生成的构造函数,都叫做默认构造函数。这三个函数有且只有一个存在,不能同时存在,避免调用时存在歧义

●编译器默认生成的构造对内置类型成员变量的初始化没有要求,也就是说,是否初始化是不确定的。对于自定义类型成员变量,要求必须调用这个成员变量的默认构造函数初始化。我们要初始化这个成员变量,需要用初始化列表才能解决。

注:C++把类型分为内置类型和自定义类型。内置类型就是该语言提供的原生数据类型,如:int/char/double等,自定义类型就是我们使用class/struct等关键字自己定义的类型。

#include

using namespace std;
class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	/*Date()
	{
		cout << "无参构造函数" << endl;
	}*/
	Date(int year = 2018, int month = 9, int day = 12)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "全缺省构造函数" << endl;
	}//该函数要使用时,应该把其他两个构造函数都注释掉

	/*Date(int year ,int month ,int day)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "带参构造函数" << endl;
	}*/

	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
};

int main()
{
	//Date d1;
	//d1.print();//随机值
	Date d2(2024,5,23);
	d2.print();//2024-5-23
	Date d3;
	d3.print();//2018-9-12
	return 0;
}

2 析构函数

C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能相当于Stack中实现的Destroy。

特点:

●析构函数名是在类名前加上字符~

●无参数无返回值(与构造类似,不需要加void)

●一个类只能有一个析构函数。若未显示定义,系统会自动生成默认的析构函数

●对象生命周期结束时,系统会自动调用析构函数

●与构造函数类似,编译器自动生成的析构函数对内置类型成员不做处理,自定义类型成员会调用自己的析构函数。就算我们显示写析构函数,对于自定类型还是会调用他自己的析构函数

●如果类中没有申请资源时,析构函数可以不写,直接使用编译器默认生成的析构函数就可以;或者自定义成员变量有自己的析构函数,也可以不显示写析构;但是,如果有资源申请,就一定要自己写析构,否则会造成资源泄露

●一个局部域的多个对象,C++规定:后定义的先析构

#include

using namespace std;
class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	Date(int year = 2025, int month = 2,int day = 12)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << _year << "-" << _month << "-" << _day<

上述代码在VS2022,debug版本运行结果如下图:

C++——类和对象(Ⅱ)_第1张图片

这也证实了析构函数的最后一个特点。大家可以自己写一下不同情况下的析构函数,试着调试一下,观察一下上述特点。

3 拷贝构造函数

如果一个构造函数的第一个参数是自身类类型的引用且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造。也就是说,拷贝构造其实是一种特殊的构造函数。

特点:

●拷贝构造是构造函数的一个重载

●拷贝构造第一个参数必须是类类型对象的引用。使用传值方式的话,编译器会直接报错,因为语法逻辑上会引发无穷递归调用。拷贝函数可以多个参数,但第一个必须是类类型的引用,后面的参数必须有缺省值

●C++规定自定类型对象进行拷贝行为必须调用拷贝构造,所以自定义类型传值传参和传值返回时都会调用拷贝构造完成

●如果未显示定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造函数对内置类型成员变量会进行值拷贝/浅拷贝(即一个字节一个字节地拷贝),对自定义类型成员会调用其自己的拷贝构造

●若类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝;但是,若类成员变量有指向资源的变量(如指针类),则需要我们自己实现深拷贝(即对其指向的资源也进行拷贝)。其实,如果一个类显示实现了析构并释放了资源,就需要显示写拷贝构造,否则就不需要

●传值返回会产生一个临时对象调用拷贝构造,传值引用返回就不会产生拷贝。但如果返回对象是当前函数局部域的局部对象,出了函数就销毁了,此时使用引用返回是不对的,这时的引用相当于一个野指针。所以在使用传值引用返回时,一定要确定对象在当前函数结束后仍然存在。

class Date
{
private:
	int _year;
	int _month;
	int _day;

public:
	Date(int year = 2025, int month = 2, int day = 12)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	Date(Date* d)
	{
		_year = d->_year;
		_month = d->_month;
		_day = d->_day;
		cout << "*" << endl;
	}


	~Date()
	{
		cout <<_year<<"~Date" << endl;
	}

	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
};

int main()
{
	Date d1;
	Date d2(d1);
	Date d3(2022,4,21);
	Date d4 = d3;//这样写也会调用拷贝构造

	d1.print();
	d2.print();

	d3.print();
	d4.print();

	return 0;
}

运行结果:

C++——类和对象(Ⅱ)_第2张图片4 赋值运算重载

4.1 运算符重载

●C++规定类类型对象使用运算符时,必须转换成调用对应的运算符重载,通过运算符重载为运算符指定新的含义

●运算符重载函数的名字:operator+相应的运算符。和其他函数一样,它也有其返回类型、参数列表以及函数体

●运算符重载函数的参数个数和该运算符作用对象数量一致。一元运算符有一个参数,二元运算符有两个参数,二元运算符左侧运算对象传给第一个参数,右侧运算对象传给第二个参数

●如果一个运算符重载函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此参数会少一个

●运算符重载后,优先级和结合性与对应的内置类型运算符保持一致

●不能创建新的操作符,如:operator@

● .*   ::   sizeof   ?:    .  这五个运算符不能重载,大家可能对第一个运算符比较陌生,下面给大家介绍一下:

#include

using namespace std;
class test
{
public:
	int a;
};

//.*是成员指针运算符
// 用于通过指向类成员的指针访问成员函数或成员变量。

int main()
{
	test T;
	T.a = 3;
	int test::* b = &test::a;//指向成员变量指针
	cout << T.*b << endl;//输出3
	return 0;
}

●重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义

●重载++和--运算符时,有前置和后置之分,为了方便区分二者,C++规定:表示后置重载时,增加一个int形参(无实际意义,可传参也可不传)

●重载<<和>>时,要重载为全局函数,因为重载为成员函数,this指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用是就会变成:对象<

4.2 赋值运算符重载

赋值运算符重载是⼀个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于⼀个对象拷贝初始化给另⼀个要创建的对象。

特点:

● 赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成 const当前类类型引用,否则会传值传参会有拷贝

●有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。

●没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对自定义 类型成员变量会调用他的赋值重载函数。

●赋值运算符重载与拷贝构造函数类似,如果这个类显示实现了析构并且释放资源,则需要显示写赋值运算符重载,否则不需要。

#include

using namespace std;

class Date
{
private:
	int _year;
	int _month;
	int _day;

public:
	//传引用返回,减少拷贝
	Date& operator=(const Date& d)//被赋值对象不能改变
	{
		//自己赋值给自己,就直接返回自己
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

	Date(int year = 2005, int month = 4, int day = 21)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
};


int main()
{
	Date d1;
	d1.print();//2005-4-21
	Date d2(2000, 11, 28);
	d2.print();//2000-11-28

	d2 = d1;
	d2.print();//2005-4-21
	
	return 0;
}

5 取地址运算符重载

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器i自动生成的就可以够我们用了,不需要去显示实现。

你可能感兴趣的:(C++,c++,开发语言)