188

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

#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 个字符的字符串,例如上面的输出。

4

8 回答 8

283

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

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

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

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

#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。如果您对类型或数字撒谎,该函数没有标准的获知方式,尽管一些编译器能够在您撒谎时检查并发出警告。

于 2012-06-02T21:09:02.503 回答
44

请不要使用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% 的人需要查明#in 的%#x含义,以及它与#in 的%#f含义有何不同(是的,它们的含义完全不同)。
于 2012-06-02T21:36:08.567 回答
35

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

谢谢

于 2012-06-02T21:08:56.180 回答
11

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

std::printf("Follow this command: %s", myString.c_str());
于 2015-11-26T17:27:18.277 回答
2

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

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

于 2017-09-18T10:52:03.717 回答
1

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

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

于 2016-06-17T05:51:09.863 回答
1

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

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

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

于 2015-09-15T20:02:23.483 回答
0

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

于 2022-02-03T15:47:19.713 回答