printf with std :: string?

问题描述 投票:133回答:7

我的理解是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个字符的字符串,例如在上面的输出中。

c++ string namespaces printf std
7个回答
214
投票

它正在编译,因为printf不是类型安全的,因为它在C sense1中使用变量参数。 printf没有选择std::string,只有C风格的字符串。用其他东西代替它所期望的东西肯定不会给你想要的结果。它实际上是未定义的行为,因此任何事情都可能发生。

解决这个问题的最简单方法,因为你正在使用C ++,正在使用std::cout正常打印,因为std::string通过运算符重载支持:

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

如果由于某种原因,你需要提取C风格的字符串,你可以使用c_str()std::string方法来获得一个空终止的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。如果你撒谎的类型或数字,该功能没有标准的知道方式,虽然有些编译器能够检查并在你撒谎时发出警告。


40
投票

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

请改用cout << 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意味着什么,以及它与#%#f的含义有何不同(是的,他们的意思完全不同)。

27
投票

如果你想要一个类似c的字符串(myString.c_str())与printf一起使用,请使用const char*

谢谢


6
投票

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

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

2
投票

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

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


1
投票

主要原因可能是C ++字符串是一个包含当前长度值的结构,而不仅仅是由0字节终止的字符序列的地址。 Printf及其亲属期望找到这样的序列,而不是结构,因此被C ++字符串搞糊涂了。

对我自己说,我相信printf有一个不容易被C ++语法特征填充的地方,就像html中的表结构有一个不容易被div填充的地方。正如戴克斯特拉后来写的关于goto,他并不打算开始一个宗教,并且真的只是反对用它作为弥补设计不佳的代码的kludge。

如果GNU项目将printf系列添加到他们的g ++扩展中,那将是相当不错的。


1
投票

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

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

© www.soinside.com 2019 - 2024. All rights reserved.