关于c ++:<<和>>运算符如何进行I / O操作?


How do the “<<” and “>>” operators do I/O?

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
Operator overloading

我期待着回到C++,有一些基本的符号似乎并不突出,在其他语言。

如果你看这行代码

1
cout <<"firstvalue is" << firstvalue << endl;

我知道这是怎么回事。它将"firstvalue is x"写入控制台。x是FirstValue的值。但是,我对"<"或">>"双尖括号一无所知。我没能研究它们,也没能研究它们的作用,因为我不知道它们的正式名称。

我的问题是,在上面的陈述中到底发生了什么(一步一步的)?这些"<<"是干什么用的?我想我理解cout是一个标准的库函数,用于编写控制台。不过,我习惯了使用Objective-C或Dot符号。我不知道这个"cout"函数是什么对象的成员。

我可以更容易地理解printf,因为它至少为参数提供了大括号。例如printf("此处为您的字符串")。


C++允许运算符重载。这意味着用户定义的类型可以在内置运算符上定义自己的行为。在这种情况下,这些操作符被称为:left shiftright shift操作符。传统上,这些操作符被用于位移动,但是标准库将它们重新调整为流操作的象征。

您可以在这里找到C和C++中可用操作符的列表。

在您的例子中,您正在将一个字符串文字和某个类型的值流式传输到std::cout中,后者是std::basic_Ostream类型的对象。

一步一步

应用优先规则后,代码如下所示:

1
((cout <<"foobar") << x) << endl;

编译器将基本上把object << object表达式转换成函数调用。

1
operator<<(operator<<(operator<<(cout,"foobar"), x), endl);

然后它会找出调用哪个重载。(这是真的狡猾的就目前而言,只要相信查找带有匹配参数的operator<<的过载)。

基本类的大多数内置重载在这里和这里。


EDCOX1×0运算符是C++中的"算术左移位"。例如:

1
3 << 2

计算结果为12。原因是3的二进制表示是

1
00000011

向左移动两次

1
00001100

结果的数值为12。

它与输出有什么关系?实际上,什么都没有。然而,在C++中,由于过载,可以重新定义运算符的含义。C++标准库决定将左移位操作符的含义重新定义为"发送到流"的类型。

所以发生的是

1
std::cout <<"whatever"

返回值std::cout,但作为副作用,它输出字符串"whatever"。

之所以选择该运算符,是因为它具有合理的优先级(重载不能更改优先级,并且不能定义新的运算符),并且该形状使它看起来有点"自然"。但是请注意,左移位运算符只是一个普通运算符,例如,无法保证评估顺序:

1
std::cout << f() << g() << h();

这里的输出是调用f()的结果,接着是调用g()的结果,接着是调用h()的结果。但是函数本身可以以不同的顺序调用,例如,可以先调用h()

所以在某种意义上,操作符的"序列外观"是误导的,因为它是关于输出序列的,而不是关于评估序列的。


<<是一个运算符,就像+是一个运算符,*是一个运算符。以下是等价表达式:

1
2
5 + 3 + 2
((5 + 3) + 2)

接下来的两个是:

1
2
std::cout <<"Hello" << std::endl
((std::cout <<"Hello") << std::endl)

它只是一个有两个操作数的运算符。对于基本类型,<<>>操作符实际上被称为左、右移位操作符。它们执行位移位。例如,5 << 1将5(0101中的所有位移到一个位置,得到10(1010)。

但是,与大多数其他运算符一样,您可以重载移位运算符。在输入/输出库的情况下,移位运算符被重载,以便为流的输入和输出提供自然的语法。这是因为<<>>代币的方向性看起来像是某种东西在以某种方式流动。对于这些I/O类,这些操作符重载返回对正在执行操作符的流的引用,以便将它们链接在一起。

可以通过提供一个成员函数operator<<operator>>,使用一个参数(运算符右侧的操作数),来重载特定类的移位运算符。或者,可以提供具有相同名称的非成员函数,该函数接受两个参数,分别是运算符的两个操作数。


它们被称为流插入(或提取,在istream >>的情况下),实际上是左移位和右移位操作符的语义过载。

所以,这个:

1
int x = 1 << 1;

有点偏移,但这:

1
std::cout << x;

是流插入。您可以将其明确地写为:

1
operator <<(std::cout, x);

得到完全相同的结果。流插入操作符的传统格式(对于用户定义的类型,可以重载它们,因此编写自己的类型并不少见)是

1
std::ostream& operator <<(std::ostream&, T value);

输出流被返回(通过引用),因此您可以链接调用:您的示例翻译为:

1
2
3
4
5
6
7
operator<< (
  operator<< (
    operator<<(std::cout,"firstvalue"),
    firstvalue
  ),
  std::endl
);

哦,还有……std::coutstd::cerr等不是函数:它们是全局对象。这里的函数是重载的<<运算符。把它们看作是FILE *stdout, *stderr的等价物。

C++ IoFielts在EDCOX1上有一些优点,例如9。

  • 类型安全:您不能错误地打印带有"%f"的整数并得到垃圾,因为重载分辨率在编译时自动选择std::ostream& operator<<(std::ostream&, double)函数。
  • 支持用户定义的类型:您可以为您的Whizzy新类编写一个流插入操作符,它在任何地方都可以工作。
  • 流抽象:可以使用相同的重载(因此只写一次)来格式化为stdout和stderr(cout/cerr),以及文件(std::ofstream和字符串(std::ostringstream)。不需要单独处理printf/fprintf/snprintf

还有一些缺点:

  • 性能:对于所有的抽象,以及在运行时配置的本地系统的通用性,都会受到一些惩罚。
  • 冗长:至少对于已经被printf支持的原语类型,格式字符串更简洁、更具表现力。

它是用于以下内容的句法糖:

1
2
3
4
5
6
7
8
// Let the function 'print' be a renaming of 'operator<<'
// with T being the type of the object you want to print.
std::ostream& print(std::ostream&, const T&);

// 1) Print"first value is" and then return the stream you
// to which to just printed (ie. cout). 2) Use the returned
// stream to chain function calls and print 'firstValue'.
print(print(std::cout,"first value is"), firstValue);