C++——类与对象(中)

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

如果一个类中什么成员都没有,简称为空类

class Date{};

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

2、构造函数

2.1 概念

对于以下Date类:

#include<iostream>
using namespace std;

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2;
	d1.Init(2023, 11, 10);
	d2.Init(2001, 7, 7);
	d1.Print();
	d2.Print();
	return 0;
}

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

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

2.2 特性

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

其特征如下:

1、函数名与类名相同。

2、返回值。

3、对象实例化时编译器自动调用对应的构造函数。如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明。

4、构造函数可以重载

#include<iostream>
using namespace std;

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;
	}
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

void TestDate()
{
	Date d1;//调用无参构造器
	Date d2(2023, 1, 1);//调用带参构造器
	d1.Print();
	d2.Print();

	//注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	//以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
	//warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
	Date d3();
}

int main()
{
	TestDate();
	return 0;
}

使用缺省参数:

缺省参数和构造器

#include<iostream>
using namespace std;

class Date
{
public:
	/*
	//1、无参构造器
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	*/
	//2、带参构造器
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

void TestDate()
{
	Date d1;//全缺省
	Date d2(2023, 1, 1);
	Date d4(2021);
	Date d5(2020,3);
	d1.Print();
	d2.Print();
	d4.Print();
	d5.Print();
}
int main()
{
	TestDate();
	return 0;
}

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

#include<iostream>
using namespace std;
class Date
{
public:
	/*
	//如果用户显式定义了构造函数,编译器将不再生成
	Date(int year, int month, int day)
	{
	_year = year;
	_month = month;
	_day = day;
	}
	*/
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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

int main()
{
	//将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
	//将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成无参构造函数
    //放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
	Date d1;
    d1.Print();
	return 0;
}

6、关于编译器生成的默认成员函数,可能会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d1对象调用了编译器生成的默认构造函数,但是d1对象的_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char/double/指针...,自定义类型就是我们使用class/struct/union等自己定义的类型。看看下面的程序,就会发现编译器生成无参的默认构造函数,对于内置类型成员不做处理,对于自定义类型成员会去调用它的默认构造函数(默认构造函数第7点会提及)。

(这里对内置类型的处理:是不确定(看编译器,有些编译器会做优化),建议当成不处理)

注意:对于Date类:Date* p;

           p是内置类型。

        注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。即声明时给的缺省值。

        如果内置类型声明给了缺省值,但是自己也写了构造函数,肯定就是调自己写的构造函数了,因为前面第5点已经说了:如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

#include<iostream>
using namespace std;

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	//内置类型(基本类型)
	int _year;
	int _month;
	int _day;
	//自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}

        注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。即声明时给的缺省值。

#include<iostream>
using namespace std;

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	//内置类型(基本类型)
	int _year = 1;
	int _month = 1;
	int _day = 1;
	//自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}

如下,关于栈的代码也是一样的,需要自己写构造函数,不写构造函数的话:

        上面的Date类和栈都不能自己不写构造函数,因为编译器默认生成的无参构造器不会初始化。

        但是现在自己写一个栈的构造函数后,再写一个由栈实现的一个队列,队列我们没有写它的构造函数:

#include<iostream>
using namespace std;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;

		_a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败!!!");
		}
		_capacity = capacity;
		_top = 0;
	}

private:
	int* _a;
	int _capacity;
	int _top;
};

//两个栈实现一个队列
class MyQueue
{
private:
	Stack _pushst;
	Stack _popst;
	int _size;
};

int main()
{
	Stack st1;
	MyQueue mq;

	return 0;
}

因为上面队列里,进的是我们自己写的栈的构造函数。

vs2013:编译器没有将_size初始化

vs2019:编译器做了优化,使_size = 0

C++把类型分成内置类型和自定义类型:

        内置类型:语言原生给的类型,比如int,double,char,指针......

        自定义类型:class,struct......

        编译器默认生成的无参构造函数:对于内置类型成员不做处理,对于自定义类型成员会去调用它的默认构造。

        但是这里vs2019,编译器做了优化,对int类型的_size做了处理。

