QString.arg
在Qt中QString有个arg方法,使用方式如下:
QString i; // current file's number
QString total; // number of files to process
QString fileName; // current file's name
QString status = QString("Processing file %1 of %2: %3")
.arg(i).arg(total).arg(fileName);
通过这种方式即可构造出一个字符串:arg(i)
替换了%1
;arg(total)
替换了%2
;arg(fileName)
替换了%3
。
StringBuilder
参照QString.arg
的接口,实现类似的使用方法:
StringBuilder("Arg 1:{1};Arg 2:{2};Arg1:{1}").arg(参数).arg(参数2)
实现思路为:
- 构造时记录
format
; - 记录参数;
- 通过接口返回字符串。
构造函数
class StringBuilder
{
private:
std::string m_strFmt;
std::vector<std::string> args;
public:
StringBuilder(const char* strVal):m_strFmt(strVal){};
StringBuilder(const std::string& strVal):m_strFmt(strVal){};
}
记录参数
为了简单起见,当记录参数时直接将其转换为字符串:
std::vector<std::string> args;
template<typename T>
StringBuilder& arg(const T& argVal)
{
args.emplace_back(std::to_string(argVal));
return *this;
}
StringBuilder& arg(const char* argVal)
{
args.emplace_back(std::string(argVal));
return *this;
}
StringBuilder& arg(const std::string& argVal)
{
args.push_back(argVal);
return *this;
}
- 通过使用
std::to_string
和模板来支持各种基础类型向字符串转换; - 为
const char*
和const std::string
定制实现; - 使用
emplace_back
避免重复构造std::string
; - 使用
return *this
返回自身以支持.arg().arg()
写法。
返回字符串
通过仿函数返回字符串
//演示实现
std::string operator()()
{
std::string strResult;
std::size_t offset = 0;
while (true)
{
auto begin = m_strFmt.find_first_of('{',offset);
auto end = m_strFmt.find_first_of('}',offset);
if (begin >= m_strFmt.npos-1 || end >= m_strFmt.npos-1 || begin >= end)
{
break;
}
//追加结果
strResult += m_strFmt.substr(offset, begin - offset);
//填充参数
auto strVal = m_strFmt.substr(begin+1,end-begin-1);
auto nVal = std::atoi(strVal.c_str());
strResult += args.at(nVal);
offset = end+1;//更新offset
}
return strResult;
}
改进
这样的接口使用还是比较复杂的,譬如:
StringBuilder("This is a test! {0}{1},{2}").arg(100).arg("$").arg("please!")();
可以使用可变参数模板改造成类似printf
的方式:
//无参实现,终止条件
static inline void builder(StringBuilder& oBuilder)
{
;
}
template<typename T, typename... Args>
static void builder(StringBuilder& oBuilder, T first, const Args&... restArgs)
{
oBuilder.arg(first);
builder(oBuilder, restArgs...);
}
template<typename... Args>
static std::string build(const char* strFormat, const Args&... args)
{
StringBuilder oBuilder(strFormat);
builder(oBuilder,args...);
return oBuilder();
}
这样处理完成后就可以使用如下方式了:
build("This is a test! {0}{1},{2}",100,"$","please!");
如何实现类似printf
的格式化字符串生成?
实现兼顾易用性、效率、类型安全的printf
,具体可以参考fmt,大体的思路为:
- 保存参数及类型
- 解析格式化字符串
- 使用可变参数模板等模板技术提供易用接口
在fmt中,保存参数使用的是一个union,将参数保存为一个列表,供解析格式化字符串使用,由于保存参数的同时也保存了类型,解析规格字符串时可以与类型互相验证,从而保证类型安全;