
目录流插入和流提取流插入问题提出解决方案1友元函数friend解决方案2流提取运算符重载知识小补充const成员函数问题提出1分析解决办法问题提出2分析解决办法取地址重载一般都不会写再学构造函数初始化列表为什么要用初始化列表什么是初始化列表初始化列表的顺序流插入和流提取一般来说自定义类型是不支持运算符的想支持的话就必须运算符重载其中重载流插入的其中一个对象便是coutcin和cout分别是istream和ostream类型的一个对象它是在iostream那个头文件里定义的这些都是C已经写好在库里面的众多重载函数所以说cout能支持内置类型流插入问题提出我们通常一开始会这样运算符重载void operator(ostream out){ out_year年_month月_day日endl; }因为双操作运算符第一个参数是左操作数第二个参数是右操作数结果便是报错究其原因就是在Date类中this指针已经悄悄的将第一个参数即左参数给占用了所以应该变为下述所讲但是此种方式又不适合我们平常所用的不符合可读性解决方案1将此重载函数放到全局的位置让ostream对象放到第一个位置第二个位置才是自定义类型但这又会出现另一个问题就是类外面不能访问私有成员所以又会报错改正的方法是将private暂时屏蔽但是这样私有成员变得将不再安全所以此种解决方案是不合理的介绍另一种方法之前先介绍一个新的东西友元函数友元函数friend加了友元之后便可在不是某类里面访问某类的私有成员就相当于声明了这个友元函数是这个类的好朋友这个类相信它允许它访问解决方案2第二种解决方案就是在类里面加上一个友元声明friend void operator(ostream out, const Date d);在类里进行友元声明告诉编译器这个函数是我Date类的朋友它可以使用的私有成员即函数在类外还需要自己声明自己为了能够连续的输出自定义类型即能做到coutd1d2endl;所以应该将其的返回值变为 ostream//类里 friend ostream operator(ostream out, const Date d); //类外 ostream operator(ostream out,const Date d) { out d._year 年 d._month 月 d._day 日 endl; return out; }流提取流提取的第二个参数便不能加const了因为他要走到自定义类型里面去//类里 friend istream operator(istream in, Date d); //类外 istream operator(istream in, Date d) { in d._year d._month d._day; return in; }流本质是为了解决自定义类型输入和输出的问题因为普通的printfscanf是无法解决复杂的自定义类型输入和输出的问题的运算符重载知识小补充不能通过连接其他符号来创建新的操作符比如operator重载操作符必须有一个类类型函数用于内置类型的运算符其含义不会改变作为类成员函数重载的时候其形参看起来比操作数少1是因为隐藏了this指针这个形参有5个运算符是不能够重载的去成员变量的点.域作用限定符sizeof三目运算符.*(点星)*是可以重载的const成员函数在正常情况下下述代码是没有任何问题的Date d1(2024,7,17); d1.print();问题提出1但当我们变成这样呢const Date d1(2024,7,17); d1.print();其中d1(d1的地址就变成了 const Date*const在类型前表明的是这个指针指向的内容不可以改指针可换默认的this指针的类型是Date* const thisconst在this前表明的是这个指针本身不能改变即指针不可换内容可换出现了错误分析我们把print函数拿过来综合对比void print() { cout _year - _month - _day endl; } const Date d1(2024, 7, 16); d1.print();但this指针是隐藏的无法将它找出来加上const解决办法在成员函数的后面加const来弥补无法直接在Date* const this 的前头加const的缺点且这是规定void print() const{ cout _year - _month - _day endl; } const Date d1(2024, 7, 16); d1.print();此时的this就相当于变为const Date* const this非const对象也能调用属于权限缩小问题提出2在问题一修正的基础上第一个编不过第二个又能编过分析解决办法与问题一样不给权限放大就可以了加const一般不会修改成员就加一下const也就是说const修饰指向的内容时和非const拷贝赋值才设计权限放大和缩小的问题所以有两个原则能定义成const的成员函数都应该定义成const这样const对象和非const对象都可以调用要修改成员变量的成员函数不能定义成const例如就不能加constDate operator(int day) { if (day 0) { *this - (-day); return *this; } _day day; while (_day getMonthday(_year, _month)) { _day - getMonthday(_year, _month); _month; if (_month 12) { _year; _month 1; } } return *this; }取地址重载一般都不会写取地址重载有两个但也是默认成员函数不写也没有什么过多问题Date* operator() { return this; } const Date* operator() const { return this; }再学构造函数我们的默认构造其实不单单只是编译器默认生成的构造函数无参构造函数全缺省构造函数我们不写时编译器默认生成的构造函数都叫做默认构造函数他们三个函数有且只有一个存在不能同时存在先来看看原本的构造函数Date(int year 1900, int month 1, int day 1) { _year year; _month month; _day day; if (_year 1 || _month13 || _month1 || _daygetMonthday(_year, _month)) { assert(false); } }在CPP中还给了另外一种定义构造函数的方法初始化列表为什么要用初始化列表函数体无法初始化引用成员const和自定义类型成员没有默认构造什么是初始化列表初始化列表是每个成员定义的地方以前的Date类的三个私有成员变量没有在初始化列表显示定义但是其实它也是有定义的只是C内置类型默认给随机值而自定义类型会去调用它的默认构造函数初始化列表的使用方式以一个冒号开始接着是一个以逗号分隔的数据成员列表每个“成员变量”后面跟着一个放在括号中的初始值或表达式。没有默认构造的类类型成员变量自定义成员变量引用成员变量const成员变量必须放到初始化列表位置进行初始化显式写也行不显示写也行指的是声明给缺省值否则就会报错这三个成员的相同点都是创建及初始化初始化列表的运行逻辑尽量使用初始化列表因为那些你不在初始化列表初始化的成员也会走初始化列表如果这个成员在声明的位置给了缺省值初始化列表会用这个缺省值初始化。如果没有给缺省值对于没有显示在初始化的内置类型成员是否初始化取决于编译器比如自定义类型会调用它自己的默认构造函数没有默认构造的话就会编译报错内置类型会根据编译器自己的设定给值比如随机值但在学习过程中我对于④比较模糊C的默认构造函数有三种无参构造函数全缺省构造函数我们不写时编译器默认生成的构造函数也就是说我们怎么样都会有一个默认构造就算我们不写所以在这个情况下我应该不会说没有默认构造导致编译报错呀问题的解答什么是「真正能被调用的默认构造函数」C 里能被无参调用的构造函数才叫「默认构造函数」包括你手写的无参构造函数Date() {}你手写的全缺省构造函数Date(int year 1900, int month 1, int day 1) {}编译器自动生成的默认构造函数你没写任何构造函数时但有一个关键例外如果你手写了任何带参数的构造函数编译器就不会再自动生成默认构造函数了举个例子什么时候会真的「没有默认构造」class Date { public: // 自己手写了一个带参构造函数 Date(int year, int month, int day) { _year year; _month month; _day day; } private: int _year; int _month; int _day; };现在我写了Date(int, int, int)→ 编译器不会生成无参的Date()我也没有写无参 / 全缺省构造 → 这个类现在没有任何可以无参调用的构造函数这就是「没有默认构造函数」的情况会报错的例子当另一个类里用Date做成员且没在初始化列表里初始化它也没给它缺省值时class MyClass { private: int _a; // 内置类型 Date _date; // 自定义类型没有默认构造 }; int main() { MyClass obj; // 尝试创建对象 → 编译报错 }报错原因MyClass的构造函数编译器自动生成的会尝试初始化所有成员_a内置类型没初始化列表也没缺省值 → 随机值不报错_date自定义类型需要调用它的默认构造函数但Date类没有默认构造函数因为你写了带参构造编译器没生成编译器找不到办法初始化_date→ 直接报错所以「我们怎么样都会有一个默认构造」这句话只在一种情况成立完全没写任何构造函数→ 编译器自动生成默认构造。一旦你写了任何带参构造编译器就「罢工」了不再帮你生成默认构造。这时候如果你还想让别人能无参创建对象就必须自己写无参 / 全缺省构造否则就会出现你图里说的「没有默认构造编译报错」。有些自定义成员想要显示定义即用自己的数值初始化如下代码class A { public: A(int a 0) :_a(a) { cout 我已完成A构造 endl; } private: int _a; }; namespace lhl { class Date { public: Date(int year, int month, int day) :_ref(year) , _n(1) , _aa(10) { _year year; _month month; _day day; } private: int _year,_month,_day; A _aa; int _ref; const int _n; }; } int main() { lhl::Date d1(2024, 7, 17); return 0; }那么有了初始化列表但是不能只用初始化列表因为有些初始化或者检查工作初始化列表也不能全部搞定初始化列表的顺序初始化列表初始的顺序跟初始化列表的顺序无关只跟声明的顺序有关namespace lhl2 { class A { public: A(int a) :_a1(a) , _a2(_a1) {} void Print() { cout a1的值是 _a1 endl; cout a2的值是 _a2 endl; } private: int _a2; int _a1; }; } int main() { lhl2::A aa(1); aa.Print(); return 0; }根据上述表达可能我们会以为a1和a2的值都是11但是结果并不是显而易见a1为1a2为随机值为什么呢前面说过初始化列表初始的顺序跟初始化列表的顺序无关只跟声明的顺序有关这里先声明了_a2后来才声明了_a1所以先声明_a2因为_a2(_a1)此时的_a1还没有被赋值只是一个随机值所以_a2也会变成随机值后来声明_a1_a1(a)a为1所以_a1为1.以上便是本篇博文的学习内容如有错误还望各位大佬指点出来谢谢