总结

        1、一般情况下,我们都要自己写构造函数;

        2、成员都是自定义类型,或者声明时给了缺省值,可以考虑让编译器自己生成构造函数。

7、无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个

注意:无参构造函数全缺省构造函数我们没写时编译器默认生成的构造函数,都可以认为是默认构造函数

#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main()
{
	Date d1;//没有合适的默认构造函数
	return 0;
}

        因为这里,我们自己写了带参构造函数,编译器将不再生成无参的默认构造函数了。这个带参构造函数也不是全缺省,也没有无参构造函数。

3、析构函数

3.1 概念

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

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

3.2 特性

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

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

2、参数返回值类型。

3、一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载

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

栈的析构函数: 

#include<iostream>
using namespace std;

typedef int DataType;

class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_a = (DataType*)malloc(sizeof(DataType) * capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败!\n");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}
	void Push(DataType data)
	{
		//CheckCapacity();
		_a[_top] = data;
		_top++;
	}
	//其他方法...
	~Stack()
	{
		if (_a)
		{
			free(_a);
			_capacity = _top = 0;
			_a = nullptr;
		}
	}
private:
	DataType* _a;
	int _capacity;
	int _top;
};

int main()
{
	Stack st1;
	st1.Push(1);
	st1.Push(2);
	return 0;
}

如下,Date类和栈Stack类:

5、关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对于内置类型成员不做处理,对于自定类型成员调用它的析构函数

#include<iostream>
using namespace std;

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
	// 默认生成析构函数,行为跟构造相似
	// 内置类型成员不做处理
	// 自定义类型成员会去调用它的析构
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}
// 程序运行结束后输出:~Time()
// 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,
// 其中_year, _month,_day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;
// 而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。
// 但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,
// 而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,
// 即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的构造函数,销毁那个类的对象则调用该类的析构函数

6、如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

4、拷贝构造函数

4.1 概念

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

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

为什么需要拷贝构造函数?

#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()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;

		_a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败!!!");
		}
		_capacity = capacity;
		_top = 0;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;

		free(_a);
		_capacity = _top = 0;
		_a = nullptr;
	}
private:
	int* _a;
	int _capacity;
	int _top;
};

// 浅拷贝/值拷贝
void func1(Date date)
{
	date.Print();
}

void func2(Stack st)
{
	//...
}

int main()
{
	Date d1(2023, 10, 22);
	func1(d1);

	Stack st1;
	func2(st1);//会出问题
	return 0;
}

上方代码,对于日期类传值传参没有任何问题。

但是对于栈类,就有问题了:

那有时候有问题,有时候没有问题怎么办呢?如何解决呢?

规定,自定义类型对象拷贝的时候,调用一个函数,这个函数就叫做拷贝构造函数

4.2 特性

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

1、拷贝构造函数构造函数的一个重载形式

2、拷贝构造函数的参数只有一个必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

#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(const Date& d)  // 正确写法
	Date(const Date d)      // 错误写法:编译报错,会引发无穷递归
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

传指针也可以,且不会报错;但是Date d2(&d1); 使用起来并不直观、方便。

传指针实际上只是构成重载,拷贝构造函数规定参数使用类类型对象的引用。

加const就是为了防止修改d。

#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(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

void func1(Date date)
{
	date.Print();
}

int main()
{
	Date d1(2023, 10, 22);
	func1(d1);

	return 0;
}

3、若未显式定义,编译器会生成默认的拷贝构造函数 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

#include<iostream>
using namespace std;

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d1;
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	Date d2(d1);
	return 0;
}

        注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

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

// 这里会发现下面的程序会崩溃掉。这里就需要以后涉及的深拷贝去解决。
#include<iostream>
using namespace std;

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;
}

        注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

对于栈来讲:栈要自己写拷贝构造函数

#include<iostream>
using namespace std;

