如果(cin >> x),为什么可以使用这个条件?

62
我一直在使用《C++加速入门》这本书学习C ++,但有一个概念我似乎没有完全理解。

为什么是

int x;
if (cin >> x){}

相当于

cin >> x;
if (cin){}

从这段代码看来,我觉得我们正在使用 cin 作为变量。但是,我认为它是一个函数。当 x 拥有我们从键盘输入的任何值时,为什么我们可以以这种方式使用 cin?

9个回答

78

cin是一个istream类的对象,代表标准输入流。它对应于cstdio流中的stdin。流运算符>>重载返回对同一流的引用。流本身可以通过转换运算符在布尔条件下评估为true或false。

cin提供格式化流提取功能。操作cin >> x;

其中"x"是int类型,如果输入非数字值,则会失败。

if(cin>>x)

如果您输入字母而不是数字,将返回false。这个关于使用C++ I/O的技巧和窍门的网站也会对您有所帮助。

1
@Muhsin Ali:在学习新东西时,尝试理解不同的事物通常是很好的。然而,在这种情况下,我建议你现在把cincout视为“魔法”。当然,它们并不是魔法,但它们是使用许多相当先进的技术构建的。现在最好继续前进,等你掌握了类、继承和运算符重载后再回来看它们。 - Anders Abel
4
因为if语句所接受的表达式只能是布尔类型、整数类型或指针类型。类std::istream提供了将输入流转换为布尔类型的功能,正是这个转换运算符用于判断执行if分支还是else分支。 - David Hammen
1
@Muhsin:如果cin遇到无法处理的数据,它会进入“失败状态”,因此您必须使用if语句进行检查。请访问我在上一句中提供的链接,那里有一个代码示例。 - user195488
1
@DavidHammen std::istream提供将输入流转换为布尔值的能力,正是这个转换运算符被使用… 你能告诉我istream提供的转换运算符吗? - Quazi Irfan
@DavidHammen 谢谢。我的错,我漏了它。现在感觉像一个链条。还有一个问题,在这个上下文中C++03和C++11有什么区别? - Quazi Irfan
显示剩余5条评论

39
注意:回答是对 C++98/03 和 C++11(及更高版本)的更新后四年进行的回答。
std::cin 是 std::istream 的一个实例。该类提供了两个与此问题相关的重载:
- operator >> 将数据从流中读取到目标变量中(如果可能)。如果流的立即内容无法转换为目标变量的类型,则将流标记为无效状态,并保持目标变量不变。无论操作成功还是失败,返回值都是一个对流的引用。 - 无论是 operator void*()(C++11 之前),它将流引用转换为 void* 指针,还是 explicit operator bool()(C++11),它将流引用转换为布尔值。这种转换的结果是非空指针(C++11 之前)或 true(C++11),如果流有效,但是空指针(C++11 之前)或 false(C++11),如果流无效。
if 语句需要布尔值、整数或指针作为要测试的数量。std::cin >> x 的结果是对 istream 的引用,而它们都不符合上述条件。然而,类 istream 具有这些转换运算符,可以用来将 istream 引用转换为 if 语句中可用的东西。它是语言用于 if 测试的特定于版本的转换运算符。由于读取失败会将流标记为无效状态,因此如果读取失败,则 if 测试将失败。在C++11之前,更加复杂的operator void*转换成员的原因是,直到C++11才将已经存在的explicit关键字扩展到适用于转换运算符以及构造函数。一个非显式的operator bool()会给程序员带来太多的机会去踩坑。虽然operator void*()也存在问题,但"safe bool idiom"(安全布尔型惯用法)可以解决这个问题,但仅仅扩展explicit关键字就可以达到和"safe bool idiom"一样的效果,并且不需要使用大量的SFINAE技巧。

6
它不使用 operator bool,而是使用 operator void*operator bool 可以使它在算术上下文中使用,而 operator void* 防止这种情况发生,因为 void 是不完全的类型。在 MSVC 编译器的实现中,如果没有设置失败标志(这不能为 0,因此在布尔上下文中被评估为 true),则 operator void* 返回对象的地址,否则返回 0(在布尔上下文中为 false)。 - lccarrasco
2
根据http://www.cplusplus.com/reference/ios/ios/operator_bool/,在C++98中使用`operator void*(),而在C++11中使用operator bool()`。 - Evgeni Sergeev
1
@AbhishekMane 如果你不告诉现代兼容编译器使用C++98标准(例如--std=c++98),则std::cout<<(std::cin>>x)将无法通过编译。在所有版本的标准中,从std::cin>>x返回的值是对std::cin对象的引用,并且没有重载std::basic_ostream operator>> (const std::basic_istream&)。在C++11之前,编译器会使用std::basic_istream::operator void*()。如果输入失败,则返回空指针,如果输入成功,则返回非空指针。这就是为什么你会看到00x48650的原因。(续) - David Hammen
1
void* 转换运算符在 C++11 中已被删除,用 explicit operator bool() 替代。explicit 标签(C++11 新增)明确告诉编译器不要在 std::cout<<(std::cin>>x) 中使用该转换。这个表达式在遵循 C++11、C++14、C++17 或 C++20 标准版本的编译器中应产生编译错误。 - David Hammen
1
@AbhishekMane 我不喜欢将类型隐式转换为bool。例如,假设foo是一个指针类型,我更喜欢看到if ((foo != nullptr) && foo->is_valid())而不是if (foo && foo->is_valid())。但这只是个人偏好。在有一半像样的编译器的情况下,这两个语句在生成的汇编代码方面是相同的。 - David Hammen
显示剩余7条评论

