类和对象(C++)(中)

1. 类的默认成员函数

默认成员函数就是⽤⼾没有显式实现,编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类,我们不写的情况下编译器会默认⽣成以下6个默认成员函数,需要注意的是这6个中最重要的是前4个,最后两个取地址重载不重要,我们稍微了解⼀下即可。默认成员函数很重要,也⽐较复杂,我们要从两个⽅⾯ 去学习:
  • 第⼀:我们不写时,编译器默认⽣成的函数⾏为是什么,是否满⾜我们的需求。
  • 第⼆:编译器默认⽣成的函数不满⾜我们的需求,我们需要⾃⼰实现,那么如何⾃⼰实现?

6f6ea36b719946d2ac23087b9f1e591e.png 2. 构造函数

构造函数是 特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是 构造函数的主要任务并不是开空间创建对象(我们常使⽤的局部对象是栈帧创建时,空间就开好了),⽽是对象实例化时初始化对象。构造函数的 本质是要替代我们以前Stack和Date类中写的Init函数的功能,构造函数⾃动调⽤的特点就完美的替代的了Init。
构造函数的特点:
  1. 函数名与类名相同。
  2. ⽆返回值。 (返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)
  3. 对象实例化时系统会⾃动调⽤对应的构造函数。
  4. 构造函数可以重载
  5. 如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦⽤⼾显式定义,编译器将不再⽣成。
  6. ⽆参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。⽆参构造函数和全缺省构造函数虽然构成函数重载,但是调⽤时会存在歧义。总结⼀下就是不传实参就可以调⽤的构造就叫默认构造。
  7. 我们不写,编译器默认⽣成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要⽤初始化列表才能解决。

 

说明:C++把类型分成内置类型(基本类型)和⾃定义类型。内置类型就是语⾔提供的原⽣数据类型, 如:int/char/double/指针等,⾃定义类型就是我们使⽤class/struct等关键字⾃⼰定义的类型。
注:
  • 不传实参就能调用的就叫默认构造。
  • 一般情况下构造函数都需要自己写,少数情况下,默认生成就可以用,比如MyQueue。

 

1.  #define _CRT_SECURE_NO_WARNINGS 1
2.  #include<iostream>
3.  using namespace std;
4.  class Date
5.  {
6.  public:
7.  	// 1.⽆参构造函数
8.  	Date()
9.  	{
10. 		_year = 1;
11. 		_month = 1;
12. 		_day = 1;
13. 	}
14. 	//2.带参构造函数
15. 	Date(int year, int month, int day)
16. 	{
17. 		_year = year;
18. 		_month = month;
19. 		_day = day;
20. 	}
21. 	// 3.全缺省构造函数
22. /*Date(int year = 1, int month = 1, int day = 1)
23. {
24.    _year = year;
25.    _month = month;
26.    _day = day;
27. }*/
28. 	void Print()
29. 	{
30. 		cout << _year << "/" << _month << "/" << _day << endl;
31. 	}
32. private:
33. 	int _year;
34. 	int _month;
35. 	int _day;
};
36. int main()
37. {
38. 	// 如果留下三个构造中的第⼆个带参构造,第⼀个和第三个注释掉
39. 	// 编译报错:error C2512: “Date”: 没有合适的默认构造函数可⽤
40. 	Date d1; // 调⽤默认构造函数
41. 	Date d2(2025, 1, 1); // 调⽤带参的构造函数
42. 	// 注意:如果通过⽆参构造函数创建对象时,对象后⾯不⽤跟括号,否则编译器⽆法
43. 	// 区分这⾥是函数声明还是实例化对象
44. 	// warning C4930: “Date d3(void)”: 未调⽤原型函数(是否是有意⽤变量定义的?)
45. 	Date d3();
46. 	d1.Print();
47. 	d2.Print();
48. 	return 0;
49. }

3. 析构函数

析构函数与构造函数功能相反,析构函数不是完成对对象本⾝的销毁,⽐如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会⾃动调⽤析构函数,完成对象中资源的清理释放⼯作。析构函数的功能类⽐我们之前Stack实现的Destroy功能,⽽像Date没有Destroy,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的。
析构函数的特点:
  1. 析构函数名是在类名前加上字符 ~。
  2. ⽆参数⽆返回值。 (这⾥跟构造类似,也不需要加void)
  3. ⼀个类只能有⼀个析构函数。若未显式定义,系统会⾃动⽣成默认的析构函数。
  4. 对象⽣命周期结束时,系统会⾃动调⽤析构函数。
  5. 跟构造函数类似,我们不写编译器⾃动⽣成的析构函数对内置类型成员不做处理,⾃定类型成员会调⽤他的析构函数。
  6. 还需要注意的是我们显⽰写析构函数,对于⾃定义类型成员也会调⽤他的析构,也就是说⾃定义类型成员⽆论什么情况都会⾃动调⽤析构函数。
  7. 如果类中没有申请资源时,析构函数可以不写,直接使⽤编译器⽣成的默认析构函数,如Date;如果默认⽣成的析构就可以⽤,也就不需要显⽰写析构,如MyQueue;但是有资源申请时,⼀定要⾃⼰写析构,否则会造成资源泄漏,如Stack。
  8. ⼀个局部域的多个对象,C++规定后定义的先析构。
