曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 3568|回复: 2
打印 上一主题 下一主题

重载插入符(<<)和提取符(>>)

[复制链接]

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
跳转到指定楼层
楼主
发表于 2011-11-21 14:50:16 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
应用中,经常会用到 << 插入符将内容送往标准输出,而用 >> 提取符将从标准输入中获取内容。

在讨论重载 << 和 >> 之前,先简要介绍一下 C++ 的 I/O 系统的一些概念。

需要使用 C++ 中的 I/O 系统就需要包含头文件 <iostream>。在这个头文件中定义了一组非常复杂的类层次结构来支持 I/O 操作。I/O 类以一组模板类开始 --- 模板类 定义了一个类的结构但没有指定类使用的数据类型。在创建了一个模板类对象后,模板类才被实例化。

对于 I/O 库而言,标准 C++ 创建了 I/O 模板类的两种实例:一种用于 8 位字符,另一种用于宽字符。一般而言,8 位字符的 I/O 类最为常用。


C++ 的 I/O 系统建立再两种相关但又不相同的模板类层次结构上。第一种类层次结构是从低级的 I/O 类 basic_streambuf 中派生出来。这个类提供了底层且基本的输入输出操作,同时也为整个 C++ I/O 系统提供了根本的支持。除非在程序中需要使用高级的 I/O 技术,否则不需要直接和 basic_streambuf 打交道。

第二种层次结构,也是经常使用到的,是从类 basic_ios 中派生出来的。这是个高级的 I/O 类,它提供了格式化,错误检测以及 I/O 留的状态信息。basic_ios 被用作其它几个派生类的基类,包括 basic_istreambasic_ostream 以及 basic_iostream 。这些类分别用来创建能够输入、输出以及 输入/输出的流。


下面的表中给出了模板类的名字以及对应的基于 8 位字符的实例类:
模板类基于8位字符的类
basic_streambufstreambuuf
basic_ios ios
basic_istreamistream
basic_ostreamostream
basic_iostreamiostream
basic_fstreamfstream
basic_ifstreamifstream
basic_ofstreamofstream


下面是一个简单的示例,演示了如何创建一个插入符。首先定义一个名为 three_d 的类:
[C++] 纯文本查看 复制代码
class three_d {
public:
    int x, y, z;
    three_d(int a, int b, int c) { x = a; y = b; z = c; }
};

要创建类 three_d 的插入符,我们就必须重载运算符 << ,如下所示:
[C++] 纯文本查看 复制代码
ostream &operator << (ostream &stream, three_d obj)
{
  stream << obj.x << ", ";
  stream << obj.y << ", ";
   stream << obj.z << "\n";
   return stream;   //返回流对象
}

在上面的重载函数中,函数被声明为返回一个 ostream 类型对象的引用。只有这样声明,重载的 << 运算符才能在复合的 I/O 表达式中。
函数有两个参数,第 1 个参数是对 << 运算符左边的流对象的引用;第 2 个是运算符右边的对象,如果你愿意的话,这个参数也可以是该对象的引用。
在上面的函数中,three_d 类型对象中的 3 个坐标值(x, y, z)被插入到流中,并且函数将返回这个流。

下面程序演示了插入流的用法:
[C++] 纯文本查看 复制代码
#include <iostream>
#include <fstream>


using namespace std;


class three_d {
public:
        int x, y, z;


        three_d(int a, int b, int c)
        {
                x = a;
                y = b;
                z = c;
        }
};


//输出X,Y,Z的坐标值 -- 通过类three_d 的插入符
ostream &operator << (ostream &stream, three_d obj)
{
        stream << obj.x << ", ";
        stream << obj.y << ", ";
        stream << obj.z << "\n";


        return stream;  //返回流对象
}
int main()
{
        three_d a(1, 2, 3), b(3, 4, 5), c(5, 6, 7);


        cout << a << b << c;


        return 0;
}

运行输出:
./reload_ios
1, 2, 3
3, 4, 5
5, 6, 7
如果去掉插入符函数中合 three_d 相关的代码,那么抽象起来就得到了插入符函数的框架,如下所示:
[Plain Text] 纯文本查看 复制代码
ostream &operator<<(ostream &stream, class_type obj)
{
   //与具体的类相关的代码
   return stream;  //返回流对象
}

当然,也可以将 obj 的引用作为参数传递到函数中。

也许可能会有疑问:为什么类 three_d 的插入符函数没有以下面的形式实现:
[C++] 纯文本查看 复制代码
ostream &operator << (ostream &stream, three_d obj)
{
  cout << obj.x << ", ";
  cout << obj.x << ", ";
   cout << obj.x << "\n";
   return stream;  //返回流对象
}

注意上面使用了 cout 代替了先前的 stream 。
在上面的实现形式中,流 cout 是被硬编码到函数中的,这就限制了函数可被使用的环境。

记住运算符 << 可以应用到任何一种流,并且 << 表达式中的流对象将被作为参数传递给函数

我们可以改造一下上面的程序以验证上面的说法:
[C++] 纯文本查看 复制代码
#include <iostream>
#include <fstream>


using namespace std;


class three_d {
public:
        int x, y, z;


        three_d(int a, int b, int c)
        {
                x = a;
                y = b;
                z = c;
        }
};