class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;

		_a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败!!!");
		}

		_capacity = capacity;
		_top = 0;
	}

	// Stack st2(st1);
	Stack(const Stack& stt)
	{
		cout << "	Stack(Stack& stt)" << endl;
		// 深拷贝
		_a = (int*)malloc(sizeof(int) * stt._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, stt._a, sizeof(int) * stt._top);
		_top = stt._top;
		_capacity = stt._capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;

		free(_a);
		_capacity = _top = 0;
		_a = nullptr;
	}
private:
	int* _a;
	int _capacity;
	int _top;
};

void func2(Stack st)
{
	//...
}
int main()
{
	Stack st1;
	func2(st1);
	return 0;
}

栈必须我们自己写拷贝构造函数,但是日期类不需要我们写也能够跑代码,什么原因呢?

        原因是第3点特性,拷贝构造函数是默认成员函数,未显式定义编译器会生成默认的拷贝构造函数 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝/值拷贝。对于内置类型会完成值拷贝。

MyQueue:用栈实现队列,这样的类也不需要自己写拷贝构造函数。

#include<iostream>
using namespace std;

class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;

		_a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败!!!");
		}

		_capacity = capacity;
		_top = 0;
	}
	// Stack st2(st1);
	Stack(const Stack& stt)
	{
		cout << "	Stack(Stack& stt)" << endl;
		// 深拷贝
		_a = (int*)malloc(sizeof(int) * stt._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, stt._a, sizeof(int) * stt._top);
		_top = stt._top;
		_capacity = stt._capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;

		free(_a);
		_capacity = _top = 0;
		_a = nullptr;
	}
private:
	int* _a;
	int _capacity;
	int _top;
};

class MyQueue
{
	Stack _pushet;
	Stack _popst;
	int _size = 0;
};

void func2(Stack st)
{
	//...
}

int main()
{
	MyQueue q1;
	MyQueue q2(q1);
	return 0;
}

Date 和 MyQueue 默认生成拷贝就可以用。

1、内置类型成员完成值拷贝

2、自定义类型成员调用这个成员的拷贝构造函数

Stack需要自己写拷贝构造,完成深拷贝。

顺序表、链表、二叉树等等的类,都需要深拷贝。

5、拷贝构造函数典型调用场景:

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year, int minute, int day)
	{
		cout << "Date(int,int,int):" << this << endl;
	}
	Date(const Date& d)
	{
		cout << "Date(const Date& d):" << this << endl;
	}
	~Date()
	{
		cout << "~Date():" << this << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
Date Test(Date d)
{
	Date temp(d);
	return temp;
}
int main()
{
	Date d1(2022, 1, 13);
	Test(d1);
	return 0;
}

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用

5、赋值运算符重载

5.1 运算符重载

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

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

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

注意

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类 类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .*   ::   sizeof   ?:   . 注意以上5个运算符不能重载

内置类型对象可以直接用各种运算符,内置类型都是简单类型。

语言自己定义,编译直接转换成指令

自定义类型呢?不支持

// 全局的operator==和operator>
#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()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
//private:
	int _year;
	int _month;
	int _day;
};
// 这里会发现:运算符重载为全局的话,就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用后面需要学习的友元解决,或者干脆重载成 成员函数。

// bool Greater(Date x, Date y)
// bool Compare1(Date x, Date y)
bool operator>(const Date& x, const Date& y)
{
	if (x._year > y._year)
	{
		return true;
	}
	else if (x._year == y._year && x._month > y._month)
	{
		return true;
	}
	else if (x._year == y._year && x._month == y._month && x._day > y._day)
	{
		return true;
	}
	return false;
}
// bool Equal(Date x, Date y)
// bool Compare2(Date x, Date y)
// bool xiangdeng(Date x, Date y)
bool operator==(const Date& x, const Date& y)
{
	return x._year == y._year
		&& x._month == y._month
		&& x._day == y._day;
}