1.  #include<iostream>
2.  using namespace std;
3.  typedef int STDataType;
4.  class Stack
5.  {
6.  public:
7.  	Stack(int n=4)
8.  	{
9.  		_a = (STDataType*)malloc(sizeof(STDataType) * n);
10.  		if (nullptr == _a)
11. 		{
12. 			perror("malloc fail!");
13. 			return;
14. 		}
15. 		_capacity = n;
16. 		_size = 0;
17. 	}
18. 	~Stack()
19. 	{
20. 		cout << "~Stack" << endl;
21. 		free(_a);
22. 		_capacity = _size = 0;
23. 	}
24. private:
25. 	STDataType* _a;
26. 	size_t _capacity;
27. 	size_t _size;
28. };
29. int main()
30. {
31. 	Stack st;
32. 	return 0;
33. }

30a9f71930744d57b0520f3e1721483a.png

1.  #include<iostream>
2.  using namespace std;
3.  typedef int STDataType;
4.  class Stack
5.  {
6.  public:
7.  	Stack(int n = 4)
8.  	{
9.  		_a = (STDataType*)malloc(sizeof(STDataType) * n);
10. 		if (nullptr == _a)
11. 		{
12. 			perror("malloc fail!");
13. 			return;
14. 		}
15. 		_capacity = n;
16. 		_size = 0;
17. 	}
18. 	~Stack()
19. 	{
20. 		cout << "~Stack" << endl;
21. 		free(_a);
22. 		_capacity = _size = 0;
23. 	}
24. private:
25. 	STDataType* _a;
26. 	size_t _capacity;
27. 	size_t _size;
28. };
29. class MyQueue
30. {
31. public:
32. 	//编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源
33. 	// 显⽰写析构,也会⾃动调⽤Stack的析构
34. 	~MyQueue()
35. 	{
36. 		cout << "~MyQueue" << endl;
37. 	}
38. private:
39. 	Stack pushst;
40. 	Stack popst;
41. };
int main()
42. {
43. 	Stack st;
44. 	MyQueue mq;
45. 	return 0;
46. }

eae1a51dd2cb48208334678ea1d64b11.png

 23f5888705784d98b2ca69f04bd3f61f.png

4. 拷贝构造函数 

如果⼀个构造函数的第⼀个参数是⾃⾝类类型的引⽤,且任何额外的参数都有默认值,则此构造函数也叫做拷⻉构造函数,也就是说拷⻉构造是⼀个特殊的构造函数。
拷⻉构造的特点:
  1. 拷⻉构造函数是构造函数的⼀个重载。
  2. 拷⻉构造函数的第⼀个参数必须是类类型对象的引⽤,使⽤传值⽅式编译器直接报错,因为语法逻辑上会引发⽆穷递归调⽤。 拷⻉构造函数也可以多个参数,但是第⼀个参数必须是类类型对象的引⽤,后⾯的参数必须有缺省值。
  3. C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥⾃定义类型传值传参和传值返回都会调⽤拷⻉构造完成。
  4. 若未显式定义拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷⻉构造.
  5. 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的拷⻉构造就可以完成需要的拷⻉,所以不需要我们显⽰实现拷⻉构造。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的拷⻉构造完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃定义类型 Stack成员,编译器⾃动⽣成的拷⻉构造会调⽤Stack的拷⻉构造,也不需要我们显⽰实现MyQueue的拷⻉构造。这⾥还有⼀个⼩技巧,如果⼀个类显⽰实现了析构并释放资源,那么他就需要显⽰写拷⻉构造,否则就不需要。
  6. 传值返回会产⽣⼀个临时对象调⽤拷⻉构造,传引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回。
