printf 与 std::string?

printf with std::string?

提问人:Chunky Chunk 提问时间:6/3/2012 最后编辑:Micha WiedenmannChunky Chunk 更新时间:2/19/2023 访问量:519764

问:

我的理解是,它是命名空间的成员,那么为什么会出现以下情况呢?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

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

C++ 字符串 命名空间 printf std

评论

8赞 Jesse Good 6/3/2012
只是为了让你知道,很多人批评那本书。我能理解,因为面向对象编程的内容并不多,但我不认为它像人们声称的那么糟糕。
0赞 Chunky Chunk 6/3/2012
哎呀!好吧,在我阅读这本书时记住这一点是件好事。我敢肯定,这不会是我在未来一年左右的时间里会读的唯一一本C++书,所以我希望它不会做太多的:)
0赞 Peter VARGA 4/10/2018
使用 gcc 编译,使用最高编译器警告将回答您的问题。MSVC 如何处理这个问题 - 我不知道。

答:

43赞 Alessandro Pezzato 6/3/2012 #1

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

谢谢

321赞 chris 6/3/2012 #2

C++ 23 更新

我们现在终于有一种直接用于输出的方法:std::printstd::format

#include <print>
#include <string>

int main() {
    // ...
    std::print("Follow this command: {}", myString);
    // ...
}

这结合了两种方法的优点。

原始答案

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

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

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

如果出于某种原因需要提取 C 样式的字符串,则可以使用 的方法来获取以 null 结尾的字符串。使用您的示例:c_str()std::stringconst 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;
}

如果您想要一个类似于 但类型安全的函数,请查看可变参数模板(C++11,自 MSVC12 起在所有主要编译器上都支持)。你可以在这里找到一个例子。据我所知,在标准库中没有这样的实现,但在 Boost 中可能有,特别是 boost::formatprintf


[1]:这意味着你可以传递任意数量的参数,但函数依赖于你来告诉它这些参数的数量和类型。在 的情况下,这意味着一个字符串,其编码类型信息如 meaning。如果你在类型或数字上撒谎,该函数没有标准的知道方式,尽管一些编译器能够在你撒谎时进行检查并发出警告。printf%dint

评论

0赞 chris 1/9/2013
@MooingDuck,好点子。这是杰瑞的答案,但作为公认的答案,这就是人们看到的,他们可能会在看到其他人之前离开。我添加了该选项,以便成为看到的第一个解决方案,也是推荐的解决方案。
0赞 Ovinus Real 4/15/2021
更新:std::format 包含在 C++20 中,尽管它目前不包含在 clang 中(不确定 gcc)
1赞 chris 4/15/2021
@OvinusReal,是的,但还没有新功能可以打印到 . 单独在这里实际上没有帮助,因为没有真正的格式化(vs. )。stdoutstd::formatcout << "..." << s;cout << format("... {}", s);
39赞 Jerry Coffin 6/3/2012 #3

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

请改用。短小精悍,类型安全。事实上,当你编写C++时,你通常希望完全避免 - 它是C的遗留物,在C++中很少需要或有用。cout << your_string;printf

至于为什么要用而不是,原因很多。以下是一些最明显的示例:coutprintf

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

    std::locale loc("");
    std::cout.imbue(loc);
    
    std::cout << 123456.78;
    

    无名称区域设置 (“”) 根据用户的配置选择区域设置。因此,在我的机器(配置为美式英语)上,这将打印为 .对于将计算机配置为(例如)德国的人来说,它会打印出类似 .对于为印度配置了它的人来说,它会打印出来(当然还有很多其他的)。我得到的只有一个结果:.这是一致的,但对世界各地的每个人来说都是错误的。从本质上讲,解决它的唯一方法是单独进行格式化,然后将结果作为字符串传递给 ,因为其本身根本无法正确完成这项工作。123,456.78123.456,781,23,456.78printf123456.78printfprintf

  4. 尽管它们非常紧凑,但格式字符串可能非常难以读取。即使在几乎每天都在使用的 C 程序员中,我猜至少有 99% 的人需要查找一些东西来确定 in 的含义,以及它与 in 的含义有何不同(是的,它们的含义完全不同)。printfprintf#%#x#%#f

