【C++基础】三、类和对象(中篇)


1.类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。

 class Date {
    
     };

image-20220317080959489

2.构造函数

2.1 概念

构造函数:在对象构造时调用的函数,这个函数完成初始化工作

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	//void Display(Date* this)
	void Display()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}
	//void SetDate(Date* this,int year,int month,int day)
	void SetDate(int year, int month, int day)
	{
    
    
		_year = year;//this->_year = year;
		_month = month;//this->_month = year;
		_day = day;//this->_day = year;
	}
private:
	int _year;//年
	int _month;//月
	int _day;//日
};
int main()
{
    
    
	Date d1, d2;
	d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19)
	d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19)
	d1.Display();//d1.Display(&d1)
	d2.Display();//d2.Display(&d2)
	return 0;
}

对于Date类,可以通过SetDate公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

其特征如下:

1.函数名与类名相同

2.无返回值。

扫描二维码关注公众号,回复: 13781250 查看本文章

3.对象实例化时编译器自动调用对应的构造函数

4.构造函数可以重载。

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	//void Display(Date* this)
	void Display()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}
	//void SetDate(Date* this,int year,int month,int day)
	void SetDate(int year, int month, int day)
	{
    
    
		_year = year;//this->_year = year;
		_month = month;//this->_month = year;
		_day = day;//this->_day = year;
	}
	//构造函数 相当于SetDate,初始化,但是由程序自动调用,不需要我们去调用
	//1.无参数构造函数
	Date() {
    
    
		_year = 0;
		_month = 1;
		_day = 1;
	}
	//2.带参数构成函数    无参和带参两种方式均可
	Date(int year, int month, int day) {
    
    
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;//年
	int _month;//月
	int _day;//日
};
int main()
{
    
    
	Date d1(2021, 8, 21);
	d1.Display();
	d1.SetDate(2021, 10, 1);
	d1.Display();

	Date d2;
	d2.Display();

	//Date d1;//调用无参构造函数 注意后面不能带(),否则变成函数声明了
	//Date d2(2015, 1, 1);//调用带参的构造函数

	//d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19)
	//d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19)
	//d1.Display();//d1.Display(&d1)
	//d2.Display();//d2.Display(&d2)
	return 0;
}

image-20220317081352633

Date d2;//后面不能加(),d2(),这是语法规定的

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

这句话的意思就是”如果我们定义了构造函数,那么C++编译器就会使用我们定义的构造函数来初始化新建的对象;如果我们没有定义构造函数,那么C++编译器会自动生成一个没有参数的默认构造函数,然后用这个默认构造函数去初始化新建的对象。”

image-20220317081434639

看起来编译器默认构造函数啥也没干,还是随机值,那这个默认构造函数有啥用呢?

#include<iostream>
using namespace std;
class Time {
    
    
public:
	Time() {
    
    
		_hour = 0;
		_minute = 0;
		_second = 0;
		cout << "Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
    
    
public:
	//void Display(Date* this)
	void Display()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}
	//void SetDate(Date* this,int year,int month,int day)
	void SetDate(int year, int month, int day)
	{
    
    
		_year = year;//this->_year = year;
		_month = month;//this->_month = year;
		_day = day;//this->_day = year;
	}
	//我们没有显式定义构造函数,这里编译器生成无参默认构造函数

	构造函数 相当于SetDate,初始化,但是由程序自动调用,不需要我们去调用
	1.无参数构造函数
	//Date() {
    
    
	//	_year = 0;
	//	_month = 1;
	//	_day = 1;
	//}
	2.带参数构成函数    无参和带参两种方式均可
	//Date(int year, int month, int day) {
    
    
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}
private:
	int _year;//年
	int _month;//月
	int _day;//日
	Time _t;//自定义类型
};
int main()
{
    
    
	//Date d1(2021,8,21);
	Date d1;//调用了编辑器生成的默认构造函数
	d1.Display();
	d1.SetDate(2021, 10, 1);
	d1.Display();

	Date d2;
	d2.Display();

	//Date d1;//调用无参构造函数 注意后面不能带(),否则变成函数声明了
	//Date d2(2015, 1, 1);//调用带参的构造函数

	//d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19)
	//d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19)
	//d1.Display();//d1.Display(&d1)
	//d2.Display();//d2.Display(&d2)
	return 0;
}

