前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++】— 类和对象(2)

【C++】— 类和对象(2)

作者头像
_孙同学
发布2024-10-21 21:06:05
670
发布2024-10-21 21:06:05
举报
文章被收录于专栏:技术分享
💞1.类的默认成员函数

默认成员函数是用户没有显示的写而编译器自动生成的函数,一个类,我们不写编译器会默认生成6个默认成员函数。

💞2.构造函数

构造函数是特殊的成员函数。虽然名叫构造函数,但是它的主任务并不是开空间创建对象,而是对象实例化时初始化对象。 相当于Stack中的Init函数.

📌构造函数的特点: (1)函数名与类名相同 (2)没有返回值 (3)对象实例化时系统会自动调用对应的构造函数 (4)构造函数可以重载 (5)如果没有显示定义构造函数,编译器会自动生成一个无参的构造函数,如果显式写了,编译器将不再自动生成 (6)无参构造函数,全缺省构造函数,编译器自动生成的构造函数都叫做默认构造函数。需要注意的是有且只有一个存在,不能同时存在。✏️总结:不传实参调用的构造就叫默认构造 (7)编译器默认生成的构造函数对内置类型成员变量的初始化没有要求。对自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。若没有默认构造函数编译器就会报错。要初始化这个成员变量就要用初始化列表来初始化。初始化列表在后面的章节我们会讲

🔖内置类型: 语言提供的原生数据类型,如int /char/double/指针等 🔖 自定义类型: 我们使用的class/struct 等关键字自己定义的类型

代码语言:javascript
复制
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;

};

💞3.析构函数

析构函数并不是完成对对象本身的销毁,C++中规定对象在销毁时会自动调用析构函数,完成对象中资源的清理和释放工作。析构函数就类似Stack中的Destroy功能。

📌析构函数的特点: (1)析构函数的函数名是在类名前**+** ~ (2)无参数无返回值(和构造函数类似) (3)对象生命周期结束时会自动调用对应的析构函数 (4)跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,对自定义类型成员对调用它的析构函数 (5)自定义类型成员无论什么时候都对调用它的析构函数

代码语言:javascript
复制
#include<iostream>

using namespace std;
typedef int STDataType;
class Stack
{
public:
	//构造函数
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (_a == nullptr)
		{
			perror("malloc fail");
			return;
		}

		_capacity = n;
		_top = 0;
	}
	//析构函数
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};

//两个队列实现栈
class MyQueue
{
	//编译器默认生成MyQueue的析构函数调用了Stack的析构,释放Stack内部的资源
	/*~MyQueue()
	{
	
	}*/
private:
	Stack pushst;
	Stack popst;
};
int main()
{
	Stack st;
	MyQueue mq;

	return 0;
}

💞4.拷贝构造函数

若一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值(缺省值),则此函数叫做拷贝构造函数。也就是说拷贝构造是一个特殊的构造函数

📌拷贝构造函数的特点: (1)拷贝构造函数是构造函数的重载 (2)拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值的方式就会陷入无穷递归中,编译器就会报错。拷贝构造允许有多个参数,但第一个参数必须是类类型的引用,后面的参数必须有缺省值。 (3)C++规定自定义类型对象进行传值传参时必须调用拷贝构造,所以自定义类型进行传值传参和传值返回都会调用拷贝构造来完成。 (4)如果未显式写拷贝构造函数,编译器会自动生成拷贝构造函数。自动生成的拷贝构造函数对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节拷贝),对自定义成员变量会调用它的拷贝构造函数。 (5)像Date这样的成员变量都是内置类型并且没有指向什么资源,编译器自动生成的拷贝构造函数就可以完成需要的拷贝,所以不需要我们显示实现拷贝构造。像Stack这样的类,_a指向了资源,编译器自动生成的拷贝构造是浅拷贝不符合我们的需求,所以需要我们自己完成深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的的类型内部也是Stack的成员,编译器自动生成的拷贝构造会调用Stack的拷贝构造,不需要我们实现MyQueue的拷贝构造。✏️总结:如果一个类显示实现了析构并释放了资源,那么它就需要写拷贝构造,否则不需要。 (6)传值返回会产生一个临时对象调用拷贝构造,但传引用返回,返回的是引用对象的别名,没有产生拷贝。但是如果传引用返回返回的是一个当前函数局部域的局部对象,函数结束返回值就销毁了,这时的引用相当于一个野指针。✏️所以:传引用返回一定要确保返回对象在函数结束后还在,才能用引用返回。

代码语言:javascript
复制
#include<iostream>

using namespace std;

