C++允许在自己的类中,或是在全局作用域中重定义运算符的含义。由于很多面向对象的语言没有提供这种能力,因此你可能会低估这种特性在C++中的作用。C++中运算符的概念十分广泛,甚至包含[](数组索引)、()(函数调用)、类型转换以及内存分配和释放例程。可以通过运算符重载来改变语言运算符对自定义类的行为。能让自己的类具有内建类型的类似行为,甚至可以编写看上去类似于数组、函数或指针的类。在博主的《C++运算符重载》系列博文中会对我们常用的运算符提供重载的实例,希望大家能有所收获。额,本篇博文就让我们一起来探讨一下重载运算符中的流插入运算符和流提取运算符重载吧。
在C++中,不仅算术操作需要使用运算符,从流中读写数据都可以使用运算符。流操作运算符有“<<”(流插入运算符)和“>>”(流提取运算符)。
由于不能向istream类和ostream类添加方法,因此应该将流插入运算符和流提取运算符重载为全局函数。将流插入运算符的第一个参数设置为ostream的引用(不要加const),这个运算符就能够应用于文件输出流、字符串输出流、cout、cerr和clog等。与此类似,将流提取运算符的第一个参数设置为istream的引用(不要加const),这个运算符就能够应用于文件输入流、字符串输入流和cin。因为流插入和流提取都会改变流的状态,所有operator <<(或operator >>)的第一个参数是ostream(或istream)的非const引用。
operator<<(或operator >>)的第二个参数是对要写入(或读取)的对象的引用。流插入运算符不会修改写入的对象,因此这个引用可以是const。然而流提取运算符会修改对象,因此要求这个参数是非const引用。两个流运算符返回的都是第一个参数传入的流对象的引用,所以这个运算符可以嵌套。说了那么多的概念,可能有的小伙伴已经晕了。那咱们来简单的封装一个复数类,用流运算符重载完成输入和输出。
//Complex.h
#pragmaonce
#include<iostream>
//前向声明(由于函数模板中有用到Complex类,所以需要前向声明Complex类)
template <typenameT>
classComplex;
//函数模板声明
template <typenameT>
Complex<T>operator + (constComplex<T> &lhs,const Complex<T>&rhs);
template <typenameT>
Complex<T>operator - (constComplex<T> &lhs,const Complex<T>&rhs);
template <typenameT>
std::ostream&operator<< (std::ostream &ostr,constComplex<T> &rhs);
template <typenameT>
std::istream&operator>> (std::istream &istr, Complex<T> &rhs);
template <typenameT>
class Complex
{
public:
Complex(Treal = T(), T image = T());
virtual~Complex();
//将函数模板声明为Complex类的友元(可以访问Complex类的private成员)
friendComplex<T> operator + <T> (constComplex<T> &lhs,constComplex<T> &rhs);
friendComplex<T> operator - <T> (constComplex<T> &lhs,constComplex<T> &rhs);
//由于流运算符会修改流的状态,所以第一个参数为非const引用
//为了支持链式操作,返回第一个参数的引用
friendstd::ostream& operator << <T>(std::ostream &ostr,constComplex<T> &rhs);
//由于流提取运算符会修改第二个参数,所以第二个参数为非const引用
friendstd::istream& operator >> <T>(std::istream &istr, Complex<T> &rhs);
private:
T m_real;
T m_image;
};
//包含模板实现部分
//如果"Complex.inl"是头文件就直接包含就行
//如果"Complex.inl"是源文件,则必须将其从项目中移除
#include"Complex.inl"
//Complex.inl
usingnamespacestd;
template <typenameT>
Complex<T>::Complex(Treal/* =T()*/, T image/*= T()*/)
: m_real(real),m_image(image)
{
}
template <typenameT>
Complex<T>::~Complex()
{
}
template <typenameT>
Complex<T>operator+ (const Complex<T> &lhs,constComplex<T> &rhs)
{
returnComplex<T>(lhs.m_real + rhs.m_real, lhs.m_image + rhs.m_image);
}
template <typenameT>
Complex<T>operator- (const Complex<T> &lhs,constComplex<T> &rhs)
{
returnComplex<T>(lhs.m_real - rhs.m_real, lhs.m_image - rhs.m_image);
}
template <typenameT>
ostream& operator<< (ostream &ostr, constComplex<T> &rhs)
{
ostr<< rhs.m_real <<" + "<< rhs.m_image <<"i";
returnostr;
}
template <typenameT>
istream& operator>> (istream &istr, Complex<T> &rhs)
{
istr>> rhs.m_real >> rhs.m_image;
returnistr;
}
//main.cpp
#include"Complex.h"
int main(intargc,char**argv)
{
Complex<double>myComplex1(2, 3);
Complex<double>myComplex2(6, 7);
//使用流插入运算符完成复数对象的输出
cout<<"myComplex1 = "<< myComplex1 << endl;
cout<<"myComplex2 = "<< myComplex2 << endl;
cout<<"myComplex1 + myComplex2 = "<< myComplex1 + myComplex2 << endl;
cout<<"myComplex1 - myComplex2 = "<< myComplex1 - myComplex2 << endl;
//使用流提取运算符完成复数对象的输入
cin>> myComplex1;
cout<<"myComplex1 = "<< myComplex1 << endl;
return0;
}
程序运行结果:
可能你觉得前面代码中的前向引用声明有点复杂,而且还和类里的友元声明有点重复。有没有简便的方法,不用写那些复杂的前向引用声明呢?有,像下面这样就可以。
//Complex.h
#pragmaonce
#include<iostream>
usingnamespacestd;
template <typenameT>
classComplex
{
public:
Complex(T real =T(),T image = T());
virtual~Complex();
//将函数模板声明为Complex类的友元(可以访问Complex类的private成员)
friendComplex<T>operator+ <T> (constComplex<T>&lhs,constComplex<T> &rhs);
friendComplex<T>operator- <T> (constComplex<T>&lhs,constComplex<T> &rhs);
//由于流运算符会修改流的状态,所以第一个参数为非const引用
//为了支持链式操作,返回第一个参数的引用
friendstd::ostream& operator<< <T> (std::ostream&ostr,constComplex<T> &rhs);
//由于流提取运算符会修改第二个参数,所以第二个参数为非const引用
friendstd::istream& operator>> <T> (std::istream&istr,Complex<T>&rhs);
private:
T m_real;
T m_image;
};
//包含模板实现部分
//如果"Complex.inl"是头文件就直接包含就行
//如果"Complex.inl"是源文件,则必须将其从项目中移除
#include"Complex.inl"
注意到没有,用“usingnamespacestd;”就可以避免繁琐的前向引用声明啦。不过,此时可以将“Complex.inl”文件中的“usingnamespacestd;”去掉了,因为“Complex.h”已经包含了哦。相信你现在已经学会了流运算符重载的编写,不过光看是不行的哦,自己动手敲一下。博主为了方便讲解,封装的Complex模板类十分简单,你可以尝试一下更接近于内建类型的版本哦。虽然你封装的类其他人已经封装过了,不过在学习阶段还是得多折腾,这样才能加深你对这部分知识的理解。
C++运算符重载中的算术运算符重载就讲到这里,相信大家对它的概念和用法都已经熟悉了吧。如果想了解更多关于C++运算符重载的知识,请关注博主的《C++运算符重载》系列博文,在那里我们将会通过程序实例去探讨C++运算符重载的魅力,相信你在那里会有不一样的收获。当然,如果你对C++很感兴趣的话,那么请关注博主的《漫谈继承技术》和《灵活而奇特的C++语言特性》系列博文,在那里你也许会发现C++更大的魅力,让你对这门博大精深的语言更加爱不释手。