目录
[1. 赋值运算符重载](#1. 赋值运算符重载)
[1.1 运算符重载](#1.1 运算符重载)
[1.2 赋值运算符重载](#1.2 赋值运算符重载)
[1.3 日期类的实现](#1.3 日期类的实现)
[1.4 const修饰成员变量](#1.4 const修饰成员变量)
[2. 构造函数之初始化列表](#2. 构造函数之初始化列表)
[3. 类型转换](#3. 类型转换)
[3. static成员](#3. static成员)
[4. 友元](#4. 友元)
[5. 内部类](#5. 内部类)
[6. 日期差](#6. 日期差)
1. 赋值运算符重载
1.1 运算符重载
- 当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。
- 运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。(operator加运算符构造函数名)和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。
- 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
- 如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少⼀个。
- 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。
- 不能通过连接语法中没有的符号来创建新的操作符:比如operator@。
- .* :: sizeof ?: . 注意以上5个运算符不能重载。(选择题里面常考,大家要记一下)
- 重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: int operator+(int x, int y)
- 一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如Date类重载operator-就有意义,但是重载operator+就没有意义。(一个类重载哪些操作符是看需要,看重载有没有价值和意义)
- 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。 C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,方便区分。
- 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream发到第一个形参位置就可以了,第二个形参位置当类类型对象。
cpp
#include<iostream>
using namespace std;
// 编译报错:"operator +"必须⾄少有⼀个类类型的形参
int operator+(int x, int y)
{
return x - y;
}
class A
{
public:
void func()
{
cout << "A::func()" << endl;
}
};
//将指向A类中的无参无返回值的函数指针取个别名叫做PF
typedef void(A::* PF)(); //成员函数指针类型
int main()
{
// C++规定成员函数要加&才能取到函数指针
PF pf = &A::func;
A obj;//定义ob类对象temp
// 对象调⽤成员函数指针时,使⽤.*运算符
(obj.*pf)();
return 0;
}
cpp
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//这里把成员变量公有化,是为了让全局函数operator==中的对象可以使用成员变量
// 重载为全局的⾯临对象访问私有成员变量的问题
// 有⼏种⽅法可以解决:
// 1、成员放公有
// 2、Date提供getxxx函数
// 3、友元函数
// 4、重载为成员函数(一般使用这种方法)
int _year;
int _month;
int _day;
};
//这里用const修饰是因为怕在比较相等的过程中,把对象的值给修改了
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year &&
d1._month == d2._month &&
d1._day == d2._day;
}
int main()
{
Date d1(2024, 11, 30);
Date d2(2024, 11, 31);
//运算符重载可以显示调用
operator==(d1, d2);
//直接写,转换调用,编译器会转换成operator==(d1, d2);
d1 == d2;
return 0;
}
重载成成员函数
cpp
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
bool operator==(const Date d2)
{
return _year == d2._year &&
_month == d2._month &&
_day == d2._day;
}
//前置加加
Date& operator++()
{
cout << "前置加加" << endl;
//...
return *this;
}
//后置加加
Date operator++(int)
{
Date tmp = *this;
cout << "后置加加" << endl;
//...
return tmp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 11, 30);
Date d2(2024, 11, 31);
// 运算符重载函数可以显⽰调⽤
d1.operator==(d2);
// 编译器会转换成 d1.operator==(d2);
d1 == d2;
// 编译器会转换成 d1.operator++();
++d1;
// 编译器会转换成 d1.operator++(0);
d1++;
return 0;
}
如果全局也有operator==,在调用过程中默认还是使用类里面的成员函数operator==
1.2 赋值运算符重载
赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象。
赋值运算符重载不能跟运算符重载一样,在类和全局都可以写,赋值运算符重载全局写不了
赋值运算符重载的特点:
- 赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成 const当前类类型引用,否则会传值传参会有拷贝
- 有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。
- 没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载函数。
- 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调⽤Stack的赋值运算符重载, 也不需要我们显⽰实现MyQueue的赋值运算符重载。这里还有一个小技巧,如果⼀个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。
cpp
#include<iostream>
using namespace std;
class Date
{
public:
//构造函数
Date(int year = 2024, int month = 11, int day = 30)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
//判断对象是否不相等
bool operator!=(const Date& d)
{
return !(_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;
}
// d1 = d2表达式返回的对象应该是d1,也就是*this
return *this;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 11, 30);
Date d2(d1);
Date d3 = d1;
Date d4;
d4 = d1;
//需要注意这里是拷贝构造,不是赋值
//请牢牢记住赋值运算符重载完成两个已经存在的对象之间的拷贝赋值
//而拷贝构造用一个对象拷贝初始化另一个要创建的对象
Date d5 = d1;
return 0;
}
对上述代码进行补充说明
我们不难发现,void operator=(const Date& d)无返回值也可以完成赋值操作,那为什么还要使用Date呢?
原因是:要支持连续赋值
因为返回值是void,所以连续赋值就会报错。
那我们看看连续赋值过程中的返回值是什么?
下面说的func函数只是随便举的一个函数例子
总结一下:返回对象是一个局部对象或临时对象,出了当前func函数作用域就析构销毁了,那么就不能用引用返回,用引用返回是存在风险的。
出了作用域返回对象还在没有析构,那就可以使用引用返回,减少拷贝
a、返回对象生命周期到了,会析构,传值返回
b、返回对象生命周期没到,不会析构,传引用返回
1.3 日期类的实现
有人问:函数重载和运算符重载有什么区别?
函数重载:可以让函数名相同,参数不同的函数存在
运算符重载:让自定义类型可以使用运算符,并且控制运算符的行为,增强可读性
多个运算符重载可以构造函数重载(前置加加和后置加加就是一个最好的例子)
cpp
#include <iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 2024, int month = 11, int day = 26)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//Date* const this
Date& operator=(const Date& d)
{
if (*this != d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//这里返回*this是为了可以支持连续赋值
return *this;
}
bool operator<(const Date& d) const;
bool operator<=(const Date& d)const;
bool operator>(const Date& d)const;
bool operator>=(const Date& d)const;
bool operator==(const Date& d)const;
bool operator!=(const Date& d)const;
Date& operator+=(int day);
Date operator+(int day)const;
Date& operator-=(int day);
Date operator-(int day)const;
Date& operator++();
Date operator++(int);
int operator-(const Date& d)const;
//判断输入的日期是否是违法的
bool CheckDate();
//ostream& operator<<(ostream& out);
//istream& operator>>(istream& in);
void Print()const
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int GetMonthDay(int year, int month)
{
static int arr[] = { -1,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;
}
return arr[month];
}
~Date()
{
_year = _month = _day = 0;
}
private:
int _year;
int _month;
int _day;
};
bool Date::operator<(const Date& d) const
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month < d._month)
{
return true;
}
else if (_month == d._month)
{
if (_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 (_year == d._year &&
_month == d._month &&
_day == d._day);
}
bool Date::operator!=(const Date& d)const
{
return !(*this == d);
}
//Date& Date::operator+=(int day)
//{
// return *this = *this + day;
//}
//
//Date Date::operator+(int day)
//{
// Date tmp = *this;
// tmp._day += day;
// while (tmp._day > GetMonthDay(tmp._year, tmp._month));
// {
// tmp._day -= GetMonthDay(tmp._year, tmp._month);
// tmp._month++;
// if (tmp._month == 13)
// {
// tmp._year++;
// tmp._month = 1;
// }
// }
// return tmp;
//}
Date& Date::operator+=(int day)
{
if (day < 0)
{
return *this -= -day;
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
//因为加到大于本月的天数了
//所以要减去本月已经过完的天数
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
Date Date::operator+(int day)const
{
Date tmp = *this;
tmp += day;
return tmp;
}
//Date& Date::operator-=(int day)
//{
// return *this = *this - day;
//}
//Date Date::operator-(int day)
//{
// Date tmp = *this;
// if (day < 0)
// {
// return tmp + (-day);
// }
// tmp._day -= day;
// while (tmp._day < GetMonthDay(tmp._year, tmp._day))
// {
// tmp._month--;
// if (tmp._month == 0)
// {
// tmp._month = 12;
// tmp._year--;
// }
// tmp._day += GetMonthDay(tmp._year, tmp._day);
// }
// return tmp;
//}
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day < 0)
{
_month--;
if (_month == 0)
{
_month = 12;
}
//当前月份已经减完了,所以要借上个月的天数
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day)const
{
Date tmp = *this;
tmp -= day;
return tmp;
}
Date& Date::operator++()
{
*this += 1;
return *this;
}
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
//const Date* const this
int Date::operator-(const Date& d) const
{
Date Max = *this;
Date Min = d;
int sum = 0;
int flag = 1;
// <小于返回真,大于返回0
// <就是说如果是Min大于Max的话那就要交换了
if (Max < Min)
{
Max = d;
Min = *this;
flag = -1;
}
while (Min != Max)
{
Min += 1;
sum++;
}
return flag * sum;
}
//ostream& Date::operator<<(ostream& out)
//{
// cout << _year << "/" << _month << "/" << _day << endl;
// return out;
//}
//
//istream& Date::operator>>(istream& in)
//{
// cout << "请输入年月日" << endl;
// in >> _year >> _month >> _day;
// return in;
//}
//在类外面是使用不了类里面私有的成员变量,所以我们先让其变成共有的
//等等解决方法就是有元
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "/" << d._month << "/" << d._day << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
cout << "请输入年月日" << endl;
in >> d._year >> d._month >> d._day;
if (!d.CheckDate());
return in;
}
bool Date::CheckDate()
{
if (_month > 12 || _month < 1 || _day < 1 || _day > GetMonthDay(_year, _month))
{
cout << "日期非法" << endl;
return false;
}
return true;
}
将上面重点模块进行抽丝剥茧
首先我们要先忽略掉上面有些函数后面加的const,等到下一个小章节1.4,我们再来说明
上面我们是先写了加等,然后用加来调用加等。那如果我们先写加,后面用加等来调用加,这是否是可行的呢?
首先这肯定是可行的,但同样都是实现一样的效果,先实现加后用加等来调用加的效率就会比先实现加等后用加来调用加等的效率还要低,因为多产生了2次的拷贝构造。
operator<<(ostream& out)和operator>>(istream& in)放到类里面,变成成员函数会发生什么呢?
然后将其变成全局函数也有个问题:函数对象内无法访问私有成员变量
1.4 const修饰成员变量
- 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
- const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。 const修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const Date* const this(const 修饰*this,本质改变this类型)
cpp
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 2024, int month = 11, int day = 30)
{
_year = year;
_month = month;
_day = day;
}
//本来是Date* const this
//加const就变成const Date* const this
void Print() const
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//这⾥⾮const对象也可以调⽤const成员函数是⼀种权限的缩⼩
Date d1(2024, 6, 1);
d1.Print();
const Date d2(2024, 11, 30);
d2.Print();
return 0;
}
2. 构造函数之初始化列表
- 之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有⼀种方式,就是初始化列表,初始化列表的使用方式是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
- 每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。
- 引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。
- C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。
- 尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。
- 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。
初始化列表总结:
- 无论是否显示写初始化列表,每个构造函数都有初始化列表;
- 无论是否在初始化列表显示初始化,每个成员变量都要走初始化列表初始化;
cpp
#include <iostream>
using namespace std;
class Time
{
public:
Time(int hour)
:_hour(hour)
{
cout << "Time(int hour)" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int& ret, int year = 1, int month = 1, int day = 1)
:_year(year),
_month(month),
_day(day),
_i(3),
ref(ret),
t1(3)
{}
private:
int _year;
int _month;
int _day;
const int _i = 2;//const修饰的成员变量(给了缺省值)
int& ref;//引用
Time t1;//没有默认构造的自定义类型
};
int main()
{
int x = 2;
Date d1(x, 2024, 11, 28);
return 0;
}
对上面代码的抽丝剥茧
补充一下:如果是引用传参一般都是用半缺省,引用放在最左边
总结一下:实践中尽可能使用初始化列表初始化,不方便在使用函数体初始化
cpp
#include <iostream>
using namespace std;
class Time
{
public:
Time(int hour)
:_hour(hour)
{
cout << "Time(int hour)" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int& ref)
:_month(2),
ret(ref),
ptr((int*)malloc(40)),
_t1(2)
{
//将这40个字节空间全部初始化为0
memset(ptr, 0, 40);
cout << "Date()" << endl;
}
void Print()const
{
cout << _year << "/" << _month << "/" << _day;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
const int _i = 1;
int& ret;
int* ptr;
Time _t1;
};
int main()
{
int x = 2;
Date d1(x);
return 0;
}
cpp#include <iostream> using namespace std; class A { public: A(int a) :_a1(a) , _a2(_a1) {} void Print() { cout << _a1 << " " << _a2 << endl; } private: int _a2; int _a1; }; int main() { A aa(1); aa.Print(); }
运行结果是:D
因为:成员变量在类中声明次序就是其在初始化列表中初始化顺序,与其在初始化列表中的先后次序无关
3. 类型转换
- C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
- 构造函数前⾯加explicit就不再支持隐式类型转换。
- 类类型的对象之间也可以隐式转换,需要相应的构造函数支持。
cpp
#include <iostream>
using namespace std;
class A
{
public:
A(int a)
:_a1(a)
{
cout << "class A" << endl;
}
A(int a1, int a2)
:_a1(a1),
_a2(a2)
{
cout << "A(int a1, int a2)" << endl;
}
A(const A& a)
{
_a1 = a._a1;
_a2 = a._a2;
cout << "A(const A& a)" << endl;
}
int Get()const
{
return _a1 + _a2;
}
void Print() const
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
//explicit B(const A& a)
B(const A& a)
:_b(a.Get())
{
cout << "class B" << endl;
}
private:
int _b = 0;
};
int main()
{
//构造
A a(2024);
//拷贝构造
A b = a;
//拷贝构造
A c(a);
//构造加拷贝构造
//因为A类型的单参数构造函数传的是int类型
//所以这里把3构造一个A类型的临时对象
//然后把临时对象通过拷贝构造到d对象中
//编译器遇到构造加拷贝构造,会把其优化成构造
A d = 3;
//引用的是4拷贝构造出来的临时对象
const A& ret = 4;
//逗号表达式,相对与2
A aa3 = (1, 2);
A aa4 = { 1,2 };
B b1 = aa3;
A aa5 = { 5, 6 };
B b2 = aa5;
const B& b3 = aa5;
return 0;
}
对上面代码的抽丝剥茧
类类型的对象之间也可以隐式转换,需要相应的构造函数支持。
3. static成员
- 用static修饰的成员变量,称之为静态成员变量,静态成员变量一定要在类外进行初始化。
- 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
- 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
- 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针。
- 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
- 突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。
- 静态成员也是类的成员,受public、protected、private访问限定符的限制。
- 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是给构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。
cpp
#include <iostream>
using namespace std;
class A
{
public:
A()
{
_scount++;
}
A(const A& ret)
{
_scount++;
}
~A()
{
_scount--;
}
static int GetACount()
{
return _scount;
}
private:
static int _scount;
};
//类外定义
int A:: _scount = 10;
int main()
{
cout << A::GetACount() << endl;
A a1;
A a2(a1);
a1.GetACount();
return 0;
}
上面代码的补充说明:
cpp
#include <iostream>
using namespace std;
class Sum
{
public:
Sum()
{
_ret += _i;
++_i;
}
static int GetRet()
{
return _ret;
}
private:
static int _i;
static int _ret;
};
int Sum::_i = 1;
int Sum::_ret = 0;
class Solution {
public:
int Sum_Solution(int n) {
// 变⻓数组
//定义n个Sum类型的数组就要构造n个Sum类型的对象
//因此可以在构造的时候++
Sum arr[n];
return Sum::GetRet();
}
};
4. 友元
- 友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到⼀个类的里面。
- 外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,他不是类的成员函数。
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
- 一个函数可以是多个类的友元函数。
- 友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有和保护成员。
- 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
- 友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。
- 有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元函数
cpp
#include <iostream>
using namespace std;
//前置声明,否则A的友元函数声明编译器不认识B
class B;
class A
{
friend void Func(const A& a, const B& b);
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
friend void Func(const A& a, const B& b);
private:
int _b1 = 1;
int _b2 = 2;
};
void Func(const A& a, const B& b)
{
cout << a._a1 << " " << b._b1 << endl;
}
int main()
{
A aa1;
B aa2;
Func(aa1, aa2);
}
友元类
cpp
#include <iostream>
using namespace std;
class A
{
//友元声明
friend class B;
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
void Func1(const A& aa1)
{
cout << aa1._a1 << endl;
cout << _b1 << endl;
}
void Func2(const A& aa2)
{
cout << aa2._a2 << endl;
cout << _b2 << endl;
}
private:
int _b1 = 3;
int _b2 = 4;
};
int main()
{
A a1;
B b2;
b2.Func1(a1);
b2.Func2(a1);
return 0;
}
5. 内部类
- 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
- 内部类默认是外部类的友元类。
- 内部类本质也是一种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。
cpp
#include <iostream>
using namespace std;
class A
{
private:
static int _k;
int _h = 1;
public:
class B
{
public:
void foo(const A& a)
{
//这里可以直接使用_k的原因是B是A的有元可以直接访问私有
//并且_k并不存在对象中,所以不用对象来调用也是可以的
cout << _k << endl;
//_h不可以直接写的原因是_h是A的成员变量
//虽然B中是可以访问A中私有的,但是this指针只能访问B中的成员变量
//_h不是B中的成员变量,所以只能通过A类型对象.来访问了
cout << a._h << endl;
}
};
};
int main()
{
cout << sizeof(A) << endl;
//B b这样定义是错误的,因为找不到B类
A::B b;
A aa;
b.foo(aa);
return 0;
}
6. 日期差
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <climits>
#include <iostream>
using namespace std;
class Date
{
public:
int operator-(Date& d2);
bool operator<(const Date& d2);
bool operator==(const Date& d)const;
void operator=(const Date& d);
bool operator!=(const Date& d)const;
Date& operator++();
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
bool Date::operator<(const Date& d2)
{
if (this->_year < d2._year)
{
return true;
}
else if (this->_year == d2._year)
{
if (this->_month < d2._month)
{
return true;
}
else if (this->_month == d2._month)
{
if (this->_day < d2._day)
{
return true;
}
}
}
return false;
}
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);
}
int RerDay(int year, int month)
{
static int arr[] = { -1,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;
return arr[month];
}
Date& Date::operator++()
{
this->_day++;
if (this->_day > RerDay(this->_year, this->_month))
{
this->_month++;
this->_day = 1;
if (this->_month == 13)
{
this->_year++;
this->_month = 1;
}
}
return *this;
}
void Date::operator=(const Date& d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
//*this 2024 11 1
// d 2024 11 12
int Date::operator-(Date& d2)
{
//2024 11 1
Date Max = *this;
//2024 11 12
//小的减大的得到的是负数
//2024 11 12 - 2024 11 1得到的才是正数
Date Min = d2;
int flag = 1;
int sum = 0;
if (Max < Min)
{
Max = d2;
Min = *this;
flag = -1;
}
while (Max != Min)
{
++Min;
sum++;
}
return flag * sum;
}
int main() {
int year1, month1, day1;
int year2, month2, day2;
scanf("%4d%2d%2d", &year1, &month1, &day1);
scanf("%4d%2d%2d", &year2, &month2, &day2);
//2024 11 30
Date d1(year1, month1, day1);
//2023 2 9
Date d2(year2, month2, day2);
//ret = 0假
int rer = d1 < d2;
int flag = 1;
if (rer == 0)
{
flag = -1;
}
int sum = 0;
int MaxY = year1;
int MinY = year2;
if (MinY > MaxY)
{
MinY = year1;
MaxY = year2;
}
// k = 1000
int k = MaxY - MinY;
int ret = 0;
if (rer)
{
Date d3(year1, month2, day2);
ret = d3 - d1;
}
else
{
//d1 = 2024 11 30
//d2 = 2023 2 9
//d3 = 2023 11 30
//构造一个日期
//让把最大那个年份改成最小的那个年份
Date d3(year2, month1, day1);
//计算2023 11 30号到2023 2 9号经过了多少天
ret = d3 - d2;
}
//MinY = 2023 MaxY = 2024 ,他们之间差一年
for (int i = MinY; i < MaxY; i++)
{
if (i % 4 == 0 && i % 100 != 0 || i % 400 == 0)
{
sum += 366;
}
sum += 365;
}
//差了几年算出来了,就要加上ret,ret:除去年还差几天
if (ret < 0)
{
sum += ret;
}
else
{
sum += ret + 1;
}
sum *= flag;
cout << sum << endl;
return 0;
}
当 ret < 0 时,说明在之前计算两个日期差的过程中可能由于某种原因,得到的天数差是负数。但实际上,在整个计算两个日期相隔天数的逻辑中,这里的负数天数差也是有效的,它表示了一种相对的时间间隔关系。所以在这种情况下,直接将这个负数的天数差累加到 sum 中,不需要进行额外的调整.
ret >= 0
在整个计算两个日期相隔天数的逻辑中,除了这个天数差之外,还需要考虑年份之间的间隔天数。例如,如果两个日期分别是 2024-01-01 和 2025-01-01,通过 Date 类的减法运算符重载函数得到的 ret 可能是 365(假设不考虑闰年等特殊情况),但实际上两个日期之间相隔的天数应该是 366(因为要包含两个日期当天,即从 2024-01-01 到 2025-00-31 是 365 天,再加上 2025-01-01 这一天)。所以当 ret >= 0 时,需要在累加 ret 到 sum 的时候再加 1,以准确计算出两个日期之间相隔的总天