image-20220317081552304

添加一个自定义类型Time后,可以发现Date的编译器生成的默认构造函数实际上调用了Time的构造函数。也就是说:

默认生成的无参构造函数(语法坑:双标狗)

1、针对内置类型的成员变量没有做处理(比如int、char等类型)

2、针对用户自定义类型的成员变量,调用它的构造函数初始化(比如Time类型)

为了解决上面内置类型不初始化的问题,C++11打了一个补丁,允许给内置类型设置缺省值。

int _year = 0;
int _month = 1;
int _day = 1;
//注意这里是成员变量的声明,不是定义,没有开空间

**6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。**注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

image-20220317081628229

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	无参构造函数
	//Date() {  
	//	_year = 0;
	//	_month = 1;
	//	_day = 1;
	//}
	带参构成函数
	//Date(int year, int month, int day) { 
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}
	//上面两种方式的合二为一 全缺省构造函数
	Date(int year = 0, int month = 1, int day = 1) {
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Display()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}
private:
	int _year;//年
	int _month;//月
	int _day;//日
};
int main()
{
    
    
	Date d1(2021, 10, 1);
	Date d2;
	d1.Display();
	d2.Display();
	return 0;
}

image-20220317081718439

注意:无参的构造函数和全缺省的构造函数,只能存在一个;如果两个同时存储,那么调用的时候会产生歧义,因为编译器不知道该调用哪个了。

image-20220317081748428

image-20220317081757425

7.关于编译器生成的默认成员函数,很多童鞋会有疑惑∶在我们不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?

d对象调用了编译器生成的默认构造函数,但是d对象的_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么作用??

解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char…,。自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序

就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数

(这个前面已经说的很清楚了,语法坑:双标狗)

8.成员变量的命名风格

建议成员变量名称前面加上下划线_

int _year;
int _month;
int _day;
//或者也可以这样
int m_year;
int m_month;
int m_day;
//m-member 成员

3.析构函数

3.1 概念

前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。

析构:对象生命周期到了以后,自动调用。完成对象里面的资源清理(资源:对象创建时向系统申请的空间,比如所 malloc 出来的空间)

3.2 特性

析构函数是特殊的成员函数。其特征如下:

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

2.无参数无返回值

3.一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数

4.对象生命周期结束时,C++编译系统系统自动调用析构函数

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	//构造函数
	Date(int year = 0, int month = 1, int day = 1) {
    
    
		_year = year;
		_month = month;
		_day = day;
		cout << "自动调用构造函数" << endl;
	}
	//析构函数
	~Date() {
    
    
		cout << "自动调用析构函数~" << endl;
	}
	void Display()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}

private:
	int _year;//年
	int _month;//月
	int _day;//日
};
int main()
{
    
    
	Date d1(2021, 10, 1);
	Date d2;
	d1.Display();
	d2.Display();
	return 0;
}

image-20220317081918501

析构函数什么时候使用呢?

当我们动态开辟数组,比如malloc/colloc/realloc出来的空间,使用之后需要将这些堆上的空间释放掉,就可以利用析构函数将资源清理掉。

#include<iostream>
using namespace std;
class Stack {
    
    
public:
	//构造函数,完成初始化
	Stack(int n = 10) {
    
    
		_array = (int*)malloc(sizeof(int) * n);
		_size = 0;
		_capacity = 10;
	}
	//析构函数,完成资源清理
	~Stack() {
    
    
		free(_array);
		_array = NULL;
		_size = 0;
		_capacity = 0;
	}
private:
	int* _array;
	int _size;
	int _capacity;
};
int main() {
    
    
	Stack st;
	return 0;
}

调用构造函数完成初始化

image-20220317082040745

调用析构函数完成资源清理

image-20220317082050845

一个类要有什么样的成员变量和成员函数,需要根据类的实际使用场景来确定。

