C++(二)类和对象上篇
1. 类与对象的概念
C语言是面向过程(功能)的语言,注重解决问题的过程、步骤;C++是面向对象的语言,注重对象之间的关系及其交互,面向对象是比面向功能更高级的开发方式,像所熟知的Java,C#,python都是面向对象的语言;其实C++最早的别名是C with classes,主要做的改进就是加入类(class)和对象(object),将现实世界类和对象映射到虚拟计算机系统,比如我们在等待外卖的时候,可以看到地图上的骑手距我们还有多远,骑手是一类对象,用户是一类对象,一个类可以实例化出很多对象,注重对象之间的关系,如下图所示。

其实类的思想是封装,我们来看一下为什么要提出封装的概念;如下图所示,在C语言中变量和函数是分离的,我们提供了接口供使用者调用,但是对于取栈顶元素这个函数,有的人可能感觉就一句代码我直接就写了,还调什么函数啊,然后就出现了下面的代码,因为不知道top究竟是指向栈顶元素还是栈顶元素的下一个位置,不知道底层实现,在这里乱用,很危险。
intmain(){ ST st;STInit(&st);int top = st.a[top];return0;}
1.1 类的定义与实例化
于是C++提出将变量和函数封装到一起,封装的思想是规范的管理;用访问限定符(public, private, protected)来限定类外对类内成员变量、成员函数的访问;一般情况下,变量是私有,用户不能访问,函数是公有,用户可以使用,在这种情况下,用户只能调用已有方法,不能随意访问成员变量(又称属性);类里面可以定义成员变量和成员函数,成员变量一般是私有,成员函数一般是公有,类的定义和使用(实例化)举例如下
//此处class也可为structclassStack{public:voidInit(int defaultCapacity=4){ _a =(int*)malloc(sizeof(int)* defaultCapacity); _capacity = defaultCapacity); _top =0;}boolEmpty(){return _top ==0;}voidPush(int x){if(_top == _capacity){ STDataType* tmp =(int*)realloc(_a,sizeof(int)* _capacity *2);if(tmp ==NULL){perror("realloc failed");return;} _a = tmp; _capacity *=2;} _a[_top++]= x;}intTop(){assert(!Empty());return _a[_top -1];}voidDestroy(){free(_a); _a =NULL; _top =0; _capacity =0;}private:int* _a;int _top;//或top_,以区分传进来的参数名int _capacity;};intmain(){ Stack st;//类名可以直接做类型 st.Init();return0;}如下图所示,当我们尝试访问类的私有成员变量时就编不过~

我们看到成员变量是上锁的

现阶段private和protected看作等价,但在继承中有不同,一般不用protected;public, private, protected的作用遇到下一个为止;class中默认私有,struct中默认公有
类一般在头文件定义,在类里定义的函数一般是内联(较长或递归就不是内联,决定权在编译器手里),类里较长的函数可以先声明,在另一个文件定义。
同时也引出类域作为整体这个概念,C/C++可以理解为{ }定义的是一个域,域会限制访问,变量搜索优先级(如果是在类里):局部域->类域->全局域,一般认为展开的命名空间和全局域等价,命名空间不展开不会主动去命名空间里找,局部域、全局域会影响生命周期。
类实例化出对象,对象才开了空间,对象才能存数据,举个例子,类好似设计图,实例化出来的对象好似根据设计图盖出来的一栋又一栋房子;不能用类访问数据,因为成员变量在类里仅仅做了声明,如下图所示