1.  #include<iostream>
2.  using namespace std;
3.  
4.  class Date
5.  {
6.  public:
7.  	Date(int year = 1, int month = 1, int day = 1)
8.  	{
9.  		_year = year;
10. 		_month = month;
11. 		_day = day;
12. 	}
13. 	// 编译报错:error C2652: “Date”: ⾮法的复制构造函数: 第⼀个参数不应是“Date”
14. 	//Date(Date d)
15. 	Date(const Date& d)
16. 	{
17. 		_year = d._year;
18. 		_month = d._month;
19. 		_day = d._day;
20. 	}
21. 	Date(Date* d)
22. 	{
23. 		_year = d->_year;
24. 		_month = d->_month;
25. 		_day = d->_day;
26. 	}
27. 	void Print()
28. 	{
29. 		cout << _year << "-" << _month << "-" << _day << endl;
30. 	}
31. private:
32. 	int _year;
33. 	int _month;
34. 	int _day;
35. };
36. void Func1(Date d)
37. {
38. 	cout << &d << endl;
39. 	d.Print();
40. }
41. Date& Func2()
42. {
43. 	Date tmp(2024, 7, 5);
44. 	tmp.Print();
45. 	return tmp;
46. }
47. int main()
48. {
49. 	Date d1(2024, 7, 5);
50. 	// C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥传值传参要调⽤拷⻉构造
51. 	// 所以这⾥的d1传值传参给d要调⽤拷⻉构造完成拷⻉,传引⽤传参可以较少这⾥的拷⻉
52. 	Func1(d1);//这里先调用拷贝构造,再调用Func1函数
53. 	cout << &d1 << endl;
54. 	// 这⾥可以完成拷⻉,但是不是拷⻉构造,只是⼀个普通的构造
55. 	Date d2(&d1);
56. 	d1.Print();
57. 	d2.Print();
58. 	//这样写才是拷⻉构造,通过同类型的对象初始化构造,⽽不是指针
59. 	Date d3(d1);
60. 	d2.Print();
61. 	// 也可以这样写,这⾥也是拷⻉构造
62. 	Date d4 = d1;
63. 	d2.Print();
64. 	// Func2返回了⼀个局部对象tmp的引⽤作为返回值
65. 	// Func2函数结束,tmp对象就销毁了,相当于了⼀个野引⽤
66. 	Date ret = Func2();
67. 	ret.Print();
68. 	return 0;
69. }

 82d80f0ddfc84a7aa2e8977e64603b34.png

 这里每次调用拷贝构造函数之前都要先传值传参,传值传参是一种拷贝,又形成一个新的拷贝构造,就形成了无穷递归,但是如果传引用返回就不会形成拷贝构造,自然也不会形成无穷递归。

C++规定:类类型传值传参必须调用拷贝构造。

c342e4a0361f4df88c6278c0a1f89b14.png

所以类似于栈一样的默认生成的拷贝构造不支持我们的需要,所以我们需要自己写,再开辟一块空间。 

5. 赋值运算符重载

5.1. 运算符重载

  • 当运算符被⽤于类类型的对象时,C++语⾔允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使⽤运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错。
  • 运算符重载是具有特殊名字的函数,他的名字是由operator和后⾯要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。
  • 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。
  • 如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数⽐运算对象少⼀个。
  • 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。
  • 不能通过连接语法中没有的符号来创建新的操作符:⽐如operator@。
  • .*   ::   sizeof   ?:   .   注意以上5个运算符不能重载。(选择题⾥⾯常考)
  • 操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: int operator+(int x, int y)
  • ⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义,⽐如Date类重载operator-就有意义,但是重载operator+就没有意义。
  • 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分。
  • 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了 对象<<cout,不符合使⽤习惯和可读性。
  • 重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。
1.  #include<iostream>
2.  using namespace std;
3.  
4.  class Date
5.  {
6.  
7.  public:
8.  	Date(int year=2024,int month=10,int day=5)
9.  	{
10. 		_year = year;
11. 		_month = month;
12. 		_day = day;
13. 	}
14. 	//Date();
15. public:
16. 	int _year;
17. 	int _month;
18. 	int _day;
19. };
20. bool operator<(const Date& x1, const Date& x2)
21. {
22. 	if (x1._year < x2._year)
23. 	{
24. 		return true;
25. 	}
26. 	else if (x1._year == x1._year&&x1._month<x2._month)
27. 	{
28. 		return true;
29. 	}
30. 	else if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day)
31. 	{
32. 		return true;
33. 	}
34. 	return false;
35. }
36. int main()
37. {
38. 	Date d1;
39. 	Date d2(2024, 3, 24);
40. 	//if (operator<(d1, d2))
41.     if(d1<d2)
42. 	{
43. 		printf("True");
44. 	}
45. 	else
46. 	{
47. 		printf("False");
48. 	}
49. 	return 0;
50. }

上述代码中成员函数被置为公开,但如果我们就是想将成员函数保护起来,不让随意访问,可以有三种方法,第一种使用get函数,第二种使用友元函数(以后再讲)着重讲一下第三种,将函数写在类内部,便可随意访问成员函数不受限制。

1.  #include<iostream>
2.  using namespace std;
3.  
4.  class Date
5.  {
6.  
7.  public:
8.  	Date(int year = 2024, int month = 10, int day = 5)
9.  	{
10. 		_year = year;
11. 		_month = month;
12. 		_day = day;
13. 	}
14. 	bool operator<(const Date& d)
15. 	{
16. 		if (_year < d._year)
17. 		{
18. 			return true;
19. 		}
20. 		else if (_year == d._year &&_month < d._month)
21. 		{
22. 			return true;
23. 		}
24. 		else if (_year == d._year && _month == d._month && _day < d._day)
25. 		{
26. 			return true;
27. 		}
28. 		return false;
29. 	}
30. 
31. 	//Date();
32. private:
33. 	int _year;
34. 	int _month;
35. 	int _day;
36. };
37. int main()
38. {
39. 	Date d1;
40. 	Date d2(2024, 3, 24);
41. 	//if (operator<(d1, d2))
42. 	if (d1 < d2)
43. 	{
44. 		printf("True");
45. 	}
46. 	else
47. 	{
48. 		printf("False");
49. 	}
50. 	return 0;
51. }

但我们会发现写在类内部时,参数少了一个,这是因为如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数⽐运算对象少⼀个,这是系统给我们加上了。

简单介绍一下 .* 运算符(了解一下即可)

