4.5.2 左移运算符重载
作用:可以输出自定义数据类型
问题引出:
// 定义类
class Person
{
public:
int m_A;
int m_B;
Person(int a, int b)
{
m_A = a;
m_B = b;
}
};
// 测试
void test01()
{
int a = 10;
cout << a << endl;
// 上面的操作,编译器是允许的
// 现在我们想要仿照上面的功能,实现"cout << p << endl"就能输出Person对象中的属性m_A和m_B
// 但是这样编译器就会报错
Person p(10, 20);
cout << p << endl; // 报错:没有与这些操作数匹配的 "<<" 运算符
}
以上就是左移运算符无法认识Person
类所导致了,它需要我们为Person类型进行左移运算符重载
先去掉简化问题,去掉<< endl
,即
void test01()
{
int a = 10;
cout << a << endl;
Person p(10, 20);
cout << p; // 报错:没有与这些操作数匹配的 运算符 操作数类型为: std::ostream << Person
}
首先我们通过成员函数来实现左移运算符重载,以达到最终能够使用cout << p;
的目的:
4.5.2.1 成员函数实现左移运算符重载(无法实现)
在Person
类中实现:
class Person
{
public:
int m_A;
int m_B;
Person(int a, int b)
{
m_A = a;
m_B = b;
}
/******************************************成员函数实现左移运算符重载***************************************/
// 在不知道函数名返回值类型时先写着void,后续根据应用需要再来确定具体类型
void operator<<(Person &p){}
// 如果是上述的实现方式,那么调用该成员函数时就是这样的情形:p1.operator<<(p2),其简化后调用方式就是:p1 << p2
// 这样实现的效果不是我们想要的:cout << p
/******************************************************************************************************/
};
改变实现方法?…:
class Person
{
public:
int m_A;
int m_B;
Person(int a, int b)
{
m_A = a;
m_B = b;
}
/******************************************成员函数实现左移运算符重载***************************************/
void operator<<(cout){}
// 如果是上述的实现方式,那么调用该成员函数时就是这样的情形:p1.operator<<(cout),其简化后调用方式就是:p1 << cout
// 这样实现的效果也不是我们想要的:cout << p
/******************************************************************************************************/
};
可以看到,通过成员函数来实现重载会有一个问题,就是Person
的对象会在<<
的左边,这是受限于成员函数的调用语法
(Person对象).(Person成员函数)
所导致的,由此看来:
通过成员函数来实现左移运算符重载以达到我们预期的cout << p;
语法效果是不可行的
于是我们采用全局函数来实现左移运算符重载:
4.5.2.2 全局函数实现左移运算符重载
于是我们在全局域内定义左移运算符重载的全局函数:
// 以下语法不可直接编译,只是先用于演绎实现该全局函数的框架
void operator<<(cout, p){}
// 如果是上述的实现方式,那么调用该成员函数时就是这样的情形:cout.operator<<(p),其简化后调用方式就是:cout << p
// 这样实现的效果就跟我们的预期相符合了
接下来我们就是要来完善这个全局函数的形参语法格式了,也就是他们的类型,p
的类型好说,是Person
,那么cout
的类型是???
此处需要补充一下:
关于cout
的补充知识
cout
的类型是ostream
(标准输出流类),cout
是标准输出流对象
于是上述函数的函数头void operator<<(cout, p)
中就应该写成这样:
void operator<<(ostream cout, Person p)
由于cout
在全局中只能有一个,也就是说不能有拷贝体,如果是上面这样定义的形参类型,就会导致该函数在调用时,函数局部内产生了一份cout
的拷贝体,所以此处我们应该给形参cout
加上一个&
使得其成为一个引用以使得不会产生一个cout
的拷贝体,而只是给外部传入的这个全局唯一的cout
取了个别名也叫cout
至于Person p
习惯上我们也是为了避免调用函数时产生拷贝体而影响程序的执行效率,采用引用的方式进行参数传递
于是函数头最后应该是这样子的:
void operator<<(ostream &cout, Person &p)
OK,函数头搞定接下来完善这个重载函数的函数体:
void operator<<(ostream &cout, Person &p)
{
cout << "m_A =" << p.m_A << " m_B = " << p.m_B;
}
于是整体测试代码是这样子的:
// 定义类
class Person
{
public:
int m_A;
int m_B;
Person(int a, int b)
{
m_A = a;
m_B = b;
}
};
// 实现左移运算符重载的全局函数
void operator<<(ostream &cout, Person &p)
{
cout << "m_A =" << p.m_A << " m_B = " << p.m_B;
}
// 测试
void test()
{
int a = 10;
cout << a << endl;
Person p(10, 20);
cout << p;
}
void test()
执行结果:
上述部分代码实现完毕!
但是上面的案例中我们使用的只是cout << p;
,如果我们想要实现cout << p << endl;
可不可行呢?
于是我把上述代码中第28行cout << p;
改成了cout << p << endl;
,出现了下面的情况:
这是因为<<的左边要是ostream
类型的对象(如cout
),也就是说cout << p
执行完成后要返回一个ostream
对象(这就是我们之前学到级联),所以就是说重载函数void operator<<(ostream &cout, Person &p)
的返回值类型不应该是void
,而应该是ostream
,所以我们将重载函数修改后如下:
ostream& operator<<(ostream &cout, Person &p)
{
cout << "m_A =" << p.m_A << " m_B = " << p.m_B;
return cout; // 返回cout这个ostream对象
}
此时代码cout << p << endl;
执行的过程就是:
cout << p
调用重载函数,然后返回cout
- 返回逻辑是:
&(cout << p) = cout
,即给cout
起了个别名叫cout << p
- 于是
cout << p
在函数执行完成后就相当于cout
- 此时的
cout << p << endl
就是cout << endl
于是换行功能就能正常执行了
以上即是链式变编程的思想。
此时整体测试代码如下:
// 定义类
class Person
{
public:
int m_A;
int m_B;
Person(int a, int b)
{
m_A = a;
m_B = b;
}
};
// 实现左移运算符重载的全局函数
ostream& operator<<(ostream &cout, Person &p)
{
cout << "m_A =" << p.m_A << " m_B = " << p.m_B;
return cout;
}
// 测试
void test()
{
int a = 10;
cout << a << endl;
Person p(10, 20);
cout << p << endl; // 加上"<< end"
}
代码运行结果如下:
此时当我们将29行代码去级联其他内容时也可以正常执行
cout << p << "Hello World!" << endl;
执行结果如下:
另外还有一个小细节,我们如果将重载函数体中的cout
更换其他如out
,也是没有问题的,因为cout
在函数中至始至终只是全局域中的cout
的一个别名,取什么名是无所谓的,在函数中使用这个变量名out
时,实际上使用的就是全局域中的cout
。将重载函数体中的cout
更换其他如out
后,代码如下:
ostream& operator<<(ostream &out, Person &p)
{
out << "m_A =" << p.m_A << " m_B = " << p.m_B;
return out;
}
此时执行结果不变。
考虑到实际项目开发过程中,对象的属性是私有private
的,为了使得我们这个左移运算符重载函数能够轻松访问(我们不使用对象的get()
),我们结合友元技术,使得该重载函数可以访问对象的私有属性。修改后整体测试代码如下:
// 定义类
class Person
{
friend ostream& operator<<(ostream& out, Person& p); // 结合友元技术让重载函数可以访问私有属性
private:
int m_A;
int m_B;
public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
};
// 实现左移运算符重载的全局函数
ostream& operator<<(ostream &cout, Person &p)
{
cout << "m_A =" << p.m_A << " m_B = " << p.m_B;
return cout;
}
// 测试
void test()
{
int a = 10;
cout << a << endl;
Person p(10, 20);
cout << p << endl;
}