//输出X,Y,Z的坐标值 -- 通过类three_d 的插入符
ostream &operator << (ostream &stream, three_d obj)
{
        cout << obj.x << ", ";
        cout << obj.y << ", ";
        cout << obj.z << "\n";


        return stream;  //返回流对象
}


int main()
{
        ofstream out("test.txt");
        if (!out) {
                cout << "Cannot open file.\n";
                return 1;
        }


        three_d a(1, 2, 3), b(3, 4, 5), c(5, 6, 7);


        out << a << b << c;


        return 0;
}

在上面程序中,我们将 cout 硬编码到插入符函数中。另外,在 main 中为打开并写入当前目录下的 test.txt 文件而建立了一个输出流 out (希望写入文件) 。当我们运行程序时,可以看到标准输出中可以输出:
./reload_ios
1, 2, 3
3, 4, 5
5, 6, 7
但是打开文件 test.txt 查看时是没有内容的。这是因为在插入符函数中已经将输出流写死为 cout (标准输出),实际上在函数内部根本没有对 out 这个流有任何操作,它只是被简单的返回。

然而,如果我们将上面的 cout 改成 stream 的话那就可以正常的写入文件了,这是因为我们通过参数中队 out 的引用,将数据输送到其中,然后再返回这个引用。

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
沙发
 楼主| 发表于 2011-11-21 17:37:10 | 只看该作者

使用友元函数重载插入符

上面,重载的插入符函数并不是类 three_d 的成员函数。事实上,不论是插入符函数还是提取符函数,都不可能作为类的成员函数!原因是:

如果一个运算符函数是类的成员函数,那么运算符左边的操作对象 (通过 this  指针被隐式地传递给函数) 必须是这个类的对象,并且正是这个对象调用了运算符函数 --- 这个规则是不变的。(还可以参考:《使用成员函数重载运算符
)。然而,当我们使用重载的插入符函数时,运算符左边的对象是流,右边的对象才是要被输出的类的对象。因此,重载插入符函数必须是非成员函数

既然插入符函数无法成为类的成员函数,那么就会带来一个问题:重载的插入符函数如何才能访问类中的私有成员?尽管在上面的程序中,插入符函数可以访问类中的公有成员变量 x, y, z 。但是数据隐藏却是 OOP 的一种重要的特征,将所有的数据对外部公开将带来严重的数据不一致性。对于这一问题的解决方法是:将插入符函数作为类的友员函数。这样函数就能够访问类中的私有成员了。

演示程序:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;


class three_d {
        int x, y, z;    //三个坐标值,现在是类的私有成员
public:
        three_d(int a, int b, int c) { x = a; y = b; z = c; }


        friend ostream &operator << (ostream &stream, three_d obj);
};


//输出x,y,z的坐标值 - 通过类 three_d 的插入符函数
ostream &operator << (ostream &stream, three_d obj)
{
        stream << obj.x << ", ";
        stream << obj.y << ", ";
        stream << obj.z << "\\n";


        return stream;  //返回流对象
}


int main()
{
        three_d a(1, 2, 3), b(4, 5, 6), c(7, 8, 9);


        cout << a << b << c;


        return 0;
}

运行输出:
./friend_reload
1, 2, 3
4, 5, 6
7, 8, 9
现在,被定义为类 three_d 的友员函数的插入符函数终于可以访问私有成员变量 x, y, z 了。这很好的保持了 OOP 的封装原则。

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
板凳
 楼主| 发表于 2011-11-23 00:27:28 | 只看该作者

重载提取符(&gt;&gt;)

同重载插入符一样,也可以重载提取符。

提取符函数必须返回一个 istream 类型对象的引用,它的第一个参数也必须是 istreram 类型对象的引用,这个流对象也就是运算符左边的操作对象;第二个参数是用来接收输入的变量引用,因此函数可以使用输入信息来修改这个变量的值。提取码函数的代码框架如下:
[C++] 纯文本查看 复制代码
istream &operator >> (istream &stream, object_type &obj)
{
   //在这里添加提取符代码
   return stream;
}


示例程序:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;


class three_d {
        int x, y, z;
public:
        three_d (int a, int b, int c) { x = a; y = b; z = c; }
        friend ostream &operator << (ostream &stream, three_d obj);
        friend istream &operator >> (istream &stream, three_d &obj);
};


//输出 3 个坐标值 -- 通过 three_d 插入符
ostream &operator << (ostream &stream, three_d obj)
{
        stream << obj.x << ", ";
        stream << obj.y << ", ";
        stream << obj.z << "\\n";


        return stream;
}


//获得3个坐标值 -- 通过 three_d 提取符
istream &operator >> (istream &stream, three_d &obj)
{
        cout << "Enter X,Y,Z values: ";
        stream >> obj.x >> obj.y >> obj.z;
        return stream;
}


int main()
{
        three_d a(1, 2, 3);


        cout << a;


        cin >> a;


        cout << a;


        return 0;
}

运行输出:
[quote]./reload_cin
1, 2, 3
Enter X,Y,Z values: 5 6 7
5, 6, 7[/mw_shl_code]
和插入符函数一样,提取符函数也不能是成员函数,但可以是类的友员函数,或者干脆是独立于类的函数。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-18 01:32 , Processed in 0.080840 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表