int main()
{
    // 内置类型对象可以直接用各种运算符,内置类型都是简单类型
	// 语言自己定义,编译直接转换成指令
	// 自定义类型呢?不支持
	/*
	int x = 1, y = 2;
	bool ret1 = x > y;
	bool ret2 = x == y;
	*/
	Date d1;
	Date d2(2023, 10, 22);

	//d1 == d2;// 自定义类型是不能直接用各种运算符
	//d1 > d2;
	/*
	cout << Greater(d1, d2) << endl;
    cout << Compare1(d1, d2) << endl;// 这种命名方式方式不直观
    cout << Equal(d1, d2) << endl;
    cout << Compare2(d1, d2) << endl;// 这种命名方式方式不直观
	cout << xiangdeng(d1, d2) << endl;// 这种命名方式是不可取的
	*/

	// 运算符重载
	// 函数重载
	// 他们之间没有关联
	// 运算符重载:使得自定义类型可以直接使用运算符
	// 函数重载:可以允许参数不同的同名函数,
	cout << operator>(d1, d2) << endl;// 这样使用是正确的
	cout << operator==(d1, d2) << endl;
	/*
	cout << (d1 > d2) << endl;// 这样使用也是正确的
	cout << (d1 == d2) << endl;
	*/
    //以下方式就方便阅读代码且直观
	bool ret1 = d1 > d2;  // operator>(d1, d2);
	bool ret2 = d1 == d2; // operator==(d1, d2);
    cout << ret1 << endl;
	cout << ret2 << endl;
	return 0;
}

重载成 成员函数:

// 重载成 成员函数
#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==(Date* this, const Date& d2)
	// 这里需要注意的是,左操作数是this,指向调用函数的对象
	bool operator==(const Date & d2)
	{
		return _year == d2._year
		    && _month == d2._month
			&& _day == d2._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    Date d1;
	Date d2(2023, 10, 22);
	bool ret1 = d1 == d2;//d1.operator==(d2) -> d1.operator==(&d1,d2)
    cout << ret1 << endl;
    return 0;
}

一个重载哪些运算符呢?主要这个运算符有没有意义

有意义就可以实现,没有意义就不要实现。

例如,对于日期类:

d1 + d2 //没有意义

d1 - d2 //有意义,两个日期相差多少天,返回值是int,即天数

d1 * d2 //没有意义

d1 / d2 //没有意义

d1 + 10 //日期加上天数,有意义,返回值是日期类

d1 - 10 //日期减去天数,有意义,返回值是日期类

// 日期加上天数
#include<iostream>
#include<assert.h>
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	int GetMonthDay(int year, int month)
	{
		assert(year >= 1 && month >= 1 && month <= 12);

		int monthArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30,31 };

		if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
			return 29;

		return monthArray[month];
	}
	// d1 += 100
	Date& operator+=(int day)
	{
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);

			++_month;

			if (_month == 13)
			{
				_year++;
				_month = 1;
			}
		}
		return *this;
	}

	// d1 + 100 ,不改变d1
	/*
    Date operator+(int day)
	{
		Date tmp(*this);

		tmp._day += day;
		while (tmp._day > GetMonthDay(tmp._year, tmp._month))
		{
			tmp._day -= GetMonthDay(tmp._year, tmp._month);

			++tmp._month;

			if (tmp._month == 13)
			{
				tmp._year++;
				tmp._month = 1;
			}
		}
		return tmp;
	}
    */
	// 以下是operator+优化版本
	// + 复用 +=
	Date operator+(int day)
	{
		Date tmp(*this);
		tmp += day;
		return tmp;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(2023, 12, 22);

	d2 += 50;
	d2.Print();

	Date ret3 = d1 + 450;
	d1.Print();
	ret3.Print();

    /*
    // 内置类型
	int i = 0, j = 0;
	int ret = j += i += 50;
    */
	return 0;
}
// += 复用 +
// 日期+=天数
// d1 += 100,d1要改变
// d1 + 100,d1不改变
Date& operator+=(int day)
{
	*this = *this + day;
	return *this;
}
// 日期+天数
Date operator+(int day)
{
	Date tmp(*this);

	tmp._day += day;
	while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year, tmp._month);

		++tmp._month;

		if (tmp._month == 13)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}
	return tmp;
}

+复用+=                                                                                            +=复用+

则+复用+=更好

5.2 赋值运算符重载

1、赋值运算符重载格式

  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this:要符合连续赋值的含义
