目录
流插入和流提取
一般来说自定义类型是不支持运算符的,想支持的话就必须运算符重载
其中重载流插入的其中一个对象便是cout
cin和cout分别是istream和ostream类型的一个对象,它是在iostream那个头文件里定义的
这些都是C++已经写好在库里面的众多重载函数,所以说cout能支持内置类型
流插入<<
问题提出:
我们通常一开始会这样运算符重载<<
void operator<<(ostream& out){
out<<_year<<"年"<<_month<<"月"<<_day<<"日"<<endl;
}
因为双操作运算符,第一个参数是左操作数,第二个参数是右操作数
结果便是报错
究其原因就是在Date类中,this指针已经悄悄的将第一个参数,即左参数给占用了
所以应该变为下述所讲
但是此种方式又不适合我们平常所用的,不符合可读性
解决方案1
将此重载函数放到全局的位置,让ostream对象放到第一个位置,第二个位置才是自定义类型
但这又会出现另一个问题,就是类外面不能访问私有成员,所以又会报错
改正的方法是:将private暂时屏蔽
但是这样私有成员变得将不再安全
所以此种解决方案是不合理的
介绍另一种方法之前,先介绍一个新的东西:友元函数
友元函数(friend)
加了友元之后,便可在不是某类里面访问某类的私有成员
就相当于,声明了这个友元函数是这个类的好朋友,这个类相信它,允许它访问
解决方案2
第二种解决方案就是在类里面加上一个友元声明
friend void operator<<(ostream& out, const Date& d);
为了能够连续的输出自定义类型,即能做到cout<<d1<<d2<<endl;
所以应该将其的返回值变为 ostream!
//类里
friend ostream& operator<<(ostream& out, const Date& d);
//类外
ostream& operator<<(ostream& out,const Date& d) {
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
流提取
流提取的第二个参数便不能加const了,因为他要走到自定义类型里面去
//类里
friend istream& operator>>(istream& in, Date& d);
//类外
istream& operator>>(istream& in, Date& d) {
in >> d._year >> d._month >> d._day;
return in;
}
流本质是为了解决,自定义类型输入和输出的问题,因为普通的printf,scanf是无法解决复杂的自定义类型输入和输出的问题的
运算符重载知识小补充
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型函数
- 用于内置类型的运算符,其含义不会改变
- 作为类成员函数重载的时候,其形参看起来比操作数少1,是因为隐藏了this指针这个形参
- 有5个运算符是不能够重载的
- 去成员变量的点 .
- 域作用限定符 ::
- sizeof
- 三目运算符?:
- .* (点星)
*是可以重载的
const成员函数
在正常情况下,下述代码是没有任何问题的
Date d1(2024,7,17);
d1.print();
问题提出1
但当我们变成这样呢
const Date d1(2024,7,17);
d1.print();
出现了错误
分析
我们把print()函数拿过来综合对比
void print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
const Date d1(2024, 7, 16);
d1.print();
但this指针是隐藏的,无法将它找出来加上const
解决办法
在成员函数的后面加const,来弥补无法直接在Date* const this 的前头加const的缺点,且这是规定
void print() const{
cout << _year << "-" << _month << "-" << _day << endl;
}
const Date d1(2024, 7, 16);
d1.print();
此时的this就相当于变为 const Date* const this
非const对象也能调用,属于权限缩小
问题提出2
在问题一修正的基础上
第一个编不过,第二个又能编过
分析
解决办法
与问题一样,不给权限放大就可以了,加const,一般不会修改成员就加一下const
所以有两个原则
- 能定义成const的成员函数都应该定义成const,这样const对象和非const对象都可以调用
- 要修改成员变量的成员函数,不能定义成const
例如,+=就不能加const
Date& operator+=(int day) {
if (day < 0) {
*this -= (-day);
return *this;
}
_day += day;
while (_day > getMonthday(_year, _month)) {
_day -= getMonthday(_year, _month);
_month++;
if (_month > 12) {
_year++;
_month = 1;
}
}
return *this;
}
取地址重载(一般都不会写)
取地址重载有两个,但也是默认成员函数,不写也没有什么过多问题
Date* operator&() {
return this;
}
const Date* operator&() const {
return this;
}
再学构造函数
先来看看原本的构造函数
Date(int year = 1900, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
if (_year < 1 || _month>13 || _month<1 ||
_day>getMonthday(_year, _month)) {
assert(false);
}
}
在CPP中还给了另外一种定义构造函数的方法
初始化列表
为什么要用初始化列表
函数体无法初始化引用成员,const和自定义类型成员没有默认构造
什么是初始化列表
初始化列表是每个成员定义的地方
以前的Date类的三个私有成员变量没有在初始化列表显示定义,但是其实它也是有定义的,只是C++内置类型默认给随机值,而自定义类型会去调用它的默认构造函数
有些自定义成员想要显示定义,即,用自己的数值初始化
如下代码
class A {
public:
A(int a = 0)
:_a(a)
{
cout << "我已完成A构造" << endl;
}
private:
int _a;
};
namespace lhl {
class Date {
public:
Date(int year, int month, int day)
:_ref(year)
, _n(1)
, _aa(10)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year,_month,_day;
A _aa;
int& _ref;
const int _n;
};
}
int main() {
lhl::Date d1(2024, 7, 17);
return 0;
}
那么有了初始化列表,但是不能只用初始化列表,因为有些初始化或者检查工作,初始化列表也不能全部搞定
初始化列表的顺序
初始化列表初始的顺序,跟初始化列表的顺序无关,只跟声明的顺序有关
namespace lhl2 {
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}
void Print() {
cout << "a1的值是" << _a1 << endl;
cout << "a2的值是" << _a2 << endl;
}
private:
int _a2;
int _a1;
};
}
int main() {
lhl2::A aa(1);
aa.Print();
return 0;
}
根据上述表达,可能我们会以为a1和a2的值都是1,1
但是结果并不是
显而易见,a1为1,a2为随机值
为什么呢?
前面说过:初始化列表初始的顺序,跟初始化列表的顺序无关,只跟声明的顺序有关
这里先声明了_a2,后来才声明了_a1
所以先声明_a2,因为_a2(_a1),此时的_a1还没有被赋值,只是一个随机值,所以_a2也会变成随机值
后来声明_a1,_a1(a),a为1,所以_a1为1.
以上便是本篇博文的学习内容,如有错误,还望各位大佬指点出来,谢谢!