说明:本文重点是掌握输出操作符重载的代码写法。文章中的解耦(又称解耦合)和模块化概念属于选读理解的概念,不需要初学者掌握。
当类对象有多个成员变量的时候,输出这些变量往往比较麻烦。比如:
- Student stu("001", "张三", 18, "1990-02-12");
- std::cout<
" " - <
" " - <
" " - <
//每个成员都要写
尤其是,有时候我们还需要输出同样的格式到文件里:
- Student stu("001", "张三", 18, "1990-02-12");
- ofstream foutf()
- fout
- <
- <
- <
- <
;//每个成员都要写
有时候甚至,还可能输出到日志里,等等。
如果每次想输出学生的信息都这么大费周折,写一遍这样的重复代码,那么就会有两个问题。
1 代码量增加,是否可以复用呢?把输出的代码模块化为一个函数不就可以不用写这么多次重复代码了吗
2 一旦输出学生信息的格式需要调整,比如增加了一个成员变量需要输出,这就不得不到处去找输出的代码,把这个新增的成员变量一起输出。很明显,这很容易遗漏,而且找起来也很费劲。甚至很难找全。如果使用函数来专门实现对象的输出,那么就不会再存在这个问题。
如果使用代码复用(封装成函数),那么应该有两个函数需要实现:输出到控制台,输出到文件。
- //声明两个函数
- void output1(std::ostream& cout, const Students& student);//声明:输出学生信息到控制台
- void output2(std::ofstream& fout, const Student& student);//声明:输出学生信息到文件
- //具体实现如下
- void output1(std::ostream& cout, const Students& student)//实现:输出学生信息到控制台
- {
- cout<
" " - <
" " - <
" " - <
//每个成员都要写 - }
- void output2(std::ofstream& fout, const Student& student)//实现:输出学生信息到文件
- {
- fout
- <
" " - <
" " - <
" " - <
//每个成员都要写 - }
-
- //这样我们就可以通过调用上面的函数来实现代码复用
-
- ofstream fout("outputfile.txt");
- Student student("001", "张三", 18, "1990-02-12");
- output1(cout,student);//一次调用
- output2(fout,student);//一次调用
- .......
- output1(cout,student);//另一次调用
- output2(fout,student);//另一次调用
- .......
输出目的地和对象之间没有解耦。从上面的代码可以看出,输出到文件和输出到文件,仅仅是第一个参数(输出目的地)不同,其余代码都相同。
3 要解决输出学生对象的信息到不同的目的地,并且利用代码复用(也就是封装成函数),那么就需要利用多态。也就是,第一个目的地参数的类型设置为cout 和fout的共同父类std::ostream的引用 :
- void output(std::ostream& out, const Student& student);//输出目的地和对象解耦的函数
- void output(std::ostream& out, const Student& student)//具体实现
- {
- out<
" " - <
" " - <
" " - <
//每个成员都要写 - }
这时候,我们就可以由上面的两个函数,变为1个函数。
- ofstream fout("outputfile.txt");
- Student student("001", "张三", 18, "1990-02-12");
- output(fout,student);//一次调用
- output(cout,student);//一次调用
- .......
- output(cout,student);//另一次调用
- output(fout,student);//另一次调用
- .......
多态的触发条件 之一就是通过基类的引用来实现。
解耦
此时上面的函数output就做到了将类Student的数据输出,和输出的目的地(控制台,或者文件)之间解耦合。
也就是说,不管将student对象输出到什么地方,student的输出代码都不会掺杂进来和输出目的地相关的代码。
比如不需要知道有ofstream这种类型,不需要知道有cout这个对象。
这就是代码的解耦合。
上面的解耦合版本还有一个问题,连续输出多个学生信息的时候,代码写起来不方便。
还记得cout连续输出多个变量的写法吗?
cout<" "<" "<//连续输出多个对象到控制台,很方便
但是我们的版本就只能像下面这样写:
- output(cout,stu1);
- cout<<" ";
- output(cout,stu2);
- cout<<" ";
- output(cout,stu3);
明显变麻烦了。
怎么样能够像cout那样可以连续输出呢?那就是函数返回目的地流对象。
- std::ostream& output(std::ostream& out, const Student& student);//返回流对象,方便连续输出
- //这时候我们就可以像下面这样使用了
- cout<<" "<<output(cout,stu1)<<" "<<output(cout,stu2)<<" "<<output(cout,stu3);//至少可以在一行里输出了
现在上面的代码离我们期待的连续输出还有最后一步,那就是去掉output这个函数,改为<<输出操作符。
这件正是输出操作符重载的含义:对象通过重载一个叫做输出操作符的函数来实现上面output的功能,从而可以给任何ostream的派生类(比如,cout,ofstream,ostringstream)对象无差别的输出。
我们可以通过实现一个函数(输出操作符函数<<),让类对象可以像普通类型一样输出:
- #include
- #include
-
- //学生类
- class Student
- {
- //输出操作符重载:将Student对象的数据输出到目的地os
- //os可以是控制台屏幕cout和文件ofstream对象,他们都可以看做是ostream对象
- //友元friend表示这个函数不是类的成员函数,而是一个全局函数,
- //而且可以访问类的私有成员:不然在函数内访问类的私有成员就只能写stu.get_id() stu.get_name() stu.get_age()这样的代码
- friend std::ostream& operator<<(std::ostream& os, const Student& stu);
- public:
- std::string m_id;//学号
- std::string m_name;//姓名
- int m_age;//年龄
- std::string m_date;//生日
- };
-
- std::ostream & operator<<(std::ostream & os, const Student & stu)
- {
- //向os输出Student对象的每一个成员变量,从而将Student输出到os
- os
- //如果这个函数不是friend,这里就只能写 stu.get_id()这样的代码来获取类的成员变量,比较麻烦
- << stu.m_id << " "
- << stu.m_name << " "
- << stu.m_age << " "
- << stu.m_date;
- return os;//这样os就可以连续输出多个对象。例如, cout<
- }
-
- int main(int argc, char** argv)
- {
- //定义一个学生对象
- Student stu{"001", "zhangsan", 18, "1995-09-08"};
- //将学生信息再次输出到控制台
- //这里可以直接输出stu对象
- std::cout << "学生信息:" << stu << std::endl;
-
- return 0;
- }
程序输出:

输出对象到文件
由于ofstream和cout一样,也是ostream的一种,所以我们也可以直接不用再写新代码就可以将对象写入文件:
- #include
- #include
- #include
-
- class Student
- {
- friend std::ostream& operator<<(std::ostream& os, const Student& stu);
- public:
- std::string m_id;//学号
- std::string m_name;//姓名
- int m_age;//年龄
- std::string m_date;//生日
- };
-
- std::ostream & operator<<(std::ostream & os, const Student & stu)
- {
- os
- << stu.m_id << " "
- << stu.m_name << " "
- << stu.m_age << " "
- << stu.m_date;
- return os;
- }
-
- int main(int argc, char** argv)
- {
- //定义一个学生对象
- Student stu{"001", "zhangsan", 18, "1995-09-08"};
- //输出学生信息到控制台
- std::cout << stu << std::endl;
-
- std::ofstream fout("students.txt");
- //输出学生信息到文件
- fout << stu << std::endl;
-
- return 0;
- }
程序输出(控制台和文件):
