3. const成员
在1.3的pass6的结尾,我们谈到了一个问题,在3.3进行分析。
在这之前我们需要了解const修饰变量的关系:
3.1 const 限定
对于const的·限定,实际上是C语言中我们就需要掌握的东西,但这里还是要重新讲解一下。:既然说到C语言中的const,就离不开指针最具代表的东西,先来看看这个:
int* const a; const int* a;
//
我们知道,const限定代表不可修改,但对于指针来说,当我们用const修饰时,是指针本身不可修改还是指针指向的内容不可修改呢?这与具体的写法有关,就比如上面两行代码,在这里我们直接记这个结论:const 在* 的前面,就代表指向的内容不可修改;const在*的后面,就代表指针不可修改。
为了便于理解,我们可以这样思考,抛开const不说,我们在int*定义变量时,变量名一定是指针变量,因此如果*在const的后面,const的定义对象就是解引用的指针,解引用的指针就是常量,因此const int*pb 就代表这pb这个指针指向的内容不可修改,也就是b不可修改,但是pb却是可以修改的;反之,就是a可以修改,而pa不可以修改。
3.2 const 修饰权限
经过上面的复习之后,接下来我们开始正式解释const成员。(仍以Date类)
对于Date类,当我们将其const之后,用Print方法却显示错误。事实上,这个提示已经很清楚了,是因为调用时产生了权限的放大。
那我们就需要观察一下Print中的参数了:
我们发现,在Print中,只有唯一的参数,也就是this指针。通过上述日期类的实现我们可以清楚地明白:this指针是不可以修改的,但this指针指向的内容是可以修改的(比如++、+=等)因此通过3.1的描述,我们知道this指针的类型是Date* const this。
而对于上述我们所定义的d2,就是this指向的内容,也就是说,不可修改的d2传到Print中变成了可修改的类型,这就是权限的放大,因此这样会产生错误。
但是我们需要解决这样的问题,就需要将Print方法中的this指针的类型从Date* const this 变成 const Date* const this,但是this是内置不显示的,无法直接进行修改。这个时候,就增加了一个语法:在函数名的后面加上const就表示this指向的内容不可修改,同时也就等同于const Date* const this,即:
void Print() const { cout << _year << "/" << _month << "/" << _day << endl; }
这就是我们在const成员中所讲的最核心的内容。
这样一来,我们就可以合理的解释上述pass6最后的原因,并能加以修改:
3.3 this指针的比较运算符重载
通过这一节的学习,我们明白pass6这里的错误也是权限放大的原因,即d > *this 会变成:d.operator>(*this),而d在这个函数中是const限定的不可修改的,传入>的运算符重载之后,就会使得权限放大,因此,我们也需要对这些比较运算符中的this指向的没人进行const的修饰,即将所有函数的后面加上const;而对于那些自己不改变的运算符重载也可以加上const修饰(+、-)
即日期类的代码的所有细节处理我们都已经优化完毕,下面就是Date类的最终的代码:
4.日期(Date)类最终代码
Date.h
#pragma once #include<iostream> using namespace std; class Date { public: friend ostream& operator<<(ostream& out, const Date& d); friend istream& operator>>(istream& in, Date& d); int GetMonthDay(int year, int month) { static int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) { return 29; } else { return monthDayArray[month]; } } Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; // 检查日期是否合法 if (!(year >= 1 && (month >= 1 && month <= 12) && (day >= 1 && day <= GetMonthDay(year, month)))) { cout << "非法日期" << endl; } } void Print() const { cout << _year << "/" << _month << "/" << _day << endl; } bool operator==(const Date& d) const; // d1 > d2 bool operator>(const Date& d) const; // d1 >= d2 bool operator>=(const Date& d) const; bool operator<=(const Date& d) const; bool operator<(const Date& d) const; bool operator!=(const Date& d) const; // d1 += 100 Date& operator+=(int day); // d1 + 100 Date operator+(int day) const; // d1 -= 100 Date& operator-=(int day); // d1 - 100 Date operator-(int day) const; // 前置 Date& operator++(); // 后置 Date operator++(int); // 前置 Date& operator--(); // 后置 Date operator--(int); //日期-日期 int operator-(const Date& d) const; //int DayCount(); //void operator<<(ostream& out); private: int _year; int _month; int _day; }; //inline ostream& operator<<(ostream& out, const Date& d) //{ // out << d._year << "年" << d._month << "月" << d._day << "日" << endl; // return out; //} inline ostream& operator<<(ostream& out, const Date& d) { out << d._year << "年" << d._month << "月" << d._day << "日" << endl; return out; } // cin >> d1 operator(cin, d1) inline istream& operator>>(istream& in, Date& d) { in >> d._year >> d._month >> d._day; return in; }
Date.cpp
#define _CRT_SECURE_NO_WARNINGS 1 #include"Date.h" bool Date::operator==(const Date& d) const { return _year == d._year && _month == d._month && _day == d._day; } // d1 > d2 bool Date::operator>(const Date& d) const { if (_year > d._year) { return true; } else if (_year == d._year && _month > d._month) { return true; } else if (_year == d._year && _month == d._month && _day > d._day) { return true; } return false; } bool Date::operator>=(const Date& d) const { return *this > d || *this == d; //这里就复用了前两个函数 } bool Date::operator<=(const Date& d) const { return !(*this > d);//复用 } bool Date::operator<(const Date& d) const { return !(*this >= d);//复用 } bool Date::operator!=(const Date& d) const { return !(*this == d);//复用 } Date& Date::operator+=(int day) { if (day < 0) { //return *this -= -day; 和下面的方式均可 return *this -= abs(day); //复用下面的 -= } _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month += 1; if (_month == 13) { ++_year; _month = 1; } } return *this; } Date Date::operator+(int day) const { Date ret(*this); //*this本身不改变,因此需要再拷贝一个对象,对其进行计算 ret += day; //复用 return ret; } Date& Date::operator-=(int day) { if (day < 0) { //return *this -= -day; return *this += abs(day);// 复用 } _day -= day; while (_day <= 0) { --_month; if (_month == 0) { --_year; _month = 12; } _day += GetMonthDay(_year, _month); } return *this; } Date Date::operator-(int day) const { Date ret(*this); ret -= day;//复用 return ret; } //前置++ Date& Date::operator++() { *this += 1;//复用 return *this; } //后置++ 多一个int参数主要是为了和前置区分,构成函数重载 Date Date::operator++(int) { Date tmp(*this); *this += 1;//复用 return tmp; } Date& Date::operator--() { *this -= 1;//复用 return *this; } // 后置 Date Date::operator--(int) { Date tmp(*this); *this -= 1;//复用 return tmp; } //int Date::DayCount() //{ // Date tmp(*this); // int day = 0; // while (tmp._year > 0) // { // while (tmp._month > 0) // { // while (tmp._day > 0) // { // day += tmp._day; // tmp._day = 0; // } // //tmp._month--; // tmp._day = GetMonthDay(tmp._year, tmp._month--); // } // tmp._year--; // tmp._month = 12; // } // // return day; //} 日期-日期 //int Date::operator-(Date& d) //{ // int day = d.DayCount(); // int Day = DayCount();//实际上是this->DayCount() , this省略了 // // return Day - day; //} int Date::operator-(const Date& d) const { Date max = *this; Date min = d; int flag = 1; if (*this < d)//这里将在下面描述顺序问题 { max = d; min = *this; flag = -1; } int n = 0;//计算相差的天数 while (min != max) { ++n; ++min; } return n * flag; } //ostream& operator<<(ostream& out, const Date& d) //{ // out << d._year << "年" << d._month << "月" << d._day << "日" << endl; // return out; //}
Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1 #include "Date.h" void TestDate1() { Date d1(2022, 10, 8); Date d3(d1); Date d4(d1); d1 -= 10000; d1.Print(); Date d2(d1); /*Date d3 = d2 - 10000; d3.Print();*/ (d2 - 10000).Print(); d2.Print(); d3 -= -10000; d3.Print(); d4 += -10000; d4.Print(); } void TestDate2() { //(++d1).Print(); // d1.operator++() //d1.Print(); //(d2++).Print(); // d1.operator++(1) //d2.Print(); Date d1(2022, 10, 8); Date d2(d1); Date d3(d1); Date d4(d1); (--d3).Print(); d3.Print(); (d4--).Print(); d4.Print(); int p = d3 - d4; cout << p << endl; /*cout <<d4.DayCount()<<endl; Date d; cout << d.DayCount()<<endl;*/ Date d5(2022, 9, 8); cout << d5 - d1 << endl; } void TestDate3() { Date d1(2022, 10, 11); Date d2(2023, 1, 20); cout << d2 - d1 << endl; } void TestDate4() { Date d1, d2; //cin >> d1; // 流提取 //cout << d1; // 流插入 编译器里面无法识别Date类,因此需要自己实现 cin >> d1 >> d2; cout << d1 << d2 << endl; } void TestDate5() { Date d1(2022, 10, 10); d1.Print(); const Date d2(2022, 10, 10); d2.Print(); } int main() { TestDate5(); int i = 1; double d = 1.11; // cout为什么能自动识别类型:实际上是函数重载 /*cout << i; cout << d;*/ return 0; }
将这个与1.0中的代码对比会发现,最终代码对细节处理的效果以及涉及的知识储备远远高于1.0中的代码。
5.取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date { public : Date* operator&() { return this; } const Date* operator&()const { return this; } private : int _year ; // 年 int _month ; // 月 int _day ; // 日 };
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
即这两个操作符重载,基本上是不会用到的,因此这里只需要知道有这个东西即可。