• C++类和对象(中)【万字详解】


    这一篇就是C++中的类和对象的核心内容了.

    目录

    类的6个默认成员函数

    构造函数 

    概念

    特性

    析构函数

    概念

    特性

    拷贝构造函数

    概念

    特性

    赋值运算符重载

    运算符重载

    赋值运算符重载

    const成员

    const修饰的类成员函数

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

    类的6个默认成员函数

    如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。

    先看下图:

    一个空类默认有6个默认成员函数:

    分别是:构造函数、析构函数、拷贝构造函数、赋值重载、普通对象取地址、const对象取地址.

    我们将逐一介绍它.

    构造函数 

    概念

    构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次

    看下面一段日期类代码:

    1. class Date
    2. {
    3. public:
    4. void SetDate(int year, int month, int day)
    5. {
    6. _year = year;
    7. _month = month;
    8. _day = day;
    9. }
    10. void Display()
    11. {
    12. cout << _year << "-" << _month << "-" << _day << endl;
    13. }
    14. private:
    15. int _year;
    16. int _month;
    17. int _day;
    18. };
    19. int main()
    20. {
    21. Date d1, d2;
    22. d1.SetDate(2018, 5, 1);
    23. d1.Display();
    24. Date d2;
    25. d2.SetDate(2018, 7, 1);
    26. d2.Display();
    27. return 0;
    28. }

    对于Date类,可以通过SetDate公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

    此时构造函数就发挥出了作用.

    特性

    构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象

    这六个成员函数都是特殊的,不能以常规角度理解它.

    构造函数给人的感觉就是构造一个东西,构造一个对象,但它并不是,只是对类里的对象进行一个初始化.

    特征如下:

    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; // 调用无参构造函数1
    22. Date d2(2015, 1, 1); // 调用带参的构造函数2
    23. //编译器会根据参数的匹配自动调用合适的构造函数
    24. // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
    25. // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
    26. Date d3();
    27. }

    5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

    编译器默认生成的构造函数1.对内置类型不做处理,2.自定义类型自动调用它的默认构造函数.

    6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

    1. // 默认构造函数
    2. class Date
    3. {
    4. public:
    5. Date()
    6. {
    7. _year = 1900;
    8. _month = 1;
    9. _day = 1;
    10. }
    11. Date(int year = 1900, int month = 1, int day = 1)
    12. {
    13. _year = year;
    14. _month = month;
    15. _day = day;
    16. }
    17. private:
    18. int _year;
    19. int _month;
    20. int _day;
    21. };
    22. // 以下测试函数能通过编译吗?
    23. void Test()
    24. {
    25. Date d1;
    26. }

    我们看到Date类的构造函数只两个:一个无参的构造函数,一个是全缺省的构造函数.都是默认构造函数,存在了两个,那么存在了什么问题呢?

    首先用Date创建一个d1对象,那么d1应该调用哪个构造函数呢?

    两个构造函数都可以调用,第一个是无参的没有问题,第二个是没有给参数,也会把缺省值赋给d1,也可进行调用

    所以这里就产生了歧义,即二义性。这段代码所以并编译不过去,因为编译器不知道调用哪个构造函数.

     不调用可以,但是调用会存在歧义.

    7. 关于编译器生成的默认成员函数,很多同学会有疑惑:在我们不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象year/month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??
    解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char...,自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数

    所以不对内置类型做处理也算是C++的一种小缺陷吧.

    看下面一段代码:

    1. #include<iostream>
    2. using namespace std;
    3. class Time
    4. {
    5. public:
    6. Time()
    7. {
    8. cout << "Time()" << endl;
    9. _hour = 0;
    10. _minute = 0;
    11. _second = 0;
    12. }
    13. private:
    14. int _hour;
    15. int _minute;
    16. int _second;
    17. };
    18. class Date
    19. {
    20. private:
    21. // 基本类型(内置类型)
    22. int _year;
    23. int _month;
    24. int _day;
    25. // 自定义类型
    26. Time _t;
    27. };
    28. int main()
    29. {
    30. Date d;
    31. return 0;
    32. }

    我们看到用Date类创建了一个d对象,创建之后,得去调用Date的构造函数.

    Date没有显式写构造函数,则编译器默认生成一个构造函数

    默认生成的构造函数:内置类型不做处理,自定义类型调用它的默认构造函数.

    _year,_minth,_day不做处理,调用Time的默认构造函数

    调用完成之后,Date默认生成的构造函数也就结束了.

    我们运行来运行一下.

    可以看到确实调用了.

    这里再强调以下,编译器生成的默认构造函数只能调用自定义类型的默认构造函数!

    我们在Time类里写一个构造函数,但是不是默认构造的.

    这样既然写了构造函数,编译器便不能生成Time的默认构造函数了.所以在Date默认生成的构造函数里调用Time的默认构造函数时便会出错,因为Time此时并没有默认构造函数.

    这里可以稍微提一下:

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

    这个并不是说默认构造函数可以将内置类型的数据初始化了,而是说可以给内置类型变量的默认值,写默认值的话,默认构造函数则会根据默认值的值进行初始化的.

    例如:

    1. class Date
    2. {
    3. public:
    4. // 基本类型(内置类型)
    5. int _year = 1;
    6. int _month = 2;
    7. int _day = 3;
    8. };
    9. int main()
    10. {
    11. Date d;
    12. cout << d._year << endl << d._month << endl << d._day << endl;
    13. return 0;
    14. }

     可以看到没有默认生成的构造函数也将内置类型初始化了.

    注意:指针也是内置类型.

    析构函数

    概念

    前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?


    析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作

    特性

    其特征如下:
    1. 析构函数名是在类名前加上字符 ~。
    2. 无参数无返回值。
    3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。//默认生成构造函数特点:跟构造函数类似:a. 内置类型不做处理。b.自定义类型成员回去调用它的析构.
    4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

    由于析构函数无参数,无返回值,所以构不成重载,即只能有一个析构函数.

    看下面代码:

    1. #include<iostream>
    2. #include<assert.h>
    3. using namespace std;
    4. typedef int DataType;
    5. class SeqList
    6. {
    7. public:
    8. SeqList(int capacity = 10)//构造函数
    9. {
    10. _pData = (DataType*)malloc(capacity * sizeof(DataType));
    11. assert(_pData);
    12. _size = 0;
    13. _capacity = capacity;
    14. }
    15. ~SeqList()//析构函数,注意格式:~类名
    16. {
    17. if (_pData)
    18. {
    19. free(_pData); // 释放堆上的空间
    20. _pData = NULL; // 将指针置为空
    21. _capacity = 0;
    22. _size = 0;
    23. }
    24. }
    25. private:
    26. int* _pData;
    27. size_t _size;
    28. size_t _capacity;
    29. };

    5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。

    看下面一段代码:

    1. #include<iostream>
    2. #include<string.h>
    3. using namespace std;
    4. class String
    5. {
    6. public:
    7. String(const char* str = "jack")
    8. {
    9. _str = (char*)malloc(strlen(str) + 1);
    10. strcpy(_str, str);
    11. }
    12. ~String()
    13. {
    14. cout << "~String()" << endl;
    15. free(_str);
    16. }
    17. private:
    18. char* _str;
    19. };
    20. class Person
    21. {
    22. private:
    23. String _name;
    24. int _age;
    25. };
    26. int main()
    27. {
    28. Person p;
    29. return 0;
    30. }

    看运行结果图:

     

    可以看到用Person类生成了一个p对象,person对象里没有析构函数,编译器会默认生成一个析构函数~Person();然后这个默认析构函数去调用自定义类型的析构函数.所以输出~string().

    拷贝构造函数

    概念

    我们创建对象的时候,如果想创建一个和某个对象的完全一样的对象,这个时候就需要拷贝构造函数.

    拷贝构造函数实质上是一个构造函数的一个特殊形式,一个重载.但是它必须且只能有一个参数,该形参是本类类对象的引用.

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

    特性

    拷贝构造函数也是特殊的成员函数,其特征如下:
    1.
    拷贝构造函数是构造函数的一个重载形式。
    2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

    那么传值为什么会引起无限递归呢?

    这里可能有些复杂,难以理解,慢慢来讲解它.

    先看来代码:

    1. #include<iostream>
    2. #include<string.h>
    3. using namespace std;
    4. class Date
    5. {
    6. public:
    7. Date(int year = 1900, int month = 1, int day = 1)//构造函数
    8. {
    9. _year = year;
    10. _month = month;
    11. _day = day;
    12. }
    13. Date(const Date& d)//拷贝构造函数,注意这里必须加引用才可以.
    14. {
    15. _year = d._year;
    16. _month = d._month;
    17. _day = d._day;
    18. }
    19. private:
    20. int _year;
    21. int _month;
    22. int _day;
    23. };
    24. int main()
    25. {
    26. Date d1;
    27. Date d2(d1);
    28. return 0;
    29. }

    上面拷贝构造加了引用,但是如果不加引用为什么会引发无限递归呢?

    首先我们要理解传值调用时发生了什么

    先看这段Date d2(d1);用d1对象构造d2,所以要去调用拷贝构造函数.

    拷贝构造函数:Date(const Date d);我们想要的是把d1对象赋值给d.

    但是赋值的时候,它要怎么赋值呢?是不是又要用d1对象构造d,因为参数会拷贝一份实参.

    d1对象构造d,再次调用d的拷贝构造函数.

    调用了d的拷贝构造函数,需要把参数传给d,而此时传给d,又需要调用拷贝构造函数....

    如此循环,就发生了无限递归.

     这是无限递归的原因图.

    改正的方法就是直接加上引用.

    改成引用之后,传过去的就是d1的别名,相当于d就是d1,此时便不再创建一份拷贝,即也不需要再调用拷贝构造函数.也就不会引发无限递归问题了.

    3. 若未显式定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝

    注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

    浅拷贝就是新拷贝的对象和原对象指向同一块空间,新拷贝的对象的值的改变也会引发原对象的值的改变,所以也是不推荐的.

    深拷贝实质是在内存里重新为新拷贝的对象开辟一块空间,不指向同一块空间。然后这样修改值也就会不会影响到原对象了.

    1. #include<iostream>
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date(int year = 1900, int month = 1, int day = 1)
    7. {
    8. _year = year;
    9. _month = month;
    10. _day = day;
    11. }
    12. private:
    13. int _year;
    14. int _month;
    15. int _day;
    16. };
    17. int main()
    18. {
    19. Date d1;
    20. // 这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。
    21. Date d2(d1);
    22. return 0;
    23. }

    4. 那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

    1. class String
    2. {
    3. public:
    4. String(const char* str = "jack")
    5. {
    6. _str = (char*)malloc(strlen(str) + 1);
    7. strcpy(_str, str);
    8. }
    9. ~String()
    10. {
    11. cout << "~String()" << endl;
    12. free(_str);
    13. }
    14. private:
    15. char* _str;
    16. };
    17. int main()
    18. {
    19. String s1("hello");
    20. String s2(s1);
    21. }

    可以看到s1和s2的地址完全一样.

    一个地址还好,我不修改它不就行了吗?但是我们来运行一下这个程序.

     可以看到,程序报错了.这是为什么呢?

    仔细想一下,s1,s2都是对象,当程序结束的时候,他们都要调用对应的析构函数,但是我们又知道,因为是浅拷贝,s1和s2地址一样.s1被析构以后,相当于这块空间已经被释放掉了,然后呢,s2又释放了一次,这样就造成了错误,因为同一块空间不能被析构两次.

    所以,浅拷贝还存在一个问题,析构的时候会存在被释放两次的问题.

    拷贝构造函数暂时到这里.

    赋值运算符重载

    运算符重载

    先说我们上面写的Date类,里面记录了年月日.

    当我们定义了两个Date对象,比如Date d1(2007,4,6);Date d2(2003,6,9);

    我们如果想比较两个对象谁的日期比较大,该怎么比较呢??

    先来说一下:

    内置类型可以直接使用运算符运算,编译器知道该如何运算.

    自定义类型无法直接使用运算符,因为编译器不知道要如何运算,想要支持,自己实现运算符的重载即可.

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

    注意:
    1.不能通过连接其他符号来创建新的操作符:比如operator@
    2.重载操作符必须有一个类类型或者枚举类型的操作数

    3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
    4.作为类成员的重载函数时,其形参看起来比操作数数目少1,因为成员函数的操作符有一个默认的形参this,限定为第一个形参.
    5.操作符有一个默认的形参this,限定为第一个形参
    .*  ::sizeof ?:  、. 注意以上5个运算符不能重载。

    根据下面代码看一下用法

    1. #include<iostream>
    2. using namespace std;
    3. #pragma warning(disable:4996)
    4. class Date
    5. {
    6. public:
    7. Date(int year = 1900, int month = 1, int day = 1)
    8. {
    9. _year = year;
    10. _month = month;
    11. _day = day;
    12. }
    13. //private:
    14. int _year;
    15. int _month;
    16. int _day;
    17. };
    18. // 这里会发现运算符重载成全局的就需要成员变量是共有的,那么问题来了,封装性如何保证?
    19. // 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
    20. bool operator==(const Date& d1, const Date& d2)
    21. {
    22. return d1._year == d2._year
    23. && d1._month == d2._month
    24. && d1._day == d2._day;
    25. }
    26. void Test()
    27. {
    28. Date d1(2007, 4, 6);
    29. Date d2(2003, 6, 9);
    30. cout << (d1 == d2) << endl;
    31. }
    32. int main()
    33. {
    34. Test();
    35. return 0;
    36. }

    我们运行一下:

     输出0,此时已经实现了自定义对象之间的比较了.

    为了更加严谨,我们将两个日期改成相同的.如果输出1,说明是真,结果正确.

     结果符合预期.这样我们就实现了==运算操作符.

    这里提一下:写运算符重载时,最好在参数前面加上const,因为后面我们不需要修改这两个参数内容,而且在我们不小心失误修改了参数的情况下,能直接报错,会省很多事的.

    但是上面那种写法,我们不得不把类的私有成员改成公有的,因为在类外无法调用类对象的私有成员.

    为了解决这种问题,我们可以把这个运算符重载写到类里面,这样就可以调用类的私有成员了,但有没有其它的问题?我们来看一下.

    此时看到问题:此运算符函数的参数太多.不就是两个参数吗,怎么参数过多?

    在类里面的函数,是会有一个隐藏的this指针,代表调用的那个对象。我们又知道不能显式的写this,所以这个运算符函数里面只写一个参数即可.

    1. class Date
    2. {
    3. public:
    4. Date(int year = 1900, int month = 1, int day = 1)
    5. {
    6. _year = year;
    7. _month = month;
    8. _day = day;
    9. }
    10. bool operator==(const Date& d2)//只写一个参数,因为前面会有一个隐藏的this指针
    11. {
    12. return _year == d2._year
    13. && _month == d2._month
    14. && _day == d2._day;
    15. }
    16. private:
    17. int _year;
    18. int _month;
    19. int _day;
    20. };

    再次运行:

    1. void Test()
    2. {
    3. Date d1(2022, 1, 4);
    4. Date d2(2022, 1, 4);
    5. cout << (d1 == d2) << endl;
    6. }

    依然成功运行了.

    其实d1==d2这个时候相当于是d1.operator(d2);

    此时类里的this相当于也是d1.用d1来调用operator运算符重载函数.

    这里只举了一些样例,比如>=,<=,>,<这些都还没实现,我最后会把所有日期类所有这些操作符的实现全部写出来,放到本文章最后.

    还有操作的类型不一定是同类的,还可以是类型类+整型类等等,都可以.

    赋值运算符重载

    和运算符重载是一个意思,只不过是赋值类的运算符重载.

    1. #include<iostream>
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date(int year = 1900, int month = 1, int day = 1)
    7. {
    8. _year = year;
    9. _month = month;
    10. _day = day;
    11. }
    12. Date(const Date& d)
    13. {
    14. _year = d._year;
    15. _month = d._month;
    16. _day = d._day;
    17. }
    18. Date& operator=(const Date& d)
    19. {
    20. if (this != &d)
    21. {
    22. _year = d._year;
    23. _month = d._month;
    24. _day = d._day;
    25. }
    26. return *this;
    27. }
    28. private:
    29. int _year;
    30. int _month;
    31. int _day;
    32. };
    33. int main()
    34. {
    35. Date d1(2022, 4, 6);
    36. Date d2;
    37. d2 = d1;
    38. return 0;
    39. }

    我看先断点到d2=d1处,但注意,此时d1=d2还没有运行.

    接着向下运行:

     我们发现已经赋值成功了.

    这就是赋值重载运算符的一种应用.

    赋值运算符主要注意以下五点:

    1. 参数类型
    2. 返回值
    3. 检测是否自己给自己赋值
    4. 返回*this
    5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。

    我们主要说以下第五条.

    先看下面一段程序.

    1. #include<iostream>
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date(int year = 1900, int month = 1, int day = 1)
    7. {
    8. _year = year;
    9. _month = month;
    10. _day = day;
    11. }
    12. private:
    13. int _year;
    14. int _month;
    15. int _day;
    16. };
    17. int main()
    18. {
    19. Date d1;
    20. Date d2(2022,6, 9);
    21. // 这里d1调用的编译器生成operator=完成拷贝,d2和d1的值也是一样的。
    22. d1 = d2;
    23. return 0;
    24. }

     我们看一看没有写赋值运算符重载的情况下,只依靠编译器默认生成的能不能完成拷贝操作.

    看运行结果:

     可以看到也完成了拷贝赋值.

    那么这里就有一个问题:

    那么编译器生成的默认赋值重载函数已经可以完成字节序的值拷贝了(浅拷贝),我们还需要自己实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

    其实和刚才讲拷贝构造函数的情况一样,浅拷贝造成的同一块地址被析构两次,进而造成系统崩溃.

    1. #include<iostream>
    2. using namespace std;
    3. class String
    4. {
    5. public:
    6. String(const char* str = "")
    7. {
    8. _str = (char*)malloc(strlen(str) + 1);
    9. strcpy(_str, str);
    10. }
    11. ~String()
    12. {
    13. cout << "~String()" << endl;
    14. free(_str);
    15. }
    16. private:
    17. char* _str;
    18. };
    19. int main()
    20. {
    21. String s1("hello");
    22. String s2("world");
    23. s1 = s2;
    24. }

    运行一下:

    这就造成了错误.因为释放的地址不能再被释放.

    写日期类的时候注意:任何一个类,只需要写一个 > == 或者< == 重载,剩下的比较运算符复用即可.

    比如我们要实现>=,假设我们已经实现了 > ==,我们此时只需要返回d1>d2 && d1==d2即可,这样就实现了>=,所以要学会复用写过的东西. 

    const成员

    const修饰的类成员函数

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

    编译器会做以下处理:

    这样就无法改变this指针所指向的内容了若改变则会报错

    1. #include<iostream>
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date(int year = 1990, int month = 10, int day = 20)
    7. {
    8. _year = year;
    9. _month = month;
    10. _day = day;
    11. }
    12. void func() const
    13. {
    14. _year = 1990;
    15. _month = 20;
    16. _day = 30;
    17. }
    18. private:
    19. int _year;
    20. int _month;
    21. int _day;
    22. };
    23. int main()
    24. {
    25. Date d1;
    26. d1.func();
    27. }

    运行一下:

     看这些错误,表达式必须是可修改的左值,说明此时this指针已经被const修饰了,变成常量了.

    所以也会有后面三个错误,通过常量对象访问成员变量.所以这就是const修饰成员函数的本质.

    那么我们再来看以下代码:

    1. #include<iostream>
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. void Display()
    7. {
    8. cout << "Display ()" << endl;
    9. cout << "year:" << _year << endl;
    10. cout << "month:" << _month << endl;
    11. cout << "day:" << _day << endl << endl;
    12. }
    13. void Display() const
    14. {
    15. cout << "Display () const" << endl;
    16. cout << "year:" << _year << endl;
    17. cout << "month:" << _month << endl;
    18. cout << "day:" << _day << endl << endl;
    19. }
    20. private:
    21. int _year; //
    22. int _month; //
    23. int _day; //
    24. };
    25. void Test()
    26. {
    27. Date d1;
    28. d1.Display();
    29. const Date d2;
    30. d2.Display();
    31. }
    32. int main()
    33. {
    34. Test();
    35. }

    这个类里面不同的是没有构造函数.

    那么运行会有什么问题呢?

     可以看到d2被const修饰.然后错误是必须初始化const对象.

    因为被const修饰后,后面这个对象便不可以再被改变,所以必须初始化.

    初始化方式可以有好多种:1.写构造函数2.在成员变量的后面直接给默认值3.初始化列表(后面说).

    有以下几个问题说明一下:

    1. const对象可以调用非const成员函数吗?

    不可以,const对象的权限比非const权限小,权限小不能调用权限大的函数.即权限不可以放大.
    2. 非const对象可以调用const成员函数吗?

    可以,权限可以缩小.
    3. const成员函数内可以调用其它的非const成员函数吗?

    不可以,原理和上面的类似,权限不可以放大
    4. 非const成员函数内可以调用其它的const成员函数吗?

    可以,权限可以缩小.

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

    这个用的不太多,而且平常写的时候也不需要写这个重载,所以这里就大致说一下用法即可.

    1. class Date
    2. {
    3. public:
    4. Date* operator&()//取地址重载
    5. {
    6. return this;
    7. }
    8. const Date* operator&()const//const取地址重载
    9. {
    10. return this;
    11. }
    12. private:
    13. int _year; // 年
    14. int _month; // 月
    15. int _day; // 日
    16. };

    只有特殊情况才需要重载,比如想要别人获取指定的内容.

    这样类和对象的六个默认成员函数就说完了.当然这些还不够.

    怎么熟练运用以及一些细节还需要后面慢慢探索.

    这里把日期类的各种运算符重载写一下:

    1. #include<iostream>
    2. #include<assert.h>
    3. //任何一个类,只需要写一个> == 或者< == 重载,剩下的复用即可
    4. using namespace std;
    5. class Date
    6. {
    7. public:
    8. bool CheckDate()
    9. {
    10. if(_year >= 1
    11. && _month > 0 && _month < 13
    12. && _day > 0 && _day <= GetMonthDay(_year, _month))
    13. {
    14. return true;
    15. }
    16. return false;
    17. }
    18. bool IsLeapYear(int year)
    19. {
    20. if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
    21. return true;
    22. return false;
    23. }
    24. int GetMonthDay(int year, int month)
    25. {
    26. static int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    27. if (month == 2 && IsLeapYear(year))
    28. {
    29. return 29;
    30. }
    31. else
    32. {
    33. return days[month];
    34. }
    35. }
    36. //构造函数
    37. Date(int year = 1900, int month = 1., int day = 1)
    38. {
    39. _year = year;
    40. _month = month;
    41. _day = day;
    42. }
    43. //拷贝构造函数
    44. Date(const Date& d)
    45. {
    46. _year = d._year;
    47. _month = d._month;
    48. _day = d._day;
    49. if (!CheckDate())
    50. {
    51. cout << _year << _month << _day << endl;
    52. cout << "刚构造的日期非法" << endl;
    53. assert(CheckDate());
    54. }
    55. }
    56. //析构函数
    57. ~Date()
    58. {
    59. }
    60. //日期 += 天数
    61. Date& operator +=(int day)
    62. {
    63. if (day < 0)
    64. {
    65. return *this -= -day;
    66. }
    67. _day += day;
    68. while (_day > GetMonthDay(_year, _month))
    69. {
    70. _day -= GetMonthDay(_year, _month);
    71. _month++;
    72. if (_month == 13)
    73. {
    74. _month = 1;
    75. _year++;
    76. }
    77. }
    78. return *this;
    79. }
    80. //日期 + 天数
    81. Date& operator +(int day)
    82. {
    83. Date ret = *this;
    84. ret += day;
    85. return ret;
    86. }
    87. //日期 - 天数
    88. Date operator -(int day)
    89. {
    90. Date tmp = *this;
    91. tmp -= day;
    92. return tmp;
    93. }
    94. //日期 -= 天数
    95. Date& operator -= (int day)
    96. {
    97. if (day < 0)
    98. {
    99. return *this += -day;
    100. }
    101. _day -= day;
    102. while (_day <= 0)
    103. {
    104. _month--;
    105. _day += GetMonthDay(_year, _month);
    106. if (_month == 0)
    107. {
    108. _year--;
    109. _month = 12;
    110. }
    111. }
    112. return *this;
    113. }
    114. //前置++
    115. Date& operator ++()
    116. {
    117. return *this += 1;
    118. }
    119. //后置++
    120. Date operator ++(int)
    121. {
    122. Date tmp(*this);
    123. *this += 1;
    124. return tmp;
    125. }
    126. //后置--
    127. Date operator --(int)
    128. {
    129. Date tmp = *this;
    130. *this -= 1;
    131. return tmp;
    132. }
    133. //前置--
    134. Date operator --()
    135. {
    136. return *this -= 1;
    137. }
    138. // >运算符重载
    139. bool operator>(const Date& d)
    140. {
    141. if (_year > d._year)
    142. {
    143. return true;
    144. }
    145. else if (_year == d._year && _month > d._month)
    146. {
    147. return true;
    148. }
    149. else if (_year == d._year && _month == d._month && _day > d._day)
    150. {
    151. return true;
    152. }
    153. else
    154. {
    155. return false;
    156. }
    157. }
    158. //= 运算符重载
    159. Date& operator =(const Date& d)
    160. {
    161. if (this != &d)
    162. _year = d._year;
    163. _month = d._month;
    164. _day = d._day;
    165. return *this;
    166. }
    167. // ==运算符重载
    168. bool operator==(const Date& d)
    169. {
    170. return this->_year == d._year
    171. && this->_month == d._month
    172. && this->_day == d._day;
    173. }
    174. // >=运算符重载
    175. bool operator >= (const Date& d)
    176. {
    177. return (*this > d) || (*this == d);
    178. }
    179. // <运算符重载
    180. bool operator < (const Date& d)
    181. {
    182. return !(*this >= d);
    183. }
    184. // <=运算符重载
    185. bool operator <= (const Date& d)
    186. {
    187. return (*this < d) || (*this == d);
    188. }
    189. // !=运算符重载
    190. bool operator != (const Date& d)
    191. {
    192. return !(*this == d);
    193. }
    194. // 日期-日期 返回天数
    195. int operator-(const Date& d)
    196. {
    197. int flag = 1;
    198. Date max = *this;
    199. Date min = d;
    200. if (*this < d)
    201. {
    202. max = d;
    203. min = *this;
    204. flag = -1;
    205. }
    206. int n = 0;
    207. while (min != max)
    208. {
    209. ++min;
    210. ++n;
    211. }
    212. return n * flag;
    213. }
    214. private:
    215. int _year;
    216. int _month;
    217. int _day;
    218. };
    219. int main()
    220. {
    221. Date d1(2022, 7, 36);
    222. Date d2(2022, 7, 24);
    223. Date d3(2022, 8, 26);
    224. cout << (d1 - d3) << endl;
    225. return 0;
    226. };

  • 相关阅读:
    5、超链接标签
    错误模块路径: ...\v4.0.30319\clr.dll,v4.0.30319 .NET 运行时中出现内部错误,进程终止,退出代码为 80131506。
    jni场景下c++代码种,编写jstring转char*
    React -- nextjs src/pages里的文件在浏览器显示404
    c语言练习:POJ 1003 宿醉(HangOver)
    Reparameterization trick(重参数化技巧)
    用了 TCP 协议,数据一定不会丢吗?
    eclipse创建Maven项目(保姆级教学)
    Java-程序控制语句
    ROS service简单使用示例
  • 原文地址:https://blog.csdn.net/weixin_47257473/article/details/127596639