【问题标题】:printf with std::string?printf 与 std::string?
【发布时间】:2023-03-11 05:45:01
【问题描述】:

我的理解是stringstd命名空间的成员,那么为什么会出现以下情况呢?

#include <iostream>

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString);
    cin.get();

    return 0;
}

程序每次运行时,myString 都会打印一个看似随机的 3 个字符的字符串,例如上面的输出。

【问题讨论】:

  • 只是为了让你知道,很多人criticize 那本书。我可以理解,因为面向对象编程的内容并不多,但我认为它并没有人们声称的那么糟糕。
  • 哎呀!好吧,在我读这本书的时候记住这一点是很好的。我敢肯定它不会是我在接下来一年左右的时间里会读到的唯一一本 C++ 书,所以我希望它不会造成太大的损害:)
  • 使用最高编译器警告将回答您的问题 - when 使用 gcc 编译。 MSVC 如何处理这个问题 - 我不知道。

标签: c++ string namespaces printf std


【解决方案1】:

它正在编译,因为 printf 不是类型安全的,因为它使用 C 意义上的可变参数1printf 没有 std::string 的选项,只有 C 样式的字符串。使用其他东西代替它所期望的绝对不会给你想要的结果。这实际上是未定义的行为,所以任何事情都可能发生。

由于您使用的是 C++,因此解决此问题的最简单方法是使用 std::cout 正常打印它,因为 std::string 通过运算符重载支持它:

std::cout << "Follow this command: " << myString;

如果由于某种原因需要提取 C 风格的字符串,可以使用 std::stringc_str() 方法来获取以 null 结尾的 const char *。使用您的示例:

#include <iostream>
#include <string>
#include <stdio.h>

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString.c_str()); //note the use of c_str
    cin.get();

    return 0;
}

如果您想要一个类似于 printf 的函数,但类型安全,请查看可变参数模板(C++11,自 MSVC12 起在所有主要编译器上都支持)。您可以找到一个here 的示例。我知道在标准库中没有类似的实现,但在 Boost 中可能有,特别是 boost::format


[1]:这意味着你可以传递任意数量的参数,但函数依赖于你告诉它这些参数的数量和类型。对于printf,这意味着带有编码类型信息的字符串,例如%d,意思是int。如果你对类型或数字撒谎,该函数没有标准的获知方式,尽管一些编译器能够在你撒谎时检查并发出警告。

【讨论】:

  • @MooingDuck,好点子。这是在杰瑞的答案中,但作为公认的答案,这是人们所看到的,他们可能会在看到其他人之前离开。我已经添加了该选项,以便成为第一个看到的解决方案,也是推荐的解决方案。
  • 更新:std::format 包含在 C++20 中,虽然它目前不包含在 clang 中(不确定 gcc)
  • @OvinusReal,是的,但还没有新功能可以打印到stdoutstd::format 单独在这里实际上并没有帮助,因为没有真正的格式化(cout &lt;&lt; "..." &lt;&lt; s;cout &lt;&lt; format("... {}", s);)。
【解决方案2】:

请不要使用printf("%s", your_string.c_str());

请改用cout &lt;&lt; your_string;。简短、简单且类型安全。事实上,当您编写 C++ 时,您通常希望完全避免使用 printf —— 它是 C 的剩余部分,在 C++ 中很少需要或有用。

至于为什么你应该使用cout而不是printf,原因很多。以下是一些最明显的示例:

  1. 如问题所示,printf 不是类型安全的。如果您传递的类型与转换说明符中给出的类型不同,printf 将尝试使用它在堆栈上找到的任何内容,就好像它是指定的类型一样,给出未定义的行为。一些编译器可以在某些情况下发出警告,但一些编译器不能/根本不会,而且在所有情况下都不能。
  2. printf 不可扩展。您只能将原始类型传递给它。它理解的一组转换说明符在其实现中是硬编码的,您无法添加更多/其他。大多数编写良好的 C++ 应该主要使用这些类型来实现面向正在解决的问题的类型。
  3. 它使体面的格式化变得更加困难。举一个明显的例子,当您打印数字供人们阅读时,您通常希望每隔几位插入数千个分隔符。用作分隔符的确切位数和字符各不相同,但cout 也涵盖了这一点。例如:

    std::locale loc("");
    std::cout.imbue(loc);
    
    std::cout << 123456.78;
    

    无名语言环境(“”)根据用户的配置选择一个语言环境。因此,在我的机器(配置为美国英语)上,它打印为123,456.78。对于将计算机配置为(例如)德国的人,它会打印出类似123.456,78 的内容。对于为印度配置的人,它会打印为1,23,456.78(当然还有很多其他的)。使用printf,我只得到一个结果:123456.78。它是一致的,但对于世界各地的每个人来说,它始终错误。本质上,解决它的唯一方法是单独进行格式化,然后将结果作为字符串传递给printf,因为printf 本身根本不会正确地完成这项工作。

  4. 尽管printf 格式的字符串非常紧凑,但它们可能非常难以阅读。即使在几乎每天都使用printf 的 C 程序员中,我猜至少 99% 的人都需要查明%#x 中的# 是什么意思,以及这与@987654343 的含义有何不同@ in %#f 表示(是的,它们的意思完全不同)。