#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(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	Date& operator=(const Date& d)
	{
        // 检测是否自己给自己赋值
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

// operator= 我们不写,编译器会生成默认的operator=
// 跟拷贝构造的行为类似,内置类型值拷贝,自定义类型调用它的赋值
// Date、MyQueue 也可以不写,默认生成operator=就可以用
// Stack必须自己实现operator=,实现深拷贝
int main()
{
	Date d1(2023, 12, 24);
	Date d2(2022, 1, 1);
	
	// 一个已经存在的对象去拷贝初始化另一个对象
	Date d3(d1);
	d3.Print();
	
	// 两个已经存在的对象拷贝
	//d2 = d1;
	d2 = d1 = d3;//为了可以连续赋值,赋值运算符重载需要返回值
	d2.Print();
	//d1 = d1;//自己给自己赋值,是可以的,但是没必要去跑赋值运算符重载代码
	
	/*
	// 内置类型
	int i, j;
	i = j = 10;//连续赋值
	*/
	return 0;
}

2、赋值运算符只能重载成类的成员函数,不能重载成全局函数

#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()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	int _year;
	int _month;
	int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
	if (&left != &right)
	{
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}

int main()
{
	Date d1(2023, 12, 24);
	Date d2;
    
	d2 = d1;
	d2.Print();
	return 0;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

        原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

3、用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的。而自定义类型成员变量需要调用对应类的复制运算符重载完成赋值。

#include <iostream>
using namespace std;

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time& operator=(const Time& t)
	{
		if (this != &t)
		{
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	Date d2;
	d1 = d2;
	return 0;
}

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

// 这里会发现下面的程序会崩溃掉。这里就需要深拷贝去解决,以后会提到。
#include <iostream>
using namespace std;

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2;
	s2 = s1;
	return 0;
}

程序崩溃:

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

5.3 前置++和后置++重载

#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()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	// 前置++:返回+1之后的结果
	// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
	Date& operator++()
	{
		_day += 1;
		return *this;
	}
	// 后置++:
	// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
	// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
	// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
	// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
	Date operator++(int)
	{
		Date temp(*this);
		_day += 1;
		return temp;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d;
	Date d1(2023, 1, 13);
	cout << "d1:";
	d1.Print();
	d = d1++;// d: 2023,1,13   d1:2023,1,14
	cout << "d = d1++之后,d和d1分别是:" << endl;
	d.Print();
	d1.Print();
	d = ++d1;// d: 2023,1,15   d1:2023,1,15
	cout << "继续:d = ++d1之后,d和d1分别是:" << endl;
	d.Print();
	d1.Print();
	return 0;
}

6、日期类的实现

6.1 实现:日期 - 日期,返回天数

方法一:两个日期先各自与当年的1月1日作差,再两个年份的1月1日作差

方法二:

6.2 实现代码1

// 日期类的实现:头文件Date.h
#pragma once

#include<iostream>
#include<assert.h>
using namespace std;

class Date
{
public:
	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);
	// 打印日期
	void Print();
	 拷贝构造函数
	 d2(d1)
	//Date(const Date& d);
	 析构函数
	//~Date();

	// 获取某年某月的天数
	int GetMonthDay(int year, int month);

	// 赋值运算符重载
	// d2 = d3   ->   d2.operator=(&d2, d3)
	Date& operator=(const Date& d);

	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day);
	// 日期-=天数
	Date& operator-=(int day);
	// 日期-天数
	Date operator-(int day);

	// 前置++
	Date& operator++();
	// 后置++
	Date operator++(int);
	// 前置--
	Date& operator--();
	// 后置--
	Date operator--(int);

	// >运算符重载
	bool operator>(const Date& d);
	// ==运算符重载
	bool operator==(const Date& d);
	// >=运算符重载
	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;
};
// 日期类的实现:文件Date.cpp
#include "Date.h"

// 全缺省的构造函数
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;

	if (_year < 1 ||
		_month < 1 || _month > 12 ||
		_day < 1 || _day > GetMonthDay(_year, _month))
	{
		//assert(false);
		Print();
		cout << "日期非法" << endl;
	}
}