class Date
{
public:
	Date(int year = 1,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造函数
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	//虽然可以传参但不是拷贝构造
	/*Date(Date* d)
	{
		_year = d->_year;
		_month = d->_month;
		_day = d->_day;
	}*/

private:
	int _year;
	int _month;
	int _day;
};



int main()
{
	Date d1(2024,9,27); //C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,这里传值传参要调用拷贝构造
	Date d2(d1);
	Date d2 = d1;//这两种写法都是可以的,也都是拷贝构造
	

	return 0;
}

💞5.赋值运算符重载

💞5.1 运算符重载

🌵当运算符被用到类类型对象时,C++允许我们用运算符进行重载的形式指定新的定义。C++规定当类类型使用运算符时必须对运算符进行重载。若没有运算符重载,编译器就会报错。

🌵运算符重载是具有特殊名字的函数,由operator和后面要定义的运算符共同构成。

🌵重载运算符函数的参数的个数和该运算符作用的对象个数一样多(比如a+b,+的作用对象是a和b所以+运算符重载函数有两个参数) 🌵一个重载运算符函数如果是成员函数,那么它的第一个运算对象传给隐式地this指针,因此运算符重载作为成员函数时,参数比运算对象少一个。 🌵运算符重载后,其优先性和结合性与内置类型一样。

🌵.* ,::,sizeof,?:,. 这几个运算符不能重载。 🌵operator++默认为前置++,后置++给operator++增加一个int的形参,与前置++构成函数重载。

🌵重载<<>>时,需要重载为全局函数,因为重载为成员函数,this指针抢了第一个形参位置,而第一个形参调用的是左侧对象,调用时就成了对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第一个形参位置就行了。

代码语言:javascript
复制
#include<iostream>

using namespace std;


class Date
{
public:
	//成员函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//<的重载函数
	bool operator<(const Date& d)
	{
		if (_year < d._year)
		{
			return true;
		}
		else if (_year == d._year && _month < d._month)
		{
			return true;
		}
		else if (_year == d._year && _month == d._month && _day < d._day)
		{
			return true;
		}

		return false;
	}
private:
	//成员变量
	int _year;
	int _month;
	int _day;
};


int main()
{
	Date d1(2024, 9, 27);
	Date d2(2024, 10, 1);
	
	cout << d1.operator<(d2) << endl;

	return 0;
}

运行结果:

💞5.2 赋值运算符重载

赋值运算符重载是一个默认成员函数,用于两个已经存在的对象的直接拷贝赋值。而拷贝构造是一个对象拷贝初始化给另一个要创建的对象。

📌赋值运算符重载的特点: 🌵赋值运算符重载是运算符重载,并且规定重载为成员函数。赋值运算符重载的参数建议写成const+类类型的引用,否则会传值传参会有拷贝 🌵有返回值,并且建议写成当前类类型的引用,引用返回可以提高效率,有返回值是为了支持连续赋值。 🌵没有显示实现时,编译器会默认生成一个默认赋值运算符重载,默认赋值运算符对内置类型完成值拷贝/浅拷贝对自定义类型成员会调用它的赋值重载函数 🌵像Date这样的类成员变量全是内置类型没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成我们需要的拷贝,所以不需要我们显示实现赋值运算符重载。而像Stack这样的类,_a指向了内部资源,编译器自动生成的赋值运算符重载完成的是值拷贝/浅拷贝不符合我们的要求,所以需要我们自己实现**深拷贝。**像MyQueue这样的自定义类型,内部时Stack的成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载。✏️总结:如果一个类显示实现了析构并释放了资源,那么它就需要显示写赋值运算符重载。

代码语言:javascript
复制
#include<iostream>

using namespace std;


class Date
{
public:
	//成员函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//赋值运算符重载
	Date& operator=(const Date& d)//有返回值是为了支持连续赋值
	{
		if (this != &d)//判断是否为自己给自己赋值
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	
	}
private:
	//成员变量
	int _year;
	int _month;
	int _day;
};


int main()
{
	Date d1(2024, 9, 27);
	Date d2;

	d2.operator=(d1);

	d1.Print();
	d2.Print();

	return 0;
}

💞6.取地址运算符重载

💞6.1const成员函数

const修饰的成员函数称为const成员函数。const修饰成员函数const放到成员函数的后面。 const修饰成员函数其实修饰的是成员函数的this指针,表示该成员函数中不能对类的成员进行修改。

const修饰Date类的Print函数时,Print隐含的this指针由Date* const this变成了const Date* const this

代码语言:javascript
复制
#include<iostream>
using namespace std;

class Date
{
public:
	//成员函数
	//构造函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	
	//void Print(const Date* const this)
	void Print() const //const放在成员函数的后面。const修饰的不是this本身,而是this指向的内容
	{
		cout << _year << "-" << _month << "-" << _day << endl;

	}
private:
	//成员变量
	int _year;
	int _month;
	int _day;
};


int main()
{
	//非const对象也可以调用const成员函数是一种权限的缩小
	Date d1(2024, 9, 27);
	d1.Print(); 

	Date d2;
	d2.Print();

	return 0;
}
💞6.2 取地址运算符重载

取地址运算符重载分为普通的取地址运算符重载和被const修饰的取地址运算符重载。一般编译器自动生成的就够我们用了,不需要去显示实现。除非我们不想让别人知道当前类对象的地址。

代码语言:javascript
复制
class Date
{

public:
	Date* operator&()//普通对象调用普通版本
	{
		return this;
		//return 0x222232   不想让别人取到当前类的地址,胡乱返回一个
	}

	const Date* operator&()const //const对象调用const版本
	{
		return this;
		//return nullptr;
	}
private:
	int _year;
	int _month;
	int _day;
};

如果不显示写这样也可以取到地址,原因是取地址运算符重载是默认成员函数,不写编译器会默认生成。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 💞1.类的默认成员函数
  • 💞2.构造函数
  • 💞3.析构函数
  • 💞4.拷贝构造函数
  • 💞5.赋值运算符重载
    • 💞5.1 运算符重载
      • 💞5.2 赋值运算符重载
      • 💞6.取地址运算符重载
        • 💞6.1const成员函数
          • 💞6.2 取地址运算符重载
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档