【讨论】:

  • @TheDarkIn1978:你可能忘了#include &lt;string&gt;。 VC++ 的标头中有一些奇怪之处,可以让您定义一个字符串,但不能将其发送到 cout,而不包括 &lt;string&gt; 标头。
  • @Jerry:只想指出,在处理大数据时,使用 printf 比使用 cout 快得多。因此,请不要说它没用:D
  • @Programmer:见stackoverflow.com/questions/12044357/…。总结:大多数时候cout 比较慢,是因为你在不该使用的地方使用了std::endl
  • 典型的 C++ 专家傲慢。如果 printf 确实存在,为什么不使用它呢?
  • 好的,很抱歉评论这么快。尽管如此,printf 对于调试来说还是很方便的,而且流虽然功能强大得多,但也有一个缺点,即代码没有给出任何关于实际输出的想法。对于格式化输出, printf 仍然是一个可行的替代方案,遗憾的是两个系统不能更好地合作。当然,这只是我的看法。
【解决方案3】:

如果您希望类似 c 的字符串 (const char*) 与 printf 一起使用,请使用 myString.c_str()

谢谢

【讨论】:

    【解决方案4】:

    使用std::printf 和 c_str() 示例:

    std::printf("Follow this command: %s", myString.c_str());
    

    【讨论】:

      【解决方案5】:

      printf 接受可变数量的参数。那些只能有普通旧数据(POD)类型。将 POD 以外的任何内容传递给 printf 的代码只会编译,因为编译器假定您的格式正确。 %s 表示相应的参数应该是指向char 的指针。在您的情况下,它是 std::string 而不是 const char*printf 不知道,因为参数类型丢失了,应该从格式参数中恢复。当将 std::string 参数转换为 const char* 时,结果指针将指向一些不相关的内存区域,而不是您想要的 C 字符串。因此,您的代码会打印出乱码。

      虽然printfan excellent choice for printing out formatted text(尤其是如果您打算使用填充),但如果您没有启用编译器警告,则可能会很危险。 始终启用警告,因为这样的错误很容易避免。如果printf 家族可以以更快、更漂亮的方式完成相同的任务,则没有理由使用笨拙的std::cout 机制。只要确保您已启用所有警告 (-Wall -Wextra),您就会很好。如果您使用自己的自定义 printf 实现,您应该使用 __attribute__ 机制声明它,enables the compiler to check the format string against the parameters provided

      【讨论】:

        【解决方案6】:

        主要原因可能是 C++ 字符串是一个包含当前长度值的结构,而不仅仅是以 0 字节结尾的字符序列的地址。 Printf 及其亲属希望找到这样的序列,而不是结构,因此会被 C++ 字符串混淆。

        就我自己而言,我认为 printf 有一个 C++ 语法特性无法轻易填补的地方,就像 html 中的表结构有一个 div 无法轻易填补的地方一样。正如 Dykstra 后来关于 goto 所写的那样,他并不打算创立一种宗教,实际上只是反对将其用作弥补设计不佳的代码的工具。

        如果 GNU 项目将 printf 系列添加到他们的 g++ 扩展中,那就太好了。

        【讨论】:

          【解决方案7】:

          如果大小很重要,Printf 实际上非常好用。这意味着,如果您正在运行一个内存问题的程序,那么 printf 实际上是一个非常好的解决方案。 Cout 本质上是移动位来为字符串腾出空间,而 printf 只是接受某种参数并将其打印到屏幕上。如果您要编译一个简单的 hello world 程序,printf 将能够在不到 60, 000 位的时间内编译它,而 cout 则需要超过 100 万位才能编译。

          对于您的情况,id 建议使用 cout,因为它使用起来更方便。虽然,我认为 printf 是一件好事。

          【讨论】:

            【解决方案8】:

            您可以使用snprinft 来确定所需的字符数并分配合适大小的缓冲区。

            int length = std::snprintf(nullptr, 0, "There can only be %i\n", 1 );
            char* str = new char[length+1]; // one more character for null terminator
            std::snprintf( str, length + 1, "There can only be %i\n", 1 );
            std::string cppstr( str );
            delete[] str;
            

            这是对 cppreference.com 上 example 的小改编

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-12-10
              • 1970-01-01
              • 2011-09-24
              • 2011-09-17
              • 1970-01-01
              相关资源
              最近更新 更多