)
一.类的6个默认成员函数在C语言中如果一个类没有显式定义某些特殊的成员函数。编译器会在需要的时候自动为类生成默认的版这些由编译器生成的成员函数被叫做类的默认成员函数。这些成员函数让用户在定义类的时候更加方便。根据上面的了解我们知道如果有一个类什么成员都没有就是空类。那么空类中就是什么也没有的类吗当然不是这个空类中还会有编译器生成的6个默认成员函数。下面图片是对6个默认成员函数的粗略介绍上述图片简单说明了6个默认成员函数的作用比如构造函数的作用是对类的成员进行初始化析构函数的作用是完成类的清理工作。等等······二.构造函数1.作用class Date { public: void Init(int year, int month, int day) //全缺省初始化函数 { _year year; _month month; _day day; } void Print() //打印函数有隐藏的this指针可以打印相关数据 { cout _year - _month - _day endl; } private: //定义私有的成员变量 int _year; int _month; int _day; }; int main() { Date d1; //创建一个新的类对象名字为d1 d1.Init(2022, 7, 5); //通过成员函数进行类对象的初始化 d1.Print(); Date d2; d2.Init(2022, 7, 6); d2.Print(); return 0; }上述代码的具体解释首先定义了日期类其中自己定义了类的初始化函数当类对象在创造后需要初始化时调用该函数是有些冗余和麻烦的而今天要学习的默认构造函数就是解决这一麻烦的。下面来解释一下默认构造函数的一些特性2.特性构造函数是特殊的默认成员函数需要注意的是构造函数的名字虽然叫做构造但是构造函数的主要任务不是开辟空间创建类而是初始化类的对象。其特征如下1.函数名与类名相同2.函数没有返回值3.对象实例化时编译器自动调用相应的构造函数4.构造函数可以重载下面是运用构造函数的例子class Date { public: // 1.无参构造函数 Date() { _year 0; _month 1; _day 1; } // 2.带参构造函数 Date(int year, int month, int day) { _year year; _month month; _day day; } private: int _year; int _month; int _day; }; void TestDate() { Date d1; // 调用无参构造函数 Date d2(2015, 1, 1); // 调用带参的构造函数 // 注意如果通过无参构造函数创建对象时对象后面不用跟括号否则就成了函数声明 // 以下代码的函数声明了d3函数该函数无参返回一个日期类型的对象 // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?) Date d3(); }上述代码的具体解释首先定义了一个日期类随后编写了两个构造函数分别是无参的构造函数和带参的构造函数。无参的构造函数将创建的类初始化为合适的值年为0月为1日为1带参的构造函数将创建的类初始化为参数的值年为2015月和日为1。这里需要注意的是类在创建的时候就顺便调用了构造函数对类进行了初始化。在创建类的时候对于无参数的类不应该带后面的括号防止编译器认为此语句为函数的声明对于有参的类的初始化直接在创建类的语句后面加上括号内部带上对应的参数即可。根据上面的默认构造函数例子的认识我们能方便的创建完类对象之后顺便完成类对象的初始化操作。如果类中没有直接定义构造函数那么C编译器会自动生成一个无参的默认构造函数一旦用显式定义构造函数则编译器将不在默认生成构造函数了。这就是默认的含义在没有定义的情况下会默默的自动生成下面代码是相关的证明class Date { public: /* // 如果用户显式定义了构造函数编译器将不再生成 Date(int year, int month, int day) //自己定义的默认构造函数 { _year year; _month month; _day day; } */ void Print() { cout _year - _month - _day endl; } private: int _year; int _month; int _day; }; int main() { // 将Date类中构造函数屏蔽后代码可以通过编译因为编译器生成了一个无参的默认构造函数 // 将Date类中构造函数放开代码编译失败因为一旦显式定义任何构造函数编译器将不再生成 // 无参构造函数放开后报错error C2512: “Date”: 没有合适的默认构造函数可用 Date d1; //创建一个类对象顺便对其进行初始化 return 0; }上述代码的具体解释将原来定义的构造函数注释掉代码依旧可以编译通过原因是编译器生成了默认的构造函数进行类的初始化。但是打印出结果会是一个随机值说明默认的构造函数将其初始化为随机值那么问题来了初始化和不初始化效果是一样的为什么还要编译器自动调用默认的构造函数呢稍等片刻下面我们会解释这个问题。将注释放开编译将不会通过因为用户显式定义了有参的构造函数编译器就不会生成默认的无参构造函数。导致d1没有合适的构造函数可以用。总结一句就是创建类对象时无参数会调用编译器自动生成的空参数构造函数当没有显式定义时、手写的无参构造函数、手写的全缺省的构造函数。那么根据上述代码我们可以看出不实现构造函数的情况下编译器会生成默认的构造函数。但是看起来默认的构造函数又没什么用。d对象调用了编译器生成的默认构造函数但是初始化的结果依旧是随机值。那么其中又有什么作用呢C语言把类型分为内置类型和自定义类型。内置类型就是语言提供的数据类型比如int类型、char类型自定义类型就是我们使用class和struct等自己定义的类型看看下面的程序我们就能理解默认构造成员函数的深层作用class Time { public: Time() //创建一个无参的时间默认构造函数 { cout Time() endl; _hour 0; _minute 0; _second 0; } private: //私有的定义时间类的成员变量 int _hour; int _minute; int _second; }; class Date //创建一个日期Date类 { private: // 基本类型(内置类型) int _year; int _month; int _day; // 自定义类型 Time _t; }; int main() { Date d; return 0; }上述代码的具体解释首先定义了一个时间类内部的成员函数为无参的构造函数成员变量为时分秒。随后又定义了日期类其中的成员变量有年月日和自定义类型的时间类。最后在主函数中创建日期类进行调式会发现年月日被初始化为随机值但是自定义类型的时间类的成员变量均初始化为0。C11中针对内置类型成员具有不初始化的缺陷但是又打了补丁内置类型在变量类中声明时可以给默认值。注意无参的构造函数、全缺省的构造函数、我们没写编译器默认生成的构造函数都是默认构造函数。class Date { public: Date() { _year 1900; _month 1; _day 1; } Date(int year 1900, int month 1, int day 1) { _year year; _month month; _day day; } private: int _year; int _month; int _day; }; // 以下测试函数能通过编译吗 void Test() { Date d1; }上述代码是不能编译通过的因为我们在上述代码中出现了两个默认构造函数编译器不知道该执行哪一个上面的代码中出现了无参的构造函数和全缺省的构造函数当创建类且无参数需要调用构造函数时编译器就会迷茫不知道该按照哪一种构造函数进行初始化。解决这问题的方法之一是删掉两种构造函数的其中一个。三.析构函数1.概念在前面的构造函数中我们了解到一个类对象是怎么创建的那么一个类对象是怎么没的呢析构函数与构造函数的作用相反析构函数不是完成对类对象本身的销毁局部对象的销毁工作是编译器完成的。在编译器完成对类对象这个局部对象销毁的同时该类对象会自动调用析构函数完成类对象中资源的销毁清理工作。2.特性析构函数是特殊的默认成员函数其特征如下1.析构函数名字是在类名之前加上字符~2.无参数并且没有返回值3.一个类只有一个析构函数如果没有显式定义系统会自动生成默认的析构函数4.析构函数不能重载5.类对象的生命周期结束时C编译器会自动调用析构函数。typedef int DataType; class Stack //创建了一个数据结构栈类 { public: Stack(size_t capacity 3) //栈的空间开辟 { _array (DataType*)malloc(sizeof(DataType) * capacity); if (NULL _array) { perror(malloc申请空间失败!!!); return; } _capacity capacity; _size 0; } void Push(DataType data) //栈的成员函数 { // CheckCapacity(); _array[_size] data; _size; } // 其他方法... ~Stack() //创建栈的默认析构函数 { if (_array) { free(_array); _array NULL; _capacity 0; _size 0; } } private: DataType* _array; int _capacity; int _size; }; void TestStack() { Stack s; s.Push(1); s.Push(2); }上述代码构建了关于数据结构的栈的类那么析构函数就可以在代码生命周期结束时销毁栈在创建时开辟的内存空间。这就是析构函数的作用并不是销毁栈这个类对象而是销毁栈创建时资源开辟的空间。关于编译器自动生成的析构函数会完成些什么事情呢下面的程序我们会看到编译器生成的默认析构函数对自定义类型成员调用它的析构函数。class Time { public: ~Time() //创建时间Time类的析构函数 { cout ~Time() endl; //打印信息提供程序执行此函数的信息 } private: int _hour; int _minute; int _second; }; class Date //创建一个日期Date类 { private: // 基本类型(内置类型) int _year 1970; int _month 1; int _day 1; // 自定义类型 Time _t; }; int main() { Date d; return 0; }上述代码的具体解释首先定义了一个时间类成员变量有时分秒成员函数有默认的析构函数。随后又创建了一个日期类成员变量有内置类型年月日和自定义类型时间类。最后代码的输出结果为~Time()。在主函数中并没有直接创建Time类的对象为什么会调用Time类的析构函数呢因为主函数中创建了一个日期类对象d而d中包含四个成员变量其中年月日是内置类型的成员变量销毁时不需要进行资源的清理最后系统直接将其内存回收即可而_t是Time类对象所以在d销毁时要将其内部包含的Time类的_t对象销毁所以需要调用Time类的析构函数。而Date没有显式提供析构函数所以编译器会自动调用一个默认的析构函数目的是在其内部调用Time类的析构函数即当Date对象销毁时要保证其内部每一个自定义对象都可以正确销毁。主函数没有直接调用Time类的析构函数而是显式调用编译器为Date类生成的默认析构函数。注意创建哪个类的对象则调用哪个类的析构函数销毁哪个类则调用该类的析构函数。如果类中没有申请资源析构函数可以不写直接使用编译器默认生成的析构函数即可比如Date类有资源申请时一定要写析构函数否则会造成资源的浪费或泄露比如Stack类。根据上述两个默认成员函数的学习我们可以知道这两个默认成员函数对于内置类型均不做处理构造函数不会初始化内置类型随机值析构函数对内置成员不做处理不会释放堆内存四.拷贝构造函数1.概念在创建类对象时当我们想要创建两个一模一样的两个类时其中一个类对象的改变难道还需要我们手动更新另一个类对象的值吗这显然不方便也不太可能这时候就需要用到拷贝构造函数。其中拷贝构造函数只有单个形参数该形参是对本类类型对象的const修饰的引用后面会详细介绍原因用已经存在的类对象创建新的类对象。这个过程中由编译器自动调用。2.特征拷贝构造函数是一个特殊的成员函数其特征如下1.拷贝构造函数时构造函数的一种重载形式2.拷贝构造函数的参数只有一个且必须是类对象的引用使用传值方式编译器会直接报错因为会引发无穷递归调用class Date { public: Date(int year 1900, int month 1, int day 1) { _year year; _month month; _day day; } Date(const Date d) // 正确写法 // Date(Date d) // 错误写法编译报错会引发无穷递归 { _year d._year; _month d._month; _day d._day; } private: int _year; int _month; int _day; }; int main() { Date d1; Date d2(d1); //创建一个类对象d2要求拷贝d1的相关数据 return 0; }上述代码的具体解释代码首先定义了一个日期类在日期类中有全缺省的构造函数和拷贝构造函数还有日期类的成员变量年月日。其中拷贝构造函数的具体解释开始的时候函数运行时需要先传参。拷贝构造的形式参数是Date dd是局部对象创建形式参数d时又会调用拷贝构造函数层层传递最后陷入无穷的循环导致栈溢出。所以应该将形式参数的形式改写为引用的形式这样d就是d1的别名不会产生新的对象更不会调用拷贝构造函数。根据上图再次进行解释当程序执行到主函数的Date d2(d1)语句时首先应该进行函数的传参将d1这个实际参数的值传递给默认拷贝构造函数的形式参数就会再次调用拷贝构造函数拷贝d1实际参数的值给形式参数这个过程又是一个拷贝构造类似于Date d(d1)因为再次的拷贝构造需要先传递函数的实际参数这个过程又是拷贝构造类似于Date d(d1)层层递归、无穷无尽如果没有显式定义拷贝构造函数编译器会生成默认的拷贝构造函数默认的拷贝构造函数对象按内存存储字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。下面是相关特性的代码举例class Time { public: Time() //时间Time类的默认构造函数 { _hour 1; _minute 1; _second 1; } Time(const Time t) //时间Time类的默认拷贝构造函数 { _hour t._hour; _minute t._minute; _second t._second; cout Time::Time(const Time) endl; } private: //时间类的成员变量 int _hour; int _minute; int _second; }; class Date //创建一个日期Date类 { private: // 基本类型(内置类型) int _year 1970; int _month 1; int _day 1; // 自定义类型 Time _t; }; int main() { Date d1; // 用已经存在的d1拷贝构造d2此处会调用Date类的拷贝构造函数 // 但Date类并没有显式定义拷贝构造函数则编译器会给Date类生成一个默认的拷贝构造函数 Date d2(d1); //创建类对象d2随后将d1的值拷贝给d2 return 0; }通过运行上述的代码可以知道虽然自身没有定义日期类的默认拷贝构造函数但是还是会调用编译器自动生成的默认拷贝构造函数通过浅拷贝的方式进行日期类对象的值拷贝。注意编译器生成的默认拷贝构造函数中内置类型是按照字节方式进行直接拷贝的逐个字节对应起来进行一对一字节的拷贝而自定义类型是调用其拷贝构造函数完成拷贝的。所以说编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了那么还需要显式实现该默认成员函数吗当然日期类是没有必要的那么下面的例子呢// 这里会发现下面的程序会崩溃掉这里就需要我们以后讲的深拷贝去解决。 typedef int DataType; class Stack //创建一个数据结构栈类 { public: Stack(size_t capacity 10) //栈的默认构造函数 { _array (DataType*)malloc(capacity * sizeof(DataType)); if (nullptr _array) { perror(malloc申请空间失败); return; } _size 0; _capacity capacity; } void Push(const DataType data) //入栈的类成员函数 { // CheckCapacity(); _array[_size] data; _size; } ~Stack() //栈的默认析构函数用于程序结束后的资源清理 { if (_array) { free(_array); _array nullptr; _capacity 0; _size 0; } } private: DataType *_array; //私有的创建栈相关的成员变量 size_t _size; size_t _capacity; }; int main() { Stack s1; //创建一个栈s1 s1.Push(1); s1.Push(2); s1.Push(3); s1.Push(4); //逐步给栈s1压入数据 1 2 3 4 Stack s2(s1); //创建栈类的新对象s2通过默认的拷贝构造函数将s1的值拷贝给s2 return 0; }上述代码的具体解释创建一个数据结构的栈类成员函数有默认的构造函数、默认的的析构函数、入栈函数等·······成员变量有_array、_size、_capacity。但是上述自定义类没有默认拷贝构造函数所以只能调用编译器自动生成的默认拷贝构造函数。详细请看下方图片上述图片让我们认识到了当类中没有涉及到资源申请时拷贝构造函数是否写都可以接受但是一旦涉及到资源申请拷贝构造函数一定要自己定义否则就会浅拷贝可能进而导致程序的崩溃。拷贝构造函数典型的使用场景使用已经存在对象创建新的类对象函数类型为类类型对象函数返回值为类类型对象class Date { public: Date(int year, int minute, int day) //默认构造函数 { cout Date(int,int,int): this endl; } Date(const Date d) //默认的拷贝构造函数 { cout Date(const Date d): this endl; } ~Date() //默认的析构函数 { cout ~Date(): this endl; } private: //私有的成员变量 int _year; int _month; int _day; }; Date Test(Date d) { Date temp(d); return temp; } int main() { Date d1(2022,1,13); //创建一个类对象d1.初始化为2022年1月13日 Test(d1); //调用测试函数函数的实际参数为d1 return 0; }通过上述的图片我们可以得知首先代码创建类对象d1调用构造函数随后掉用test函数首先先进行函数的传参将d1传参的过程会调用拷贝构造函数随后进入Test函数内部调用拷贝构造函数创建temp类对象将d的值拷贝给temp因为Test函数是Date类型的返回值返回时会创建临时变量接受返回值最后将其值进行返回。这个过程也属于拷贝构造。假设创建的临时变量名为f则拷贝构造为Date f(temp)随后将f的值返回。最后就是程序结束时调用默认析构函数的步骤。因为局部变量先构造后析构所以析构函数逐步析构temp、参数d、临时变量f最后再销毁最先构造的变量的d1。为了提高程序的运行效率一般在对象传参时尽量使用引用类型返回根据实际场景能用引用返回尽量用引用返回。五.赋值运算符重载1.概念C为了增加代码的可读性从而引入了运算符重载运算符重载是具有特殊函数名的函数也是具有对应运算符的返回值类型、函数名以及函数参数列表。其函数的返回值类型、函数参数列表与普通函数相似。函数名字为关键字operator后面加上需要重载的运算符符号。函数原型为返回值类型 operator操作符(参数列表)。注意不能通过连接其他无意义操作符来创造新的操作符例如operator。重载操作符必须有一个类类型的参数用于内置类型的运算符其含义不能改变。例如内置类型的整形不能改变其含义。作为类成员函数重载时其形式参数的数量看起来比操作数少1其实是因为类的成员函数的第一参数为隐含的this指针。再进行赋值运算符重载的学习中要注意 * :: sizeof ? : .以上五个运算符不可以重载。// 全局的operator class Date { public: Date(int year 1900, int month 1, int day 1) //全缺省的构造函数 { _year year; _month month; _day day; } //private: int _year; int _month; int _day; }; // 这里会发现运算符重载成全局的就需要成员变量是公有的那么问题来了封装性如何保证 bool operator(const Date d1, const Date d2) { return d1._year d2._year d1._month d2._month d1._day d2._day; } void Test () { Date d1(2018, 9, 26); Date d2(2018, 9, 27); cout(d1 d2)endl; } class Date { public: Date(int year 1900, int month 1, int day 1) //全缺省的构造函数 { _year year; _month month; _day day; } // bool operator(Date* this, const Date d2) // 这里需要注意的是左操作数是this指向调用函数的对象 bool operator(const Date d2) //判断是否相等的运算符重载 { return _year d2._year; _month d2._month _day d2._day; } private: int _year; int _month; int _day; };2.特征1.赋值运算符重载格式参数类型const T传递引用可以提高传参效率返回值类型T返回引用可以提高返回效率减少临时变量的拷贝构造class Date { public : Date(int year 1900, int month 1, int day 1) //日期类的构造函数 { _year year; _month month; _day day; } Date (const Date d) //日期类的拷贝构造函数 { _year d._year; _month d._month; _day d._day; } Date operator(const Date d) //日期类的赋值重载函数 { if(this ! d) //当两者完全相同时防止互相自赋值 { _year d._year; _month d._month; _day d._day; } return *this; } private: int _year ; int _month ; int _day ; };上述赋值重载操作的具体解释赋值重载的函数参数为引用类型防止传递的时候掉用拷贝构造从而提高参数的传递效率赋值重载的函数返回值是传引用返回防止返回时创建临时变量再次调用拷贝构造从而提高函数的返回效率。2.赋值运算符只能重载成类的成员函数不能重载成全局函数class Date { public: Date(int year 1900, int month 1, int day 1) //日期类的全缺省构造函数 { _year year; _month month; _day day; } int _year; int _month; int _day; }; // 赋值运算符重载成全局函数注意重载成全局函数时没有this指针了需要给两个参数 Date operator(Date left, const Date right) { if (left ! right) { left._year right._year; left._month right._month; left._day right._day; } return left; } // 编译失败 // error C2801: “operator ”必须是非静态成员上述赋值运算符重载成了全局函数所以产生了编译错误。原因是赋值运算符如果不显式实现编译器会在类中生成一个默认的赋值运算符重载函数。此时用户再在类外自己实现一个全局的运算符重载就会和编译器在类中生成的形成冲突。3.用户没有显式实现时编译器会生成一个默认赋值运算符重载函数以值的方式逐字节进行拷贝。注意内置类型成员变量是直接复制的而自定义类型成员变量需要调用对应的赋值运算符重载完成赋值。class Time { public: Time() //时间类的构造函数 { _hour 1; _minute 1; _second 1; } Time operator(const Time t)//时间类的赋值重载函数 { if (this ! t) { _hour t._hour; _minute t._minute; _second t._second; } return *this; } private: int _hour; int _minute; int _second; }; class Date { private: // 基本类型(内置类型) int _year 1970; int _month 1; int _day 1; // 自定义类型 Time _t; }; int main() { Date d1; Date d2; d1 d2; return 0; }既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了还需要自己实现吗当然像日期类这样的类是没必要自己实现的那么下面的类呢// 这里会发现下面的程序会崩溃掉这里就需要我们以后讲的深拷贝去解决。 typedef int DataType; class Stack { public: Stack(size_t capacity 10) //栈类的构造函数 { _array (DataType*)malloc(capacity * sizeof(DataType)); if (nullptr _array) { perror(malloc申请空间失败); return; } _size 0; _capacity capacity; } void Push(const DataType data) //栈类的插入数据函数 { // CheckCapacity(); _array[_size] data; _size; } ~Stack() { if (_array) { free(_array); _array nullptr; _capacity 0; _size 0; } } private: //栈私有的成员变量 DataType *_array; size_t _size; size_t _capacity; }; int main() { Stack s1; //创建一个类对象s1 s1.Push(1); s1.Push(2); s1.Push(3); s1.Push(4); //随后逐步插入数据1 2 3 4 Stack s2; s2 s1; //随后利用运算符重载将s1类的相关数据拷贝给s2 return 0; }注意如果类中未涉及到资源管理赋值运算符是否实现都可以一旦涉及资源管理就必须要自己实现赋值运算重载函数通过上述的图片可以得出因为栈需要进行资源管理并且代码中并没有自己实现赋值运算符的重载函数编译器发现两者之间的复制关系就会采用浅拷贝的方式将s1原封不动拷贝到另一个对象中两个类对象中成员变量指向的是同一块空间。随后s2原来的空间会直接丢失掉又因为两者共享一份内存空间所以销毁时会销毁两次最终程序崩溃。3.前置和后置的重载class Date { public: Date(int year 1900, int month 1, int day 1) { _year year; _month month; _day day; } //前置 //this指向的对象函数结束后不会销毁故以引用方式返回提高效率 Date operator() { _day 1; return *this; } // 后置 // C规定后置重载时多增加一个int类型的参数但调用函数时该参数不用传递编译器自动传递 //后置是先使用后1因此需要返回1之前的旧值故需在实现时需要先将this保存一份然后给this1 // 而temp是临时对象因此只能以值的方式返回不能返回引用 Date operator(int) { Date temp(*this); _day 1; return temp; } private: int _year; int _month; int _day; }; int main() { Date d; Date d1(2022, 1, 13); d d1; // d: 2022,1,13 d1:2022,1,14 d d1; // d: 2022,1,15 d1:2022,1,15 return 0; }上述代码的具体解释前置是先加后使用所以返回的是加1之后的值后置是先用后加所以返回的是首先保存的临时变量temp但是后置有一个C的规定在函数参数的括号里需要有个int用于编译器区分前置和后置。六.日期类的实现class Date { public: // 获取某年某月的天数 int GetMonthDay(int year, int month) { static int days[13] {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int day days[month]; if (month 2 ((year % 4 0 year % 100 ! 0) || (year%400 0))) { day 1; } return day; } // 全缺省的构造函数 Date(int year 1900, int month 1, int day 1); // 拷贝构造函数 // d2(d1) Date(const Date d); // 赋值运算符重载 // d2 d3 - d2.operator(d2, d3) Date operator(const Date d); // 析构函数 ~Date(); // 日期天数 Date operator(int day); // 日期天数 Date operator(int day); // 日期-天数 Date operator-(int day); // 日期-天数 Date operator-(int day); // 前置 Date operator(); // 后置 Date operator(int); // 后置-- Date operator--(int); // 前置-- Date operator--(); // 运算符重载 bool operator(const Date d); // 运算符重载 bool operator(const Date d); // 运算符重载 bool operator (const Date d); // 运算符重载 bool operator (const Date d); // 运算符重载 bool operator (const Date d); // !运算符重载 bool operator ! (const Date d); // 日期-日期 返回天数 int operator-(const Date d); private: int _year; int _month; int _day; };上述代码实现了小部分的日期类的书写剩下的交给后章节的拓展内容。七.const成员函数将const修饰的成员函数称为const成员函数const修饰类成员函数实际修饰该成员函数隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。通过给函数参数括号后写const关键字表示的是给该隐含的this指针参数指向的内容赋予只读属性。我们来看下面这段代码class Date { public: Date(int year, int month, int day) //日期类的构造函数 { _year year; _month month; _day day; } void Print() { cout Print() endl; cout year: _year endl; cout month: _month endl; cout day: _day endl endl; } void Print() const { cout Print()const endl; cout year: _year endl; cout month: _month endl; cout day: _day endl endl; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; void Test() { Date d1(2022,1,13); //创建Date类型的类对象d1并初始化 d1.Print(); //打印d1调用时的关键信息 const Date d2(2022,1,13); //创建const Date类型的类对象d2并初始化 d2.Print(); }const对象对象内部成员不可以修改只能调用带const修饰的成员函数。成员函数末尾加上const函数参数隐含的this指针类型就变成了const Date*不能修改指针指向的内容。非const成员函数this是普通的Date*允许修改成员不能被const对象调用。下面思考下面的4个问题const对象可以调用非const成员函数吗非const对象可以调用const成员函数吗const成员函数可以调用其它的非const成员函数吗非const成员函数可以调用其他const成员函数吗上述问题的答案答案一不可以答案二可以答案三不可以答案四可以d1是Date类型的d2是const Date类型的。权限只能缩小不能放大。八.取地址及const取地址操作符重载这两个默认成员函数一般不用重新定义 编译器默认会生成。六个默认成员函数其实重要的只有前四个函数下面是关于两个默认成员函数的代码举例class Date { public : Date* operator() { return this ; } const Date* operator()const { return this ; } private : int _year ; // 年 int _month ; // 月 int _day ; // 日 };上述两种默认成员函数的具体解释第一种默认成员函数返回值类型是Date*函数的隐形参数类型是Date*this指针类型。第二种默认成员函数的函数返回值是const Date *类型函数隐形参数的类型是const Date*类型。根据上述两种默认成员函数的解释我们可以知道这两种默认成员函数构成重载当取地址取得是可读可写类型的指针时程序会调用第一个默认成员函数。但是如果遇到const修饰的只读指针就只可以调用第二个默认成员函数。第二个默认成员函数可以得出const修饰的指针类型。这两个运算符一般不需要重载使用编译器生成的默认取地址的重载即可只有在特殊情况下才需要重载例如想让别人获取到指定内容。