【问题标题】:What are the historical reasons for the fact that scanf and printf have no analogue in C++ streams?scanf 和 printf 在 C++ 流中没有类似物的历史原因是什么?
【发布时间】:2023-03-07 18:22:01
【问题描述】:
printf("%d: %d, %d\n", foo, bar, baz);

干净得多
std::cout << foo << ": " << bar << ", " << baz << "\n";

而且根本没有明显的重写方法

scanf("%d: %d, %d\n", &foo, &bar, &baz);

除了,说

std::cin >> foo;
std::cin.ignore();
std::cin >> bar;
std::cin.ignore();
std::cin >> baz;
std::cin.ignore();

由于显而易见的原因,这是劣等的。

为什么没有像istream::scanfostream::printf这样的函数?我看不出有什么理由不能做到以下几点:

std::cout.printf("%d: %d, %d\n", foo, bar, baz);
std::cin.scanf("%d: %d, %d\n", foo, bar, baz);

我敢肯定,一定有人在某个时候为标准提出了它,但它一定被拒绝了。为什么?

【问题讨论】:

  • 因为 scanf 和 printf 本质上是不安全的。他们使用数据来查找代码,这是致命错误的良好来源。在 boost 库中有一个基于模板的安全模拟 printf。
  • 偶尔,我将template&lt;int N&gt; std::istream&amp; operator&gt;&gt; (std::istream&amp; i, char const(&amp;)[N]) { return i.ignore(N); } 用于非常基本的输入任务(可能加上错误检查)。然后你可以写std::cin &gt;&gt; foo &gt;&gt; ": " &gt;&gt; bar &gt;&gt; ", " &gt;&gt; baz &gt;&gt; "\n"; 但是,运算符中仍然存在不对称性。
  • 我们现在有可变参数模板;在原始流设计中,这些函数必须使用可变参数列表(带有省略号和va_args)。您会丢失类型信息(安全性)并且无法恢复类型以允许自定义类型重载(输出 MyClassType)。所以也许这个问题的一部分是为什么它没有被添加到 C++11
  • 与 dyp 类似,我有一个 ignore 类,它允许我写 std::cin &gt;&gt; foo &gt;&gt; ignore(": ") &gt;&gt; bar &gt;&gt; ignore(", ") &gt;&gt; baz; 我觉得额外的冗长是值得的。

标签: c++ c++11 iostream history c++-standard-library


【解决方案1】:

您仍然可以在 C++ 中使用 printfscanf。那么,你的提议有什么好处呢?只是给他们不同的名字,你什么也得不到。

它们不是 C++ 风格的函数,主要是因为它们不是类型安全的,并且容易出现编译器检查无法避免的运行时错误。

这些函数的 C++ 类似物是 stringstream

【讨论】:

  • 对于标准流,您可以根据需要使用scanfprintf。对于fstreams,你不能。将一个文件打开两次,同时打开一个 C 文件和一个 C++ fstream 似乎很愚蠢,这样除了 C++ 流操作之外,您还可以在其上使用 scanfprintf
  • 我明白你的意思,但这些确实是肮脏的功能。如果您了解它们是多么非 C++,那么应该很清楚为什么设计者一直不愿意继续 C++ 中的格式说明符习语。有一些 C++ 替代方案更安全,比如Boost::Format,但我不会说它被普遍接受。 printf 的吸引力在于它的代码量非常少。 这不是好的 C++ 设计的吸引力
  • 是的,您仍然可以在 C++ 中使用 C 函数,但它们不会为您提供 C++ std::strings。 fprintf() 对于格式化输出内容和通过open_memstream() 生成字符串都非常有用。后者会为您生成完美分配的 C 字符串,但不会生成 C++ std::string。在这方面,C++ 系统确实不如 C 系统,而且不具备真正的互操作性。
【解决方案2】:

首先,您始终可以使用 C++ 中的 C 函数。

其次,很少有“真正的”程序使用 scanf,因为您通常需要一些可以让您更好地控制您阅读的内容以及如何将其与您不想阅读的输入内容分开的东西。程序从文件或某些用户界面读取,但很少从“标准输入”读取,如果这样做,就像过多的 unix 工具一样,它们通常不使用 scanf。

使用 printf,可能没有任何理由在 C++ 类中将 printf 或 sprintf 复制为字符串。

【讨论】:

  • re: 从文件读取,如果 scanfprintf 等价物在 istreamostream 中实现,就像我说的那样,它们也可用于读取和写入文件。
【解决方案3】:

类型安全,并以不可检查的方式使用数据确定程序流程。

在 C++11 之前,C++ 中没有类型安全的变量构造。因此,可变的 printf 语法被禁止。现在你可以接近了:

cpprintf("bob %s your %s! %d\n")<< "is" << "uncle" << 42;

我们在其中创建一个格式化程序对象,然后将&lt;&lt; 参数放入其中。

这里的缺点是字符串"bob... 正在控制代码流——数据控制代码是错误和漏洞的严重来源。

当用户定义的 constexpr 文字到达时,我们终于能够“修复”这个问题,因此可以在编译时解析格式字符串并检查参数的类型安全性。

哦,最后一个问题是printf 不允许对象自己格式化——它不能被 printf 用户扩展。

【讨论】:

  • “arfuments 检查了类型安全性。” 错字。无论如何,您不需要这些信息,它是多余的(我不确定为什么人们坚持使用 C printf 语法):print("% is the %, %\n", 42, "solution", "Arthur"); 格式化字符串无论好坏都允许简单的动态格式化。顺便说一句:用户定义的 constexpr 文字有什么问题? UD 文字运算符不能是constexpr 吗?或者你想对字符串的内容进行元编程?
  • @dyp 我实际上想要的不仅仅是constexpr(我措辞...错误)——我希望字符串作为类型的内容,所以"%d -- %d" 的类型是 '吃两个整数',而不是'吃一些参数,如果有空间就塞进去'&lt;char...&gt; 样式语法,我认为它已被删除,这就是我所说的。
  • 我想我也是这个意思 :) "metaprogramming on the contents" 我们已经可以进行编译时字符串解析,但是使用它来计算类型是不切实际的,因为所需的调用机制(通过宏分解字符串,或将其作为静态成员函数放入本地类型等)不幸的是,&lt;char...&gt; 语法仅允许用于整数/浮点类型,但不适用于字符串:(
  • @dyp 作为解决方法,使用十六进制。每个代码点都被编码为两个或多个数字。前导数字编码以下序列的子类型。所以我们得到了像0x1d010C01480165016C016C016F01200157016F0172016C016401211d_printf(3,7) 这样的精彩内容,意思是printf("%dHello world!%d",3,7)——类型安全,易于阅读! :p
  • 哦,这将在 C++1y 中更加更简洁——我们终于可以使用数字组分隔符了:0B00011101'00011010010010000110010101101100011011000110111101010111011011110111001001101100011001000010000100000000'00011101_printf(3,7) sa 是一个错字;)
猜你喜欢
  • 2017-05-17
  • 2015-08-06
  • 2016-01-03
  • 1970-01-01
  • 1970-01-01
  • 2017-01-07
  • 1970-01-01
  • 2019-02-15
  • 1970-01-01
相关资源
最近更新 更多