1.  #include<iostream>
2.  using namespace std;
3.  class Date
4.  {
5.  public:
6.  	Date(int year,int month,int day)
7.  	{
8.  		year = _year;
9.  		month = _month;
10. 		day = _day;
11. 	}
12. private:
13. 	int _year;
14. 	int _month;
15. 	int _day;
16. };
17. class A
18. {
19. public:
20. 	void func()
21. 	{
22. 		cout << "A::func()" << endl;
23. 	}
24. };
25. //typdef 时,改变后的名字要放在*后
26. typedef void(A::* PF)();
27. int main()
28. {
29. 	//定义函数指针类型,如果包含的函数在类中,需要指定域类
30. 	/*void(A::*pf)();
31. 	//C++规定:如果要取到函数指针,不仅要指定类域,还要在前面加上&
32. 	pf = &A::func;*/
33. 	PF pf;
34. 	pf = &A::func;
35. 	A obj;
36. 	//进行回调函数时,应使用操作符.*
37. 	(obj.*pf)();
38. 	Date d1(2024, 11, 1);
39. 	Date d2(2024, 11, 2);
40. 	return 0;
41. }

operator前支++和后置++区别与联系:

重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分。

1.  #include<iostream>
2.  using namespace std;
3.  class Date
4.  {
5.  public:
6.  	// ++d1
7.  	//d1.operator++()
8.  	Date(int year, int month, int day)
9.  	{
10. 		_year = year;
11. 		_month = _month;
12. 		_day = day;
13. 	}
14. 	// ++d1
15. 	//d1.operator++()
16. 	Date operator+=(int i)
17. 	{
18. 
19. 	}
20. 	Date operator++()
21. 	{
22. 		//_day += 1;
23. 		//先不管月进位,主要了解前置++和后置++
24. 		*this += 1;
25. 		return *this;
26. 	}
27. 	//使用引用传值,d1还在,不用引用会造成无穷递归
28. 	// d1++
29. 	//d1.operqtor++(1)参数只要是整数都可以
30. 	Date operator++(int i)
31. 	{
32. 		Date tmp(*this);
33. 		*this += 1;
34. 		return tmp;
35. 		//返回tmp的拷贝,所以不用引用传值
36. 	}
37. private:
38. 	int _year;
39. 	int _month;
40. 	int _day;
41. };
42. int main()
43. {
44. 	Date d1(2024,11,20);
45. 	d1++;
46. 	return 0;
47. }

5.2 赋值运算符重载

赋值运算符重载是⼀个默认成员函数,⽤于完成 两个已经存在的对象直接的拷⻉赋值,这⾥要注意跟拷⻉构造区分,拷⻉构造⽤于 ⼀个已经存在的对象拷⻉初始化 给另⼀个要创建的对象
赋值运算符重载的特点:
  1. 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const 当前类类型引⽤,否则会传值传参会有拷⻉。
  2. 有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值⽬的是为了⽀持连续赋值场景。
  3. 没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认拷⻉构造函数类似,对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的赋值重载函数。Stack需要自己实现深拷贝的operator=
  4. 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的赋值运算符重载就可以完成需要的拷⻉,所以不需要我们显⽰实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的赋值运算符重载完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器⾃动⽣成的赋值运算符重载会调⽤Stack的赋值运算符重载,也不需要我们显⽰实现MyQueue的赋值运算符重载。这⾥还有⼀个⼩技巧,如果⼀个类显⽰实现了析构并释放资源,那么他就需要显⽰写赋值运算符重载,否则就不需要。
1.  #include<iostream>
2.  using namespace std;
3.  class Date
4.  {
5.  public:
6.  	// ++d1
7.  	//d1.operator++()
8.  	Date(int year, int month, int day)
9.  	{
10. 		_year = year;
11. 		_month = month;
12. 		_day = day;
13. 	}
14. 	//这里也是拷贝构造,所以要加上引用
15. 	void operator=(const Date& d)
16. 	{
17. 		_year = d._year;
18. 		_month = d._month;
19. 		_day = d._day;
20. 	}
21. 	void Print()
22. 	{
23. 		cout << _year << " " << _month << " " << _day << endl;
24. 	}
25. private:
26. 	int _year;
27. 	int _month;
28. 	int _day;
29. };
30. int main()
31. {
32. 	Date d1(2024, 10, 3);
33. 	Date d2(2024, 1, 4);
34. 	d2 = d1;
35. 	d1.Print();
36. 	d2.Print();
37. 	return 0;
38. }

也可以进行连续赋值,赋值的规则是从右往左进行赋值,比如:d3=d2=d1,是先进行d2=d1,然后再将d2=d1的值赋值给d3。不要自己给自己赋值。

