一、日期类的实现
#include <iostream>
using namespace std;
#include <stdbool.h>
#include <assert.h>
class Date {
public:
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
int GetMonthDay(int year, int month);
bool isLeapYear(int year);
bool operator!=(const Date& d);
bool operator==(const Date& d);
bool operator<(const Date& d);
bool operator>(const Date& d);
bool operator<=(const Date& d);
bool operator>=(const Date& d);
Date operator+(int day);
Date& operator+=(int day);
Date operator-(int day);
Date& operator-=(int day);
//前置、后置
Date& operator++();//前置
Date operator++(int x);//后置:参数x不做要求,当传参数时,编译器就会知道这是后置++;
Date& operator--();
Date operator--(int x);
int operator-(const Date& d);
private:
int _year;
int _month;
int _day;
};
bool Date::isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
int Date::GetMonthDay(int year, int month) {
assert(year > 0);
assert(month > 0 && month < 13);
int monthDay[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && isLeapYear(year)) {
return 29;
}
else {
return monthDay[month];
}
}
bool Date::operator<(const Date& d) {
return _year < d._year
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day);
}
bool Date::operator==(const Date& d) {
return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator<=(const Date& d) {
return *this < d || *this == d;
}
bool Date::operator!=(const Date& d) {
return !(*this == d);
}
bool Date::operator>(const Date& d) {
return !(*this <= d);
}
bool Date::operator>=(const Date& d) {
return !(*this < d);
}
Date Date::operator+(int day) {
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;
}
Date& Date::operator+=(int day) {
(*this)._day += day;
while ((*this)._day > GetMonthDay((*this)._year, (*this)._month)) {
(*this)._day -= GetMonthDay((*this)._year, (*this)._month);
(*this)._month++;
if ((*this)._month == 13) {
(*this)._year++;
(*this)._month = 1;
}
}
return *this;
}
Date Date::operator-(int day) {
Date d = (*this);
d._day -= day;
while (day <= 0) {
d._month--;
if (d._month == 0) {
d._year--;
d._month = 0;
}
d._day = GetMonthDay(d._year, d._month);
}
return d;
}
Date& Date::operator-=(int day) {
_day -= day;
while (_day <= 0) {
_month--;
if (_month == 0) {
_year--;
_month = 1;
}
_day += GetMonthDay(_year, _day);
}
return *this;
}
Date& Date::operator++() {
*this += 1;
return *this;
}
//这里的形参可以不写:
Date Date::operator++(int) {
Date tmp(*this);
*this += 1;
return tmp;
}
//前置减减:
Date& Date::operator--() {
(*this) -= 1;
return *this;
}
//后置减减:先返回再使用
Date Date::operator--(int) {
Date d(*this);
(*this) -= 1;
return d;
}
int Date::operator-(const Date& d) {
Date max = *this;
Date min = d;
if (max < min) {
max = d;
min = *this;
}
int day = 0;
while (min<max) {
min++;
day++;
}
return day;
}
二、const成员
如上图,
- 在调用类对象成员函数时会将d1的地址传给Print()函数中隐藏的this指针,其类型是Date*
- 对于Func函数中const修饰的形参,其成员变量也是const修饰的,其类型是const Date*
当在const修饰的Print函数内部调用无const修饰的operator==函数时,此时编译器就会报错,这是因为operator==(d1) ==> const (*this).operator==(d),而Print修饰的函数内部成员变量是const修饰的,而operator==修饰的函数是无const函数修饰的,这里就是“权限的放大”,因此,只要不改变成员变量,都可以加上const。
- const对象/函数不可调用非const修饰的函数;
- 非const对象/函数可以调用const修饰的函数。
三、友元函数
在编译代码时可以发现对于内置类型变量可以直接使用流插入函数;对于自定义类型变量则无法使用:
如上图,对于内置类型a,b来说,cout<<a 本质就是 cout.operator<<(a);cout.operator<<(b),函数已经完成内置类型的实现方法;但对于自定义类型来说cout.operator(d1)就会报错,这是因为类型不匹配,成员函数默认隐藏的this指针会将cout视为this指针,自然就会报错,因此想办法将d1传给this指针。
void operator<<(std::ostream& out) { out << _year << "-" << _month << "-" << _day << endl; }
如上图,此时d1就会传给this指针。
但是将cout流插入到d1中会发现程序通过,并且结果是正确的,这里的d1<<cout就相当于d1.operator<<(cout),其结果也就与上述一致。
但本应该是d1插入到流中(cout<<d1),现在却是流插入到d1中,如何解决呢???
将流插入函数方法拿到类对象外部实现,并且在类对象里面命名成友元函数;
如上图,将operator<<函数拿到类外部实现,并且,为了能够在类外部读取到类对象的内部成员变量还需要在类中声明operator<<是友元函数,此时就可以在类外部访问类的成员变量。
如何实现连续流插入???
实现连续流插入就是cout<<d1<<d2;代码会首先执行cout<<d1,然后将其返回值流插入d2;但是对于上述代码实现并没有返回值,因此做出如下改进:
如上图所示,对流插入函数添加返回值类型为std::ostream&,这样第一次流插入的返回值就会返回给第二次流插入。
同样对于流提取来说,实现方法与流插入相同:
友元函数:
- 友元函数可以访问类的私有和保护成员,但不是类的成员函数;
- 友元函数不能用const修饰;
- 友元函数可以在类中任意位置声明;
- 友元函数不是成员函数,因此没有this指针。
友元类:
- 友元类中的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类的非公有成员;
- 友元类关系是单向的且友元关系不能传递;
- 友元关系不能继承。
如上图,在B是A的友元类,此时通过B内部的A类类型成员_aa就可以访问到A类内部的成员变量及函数。
四、初始化列表
- 类中的每个成员变量只能在初始化列表中出现一次;
- 适用范围:引用类型变量、const修饰变量、无默认构造函数的自定义类型成员变量;
- 对于自定义类型成员变量,编译器会优先使用初始化列表,然后再是默认构造函数;
- 成员变量在类中的声明顺序就是其在初始化列表的初始化顺序,与其在初始化列表中的顺序无关;
如上图,初始化列表初始化与函数内部区别就在于初始化列表初始化是对类对象内部成员变量的初始化,对于某些变量只能在定义的时候进行初始化(例如_b,_c),对于_a这种类型变量既可以在初始化列表中初始化,也可以在函数内部初始化。
如上图,当在P类中引入自定义类型A _aa且A没有默认构造函数时,此时就会报错,要么对A实现默认构造函数,要么在初始化列表对其初始化,如下图:
五、explicit关键字
如上图,使用常量直接对类对象v2进行初始化,可以发现编译器并没有报错,其本质是一种隐式类型转换,编译器自身用20构造一个对象并将该对象赋值给v2。
当给构造函数加上explicit修饰时,此时编译器就会报错,此时就无法进行隐式类型转换。
六、static成员
如上图,static修饰的变量在类中声明,在类外部定义;
- 静态成员是所有类共享的,不属于某个类,存储在静态区;
- 静态成员变量在类外定义,类中只是声明;
- 静态成员没有隐藏的this指针,因此不能访问非静态成员;
- 静态成员也受public的等关键字的影响。
当静态成员变量被private修饰时,可以定义函数获得:
如上图,定义函数获得静态成员变量。
七、内部类
一个类定义在另一个类的内部就叫内部类,内部类独立于其他类,无法通过外部类访问内部类的成员。并且,内部类是外部类的友元类,内部类可以通过外部类的对象参数对外部类成员进行访问,但外部类不是内部类的友元类。
如上图,在计算包含内部类的类A时,sizeof(A)只计算A中变量的大小且不包含静态成员变量_k,因为_k在静态区,所有类对象都可以访问。并且这里也不包含内部类B的大小。