C++之类和对象(中)

目录

默认成员函数

构造函数

构造函数的定义

构造函数的性质

默认的构造函数

编译器自动生成的构造函数的作用

析构函数

析构函数的定义

析构函数的性质

编译器自动生成的析构函数的作用

拷贝构造函数

拷贝构造函数的性质

 编译器生成的拷贝构造函数的作用

运算符重载

运算符重载的定义

赋值运算符重载

编译器生成的赋值运算符重载

const成员

总结


默认成员函数

我们知道,类中我们可以定义类的成员函数,但是当我们一个成员函数都不定义时,难道类中就没有成员函数吗?当然不是,当我们不定义成员函数时,编译器会默认生成六个成员函数,我们本期主要讲解C++中的默认成员函数。

构造函数

什么是构造函数呢?
在之前学习栈这个数据结构对栈进行相关操作时,我们会对栈进行初始化,这里的初始化操作我们可以理解为构造函数所起的作用。我们依然通过日期类为大家讲解。

构造函数的定义

构造函数:构造函数是一个特殊的成员函数,在实例化对象时编译器自动调用构造函数,并且整个过程只调用一次,构造函数主要是用于对对象的成员变量进行初始化。

构造函数的性质

先看如下代码:

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	//无参的构造函数
	Date()
	{
		_year = 2023;
		_month = 11;
		_day = 18;
	}
	//全缺省的构造函数
	Date(int year=2023,int month=11,int day=18)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//编译器生成的默认构造函数
	void PrintInfo()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
};

1.构造函数没有函数名称

参考上述代码

2.构造函数没有返回值

参考上述代码

3.创建对象时,编译器会自动调用构造函数对对象进行初始化。

4.构造函数可以重载

上述代码中的有参数的构造函数和无参数的构造函数满足函数重载

默认的构造函数

什么是默认的构造函数呢?我们直接给出定义,默认的构造函数就是不用参数就可以调用的构造函数,默认的构造函数总共有三种:

1.没有参数的构造函数

	Date()
	{
		_year = 2023;
		_month = 11;
		_day = 18;
	}

2.全缺省的构造函数

Date(int year=2023,int month=11,int day=18)
	{
		_year = year;
		_month = month;
		_day = day;
	}

3.当我们自己没有写构造函数时,编译器会自动生构造函数,我们一旦写了构造函数,编译器就不会自动生成构造函数。编译器自动生成的构造函数也是默认构造函数。

编译器自动生成的构造函数的作用

1.对内置类型的成员变量,不做任何处理;

2.对自定义类型的成员变量,会调用它们的默认构造函数进行初始化操作,如果自定义类型没有默认构造函数,编译器就会报错。

析构函数

什么是析构函数呢?

同样回到栈的操作,栈在使用完之后,为了避免内存泄漏,我们会对栈的资源进行清理(释放指针,指针置空),比如动态开辟的空间的释放。析构函数也是类似的功能,主要完成对对象相关资源的清理。

析构函数的定义

析构函数:析构函数是一类特殊的成员函数,在对像销毁时编译器自动调用析构函数,完成对对象资源的清理工作。

析构函数的性质

我们用C++实现栈的代码为大家一一解释:

class Stack
{
private:
	int* _a;
	int _top;
	int _capacity;
public:
	//构造函数,栈的初始化
	Stack(int top = 0,int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			cout <<"malloc fail" << endl;
			exit(-1);
		}
		_top = top;
		_capacity = capacity;
	}
	//析构函数,栈资源的清理
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
};

1.析构函数无参数无返回值。

参考上述代码

2.析构函数函数名为~函数名

参考上述代码

3.一个类只有一个析构函数,若没有显示定义,编译器会自动生成一个析构函数。

4.与构造函数相反,在对象销毁时,编译器会自动调用析构函数,完成对象相关资源的清理工作。

编译器自动生成的析构函数的作用

1.对于内置类型成员变量不做处理

