• [C++基础]-类和对象(中~万字详解)


    前言

    作者小蜗牛向前冲

    名言我可以接受失败,但我不能接受放弃

      如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。

    目录

    一 类的默认的6个成员函数 

    1.1 类的6个默认成员函数

    1 .2 构造函数

    1.2.1 构造函数的由来

    1.2.2 构造函数的特性

    1.3 析构函数

    1.3.1 概念

    1.3.2 特征 

    1.4 拷贝构造函数

    1.4.1 语法知识

    ​编辑1.4.2  拷贝构造函数的参数

    1.4.3 浅拷贝

    二  赋值运算符重载

    2.1 运算符重载

    2.1.1 运算符重载的引入

    2.1.2 算数符重载的特性

    2.1.3 算数符重载

    2.2 赋值运算符重载

    2.2.1 基本知识

    2.2.2 语法特性

    三 日期类的实现

    四 取地址及const取地址操作符重载

    五 总结


    话不多说,这里干货满满,请各位客官尽情欣赏! 

    一 类的默认的6个成员函数 

    1.1 类的6个默认成员函数

    对于一个类来说,如果我们什么都没写,我们称为空类。但是类中真的什么也没有做吗?

    其实不然,在任何类在什么都不写的情况下,编译器会生成6给默认的成员函数。

    这6个成员函数到底是做什么的呢?

    简单的可以分为三类:

    初始化和清理:构造函数主要完成初始化工作,析构函数主要完成清理工作。

    拷贝复制:拷贝构造函数是使用同类对象初始化创建对象,赋值重载主要是把一个对象赋值给另外一个对象。

    取地址重载:主要是对普通对象和const对象取地址。

    1. class Date//空类
    2. {
    3. };

    1 .2 构造函数

    1.2.1 构造函数的由来

    上面我们说了构造函数是用来初始化的,那他到底是初始化什么呢?

    其实初始是让类的每个数据成员都有 一个合适的初始值。

    我们以日期类来比较:

    1. class Date
    2. {
    3. //对类初始化
    4. init(int year, int month, int day)
    5. {
    6. _year = year;
    7. _month = month;
    8. _day = day;
    9. }
    10. private:
    11. int _year;
    12. int _month;
    13. int _day;
    14. };

    我们可以写个类的成员函数来初始化日期,但是我们每次要初始化日期都要调用该函数,有没有什么方式,让我们在类中就能够直接初始化日期呢?

    所以构造函数就来了,构造函数是一个特殊的成员函数名字和类名相同,创建类对象的时候会由编译器自己调用,用来保证每个成员函数都有一个合适的初始值,并且在每个对象的生命周期内调用一次

    1.2.2 构造函数的特性

    构造函数是特殊的成员函数,虽然叫构造函数,但是他并不是开辟空间去创建对象,而是只负责初始化对象。

    特征:

    1 函数名和类名相同

    2 无返回值

    3 对象实例化时,编译器自动调用构造函数

    4 构造函数可以重载 

    1. class Date
    2. {
    3. public:
    4. //1.无参构造函数
    5. Date()
    6. {}
    7. //2.有参数的构造函数
    8. Date(int year, int month, int day)
    9. {
    10. _year = year;
    11. _month = month;
    12. _day = day;
    13. }
    14. private:
    15. int _year;
    16. int _month;
    17. int _day;
    18. };
    19. void TestDate()
    20. {
    21. Date d1;//无参数调用
    22. Date d2(2022, 10, 14);//有参数调用
    23. }

    这里我们要注意当我们无参调用的的时候,如果后面跟了括号就变成了函数的声明。 

    上面我们是自己写了构造函数,但是其实有时候我们可以不用自己写让编译器直接生成,我们可以屏蔽date构造函数,观察一下对象是否会被初始化。

     这里我们发现,对象没有被初始化,这是因为,编译器默认只对自定义类型进行初始化处理,而对内置类型不处理。

    那内置类型怎么办呢?

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

    1. class Time
    2. {
    3. public:
    4. Time()
    5. {
    6. cout << "Time()" << endl;
    7. _hour = 0;
    8. _minute = 0;
    9. _second = 0;
    10. }
    11. private:
    12. int _hour;
    13. int _minute;
    14. int _second;
    15. };

    对于这个时间类来是为初始化内置类型,我们可以在构造函数中直接对内置类型给默认值。
    无参的构造函数全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个
    注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
    是默认构造函数。

    1.3 析构函数

    1.3.1 概念

    通过前面说的构造函数,我们知道了一个对象是怎么来的,但是一个对象又是怎么消失的呢?

    这就不的不提我们的析构函数:和构造函数的功能相反,析构函数不是完成对对象本体的销毁,局部对象的销毁是由编译器来做的。而对象在销毁的时候会自动调用析构函数,完成对象中资源的清理工作。

    1.3.2 特征 

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

    1 析构函数的函数名是类名前面加~

    2 无参数无返回类型 

    3 一个类只能有一个析构函数,如果没有系统会自动生成,而且析构函数是不可以重载的

    4 对象的生命周期结束后,编译器会自动调用析构函数

    1. typedef int DataType;
    2. class Stack
    3. {
    4. public:
    5. Stack(size_t capacity = 4)
    6. {
    7. _array = (DataType*)malloc(sizeof(DataType) * capacity);
    8. if (_array == NULL)
    9. {
    10. perror("malloc fail");
    11. exit(-1);
    12. }
    13. _capacity = capacity;
    14. _size = 0;
    15. }
    16. //入栈
    17. void Push(DataType data)
    18. {
    19. //这里应该还要写应该检测容量是否满
    20. _array[_size] = data;
    21. ++_size;
    22. }
    23. //栈销毁析构函数
    24. ~Stack()
    25. {
    26. if (_array)
    27. {
    28. free(_array);
    29. _array = NULL;
    30. _capacity = 0;
    31. _size = 0;
    32. }
    33. }
    34. private:
    35. DataType* _array;
    36. int _capacity;
    37. int _size;
    38. };
    39. void TestStack()
    40. {
    41. Stack s;
    42. s.Push(1);
    43. s.Push(2);
    44. }

    这里我们写一个栈来测试一下析构函数到底是在什么时候去清理对象的,

     这里我们可以看出,出main函数的一瞬间就调用了析构函数,将对对象进行了清理。

    我们来思考一个问题,如果定义了一个类Date里面都是内置类型的成员变量类Time这里面定义的是自定义类型,但我们在mian函数的时候只对Date建立对象,那其Time在调用结束后也会调用析构函数吗?

    这里是会的,那么是怎么做到都呢?

    虽然我对Date建立对象并进行调用,但是因为该类的成员变量是内置类型,所以编译器不会处理(系统直接将其内存回收),而Time成员变量是自定义类型,需要调用析构函数处理,main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,所以当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁,main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数。即:创建哪个类的对象则调用该类的构造函数,销毁那个类的对象则调用该类的析构函数。
    注意:

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

    2 对于析构函数和构造函数在什么情况要自己写,这个我们要因为情况而定(要面向需求),但是要写析构函数的类,那么他一定是要写构造函数的。

    1.4 拷贝构造函数

    1.4.1 语法知识

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

    特征:

    1 拷贝构造函数是构造函数的一种重载形式 

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

    1. class Date
    2. {
    3. public:
    4. //构造函数初始化
    5. Date(int year = 1949, int month = 9, int day = 1)
    6. {
    7. _year = year;
    8. _month = month;
    9. _day = day;
    10. }
    11. //拷贝构造
    12. Date(const Date& d)
    13. {
    14. _year = d._year;
    15. _month = d._month;
    16. _day = d._day;
    17. }
    18. private:
    19. int _year;
    20. int _month;
    21. int _day;
    22. };

    1.4.2  拷贝构造函数的参数

    我们在上面演示了用引用做参数的拷贝构造,如果我们传值会怎么样呢?

    这时就会发生会引无穷的递归调用

    原因: 当我们将d1拷贝给对象d2时,编译器会调用拷贝构造函数,但是在调用拷贝构造函数之前,又要完成参数d对d1的临时拷贝(形参是实参的一份临时拷贝),d拷贝d1这里又要调用临时拷贝从而引发无穷递归。

    而我们用引用做参数就不会出现这种状况,引用是实参的别名,不需要拷贝实参,也就不会存在无穷递归的问题,但是大家可能会问为什么我们要给参数加上const,其实是为了避免在函数的内部发生拷贝出错。

    这里简单归纳一下const修饰指针的用法:

    1 const 在*号的右边

    int* const p1;

    这情况下,指针是只读的,也就是 p3 本身的值不能被修改

    2 const在*号的左边

    1. const int* p2;
    2. int const* p3;

    这二种情况都是指针不可以读,但是指针的内容可以被修改。

     这里我们只要记住拷贝构造的参数是引用传参

    1.4.3 浅拷贝

    默认拷贝构造的拷贝规则如下:默认的拷贝构造函数对内置类型以字节为单位直接进行拷贝 – 浅拷贝,对自定义类型调用其自身的拷贝构造函数

    下面我们用栈的拷贝去理解:

    1. class Stack
    2. {
    3. public:
    4. Stack(int capacity = 4)
    5. {
    6. _a = (int*)malloc(sizeof(int) * capacity);
    7. if (_a == nullptr)
    8. {
    9. perror("malloc fail\n");
    10. exit(-1);
    11. }
    12. _top = 0;
    13. _capacity = capacity;
    14. cout << "Stack 构造" << endl;
    15. }
    16. ~Stack()
    17. {
    18. free(_a);
    19. _a = NULL;
    20. _top = _capacity = 0;
    21. }
    22. void Push(int date)
    23. {
    24. _a[_top++] = date;
    25. }
    26. private:
    27. int* _a;
    28. int _top;
    29. int _capacity;
    30. };

     这里我们可以看到,并没有自己写拷贝构造函数,但是当我们将s1拷贝到s2中时候,编译器会自己生成一个拷贝构造函数,按照字节的大小将s1对象中的成员变量拷贝到s2中。

     但是当我们继续调试的时候程序会崩溃:

     这里是为什么呢?我们可以细细分析s1拷贝过程:编译器按照字节将s1中的内联拷贝到s2中,但是成员变量-a指向的是同一块空间(也就说公用了同一块动态空间),当是当我们main函数调用完成后就会消耗s2和s1,而且调用析构函数销毁的顺序是先s2在s1,当main函数调用析构函数销毁了s2,去调用s1的析构函数时,会导致_a指向的空间被二次释放,从而让程序崩溃,这里我们在对对象s2插入3的时候也会影响s1,因为二者是公用一块空间。

     正确的拷贝方式:为s2_a单独开辟一块空间,并将s1中_a的内容拷贝到该空间,其余内置成员变量按字节进行拷贝。

    这里我们只要自己在写应该拷贝构造函数就可以了(深拷贝)

    1. Stack(const Stack& st) //拷贝构造
    2. {
    3. _a = (int*)malloc(sizeof(int) * st._capacity);
    4. if (_a == nullptr)
    5. {
    6. perror("malloc fail\n");
    7. exit(-1);
    8. }
    9. memcpy(_a, st._a, sizeof(int) * st._capacity);
    10. _top = st._top;
    11. _capacity = st._capacity;
    12. }

    在了解Stack的拷贝构造函数,在去看一下Date和 MyQueue的拷贝构造函数.

    对于Date类来说,由于他的成员变量都是内置类型,所以用编译器生成的拷贝构造函数也是没问题的

     而MyQueue中他的成员变量都是自定义类型,所以默认成员函数回去调用其自身的拷贝构造,即 Stack 的拷贝构造,而 Stack 的拷贝构造虽然需要深拷贝,但我们已经显式定义,所以也不需要我们提供拷贝构造:

     总结:

    如果类中没有资源的申请,我们就不用自己写拷贝构造函数,用编译器自己生成的即可,果然有资源的申请就要自己写拷贝构造,否则就可以出现浅拷贝及一块空间被多次释放的情况。

    由于析构函数和拷贝构造函数在资源管理方面非常相似,所以我们可以认为要写析构函数的地方,就要写拷贝构造函数。

    拷贝构造的经典使用场景:

    • 使用已存在对象创建新对象;
    • 函数参数类型为类类型对象;
    • 函数返回值类型为类类型对象;

    二  赋值运算符重载

    2.1 运算符重载

    2.1.1 运算符重载的引入

    对于编译器来说,他能够对内置类型经行"+"或者"-"的操作符经行运算,但是他不能对自定义类型进行计算,以日期类为例子:"日期”+“日期”或者“日期”-“日期",如果我们要进行这些操作就只能去对于函数AddDate或者SubDate,但是这样可以读行就会比较差(特殊是我们函数定义名字起来的不号的时候),这些函数的可读性始终是没有 + - > < 这些符号的可读性高的。

    所以C++为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
    函数名字为:关键字operator后面接需要重载的运算符符号。
    函数原型:返回值类型 operator操作符(参数列表

    下面我们以日期类举例:日期+日期

    1. class Date
    2. {
    3. public:
    4. // 获取某年某月的天数
    5. int GetMonthDay(int year, int month)
    6. {
    7. static int MonthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    8. //判断是否为闰年
    9. if (year % 4 == 0 && year % 100 != 0 && month == 2 || year % 400 == 0 && month == 2)
    10. {
    11. return 29;
    12. }
    13. else
    14. {
    15. return MonthDayArray[month];
    16. }
    17. }
    18. //构造函数
    19. Date(int year = 1949, int month = 10, int day = 1)
    20. {
    21. _year = year;
    22. _month = month;
    23. _day = day;
    24. //检测日期是否符合规则
    25. if (!(year >= 1 &&
    26. month >= 1 && month <= 12 &&
    27. day >= 1 && day <= GetMonthDay(year, month)))
    28. {
    29. cout << "日期不符合规则" << endl;
    30. }
    31. }
    32. //打印日期
    33. void print()
    34. {
    35. cout << _year << "-" << _month << "-" << _day << endl;
    36. }
    37. private:
    38. int _year;
    39. int _month;
    40. int _day;
    41. };

    函数实现日期+日期

    1. int AddDate(Date& d, int day)
    2. {
    3. d._day += day;
    4. while (d._day > GetMonthDay(d._year, d._month))
    5. {
    6. //天数大于该月最大天数就月++
    7. d._day -= GetMonthDay(d._year, d._month);
    8. d._month++;
    9. if (d._month > 12)//保证月份在12个月内
    10. {
    11. d._month -= 12;
    12. d._year++;
    13. }
    14. }

    运算符重载的方式实现:

    1. //运算重载+=
    2. void operator+=(int day)//这里隐藏了this指针参数
    3. {
    4. this->_day += day;
    5. while (_day > GetMonthDay(this->_year, this->_month))
    6. {
    7. _day -= GetMonthDay(this->_year, this->_month);
    8. _month++;
    9. if (_month > 12)
    10. {
    11. _month -= 12;
    12. _year++;
    13. }
    14. }
    15. }

    这里不然看出运算符重载和函数的实现区别不大,但是运算重载却显的非常的直观。

    注意:

    1 对于运算符重载相比于函数的参数少了Date& d(第1个对象的日期),这里的this指针就相当于第一个操作数。

    2 对于在类外部无法访问类的私有成员变量的问题其实也可以使用友元解决

    2.1.2 算数符重载的特性

    算数符重载的特性:

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

    我们不能创建没有的运算符,这样编译器是无法识别的。

     在重载操作符是必须要有一个类类型参数,因为我们重载定义的是自定义类型,而不是内置类型

    2.1.3 算数符重载

    对于算数符重载的类型有很多如: operator==,perator>=,perator>,perator<,perator!=,perator+=,perator+,perator-=,perator-,perator++。对于这些算法符重载我们要注意perator++,对于++自定义类型的重载我们可以分为前置++和后置++,他们都是perator++,但是C++规定对于后置++成员函数的实现的时候,我们要加是一个int的参数,而我们在调用后置++的时候是传任意一个整形都是可以的

    下面我以日期类为例子:

    前置++的实现,不需要传参

    后置++的实现,我们只要传一个整形的参数

     虽然说什么要实现运算重载很多,但是我们只要实现了 operator==和 operator>这二种重载其他大大重载就变的很简单了。

    算术==的重载, operator==:

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

     算术>的重载, operator>:

    1. // d1 > d2
    2. bool Date::operator>(const Date& d)const
    3. {
    4. if (_year > d._year)
    5. {
    6. return true;
    7. }
    8. else if (_year == d._year && _month > d._month)
    9. {
    10. return true;
    11. }
    12. else if(_year == d._year && _month == d._month && _day > d._day)
    13. {
    14. return true;
    15. }
    16. return false;
    17. }

    那么我们实现算术>=的重载就变的非常简单了,只要结合和前面的>和==重载就可以很容易写出>=的重载:

    1. //d1 >= d2
    2. bool Date::operator>=(const Date& d)const
    3. {
    4. return *this > d || *this == d;//这里是用了重载复用,而不是简单的算术比较
    5. }

    后面的重载我就不为大家穷举了,大家可以通过算术符的复用很快的到结果了。

    大家可能注意到了我在函数成员的后面加上了const这是什么意思呢?为什么要加了呢?在下面会为大家解释。

    2.2 赋值运算符重载

    2.2.1 基本知识

    对于赋值重载运算符就是将赋值运算符(=)进行重载,这是针对自定义类型的,如果用户没有实现那么编译器会自己生成一个,以值的方式进行逐字节拷贝

    赋值运算符重载格式(以日期类为例)

    参数类型:const Date&,传递引用可以提高传参效率

    返回值类型:date&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

    检测是否自己给自己赋值,也就是说自己给自己赋值是不被允许的。

    返回*this :要复合连续赋值的含义

    这里实现日期类的赋值重载:

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

    2.2.2 语法特性

    拷贝构造和赋值重载有什么区别吗

    其实拷贝构造函数完成的是初始化工作,在创建对象时自动调用,是不需要有返回值的。

    赋值重载完成的是已存在的对象之间的拷贝需要手动调用,是有返回值的。

    赋值重载函数可以给全局变量赋值吗?

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

    这个代码的执行是会报错

    1. // 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
    2. Date& operator=(Date& left, const Date& right)
    3. {
    4. if (&left != &right)
    5. {
    6. left._year = right._year;
    7. left._month = right._month;
    8. left._day = right._day;
    9. }
    10. return left;
    11. }
    12. // 编译失败:
    13. // error C2801: “operator =”必须是非静态成员

    三 日期类的实现

    这里我们要完整的实现一下日期类:

    Ddae.h 

    这里定义了日期类的函数功能:

    内联函数

    有些函数的实现我们是直接在Date.h这个定义文件中实现的,这是因为这些函数体不长,而且要被频繁调用所以最好定义为内联函数(提供代码的效率)

    const 成员

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

    至于为什么要用const修饰this指针?

    主要是防止下面无意写出将权限放大导致编译不通过的情况,如果用const修饰就能有很好的适应性,而且凡是内部不改变成员变量,其实也就是*this对象数据的,这些成员函数都应该加const

    友元定义:

     在重载输入和输出操纵符的时候,发现如果将重载定义成成员函数,那么代码的很习惯和和可读性多会哼差,那么我们只能定义为全局的函数,但这条因为在函数中我们要使用私有成员变量(在类外是不能不访问的),这时我们可以在类中定义个友元(frend)就可以在类外使用了。

    1. #pragma once
    2. #include
    3. #include
    4. using namespace std;
    5. //日期类
    6. class Date
    7. {
    8. // 友元声明(类的任意位置)
    9. friend ostream& operator<<(ostream& out, const Date& d);
    10. friend istream& operator>>(istream& in, Date& d);
    11. public:
    12. // 获取某年某月的天数
    13. int GetMonthDay(int year, int month)
    14. {
    15. static int MonthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    16. //判断是否为闰年
    17. if (year % 4 == 0 && year % 100 != 0 && month == 2 || year % 400 == 0 && month == 2)
    18. {
    19. return 29;
    20. }
    21. else
    22. {
    23. return MonthDayArray[month];
    24. }
    25. }
    26. //构造函数
    27. Date(int year = 1, int month = 1,int day = 1)
    28. {
    29. _year = year;
    30. _month = month;
    31. _day = day;
    32. //检测日期是否符合规则
    33. if (!(year >= 1 &&
    34. month >= 1 && month <= 12 &&
    35. day >= 1 && day <= GetMonthDay(year, month)))
    36. {
    37. cout << "日期不符合规则" << endl;
    38. }
    39. }
    40. //打印日期
    41. void print()
    42. {
    43. cout << _year << "-" << _month << "-" << _day << endl;
    44. }
    45. //d1 == d2
    46. bool operator==(const Date& d)const;
    47. // d1 > d2
    48. bool operator>(const Date& d)const;
    49. // d1 >= d2
    50. bool operator>=(const Date& d)const;
    51. //d1 <= d2
    52. bool operator<=(const Date& d)const;
    53. //d1 < d2
    54. bool operator<(const Date& d)const;
    55. //d1 != d2
    56. bool operator!=(const Date& d)const;
    57. // d1 += 100
    58. Date& operator+=(int day);
    59. // d1 + 100
    60. Date operator+(int day);
    61. // d1 -= 100
    62. Date& operator-=(int day);
    63. // d1 - 100
    64. Date operator-(int day);
    65. // 前置
    66. Date& operator++();
    67. // 后置
    68. Date operator++(int);
    69. // d1 - d2
    70. int operator-(const Date& d) const;
    71. //这种声明不符合阅读习惯,所以输入和输出的重载我们一般不定义为成员函数
    72. // d1.operator<<(cout); // d1 << cout
    73. /* void operator<<(ostream& out)
    74. {
    75. out << _year << "年" << _month << "月" << _day << "日" << endl;
    76. }*/
    77. private:
    78. int _year;
    79. int _month;
    80. int _day;
    81. };
    82. //这里要用到友元声明
    83. inline ostream& operator<<(ostream& out, const Date& d)
    84. {
    85. out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    86. return out;
    87. }
    88. // cin >> d1 operator(cin, d1)
    89. inline istream& operator>>(istream& in, Date& d)
    90. {
    91. in >> d._year >> d._month >> d._day;
    92. return in;
    93. }

    Date.cpp

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include"Date.h"
    3. //d1==d2
    4. bool Date::operator==(const Date& d)const
    5. {
    6. return _year == d._year &&
    7. _month == d._month &&
    8. _day == d._day;
    9. }
    10. // d1 > d2
    11. bool Date::operator>(const Date& d)const
    12. {
    13. if (_year > d._year)
    14. {
    15. return true;
    16. }
    17. else if (_year == d._year && _month > d._month)
    18. {
    19. return true;
    20. }
    21. else if(_year == d._year && _month == d._month && _day > d._day)
    22. {
    23. return true;
    24. }
    25. return false;
    26. }
    27. //d1 >= d2
    28. bool Date::operator>=(const Date& d)const
    29. {
    30. return *this > d || *this == d;//这里是用了重载复用,而不是简单的算术比较
    31. }
    32. //d1 <= d2
    33. bool Date::operator<=(const Date& d)const
    34. {
    35. return !(*this > d);
    36. }
    37. //d1 < d2
    38. bool Date::operator<(const Date& d)const
    39. {
    40. return !(*this >= d);
    41. }
    42. //d1 != d2
    43. bool Date::operator!=(const Date& d)const
    44. {
    45. return !(*this == d);
    46. }
    47. // d1 += 100
    48. Date& Date::operator+=(int day)
    49. {
    50. _day += day;
    51. while (_day > GetMonthDay(this->_year, this->_month))
    52. {
    53. _day -= GetMonthDay(this->_year, this->_month);
    54. _month++;
    55. if (_month > 12)
    56. {
    57. _year++;
    58. _month -= 12;
    59. }
    60. }
    61. return *this;
    62. }
    63. // d1 + 100
    64. Date Date::operator+(int day)
    65. {
    66. Date tmp(*this);//这里拷贝一份d1的日期
    67. tmp += 100;//重载+=运算
    68. return tmp;
    69. }
    70. // d1 -= 100
    71. Date& Date::operator-=(int day)
    72. {
    73. if (day < 0)//传的天数为负数相当于+=
    74. {
    75. //return *this -= -day;
    76. return *this += abs(day);
    77. }
    78. _day -= day;
    79. while (_day <0)
    80. {
    81. _month--;
    82. _day += GetMonthDay(this->_year, this->_month);//下个月,天应该增加
    83. if (_month == 1)
    84. {
    85. _year--;
    86. _month = 12;
    87. }
    88. }
    89. return *this;
    90. }
    91. // d1 - 100
    92. Date Date::operator-(int day)
    93. {
    94. Date tmp(*this);
    95. tmp -= day;
    96. return tmp;
    97. }
    98. // 前置++
    99. Date& Date::operator++()
    100. {
    101. *this += 1;
    102. return *this;
    103. }
    104. //对于如何取分后置和前置,C++规定后置会带一个参数
    105. // 这里构成函数重载
    106. // 后置++
    107. Date Date::operator++(int)
    108. {
    109. Date tmp(*this);//拷贝保存好*this++之前的值
    110. *this += 1;
    111. return tmp;
    112. }
    113. // d1 - d2
    114. int Date::operator-(const Date& d) const
    115. {
    116. Date max = *this;//假设的d1日期大
    117. Date min = d;
    118. int flag = 1;
    119. //找出最大的日期
    120. if (*this < d)
    121. {
    122. max = d;
    123. min = *this;
    124. flag = -1;
    125. }
    126. int n = 0;
    127. while (max != min)
    128. {
    129. n++;
    130. min++;
    131. }
    132. return n * flag;
    133. }
    134. //一般不这样写
    135. // cout << d1; // operator<<(cout, d1);
    136. //ostream& operator<<(ostream& out, const Date& d)
    137. //{
    138. // out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    139. //
    140. // return out;
    141. //}

    四 取地址及const取地址操作符重载

    这是类的默认6个成员函数在中最后二个,因为这二个在实际使用中并不常见,这里我们就举例说明一下:

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

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

    五 总结

    在本篇博客中,我们学习了,构成函数,析构函数,拷贝构造函数,算术符和赋值符的重载,const成员, 取地址及const取地址操作符重载,这些内容多非常重要而且细节非常多,这也就造成了理解起来困难,下面我们在一起进行总结复习一下!

    构造函数

    • 构造函数的作用是用来初始化成员变量的,在每个对象的生命周期都会调用一次。
    • 编译器对于内置类型的成员变量是不会处理的,所以在C++11中就打了个补丁,让我们可以在类声明的时候给应该默认值。
    • 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

     析构函数

    • 析构函数是用来清理资源空间的,就相当于栈的销毁函数,每当main函数调用结束后,编译器就会自动调用析构函数,用户没有写就会自生成。
    • 创建哪个类的对象则调用该类的构造函数,销毁那个类的对象则调用该类的析构函数。

    拷贝构造函数 

    • 拷贝构造函数是也是构造函数,他是的拷贝的对象是已经建立了的,是没有返回值的。
    • 拷贝构造函数的参数是引用,不能用传值拷贝(会出现无穷递归)。
    • 编译器生成的大多是浅拷贝,会让开辟的空间多次释放,在要申请资源的类中,最好是自己去写拷贝构造函数。

    算术符和赋值符的重载

    • 算术符的重载引入是为了增加代码的可读性。
    • 他的参数要比实际参数要少一个,隐藏了一个指向第一个操作数的指针this。
    • .*  ::  sizeof  ?:  . 注意以上5个运算符不能重载。

     const成员

    • 这里我们只要记住const修饰的是指针this,这个时候this指针权限被限制的非常小,就不会存在权限放大的问题了。

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

    • 记住一个特殊情况,当只想让别人获得指定的内容才需要自己写成员函数,其余用编译器默认生成的就可以了。
  • 相关阅读:
    BERT: 面向语言理解的深度双向Transformer预训练
    再想一想GPT
    MySQL中对于索引的理解
    macOS 运行xxx.command文件提示”无法执行,因为您没有正确的访问权限“解决方法
    linux操作系统进程控制详解
    Java(面试题③)
    【无标题】近几年攻防演练攻击队典型突破的例子
    API(3) StringBuffer类和StringBulider类
    mysql 字符串截取
    javaweb基础:浅聊监听器
  • 原文地址:https://blog.csdn.net/qq_61552595/article/details/127322335