1.2 类与对象大小计算
对象大小计算,也可以用类来计算;只计算成员变量的大小,不计算成员函数的大小,因为成员函数是所有实例化出的对象都要使用的,每个对象都要存一次函数太浪费了,所以放在公共区域了,每次调用的时候编译器会自己去找;
1.2.1 有成员变量(内存对齐)
规则如下:内存对齐,变量对齐数为min{变量大小,默认对齐数(VS下为8B)},第一个变量存储位置从0开始计算,每个变量存储位置为自身对齐数的整数倍,整个类的大小为所有对齐数中最大值的整数倍。
举例如下
//VS 64xintmain(){ Stack st; cout <<"sizeof(Stack)="<<sizeof(Stack)<< endl; cout <<"sizeof(st)="<<sizeof(st)<< endl;return0;}输出结果如下
sizeof(Stack)=16sizeof(st)=16对齐数计算

存储示意图

如果不内存对齐存放,就会导致读写效率下降,性能有所损失,举例如下
//VS 64xstructA{char c;int i;};如果不内存对齐,如下图,假设机器字长是4B,每次读写32bits,那么读i这个变量需要读两次,因为不能随意读写,只能从c的位置开始读,并且两次读写都有冗余值,最后还要将两次的冗余值舍掉,将留下的部分按顺序拼接。

如果内存对齐,如下图,访问i只需访问一次即可。

本质是用空间换时间,大多数情况下其实空间是充足的,但是嵌入式场景开发、对时间性能要求没那么高的场景下也可以考虑空间换时间,此时默认对齐数调小一些即可。
1.2.2 无成员变量
无成员变量的情况下类的大小为1,起到占位的作用,不开空间,如何区分实例化的不同对象呢?
1.3 this指针

如上图所示,如果是在C中对栈进行操作的话,我们看到&st这个传参很频繁,C++就给出了this指针的概念,我们在用对象去调用类里的成员函数时不需要传对象的地址,并且是不允许我们传参!由编译器来传,this指针就是对象的指针,是形参,与普通参数一样存放在栈上,VS2022对指针进行优化,对象的地址存放在寄存器rcx中,如下图所示