2.对于自定义类型成员变量会去调用自定义类型的析构函数,完成对对象资源的清理工作。

注意:并不是所有的类都需要析构函数,当我们自定义类型的操作关系到堆区资源的开辟时,为了防止内存泄漏,我们一般会用到析构函数,但是之前的日期类,因为没有牵扯堆区资源的开辟,所以用不到析构函数。

拷贝构造函数

什么是拷贝构造函数呢?
引用生活中的例子,可以理解为你在学校时,抄写其它同学的作业。在类和对象中,拷贝构造函数就是用一个已经存在的对象,对一个新创建的对象进行赋值操作。

拷贝构造函数的性质

我们通过日期类和栈类为大家一一讲解,先来看如下代码:

class Stack
{
private:
	int* _a;
	int _top;
	int _capacity;
public:
	//构造函数,栈的初始化
	Stack(int top = 0,int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			cout <<"malloc fail" << endl;
			exit(-1);
		}
		_top = top;
		_capacity = capacity;
	}
	//析构函数,栈资源的清理
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
};
class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	//无参的构造函数
	Date()
	{
		_year = 2023;
		_month = 11;
		_day = 18;
	}
	//全缺省的构造函数
	Date(int year = 2023, int month = 11, int day = 18)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//编译器生成的默认构造函数
	void PrintInfo()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//拷贝构造函数
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
};

int main()
{

	Date d1(2023, 11, 20);
	Date d2(d1);
	d2.PrintInfo();
	Stack s1(0, 8);
	Stack s2(s1);
	return 0;
}

1.拷贝构造函数的形参只有一个,且必须使用引用传参(且应该为const引用,使用const引用是为了防止实参被修改,不能你抄了我的作业我的作业也被你改了),如果使用值传参,会导致无限递归导致栈溢出。参考上述代码

2.拷贝构造函数也是一个特殊的成员函数,是构造函数的一种。

3.当我们没有显示生成拷贝构造函数时,编译器会自动生成拷贝构造函数。

 编译器生成的拷贝构造函数的作用

1.对于内置类型的成员变量进行字节拷贝(浅拷贝)。

2.对于自定义类型的成员变量会去调用自定义类型的拷贝构造函数。

注意:编译器生成的拷贝构造函数也得看使用场景。

        比如日期类,我们可以直接使用编译器生成的拷贝构造函数进行字节拷贝,因为日期类没有在堆上动态开辟空间,不用调用析构函数。

        对于栈这个类而言,我们则不能直接使用编译器生成的拷贝构造函数。我们知道,栈的空间我们是需要动态开辟的,既然动态开辟了空间,最终对象销毁时,就要调用析构函数进行对象资源的释放,但是如果我们使用了编译器生成的拷贝构造函数进行了字节拷贝,那么就会导致两个指针变量的值相同,都指向了同一块空间,我们知道,在释放空间时,一块空间是不能被释放两次的,如果释放两次就会出错,所以我们不能使用编译器生成的拷贝构造函数,我们得自己写拷贝构造函数(我们称之为深拷贝,后续会为大家讲解)。

何为深拷贝?何为浅拷贝?

就通过栈这个类而言,如果我们采用了浅拷贝,浅拷贝就是字节拷贝,就会导致两个指针指向了同一块空间,释放就会出错。深拷贝就是我们动态开辟了一块与原对象大小相同的空间,这块空间是在堆上开辟的,然后两个指针就指向了不用的两块空间,然后在新空间中,然后对其他的成员变量再进行拷贝赋值,这样就不会在调用析构函数时出错,这些都只是浅浅的阐述了一下,具体的深拷贝我们会在后面为大家仔细讲解。

运算符重载

什么是运算符重载呢?

大家先思考一个问题,我们一般在进行加法的运算时,通常是两个数字进行相加,加法操作符的两个操作数都是对于编译器中的内置类型而言的,怎样对自定义类型之间通过操作符进行操作呢?比如两个日期来比较大小,两个日期来进行相减,编译器是不支持自定义类型之间进行这些操作的,因此我们要用到运算符重载来让编译器支持这些类似的操作