5.关于编译器自动生成的析构函数,是否会完成一些事情呢?

下面的程序我们会看到,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。

(类似于构造函数的语法坑:双标狗)

跟构造函数类似,编译器默认生成的析构函数做了偏心的处理:

1)内置类型不处理

2)自定义类型会去调用它的析构函数

#include<iostream>
using namespace std;
class Time {
    
    
public:
	~Time() {
    
    
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
    
    
public:
	//构造函数
	Date(int year = 0, int month = 1, int day = 1) {
    
    
		_year = year;
		_month = month;
		_day = day;
		cout << "自动调用构造函数" << endl;
	}
	析构函数
	//~Date() {
    
    
	//	cout << "自动调用析构函数~" << endl;
	//}
	void Display()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}

private:
	int _year;//年
	int _month;//月
	int _day;//日
	Time _t;//自定义类型
};
int main()
{
    
    
	Date d1(2021, 10, 1);
	Date d2;
	d1.Display();
	d2.Display();
	return 0;
}

image-20220317082207313

4.拷贝构造函数

4.1 概念

在现实生活中,可能存在长得一模一样的事物,我们称其为双胞胎(孪生/双生)。

image-20220317082237176

image-20220317082250391

那在创建对象时,可否创建一个与一个对象一模一样的新对象呢?

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

4.2 特性

拷贝构造函数也是特殊的成员函数,其特征如下:

1.拷贝构造函数是构造函数的一个重载形式。

2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

#include<iostream>
using namespace std;
class Date 
{
    
    
public:
	Date(int year = 0, int month = 1, int day = 1)
    {
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造函数
	Date(const Date& d) 
    {
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1(2021, 10, 1);
	Date d2(2021, 10, 1);
	Date d3(d1);
	return 0;
}

image-20220317082344773

如果不使用引用,在调用拷贝构造函数之前需要先传参,传参的时候要先进行拷贝构造,拷贝构造之前又要先传参….语义上

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 0, int month = 1, int day = 1) 
    {
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造函数
	Date(const Date d) 
    {
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main() 
{
    
    
	Date d1(2021, 10, 1);
	Date d2(2021, 10, 1);
	Date d3(d1);
	return 0;
}

image-20220317082450078

传参的过程又是一个拷贝构造的调用?这句话怎么理解?

func1(int i) 
{
    
    
	//函数内部代码
}
int main() 
{
    
    
	int j = 0;
	func1(j);//调用func1的时候,要先将j传给i
}

实参传给形参是一个赋值的方式

换成class Date类的话

image-20220317082541653

d1传给d的过程就是一个赋值的过程,但是这个自定义类的赋值不同于内置类型的直接赋值,而是再次通过拷贝构造函数给其赋值,然后就形成了语义上的无穷递归……

image-20220317082558540

传值的方式,实参接收形参传过来的值时,需要创建一个临时对象,就会引发对象的拷贝构造。

而传引用的方式(引用是一个变量的别名)没有传参赋值的这个过程,也就不会形成无穷的递归。

拷贝构造也可以通过类似赋值的方式进行

Date d4 = d1;

image-20220317082637002

Date(const Date &d)这里为什么要加上const的呢?

1)使用const可以保证对象d的内容不被修改

2)如果Date(const Date &d){ };花括号内部代码出错了,比如说写反了,编译器会查出来。

3.若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。

默认的拷贝构造函数,跟默认的构造和析构函数不太一样,不会去区分内置类型和自定义类型,都会区处理

1)内置类型,字节序的浅拷贝

2)自定义类型,会去调用它的拷贝构造函数完成拷贝

字节序的浅拷贝:就像用 memcpy 函数完成拷贝一样(一个字节接着一个字节的拷贝复制)

Stack St1;
Stack st2(st1);
//同一块空间会释放两次

编译器默认生成的拷贝构造函数并不能解决所有问题,所以需要我们定义深拷贝构造来解决这个问题。

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
 };
int main()
{
    
    
	Date d1;
	//这里d2调用默认拷贝构造完成拷贝,d2和d1的值是一样的
	Date d2(d1);
	return 0;
}