7

cinistream类型的全局变量,而不是函数。

istream类重载了>>运算符进行输入,并返回调用该运算符的对象(cin)的引用。


它不返回对象,而是返回对对象的引用。是吗? - Olympian
@Olympian:不,这是一个实例化。 - user195488
@Mushin:只有当存在这样的>>重载时才可以。 - SLaks
@Muhsin: cin是istream对象,它有所有内置类型的重载运算符>>。可以将其视为cin.operator>>(x),也就是>>仅仅是cin的成员函数。 - user72424
@Olympian:它仍然是一个实例化。 - user195488
显示剩余4条评论

7

cinstd命名空间中的变量。

operator>>返回对cin的引用,因此您可以写:cin >> a >> b,而不是cin >> a; cin >> b;


6
上面的答案很有用。这里我想补充一点。 std::cinistream 类的一个对象,表示标准输入流(即键盘),对应于 C 语言中的 stdincin >> x 首先会从标准输入流读取一个整数,并将其赋值给 x,然后返回一个指向 cin 的自引用。因此,函数调用 cin >> x 的返回值仍然是 cin
因此,从 if 条件 的角度来看,if(cin)if(cin >> x) 相似。标准 IO 库 为流定义了一个类似于以下实现的函数:
explicit operator bool() const; // C++11

或者

operator void*() const; //C++98, C++2003

从这两个声明中,我们知道它们直接或间接地(通过明显的void*指针转换为bool)将流类型转换为bool类型。
在这两个函数中,它们依赖于一些基本的IO流状态(类字段)来确定是否返回false或true(对于void*情况,它是nullptr或不是)。
cin是istream类的一个实例,它继承了转换为bool的函数。所以它可以工作!

5
因为表达式的结果。
cin >> x

评估为

cin

读取流之后。

那就意味着我可以调用cin,它仍将具有x的值,直到我覆盖它? - Muhsin Ali
不,cin将始终是istream的一个实例。但变量x将继续保持其值,因此无论如何都不需要再次从流中获取它。 - Erix

2

std::cinstd::istream 类的一个实例。

cin >> x 只是在 cin 对象上调用一个函数。您可以直接调用该函数:

cin.operator >>(x);

为了让您一次读取多个变量,operator >>函数返回调用它的流的引用。您可以调用以下代码:
cin >> x >> y;

或等价地说:
cin.operator >>(x).operator >>(y);

或者:

std::istream& stream = cin.operator >>(x);
stream.operator >>(y);

这个难题的最后一部分是std::istream可以转换bool。 bool相当于调用!fail()

因此,在以下代码中:

int x;
std::istream& stream = std::cin.operator >>(x);
bool readOK = !stream.fail();
if (readOK)
{
  std::cout << x << "\n";
}

bool readOK = !stream.fail(); 可以简化为 bool readOK = stream;

您不需要一个单独的 bool 来存储流的状态,所以可以直接使用 if (stream)

删除临时的 stream 变量后得到 if (std::cin.operator >>(x))

直接使用运算符回到原始代码:

int x;
if (std::cin >> x)
{
  std::cout << x << "\n";
}

如果 std::cout<<(std::cin>>x); 失败,则输出 0,如果成功,则输出 0x486650,为什么它不会输出 1 - Abhishek Mane
1
@AbhishekMane,我不确定你的意思,但你可以打开一个新问题并提供更多细节。对于我来说,std::cout<<(std::cin>>x);无法编译。 - Alan Birtles
我在stackoverflow上提出了一个问题,请回答:https://stackoverflow.com/q/68849622/11862989 还有这个答案:https://dev59.com/mGw15IYBdhLWcg3wJ4is#6791854 。他提到了“它是语言用于if测试的特定版本转换运算符”。我认为这与我的问题有关,但我不理解。 - Abhishek Mane

1

1) cinistream的一个实例,参见http://www.cplusplus.com/reference/iostream/cin/

2) istream>>运算符将返回其左操作数,这里是cin,参见http://www.cplusplus.com/reference/istream/istream/operator%3E%3E/。如果没有从cin中提取出任何字符,则该运算符将设置failbit。如果读者已完成EOF,则不会再有字符可读,此时也会设置failbit

3) 在上述第2点中,当读取操作后条件被评估时,if (cin >> x) 应该像 if (cin) 一样,参考此链接 http://www.cplusplus.com/reference/ios/ios/operator_bool/,你会看到,这个 if 块将返回:

  • 如果至少设置了 failbitbadbit 中的一个,则返回空指针。否则返回其他某些值(适用于 C++98 标准)。

  • 如果至少设置了这些错误标志之一,则函数返回 false,否则返回 true(适用于 C++11 标准)。


0

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