运算符重载的定义

为了增加代码的可读性,C++引入了一种新的函数,我们称之为运算符重载,这种新的函数与普通的函数类似,有函数返回类型,函数名称,形参列表,不过其函数名比较特殊,通常由operator+操作符组成,这个操作符就是我们重载的操作符。比如:operator>,operator-

运算符重载的函数原型为:函数返回值类型 operator操作符(参数列表) 

注意:1.函数返回值类型应该视情况而定,比如两个日期类相减,函数的返回值就应该是天数,应该为整型,两个提日期类比较大小,函数的返回值类型就应该是bool类型。

           2.我们不能随意的对运算付进行重载,只能对普遍认知的运算符进行重载,+,=,>等等诸如此类的操作符进行重载,不能对@一些冷门操作符进行重载。

           3.运算符重载只能对自定义类型进行重载,不能对内置类型进行重载。

           4.运算符重载作为类成员函数时,含有一个默认的形参,为this指针。

            5..* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。

 我们给出日期类中,两个日期类比较大小的运算符重载。

class Date {
private:
	int _year;
	int _month;
	int _day;
public:
	Date(int year = 2023,int month=11,int day=30)
	{
		_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;
		else {
			return false;
		}
	}
};

赋值运算符重载

代码如下:

Date& operator=(const Date& d)
	{
		//赋值运算符重载不能自己给自己赋值,所以需要判断
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		
		return *this;
	}

赋值运算符重载和拷贝构造函数的区别:

拷贝构造函数是用一个已经存在的对象初始化一个刚刚创建的对象;

赋值运算符重载是两个已经存在的对象之间进行赋值操作。

注意:1.赋值运算符重载的返回类型是对象的引用,对象在赋值重载整个栈帧创建和销毁的整个周期中都没有销毁,且引用可以减少对象的拷贝。

           2.一定要检测自己是否给自己赋值。

           3.我们没有显示定义赋值运算符重载,编译器会默认生成一个赋值运算符重载。

编译器生成的赋值运算符重载

编译器生成的赋值运算符重载和编译器生成的拷贝构造函数类似:

1.对于内置类型成员变量,完成字节序的值拷贝(浅拷贝)。

2.对于自定义类型的成员变量,会去调用自定义类型的赋值运算符重载。

const成员

什么是 const成员呢?我们通过一段代码为大家讲解:

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

	void Print()
	{
		cout << _year << _month << _day << endl;
	}
    void Print()const   //可以理解为const Date*const this,this指针被两个const修饰
	{
		cout << _year << _month << _day << endl;
	}
};


int main()
{
	Date d1;
	const Date d2;
	return 0;
}

       我们在C++基础我们讲解了权限的相关概念,我们知道,权限的放大是不允许的,权限不变和权限缩小是允许的。在类和对象上期,我们学习了this指针,我们知道this指针的类型是类名*const this,也就意味着this指针的指向是不可修改的,但是this指针指向的对象是可以修改的,但是上述代码中我们创建了const对象d2,也就意味着d2是不可以被修改的,也就意味着d2对象不能调用一般的的成员函数,得去调用const函数。常规的对象和const对象都可以调用const函数,因为分别是权限的缩小和权限不变,这是允许的。

总结

本期主要学写了C++中的四个重要的默认成员函数,默认成员函数就是我们不写,编译器会默认生成的成员函数。

这个四个默认成员函数我们可以分两类记忆:

1.我们不写编译器生成的构造函数和析构函数,对内置类型不做处理,对自定义类型会去调用其默构造或者析构。

2.我们不写编译器生成的拷贝构造函数和赋值运算符重载,对于内置类型进行字节序的值拷贝(浅拷贝),对于自定义类型调用它们的拷贝构造函数或赋值运算符重载。

好了,本期的内容就是这些,本期内容到此结束^_^ 

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