// 打印日期
void Date::Print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
	static int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = days[month];
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{
		day += 1;
	}
	return day;
}

// 赋值运算符重载
Date& Date::operator=(const Date& d)
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

// 日期+=天数
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)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}
// +复用 +=
// 日期+天数
Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}
// 日期-天数
Date Date::operator-(int day)
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}

//语法设计。无法逻辑闭环,那么这时就只能特殊处理
// 前置++
// ++d1
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
// 后置++
// d1++
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}
// 前置--
// --d1
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}
// 后置--
// d1--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}

// >运算符重载
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;
	return false;
}
// >=运算符重载
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);
}

// ==运算符重载
bool Date::operator==(const Date& y)
{
	return _year == y._year
		&& _month == y._month
		&& _day == y._day;
}
// !=运算符重载
bool Date::operator!=(const Date& d)
{
	return !(*this == d);
}

// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
	// 假设左大右小1
	int flag = 1;
	Date max = *this;
	Date min = d;

	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n * flag;
}
// 日期类的实现:测试文件Test.cpp
#include "Date.h"

void TestDate1()
{
	Date d1(2003, 5, 18);
	d1.Print();
	Date d2(2023, 10, 24);
	d2.Print();

	Date ret1 = d1 + 100;
	ret1.Print();
	Date ret2 = d1 + 30000;
	ret2.Print();

	Date ret3 = d1 - 100;
	ret3.Print();
	Date ret4 = d1 - 10000;
	ret4.Print();
}
void TestDate2()
{
	Date d1(2023, 10, 24);
	d1.Print();

	++d1;
	d1.operator++();
	d1.Print();

	d1++;
	d1.operator++(10);
	d1.operator++(1);
	d1.Print();
}
void TestDate3()
{
	Date d1(2023, 10, 24);
	d1.Print();
	Date d2(2024, 2, 10);
	d2.Print();

	cout << d2 - d1 << endl;
}
void TestDate4()
{
	Date d1(2023, 10, 24);
	Date d;
	d1 += -100;

	d1.Print();
}
int main()
{
	TestDate1();
	cout << "======================" << endl;
	TestDate2();
	cout << "======================" << endl;
	TestDate3();
	cout << "======================" << endl;
	TestDate4();
	return 0;
}

6.3 cout和cin

6.3.1 cout流插入

下面主要以流插入cout为例:

void Test()
{
	Date d1(2022, 1, 13);
	
    d1.Print();
	//cout << d1 << endl;//直接这样写是错误的,Date类是自定义类型
    //我们需要自己写重载函数
}

(1)ostream类定义了从内存到输出设备的功能,我们常用的cout就是ostream类的对象。

(2)istream类定义了从输入设备到内存的功能,我们常用的cin就是istream类的对象。

(3)iostream文件定义了ostream和istream类的对象,也就是cout和cin,我们平时引用头文件就是在做这个操作。

内置类型为什么支持流插入和流提取?因为C++这个库里已经帮我们写好了

内置类型为什么自动识别类型?因为函数重载和参数匹配。

double d = 1.1;
int i = 2;

cout << i;
cout << d;

关键部分代码如下:

void Date::operator<<(ostream& out)
{
	out << _year << "年" << _month << "月" << _day << "日" << endl;
}

void Test()
{
	Date d1(2010, 10, 21);
	//d1.Print();
	//cout << d1;//这样写不行
	d1 << cout;//这样写可以

	/*
	double d = 1.1;
	int i = 2;
	cout << d << endl;
	cout << i << endl;
	*/
}

上面代码:cout << d1这样是写不行的,但是d1 << cout却可以。

        因为双操作数的运算符,第一个参数是左操作数,第二个参数是右操作数。

        d1 << cout; 可以转换为d1.operator<<(&d1, cout)

        d1 << cout; 代码可以跑起来,但是不符合我们平时的习惯。

        Date对象默认占据第一个位置,所以<<实现成 成员函数是不太好的。

把该重载函数写到全局,关键部分代码如下:

void operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	// 但是现在面临新的问题:访问私有的问题
	// 目前,暂且将_year、_month、_day改为public
}

void TestDate5()
{
	Date d1(2023, 10, 21);
	//d1.Print();
	// << 重载成成员函数
	//cout << d1;//这样写不行
	//d1 << cout;//这样写可以

	// << 重载成全局函数
	cout << d1;// operator<<(cout, d1)
}

6.3.2 解决访问私有的问题:

方法一:将其改为公有public,但是这种方法是不可取的,一般情况,类的数据建议设置成私有的。

方法二:流插入可以提供get方法,流提取可以提供set方法(类似于Java的get和set方法)

方法三:友元,关键字 friend

//友元函数
friend void operator<<(ostream& out, const Date& d);

6.3.3 其他问题:

流插入还支持cout << d << i << endl;这样的写法

但是以上的实现并不支持这样的实现。

解决:operator<<函数调用应该有返回值,且是ostream&,然后继续去做后一次流插入的左操作数

关键部分代码如下:

friend ostream& operator<<(ostream& out, const Date& d);

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

void Test()
{
	Date d1(2023, 10, 21);
	Date d2(2022, 9, 20);
	cout << d1;
	cout << d1 << d2 << endl;
}

6.3.4 流提取cin

关键部分代码如下:

friend istream& operator>>(istream& in, Date& d);

// 流提取cin
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

void Test()
{
	Date d1, d2;
	cin >> d1 >> d2;
	cout << d1 << d2 << endl;
}

6.3.5 总结

总结

1、其他的运算符一般是实现成成员函数。

2、>>、<<运算符必须是实现到全局,这样才能让流对象做第一个参数,才符合我们平时的阅读习惯。

3、C++的流,本质是为了解决,自定义类型的输入和输出问题;

C语言中的printf和scanf无法解决自定义类型的输入输出问题;

C++使用面向对象运算符重载解决自定义类型的输入输出问题。

6.4 实现代码2

// 日期类的实现:头文件Date.h
#pragma once

#include<iostream>
#include<assert.h>
using namespace std;

class Date
{
public:
	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);
	// 打印日期
	void Print();
	 拷贝构造函数
	 d2(d1)
	//Date(const Date& d);
	 析构函数
	//~Date();

	// 获取某年某月的天数
	int GetMonthDay(int year, int month);

	// 赋值运算符重载
	// d2 = d3   ->   d2.operator=(&d2, d3)
	Date& operator=(const Date& d);

	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day);
	// 日期-=天数
	Date& operator-=(int day);
	// 日期-天数
	Date operator-(int day);

	// 前置++
	Date& operator++();
	// 后置++
	Date operator++(int);
	// 前置--
	Date& operator--();
	// 后置--
	Date operator--(int);

	// >运算符重载
	bool operator>(const Date& d);
	// ==运算符重载
	bool operator==(const Date& d);
	// >=运算符重载
	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);

	//void operator<<(ostream& out);
	//友元函数
	//friend void operator<<(ostream& out, const Date& d);
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
private:
	int _year;
	int _month;
	int _day;
};

// 将<<重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
// 而且将d写到第二个参数
//void operator<<(ostream& out, const Date& d);
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
// 日期类的实现:文件Date.cpp
#include "Date.h"

// 全缺省的构造函数
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;

	if (_year < 1 ||
		_month < 1 || _month > 12 ||
		_day < 1 || _day > GetMonthDay(_year, _month))
	{
		//assert(false);
		Print();
		cout << "日期非法" << endl;
	}
}

// 打印日期
void Date::Print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
	static int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = days[month];
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{
		day += 1;
	}
	return day;
}

// 赋值运算符重载
Date& Date::operator=(const Date& d)
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

// +复用 +=
// 日期+=天数
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)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}
// 日期+天数
Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}
// 日期-天数
Date Date::operator-(int day)
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}

// 前置++
// ++d1
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
// 后置++
// d1++
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}
// 前置--
// --d1
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}
// 后置--
// d1--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}

// >运算符重载
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;
	return false;
}
// >=运算符重载
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);
}

