当前位置:   article > 正文

类和对象:赋值函数

类和对象:赋值函数

1.运算符重载

• 当运算符被⽤于类类型的对象时,C++语⾔允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使⽤运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错;(运算符重载转化成一个函数)
• 运算符重载是具有特别名字的函数,他的名字是由operator(关键字)和后⾯要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体;(格式:operator+运算符 构成函数名)
• 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多,⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数;(++/- -/*(解引用)/.....就是一元)
• 如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数⽐运算对象少⼀个;
• 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致;

  1. #include<iostream>
  2. using namespace std;
  3. class Date
  4. {
  5. public:
  6. Date(int year = 1, int month = 1, int day = 1)
  7. {
  8. _year = year;
  9. _month = month;
  10. _day = day;
  11. }
  12. void Print()
  13. {
  14. cout << _year << "/" << _month << "/" << _day << endl;
  15. }
  16. //Get函数
  17. int Get_year()
  18. {
  19. return _year;
  20. }
  21. int Get_month()
  22. {
  23. return _month;
  24. }
  25. int Get_day()
  26. {
  27. return _day;
  28. }
  29. //因为存在this指针,所以应当少一个
  30. bool operator==(Date d2)
  31. {
  32. return _year == d2._year
  33. && _month == d2._month
  34. && _day == d2._day;
  35. }
  36. private:
  37. int _year;
  38. int _month;
  39. int _day;
  40. //自定义类型怎么去比较?他的行为应该是我们自己去定义的,而不是系统去定义的
  41. };
  42. //比较大小的返回值是一个bool值
  43. bool operator<(Date d1, Date d2)
  44. {
  45. }
  46. //重载成全局的,方式是最正的:二元的:左操作数传给第一个参数,右操作数传给第二个参数
  47. //bool operator==(Date d1, Date d2)
  48. //{
  49. // return d1._year == d2._year
  50. // && d1._month == d2._month
  51. // && d1._day == d2._day;
  52. // //没有访问权限,1:可以直接将private的内容包含于public
  53. // //·······2:提供Get函数
  54. // //·······3:友元
  55. // //·······4:放在类里面成为成员函数
  56. //}
  57. int main()
  58. {
  59. Date x1(2024, 7, 16);
  60. Date x2(2024, 7, 16);
  61. 到底是x1对应d1,还是d2
  62. 可以显现调用(跟一个普通函数一样):
  63. //operator==(x1, x2);
  64. 还可以:
  65. //x1 == x2;
  66. //全局的和成员的都有,会优先调用成员的
  67. //改后:
  68. x1.operator==(x2);
  69. x1 == x2;
  70. return 0;
  71. }

• 不能通过连接语法中没有的符号来创建新的操作符/运算符:⽐如operator@;
•  '' .* ''      '' : : ''     '' sizeof ''      '' ?: ''     '' .''  注意以上5个运算符不能重载。(选择题⾥⾯常考,⼤家要记⼀下)重载操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: int operator+(int x, int y);
" .* ":案例:

  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6. void func()
  7. {
  8. cout << "A::func()" << endl;
  9. }
  10. };
  11. //用回调的方式去调用成员函数的指针
  12. //普通函数指针;void(*)();
  13. typedef void(A::* PF)(); //成员函数指针类型
  14. int main()
  15. {
  16. //void(A:: * PF)() = nullptr;
  17. PF pf = nullptr;
  18. //实现回调
  19. // C++规定成员函数要加&才能取到函数指针
  20. pf = &A::func;
  21. //普通的,全局的函数是可以回调的
  22. //(*pf)();//成员函数回调不了,因为有隐含的this指针
  23. //要调用函数指针,要传隐含的this指针这个实参
  24. A aa;
  25. //(*pf)(&aa);//这样也不行,因为this指针在形参和实参的位置不能显示
  26. //C++规定:回调成员函数的指针要这么回调:
  27. (aa.*pf)();//aa就悄悄传给this
  28. }

 

• ⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义,⽐如Date类重载operator-就有意
义(天数),但是重载operator+就没有意义;
日期加日期没有意义,不会构成重载;又因为没有要求运算符两边必须要同类型,而是只要求至少有一个类类型的参数:

  1. d1 + 100;//100天后日期是多少
  2. d1 - 100;//100天前日期是多少
  3. d1 - d2;//间隔天数

运算符重载:

  1. //d1+100
  2. Date operator+(int day);
  3. //d1-100
  4. Date operator-(int day);
  5. //d1-d2
  6. int operator-(const Date& d);

注:运算符重载和函数重载都用了重载,但他们没有关系;函数重载是函数名相同,参数不同;运算符重载是重新定义运算符的行为,另外,两个运算符重载函数可以构成函数重载

• 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分,
C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分;
• 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位
置,第⼀个形参位置是左侧运算对象,调⽤时就变成了 对象<<cout,不符合使⽤习惯和可读性。
重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对
象;

2.赋值运算符重载

赋值运算符重载是⼀个默认成员函数,⽤于完成两个已经存在的对象直接的拷⻉赋值,这⾥要注意跟拷⻉构造区分,拷⻉构造⽤于⼀个对象拷⻉初始化给另⼀个要创建的对象

  1. int main()
  2. {
  3. Date d1(2024, 7, 5);
  4. Date d2(2024, 7, 6);
  5. //赋值重载拷贝
  6. d1 = d2;
  7. //拷贝构造
  8. Date d3(d2);
  9. Date d4 = d2;
  10. return 0;
  11. }

 

赋值运算符重载的特点:

1. 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数(不可以是全局)。赋值运算重载的参数建议写成 const 当前类类型引⽤,否则会传值传参会有拷⻉;

  1. void operator=(const Date& d)
  2. {
  3. //尽管用传值传参,不会产生所谓的无穷递归,现在是赋值重载,传值传参,把d2传给d,调用拷贝构造,调用完回来了,接着调用赋值
  4. //operator赋值//但是建议用引用
  5. _year = d._year;
  6. _month = d._month;
  7. _day = d._day;
  8. }
  9. bool operator==(const Date& d)
  10. {
  11. //拷贝构造赋值重载拷贝去传值传参形成新的拷贝构造。。。。。无穷递归
  12. //operator等于
  13. }

因为:拷贝构造和复制重载两者本来就不一样!!! 

2. 有返回值 ,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率, 有返回值⽬的是为了⽀持连赋值场景 ;

  1. Date operator=(const Date& d)
  2. {
  3. _year = d._year;
  4. _month = d._month;
  5. _day = d._day;
  6. //d3=d1
  7. //this是d3的地址
  8. return *this;//类里面可以显示写
  9. }

优化:传值返回也会生成一个拷贝,就调用拷贝构造,白白生成一个拷贝,付出了代价:用印用来解决:提高效率:

  1. Date& operator=(const Date& d)
  2. {
  3. _year = d._year;
  4. _month = d._month;
  5. _day = d._day;
  6. return *this;
  7. }

 

3. 没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认的拷贝构造函数类似,对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的赋值重载;
4. 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的赋值运算符重载就可以完成需要的拷⻉,所以不需要我们显⽰实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的赋值运算符重载完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器⾃动⽣成的赋值运算符重载会调⽤Stack的赋值运算符重载,也不需要我们显⽰实现MyQueue的赋值运算符重载。这⾥还有⼀个⼩技巧,如果⼀个类显⽰实现了析构并释放资源,那么他就需要显⽰写赋值运算符重载,否则就不需要;

3.日期类实现

补充:两个日期间隔多少天(有算正负数)-----算法:图解:

 

另外:还可以尽可能复用逻辑(代码中会演示到):取小日期(比较大小),让小的日期不断++,真到与大日期相等:(不过相比于上图方法,会相对慢,但cpu太快了,无所谓)

代码:

头文件:Date.h

  1. #pragma once
  2. #include<iostream>
  3. using namespace std;
  4. #include<assert.h>
  5. class Date
  6. {
  7. //我和小笨蛋不认识,所以不可以去他家玩,但我很想去,因为他爸爸会数分,就给小笨蛋吹彩虹屁,就和他成为朋友了
  8. //友元声明:friend+函数声明
  9. friend ostream& operator<<(ostream& out, const Date& d);
  10. friend istream& operator>>(istream& in, Date& d);
  11. public:
  12. bool CheckDate();
  13. Date(int year = 1900, int month = 1, int day = 1);
  14. void Print()const;
  15. //频繁调用的小函数
  16. //没有做声明和定义分离, 因为定义在类里面的成员函数默认是内联inline
  17. int GetMonthDay(int year, int month)
  18. {
  19. //防止month在减的时候是从一月减到零月:
  20. assert(month > 0 && month < 13);
  21. //因为要频繁调用,所以放到静态区去
  22. static int monthDayArray[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
  23. if ((month == 2) && (year % 100 != 0 && year % 4 == 0) || (year % 400 == 0))
  24. {
  25. return 29;
  26. }
  27. return monthDayArray[month];
  28. }
  29. bool operator<(const Date& d)const;
  30. bool operator<=(const Date& d)const;
  31. bool operator>(const Date& d)const;
  32. bool operator>=(const Date& d)const;
  33. bool operator==(const Date& d)const;
  34. bool operator!=(const Date& d)const;
  35. Date operator+(int day)const;
  36. Date& operator+=(int day);
  37. Date operator-(int day)const;
  38. Date& operator-=(int day);
  39. d1++
  40. //Date operator++();
  41. ++d1
  42. //Date operator++();
  43. //这么区分这两个(d1++,++d1):重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。
  44. //C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分。
  45. //d1++:后置++
  46. Date operator++(int);
  47. //++d1:前置++
  48. Date& operator++();
  49. Date operator--(int);
  50. Date& operator--();
  51. //d1-d2
  52. int operator-(const Date& d);
  53. //printf与scanf他们直接适应的是内置类型
  54. //流插入之所以可以自定识别类型,是因为本质是函数重载
  55. //void operator<<(ostream& out);//写成成员函数是为了访问私有,但是这个实现形式倒反天罡
  56. private:
  57. int _year;
  58. int _month;
  59. int _day;
  60. };
  61. //全局
  62. //流插入
  63. ostream& operator<<(ostream& out, const Date& d);
  64. //用友元声明:让类外面的函数访问类里面的元素
  65. //可是连续输出又不行:EG:cout<<d1<<d2
  66. //流插入是从左到右(赋值相反),所以应该返回out
  67. //流提取
  68. istream& operator>>(istream& in, Date& d);//提取的值要放到Date对象中,所以不加const

源文件:Date.cpp

  1. #include"Date.h"
  2. bool Date::CheckDate()
  3. {
  4. if (_month < 1 || _month > 12
  5. || _day < 1 || _day > GetMonthDay(_year, _month))
  6. {
  7. return false;
  8. }
  9. else
  10. {
  11. return true;
  12. }
  13. }
  14. Date::Date(int year, int month, int day)
  15. {
  16. _year = year;
  17. _month = month;
  18. _day = day;
  19. if (!CheckDate())
  20. {
  21. cout << "非法日期" << endl;
  22. Print();
  23. }
  24. }
  25. void Date::Print() const
  26. {
  27. cout << _year << "-" << _month << "-" << _day << endl;
  28. }
  29. //这是+=:会改变_day
  30. Date& Date::operator+=(int day)
  31. {
  32. //有坑:当day为负数时:
  33. // 解决:
  34. if (day < 0)
  35. {
  36. return *this -= (-day);
  37. }
  38. //与加法进数一样,天满了往月上进位,月满了往年进位
  39. _day += day;
  40. while (_day > GetMonthDay(_year, _month))
  41. {
  42. _day -= GetMonthDay(_year, _month);
  43. _month++;
  44. if (_month == 13)
  45. {
  46. _year++;
  47. _month = 1;
  48. }
  49. }
  50. return*this;
  51. }
  52. //d1+100:不会改变d1
  53. Date Date::operator+(int day)const
  54. {
  55. //拷贝构造的场景
  56. Date tmp = *this;
  57. //+=就会往tmp上走
  58. /*tmp._day += day;
  59. while (tmp._day > GetMonthDay(tmp._year, tmp._month))
  60. {
  61. tmp._day -= GetMonthDay(tmp._year, tmp._month);
  62. tmp._month++;
  63. if (tmp._month == 13)
  64. {
  65. tmp._year++;
  66. tmp._month = 1;
  67. }
  68. }*/
  69. //复用:
  70. tmp += day;
  71. return tmp;//tmp为局部对象,出了作用域销毁,不能用引用返回,即使会多进行一次拷贝
  72. }
  73. Date& Date:: operator-=(int day)
  74. {
  75. if (day < 0)
  76. {
  77. return *this += (-day);
  78. }
  79. _day -= day;
  80. while (_day <= 0)
  81. {
  82. _month--;
  83. if (_month == 0)
  84. {
  85. _month = 12;
  86. }
  87. _day += GetMonthDay(_year, _month);
  88. }
  89. return *this;
  90. }
  91. Date Date:: operator-(int day)const
  92. {
  93. Date tmp = *this;
  94. /*tmp._day -= day;
  95. while (tmp._day <= 0)
  96. {
  97. tmp._month--;
  98. if (tmp._month == 0)
  99. {
  100. tmp._month = 12;
  101. }
  102. tmp._day += GetMonthDay(tmp._year, tmp._month);
  103. }*/
  104. tmp -= day;
  105. return tmp;
  106. }
  107. d1-=100
  108. 用-实现-=:d1改变
  109. //Date& Date:: operator-=(int day)
  110. //{
  111. // /*Date tmp = *this - day;
  112. // *this = tmp;*/
  113. // *this = *this - day;
  114. // return *this;
  115. //}
  116. //其实-复用-=更好:因为-的效率本身是更低的
  117. //:
  118. //Date tmp = *this;是一次拷贝,return *this 传值返回也是一次拷贝
  119. //-复用-=或-=复用-,在“-”中,都要进行两次构造,所以,真正的区别在于-=
  120. //如果-=是自己实现,全程没有拷贝;
  121. //但是-=去复用-,就会加上-带来的两次拷贝和多加的赋值拷贝,很亏
  122. //至少实现<+=或>+=
  123. bool Date:: operator<(const Date& d)const
  124. {
  125. if (_year < d._year)
  126. {
  127. return true;
  128. }
  129. else if (_year == d._year)
  130. {
  131. if (_month < d._month)
  132. {
  133. return true;
  134. }
  135. else if (_month == d._month)
  136. {
  137. return _day < d._day;
  138. }
  139. }
  140. return false;
  141. }
  142. //d1<=d2
  143. bool Date:: operator<=(const Date& d)const
  144. {
  145. return *this < d || *this == d;
  146. }
  147. bool Date:: operator>(const Date& d)const
  148. {
  149. /*if (_year > d._year)
  150. {
  151. return true;
  152. }
  153. else if (_year == d._year)
  154. {
  155. if (_month > d._month)
  156. {
  157. return true;
  158. }
  159. else if (_month == d._month)
  160. {
  161. return _day > d._day;
  162. }
  163. }
  164. return false;*/
  165. return !(*this <= d);
  166. }
  167. bool Date:: operator>=(const Date& d)const
  168. {
  169. return !(*this < d);
  170. }
  171. bool Date:: operator==(const Date& d)const
  172. {
  173. return _year == d._year && _month == d._month && _day == d._day;
  174. }
  175. bool Date:: operator!=(const Date& d)const
  176. {
  177. return !(*this == d);
  178. }
  179. //d1++
  180. //d1.operator(0);
  181. Date Date::operator++(int)
  182. {
  183. Date tmp = *this;
  184. *this += 1;
  185. return tmp;
  186. }
  187. //++d1
  188. //d1.operator();
  189. Date& Date::operator++()
  190. {
  191. *this += 1;
  192. return *this;
  193. }
  194. //所以:能用前置就用前置:引用返回:少拷贝
  195. Date Date::operator--(int)
  196. {
  197. Date tmp = *this;
  198. *this -= 1;
  199. return tmp;
  200. }
  201. Date& Date::operator--()
  202. {
  203. *this -= 1;
  204. return *this;
  205. }
  206. //天数的实现
  207. int Date:: operator-(const Date& d)
  208. {
  209. int flag = 1;
  210. int n = 0;
  211. //假设法
  212. Date max = *this;
  213. Date min = d;
  214. if (*this < d)
  215. {
  216. max = d;
  217. min = *this;
  218. flag = -1;
  219. }
  220. while (min != max)
  221. {
  222. ++min;
  223. ++n;
  224. }
  225. return n * flag;
  226. }
  227. //倒反天罡
  228. //void Date::operator<<(ostream& out)
  229. //{
  230. // //out就是cout
  231. // //对于二元函数,默认左操作数对应第一个参数
  232. // out << _year << "年" << _month << "月" << _day << "日" << endl;
  233. // //要这么配得上:d1<<out//d1.operator<<(cout)
  234. //}
  235. //因为流对象中含有指向IO缓冲区的指针,假如流对象可以复制,那么将会有两个指针同时操作缓冲区,如何释放、如何修改都会有冲突同步问题,因此流对象无法复制。
  236. ostream& operator<<(ostream& out, const Date& d)
  237. {
  238. out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
  239. return out;
  240. }
  241. istream& operator>>(istream& in, Date& d)
  242. {
  243. while (1)
  244. {
  245. cout << "请依次输入年月日:>";
  246. in >> d._year >> d._month >> d._day;
  247. //在进行流提取是,cout的buffer刷新了,be flushed了
  248. if (!d.CheckDate())
  249. {
  250. cout << "输入日期非法";
  251. d.Print();
  252. cout << "请重新输入:" << endl;
  253. }
  254. else
  255. {
  256. break;
  257. }
  258. }
  259. return in;
  260. }

 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/861911
推荐阅读
相关标签
  

闽ICP备14008679号