LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 957|回复: 9

操作符 <<重载的问题,请教了!

[复制链接]
发表于 2005-10-27 15:41:33 | 显示全部楼层 |阅读模式
我新建了一个类IntArray,eclipse3.1帮我生成了2个文件IntArray.h和IntArray.cpp。我在IntArray.h中声明了操作符 <<重载的函数没有问题
                      friend std:stream &operator << (std:stream &, const IntArray &);

可是在IntArray.cpp文件中定义时,函数声明部分出了问题:
           std:stream& IntArray:perator << (std:stream &os, const IntArray &rhs)
提示错误为:
1.  error: `std:stream& IntArray:perator<<(std:stream&, const IntArray&)' must take exactly one argument
2.  error:no `std:stream& IntArray:perator<<(std::ostream&, const IntArray&)' member function declared in class `IntArray'
    请问我的问题出在哪里呀?请大家帮帮我!谢谢回复!
发表于 2005-10-27 18:16:21 | 显示全部楼层
错误信息说的很清楚了,你这样声明根本就是错误的,friend不是member function,所以不是IntArray :: operator<<。要这样在.h声明:

  1. class IntArray {
  2. public:
  3.     ...
  4.     friend std::ostream& operator<<(std::ostream&, const IntArray&);
  5. };

  6. std::ostream& operator<<(std::ostream&, const IntArray&);
复制代码

然后再在.cc文件中定义。

容我再多说几句:把operator<<声明成friend是不好的做法。这使得在对该类的继承类使用operator<<成为不可能。标准的作法是:

  1. class IntArray {
  2. public:
  3.     ...
  4.     virtual void print(std::ostream& strm) const;
  5. };

  6. std::ostream& operator<<(std::ostream& strm, const IntArray& ia)
  7. {
  8.     ia.print(strm);
  9.     return strm;
  10. }
复制代码

这样才是标准的做法。用friend的方法已经被deprecated了。还望楼主考虑

参考:
The C++ Standard Library - A Tutorial and Reference : Nicolai M. Josuttis
回复 支持 反对

使用道具 举报

发表于 2005-10-27 22:00:45 | 显示全部楼层
受教了。
干嘛要自己写array呢? vector<int>不就得了吗?
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-10-28 15:54:21 | 显示全部楼层
非常感谢manphiz的回复!尤其是“多说的那几句”对我帮助很大!
但是出于一种学习的目的,有一点不明白的地方我还是想问一下:
你说的声明friend重载<<操作符的方法中std:stream& operator<<(std:stream&, const IntArray&);是在类域范围之外呀?!
还有,你说的“然后再在.cc文件中定义”该如何定义呢?如果我在.cc文件中这样定义:
std:stream& operator<<(std:stream&, const IntArray&);那编译器如何知道这是
IntArray类的成员函数呢?一个类域操作符是需要的吧-IntArray::?

请原谅我的无知,烦请解释一下,谢谢!
回复 支持 反对

使用道具 举报

发表于 2005-10-29 03:15:24 | 显示全部楼层
声名为friend的函数不是类的成员,而是全局函数!和名字一样,friend只是朋友,而不是一分子。虽然他也能访问类的protected和private成员,但他不是类成员!
下面是简单的例子:

  1. // foo.h
  2. #include <ostream>

  3. class test {
  4. public:
  5.     test(int ii):i(ii) {};
  6.     friend std::ostream& operator<<(std::ostream&, const test&);
  7. private:
  8.     int i;
  9. };

  10. std::ostream& operator<<(std::ostream&, const test&);

  11. // foo.cc
  12. #include "foo.h"
  13. std::ostream& operator<<(std::ostream& strm, const test& t)
  14. {
  15.     strm << t.i;
  16. }
复制代码

可以看出,程序中的operator<<()实际上仅仅是全局函数operator<<()的重载,只不过他能访问test类的私有成员,仅此而已,他还是全局函数,而不是类的方法。可以看出,如果在test类中定义变量i的get-set操作,或者干脆把i声名成public,那么这个operator<<()甚至根本不用声名为friend就能满足需要了。

注意,这个类的friend不能成为其派生类的friend,正是因为他是全局函数而不是成员函数,因此不可能为virtual。

而且,随意声明为friend也会影响类的封装性。把operator<<()声明为某类的friend就是不好的例子。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-10-29 08:55:16 | 显示全部楼层
谢谢你--manphiz!
现在我大致上明白了这个道理!但还有几个问题没有搞清楚:
在你第一篇回复里所说的--标准的作法中是在头文件中声明了一个print()函数,而后在类域之外重载的<<函数中调用它,为什么不能把std:stream& operator<<(std:stream& strm, const IntArray& ia)函数声明直接放在类域里呢?直接在头文件的类域里声明<<操作符的重载函数必须声明为友元么?能不能给一个例子说明“标准的作法”是怎样分别在头文件和cpp文件中定义的?
     抱歉!让你费心了!