// ==运算符重载
bool Date::operator==(const Date& y)
{
	return _year == y._year
		&& _month == y._month
		&& _day == y._day;
}
// !=运算符重载
bool Date::operator!=(const Date& d)
{
	return !(*this == d);
}

// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
	// 假设左大右小1
	int flag = 1;
	Date max = *this;
	Date min = d;

	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n * flag;
}

// 流插入cout
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}
// 流提取cin
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
// 日期类的实现:测试文件Test.cpp
#include "Date.h"

void TestDate1()
{
	Date d1(2003, 5, 18);
	d1.Print();
	Date d2(2023, 10, 24);
	d2.Print();

	Date ret1 = d1 + 100;
	ret1.Print();
	Date ret2 = d1 + 30000;
	ret2.Print();

	Date ret3 = d1 - 100;
	ret3.Print();
	Date ret4 = d1 - 10000;
	ret4.Print();
}
void TestDate2()
{
	Date d1(2023, 10, 24);
	d1.Print();

	++d1;
	d1.operator++();
	d1.Print();

	d1++;
	d1.operator++(10);
	d1.operator++(1);
	d1.Print();
}
void TestDate3()
{
	Date d1(2023, 10, 24);
	d1.Print();
	Date d2(2024, 2, 10);
	d2.Print();

	cout << d2 - d1 << endl;
}
void TestDate4()
{
	Date d1(2023, 10, 24);
	Date d;
	d1 += -100;

	d1.Print();
}
void TestDate5()
{
	Date d1(2023, 10, 21);
	Date d2(2022, 9, 20);

	cout << d1;
	cout << d1 << d2 << endl;
}
void TestDate6()
{
	Date d1, d2;
	cin >> d1 >> d2;
	cout << d1 << d2 << endl;
}
int main()
{
	TestDate1();
	cout << "======================" << endl;
	TestDate2();
	cout << "======================" << endl;
	TestDate3();
	cout << "======================" << endl;
	TestDate4();
	cout << "======================" << endl;
	TestDate5();
	cout << "======================" << endl;
	TestDate6();
	return 0;
}

7、const成员

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

我们来看看下面的代码:

        以上代码,编译没有通过,原因是:Print()含有隐含的this指针,Date* this,而&d1是const Date*类型,这里是一个权限放大的问题。

        那在Print()函数要加个const,怎么加呢?规定在成员函数的后面加const,且声明和定义都要加const。

// const对象和非const对象都可以调用const成员函数
#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const
	{
		cout << "Print()" << endl;
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;  // 年
	int _month; // 月
	int _day;   // 日
};
int main()
{
	// const对象和非const对象都可以调用const成员函数
	const Date d1(2022, 1, 13);
	d1.Print();
	Date d2(2000, 1, 1);
	d2.Print();
	return 0;
}

成员函数定义的原则

1、能定义成const的成员函数都应该定义成const,这样const对象(权限平移)和非const对象(权限缩小)都可以调用;

2、要修改成员变量的成员函数,不能定义成const的成员函数。(const对象不能调用要修改成员变量的成员函数,这是合理的,非const对象才能调用)。

例如:Date& operator+=(int day);Date& operator-=(int day);不能加const;

Date operator+(int day);Date operator-(int day);可以加const。

1、const对象可以调用const成员函数

2、const对象不可以调用非const成员函数

3、非const对象可以调用const成员函数

4、非const对象可以调用非const成员函数

5、const成员函数内不可以调用其它的非const成员函数

6、非const成员函数内可以调用其它的非const成员函数

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

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

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date* operator&()
	{
		cout << "Date* operator&()" << endl;
		return this;
	}
	const Date* operator&() const
	{
		cout << "const Date* operator&() const" << endl;
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

int main()
{
	const Date d1(2022, 1, 13);
	Date d2(2000, 1, 1);

	cout << &d1 << endl;
	cout << &d2 << endl;
	return 0;
}

如果只屏蔽掉Date* operator&():

如果只屏蔽掉const Date* operator&() const:

如果都屏蔽掉:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值