目录
6、关于日期类的练习
6.1、Test.cpp 源文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"Date.h" //在返回过程中,使用传值返回时,一定是对的,但不一定是最高效的,当传引用和传址返回正确时,则传值返 //回效率最低,当传引用和传址返回错误时,可以说传值返回的效率最高,因为传址和传引用返回都是错误的 //,当三种返回方式均可正确使用时,传引用效率最高,传址次之,传值最低、 //Func全局函数的声明、 void Func(const Date& d); int main() { //Date d1(2022,2,2); //Date d2(2022,2,1); //cout << (d1 > d2) << endl; //cout << d1 > d2 << endl; //(d1 > d2)等价于d1.operator>(&d1,d2),该>运算符重载函数(非静态类成员函数)的返回值为1, //此时,cout << (d1 > d2) << endl;替换成了:cout << 1 << endl; //又因为流插入运算符和流提取运算符(包括两者的重载)的结合性均是从左往右,所以会先执行:cout //<<1 ,而又由于对象cout属于自定义类型的全局对象,所以此处的运算符<<需要进行<<运算符重载,而又 //因右操作数属于内置类型,所以此时库支持直接进行使用,然后该流插入运算符重载函数(非静态类成员 //函数)的返回值仍是cout,再执行:cout<<endl;即可、 //但是如果写成:cout << d1 > d2 << endl;的话,就会报错,这是因为,由于运算符的优先级不受运 //算符重载的影响,所以,此处的流插入运算符的优先级和流插入运算符重载的优先级是相同的,并且,流插 //入运算符的优先级高于>运算符的优先级,且流插入运算符重载的优先级高于>运算符重载的优先级,所以 //说,当执行代码:cout << d1 > d2 << endl;时,会先执行:<<(流插入运算符重载),再执行>(>运算符 //重载),即,先执行:cout << d1,由于这里的对象cout和d1均属于自定义类型的对象,所以此处的流提取 //运算符需要进行运算符重载,并且又因为右操作数属于自定义类型的对象,即总结的第二种类型,故需要我 //们手动显式实现全局的流插入运算符重载函数,但目前,我们并没有实现,所以会报错, //则该行代码: cout << d1 > d2 << endl; 后面的操作就不会再进行了、 //注意:,流插入运算符的优先级高于 >,<,==,!=,>=,<= 等运算符的优先级,且流插入运算符重载的优先级高于 >,<,==,!=,>=,<= 等运算符重载的优先级,具体需要查表、 /*Date d1(2022, 2, 4); Date d2(2022, 2, 5); cout << (d1 > d2) << endl;*/ //Date d1(2022, 5, 18); //Date d2 = d1 + 15; 此处调用拷贝构造函数(非静态类成员函数),由于在C++中,自定义类型的对象在传值返回或者传值传参时,会调用拷贝构造函数,故,当执行完代码: d1+15 之后,该表达式把结果 通过传值返回的形式返回到一个临时的自定义类型的对象中,并且编译器会认为该临时的对象是已经定义完毕的,此时就是使用一个已经定义好的自定义类型的对象去初始化同类型 的正在定义的对象,则会调用拷贝构造函数(非静态类成员函数),而不是调用赋值运算符重载函数(非静态类成员函数),当调试的时候,不能直接在 return ret; 时按F11,因为此时 自定义类型的对象传值返回时还会再调用一次拷贝构造函数,要注意这一点、 //Date d3; //d3 = d1 + 15; //此处调用的是赋值运算符重载函数(非静态类成员函数)、 由于在类体中并没有显式的实现拷贝构造函数和赋值运算符重载函数,故,系统会自动生成并调用,而此时,如果我们显式的在类体中分别实现一个拷贝构造函数和赋值运算符重载函数, 并且右操作数都使用传引用传参,且如果不想改变传过来的自定义类型的对象(右操作数)中的内容,则都习惯加上关键字const,如果在这两个函数的形参位置,右操作数的别名的类型前 都不加const的话,按理说是应该报错的,这是因为,上述两处代码当执行完 d1+15 后,该表达式均会把结果通过传值返回的形式返回到一个临时的自定义类型的对象中,只有传值返回时, 才会把结果返回到一个临时的对象(变量)中,而临时的对象或变量均具有常属性,如果在拷贝构造函数和赋值运算符重载函数(均是非静态类成员函数)的形参部分中的右操作数的别名的 类型前均不加关键字const的话,权限就为: 由只读变成可读可写,所以,权限放大,会报错,则我们最好在这两个函数的形参部分中右操作数的别名前面的类型的前面加上关键字const,但 在VS2013下并没有进行报错,这是由于该版本的编译器进行了优化,在VS2019下和Linux系统下就会报错、 总结:如果不想改变传过来的自定义类型的对象(右操作数)中的内容,则都习惯加上关键字const的原因是因为:可以避免误写把自定义类型的对象(右操作数)中的内容修改掉,其次就是像 上述这样的情况,即,传过来的自定义类型的对象(右操作数)具有常属性,加上关键字const的话,则可以成功编译、 //(d1 += 10)+= 20; // += 运算符以及 += 运算符重载 均是从右往左进行结合、 //Date d4(2022, 5, 18); //Date d5 = d4 + 10; //d4.Print(); //d5.Print(); //Date d5(2022, 5, 18); //Date d6 = d5 - 10; //(d5 -= 1) -= 2; //d5.Print(); //d6.Print(); //Date d7(2022, 5, 18); //d7 += 10000; // + 运算符重载复用 += 运算符重载,故只需测试 += 运算符重载即可、 //d7.Print(); //d7 -= 10000; // - 运算符重载复用 -= 运算符重载,故只需测试 -= 运算符重载即可、 //d7.Print(); //Date d8(2022, 5, 18); //Date d9=d8 - -1; //d8.Print(); //d9.Print(); 由于我们最好使用效率较高的 - 运算符重载复用 -= 运算符重载和 + 运算符重载复用 += 运算符重载 所以针对于上述day为负数的情况,只需要在 -= 运算符重载函数和 += 运算符重载函数中加上if语句判断即可、 //Date d(2022, 5, 18); //Date ret1 = ++d; //编译器会自动把 ++d 替换为: d.operator++(&d); //ret1.Print(); //d.Print(); //Date ret2=d++; //编译器会自动把 d++ 替换为: d.operator++(&d,0); 或 //d.operator++(&d,1); 这里的整型变量0或1, 是编译器自动传过去的,我们在调用后置 ++ 运算符重载函数时,仍只需要写出: d++; 即可、 //ret2.Print(); //d.Print(); //Date da(2022, 5, 18); //Date ret1 = --da; //编译器会自动把 --da 替换为: da.operator--(&da); //ret1.Print(); //da.Print(); //Date ret2=da--; //编译器会自动把 da-- 替换为: da.operator--(&da,0); 或 //da.operator--(&da,1); 这里的整型变量0或1, 是编译器自动传过去的,我们在调用后置 -- 运算符重载函数时,仍只需要写出: da--; 即可、 //ret2.Print(); //da.Print(); //Date db(2022, 5, 18); //Date dc(2020, 2, 4); //cout << (db - dc) << endl; //cout << (dc - db) << endl; Date dd(2022, 5, 18); dd.Print(); //dd.Print(&dd); 此时,自定义类型的对象dd的类型为Date,则 &dd 的类型为: Date*,而非静态类成员函数Print的形参中隐藏的this指针的类型为: Date* const this,另外, //只有当进行引用(取别名)或者使用指针(只考虑指针所指向的内容的读写权限,而不考虑指针本身的读写权限)的时候,才会考虑读写权限的问题,此时权限为: 由可读可写变为可读可写, //可以成功编译,不会报错、 Func(dd); //Date de(2022, 5, 18); //(de + 1).Print(); //(de + 1).Print(&(de+1)); 当执行代码: (de+1) 时,会调用 + 运算符重载函数,但由于该函数是传值返回,故当执行完代码: (de+1) 后,得到一个常属性的临时变量, 则 &(de+1) 的类型则为: const Date* ,如果非静态类成员函数Print的形参列表后不加上关键字const的话,则隐藏的this指针的类型为: Date* const , 那么此时就属于权限放大,编译错误,但是在VS2013和VS2019下均没有报错,这是因为VS编译器对这块进行了优化,但按理说 是错误的,在Linux系统下就会报错了、 Date df(2022, 5, 18); cout << &df << endl; //const Date* ret = &df; //不会调用拷贝构造函数,也不会调用赋值运算符重载函数、 //如果只在类体中显式的实现 const取地址 运算符重载函数的话,首先,当执行代码: &df 是可以的,属于权限缩小,但是此时当执行完 &df 后,即调用完成 const取地址 运算符重载函数后 //返回值的的类型是 const Date*,而此处的变量 ret 的类型是 Date*, 这属于权限放大,不属于类型不匹配,所以会报错、 return 0; } //Func全局函数的定义、 void Func(const Date& d) { //报错、 d.Print(); //d.Print(&d); 此时,自定义类型的对象d的类型是:const Date, 那么 &d 的类型则为: const Date* ,而非静态类成员函数Print的形参中隐藏的this指针的类型为: Date* const, //此时权限为: 由 可读不可写变为可读可写,权限放大,不可以成功编译,会报错,但是如果非要执行代码: d.Print(); 并且可以成功编译,不会报错,应该怎么办呢? //此时可以把非静态类成员函数Print的形参中隐藏的this指针的类型改为: const Date* const,这样的话,再执行代码: d.Print(); 时,权限即为: 可读不可写变为可读不可写,则可以成功编译 //但是又由于非静态类成员函数Print的形参中隐藏的this是隐藏的,我们没有办法直接在其类型前面加关键字const,这应该怎么办呢? //我们知道,在非静态类成员函数Print的形参列表中不可以显式的写出来隐藏的this指针,所以C++编译器给了一种方法,如下所示: //void Print()const 非静态类成员函数 //{ // cout << _year << "-" << _month << "-" << _day << endl; //} //上述操作就 间接 的在非静态类成员函数Print的形参列表中的第一个位置上的隐藏的this指针的类型前面加上了关键字const,此时,隐藏的this指针的类型为: const Date* const this , //此时,在Func全局函数中执行代码: d.Print(); 时,就可以成功编译,不会报错、 //注意:上述操作,即在非静态类成员函数的形参列表后面加上关键字const,编译器只会在该非静态类成员函数形参列表中第一个位置上的隐藏的this指针的类型前面加上关键字const,而不是 //在形参列表中的所有的形参的类型前面加上关键字const、 //上述操作并不代表着在所有的非静态类成员函数的形参列表的后面加上关键字const,而是,当某些非静态类成员函数的形参列表中的第一个位置上隐藏的this指针所指向的内容不需要被修改时, //即,某些非静态类成员函数的函数体中不修改类成员变量的时候才会进行上述操作、 cout << &d << endl; }
6.2、Date.cpp 源文件
#define _CRT_SECURE_NO_WARNINGS 1 //*************************************************************************************************************************************************改成内联函数错误的方法、 //#include"Date.h" < 运算符重载函数的定义、 //bool Date::operator<(const Date& d)const //指定类域、 //{ // if ((_year < d._year) // || (_year == d._year && _month < d._month) // || (_year == d._year && _month == d._month && _day < d._day)) // { // return true; // } // return false; //} // // < 运算符重载函数的定义的拓展、 bool Date::operator<(const Date& d)const //指定类域、 { if ((_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day)) { return true; } return false; } // > 运算符重载函数的定义、 //bool Date::operator>(const Date& d)const //指定类域、 //{ // 方法一: // //if ((_year > d._year) // // || (_year == d._year && _month > d._month) // // || (_year == d._year && _month == d._month && _day > d._day)) // //{ // // return true; // //} // //else // //{ // // return false; // //} // // //方法二: // return !(*this <= d); //逻辑取反、 // // 方法三(拓展): // //return d < *this; //d.operator(&d,*this); 其中,自定义类型的对象d的类型是 const Date,所以 &d 之后的类型则为:const Date* // 现在要把自定义类型的对象d的 地址 传给隐藏的this指针,而this指针的类型为: Date* const ,注意,只有引用或者是使用指针时才考虑 // 读写权限的问题,即,在此处只考虑指针所指向的内容的读写权限问题,而不考虑指针本身的读写权限问题,故,应该是由 只读 变为了 可读可写 // 权限放大,编译错误,但又由于自定义类型的对象d的类型最好写成 const Date,即,加上const,故,若想要此处成功进行编译的话,只能改变隐藏的 // this指针的类型为: const Date* const,则可成功编译,若想要在隐藏的this指针的类型前面加上const的话,可以进行如上操作,这是因为,由于 // 形参this指针是隐藏的,我们无法直接在其类型的前面加上const,具体方法如上所示、 //} // == 运算符重载函数的定义、 //bool Date::operator==(const Date& d)const //指定类域、 //{ // return (_year == d._year && _month == d._month && _day == d._day); //} // <= 运算符重载函数的定义、 //bool Date::operator<=(const Date& d)const //指定类域、 //{ // return *this < d || *this == d; //} // >= 运算符重载函数的定义、 //bool Date::operator>=(const Date& d)const //指定类域、 //{ // 方法一: // //return *this > d || *this == d; // // //方法二: // return !(*this < d); //逻辑取反、 //} // != 运算符重载函数的定义、 //bool Date::operator!=(const Date& d)const //指定类域、 //{ // return !(*this == d); //逻辑取反、 //} //*************************************************************************************************************************************************改成内联函数错误的方法、 #include"Date.h" //获取某一年某一月共有几天(非内敛函数)、 int Date::GetMonthDay(int year, int month)const //指定类域、 { assert(year >= 1 && month >= 1 && month <= 12); //由于函数 GetMonthDay 会被经常调用,则每次都会在 栈区 上开辟13个内存空间,会导致效率低下,所以在此加上static,放在静态区上,可解决问题,不加static也是可以的,但 //加上会更好,在此也不会出现线程安全的问题,多个线程同时去读取是没有问题的,多个线程同时去写才会容易出现线程安全的问题,这里是读取,所以不会出现问题,为了更加安全 //可在static前加上const,线程安全问题一般出现在,全局或者静态 的多个线程同时写的时候、 const static int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (month == 2 && isLeapYear(year)) { return 29; } else { return monthDayArray[month]; } } //构造函数(非内敛函数)、 //当构造函数在类体中的声明的形参列表中给了缺省值的话,在类体外定义的时候就不需要再写了、 Date::Date(int year, int month, int day) //指定类域、 { if (year >= 1 //默认只考虑公元以后的,公元前的不考虑、 && month <= 12 && month >= 1 && day >= 1 && day <= GetMonthDay(year, month)) { _year = year; _month = month; _day = day; } else { cout << "日期非法!" << endl; } } // + 运算符重载函数的定义(非内敛函数)、 //Date Date::operator+(int day) //指定类域、 //{ // _day += day; // while (_day > GetMonthDay(_year, _month)) // { // _day -= GetMonthDay(_year, _month); // _month++; // if (_month == 13) // { // _year++; // _month = 1; // } // } // return *this; //} //上述代码存在一定的问题,比如在main函数中执行: Date d1(2022, 5, 18); Date d2 = d1 + 15; 时,上述代码会把自定义类型的对象d1中的日期也修改掉, //所以再次优化如下所示: Date Date::operator+(int day)const //指定类域、 { Print(); //某一个类成员函数的函数体内可以调用另外一个类成员函数、 //此时, + 运算符重载函数的形参列表中隐藏的this指针,不仅仅可以指向某一个对象的类成员变量,还可以指向某一个对象的类成员函数,故这里的代码: Print(); 会被编译器自动替换为: this->Print(); //即, this->Print(this); 而此时this指针的类型,即 + 运算符重载函数的形参列表中第一个位置上隐藏的this指针的类型为: const Date* const, 如果非静态类成员函数Print的形参列表后面没有加 //关键字const的话,则非静态类成员函数Print的形参列表第一个位置上隐藏的this指针的类型为: Date* const , 那么此时就属于权限放大,则编译错误,故,const类成员函数不能调用其他的非const类成员函数, //此处的 + 运算符重载函数即为const类成员函数,而非静态类成员函数Print的形参列表后不加关键字const在此即为非const类成员函数、 方法一、 //Date ret = *this; //调用 拷贝构造函数 ,等价于 Date ret(*this); //ret._day += day; //while (ret._day > GetMonthDay(ret._year, ret._month)) //{ // ret._day -= GetMonthDay(ret._year, ret._month); // ret._month++; // if (ret._month == 13) // { // ret._year++; // ret._month = 1; // } //} //return ret; //出了作用域,局部的自定义类型的对象ret就不在了,故这里只能使用 传值 返回,但在某些情况下,此处使用传引用返回得到的结果是一样的, 这取决于编译器会不会自动对栈帧中的空间进行清除,在VS编译器下,是不会自动清除的,所以此处即使使用传引用返回得到的结果也是一样的,但其他编译器 可能会自动清除,从而导致形成由传引用返回造成的野指针的情况,故,在此我们尽量使用传值返回,不会出现错误、 //方法二、 // + 运算符重载复用 += 运算符重载、 Date tmp(*this); tmp += day; return tmp; //当使用方法二时, + 运算符重载函数按理说要作为内敛函数,但这里就不改变了、 } // += 运算符重载函数的定义(非内敛函数)、 //具有返回值,有些地方需要使用到 += 运算符重载的连续使用,但不能写成: const Date& Date::operator+=(int day); ,否则不支持 += 运算符重载的连续使用、 //例如:(d1 += 10)+= 20; Date& Date::operator+=(int day) //指定类域、 { Print(); //某一个类成员函数的函数体内可以调用另外一个类成员函数、 //此时, += 运算符重载函数的形参列表中隐藏的this指针,不仅仅可以指向某一个对象的类成员变量,还可以指向某一个对象的类成员函数,故这里的代码: Print(); 会被编译器自动替换为: this->Print(); //即, this->Print(this); 而此时this指针的类型,即 += 运算符重载函数的形参列表中第一个位置上隐藏的this指针的类型为: Date* const, 如果非静态类成员函数Print的形参列表后面加上了 //关键字const的话,则非静态类成员函数Print的形参列表第一个位置上隐藏的this指针的类型为: const Date* const , 那么此时就属于权限缩小,则编译成功,故,非const类成员函数能调用其他的const类成员函数, //此处的 += 运算符重载函数即为非const类成员函数,而非静态类成员函数Print的形参列表后加上关键字const,在此即为const类成员函数、 //方法一、 if (day < 0) { return *this -= -day; } _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month == 13) { _year++; _month = 1; } } return *this; // *this 出了该 += 运算符重载函数(非静态类成员函数)的函数体之后,生命周期并没有结束,则此时三种返回形式均可使用,但最好使用传引用返回、 方法二、 += 运算符重载复用 + 运算符重载、 //*this = *this + day; //return *this; } //总结: //1、当 + 运算符重载函数的定义(非内敛函数)使用方法一,而 += 运算符重载函数的定义(非内敛函数)是以方法二时,即,+= 运算符重载复用 + 运算符重载、 //2、当 + 运算符重载函数的定义(非内敛函数)使用方法二,而 += 运算符重载函数的定义(非内敛函数)是以方法一时,即,+ 运算符重载复用 += 运算符重载、 //那么这两种方案,那种更好呢,答案是第2种方法更好,当我们使用方法1和方法2,各自调用一次 + 运算符重载函数和 += 运算符重载函数,则会发现,方法2总共 //调用了两次拷贝构造函数,而方法1总共调用了4次拷贝构造函数,因为,使用方法2会更加合适,效率更高、 // - 运算符重载函数的定义(非内敛函数)、 Date Date::operator-(int day)const //指定类域、 { // - 运算符重载复用 -= 运算符重载、 Date tmp(*this); tmp -= day; return tmp; //此时, - 运算符重载函数按理说要作为内敛函数,但这里就不改变了、 } // -= 运算符重载函数的定义(非内敛函数)、 //具有返回值,有些地方需要使用到 -= 运算符重载的连续使用,但不能写成: const Date& Date::operator-=(int day); ,否则不支持 -= 运算符重载的连续使用、 //例如:(d1 -= 10)-= 20; // -= 运算符以及 -= 运算符重载 均是从右往左进行结合、 Date& Date::operator-=(int day) //指定类域、 { if (day < 0) { return *this += -day; } _day -= day; while (_day <= 0) { _month--; if (_month == 0) { _month = 12; _year--; } _day += GetMonthDay(_year, _month); } return *this; // *this 出了该 -= 运算符重载函数(非静态类成员函数)的函数体之后,生命周期并没有结束,则此时三种返回形式均可使用,但最好使用传引用返回、 } // - 运算符重载函数(日期减日期)的定义(非内敛函数)、 int Date::operator-(const Date&d)const { int flag = 1; Date max (*this); //调用拷贝构造函数(非静态类成员函数)、 Date min (d); //调用拷贝构造函数(非静态类成员函数)、 if (*this < d) { min = *this; //调用赋值运算符重载函数(非静态类成员函数)、 max = d; //调用赋值运算符重载函数(非静态类成员函数)、 flag = -1; } int n = 0; while (min != max) { min++; n++; } return n*flag; }
6.3、Date.h 头文件
#pragma once #include<iostream> #include<assert.h> using std::cout; using std::cin; using std::endl; //当对某一个运算符进行运算符重载时,要保证运算符重载后的操作是有意义的、 class Date { //在类体中,编译器并不是只会向上查找,而是将该类看做一个整体,从该整体中进行查找,除了类之外,编译器均默认是向上查找、 public: bool isLeapYear(int year)const //(内敛函数)、 { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } //获取某一年某一月共有几天(非内敛函数)、 int GetMonthDay(int year, int month)const; //构造函数(非内敛函数)、 Date(int year = 1, int month = 1, int day = 1); 打印输出函数(内敛函数)、 //void Print() //void Print(Date* const this) //{ // cout << _year << "-" << _month << "-" << _day << endl; //} void Print(Date* const this),当关键字const修饰变量的时候,该变量不可被改,但是可以进行初始化,如: const int a = 10; 此时是可以成功编译的,不会报错,当再执行代码: a=20; 时就会报错、 //打印输出函数(内敛函数)、 void Print()const //void Print(const Date* const this) { cout << _year << "-" << _month << "-" << _day << endl; } //*************************************************************************************************************************************************改成内联函数错误的方法、 < 运算符重载函数的声明、 //inline bool operator<(const Date& d)const; // < 运算符重载函数的声明的拓展、 inline bool operator<(const Date& d)const; > 运算符重载函数的声明、 //inline bool operator>(const Date& d)const; == 运算符重载函数的声明、 //inline bool operator==(const Date& d)const; <= 运算符重载函数的声明、 //inline bool operator<=(const Date& d)const; >= 运算符重载函数的声明、 //inline bool operator>=(const Date& d)const; != 运算符重载函数的声明、 //inline bool operator!=(const Date& d)const; //上面这七个运算符重载函数的声明(非静态类成员函数)对应的定义的函数体中的代码都比较少,那么能否将他们改成内联函数呢? //以 > 运算符重载函数为例,假设在其定义的地方不加inline,而在声明的地方加上inline的话,此时在mian函数中当调用 > 运算符重载函数时,则会 //报一个链接错误,这是因为: 内联函数的声明和定义不能分离(不同文件),由于Date.cpp源文件中包含了Date.h头文件,则在Date.cpp源文件中就会认为 // > 运算符重载函数是一个内联函数,这样的话,不管该 > 运算符重载函数的定义中代码有多少,即,不管展开不展开,编译器都不会把它的函数名放在 //Date.cpp源文件生成的 .o 文件中的符号表内,在test.cpp源文件中不存在 > 运算符重载函数的定义,故,在test.cpp源文件生成的.o文件中的符号表中也不存在 > 运算符重载函数的函数名, //而当在 test.cpp 源文件中包含头文件后,在该源文件中只有 > 运算符重载函数的声明,当在main函数中调用 > 运算符重载函数时,test.cpp源文件中 //只有 > 运算符重载函数的声明,并且不能在所有的.o文件中找到该 > 运算符重载函数的定义,所以会出现一个链接错误,但是可以成功编译,具体请见 C++入门下篇 博客、 //但是就目前而言,就想让 > 运算符重载函数成为内联函数,应该怎么办呢? 可以把该函数的声明和定义均放在类体(不分离(同一个文件中)不分开写)中, //因为,在类体中 声明和定义的类成员函数(不分开写) 均默认为内联函数,包括在类体中显式定义的构造函数,析构函数等等,这时,由于 > 运算符重载函数的声明和定义都在类体中(未分开写), //并没有分离,所以,也不会存在会出现链接错误的情况,对于其他的运算符重载函数而言也是一样、 //*************************************************************************************************************************************************改成内联函数错误的方法、 //*************************************************************************************************************************************************改成内联函数方法一、 // < 运算符重载函数的声明和定义、 bool operator<(const Date& d)const //不需要指定类域、 { if ((_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day)) { return true; } return false; } < 运算符重载函数的声明和定义的拓展、 //bool operator<(const Date& d)const //不需要指定类域、 //{ // if ((_year < d._year) // || (_year == d._year && _month < d._month) // || (_year == d._year && _month == d._month && _day < d._day)) // { // return true; // } // return false; //} // > 运算符重载函数的声明和定义、 bool operator>(const Date& d)const //不需要指定类域、 { 方法一: //if ((_year > d._year)= // || (_year == d._year && _month > d._month) // || (_year == d._year && _month == d._month && _day > d._day)) //{ // return true; //} //else //{ // return false; //} //方法二: return !(*this <= d); //逻辑取反、 方法三(拓展): //return d < *this; //d.operator(&d,*this); } // == 运算符重载函数的声明和定义、 bool operator==(const Date& d)const //不需要指定类域、 { return (_year == d._year && _month == d._month && _day == d._day); } // <= 运算符重载函数的声明和定义、 bool operator<=(const Date& d)const //不需要指定类域、 { return *this < d || *this == d; } // >= 运算符重载函数的声明和定义、 bool operator>=(const Date& d)const //不需要指定类域、 { 方法一: //return *this > d || *this == d; //方法二: return !(*this < d); //逻辑取反、 } // != 运算符重载函数的声明和定义、 bool operator!=(const Date& d)const //不需要指定类域、 { return !(*this == d); //逻辑取反、 } //*************************************************************************************************************************************************改成内联函数方法一、 //Date日期类使用不到下面两个函数(非静态类成员函数)、 拷贝构造函数、 //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; //} // + 运算符重载函数的声明(非内敛函数)、 Date operator+(int day)const; // += 运算符重载函数的声明(非内敛函数,虽然属于类成员函数,但并不是内敛函数)、 Date& operator+=(int day); // - 运算符重载函数的声明(非内敛函数)、 Date operator-(int day)const; // -= 运算符重载函数的声明(非内敛函数,虽然属于类成员函数,但并不是内敛函数)、 Date& operator-=(int day); //前置 ++ 运算符重载函数的声明和定义(内敛函数)、 Date& operator++() //不需要指定类域、 { *this += 1; return *this; } //后置 ++ 运算符重载函数的声明和定义(内敛函数)、 //如果此处的形参列表中没有这里的 int i ,则两者构不成 函数重载 ,因为在同一个作用域中,函数名和形参列表均相同,则会产生命名冲突, //C++语法规定,对于后置 ++ 运算符重载函数(非静态类成员函数)的形参列表中除了在形参列表中第一个位置上有一个隐藏的this指针外,还会 //多一个整型形参i,即: int i (编译器固定为整型,不可改变),此时两者就能够构成函数重载,从而不会产生命名冲突,注意,这里多的一个整型形参i, //并不是要求我们必须在调用后置 ++ 运算符重载函数时需要传过去一个整型的实参,而是编译器当发现我们在调用后置 ++ 运算符重载函数时会自动 //传过去一个整型的变量可能是0,也可能是1,我们不需要知道,这个整型变量没有意义、 1、 不需要指定类域、 //Date operator++(int i) //注意:不可写成全缺省:Date operator++(int i=10),这样的话,虽然能够构成函数重载,即,语法上是可以的,但当 //{ //调用的时候就会出现问题,当编译器调用前置++运算符重载函数时,就会出现歧义、 // Date ret(*this); // *this += 1; // return ret; //} //2、 //注意:实际中,通常写成如下所示: //不需要指定类域、 Date operator++(int)//此时这种形式代表着:形参不接收由实参传过来的整型变量,或者即使接收了也不会使用该整型变量,所以传过来的整型变量的值 { //到底是多少,根本就不重要、 Date ret(*this); *this += 1; return ret; } //前置 -- 运算符重载函数的声明和定义(内敛函数)、 Date& operator--() //不需要指定类域、 { *this -= 1; return *this; } //后置 -- 运算符重载函数的声明和定义(内敛函数)、 Date operator--(int) //不需要指定类域、 { Date ret(*this); *this -= 1; return ret; } // - 运算符重载函数(日期减日期)的声明(非内敛函数)、 int operator-(const Date&d)const; //日期减日期的 - 运算符重载函数与日期减天数的 - 运算符重载函数构成函数重载、 //下面这两者如果我们不在类体中显式的实现,则编译器会在类体中自动生成并调用,两者均属于默认属性的,在调用时不需要自己传参数 //这两者在大部分情况下我们都不需要在类体中显式的实现,编译器在类体中自动生成并调用的就已经足够使用,如果我们在类体中显式的实现, //则编译器就不会再在类体中自动生成了,大部分情况下我们都不需要在类体中显式的实现,除非不想让别人知道真实的地址,则可以自己在 //类体中显式的实现,并且返回空指针nullptr,或者想让别人获得指定的内容、 //1、 //如果我们不在类体中显式的实现下面这两个函数的话,当在main函数中调用 & 取地址运算符重载函数的时候,编译器会自动在类体中生成并调用一个 & 取地址运算符重载函数, //当在main函数中调用 const取地址 运算符重载函数的时候,编译器会在类体中自动生成并调用一个 const 取地址运算符重载函数, //2、 //如果只在类体中显式的实现 const 取地址运算符重载函数时,当在main函数中调用 const 取地址运算符重载函数时是可以的,属于权限不变,当在main函数中调用 & 取地址运算符重载函数时, //也会调用类体中显式实现的 const 取地址运算符重载函数,这属于权限缩小,可以成功编译,此时,编译器不会在类体中自动生成并调用一个 & 取地址运算符重载函数,因为目前来说还是有路可走的、 //我们之前所说的,在类体中不写,编译器会在类体中自动生成,这是因为,要是我们在类体中不写的话,就无路可走了,编译器才会在类体中自动生成,而此处还有路可走,所以编译器不会在类体中自动生成、 //3、 //如果只在类体中显式的实现 & 取地址运算符重载函数时,当在main函数中调用 const 取地址运算符重载函数时是可以的,此时,在类体中显式实现的 & 取地址运算符重载函数并不能被调用 //因为这属于权限放大,所以,此时编译器会自动在类体中生成并调用一个 const取地址 运算符重载函数,如果在main函数中调用 & 取地址运算符重载函数的话,则是可以调用类体中显式实现的 // & 取地址运算符重载函数,这属于权限不变,可以成功编译、 //4、 //当在类体中显式实现 & 取地址运算符重载函数和 const取地址 运算符重载函数时,在main函数中调用 const取地址 运算符重载函数和 & 取地址运算符重载函数时,他们会优先选择最合适(权限不变)的去调用、 & 取地址运算符重载函数的声明和定义、 //Date* operator&() //不需要指定类域,内敛函数、 //{ // return this; //} // const取地址 运算符重载函数的声明和定义、 const Date* operator&()const //不需要指定类域,内敛函数、 { return this; } private: int _year; int _month; int _day; }; /*************************************************************************************************************************************************改成内联函数方法二、 为了避免最初出现的链接错误,除了上述方法之外,还可以把这些运算符重载函数(非静态类成员函数)的声明放在类体中,把他们对应的定义放在头文件中的类体外, 并且需要指定类域,即保证了内联函数的声明和定义不分离(在同一个文件中),就可以了,如下所示的方法二,此时就算多个 .cpp 源文件都包头文件都是没问题的、 方法二、 //class Date //{ //public: // bool isLeapYear(int year)const //(内敛函数)、 // { // return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); // } // //获取某一年某一月共有几天(非内敛函数)、 // int GetMonthDay(int year, int month)const; // // //构造函数(非内敛函数)、 // Date(int year = 1, int month = 1, int day = 1); // // void Print()const //(内敛函数)、 // { // cout << _year << "-" << _month << "-" << _day << endl; // } // // // < 运算符重载函数的声明、 // inline bool operator<(const Date& d)const; // // < 运算符重载函数的声明的拓展、 // //inline bool operator<(const Date& d)const; // // // > 运算符重载函数的声明、 // inline bool operator>(const Date& d)const; // // // == 运算符重载函数的声明、 // inline bool operator==(const Date& d)const; // // // <= 运算符重载函数的声明、 // inline bool operator<=(const Date& d)const; // // // >= 运算符重载函数的声明、 // inline bool operator>=(const Date& d)const; // // // != 运算符重载函数的声明、 // inline bool operator!=(const Date& d)const; // //private: // int _year; // int _month; // int _day; //}; // < 运算符重载函数的定义、 //bool Date::operator<(const Date& d)const //指定类域、 //{ // if ((_year < d._year) // || (_year == d._year && _month < d._month) // || (_year == d._year && _month == d._month && _day < d._day)) // { // return true; // } // return false; //} // // < 运算符重载函数的定义的拓展、 bool Date::operator<(const Date& d)const //指定类域、 { if ((_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day)) { return true; } return false; } // > 运算符重载函数的定义、 //bool Date::operator>(const Date& d)const //指定类域、 //{ // 方法一: // //if ((_year > d._year) // // || (_year == d._year && _month > d._month) // // || (_year == d._year && _month == d._month && _day > d._day)) // //{ // // return true; // //} // //else // //{ // // return false; // //} // // //方法二: // return !(*this <= d); //逻辑取反、 // // 方法三(拓展): // //return d < *this; //d.operator(&d,*this); //} // == 运算符重载函数的定义、 //bool Date::operator==(const Date& d)const //指定类域、 //{ // return (_year == d._year && _month == d._month && _day == d._day); //} // <= 运算符重载函数的定义、 //bool Date::operator<=(const Date& d)const //指定类域、 //{ // return *this < d || *this == d; //} // >= 运算符重载函数的定义、 //bool Date::operator>=(const Date& d)const //指定类域、 //{ // 方法一: // //return *this > d || *this == d; // // //方法二: // return !(*this < d); //逻辑取反、 //} // != 运算符重载函数的定义、 //bool Date::operator!=(const Date& d)const //指定类域、 //{ // return !(*this == d); //逻辑取反、 //} // //获取某一年某一月共有几天、 // int Date::GetMonthDay(int year, int month)const // { // assert(year >= 1 && month >= 1 && month <= 12); // //由于函数 GetMonthDay 会被经常调用,则每次都会在 栈区 上开辟13个内存空间,会导致效率低下,所以在此加上static,放在静态区上,可解决问题,不加static也是可以的,但 // //加上会更好,在此也不会出现线程安全的问题,多个线程同时去读取是没有问题的,多个线程同时去写才会容易出现线程安全的问题,这里是读取,所以不会出现问题,为了更加安全 // //可在static前加上const,线程安全问题一般出现在,全局或者静态 的多个线程同时写的时候、 // const static int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // if (month == 2 && isLeapYear(year)) // { // return 29; // } // else // { // return monthDayArray[month]; // } // } 构造函数、 当构造函数在类体中的声明的形参列表中给了缺省值的话,在类体外定义的时候就不需要再写了、 //Date::Date(int year, int month, int day) //{ // if (year >= 1 //默认只考虑公元以后的,公元前的不考虑、 // && month <= 12 // && month >= 1 // && day >= 1 // && day <= GetMonthDay(year, month)) // { // _year = year; // _month = month; // _day = day; // } // else // { // cout << "日期非法!" << endl; // } /*************************************************************************************************************************************************改成内联函数方法二、
7、<<(流插入)和>>(流提取)运算符的运算符重载
对于运算符<<和>>而言,它们分别有两种含义,其一就是代表的分别是:左移运算符和右移运算符,其二代表的分别是:流插入运算符和流提取运算符,就目前而言,由于流插入运算符<<总是和cout对象一起进行使用,而cout对象又属于自定义类型的全局对象,故,要对流插入运算符进行运算符重载,同理,由于流提取运算符>>总是与cin对象一起进行使用,而cin对象又属于自定义类型的全局对象,故,要对流提取运算符进行运算符重载,在此,我们暂时不考虑流插入或流提取运算符的两个操作数均为内置类型的情况、
(此处仅以流插入运算符重载举例说明)对于流插入运算符重载而言,由于已知流插入运算符重载的其中一个操作数为自定义类型的全局 cout 对象,此时又分为两种情况:
一、另外一个操作数如果是内置类型的话,则在 C++ 标准库中的 ostream 类模板的类体中已经显式的实现了如下所示的类成员函数的函数接口(我们不能对其进行修改):
其中,形参列表中第一个形参为:ostream* const this,其类型为:ostream* const , 注意:此处的 ostream 是一个自定义的类,上图中的这些流插入运算符重载函数(非静态类成员函数)均存在于 ostream 这一个自定义类的类体中,都放在了 stream 库中,此时不需要我们手动的在ostream 类体中实现上图中这些流插入运算符重载函数(非静态类成员函数),其次,由于上图中的这些流插入运算符重载函数(非静态类成员函数)能构成函数重载,故,当另外一个操作数是内置类型的时候,操作系统可以自动识别其类型、
除此之外,还在 C++ 标准库中的 ostream 类模板的类体外的全局区域已经显式的实现了如下所示的全局函数的函数接口(我们不能对其进行修改):
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int main()
{
//1、
char arr[] = "abc\0\0\0";
//注意,当我们在""或''中写入\0时,编译器不会把他们当做是两个字符,此处均会进行转义,所以上述
//常量字符串中一共存在4个无效字符'\0',但是当我们在输入终端(黑框)里面输入\0时,此时编译器则会
//默认他们是两个字符,不会对他们进行转义,这一点要记住、
cout << strlen(arr) << endl;
cout << arr << endl;
//注意,此时arr代表的是字符数组首元素的地址,它的类型为char*类型,由于该类型为内置类型,所以
//编译器会先去C++标准库中的ostream类模板的类体中寻找与该类型完全匹配或相对而言最匹配的且已经
//显式实现的类成员函数接口,此时就找到了已经显式实现的类成员函数接口:ostream& operator<<
//(void* val);这不是完全匹配的选择,但却是在类体中相对而言最匹配的选择,此时编译器还会去C++标
//准库中的ostream类模板的类体外的全局区域寻找与该类型完全匹配或相对而言最匹配的且已经显式实
//现的全局函数的函数接口,此时就找到了已经显式实现的全局函数接口:ostream&operator<< (ostream&
//os, const char* s);这不是完全匹配的选择,但却是在类体外的全局区域中相对而言最匹配的选择,比较
//两个分别在类体内和类体外的全局区域中的相对而言最匹配的选择,得到在类体外显式实现的全局函数
//接口:ostream&operator<< (ostream& os, const char* s);更加匹配,那么此时,cout<<arr;就会被
//编译器替换为:operator<<(cout,arr);打印出来的就是字符串、
cout << (void*)arr << endl; //cout.operator<<(&cout,(void*)arr);
//若此处就想打印出来字符数组首元素的地址,可以对arr进行强制类型转换,使得这里的arr强制转换
//为其他类型的指针变量,我们最常使用的就是void*,若强制类型转为void*的话,那么编译器会先去C++标
//准库中的ostream类模板的类体中寻找与该void*类型完全匹配或相对而言最匹配的且已经显式实现的类
//成员函数的函数接口,此时找到了最合适(完全匹配)且已经显示实现的类成员函数接口:ostream&
// operator<< (void* val);此时就不会再去类体外的全局区域中寻找了,则会打印出这里的字符数组首
//元素的地址、
cout << (int*)arr << endl; //cout.operator<<(&cout,(int*)arr);
//若把这里的arr强制类型转换为其他非void*类型的指针变量,比如:int*类型等等,那么编译器会先
//去C++标准库中的ostream类模板的类体中寻找与该int*类型完全匹配或相对而言最匹配的且已经显示实
//现的类成员函数接口,此时就找到了已经显式实现的类成员函数接口:ostream& operator<< (void*
// val);但这不是完全匹配的选择,但却是在类体中相对而言最匹配的选择,所以编译器还会去C++标准库
//中的ostream类模板的类体外的全局区域寻找与该int*类型完全匹配或相对而言最匹配的且已经显式实
//现的全局函数的函数接口,但是还是没找到完全匹配的选择,所以编译器才会选择在类体内和类体外的全
//局区域中相对而言最匹配的选择即为:在类体中已经显式实现的类成员函数接口:ostream& operator<<
// (void* val);打印出这里的字符数组首元素的地址,注意,这里的void*代表无具体类型的指针变量、
//总结:当使用自定义类型的全局对象cout打印地址时,若地址的类型为char*类型,则打印出来的是
//该char*类型的地址对应的字符串,若地址的类型为除char*类型以外的其他的类型,则打印出来的就是
//地址,若已知某一个地址为char*类型,但又想打印出这个char*类型对应的地址,那么就强制类型转换为
//其他非char*类型的地址,比如void*,int*,short*类型的地址等等都可以、
//2、
char a = '\0'; //转义字符、
cout << a << endl; //空字符
cout << (int)a << endl; //0
//3、
int brr[] = { 1, 2, 3 };
cout << brr << endl;
//此处的brr代表的是int整型数组首元素的地址,即,int*类型的地址,打印出来的是int整型数组首元
//素的地址、
return 0;
}
二、
如果另外一个操作数是自定义类型的话,则需要我们自己进行显式的实现流插入运算符重载函数( 具体是在该自定义类型的操作数对应的自定义的类体中,即 Date 日期类的类体中实现还是在这个类体外面的全局区域实现,在代码中会有解释),同理,对于流提取运算符重载而言,也是类似、
cout 和 cin 实际上是属于自定义类型的全局的对象,均包含在 iostream 这个头文件中,其中,cin 属于 istream 类型的全局对象(自定义类型),而 cout,cerr,clog 都属于 ostream 类型的全局对象(自定义类型),cerr,clog 分别是用来输出错误和日志的,其次,istream(instream) 和 ostream(outstream) 均属于 stream 库、
7.1、Test.cpp 源文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"Date.h" int main() { Date d1,d2; //cout << d1; //cout.operator<<(&cout,d1); //由于对象 d1 属于自定义类型的对象,属于总结的第二种形式,故这里的<<流插入运算符需要进行运 //算符重载,当我们在Date类体中显式的实现出<<(流插入)运算符重载函数(非静态类成员函数)时,此处仍 //然会报错,原因是:当执行代码cout<<d1;时,会被编译器自动替换为:cout.operator<<(&cout,d1);而 //&cout后的类型则为:ostream*,而编译器又规定,当进行某一个运算符(双操作数)重载时,要把该进行重 //载的运算符的左操作数的地址传给隐藏的this指针,而这里的隐藏的this指针的类型为:Date* const, //但是, &cout的类型却为:ostream*,所以会报错,所以在执行代码:cout<<d1;时,就必须要把自定义类型 //的对象d1的地址传过去给隐藏的this指针才可以,所以将其改为:d1<<cout;即可成功编译,但是,对于代码: //cout<<d1;而言,是把自定义类型的对象d1插入到流cout(自定义类型的全局对象)中,如果改为: //d1<<cout;所表示的意思就变了,尽量不能这样写,即使能够成功编译,但是,只要在Date类体中显式的实 //现流插入运算符重载函数的话,就只能写成:d1<<cout;因为此时的流插入运算符重载函数属于非静态类 //成员函数,则它的形参列表中的第一个位置上一定存在一个隐藏的this指针,而该this指针的类型又和当 //前所在的类的类名有关、 //d1 << cout; //d1.operator<<(cout); //d1.operator<<(&d1,cout); cin >> d1 >>d2 ; //在黑框中,在输入没结束前,默认以回车,Tab制表符和空格进行分割,当输入要结束时,则回车字符, //Tab制表符和空格字符,均充当结束的标志,只不过此时若敲回车,则黑框直接消失,而若敲空格字符或者 //Tab制表符,则黑框不会消失,若有char类型的变量,在输入框中必须要保证为单字母才不会出错,比如A则 //不会出错,若输入的是'A'这就是三个字母,则该char类型的变量读取到'之后,该char类型的变量后面要 //是还存在非char类型的变量,此时就会读取到A和',就会读取错误,当读取错误时,则默认读到的为0,注意: //所有类型的变量包括char类型的变量,都不会读取我们在此敲入的空格,Tab制表符和回车,当float或 //者double类型的变量读取到整数时,并不会在其后面填充小数点和0,结束之后则会执行下面的代码、 //拓展、 cout << d1; //operator<<(cout,d1);调用全局的流插入运算符重载函数,在该函数的函数体内已经实现了换行,下同、 //但是,当两个.cpp文件中都包含了Date.h头文件后,再次编译就会报错说是:找到一个或多个多重定 //义的符号,这是因为,由于此时友元函数属于全局函数,不能在头文件中进行定义(可与理解成声明和定义), //否则.h头文件会在两个.cpp中进行展开,两个.cpp文件会生成两个.o文件,那么这样的话,在两个.o文件 //中的符号表中就都有该友元函数的定义(可与理解成声明和定义),那么在链接的时候,就冲突了,产生矛盾 //,故,全局函数最好不要在头文件中进行定义(可与理解成声明和定义),否则,当.h头文件在多个源文件中 //被包含时,就会出来上述重定义问题,所以最好把全局函数的定义(可与理解成声明和定义)放在.cpp文 //件中,但是全局函数的声明是可以在.h头文件中进行的,这是因为,当.h被多个源文件包含时,并不会出现 //重定义的现象,重声明是不会报错的、 //那么问题来了,为什么在类体中能够直接定义(可与理解成声明和定义)呢,就比如Date类体中类成员 //函数的定义(可与理解成声明和定义),为什么不会出现上述问题呢? //因为在类体中定义(可与理解成声明和定义)的短小的类成员函数默认是内敛函数,会进行展开,所以 //不会把这些类成员函数的函数名放进.cpp源文件生成的.o文件的符号表中,要是在类体中定义(可与理解 //成声明和定义)了的是很长(>=10)行的代码的类成员函数也会被默认成内敛函数,但不会进行展开,此时 //要记住,即使不展开,该类成员函数的函数名也不会被放进.cpp源文件生成的.o文件的符号表中,还要知道 //,在链接的时候去查找地址一般都是只有声明,没有定义(可与理解成声明和定义),所以在链接过程中会去 //查找定义(可与理解成声明和定义),一般出现在声明与定义(可与理解成声明和定义)分离(不同文件)的 //时候,但是像在类体中直接进行声明和定义的类成员函数根本就不会出现链接的时候去查找地址的情况, //因为此时既有声明也有定义(可与理解成声明和定义),当在main函数中执行代码:cout << d1;时, //在Test.cpp源文件中已经包含了头文件Date.h,而头文件中既有函数的声明也有函数的定义(可与理解成 //声明和定义),则会直接调用即可,不存在链接过程中查找地址的情况,也不会涉及到符号表,所以不会出现 //链接错误、 cout << d1 << d2; //当执行cout<<d1<<d2;时就会报错,这是因为,目前所显式实现的全局函数(<<运算符重载函数)的 //返回类型是void,不支持连续流插入操作(流插入运算符重载),流插入运算符(流插入运算符重载)的结合 //性均是从左往右,当执行代码:cout << d1 << d2;时,先执行cout<<d1从而调用全局的流插入运算符重 //载函数,该表达式的返回值继续做下一次调用流插入运算符重载的左操作数,仍是自定义类型的全局对 //象cout,故,全局的流插入运算符重载函数的返回值应该是cout,cout的类型又是ostream,所以需要再 //次进行优化、 //int i = 10; //double d = 2.2; //由于变量i和变量d均属于内置类型,所以当执行代码:cout<<i;和cout<<d;时,就是总结的第一种 //情况,他们对应的流插入运算符重载函数(非静态类成员函数)是在自定义的类ostream的类体中已经显 //式实现的,所以他们形参列表中的第一个参数即为:ostream* const this,而编译器规定,一个具有两 //个操作数的运算符进行重载时,其左操作数的地址会传给隐藏的this指针,而现在,这两句代码:cout<<i; //和cout<<d;都是把自定义类型的全局对象cout的地址传给隐藏的this指针,而&cout的类型是ostream* //,权限是由可读可写变为可读可写,所以不会报错,直接执行这两句代码即可、 return 0; }
7.2、Date.cpp 源文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"Date.h" //2(拓展)、 //<<(流插入)运算符重载函数(全局函数)、 //如果实现成全局函数的话,则在形参列表中的第一个位置上就不会再有隐藏的this指针了、 //此处的关键字const可加可不加,但最好要加上、 //void operator<<(std::ostream& out, const Date& d) //{ // out << d._year << "-" << d._month << "-" << d._day << endl; // //由于这里的d._year,d._month,d._day均属于内置类型,属于总结的第一种情况,库可以支持直接使用、 //} //但此时由于Date类中的类成员变量均是私有的,所以在类外是不可以直接进行访问的,所以还要继续 //优化,见Date类体中的友元函数的声明,在Date类体中设置函数接口的方法(方法2)不太好,这种方法和 //直接把类体中的类成员变量设为公有的方法(方法1)差距不大,比如:通过这种方法直接对类体中的类 //成员变量进行初始化的话,在某些情况下可能会出现错误,例:通过该方法直接对某一年,某一月的某一 //天进行赋值时,可能会出错,毕竟我们不太方便去检查输入的这一天是否合理,如果必须使用这种方法 //的话,就需要加上步骤来判断合理性,但是如果通过构造函数构造出来的话就不用担心这个问题,我们可 //以使用GetMonthDay函数就间接的保证了合法性,除此之外,也可以使用友元函数进行解决问题,友元函数 //也会存在上述方法2的问题,所以也要最好加上步骤来判断合理性、 //注意:友元函数的声明在类体内,而定义(可以理解成声明和定义)却在类体外,但是它不属于类成员函数( //非静态),而属于全局函数,所以友元函数形参列表的第一个位置上并不存在隐藏的this指针,并且,友元 //函数在类体内的声明,可以放在类体中任意位置、 //3(再拓展)、 //<<(流插入)运算符重载函数(全局函数)、 //此处的关键字const可加可不加,但最好要加上、 //如果实现成全局函数的话,则在形参列表中的第一个位置上就不会再有隐藏的this指针了、 //此处的传引用返回是可以的,可以理解为生命周期并未结束、 std::ostream& operator<<(std::ostream& out, const Date& d) { out << d._year << "-" << d._month << "-" << d._day << endl; //由于这里的d._year,d._month,d._day均属于内置类型,属于总结的第一种情况,库可以支持直接使用、 return out; } //上述<<(流插入)运算符重载函数(全局函数)不能写成:std::ostream& operator<<(const // std::ostream& out, const Date& d),这是因为,当执行out<<d._year时,就已经对自定义类型的对 //象out进行了改变,所以此处不能加上关键字const,同时也不能写成:const std::ostream& // operator<<(std::ostream& out, const Date& d),当在Test.cpp源文件中执行代码cout<<d1<<d2; //时,执行完毕cout<<d1后,该表达式的返回值为:out(cout),然后即为:out<<d2,此时相当于更改了<<(流 //插入)运算符重载函数(全局函数)的返回值,所以该函数返回类型的前面也不能加上关键字const、 //>>(流提取)运算符重载函数(全局函数)、 //1、 //如果实现成全局函数的话,则在形参列表中的第一个位置上就不会再有隐藏的this指针了、 //此处的传引用返回是可以的,可以理解为生命周期并未结束、 std::istream& operator>>(std::istream& in, Date& d) { in >> d._year >> d._month >> d._day ; //由于这里的d._year,d._month,d._day均属于内置类型,属于总结的第一种情况,库可以支持直接使用、 return in; } //2、 //如果实现成全局函数的话,则在形参列表中的第一个位置上就不会再有隐藏的this指针了、 //此处的传引用返回是可以的,可以理解为生命周期并未结束、 const std::istream& operator>>(const std::istream& in, Date& d) { in >> d._year >> d._month >> d._day; //由于这里的d._year,d._month,d._day均属于内置类型,属于总结的第一种情况,库可以支持直接使用、 return in; }//分析原因类似<<(流插入)运算符重载函数(全局函数)的分析、 //此时,使用友元函数进行流提取运算符重载时,也要在流提取运算符重载函数的函数体内加上步骤进行判 //断合理性、 //可以理解为cout和cin的生命周期和作用域都是整个工程、
7.3、Date.h 头文件
#pragma once #include<iostream> using std::cout; using std::cin; using std::endl; class Date { //友元函数的声明,可以放在类体中的任意位置处,友元类在后期进行阐述、 //如果在类体内声明了友元函数,那么在类体外的友元函数的定义(可以理解成声明和定义)中,就可以 //直接访问类体中私有或者保护和公有的类成员变量和类成员函数、 friend std::ostream& operator<<(std::ostream& out, const Date& d); //1、 //friend std::istream& operator>>(std::istream& in, Date& d); //2、 friend const std::istream& operator>>(const std::istream& in, Date& d); public: Date(int year=1, int month=1, int day=1) { _year = year; _month = month; _day = day; } //1、 //<<(流插入)运算符重载函数(非静态类成员函数)、 //如果实现成非静态类成员函数的话,则会在形参列表中的第一个位置上隐藏一个this指针,即: //void operator<<(Date* const this,std::ostream& out) //此处的关键字const加不加都是可以的,最好加上、 //void operator<<(const Date* const this,std::ostream& out) void operator<<(std::ostream& out)const { out << _year << "-" << _month << "-" << _day << endl; //由于这里的_year,_month,_day均属于内置类型,属于总结的第一种情况,库可以支持直接使用、 } private: int _year; int _month; int _day; };