Printf与std::字符串?

我的理解是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;
}

enter image description here

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

474609 次浏览

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

谢谢

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

修复这个问题最简单的方法,因为你在使用c++,通常是用std::cout打印它,因为std::string通过操作符重载支持这一点:

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

如果出于某种原因,你需要提取c风格的字符串,你可以使用std::stringc_str()方法来获得一个以空结束的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。如果你在类型或数字上撒谎,函数没有标准的方法来知道,尽管一些编译器有能力在你撒谎时检查并给出警告。

请不要使用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中的#是什么意思(是的,它们的意思完全不同)。

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

就我个人而言,我相信printf有一个c++语法特性无法轻易填充的地方,就像html中的表结构有一个div无法轻易填充的地方一样。就像Dykstra后来写的goto一样,他并不打算开始一种宗教,实际上只是反对把它作为一个拼凑品来弥补设计糟糕的代码。

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

使用std:: printf和c_str() 例子:< / p >
std::printf("Follow this command: %s", myString.c_str());

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

对于您的情况,我建议使用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__机制来声明它。

您可以使用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上例子的一个小改编