评论

11赞 Jerry Coffin 6/3/2012
@TheDarkIn1978:你可能忘了.VC++ 的标头中有一些奇怪的地方,可以让你定义一个字符串,但不能在不包含标头的情况下将其发送到 。#include <string>cout<string>
43赞 Programmer 2/6/2013
@Jerry:我只想指出,在处理大数据时,使用 printf 比使用 cout 快得多。因此,请不要说它是无用的:D
9赞 Jerry Coffin 2/6/2013
@Programmer:见 stackoverflow.com/questions/12044357/...。摘要:大多数时候,速度较慢,这是因为你使用了不应该使用的地方。coutstd::endl
10赞 kuroi neko 8/31/2014
好的,对不起,评论很活泼。尽管如此,printf 对于调试来说还是非常方便的,而且流虽然功能强大得多,但有一个缺点,即代码没有给出任何实际输出的想法。对于格式化输出,printf 仍然是一个可行的替代方案,可惜两个系统不能更好地合作。当然,这只是我的意见。
6赞 xryl669 3/15/2020
有一天,你必须为i18n翻译一个大型软件的所有输出,你真的会非常讨厌那些教你使用cout的人。cout 是愚蠢的,而不是无状态的(如果你使用一些修改 cout 状态的库,你最终会得到这样的代码“cout << 64”输出“40”。像这样的代码是不可翻译的,依此类推。printf 没有任何这些设计缺陷。cout << "The" << color << "house"
1赞 MMacD 9/16/2015 #4

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

就我自己而言,我相信 printf 有一个不容易被 C++ 语法特征填补的地方,就像 html 中的表格结构有一个不容易被 div 填补的地方一样。正如 Dykstra 后来写到的关于 goto 的文章,他并不打算建立一种宗教,实际上只是反对将其用作弥补设计不佳的代码的障碍。

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

15赞 Adel Ben Hamadi 11/27/2015 #5

使用 std::p rintf 和 c_str() 例:

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

评论

0赞 Sohail Si 5/16/2023
一个临时对象(值)在被馈送到包含函数时是否有效?c_str()std::printf()
0赞 Sohail Si 5/16/2023
回答我的问题:stackoverflow.com/questions/10006891/......
0赞 Sohail Si 5/16/2023
引用自:“你所关心的字符串对象是临时的。它将在完整表达式的末尾被破坏,而不是在之前,也不是之后 stackoverflow.com/users/649665/james-kanzejames-kanze
1赞 Adel Ben Hamadi 5/20/2023
@SohailSi引人入胜的问题和建设性的回答 stackoverflow.com/questions/10006891/......
0赞 howard howard 6/17/2016 #6

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

对于您的情况,id 建议使用 cout,因为它使用起来更方便。虽然,我认为 printf 是值得了解的。

2赞 Hyena 9/18/2017 #7

printf接受可变数量的参数。这些只能具有普通旧数据 (POD) 类型。将 POD 以外的任何内容传递给的代码只会编译,因为编译器假定您的格式正确。 表示相应的参数应该是指向 .就您而言,它不是. 不知道它,因为参数类型丢失了,应该从格式参数恢复。当将该参数转换为生成的指针时,指针将指向某个不相关的内存区域,而不是您想要的 C 字符串。出于这个原因,您的代码会打印出乱码。printf%scharstd::stringconst char*printfstd::stringconst char*

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

3赞 shuhalo 2/3/2022 #8

您可以使用它来确定所需的字符数并分配适当大小的缓冲区。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 示例的一个小改编

评论

0赞 Adam Erickson 5/24/2022
似乎有点啰嗦......
1赞 QuentinUK 2/19/2023 #9

这是一种通用的方法。

#include <string>
#include <stdio.h>

auto print_helper(auto const & t){
    return t;
}
auto print_helper(std::string const & s){
    return s.c_str();
}
std::string four(){
    return "four";
}
template<class ... Args>
void print(char const * fmt, Args&& ...args){
    printf(fmt, print_helper(args) ...);
}
int main(){
    std::string one {"one"};
    char const * three = "three";
    print("%c %d %s %s, %s five", 'c', 3+4, one + " two", three, four());
}