使用std :: string的printf?


157

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


8
只是让您知道,很多人批评这本书。我能理解,因为关于面向对象的编程的知识并不多,但是我认为它并不像人们声称的那样糟糕。
杰西·古德

!!好吧,当我沿本书前进时,记住这一点是一件好事。我敢肯定,它将不会是我明年或以后读的唯一的C ++书籍,所以我希望它不会造成太大的麻烦:)
TheDarkIn1978年

使用最高的编译器警告会回答你的问题- 用gcc编译。MSVC如何处理此问题-我不知道。
Peter VARGA,

Answers:


237

它之所以编译是因为printf类型安全,因为它使用的是C意义上的变量参数1printf没有选项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起所有主要编译器均支持)。您可以在这里找到一个示例。我对标准库中的实现一无所知,但具体来说,在Boost中可能没有boost::format


[1]:这意味着您可以传递任意数量的参数,但是该函数依赖于您告诉这些参数的数量和类型。在的情况下printf,这意味着带有编码类型信息的字符串,如%d含义int。如果您撒谎是关于类型或数字,则该函数没有标准的知道方式,尽管有些编译器可以在撒谎时进行检查并发出警告。


@MooingDuck,好点。这是杰里的答案,但作为人们公认的答案,这就是人们所看到的,他们可能会在见其他人之前离开。我添加了该选项,以便成为第一个看到的解决方案,也是推荐的解决方案。
克里斯,

43

请不要使用 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格式字符串可能还是不可读。即使是谁使用的C程序员printf每天几乎,我猜至少99%的人会需要寻找东西,以确保什么#%#x手段,以及如何从什么是不同#%#f手段(是的,他们的意思是完全不同的事情)。

11
@ TheDarkIn1978:您可能忘记了#include <string>。VC ++的标头中有一些奇怪之处,可让您定义一个字符串,但不将其发送给cout,而不包括<string>标头。
杰里·科芬

28
@Jerry:只想指出,在处理大数据时,使用printf比使用cout快得多。因此,请不要说它是没有用的:D
程序员

7
@Programmer:请参阅stackoverflow.com/questions/12044357/…。简介:大多数cout情况下速度较慢,这是因为您std::endl在不应该使用的地方使用过。
杰里·科芬

29
典型的C ++专家自大。如果确实存在printf,为什么不使用它呢?
kuroi neko 2014年

6
好,很抱歉发表评论。尽管如此,printf仍然非常便于调试,并且流虽然功能强大得多,但缺点是代码无法给出实际输出的任何信息。对于格式化输出,printf仍然是一个可行的选择,而且两个系统无法更好地协作也很可惜。当然只是我的意见。
kuroi neko 2014年



1

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

对于我自己来说,我相信printf的位置不容易被C ++语法功能填充,就像html中的表结构具有不容易被div填充的位置一样。正如Dykstra后来写的有关goto的文章一样,他无意开创一种宗教,实际上只是在争辩说要以宗教为借口来弥补设计不良的代码。

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


1

如果尺寸很重要,Printf实际上非常好用。这意味着,如果您正在运行一个内存问题的程序,那么printf实际上是一个非常好的解决方案。Cout实质上将位移开以为字符串留出空间,而printf只是接受某种参数并将其打印到屏幕上。如果要编译一个简单的hello world程序,则printf可以用不到60,000位(而不是cout)来编译它,而编译将花费一百万位以上。

对于您的情况,id建议仅使用cout,因为它使用起来更加方便。虽然,我认为printf是一个很好的了解。


1

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

虽然printf打印格式化文本的绝佳选择,(特别是如果您打算使用填充),但如果未启用编译器警告,则可能很危险。始终启用警告,因为这样的错误很容易避免。std::cout如果printf一家人能够以更快,更漂亮的方式完成相同的任务,那么就没有理由使用笨拙的机制。只要确保已启用所有警告(-Wall -Wextra)就可以了。如果您使用自己的自定义printf实现,则应使用使编译器能够根据提供的参数检查格式字符串__attribute__机制对其进行声明。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.