举例如下
boolEmpty(){return _top ==0;//this->_top等价于_top}//编译器会对成员函数做的处理,将所有用到的成员变量_x转化为this->_xboolEmpty(Stack*this){returnthis->_top =0;}上图的操作等价于
intmain(){ Stack st; st.Init(); st.Push(1); st.Push(2); st.Push(3); st.Push(4); st.Destroy();return0;}汇编代码如下,我们可以看到橙色框框住的是成员函数的地址;在调用Push的时候用到了对象的地址。

那么就引申出来,方法不是从对象内部进行访问的,我们来看两道题
问题在于p时空指针,使用空指针是运行时错误,编译没问题,1是正常运行,2是运行崩溃,因为首先我们根据上图看到,成员函数的地址和对象地址没有必然关联,访问成员函数不是从对象内部去访问的,1中我们访问Print的时候只是去特定的地址调用Print,做了输出,自然没问题;2中在调用Print时用到了p指向对象的成员变量,这时候要解引用访问,自然崩溃。
// 1.下面程序编译运行结果是? A.编译报错 B、运行崩溃 C、正常运行classA{public:voidPrint(){ cout <<"Print()"<< endl;}private:int _a;};intmain(){ A* p =nullptr; p->Print();return0;}// 2.下面程序编译运行结果是? A.编译报错 B、运行崩溃 C、正常运行classA{public:voidPrintA(){ cout<<_a<<endl;}private:int _a;};intmain(){ A* p =nullptr; p->PrintA();return0;}小试牛刀
- 下列有关this指针使用方法的叙述正确的是( )
A.保证基类保护成员在子类中可以被访问
B.保证基类私有成员在子类中可以被访问
C.保证基类公有成员在子类中可以被访问
D.保证每个对象拥有自己的数据成员,但共享处理这些数据的代码
D
B.不可以,C在子类和类外都可以,this是对象的指针,用于区分不同对象的私有数据;A正确,但和this无关
- 下面描述错误的是( )
A.this指针是非静态成员函数的隐含形参
B.每个非静态的成员函数都有一个this指针
C.this指针是存在对象里面的
D.this指针可以为空
C
this指针只能在类的非静态成员函数内部进行使用
2. 默认成员函数
有些时候我们可能会忘了初始化和销毁,前者导致随机值,运行错误;后者可能导致内存泄漏亦或者比较繁琐,举例如下(用栈判断括号是否匹配)

于是引入默认成员函数,
2.1 定义

2.2 分类
创建对象、销毁对象不是函数是系统来完成的,局部变量存在依赖于函数栈帧,变量创建和销毁是在创建、销毁函数栈帧时由系统完成的
2.2.1 构造函数

默认成员函数中构造函数的重载和调用说明如下
classStack{public:Stack(int defaultCapacity =4){ _a =(int*)malloc(sizeof(int)* defaultCapacity); _capacity =4; _top =0;}Stack(int* a,int n){ _a = a; _capacity = n; _top =0;}...};intmain(){int a[]={1,2,3,4,5}; Stack st1;//在实例化时自动调用默认构造函数 Stack st2(a,5);//用数组a初始化栈//Stack st(); 不能这样做这样传参的问题在于无法区分函数声明return0;}没有写构造函数,系统自动生成构造函数,使用声明的缺省值对内置变量(指针属于内置类型)进行初始化,如下图所示;但是VS2022没有缺省值的情况下,系统生成的默认构造函数给出的初始化结果也是下面这样的

自定义了拷贝构造函数,没有写构造函数,VS2022不会生成构造函数
构造函数是否需要定义举例说明如下

小试牛刀
- 下列关于构造函数的描述正确的是( )
A.构造函数可以声明返回类型
B.构造函数不可以用private修饰
C.构造函数必须与类名相同
D.构造函数不能带参数
C
2.2.2 析构函数

手动释放内存举例如下
~Stack(){free(_a);//如果不进行处理,导致_a指向的堆内存 “无人认领”,既无法被程序再次使用,也不会被系统回收,最终造成内存泄漏(内存占用越来越多,直到程序结束) _a =NULL; _top =0; _capacity =0;}小试牛刀
- 在函数F中,本地变量a和b的构造函数(constructor)和析构函数(destructor)的调用顺序是: ( )
Class A; Class B;voidF(){ A a; B b;}A.b构造 a构造 a析构 b析构
B.a构造 a析构 b构造 b析构
C.b构造 a构造 b析构 a析构
D.a构造 b构造 b析构 a析构
D
按初始化顺序构造,析构根据栈后进先出,先销毁b,后销毁a
- 设已经有A,B,C,D4个类的定义,程序中A,B,C,D析构函数调用顺序为?( )
C c;intmain(){ A a; B b;static D d;return0; }A.D B A C
B.B A D C
C.C D B A
D.A B D C
B
先构建全局变量c,按顺序构建局部变量a, b, d,由于d是static,在静态区,不在栈上,先销毁b, a, 然后是d, c.
2.2.3 拷贝构造函数
是构造函数的一个重载形式
2.2.3.1 传参
传引用,如果传值,会导致无限递归,但是编译器一般都会检查,编不过

我们来分析一下为什么会导致无限递归

接着就是无休无止的Stack st(st1);
以及形参一般都是const类型,我们形参设置,如果传过来的参数只进行读,不进行写,那就一般考虑const修饰,防止一个不留神就把它改了,如下图所示

2.2.3.2 深拷贝与浅拷贝
接下来我们探讨一下浅拷贝与深拷贝,拷贝构造函数一般原则是浅拷贝,内置类型变量只拷贝值,自定义变量调用对应的拷贝构造函数
intmain(){ Stack st1; Stack st2(st1);return0;}我们不定义拷贝构造函数,使用系统生成的拷贝构造函数,此时是浅拷贝,st1._a直接赋给了st2._a,两次析构会出问题,如下图所示

最终要达到的目的是,动态开辟空间的数组内容拷贝过来,但是地址不一致,因为一方面,st2会先被析构,此时释放_a指向的空间,如果st2是浅拷贝,将st1._a赋给st2._a,那么析构st1的时候还会释放_a,访问野指针;其次,如果修改st1,也会影响st2,这不是我们想看到的;而且析构的时候,可能会将st2置为nullptr,VS2022调试监控窗口如下

我们想看到的效果,举例如下


拷贝构造如下
Stack(const Stack& st){//类内可以访问该类的私有变量 _a =(int*)malloc(sizeof(int)* st._capacity); _capacity = st._capacity; _top = st._top;memcpy(_a, st._a,sizeof(int)* st._capacity);}所以拷贝构造一般情况下如果有动态开辟内存,就自行写拷贝构造,如果没有动态开辟,而且包含自定义类型都写了符合需求的拷贝构造,就不需要写拷贝构造,编译器生成就够
因为有些自定义类型拷贝代价较大,比如上面要拷贝数组,所以在传参的时候一般考虑传引用,除非是非法行为,比如返回局部变量的引用;正确性和效率取正确性,方向错了,跑再快有什么用。
小试牛刀
- 假定MyClass为一个类,则该类的拷贝构造函数的声明语句是( )
A.MyClass(MyClass x)
B.MyClass &(MyClass x)
C.MyClass(MyClass &x)
D.MyClass(MyClass *x)
C
2.2.2.4 赋值运算符重载
2.2.2.4.1 运算符重载
运算符重载的目的是让自定义类型能够像内置类型一样去使用操作符进行操作;
自定义类型一般都无法直接使用运算符进行操作,可以理解为结构体无法直接比大小,假如我们在类内写下面的函数来比大小,但是问题是可能有命名不规范问题,写成fun1, fun2等无法很好区分,举例如下
boolless1(const Date& d1,const Date& d2){if(d1._year < d2._year)returntrue;elseif(d1._year == d2._year && d1._month < d2._month)returntrue;elseif(d1._year == d2._year && d1._month == d2._month && d1._day < d2._day)returntrue;elsereturnfalse;}boolgreater1(const Date& d1,const Date& d2){if(d1._year > d2._year)returntrue;elseif(d1._year == d2._year && d1._month > d2._month)returntrue;elseif(d1._year == d2._year && d1._month == d2._month && d1._day > d2._day)returntrue;elsereturnfalse;}提出运算符重载,运算符重载操作数等于形参数,假设我们先在全局定义,在类外操作数等于形参数(在类外访问类内private变量的问题,可以先将类内变量置为public用于测试)
注意:
- 不能通过重载构建新的运算符,只能重载
已有的操作符 - 运算符重载,如果是赋值,必须写在
类内,因为是默认成员函数,如果不写在类内,类会自动生成,和全局定义的区分不开;其它运算符重载在全局和类内都可以,但是要考虑private. - 操作数
至少有一个是自定义类型,如果都是内置类型,要改变编译器自身的操作数原则吗,不可以; - 五个不能重载:
.* sizeof :: ?: .(笔试常考查,用*做干扰选项)
booloperator<(const Date& d1,const Date& d2){if(d1._year < d2._year)returntrue;elseif(d1._year == d2._year && d1._month < d2._month)returntrue;elseif(d1._year == d2._year && d1._month == d2._month && d1._day < d2._day)returntrue;elsereturnfalse;}intmain(){ Date d1(2026,1,10); Date d2(2025,12,31);operator<(d1,d2);//等价于(d1<d2),因为<<优先级高于<,所以加括号 d1 < d2;return0;}汇编代码operator<(d1,d2)等价于d1<d2,如下图所示

但是在类内有一个隐性参数就是this指针,所以我们手动传的形参数要比操作数少一个,在类内定义
Date::Date(int year,int month,int day){ _year = year; _month = month; _day = day;}bool Date::operator<(const Date& d)const{if(_year < d._year)returntrue;elseif(_year == d._year && _month < d._month)returntrue;elseif(_year == d._year && _month == d._month &&_day< d._day)returntrue;elsereturnfalse;}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);}bool Date::operator<=(const Date& d)const{return*this< d ||*this== d;}bool Date::operator!=(const Date& d)const{return!(*this== d);}汇编代码d1.operator<(d2)等价于d1<d2,如下图所示

2.2.2.4.2 赋值运算符重载
如果是系统生成的赋值运算符重载,处理规则和拷贝构造函数有些类似,内置变量浅拷贝,自定义变量调用对应的赋值运算符重载函数;
所以赋值运算符重载一般情况下如果有动态开辟内存,就自行写赋值运算符重载,如果没有动态开辟,而且包含自定义类型都写了符合需求的赋值运算符重载,就不需要写赋值运算符重载,编译器生成就够
举例如下,下面代码的效果和系统生成的效果一致
Date&operator=(const Date& d){//注意这个地方返回的是Date&,因为有些时候可能用到连= _year = d._year; _month = d._month; _day = d._day;return*this;}intmain(){int i,j, k =1; i = j = k;//j=k,把k的值赋给j,并且这个赋值表达式的返回值是j,接着把这个返回值赋给i Date d1(2026,12,31); Date d2(2025,12,31); Date d3; d2 = d3 = d1; d1 = d2;//等价于d1.operator(&d1, d2);return0;}拷贝构造和赋值的区别,写=不一定是赋值,从定义出发
拷贝构造是用一个变量初始化另一个变量,因为拷贝构造就是构造的一种函数重载,构造就是用来初始化的;而赋值是将一个变量的值拷贝给一个已经存在的变量;
下面是拷贝构造,不是赋值
intmain(){ Date d1; Date d2; d2 = d1;//等价于Date d2(d1);return0;}2.2.2.4.3 日期实例运算符重载
2.2.2.4.3.1 日期+/-天数 日期-日期
运算符重载,以实际意义为中心,日期加日期没有意义,但日期+天数、日期-天数、日期-日期有意义;

intDate::GetMonthDay(int year,int month){staticint days[]={0,31,28,31,30,31,30,31,31,30,31,30,31};//优化1:因为后续GetMonthDay会被频繁调用,所以此处构造局部静态数组if(month ==2&&((year %4==0&& year %100!=0)|| year %400==0))//如果&&前后判断颠倒,先判闰年,结果month不是2,没意义还消耗时间,所以先判month是否为2return29;return days[month];} Date& Date::operator+=(int day){if(day <0)return*this-=-day;//综合考虑day的情况,如果d<0,就转化为-= -day;如果d>0,要处理的非法日期就是>该月份天数 _day += day;while(_day >GetMonthDay(_year, _month)){ _day -=GetMonthDay(_year, _month); _month++;if(_month ==13){ _year++; _month =1;}}return*this;}下面const修饰的是this指向的变量,因为涉及到权限的问题,如果此处没有const,如果是const Date类型的d进行+,其指针没办法赋给* this,因为*this是Date,权限放大,权限可以平移、缩小,不能放大,所以此处用const,无论是const Date还是Date都可以接收,而且符合规则,防止误改;一般不需修改的都要加const,防止权限扩大以及误改;
这个const放末尾,因为this是不可以显式传参的,只能这样做来标明
const Date* p, Date const* p,不能修改的是p内容(相当于const修饰*p,p是指针, * p就是指向的内容);Date * const p不能修改的是p本身(const修饰指针p,指针不能改)
但是this指针的类型是Date* const this,当所指向的变量也加const,this的类型就是const Date* const this
Date Date::operator+(int day)const{ Date tmp =*this; tmp += day;return tmp;} Date& Date::operator++(){//前置 ++i*this+=1;return*this;} Date Date::operator++(int){//后置 i++ Date tmp(*this);*this+=1;return tmp;} Date& Date::operator--(){//前置 --i*this-=1;return*this;} Date Date::operator--(int){//后置 i-- Date tmp(*this);*this-=1;return tmp;} Date& Date::operator-=(int day){if(day <0){return*this+=-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 tmp(*this); tmp -= day;return tmp;}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;}使用函数重载来实现相同运算符不同功能的重载,学以致用~
我们来看拷贝消耗,如下图所示,更推荐先实现+=,再复用+=实现+

日期-日期,可以先通过对对齐年,再对齐月、日;比如2023/3/1-2020/1/1,先将2020/1/1调整到2023/1/1,中间需要特殊处理的是闰年;然后计算2023/1/1到2023/3/1相差的天数,正的+,负的-;但是上面不使用这种方法,一方面上面的代码简单易读,而且这种计算在computer面前就是小case.
2.2.2.4.3.2 cout, cin重载
之前我们提到过cout是可以自动识别类型的,查C++的标准库可以看到是函数重载实现的,如下图;


现在我们考虑实现一个自定义类型的cout,如果是下面这样,每次调用是d.Print();
voidDate::Print(){ cout <<this->_year <<"-"<<this->_month <<"-"<<this->_day << endl;}
//类内声明 ostream&operator<<(ostream& out);//类外定义 ostream& Date::operator<<(ostream& out){ cout << _year <<"年"<< _month <<"月"<< _day <<"日"<< endl;return out;}intmain(){ Date d1(2023,2,4); d1 << cout;//因为默认第一个参数是this指针,所以只能这样调用return0;}定义在类内,会导致抢位置的问题,为了使自定义类型使用cout, cin如内置类型一般,需要在全局进行定义
//类内友元,从而在类外可以访问private变量friend ostream&operator<<(ostream& out,const Date& d);friend istream&operator>>(istream& in, Date& d);//全局声明 ostream&operator<<(ostream& out,const Date& d); istream&operator>>(istream& in, Date& d);//全局定义 ostream&operator<<(ostream& out,const Date& d){ cout << d._year <<"年"<< d._month <<"月"<< d._day <<"日"<< endl;return out;//返回值是out,适用于连着打印的场景,比如cout << d1 << d2; cout << d1的返回值作为d2调用的输入} istream&operator>>(istream& in, Date& d){ in >> d._year >> d._month >> d._day;return cin;}intmain(){ Date d1(2023,2,4); cout << d1;return0;}优化
//日期实例一定要注意非法日期问题,有两种情况生成一个日期,一个是初始化,一个是流插入,我们优化这两块如下Date::Date(int year,int month,int day){if(month >0&& month <=12&& day>0&& day <=GetMonthDay(year, month)){ _year = year; _month = month; _day = day;}else{ cout <<"非法日期"<< endl;assert(false);}} istream&operator>>(istream& in, Date& d){int year, month, day; in >> year >> month >> day;if(month >0&& month <=12&& day >0&& day <= d.GetMonthDay(year, month)){//类外用对象调类内函数,此处如果没有对象,要调用GetMonthDay可以加static把其改为静态函数 d._year = year; d._month = month; d._day = day;}else{ cout <<"非法日期"<< endl;assert(false);}return cin;}牛刀小试
#include<iostream>usingnamespace std;classDate{public:Date(int year,int month,int day){ _year=year; _month=month; _day=day;}voidprint(){printf("%d-%02d-%02d\n",_year,_month,_day);}intGetDay(int year,int month){int day[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};if(month==2&&(year%400==0||(year%4==0&& year%100!=0)))return29;return day[month];} Date&operator+=(int inday){ _day +=inday;while(_day>GetDay(_year, _month)){ _day-=GetDay(_year, _month); _month++;if(_month==13){ _month=1; _year++;}}return*this;}private:int _year, _month, _day;};intmain(){int m,year, month, day, inday; cin>>m;while(m--){ cin >> year >> month >> day >> inday; Date d(year,month,day); d+=inday; d.print();}}#include<iostream>usingnamespace std;classDate{public:Date(int year,int month,int day){ _year = year; _month = month; _day = day;}intGetDay(int year,int month){int day[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};if(month ==2&&(year %400==0||(year %4==0&& year %100!=0)))return29;return day[month];}intDay(){int m=1,n=0;while(m<_month){ n+=GetDay(_year, m); m++;} n+=_day;return n;}private:int _year, _month, _day;};intmain(){int year, month, day; cin >> year >> month >> day; Date d(year, month, day); cout << d.Day()<< endl;}#include<iostream>usingnamespace std;classDate{public:Date(int year,int month,int day){ _year = year; _month = month; _day = day;}intGetDay(int year,int month){int day[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};if(month ==2&&(year %400==0||(year %4==0&& year %100!=0)))return29;return day[month];} Date&operator+=(int inday){ _day += inday;while(_day >GetDay(_year, _month)){ _day -=GetDay(_year, _month); _month++;if(_month ==13){ _month =1; _year++;}}return*this;} Date&operator++(){//前置return*this+=1;}booloperator>(Date& d){if(_year > d._year)returntrue;elseif(_year == d._year && _month > d._month)returntrue;elseif(_year == d._year && _month == d._month && _day > d._day)returntrue;elsereturnfalse;}booloperator==(Date& d){return _year == d._year && _month == d._month && _day == d._day;}booloperator!=(Date& d){return!(*this== d);}intoperator-(Date d){int n =0; Date max =*this, min = d;if(d >*this){ max = d; min =*this;}while(max != min){ n++;++min;}return n +1;}private:int _year, _month, _day;}; Date GetD(int date){int year=date/10000;int month=(date/100)%100;int day=date%100; Date d(year,month,day);return d;}intmain(){int date1, date2;while(cin >> date1 >> date2){// 注意 while 处理多个 case Date d1=GetD(date1), d2=GetD(date2);if(d1>d2) cout<<(d1-d2)<<endl;else cout<<(d2-d1)<<endl;}}#include<iostream>usingnamespace std;classDate{public:Date(int year,int month,int day){ _year = year; _month = month; _day = day;}Date(int year,int inday){ _year = year;int month=1;while(inday>GetDay(year, month)){ inday-=GetDay(year, month); month++;} _month=month; _day=inday;}voidprint(){printf("%d-%02d-%02d\n", _year, _month, _day);}intGetDay(int year,int month){int day[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};if(month ==2&&(year %400==0||(year %4==0&& year %100!=0)))return29;return day[month];}private:int _year, _month, _day;};intmain(){int year, inday;while(cin >> year >> inday){ Date d(year,inday); d.print();}}静态函数的优化
//类内声明为静态staticintGetMonthDay(int year,int month); istream&operator>>(istream& in, Date& d){int year, month, day; in >> year >> month >> day;if(month >0&& month <=12&& day >0&& day <=Date::GetMonthDay(year, month)){//类外通过域作用限定符直接调 d._year = year; d._month = month; d._day = day;}else{ cout <<"非法日期"<< endl;assert(false);}return cin;}2.2.2.5/6 取地址运算符重载
普通对象和const对象取地址操作符重载,很少自己实现,系统自动生成就够用
Date* Date::operator&(){returnthis;}//第一个const,返回的指针是不可修改的const Date* Date::operator&()const{returnthis;}二者区别主要是在如果不想让获取到普通成员的指针,可以像下面这样实现
Date* Date::operator&(){returnnullptr;}
补充一下&、取地址权限的问题

小试牛刀
- 假设 AA 是一个类, AA* abc () const 是该类的一个成员函数的原型。若该函数返回 this 值,当用 x.abc ()调用该成员函数后,x 的值是( )
A.可能被改变
B.已经被改变
C. 受到函数调用的影响
D.不变
D