• 日期类完善


    目录

    日期类:

    运算符重载:

    ​编辑

     赋值重载:

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

     实现赋值重载:

    划分成员函数:

    日期类的声明和定义分离

    日期类-=:

     日期类-

     前置后置++


    日期类

    写一个简单的日期类:

    1. #pragma once;
    2. class Date
    3. {
    4. public:
    5. Date(int year=1, int month = 1, int day = 1)
    6. {
    7. _year = year;
    8. _month = month;
    9. _day = day;
    10. }
    11. void Print()
    12. {
    13. cout << _year << "/" << _month << "/" << _day << endl;
    14. }
    15. private:
    16. int _year;
    17. int _month;
    18. int _day;
    19. };
    1. #include
    2. using namespace std;
    3. #include"Date.h"
    4. void TestDate()
    5. {
    6. Date d1;
    7. Date d2(2022, 9, 18);
    8. Date d3(2022, 2, 30);
    9. Date d4(2022, 2, 49);
    10. d3.Print();
    11. d4.Print();
    12. }
    13. int main()
    14. {
    15. TestDate();
    16. return 0;
    17. }

    我们发现,日期d3和d4都不合法,我们进行运行:

    也没有提示我们输入日期错误。

     我们可以对我们的构造函数进行优化:

    1. Date(int year = 1, int month = 1, int day = 1)
    2. {
    3. _year = year;
    4. _month = month;
    5. _day = day;
    6. if (!(year >= 1 && month >= 1 && month <= 12 && day >= 1 && day <= GetMonthDay(year, month)))
    7. {
    8. cout << "非法日期" << endl;
    9. }
    10. }

    日期类的拷贝构造:

    对于日期类,我们不需要写对应的拷贝构造,因为对于内置类型,编译器会默认执行值拷贝:

    1. void TestDate()
    2. {
    3. Date d1;
    4. Date d2(2022, 9, 18);
    5. /*Date d3(2022, 2, 30);
    6. Date d4(2022, 2, 49);
    7. d3.Print();
    8. d4.Print();*/
    9. Date d5(d2);
    10. d5.Print();
    11. }
    12. int main()
    13. {
    14. TestDate();
    15. return 0;
    16. }

    运算符重载

     赋值重载

    1. void TestDate()
    2. {
    3. Date d1;
    4. Date d2(2022, 9, 18);
    5. /*Date d3(2022, 2, 30);
    6. Date d4(2022, 2, 49);
    7. d3.Print();
    8. d4.Print();*/
    9. /*Date d5(d2);
    10. d5.Print();*/
    11. Date d5;
    12. d5 = d2;
    13. }

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

    拷贝构造:一个对象拷贝初始化另一个要创建的对象。

    赋值重载:已经存在的两个对象之间的拷贝。 

     实现赋值重载:

    赋值重载可以传值传参吗?

    可以,不会发生无穷递归,发生无穷递归的条件是: 拷贝构造时,使用传值传参,传值传参本身就是拷贝构造,拷贝构造又需要传值传参,无限循环。

    所以这里可以传值传参。

    但是传值传参还需要调用拷贝构造,有些麻烦,我们最好还是使用传地址传参。

    1. void operator=(const Date&d)
    2. {
    3. _year = d._year;
    4. _month = d._month;
    5. _day = d._day;
    6. }
    1. void TestDate()
    2. {
    3. Date d1(2022, 9, 10);
    4. Date d2;
    5. d2 = d1;
    6. }
    7. int main()
    8. {
    9. TestDate();
    10. return 0;
    11. }

    我们进行测试:

    赋值成功。

    赋值重载写的不够全面,我们知道整型可以连等:

    1. int main()
    2. {
    3. /*TestDate();*/
    4. int i, j;
    5. i = j = 1;
    6. return 0;
    7. }

    我们这里是不能连等的:

    1. void TestDate()
    2. {
    3. Date d1(2022, 9, 10);
    4. Date d2, d3;
    5. d2.Print();
    6. d3=d2 = d1;
    7. d2.Print();
    8. }
    9. int main()
    10. {
    11. TestDate();
    12. /*int i, j;
    13. i = j = 1;*/
    14. return 0;
    15. }

    原因如下:

     

     

    这里我们把d1赋值给d2之后,返回值应该是d2,但是我们实现的函数是没有返回值的,所以我们需要完善返回值:

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

    传值传参是拷贝构造,传值返回也是拷贝构造,所以我们这里可以用传引用返回:

    而且this指向的对象出了作用域依旧存在,我们可以用引用返回。

    能不能不返回*this,返回d呢?

    答:不可以

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

    1:因为d是只读的,传引用返回后的类型变成可读可写的了,属于权限的放大。

    2:赋值的返回值是左操作数,不能返回右操作数。

    划分成员函数:

    赋值运算符的重载对于内置类型完成值拷贝,所以对于日期类,我们可以不定义赋值重载

     

    我们没有定义赋值重载函数,依旧可以完成日期类对象的赋值。

    大部分类的默认赋值重载都不需要我们自定义,但是有些需要,例如栈:

    1. #pragma once;
    2. class Stack
    3. {
    4. public:
    5. Stack(int capacity=4 )
    6. {
    7. _a = (int*)malloc(sizeof(int)*capacity);
    8. if (_a == nullptr)
    9. {
    10. perror("malloc fail");
    11. exit(-1);
    12. }
    13. _top = 0;
    14. _capacity = capacity;
    15. cout << "Stack构造函数()" << endl;
    16. }
    17. Stack(const Stack&st)
    18. {
    19. _a=(int*)malloc(sizeof(int)*st._capacity);
    20. if (_a == nullptr)
    21. {
    22. perror("malloc fail");
    23. exit(-1);
    24. }
    25. memcpy(_a, st._a, sizeof(int)*st._top);
    26. _top = st._top;
    27. _capacity = st._capacity;
    28. }
    29. void Push(int x)
    30. {
    31. _a[_top++] = x;
    32. }
    33. ~Stack()
    34. {
    35. free(_a);
    36. _a = nullptr;
    37. _top = _capacity = 0;
    38. cout << "Stack析构函数()" << endl;
    39. }
    40. private:
    41. int*_a;
    42. int _capacity;
    43. int _top;
    44. };

    我们没有实现赋值重载,看看能否使用赋值重载

    1. void StackTest()
    2. {
    3. Stack st1;
    4. st1.Push(1);
    5. st1.Push(2);
    6. Stack st2;
    7. st2.Push(10);
    8. st2.Push(20);
    9. st2.Push(30);
    10. st2.Push(40);
    11. st1 = st2;
    12. }
    13. int main()
    14. {
    15. StackTest();
    16. /*int i, j;
    17. i = j = 1;*/
    18. return 0;
    19. }

    运行之后,直接报错。

     

    我们进行值拷贝:

    把st2._top赋值给st1._top,把st2._capacity赋值给st1._capacity。

    再让st1._a指向st2._a的位置。

    后创建st2的先析构,析构之后st2所指向的空间的数据释放了,这时候st1再进行析构就导致了越界访问。

    并且原本属于st1的指向的数据的地址也找不到了,造成了内存泄漏。

    所以对于栈类,我们要自己实现赋值重载:

     

    我们先释放掉st1所指向的空间的数据,然后新创建一个空间,拷贝st2的数据,让st1指向该空间:

     

    1. Stack& operator=(const Stack&st)
    2. {
    3. free(_a);
    4. _a = (int*)malloc(sizeof(int)*st._capacity);
    5. if (_a == nullptr)
    6. {
    7. perror("malloc fail");
    8. exit(-1);
    9. }
    10. memcpy(_a, st._a, sizeof(int)*st._top);
    11. _top = st._top;
    12. _capacity = st._capacity;
    13. return *this;
    14. }

     

    完成了深拷贝。 

    还有一种情况:

    用自己去赋值自己。

     

    我们进行运行,发现数据出现了错误。

     因为

     我们可以先提前判断:

    1. Stack& operator=(const Stack&st)
    2. {
    3. if (&st != this)
    4. {
    5. free(_a);
    6. _a = (int*)malloc(sizeof(int)*st._capacity);
    7. if (_a == nullptr)
    8. {
    9. perror("malloc fail");
    10. exit(-1);
    11. }
    12. memcpy(_a, st._a, sizeof(int)*st._top);
    13. _top = st._top;
    14. _capacity = st._capacity;
    15. }
    16. return *this;
    17. }

    对于一些有资源释放的类,我们也可以不自己写:

    1. class MyQueue
    2. {
    3. public:
    4. void push(int x)
    5. {
    6. _pushST.Push(x);
    7. }
    8. private:
    9. Stack _pushST;
    10. Stack _popST;
    11. size_t size = 0;
    12. };

    对于内置类型完成值拷贝,对于自定义类型,调用其默认赋值重载。

    1. void MyQueueTest()
    2. {
    3. MyQueue s1;
    4. s1.push(10);
    5. MyQueue s2;
    6. s2 = s1;
    7. }
    8. int main()
    9. {
    10. MyQueueTest();
    11. /*int i, j;
    12. i = j = 1;*/
    13. return 0;
    14. }

    完成了深拷贝。 

    日期类的声明和定义分离

    对于比较小并且频繁调用的函数,这类函数一般是内联函数,内联函数只能在类里面定义,不能声明和定义分离。

    对于行数不多并且调用不频繁的我们可以声明和定义分离。

    日期类-=:

    1. Date&Date::operator-=(int day)
    2. {
    3. _day -= day;
    4. while (_day <= 0)
    5. {
    6. _month--;
    7. if (_month == 0)
    8. {
    9. _year--;
    10. }
    11. _day += GetMonthDay(_year, _month);
    12. }
    13. return *this;
    14. }
    1. #include"Date.h"
    2. void TestDate1()
    3. {
    4. Date d1(2022, 10, 19);
    5. d1 -= 1000;
    6. d1.Print();
    7. }
    8. int main()
    9. {
    10. TestDate1();
    11. return 0;
    12. }

     日期类-

    1. Date Date::operator-(int day)
    2. {
    3. Date ret(*this);
    4. ret -=day;
    5. return ret;
    6. }
    1. #include"Date.h"
    2. void TestDate1()
    3. {
    4. Date d1(2022, 10, 19);
    5. Date d2 = d1 - 10000;
    6. d2.Print();
    7. }
    8. int main()
    9. {
    10. TestDate1();
    11. return 0;
    12. }

    假如我们要减去一个负数呢?

    1. #include"Date.h"
    2. void TestDate1()
    3. {
    4. Date d1(2022, 10, 19);
    5. d1 -= -10000;
    6. d1.Print();
    7. }
    8. int main()
    9. {
    10. TestDate1();
    11. return 0;
    12. }

     前置后置++

     

    我们在定义的时候如何区分前置和后置++?

     

    1. Date& Date::operator++()
    2. {
    3. *this += 1;
    4. return *this;
    5. }
    6. Date Date::operator++(int)
    7. {
    8. Date ret = *this;
    9. *this + 1;
    10. return ret;
    11. }

     

     

  • 相关阅读:
    nlp之文本转向量
    pollFirst(),pollLast(),peekFirst(),peekLast()
    【软考软件评测师】第二十章 计算机组成与体系结构(CPU指令系统)
    vue3中使用element-plus Notification通知组件内容添加点击自定义事件
    k8s 读书笔记 - kubernetes 是什么以及我们为什么要使用它?
    TDA4VM/VH 单核软复位原理与实现实现
    【uniapp】小程序开发4:微信小程序支付、h5跳转小程序
    windows工具:推荐一款可以截长图(滚动截图)的工具FSCapture
    小程序游戏、App游戏与H5游戏:三种不同的游戏开发与体验方式
    V3s 屏幕LCD驱动总结
  • 原文地址:https://blog.csdn.net/qq_66581313/article/details/132848014