在这篇博文里,我提到了一个例子,说的是使用C++实现类型安全的printf。这个例子很惊艳,但是在我写程序的时候,并非那么“迫切”地需要它出现在我的工具箱中,因为它并不比普通的printf方便,而且它没有出现的标准库中。所以自己也懒得整。相反,这个函数的兄弟,sprintf,倒是一个非常需要的函数。不仅仅是因为需要它类型安全,而是 sprintf 有比 printf 更多的麻烦:
- 首先它确实也不是类型安全的
- 使用sprintf之前,必须要先准备一段buffer,但这个buffer的大小难以确定,还要防溢出
- C++程序中多数时候我还是需要使用 string,于是还是要再用buffer中的字符串再生成一个string对象来使用。
这非常的麻烦,但是,sprintf 紧凑的表达是它最大的优点~我才不会用流去格式化一个字符串呢,实在是让人精神分裂啊。
想到用 variadic template 实现的 printf 之后,觉得应该是完全可以模仿这个做一个 sprintf 函数的啊,除了得到类型安全的好外之外,还能直接从函数返回字符串,又不会有溢出的问题,string和流自动地把内存管理好。一举多得。
std::string sprintf(const char *s) { std::stringstream ss; _sprintf(ss, s); return ss.str(); } void _sprintf(std::stringstream & ss, const char *s) { while (*s) { if (*s == '%') { if (*(s + 1) == '%') { ++s; } else { throw std::runtime_error("invalid format string: missing arguments"); } } ss << *s++; } } template<typename T,typename... Args> std::string sprintf(const char *s, T value, Args... args) { std::stringstream ss; _sprintf(ss, s, value, args...); return ss.str(); } template<typename T, typename... Args> void _sprintf(std::stringstream & ss, const char *s, T value, Args... args) { while (*s) { if (*s == '%') { if (*(s + 1) == '%') { ++s; } else { ss << value; _sprintf(ss, s + 1, args...); // call even when *s == 0 to detect extra arguments return; } } ss << *s++; } throw std::logic_error("extra arguments provided to lyw::sprintf"); }
使用起来也很方便,直接像原来的sprintf一样用就好了。而且如果自定义的对象实现的流操作符重载,就可以自动地与这个函数配合。非常精巧。
string str = sprintf("I tried % times in % ", 10, "Monday");
只需要加一点点代码(使用C++的流控制符),就可以实现各加精细的格式,这个版本我就不再贴了。
PS: 代码在VS2013上跑,很High,
VS2013在C++的开发环境(我是指编辑器)上做了很大的改进,而且速度也快了。算是难得