C++中的String.Format替代方案

34

我没有很多C++的工作经验,相比之下我更多地在C#上工作,因此,我想通过与我在那里所做的事情相关来提出我的问题。我必须生成特定格式的字符串,并将其传递给另一个函数。在C#中,我可以轻松地通过以下简单代码生成字符串。

string a = "test";
string b = "text.txt";
string c = "text1.txt";

String.Format("{0} {1} > {2}", a, b, c);

通过生成这样的字符串,我应该能够将其传递给system()函数。但是,system只接受char*参数。

我使用的是Win32 C++(不是C++/CLI),不能使用boost,因为它会包括所有文件的大量包含,而我的项目非常小。像sprintf()之类的东西对我很有用,但是sprintf不接受abc参数作为string类型。你有什么建议可以帮助我生成这些格式化字符串以便在程序中传递给system函数?


2
你知道Boost不会向你的二进制文件添加任何依赖项,对吧?(当然,它会向源代码添加依赖项) - Shep
7个回答

45

使用C++的方式是使用std::stringstream对象:

std::stringstream fmt;
fmt << a << " " << b << " > " << c;

使用 sprintf 是C语言的方法。

C方式难以正确处理,因为:

  • 它不安全
  • 需要缓冲区管理

当然,如果性能为关键问题(比如创建固定大小的百万个小的 stringstream 对象并将它们丢弃),您可能希望退回到 C 方法。


system(fmt) 给我一个错误,说 stringstreamchar* 之间没有合适的转换。 - user1240679
2
system(fmt.str().c_str()) 应该可以解决问题。请注意,在 Windows 上,如果调用 system() 并且即使命令及其参数正确并已正确引用也出现错误,请将整个内容用引号括起来,这样就可以解决问题了。 - Shadow2531
需要注意两点:1)system是实现定义的--因此,如果您想使代码具有可移植性,请不要使用它。2)正如其他人所提到的,使用 fmt.str().c_str() 来获取字符串的 char *(C风格字符串)表示形式。 - dirkgently

32

为了完整起见,你可以使用std::stringstream

#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::string a = "a", b = "b", c = "c";
    // apply formatting
    std::stringstream s;
    s << a << " " << b << " > " << c;
    // assign to std::string
    std::string str = s.str();
    std::cout << str << "\n";
}

或者(在这种情况下)std::string自身的字符串连接能力:

#include <iostream>
#include <string>

int main() {
    std::string a = "a", b = "b", c = "c";
    std::string str = a + " " + b + " > " + c;
    std::cout << str << "\n";
}

参考资料:


如果您真的想采用C方式,以下是相关内容:

#include <iostream>
#include <string>
#include <vector>
#include <cstdio>

int main() {
    std::string a = "a", b = "b", c = "c";
    const char fmt[] = "%s %s > %s";
    // use std::vector for memory management (to avoid memory leaks)
    std::vector<char>::size_type size = 256;
    std::vector<char> buf;
    do {
        // use snprintf instead of sprintf (to avoid buffer overflows)
        // snprintf returns the required size (without terminating null)
        // if buffer is too small initially: loop should run at most twice
        buf.resize(size+1);
        size = std::snprintf(
                &buf[0], buf.size(),
                fmt, a.c_str(), b.c_str(), c.c_str());
    } while (size+1 > buf.size());
    // assign to std::string
    std::string str = &buf[0];
    std::cout << str << "\n";
}

参考资料:


C++11之后,你可以“简化”为:

#include <iostream>
#include <string>
#include <vector>
#include <cstdio>

int main() {
    std::string a = "a", b = "b", c = "c";
    const char fmt[] = "%s %s > %s";
    // can use std::string as buffer directly (since C++11)
    std::string::size_type size = 256;
    std::string str;
    do {
        str.resize(size+1);
        // use snprintf instead of sprintf (to avoid buffer overflows)
        // snprintf returns the required size (without terminating null)
        // if buffer is too small initially: loop should run at most twice
        size = std::snprintf(
                &str[0], str.size(),
                fmt, a.c_str(), b.c_str(), c.c_str());
    } while (size+1 > str.size());
    // can strip off null-terminator, as std::string adds their own
    str.resize(size);
    // done
    std::cout << str << "\n";
}

供参考:


此外,还有Boost格式化库。对于您的示例,请看以下内容:

#include <iostream>
#include <string>
#include <boost/format.hpp>

int main() {
    std::string a = "a", b = "b", c = "c";
    // apply format
    boost::format fmt = boost::format("%s %s > %s") % a % b % c; 
    // assign to std::string
    std::string str = fmt.str();
    std::cout << str << "\n";
}

2
OP提到了boost不能使用 :/ - dirkgently

19
除了其他人提出的选项外,我可以推荐fmt库,它实现了类似于Python中str.format和C#中的String.Format的字符串格式化。以下是一个示例:
std::string a = "test";
std::string b = "text.txt";
std::string c = "text1.txt";
std::string result = fmt::format("{0} {1} > {2}", a, b, c);

免责声明:我是该库的作者。

10
你可以使用sprintf结合std::string.c_str()c_str()返回一个const char*并且可以与sprintf一起使用。
string a = "test";
string b = "text.txt";
string c = "text1.txt";
char* x = new char[a.length() + b.length() + c.length() + 32];

sprintf(x, "%s %s > %s", a.c_str(), b.c_str(), c.c_str() );

string str = x;
delete[] x;

如果你知道大小,你可以使用预分配的char数组:

string a = "test";
string b = "text.txt";
string c = "text1.txt";
char x[256];

sprintf(x, "%s %s > %s", a.c_str(), b.c_str(), c.c_str() );

应该不是 {0},{1},{2},应该是 %s 吧?这个 char* x 操作完成后我需要删除它吗,还是无所谓呢? - user1240679
在sprintf中,{0}、{1}、{2}分别代表什么?delete[x]是什么? - Igor R.
@user1240679,那是一个打字错误,这是更正后的版本。如果使用new分配了内存,请删除char*,否则会造成内存泄漏。 - Luchian Grigore
2
@LuchianGrigore:我强烈建议使用std::vectorsnprintf的组合,以避免内存泄漏和缓冲区溢出。请参阅snprintf - Matthieu M.

3

为了完整起见,使用boost::format是一种更好的方法。

cout << boost::format("%s %s > %s") % a % b % c;

随你选择。使用 boost 解决方案具有类型安全的优点,可以使用 sprintf 格式(对于那些觉得 << 语法有点笨重的人来说)。


2

如前所述,C++的做法是使用字符串流。

#include <sstream>

string a = "test";
string b = "text.txt";
string c = "text1.txt";

std::stringstream ostr;
ostr << a << " " << b << " > " << c;

请注意,您可以通过以下方式从字符串流对象获取C字符串。
std::string formatted_string = ostr.str();
const char* c_str = formatted_string.c_str();

2
你可以将字符串连接起来,构建一个命令行。
std::string command = a + ' ' + b + " > " + c;
system(command.c_str());

你不需要任何额外的库来完成这个任务。


1
据我所知,C++ 不会对此进行优化,因此每次操作都会复制该字符串,首先创建 a + ' ',然后是 a + ' ' + b,以此类推。 - c z

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接