1.  #include<iostream>
2.  using namespace std;
3.  class Date
4.  {
5.  public:
6.  	// ++d1
7.  	//d1.operator++()
8.  	Date(int year, int month, int day)
9.  	{
10. 		_year = year;
11. 		_month = month;
12. 		_day = day;
13. 	}
14. 	//这里也是拷贝构造,所以要加上引用
15. 	//d2=d1
16. 	//d2.operator=(d1)
17. 	/*void operator=(const Date& d)
18. 	{
19. 		_year = d._year;
20. 		_month = d._month;
21. 		_day = d._day;
22. 	}*/
23. 	//返回的是d2,加引用目的减少拷贝
24. 	Date& operator=(const Date& d)
25. 	{
26. 		_year = d._year;
27. 		_month = d._month;
28. 		_day = d._day;
29. 		return *this;
30. 	}
31. 	void Print()
32. 	{
33. 		cout << _year << " " << _month << " " << _day << endl;
34. 	}
35. private:
36. 	int _year;
37. 	int _month;
38. 	int _day;
39. };
40. int main()
41. {
42. 	Date d1(2024, 10, 3);
43. 	Date d2(2024, 1, 4);
44. 	Date d3(2024, 1, 1);
45. 	d3=d2 = d1;
46. 	d1.Print();
47. 	d2.Print();
48. 	d3.Print();
49. 	return 0;
50. }

总结:

  1. 构造函数一般都需要自己写,自己传参定义初始化;
  2. 析构、构造时有资源申请(如malloc或者fopen)等,就需要显示写析构函数;
  3. 拷贝构造和赋值重载,显示写了析构,内部管理资源,就需要显示实现深拷贝。

5.3 日期类成员函数实现