image-20220318141714477

4.那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

//这里会发现下面的程序会崩溃掉?需要我们以后学了深拷贝去解决
#include<iostream>
using namespace std;
class String
{
    
    
public:
	String(const char* str = "jack")
	{
    
    
		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}
	~String()
	{
    
    
		cout << "~String()" << endl;
		free(_str);
	}
private:
	char* _str;
};
int main()
{
    
    
	String s1("hello");
	String s2(s1);
}

image-20220318142123680

5.赋值运算符重载

5.1 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

运算符默认都是给内置类型变量用的,比如说 -、+、==、-= 等都是运算符。

自定义类型的变量想要使用这些运算符,需要我们自己去进行运算符重载。

运算符重载的意思就是我们自己去写一个函数定义实现这里的运算符的行为(也就是说这个运算符要干嘛要我们自己去实现)

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型operator操作符(参数列表)

bool operator == (const Date &d1,constr Date& d2)
{
    
    
//......
}
//调用的时候
operator == (d1,d2);//方式一
d1 == d2;//方式二,编译器会默认转换为 operator == (d1,d2);

函数名:operator 运算符

参数:操作符的操作数有几个就有几个参数,参数类型就是我们要操作的对象类型

返回值:看运算符运算后的结果的类型是什么

注意:

1)不能通过连接其他符号来创建新的操作符:比如operator@(@这个符号本身就不是运算符)

2)重载操作符必须有一个类类型或者枚举类型的操作数(也就是说参数中一定要有一个自定义类型的参数)

3)用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义,(+原来表示int类型,内置类型的相加,我们可以将其重载为自定义类型之间的相加,基本含义不变,使用场景扩宽了)

4)作为类成员的重载函数时,其形参看起来比操作数数目少1。成员函数的操作符有一个默认的形参this,限定为第一个形参

5). *::\ sizeof?:\.注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

这里是点星.*不可以重载,星号*是可以重载的。

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
//private:
	int _year;
	int _month;
	int _day;
};
//重载operator==,作为一个全局函数
//这里会发现运算符重载成全局后,就需要解决成员变量原来是private的问题,
//将成员变量改为public,封装性就丢失了,那么如何继续保持对象的封装性呢?
bool operator==(const Date& d1, const Date& d2)
{
    
    
	return d1._year == d2._year 
		&& d1._month == d2._month 
		&& d1._day == d2._day;
}
int main()
{
    
    
	Date d1(2019, 9, 9);
	Date d2(2019, 9, 8);
	Date d3(d1);
	cout << (d1 == d2) << endl;
	cout << (d1 == d3) << endl;
	return 0;
}

从上面这个例子,我们可以看出运算符写成全局的可以,但是面临访问私有成员变量的问题,如何解决?

方式一:利用友元函数(后面会学)

方式二:将运算符重载写成类的成员函数,放到类的内部。

我们采用方式二:将运算符重载写成类的成员函数,放到类的内部。将上面的判断是否相等运算符==重载重新实现一下。

#include<iostream>
using namespace std;
class Date
{
    
    
public:
    //bool operator==(Date* this,const Date& d)
    //左操作数会有一个默认的this指针存在,指向调用函数的对象
	bool operator==(const Date& d)
	{
    
    
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	Date d1(2019, 9, 9);
	Date d2(2019, 9, 8);
	Date d3(d1);
	cout << (d1 == d2) << endl;//d1 == d2;->d1.operator==(d2);->d1.operator(&d1,d2);
	cout << (d1 == d3) << endl;//d1 == d3;->d1.operator==(d3);->d1.operator(&d1,d3);
	return 0;
}

5.2 赋值运算符重载

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

1)赋值运算符重载用于两个已经定义出来的对象之间的拷贝赋值。

2)拷贝构造是准备定义一个新对象时用另一个对象来初始化它

两者的使用场景是不一样的

	Date& operator=(const Date& d)
	{
    
    
		if(this != &d)
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
        return *this;//将赋值的结果返回
	}

1)实际的赋值当中,存在连续赋值,比如:

int i = 0, j = 1, k = 2;
int i = j = k;//连续赋值,这里是先执行j=k,返回值是j,然后在执行i=j,返回值是i

为了使我们重载后的赋值运算符= 也满足连续赋值,所以需要其将赋值后的结果返回,使用Date& 引用返回,可以减少临时变量的创建。

2)我们在使用赋值运算符 = 时,用可能出现自身给自身赋值的情况,也就是:

Date d1;
d1 = d1;//自身给自身赋值

为了解决这种自身给自身赋值还需要依次赋值的问题,我们在开始赋值前就判断一下this指向的对象和用来赋值的对象是不是同一个,如果不是同一个,才依次赋值。

5.3 特性

赋值运算符主要有以下特性:

1.参数类型,必要至少有一个是自定义类型,不能全部都是内置类型

2.返回值,根据具体的使用场景来确定返回值

3.检测是否出现自身给自身赋值问题,并进行解决

4.返回*this,将赋值后的结果返回

5.一个类如果没有显示定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1;
	Date d2(2019, 1, 1);
	d1 = d2;//这里d1调用编译器生成的operator= 完成拷贝,拷贝之后,d1和d2的值是一样的
	return 0;
}

image-20220319181213029

那么编译器生成的默认赋值重载函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像日期类这样的类是没有必要的。那么下面这个类呢?验证以下试试?

//这里会发现下面的程序会崩溃掉?需要我们以后学了深拷贝去解决
#include<iostream>
using namespace std;
class String
{
    
    
public:
	String(const char* str = "jack")
	{
    
    
		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}
	~String()
	{
    
    
		cout << "~String()" << endl;
		free(_str);
	}
private:
	char* _str;
};
int main()
{
    
    
	String s1("hello");
	String s2(s1);
}

总结:

如果我们要实现数据结构相关的类,基本都需要自己写

//构造函数、析构函数、拷贝构造、赋值重载都需要我们自己写。
//默认生成的都有问题,不能用
class Stack
{
    
    
//...
}

如果是像日期类这样简单的类,我们只需要写构造函数就可以了,其它的可以使用编译器生成的

//自己写构造函数就可以,其他编译器生成的也可以用
class Date
{
    
    
//...
}

一个类到底要重载哪些运算符,是看我们需要哪些运算符

6.日期类的完整实现

请完成以下所有成员函数的实现。

class Date
{
    
    
public:
    //打印显示
    void Display();
	// 获取某年某月的天数
	int GetMonthDay(int year, int month);
	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);
	// 拷贝构造函数
    // d2(d1)
    Date(const Date& d);
	// 赋值运算符重载
    // d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d);
	// 析构函数
	~Date();
	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day);
	// 日期-天数
	Date operator-(int day);
	// 日期-=天数
	Date& operator-=(int day);
	// 前置++
	Date& operator++();
	// 后置++
	Date operator++(int);
	// 后置--
	Date operator--(int);
	// 前置--
	Date& operator--();
	// >运算符重载
	bool operator>(const Date& d);
	// ==运算符重载
	bool operator==(const Date& d);
	// >=运算符重载
	inline bool operator >= (const Date& d);
	// <运算符重载
	bool operator < (const Date& d);
	// <=运算符重载
	bool operator <= (const Date& d);
	// !=运算符重载
	bool operator != (const Date& d);
	// 日期-日期 返回天数
	int operator-(const Date& d);
