用C++封装cin和cout - 重载<<和>>运算符

4
我正在尝试创建一个控制台类。我想将cincout封装在这个类中,并重载<<>>运算符。这样,我就可以像下面这样使用这个类:
// Output
Console << "Call cout from Console" << endl;

// Input
string str;
Console >> str; // Call cin

我最好的猜测是:

class Console {
//...
public:
    ostream& operator<< (ostream& os)
    {
        cout << os;
        return &cout;
    }
    //...
};

但我知道那是错误的,如何重载运算符以将Console类用作cincout


2
你可能需要一个单例对象来做那件事。 - chris
5个回答

7

我有一个可以处理流操纵器的可用代码。您可以在此 Ideone 页面上看到我的代码。

以下是代码:

#include <iostream>

typedef std::ostream& (*manip) (std::ostream&);

class console {

};

template <class T> console& operator<< (console& con, const T& x) { std::cout << x; return con; }
template <class T> console& operator>>(console& con,  T& x) { std::cin >>x; return con; }
console& operator<< (console& con, manip manipulator){ std::cout<<manipulator; return con;}

int main() {
    console c;
    int a,b;
    c>>a>>b;
    c << "hello world"<<std::endl<<std::hex<<(a+b)<<std::endl;
}

感谢@MooingDuck的讨论,让我有了一个可行的答案,也感谢@111111的起点。


那似乎正是我正在寻找的,我现在要尝试一下。顺便说一句,我打算将其设置为单例,有人在评论中提到过,我可以将运算符重载声明为静态的,对吧? - Brandon Miller
等等,运算符不应该在类体中或者声明为友元吗? - Brandon Miller
@BrandonMiller 我不需要声明 friend,因为我没有访问 console 的私有成员。我可以在类 console 中声明,这样我就不需要在参数列表中传递 console 引用,或者我可以像上面那样做。 - André Oriani
@BrandonMiller 运算符必须作用于实例,因此它们不能被声明为静态的。 - André Oriani
@BrandonMiller 为了使它成为单例模式,声明结构体为 console_,然后在全局命名空间中使用 console_ console; 当然,总会存在单例问题,即如果在其构造之前(且它具有额外功能)使用该单例,你就会遇到问题。如果你有一个构造函数等,你可能希望使用习惯用法,在从函数 console() 分配 console_ 空间的静态指针的引用中返回。console_& console() { static *console_ local = new console_; return *local; } - std''OrgnlDave
显示剩余3条评论

2

我不知道为什么你想做这样的事情,但你需要捕获的不是流,而是另一种类型。但如果只是为了使std::cout和std::cin更加方便,我觉得没必要麻烦自己。

class console {

};

template<typename T>
console& operator<<(console con, const T& val) {
     std::cout << val;
     return con;
}

console c;
c << "hello world\n";

你能测试一下 c << "hello world" << std::endl 吗?我担心需要定义的 operator <<template <class T> ostream& operator(..., const T&) 还要多。 - PiotrNycz
1
@std''OrgnlDave 如果您没有提供一个复制构造函数,编译器会为您提供一个,逐个字段进行复制。 - André Oriani
我想要这个的原因是因为我有输入和输出例程,我将分组方法和其他杂项函数,我还在制作一个CommandMap类,以便在用户键入某个关键字时执行特定方法。 - Brandon Miller
@AndréOriani 这仅适用于简单的复制构造函数。无论如何,您正在堆栈上创建一个新变量并返回对该新变量的引用;由于您已经返回,因此对该区域的引用现在无效。 - std''OrgnlDave
1
@std''OrgnlDave 是的,编译器会生成一个简单的复制构造函数。我也同意您的观点,参数必须是控制台的引用。GCC会警告您提到的问题。 - André Oriani
显示剩余3条评论