1.   //Date.h
2.   
3.   #pragma once
4.   #include<iostream>
5.   #include<assert.h>
6.   using namespace std;
7.   class Date
8.   {
9.   	//简单使用一下友元函数
10.  	//友元函数声明,可以在类外面访问私有
11.  	//friend void operator<<(ostream& out, const Date& d);
12.  	friend ostream& operator<<(ostream& out, const Date& d);
13.  	friend istream& operator>>(istream& in, Date& d);
14.  public:
15.  	Date(int year=1990, int month=12, int day=10);
16.  	void Print();
17.  	int Getmonthday(int year, int month)
18.  	{
19.  		assert(month > 0 && month < 13);
20.  		static int monthArray[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
21.  		if (month==2&&(year % 100 != 0 && year % 4 == 0) || year % 400 == 0)
22.  		{
23.  			return 29;
24.  		}
25.  		return monthArray[month];
26.  	}
27.  	bool CheckDate()
28.  	{
29.  		if(_month<1||_month>12
30.  			|| _day<1 || _day>Getmonthday(_year, _month))
31.  		{
32.  			return false;
33.  		}
34.  		else
35.  		{
36.  			return true;
37.  		}
38.  	}
39.  	bool operator<(const Date& d);
40.  	bool operator>(const Date& d);
41.  	bool operator<=(const Date& d);
42.  	bool operator>=(const Date& d);
43.  	bool operator==(const Date& d);
44.  	bool operator!=(const Date& d);
45.  	Date& operator+=(int day);
46.  	Date operator+(int day);
47.  	Date& operator-=(int day);
47.  	Date operator-(int day);
48.  	//前置++
49.  	Date& operator++();
50.  	//后置++
51.  	Date operator++(int);
52.  	//两个日期相减
53.  	int operator-(const Date& d);
54.  	//流插入
55.  	//void operator<<(ostream& out);
56.  private:
57.  	int _year;
58.  	int _month;
59.  	int _day;
60.  };
61.  //void operator<<(ostream& out, const Date& d);
62.  ostream& operator<<(ostream& out, const Date& d);
63.  
64.  istream& operator>>(istream& in, Date& d);
65.  //Date.cpp
66.  #include"Date.h"
67.  Date::Date(int year, int month, int day)
68.  {
69.  	_year = year;
70.  	_month = month;
71.  	_day = day;
72.  	if (!CheckDate())
73.  	{
74.  		cout << "日期非法" << endl;
75.  		cout << *this;
76.  	}
77.  }
78.  void Date::Print()
79.  {
80.  	cout << _year << " " << _month << " " << _day << endl;
81.  }
82.  bool Date::operator<(const Date& d)
83.  {
84.  	if (_year < d._year)
85.  	{
86.  		return true;
87.  	}
88.  	else if (_year == d._year && _month < d._month)
89.  	{
90.  		return true;
91.  	}
92.  	else if (_year == d._year && _month == d._month && _day < d._day)
93.  	{
94.  		return true;
95.  	}
96.  	return false;
97.  }
98.  
99.  bool Date::operator>(const Date& d)
100. {
101. 	return !(*this <= d);
102. }
103. bool Date::operator<=(const Date& d)
104. {
105.    return *this < d || *this == d;//对象复用
106. }
107. bool Date::operator>=(const Date& d)
108. {
109. 	return !(*this < d);
110. }
111. bool Date::operator==(const Date& d)
112. {
113. 	return _year == d._year 
114. 		&& _month == d._month 
115. 		&& _day == d._day;
116. }
117. bool Date::operator!=(const Date& d)
118. {
119. 	return !(*this == d);
120. }
121. Date& Date::operator+=(int day)
122. {
123. 	if (day < 0)
124. 	{
125. 		return *this -= -day;
126. 	}
127. 	_day += day;
128. 	while (_day > Getmonthday(_year, _month))
129. 	{
130. 		_day -= Getmonthday(_year, _month);
131. 		++_month;
132. 		if (_month == 13)
133. 		{
134. 			_year++;
135. 			_month = 1;
136. 		}
137. 	}
138. 	return *this;
139. }
140.  
141. Date Date::operator+(int day)
142. {
143. 	Date tmp(*this);
144. 	tmp += day;
145. 	return tmp;
146. }
147. //不推荐,拷贝更多,且书写代码更多
148. //Date Date::operator+(int day)
149. //{
150. //	Date tmp(*this);
151. //	tmp._day += day;
152. //	while (tmp._day > Getmonthday(tmp._year, tmp._month))
153. //	{
154. //		tmp._day -= Getmonthday(tmp._year, tmp._month);
155. //		++tmp._month;
156. //		if (tmp._month == 13)
157. //		{
158. //			tmp._year++;
159. //			tmp._month = 1;
160. //		}
161. //	}
162. //	return tmp;
163. //}
164. //
165. //Date& Date::operator+=(int day)
166. //{
167. //	*this = *this + day;
168. //	return *this;
169. //}
170. Date& Date::operator-=(int day)
171. {
172. 	if (day < 0)
173. 	{
174. 		return *this += -day;
175. 	}
176. 	_day -= day;
177. 	while (_day <= 0)
178. 	{
179. 		--_month;
180. 		if (_month == 0)
181. 		{
182. 			_month = 12;
183. 			--_year;
184. 		}
185. 		_day += Getmonthday(_year, _month);
186. 	}
187. 	return *this;
188. }
189. Date Date::operator-(int day)
190. {
191. 	Date tmp(*this);
192. 	tmp -= day;
193. 	return tmp;
194. }
195. Date& Date::operator++()
196. {
197. 	*this += 1;
198. 	return *this;
199. }
200. Date Date::operator++(int)
201. {
202. 	Date tmp(*this);
203. 	*this += 1;
204. 	return tmp;
205. }
206. int Date::operator-(const Date& d)
207. {
208. 	Date max(*this);
209. 	Date min(d);
210. 	int flag = 1;
211. 	if (*this < d)
212. 	{
213. 		max = d;
214. 		min = *this;
215. 		flag = -1;
216. 	}
217. 	int n = 0;
218. 	while (min != max)
219. 	{
220. 		++min;
221. 		++n;
222. 	}
223. 	return n * flag;
224. }
225. //out就是cout
226. //void Date::operator<<(ostream& out)
227. //{
228. //	out << _year << "年" << _month << "月" << _day << "日" << endl;
229. //}
230. //存在访问私有的问题
231. //void operator<<(ostream& out, const Date& d)
232. //{
233. //	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
234. //}
235. ostream& operator<<(ostream& out, const Date& d)
236. {
237. 	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
238. 	return out;
239. }
240. istream& operator>>(istream& in, Date& d)
241. {
242. 	while (1)
243. 	{
244. 		cout << "请依次输入年月日:>";
245. 		in >> d._year >> d._month >> d._day;
246. 		if (d.CheckDate())
247. 		{
248. 			break;
249. 		}
250. 		else
251. 		{
252. 			cout << "日期非法,请重新输入" << endl;
253. 		}
254. 	}
255. 	return in;
256. }
257. //Test.cpp
258. #include"Date.h"
259. int main()
260. {
261. 	Date d1(2005, 2, 28);
262. 	/*d1.Print();
263. 	d1 += 7192;
264. 	d1.Print();
265. 	d1 + 100;*/
266. 	Date d2(2024, 11, 5);
267. 	cout << d2-d1 << endl;
268. 	d1.Print();
269. 	//⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个270. 参数。
271. 	//void operator<<(ostream & out);
272. 	//cout<<d1;
273. 	//成员指针隐含的是this指针指向Date,但却是cout传给this,cout是ostream类型匹配不上,要写274. 成d1<<cout,本质上是d1.operator<<(cout);
275. 	// 和其他成员函数不同,其存在争抢位置的问题
276. 	// 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参277. 位
278. 	//置,第⼀个形参位置是左侧运算对象,调⽤时就变成了 对象 << cout,不符合使⽤习惯和可读279. 性。
280. 	//重载为全局函数把ostream / istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型281. 对象。
282. 	//cout << d1;
283. 	//d1<<cout;
284. 	cout << d1;
285. 	//当然也会出现连续插入的情况,比如cout<<d1<<d2<<endl;的情况,本质是先执行d1插入cout, 
286. 返回cout,然后d2插入cout.
287. 	cout << d1 << d2 << endl;
288. 	cin >> d1;
289. 	cout <<d1 <<endl;
290. 	return 0;
291. }

6. 取地址运算符重载

6.1 const成员函数

  • 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后⾯。
  • const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const Date* const this。
1.  #include<iostream>
2.  using namespace std;
3.  class Date
4.  {
5.  public:
6.  	Date(int year, int month, int day)
7.  	{
8.  		_year = year;
9.  		_month = month;
10.  		_day = day;
11. 	}
12. 	//void Print<const Date* const this) const
13. 	//后面加个const,修饰的不是this指针,而是this指针指向的内容
14. 	//const修饰指向内容的时候,才涉及权限放大和缩小的问题
15. 	void Print() const
16. 	{
17. 		cout << _year << " " << _month << " " << _day << endl;
18. 	}
19. private:
20. 	int _year;
21. 	int _month;
22. 	int _day;
23. };
24. int main()
25. {
26. 	//&d -> Date*
27. 	Date d1(2024, 12, 30);
28. 	//&d -> const Date*
29. 	const Date d2(2024, 12, 25);
30. 	d1.Print();
31. 	return 0;
32. }

总结: 一个成员函数,不修改成员变量的建议都加上。

6.2 取地址运算符重载

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动⽣成的就可以够我们⽤了,不需要去显⽰实现。
1.  #include<iostream>
2.  using namespace std;
3.  class Date
4.  {
5.  public:
6.  	Date(int year, int month, int day)
7.  	{
8.  		_year = year;
9.  		_month = month;
10. 		_day = day;
11. 	}
12. 	
13. 	void Print() const
14. 	{
15. 		cout << _year << " " << _month << " " << _day << endl;
16. 	}
17. 	/*Date* operator&()
18. 	{
19. 		return this;
20. 	}*/
21. 	/*const Date* operator&()const
22. 	{
23. 		return this;
24. 	}*/
25. private:
26. 	int _year;
27. 	int _month;
28. 	int _day;
29. };
30. int main()
31. {
32. 	Date d1(2024, 12, 25);
33. 	//自定义类型,凡是想用运算符,都要重载运算符,但这里是默认成员函数,不用写编译器也会调34.     //用,一般情况下不需要写,编译器自动生成的一般够用
35. 	cout << &d1 << endl;
36. 	return 0;
37. }

除⾮⼀些很特殊的场景,⽐如我们不想让别⼈取到当前类对象的地址,就可以⾃⼰实现⼀份,胡乱返回⼀个地址。

1.  Date* operator&()
2.  {
3.  	//return this;
4.  	return nullptr;
5.  }
6.  const Date* operator&()const
7.  {
8.  	//return this;
9.  	return nullptr;
10. }

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/10108.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

港科夜闻 | 香港科大校董会汇聚顶尖医学专家及学者,为筹建第三间医学院提供专业意见...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大校董会汇聚顶尖医学专家及学者&#xff0c;为筹建第三间医学院提供专业意见。为支持政府及其新成立的工作组发展香港成为国际医疗培训、研究和创新枢纽&#xff0c;以及培养更多医生的愿景&#xff0c;香港科大于…

23.智能停车计费系统(基于springboot和vue的Java项目)

目录 1.系统的受众说明 2 相关概念和技术介绍 2.1 JAVA技术介绍 2.2 SpringBoot框架 2.3B/S架构 2.4 MySQL数据库 3 系统需求分析 3.1 问题定义 3.2 可行性分析 3.3系统用例分析 3.4 系统流程分析 3.4.1 登录流程 3.4.2 添加信息流程 3.4.3 删除信息流程 4…

golang安装,常用框架安装,记忆点

0.安装 虚拟机扩容 【Linux干货分享】LVM快速扩容虚拟机磁盘_哔哩哔哩_bilibili newvim 安装 sudo add-apt-repository ppa:neovim-ppa/stable sudo apt-get update sudo apt-get install -y neovim 最强Vim新手指南&#xff0c;手把手教你打造只属于自己的代码编辑器&am…

【Unity】Unity拖拽在Android设备有延迟和卡顿问题的解决

一、介绍 在制作Block类游戏时&#xff0c;其核心的逻辑就是拖拽方块放入到地图中&#xff0c;这里最先想到的就是Unity的拖拽接口IDragHandler,然后通过 IPointerDownHandler, IPointerUpHandler 这两个接口判断按下和松手&#xff0c;具体的实现逻辑就是下面 public void On…

云计算在esxi 主机上创建 4g磁盘,同时在此磁盘上部署linux

1 创建4g 磁盘 这个状态说明esxi 已经启动好 开启esxi 这个操作系统已经安装 这个在我们pc 上ping esxi 主机可以正常通信 这个是esxi 主机的界面 开始添加硬盘4g 重新登 在我们的esxi 主机上新增了8g的空间 很明显这是给我们 的磁盘空间装文件系统 这个很明显是格式化把文件…

关于圆周率-4

在这里总结一下&#xff0c;以上分析&#xff0c;都是基于“单位”的。计算圆周率的时候&#xff0c;我们考虑的是做一个单位来理解&#xff0c;而它的倍数则作为某种比例缩放来理解。同理欧拉函数&#xff0c;也是用弧长来作为单位的&#xff0c;其它弧长则是基于这个单位的比…

嘉吉携百余款产品与解决方案再度亮相进博会

第七届中国国际进口博览会&#xff08;下称“进博会”&#xff09;于11月5日至10日在上海国家会展中心举办。嘉吉连续第七年参与进博会&#xff0c;并以“新质绿动&#xff0c;共赢未来”为参展主题&#xff0c;携超过120款创新产品与解决方案&#xff0c;共赴进博之约。 今年嘉…

docker网络配置:bridge模式、host模式、container模式、none模式

docker网络模式选择 docker网络配置&#xff1a;bridge模式、host模式、container模式、none模式 - 熊仔其人 - 博客园 docker网络配置&#xff1a;bridge模式、host模式、container模式、none模式 在docker平台里有四种网络模式&#xff0c;今天继续分享一下它们的常用知识&a…

教材下载 3.2.5| 国家中小学智慧教育平台下载器,支持预览

教材下载是一款国家中小学智慧教育平台的下载器&#xff0c;绿色免安装&#xff0c;用户只需解压后双击exe文件即可使用。软件涵盖了小学、初中、高中以及特殊教育的教材内容&#xff0c;并支持选择不同的学科和版本。所有教材均支持在线预览&#xff0c;用户还可以下载PDF文件…

系统架构(01架构的特点,本质...)

目录 学习前言 一、软件架构简介 二、系统复杂性的来源与应对 三、大型网站的特点 四、大型网站架构目标 五、参考文献 学习前言 本节总结下架构相关的基础知识&#xff1a;概述&#xff0c;特点&#xff0c;目标&#xff0c;本质... 一、软件架构简介 所谓架构&#x…

飞凌嵌入式FET527N-C核心板现已适配Android 13

飞凌嵌入式FET527N-C核心板现已成功适配Android13&#xff0c;新系统的支持能够为用户提供更优质的使用体验。那么&#xff0c;运行Android13系统的FET527N-C核心板具有哪些突出的优势呢&#xff1f; 1、性能与兼容性提升 飞凌嵌入式FET527N-C核心板搭载了全志T527系列高性能处…

如何借助AI 来提高开发效率

前言 随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;特别是大规模语言模型&#xff08;如 GPT 系列&#xff09;的崛起&#xff0c;软件开发领域正在经历一场革命。AI 大模型不仅在代码生成方面展现出强大的能力&#xff0c;还在测试、维护和创新等多个环…

C++ 高效率整型大数运算项目优化——内置类型存储与计算

C 高效率整型大数运算项目优化——内置类型存储与计算 一、前言二、优化设计分析类的设计 三、设计实现加法减法乘法对于 lint&#xff1a;对于 iint&#xff1a; 左移与右移左移右移 除法基本除法借用内置类型计算第一种情况第二种情况其他情况区间定位二分计数内置类型求近似…

Qt教程(007):资源文件添加

文章目录 7.1 创建新的项目7.2 添加资源文件7.2 设置页面7.1 创建新的项目 选择创建项目类型 输入项目名称 勾选UI界面 7.2 添加资源文件 选中项目名称,右键,选择【Add New】 添加资源文件 选择Qt Resource File文件

利用轻易云高效集成旺店通与金蝶云星空销售出库单

重跑数据—分销旺店通销售出库单>金蝶销售出库单&#xff08;正常销售&刷单&#xff09;(ok) 在企业信息化管理中&#xff0c;数据的准确性和及时性至关重要。本文将分享一个实际案例&#xff0c;展示如何通过轻易云数据集成平台&#xff0c;将旺店通企业奇门的数据高效…

CAN总线协议

电气特性 高速CAN&#xff1a;电压差为0V时表示逻辑1&#xff08;隐性电平&#xff09;&#xff0c;电压差为2V时表示逻辑0&#xff08;显性电平&#xff09;&#xff0c;速率&#xff1a;125Kbps&#xff5e;1Mbps。低速CAN&#xff1a;电压差为-1.5V时表示逻辑1&#xff08;…

Nginx简易配置将内网网站ssh转发到外网

声明&#xff1a;本内容仅供交流学习使用&#xff0c;部署网站上线还需要根据有关规定申请域名以及备案。 背景 在内网的服务器有一个运行的网页&#xff0c;现使用ssh反向代理&#xff0c;将它转发到外网的服务器。 但是外网的访问ip会被ssh反向代理拦截 所以使用Nginx进行…

决策树算法

决策树算法对数据进行分类的一种算法&#xff0c;根据数据的属性进行分类&#xff0c;例如对鸢尾花进行分类&#xff0c;可以根据花瓣大小进行分类。决策树可以使用信息熵和基尼指数进行数据分类。 信息熵&#xff1a;信息熵越低&#xff0c;样本不确定性越小&#xff0c;对应…

程序员学长 | 最强总结,机器学习中处理不平衡数据集的五种方法!!

本文来源公众号“程序员学长”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;最强总结&#xff0c;机器学习中处理不平衡数据集的五种方法&#xff01;&#xff01; 今天给大家分享处理不平衡数据集的常用方法。 在开始之前&…

08 Oracle数据库故障应对与恢复策略:全面掌握RMAN恢复方法

文章目录 Oracle数据库故障应对与恢复策略&#xff1a;全面掌握RMAN恢复方法一、故障场景及恢复策略1.1 实例失败1.2 介质故障1.3 数据丢失 二、RMAN恢复方法详解2.1 全库恢复2.2 增量恢复2.3 时间点恢复 三、实践与总结 Oracle数据库故障应对与恢复策略&#xff1a;全面掌握RM…