private:
	int _year;
	int _month;
	int _day;
};
#include"Date.h"
//打印显示
void Date::Display()
{
    
    
	cout << _year << "/" << _month << "/" << _day << endl;
}
// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
    
    
	int day[13] = {
    
     0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
		return 29;
	return day[month];
}
// 全缺省的构造函数
Date::Date( int year , int month , int day )//注意这里不能再给缺省值了,不然会报重定义默认参数的错误
{
    
    
	_year = year;
	_month = month;
	_day = day;
}
// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
    
    
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
    
    
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}
// 析构函数
Date::~Date()
{
    
    
	cout << "自动调用析构函数" << endl;
}
// 日期+=天数
Date& 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)
		{
    
    
			_month = 1;
			++_year;
		}
	}
	return *this;
}
// 日期+天数
Date Date::operator+(int day)
{
    
    
	Date temp(*this);
	temp += day;
	return temp;
}
// 日期-天数
Date Date::operator-(int day)
{
    
    
	Date temp(*this);
	temp -= day;
	return temp;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
    
    
	if (day < 0)
		return *this += -day;
	_day -= day;
	while (_day <= 0)
	{
    
    
		--_month;
		_day += GetMonthDay(_year, _month);
		if (_month == 0)
		{
    
    
			_month = 12;
			--_year;
		}
	}
	return *this;
}
// 前置++
Date& Date::operator++()
{
    
    
	return *this += 1;
}
// 后置++
Date Date::operator++(int)
{
    
    
	Date temp(*this);
	*this += 1;
	return temp;
}
// 后置--
Date Date::operator--(int)
{
    
    
	Date temp(*this);
	*this -= 1;
	return temp;
}
// 前置--
Date& Date::operator--()
{
    
    
	return *this -= 1;
}
// >运算符重载
bool Date::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;
}
// ==运算符重载
bool Date::operator==(const Date& d)
{
    
    
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}
// >=运算符重载
inline bool Date::operator >= (const Date& d)
{
    
    
	return *this > d || *this == d;
}
// <运算符重载
bool Date::operator < (const Date& d)
{
    
    
	return !(*this >= d);
}
// <=运算符重载
bool Date::operator <= (const Date& d)
{
    
    
	return *this < d || *this == d;
}
// !=运算符重载
bool Date::operator != (const Date& d)
{
    
    
	return !(*this == d);
}
// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
    
    
	Date max (*this);
	Date min(d);
	int flag = 1;
	if (*this < d)
	{
    
    
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min < max)
	{
    
    
		++min;
		++n;
	}
	return n * flag;
}

一般情况下,一个对象拷贝构造出一个临时对象,这个临时对象再去拷贝构造另一个对象,而编译器都会进行优化,中间对象优化掉,直接第一个拷贝构造第三个,这种优化要求:它是在一个表达式执行的连续步骤中

7.const 成员

7.1 const修饰类的成员函数

将const修饰的类成员函数称为const成员函数。const修饰类成员函数,实际上修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

image-20220320102305037

我们来看看下面的代码

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	void Display()
	{
    
    
		cout << "Display()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl;
	}
	//void Display()const
	//{
    
    
	//	cout << "Display()" << endl;
	//	cout << "year:" << _year << endl;
	//	cout << "month:" << _month << endl;
	//	cout << "day:" << _day << endl;
	//}
private:
	int _year;
	int _month;
	int _day;
};
void Test()
{
    
    
	Date d1;
	d1.Display();
	const Date d2;
	d2.Display();
}
int main()
{
    
    
	Test();
	return 0;
}

7.2 请思考下面的几个问题:

1.const对象可以调用非const成员函数吗?

2.非const对象可以调用const成员函数吗?

3.const成员函数内可以调用其他的非const成员函数吗?

4.非const成员函数内可以调用其它的const成员函数吗?

在这里插入图片描述
放开const的Display(),注释掉没有const的Display()

image-20220320102934682

总结:

1)成员函数加const,变成const成员函数是有好处的,这样的const对象可以调用,非const对象也可以调用

2)是不是说所有成员函数都要加const呢?

不是,要看成员函数的功能,如果成员函数是一个修改型,比如:operator+=() 那就不能加,如果是一个只读型的,比如:Print()、operator+()、Size()等那就最好加

要修改就不加const,不修改就最好加上const

8.取地址及const取地址操作符重载

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

class Date
{
    
    
public:
	Date* operator&()
	{
    
    
		return this;
	}
	const Date* operator&()const
	{
    
    
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

这两个运算符一般需要重载,使用编译器生成的默认取地址的重载函数即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容。


9.思维导图总结

请添加图片描述

猜你喜欢

转载自blog.csdn.net/QIYICat/article/details/123837799
今日推荐