回复 支持 反对

使用道具 举报

发表于 2005-10-29 10:36:37 | 显示全部楼层
先说说声名为成员函数:
是这样的:将operator<<()定义成类成员是可以的,但是可能和你所期望的不同。比如在你对cout使用operator<<()时,你会这样写:

  1. cout << foo;
复制代码

这句程序等价于

  1. operator<<(cout, foo);
复制代码

所以将你自己的operator<<()定义成全局函数是重载这个预定义的operator<<()函数而用于你的类型。
如果将operator<<()声名为成员函数,如下:

  1. class foo {
  2. public:
  3.     ...
  4.     std::ostream& operator<<(std::ostream&);
  5. };
复制代码

注意这里的不同:
1、相比全局函数,第二个参数没有了,因为其不
2、在使用时,如果用`cout << foo;'将会出错,因为不存在全局函数`std:stream& operator<<(std:stream&, const foo&);‘编译器无法找到能匹配的函数重载。这是因为你声名的是成员函数。你在使用的时候需要这样用:

  1. t.operator<<(cout);
复制代码

或者写成

  1. t << cout;
复制代码

很明显这与一般的使用习惯严重不同,因此不这样用,而是将operator<<()声名为全局函数,在声名为类的友元。

再说说标准用法的例子:

  1. // foo.h
  2. #include <ostream>
  3. class foo {
  4. public:
  5.     foo(int ii):i(ii) {}
  6.     virtual void print(std::ostream&);
  7. private:
  8.     int i;
  9. };

  10. std::ostream& operator<<(std::ostream&, const foo&);

  11. // foo.cc
  12. #include "foo.h"
  13. void foo::print(std::ostream& strm)
  14. {
  15.     strm << this->i;
  16. }

  17. std::ostream& operator<<(std::ostream& strm, const foo& fo)
  18. {
  19.     fo.print(strm);
  20.     return strm;
  21. }
复制代码

如果我们再有一个类bar继承自foo,就只需要这样定义:

  1. // bar.h
  2. #include <ostream>
  3. #include "foo.h"

  4. class bar : public foo {
  5. public:
  6.     bar(double jj) : j(jj) {}
  7.     void print(std::ostream&);
  8. private:
  9.     double j;
  10. };

  11. // bar.cc
  12. #include "bar.h"
  13. void bar::print(std::ostream& strm)
  14. {
  15.     strm << this->j;
  16. }
复制代码

而不必要再定义他的operator<<()。因为我们可以通过对先前定义的`operator<<(std:stream&, const foo&);’中的foo的来多态处理bar。像这样:

  1. // main.cc
  2. #include <iostream>
  3. #include "bar.h"

  4. int main()
  5. {
  6.     bar b(10.0);
  7.     std::cout << b << std::endl;
  8. }
复制代码


推荐一些资料:
The C++ Programming Language中有介绍friend的概念;
Effective C++中有对操作符重载问题的详细讲解;
The C++ standard Library有对I/O stream的详细讲解。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-10-29 11:57:08 | 显示全部楼层
最后一个问题--manphiz!
“如果将operator<<()声名为成员函数,相比全局函数,第二个参数没有了,”--为什么会这样?
(这里你好像漏掉了,就像武林秘笈少了最后一招,威力大减--^_^!)还请明示。
回复 支持 反对

使用道具 举报

发表于 2005-10-29 12:59:20 | 显示全部楼层
Post by jiandaoxu
最后一个问题--manphiz!
“如果将operator<<()声名为成员函数,相比全局函数,第二个参数没有了,”--为什么会这样?
(这里你好像漏掉了,就像武林秘笈少了最后一招,威力大减--^_^!)还请明示。

在全局函数中需要第二个参数const foo&,是因为在cout << a;这句中他在调用的时候解析为

  1. operator<<(cout, a);
复制代码

这里a需要传递给函数,否则全局函数不知道要输出什么。
而作为成员函数的时候,因为是成员函数,而并不是全局函数的重载,因此接口可由你自行定义。作为成员函数的operator<<()必然是要输出该对象的某个成员,因此那个参数就不必要了,因为他能直接访问。因此定义成

  1. class foo {
  2. public:
  3.     ...
  4.     std::ostream& operator<<(ostream& strm)
  5.     {
  6.         strm << i;
  7.     }
  8. private:
  9.     int i;
  10. };
复制代码

而这样使用

  1. t.operator<<(cout);

  2. t << cout;
复制代码
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-10-29 16:35:58 | 显示全部楼层
OK!终于搞懂了,真是要多谢你呀--manphiz!
以后有不懂的地方还得向你请教。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

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