关于c ++:printf和std :: string?

printf with std::string?

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

1
2
3
4
5
6
7
8
9
10
11
12
13
#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;
}

enter image description here

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


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

由于使用的是C ++,最简单的解决方法是使用std::cout正常打印,因为std::string通过操作符重载来支持:

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

如果出于某种原因需要提取C样式的字符串,则可以使用std::stringc_str()方法来获取以空值终止的const char *。使用您的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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。如果您撒谎是关于类型或数字,则该函数没有标准的知道方式,尽管有些编译器可以在撒谎时进行检查并发出警告。


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

请使用cout << your_string;。简短,简单且类型安全。实际上,在编写C ++时,通常希望完全避免使用printf -这是C语言的遗留物,在C ++中很少需要或有用。

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

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

    1
    2
    3
    4
    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本身根本无法正确完成工作。

  • 尽管它们非常紧凑,但是printf格式的字符串还是很难理解的。即使实际上每天都使用printf的C程序员中,我猜至少有99%的人需要检查一下内容,以确保%#x中的#的含义以及与#有何不同%#f中的>表示(是的,它们表示完全不同的东西)。

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

    谢谢


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

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

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

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


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

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


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

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

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