类与对象2

类与对象2 这里我们来根据Date类的一些实际代码来说明类的知识。1首先我们来学习date的运算符重载日期加减天数前置与后置等。日期加天树我们可以类比十进制的进位逻辑用day加我们的天数如果满了这个月的最大天树我们就进位同样月到了12再进位则年加_Day x; while (_Day getmounthday(_Year, _Mounth)) { _Day- getmounthday(_Year, _Mounth); _Mounth; if (_Mounth 13) { _Mounth 1; _Year; }同样我们再跟据退位原理写出加减法逻辑_Day - x; while (_Day 1) { if (_Mounth 1) { _Mounth 12; _Year--; } else { _Mounth--; } _Day getmounthday(_Year, _Mounth);首先是日期day减然后当day小于1号则向月借用一位此时我们就要先判断月是否为边界不同于加法。以上只是逻辑代码现在我们考虑如何使用与我们可以发现用附用会减少拷贝构造所以Data operator(int x) { _Day x; while (_Day getmounthday(_Year, _Mounth)) { _Day- getmounthday(_Year, _Mounth); _Mounth; if (_Mounth 13) { _Mounth 1; _Year; } } return *this; } Data operator(int x) { Data tmp *this; tmp x; return tmp; }日期减日期由于每个月的天数没有一个固定规律不好直接去判断所以我们思考一种直接的方式去计算两个日期之间的天数我们可以先分别计算这个日期到某个日期的天数再进行相减int operator-(const Data d2) { if (_Year d2._Year _Mounth d2._Mounth _Day d2._Day) { return 0; } int day1 getdayfrom0(*this); int day2 getdayfrom0(d2); return day1 - day2; }int getdayfrom0(const Data d2) { int count1 0; for (int j 1; j d2._Year; j) { if (j % 4 0 j % 100 ! 0 || j % 400 0) { count1; } } int dayx (d2._Year - 1) * 365 count1; switch (d2._Mounth) { case 1: dayx d2._Day; break; case 2: dayx 31 d2._Day; break; case 3: dayx 28 d2._Day 31; break; case 4: dayx 28 d2._Day 31 31; break; case 5: dayx 28 d2._Day 31 31 30; break; case 6: dayx 28 d2._Day 31 30 31 31; break; case 7: dayx 28 d2._Day 31 30 31 30 31; break; case 8: dayx 31 d2._Day 28 31 30 31 30 31; break; case 9: dayx 31 d2._Day 28 31 30 31 30 31 31; break; case 10: dayx 31 d2._Day 28 31 30 31 30 31 31 30; break; case 11: dayx 31 d2._Day 28 31 30 31 30 31 31 30 31; break; case 12: dayx 31 d2._Day 28 31 30 31 30 31 31 30 31 30; break; } if ((d2._Mounth ! 2 d2._Mounth ! 1) (d2._Year % 4 0 d2._Year % 100 ! 0 || d2._Year % 400 0)) return dayx 1; else return dayx; }这里我采用1年1月1日作为计算标准。在这里我们就可以突然的发现如果日期输入错误呢所以我们需要进行检查。bool isRight() { if (_Year 1 || _Mounth 1 || _Mounth12 || _Day1 || _Daygetmounthday(_Year, _Mounth)) { return false; } return true; } Data(int year 2000, int mounth 1, int day 1) { _Day day; _Mounth mounth; _Year year; if (!isRight()) { cout 日期不合法 endl; assert(false); } cout 构造函数调用成功 endl; }2接下来我们来学习流的输入输出运算符重载流插入运算符是一个二元运算符一共有两个参数一个是我们的对象另一个我们使用cout作为参数cout是ostream类的void operator(ostream out) { cout _Year — _Mounth — _Day endl; }这是我们的初次尝试我们可以发现这个成员函数的第一个参数应该是隐藏的this指针这导致我们不能正确使用这个输入函数此时我们使用是d1cout);这是因为运算符重载的参数是从左向右一个一个对应原运算符的操作数但是此时this指针做了第一个参数所以我们选择重载为全局运算符。void operator(iostream out, const Data d)但是我们此时不能访问类的私有变量了在此我们使用友元声明后面会提到friend void operator(iostream out, const Data d);又因为我们的原是可以连续使用的使用我们将void返回改为cout的类型返回,iostream operator(iostream out, const Data d) { cout d._Year — d._Mounth — d._Day endl; return out; }cin流提取同理istream operator(istream in, Data d) { cout 请输入日期; cin d._Year d._Mounth d._Day; if (!d.isRight()) { cout 输入日期不合法 endl; in.setstate(ios::failbit); } return in; }接下来我们看这个例子const Data d1(2006,10,9); d1.Printf(); void Data::Printf() {.......};其中因为const修饰了d1但是Print的参数是Data *类型从const Data*到Data*权限被放大了所以这段代码是不能实现的。为了解决这个问题我们通常在函数的参数后面加const修饰成为const成员函数​ const Data d1(2006,10,9); d1.Printf(); void Data::Printf() const {.......}; ​这样Print对Data*和const Data*都可以使用了(权限可以缩小。3类的其他知识1类型转换在类类型进行类型转化的时候编译器会直接把这个过程优化为直接构造使用explicit可以使类型转化失效。2构造函数(1)使用列表进行进行初始化Date(int xx, int year, int month, int day) :_year(year) ,_month(month) ,_day(day) ,_ref(xx) { }在参数后面加:然后参数()里面是表达式或值初始化只能出现一次初始化列表本是一个成员定义初始化的地方每个成员都会走这个表没有在列表的成员如果有声明缺省则会使用声明缺省没有声明缺省如果是自定义类型会走默认构造没有默认构造则报错内置类型是随机值所以const类型变量没有默认构造的自定义类型引用类型这三种变量必须进行列表初始化在实际编写中我们尽量是使用列表初始化。class Data { public: ... private: ... int _Day11; int _Mounth11; int _Year11; };在声明时提供却省值这是c11中的规定这里也没有进行定义只是当初始化列表中参数内没有值时使用这里的缺省值。特别的初始化列表里的定义顺序是按照声明的顺序进行的本质上声明的顺序是内存的顺序。3static成员 1静态成员变量一定在类的外面初始化为所以的类对象共享实质是没有存放在类里面在1静态区里面类里面声明限制类域他的生命周期也是全局的。2使用static修饰成员函数这个函数没有this指针只能访问静态变量不能访问普通变量。这是一个典型的使用static修饰的教学题目。4:友元作用突破访问限定符使用在 类函数前面加friend友元函数不是成员函数是一种声明一个函数可以是多个类的友元友元是单向的不能关联友元。5内部类在类的内部在定义一个类实质是限定了被定义类的使用范围内部类本身不是真的在类的内部我们可以使用sizeof,检查内部类默认就是原类的友元。