2
这并不是对你问题的直接回答,但也许我可以给你指出一些替代方案。
请参考我的另一个问题中的答案。要自己定义所有这些<<>>操作符并不容易。不过,你可以重载控制台的streambuf。使用cin和cout的组合streambufs,
std::iostream派生你的控制台,从std::streambuf派生你的streambuf。
class console_streambuf : public std::streambuf {
public:
    console_streambuf() {
        // no buffering, overflow on every char
        setp(0, 0);
    }
    virtual int_type overflow(int_type c) {
        std::cout << c;
        return c;
    }
    ...
};

class console : public std::iostream {
public:
    console() { rdbuf(&buf); }
private:
    console_streambuf buf; 
};

1

与上面许多答案相反,使用模板实现你想要的功能非常简单。

我建议使用stringstream,因为使用ostream(cout是一个ostream)可能需要神秘的黑魔法(不是开玩笑)。

#include <string>
#include <iostream>
#include <sstream>

struct console {
  std::stringstream  data_;

  console() : data_() {  };

  // We make ourselves a template sink so we can "take" operator<<'s.
  //  The awesome advantage to using a template like this is that the
  //  compiler will allow us to "take" any data that can be converted
  //  to a stringstream, which will handle converting int's etc.
  //  for us!
  template<typename T>
  console& operator<<(const T& what) {
    data_ << what;
    return *this;  // We must return a reference if we want to
                   //  string together more than one thing, i.e.
                   //  b << " " << 4;
  }

  void flush() {
    std::cout << data_.str();
    data_.clear();
    std::cout.flush();
  }
};

int main()
{
  int a = 4;
  console b;
  console c;
  b.data_ << 2;
  c.data_ << 4;
  //b << std::cout;  // WHAT? it's possible but stupid, prints garbage

  // Because we made the template return a reference, this will
  //  allow us to chain things just like we do with cout.
  b << " HELLO WORLD! " << "yo!" << 4;

  b << a << " " << 4.2f;

  // Compiler chokes on this. It will try to convert "console"
  //  to a stringstream which it can't.
  //b << c;

  b.flush(); // Send out the output

  // Wait for key press
  char foo[500];
  gets(foo);
}

输出:

2 HELLO WORLD! yo!44 4.2

就像cout一样,只不过有更多的控制。 如果你想要进行二进制I/O,你可以使用basic_ostream和basic_istream,但我建议除非你真的非常需要,否则不要这样做。


@MooingDuck 为什么你想要缓冲控制台?“更多的控制权。”你能告诉cout在你想要它刷新之前不要刷新吗?你可能可以,但我不知道怎么做... - std''OrgnlDave

-1

重载运算符函数必须在左右两侧声明具体的类型。因此,您需要一个operator<< (int)operator<< (double)operator<< (const std::string &)等。

如果您只是要将它们传递给cin和cout,您可以使用模板成员函数来节省输入:

template <class T> Console& operator<< (const T& x) { cout << x; return *this; }

[感谢André指出应该返回Console&,这样您就可以像Console << a << b;这样连接调用]

(Note: The translated text is in Simplified Chinese.)

1
有两个问题:首先,您没有返回一个 ostream 对象。其次,为了使 consolecoutcin 一样工作,您应该返回一个 console& - André Oriani
@AndréOriani:我不同意,他“正在”返回一个ostream对象,因此'ostream&'“是”正确的返回类型。 - Mooing Duck
1
@MooingDuck,也许你插进了我们的谈话并失去了上下文。之前的代码根本没有返回任何东西。他“修复”了,但代码仍然是错误的。返回语句应该是return *this;,这样就会返回一个console引用。现在我们同意了吗?我们应该返回包装器,而不是被包装的对象。 - André Oriani
1
@MooingDuck 好的,现在我看到我的错误了。我以为它是一个成员方法。所以我说的话必须被忽略,因为它没有意义。 - André Oriani
本来应该是一个成员方法。我已经修复它以返回*this,谢谢。 - Bryan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接