1. 类的默认成员函数
- 第⼀:我们不写时,编译器默认⽣成的函数⾏为是什么,是否满⾜我们的需求。
- 第⼆:编译器默认⽣成的函数不满⾜我们的需求,我们需要⾃⼰实现,那么如何⾃⼰实现?
2. 构造函数
- 函数名与类名相同。
- ⽆返回值。 (返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)
- 对象实例化时系统会⾃动调⽤对应的构造函数。
- 构造函数可以重载。
- 如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦⽤⼾显式定义,编译器将不再⽣成。
- ⽆参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。⽆参构造函数和全缺省构造函数虽然构成函数重载,但是调⽤时会存在歧义。总结⼀下就是不传实参就可以调⽤的构造就叫默认构造。
- 我们不写,编译器默认⽣成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要⽤初始化列表才能解决。
- 不传实参就能调用的就叫默认构造。
- 一般情况下构造函数都需要自己写,少数情况下,默认生成就可以用,比如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. 析构函数
- 析构函数名是在类名前加上字符 ~。
- ⽆参数⽆返回值。 (这⾥跟构造类似,也不需要加void)
- ⼀个类只能有⼀个析构函数。若未显式定义,系统会⾃动⽣成默认的析构函数。
- 对象⽣命周期结束时,系统会⾃动调⽤析构函数。
- 跟构造函数类似,我们不写编译器⾃动⽣成的析构函数对内置类型成员不做处理,⾃定类型成员会调⽤他的析构函数。
- 还需要注意的是我们显⽰写析构函数,对于⾃定义类型成员也会调⽤他的析构,也就是说⾃定义类型成员⽆论什么情况都会⾃动调⽤析构函数。
- 如果类中没有申请资源时,析构函数可以不写,直接使⽤编译器⽣成的默认析构函数,如Date;如果默认⽣成的析构就可以⽤,也就不需要显⽰写析构,如MyQueue;但是有资源申请时,⼀定要⾃⼰写析构,否则会造成资源泄漏,如Stack。
- ⼀个局部域的多个对象,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. }
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. }
4. 拷贝构造函数
- 拷⻉构造函数是构造函数的⼀个重载。
- 拷⻉构造函数的第⼀个参数必须是类类型对象的引⽤,使⽤传值⽅式编译器直接报错,因为语法逻辑上会引发⽆穷递归调⽤。 拷⻉构造函数也可以多个参数,但是第⼀个参数必须是类类型对象的引⽤,后⾯的参数必须有缺省值。
- C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥⾃定义类型传值传参和传值返回都会调⽤拷⻉构造完成。
- 若未显式定义拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷⻉构造.
- 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的拷⻉构造就可以完成需要的拷⻉,所以不需要我们显⽰实现拷⻉构造。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的拷⻉构造完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃定义类型 Stack成员,编译器⾃动⽣成的拷⻉构造会调⽤Stack的拷⻉构造,也不需要我们显⽰实现MyQueue的拷⻉构造。这⾥还有⼀个⼩技巧,如果⼀个类显⽰实现了析构并释放资源,那么他就需要显⽰写拷⻉构造,否则就不需要。
- 传值返回会产⽣⼀个临时对象调⽤拷⻉构造,传引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回。
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. }
这里每次调用拷贝构造函数之前都要先传值传参,传值传参是一种拷贝,又形成一个新的拷贝构造,就形成了无穷递归,但是如果传引用返回就不会形成拷贝构造,自然也不会形成无穷递归。
C++规定:类类型传值传参必须调用拷贝构造。
所以类似于栈一样的默认生成的拷贝构造不支持我们的需要,所以我们需要自己写,再开辟一块空间。
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 赋值运算符重载
- 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const 当前类类型引⽤,否则会传值传参会有拷⻉。
- 有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值⽬的是为了⽀持连续赋值场景。
- 没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认拷⻉构造函数类似,对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的赋值重载函数。Stack需要自己实现深拷贝的operator=
- 像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. }
总结:
- 构造函数一般都需要自己写,自己传参定义初始化;
- 析构、构造时有资源申请(如malloc或者fopen)等,就需要显示写析构函数;
- 拷贝构造和赋值重载,显示写了析构,内部管理资源,就需要显示实现深拷贝。
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 取地址运算符重载
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. }