1 C++ std::string字符串格式化
在Python中,我们可以使用以下代码方便的格式化字符串
if __name__ == '__main__':
format_str = "There are {} fools in the world".format(10)
print(format_str)
不仅是Python,在其他高级语言中同样也可以很好地对字符串进行格式化。
本文将对C++中字符串格式化方法进行总结,包括:
- C语言中如何进行字符串格式化
- C++20之前的版本如何进行字符串格式化
- C++20的字符串格式化标准库函数std::format
1.1 C语言中的字符串格式化
在C语言中,我们可以使用
int sprintf(char* buffer, const char* format, ... ); //不推荐使用
int snprintf(char* buffer, std::size_t buf_size, const char* format, ... );
进行字符串格式化,例如
#include <iostream>
int main()
{
char format_str[64] = { 0 };
snprintf(format_str, sizeof(format_str) - 1, "There are %d fools in the world", 10);
std::cout << format_str << std::endl;
}
1.2 C++使用std::stringstream进行字符串格式化
在C++中,C++标准库在C++20之前并没有给std::string字符串类提供一个标准的字符串格式化函数,我们只能通过使用std::stringstream
字符串流来拼凑字符串,比如
#include <iostream>
#include <sstream>
int main()
{
std::stringstream ss;
ss << "There are ";
ss << 10;
ss << " fools in the world";
std::cout << ss.str() << std::endl;
return 0;
}
这种代码真的是又臭又长,特别是当格式化参数很多时。
1.3 开源的C++字符串格式化工具
本小节介绍简单好用跨平台的C++20之前的字符串格式化开源工具。
1.3.1 fmt
github地址:https://github.com/fmtlib/fmt
示例使用代码:
std::string s = fmt::format("The answer is {}.", 42);
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
1.3.2 format
Github地址:https://github.com/arajar/format
这是一个只有单个头文件的C++11标准的std::string字符串格式化工具,其只有一个Format.h
文件,头文件代码如下
#pragma once
#include <string>
#include <vector>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <iomanip>
namespace util
{
class ArgBase
{
public:
ArgBase() {}
virtual ~ArgBase() {}
virtual void Format(std::ostringstream &ss, const std::string& fmt) = 0;
};
template <class T>
class Arg : public ArgBase
{
public:
Arg(T arg) : m_arg(arg) {}
virtual ~Arg(){}
virtual void Format(std::ostringstream &ss, const std::string& fmt)
{
ss << m_arg;
}
private:
T m_arg;
};
class ArgArray : public std::vector < ArgBase* >
{
public:
ArgArray() {}
~ArgArray()
{
std::for_each(begin(), end(), [](ArgBase* p){ delete p; });
}
};
static void FormatItem(std::ostringstream& ss, const std::string& item, const ArgArray& args)
{
int index = 0;
int alignment = 0;
std::string fmt;
char* endptr = nullptr;
index = strtol(&item[0], &endptr, 10);
if (index < 0 || index >= args.size())
{
return;
}
if (*endptr == ',')
{
alignment = strtol(endptr + 1, &endptr, 10);
if (alignment > 0)
{
ss << std::right << std::setw(alignment);
}
else if (alignment < 0)
{
ss << std::left << std::setw(-alignment);
}
}
if (*endptr == ':')
{
fmt = endptr + 1;
}
args[index]->Format(ss, fmt);
return;
}
template <class T>
static void Transfer(ArgArray& argArray, T t)
{
argArray.push_back(new Arg<T>(t));
}
template <class T, typename... Args>
static void Transfer(ArgArray& argArray, T t, Args&&... args)
{
Transfer(argArray, t);
Transfer(argArray, args...);
}
template <typename... Args>
std::string Format(const std::string& format, Args&&... args)
{
if (sizeof...(args) == 0)
{
return format;
}
ArgArray argArray;
Transfer(argArray, args...);
size_t start = 0;
size_t pos = 0;
std::ostringstream ss;
while (true)
{
pos = format.find('{', start);
if (pos == std::string::npos)
{
ss << format.substr(start);
break;
}
ss << format.substr(start, pos - start);
if (format[pos + 1] == '{')
{
ss << '{';
start = pos + 2;
continue;
}
start = pos + 1;
pos = format.find('}', start);
if (pos == std::string::npos)
{
ss << format.substr(start - 1);
break;
}
FormatItem(ss, format.substr(start, pos - start), argArray);
start = pos + 1;
}
return ss.str();
}
}
使用方法
#include <iostream>
#include "Format.h"
int main()
{
std::string format_str = util::Format("There are {0} fools in the world",10);
std::cout << format_str << std::endl;
return 0;
}
1.3.3 sformat
Github:https://github.com/mmc1993/sformat
这同样是一个只有单个头文件的字符串格式化工具,在使用时我们只需要包含sformat.h
头文件,头文件的代码内容如下
#pragma once
#include <tuple>
#include <string>
#include <algorithm>
template <class T>
inline void ToString(std::string & ret, T && val)
{
ret.append(std::to_string(std::forward<T>(val)));
}
inline void ToString(std::string & ret, const std::string & val)
{
ret.append(val);
}
inline void ToString(std::string & ret, const char * val)
{
ret.append(val);
}
template <int N>
struct SFormatN {
static std::string Format(const char * fmt)
{
static_assert(false, "");
}
};
template <>
struct SFormatN<0> {
template <class ...ARGS>
static std::string Format(const char * fmt, const std::tuple<ARGS...> &)
{
return fmt;
}
};
template <class ...ARGS>
std::string SFormat(const char * fmt, const ARGS &...args)
{
const auto tuple = std::forward_as_tuple(args...);
return SFormatN<sizeof...(args)>::Format(fmt, tuple);
}
#define FMT_N(idx) case idx: ToString(ret, std::get<idx>(args)); break;
#define FMT_PARSE(N, ...) \
template <> \
struct SFormatN<N> { \
template <class... ARGS> \
static std::string Format(const char * fmt, const std::tuple<ARGS...> & args) \
{ std::string ret; \
while (*fmt != '\0') { auto idx = -1; \
if (*fmt == '{') { idx = 0; ++fmt; \
while (*fmt >= '0' && *fmt <= '9') \
{ idx *= 10; idx += (int)(*fmt++ - '0'); } \
if (*fmt != '}') idx = -1; else ++fmt; \
} \
switch (idx) { __VA_ARGS__ default: ret.append(1, *fmt++); break; } \
} \
return ret; \
} \
};
FMT_PARSE(1, FMT_N(0))
FMT_PARSE(2, FMT_N(0) FMT_N(1))
FMT_PARSE(3, FMT_N(0) FMT_N(1) FMT_N(2))
FMT_PARSE(4, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3))
FMT_PARSE(5, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4))
FMT_PARSE(6, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5))
FMT_PARSE(7, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6))
FMT_PARSE(8, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7))
FMT_PARSE(9, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8))
FMT_PARSE(10, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9))
FMT_PARSE(11, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10))
FMT_PARSE(12, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11))
FMT_PARSE(13, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12))
FMT_PARSE(14, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13))
FMT_PARSE(15, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14))
FMT_PARSE(16, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15))
FMT_PARSE(17, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16))
FMT_PARSE(18, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17))
FMT_PARSE(19, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18))
FMT_PARSE(20, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18) FMT_N(19))
FMT_PARSE(21, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18) FMT_N(19) FMT_N(20))
FMT_PARSE(22, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18) FMT_N(19) FMT_N(20) FMT_N(21))
FMT_PARSE(23, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18) FMT_N(19) FMT_N(20) FMT_N(21) FMT_N(22))
FMT_PARSE(24, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18) FMT_N(19) FMT_N(20) FMT_N(21) FMT_N(22) FMT_N(23))
从上述代码上看,我个人认为其实现的代码没有1.1.1节format简洁,并且其最大可支持的格式化参数为24个,这也是这个工具的缺陷。
使用方法
#include <iostream>
#include "sformat.h"
int main()
{
std::string format_str = SFormat("There are {0} fools in the world",10);
std::cout << format_str << std::endl;
return 0;
}
1.4 自定义的C++字符串格式化函数
我们可以使用可变参数模板+std::snprintf
定义一个字符串格式化函数
// std::string的字符串格式化函数
template<typename ... Args>
static std::string str_format(const std::string &format, Args ... args)
{
auto size_buf = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1;
std::unique_ptr<char[]> buf(new(std::nothrow) char[size_buf]);
if (!buf)
return std::string("");
std::snprintf(buf.get(), size_buf, format.c_str(), args ...);
return std::string(buf.get(), buf.get() + size_buf - 1);
}
// std::wstring的字符串格式化函数
template<typename ... Args>
static std::wstring wstr_format(const std::wstring &format, Args ... args)
{
auto size_buf = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1;
std::unique_ptr<char[]> buf(new(std::nothrow) char[size_buf]);
if (!buf)
return std::wstring("");
std::snprintf(buf.get(), size_buf, format.c_str(), args ...);
return std::wstring(buf.get(), buf.get() + size_buf - 1);
}
使用方法
#include <iostream>
template<typename ... Args>
static std::string str_format(const std::string& format, Args ... args)
{
auto size_buf = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1;
std::unique_ptr<char[]> buf(new(std::nothrow) char[size_buf]);
if (!buf)
return std::string("");
std::snprintf(buf.get(), size_buf, format.c_str(), args ...);
return std::string(buf.get(), buf.get() + size_buf - 1);
}
int main()
{
std::string format_str = str_format("There are %d fools in the world", 10);
std::cout << format_str << std::endl;
return 0;
}
1.5 C++20的字符串标准库函数std::format
经不住广大群众的吐槽,C++20标准终于推出了标准库的字符串格式化函数std::format
,该函数既支持std::string
也支持宽字符std::wstring
的格式化。
函数原型
template<class... Args>
std::string format(std::string_view fmt, const Args&... args);
template<class... Args>
std::wstring format(std::wstring_view fmt, const Args&... args);
template<class... Args>
std::string format(const std::locale& loc, std::string_view fmt, const Args&... args);
template<class... Args>
std::wstring format(const std::locale& loc, std::wstring_view fmt, const Args&... args);
函数使用
#include <iostream>
#include <format>
int main()
{
std::string format_str = std::format("There are {} fools in the world",10);
std::cout << format_str << std::endl;
return 0;
}
以上代码只能在支持C++20标准的编译器上编译通过,但是不得不感叹一句C++苦字符串格式化久矣!!!
本文作者:StubbornHuang
版权声明:本文为站长原创文章,如果转载请注明原文链接!
原文标题:C++ – std::string字符串格式化方法总结
原文链接:https://www.stubbornhuang.com/2413/
发布于:2022年11月17日 9:36:05
修改于:2024年01月30日 9:40:08
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
评论
52