boost就有一个实现类型安全format的类,不过类比较庞大,而且也比较复杂,用起来也不是很习惯。
所以还是自己简单实现一个了。
先看看需求:
format_string.format("[%] = %") % a % strName
其实就是希望后面的a和strName分别替代格式化字符串"[%] = %"中的两个%号,相当于:
stringstream format_string;
format_string << "[" << a << "] = " << strName;
当然,要做到通用,还是希望不单能输出到stringstream,最好还是能输出到std::ostream。
class format_stream
{
public:
explicit format_stream(std::ostream & outS) : m_output(outS),m_lpszFormat(&g_nEndFlags)
{
}
inline format_stream & format(const char * lpszFormat)
{
flushFormat();
m_lpszFormat = lpszFormat;
return outputPrefix();
}
template
inline format_stream & arg(const typeArg1 & val) {
getOutput() << val;
return outputPrefix();
}
~format_stream(void) {}
protected:
inline std::ostream & getOutput(void)
{
return m_output;
}
void flushFormat(void)
{
if (*m_lpszFormat)
{
getOutput() << m_lpszFormat;
m_lpszFormat = &g_nEndFlags;
}
}
format_stream & outputPrefixLoop(void);
format_stream & outputPrefix(void);
static char g_nEndFlags;
std::ostream & m_output;
const char * m_lpszFormat;
};
char format_stream::g_nEndFlags = char();
format_stream & format_stream::outputPrefix(void)
{
char * lpPos = strchr(m_lpszFormat,'%');
if (lpPos != NULL)
{
getOutput().write(m_lpszFormat, lpPos - m_lpszFormat);
m_lpszFormat = lpPos + 1;
if (*m_lpszFormat == '%' && *(m_lpszFormat + 1) != '%')
return outputPrefixLoop();
} // if (lpPos != NULL)
else
flushFormat();
return *this;
}
format_stream & format_stream::outputPrefixLoop(void)
{
while (*m_lpszFormat == '%')
{
char * lpPos = strchr(m_lpszFormat + 1, '%');
if (lpPos != NULL)
{
getOutput().write(m_lpszFormat, lpPos - m_lpszFormat);
m_lpszFormat = lpPos + 1;
if (*m_lpszFormat != '%' || *(m_lpszFormat + 1) == '%')
break;
} // if (lpPos != NULL)
else
{
flushFormat();
break;
}
} // while (*m_lpszFormat)
return *this;
}
1、规定以'%'作为占位符,表示后续的变量替换的位置。
2、两个连续'%'即 '%%'表示一个真正的'%'。不过需要注意的是:一般想输出百分数的时候,就是要写%%%,
分析程序发现三个连续'%',则认为第一个是占位符,后两个表示一个'%'。而发现四个'%'的时候,前两个都会被认
为是占位符,最后两个被认为是一个'%'。
3、boost用%连接后面的多个变量,也有些类库使用逗号。个人都不是很喜欢:'%'用得太多,程序看起来不好
看;很多书都再三声明最好不要重载逗号运算符。所以还是使用函数比较稳妥,所以就用函数arg(a)的形式。如果真
的喜欢使用'%'或者逗号,只需要增加成员函数:
template
inline format_stream & operator%(const typeArg1 & val) { return arg(val); }
template
inline format_stream & operator,(const typeArg1 & val) { return arg(val); }
4、arg还可以继续扩展,
a、同一个占位符输出多个变量,只需要增加多几个成员函数:
template
inline format_stream & arg(const typeArg1 & val1,const typeArg2 & val2)
{
getOutput() << val << val2;
return outputPrefix();
}
template
inline format_stream & arg(const typeArg1 & val1,
const typeArg2 & val2,
const typeArg3 & val3)
{
getOutput() << val1 << val2 << val3;
return outputPrefix();
}
例如有时候想输出一个范围:
stream.format("range1:% range2:%").arg(lowerbound1,'-',upperbound1);
stream.arg(lowerbound2,'~',upperbound2)
b、格式化输出。printf那么多的格式化输出参数写在格式化字符串中,我老是会记错,一旦写错
程序就容易出问题了(不单是显示错误,还有可能会coredump),另外发现不同平台格式还会有
些不一样。还是写在arg里面比较稳妥,而且程序也容易阅读。
为了和上面"同一个占位符输出多个变量"的函数区分,还是另外取一个函数名:
enum INT_BASE
{BASE10=std::ios::dec, BASE8=std::ios::oct, BASE16=std::ios::hex};
enum CHAR_CASE
{CHAR_UPCASE=0, CHAR_LOWCASE=1 };
enum BASE_FLAG
{HIDE_BASE=0, SHOW_BASE=1 };
enum POS_FLAG
{HIDE_POS=0, SHOW_POS=1 };
enum FIX_FLAG
{ FIXED=0, SCIENTIFIC=1 };
enum POINT_FLAG
{ HIDE_POINT=0, SHOW_POINT=1 };
enum ADJUSTFIELD_FLAG
{
ADJUST_LEFT=std::ios::left,
ADJUST_RIGHT=std::ios::right,
ADJUST_INTERNAL=std::ios::internal
};
template
format_stream & argWithFormat(typeInt nVal,
INT_BASE nBase = BASE10,
CHAR_CASE bUpcase = CHAR_UPCASE,
POS_FLAG bShowPos = HIDE_POS,
BASE_FLAG bShowBase = HIDE_BASE,
ADJUSTFIELD_FLAG nAdjust= ADJUST_LEFT,
int nWidth = -1,char chFill = ' ')
{
std::ios::fmtflags nFlags = getOutput().flags();
getOutput().setf((std::ios::fmtflags)nBase, std::ios::basefield);
if (bShowPos == SHOW_POS)
getOutput().setf(std::ios::showpos);
else
getOutput().unsetf(std::ios::showpos);
if (bUpcase == CHAR_UPCASE)
getOutput().setf(std::ios::uppercase);
else
getOutput().unsetf(std::ios::uppercase);
if (bShowBase == SHOW_BASE)
getOutput().setf(std::ios::showbase);
else
getOutput().unsetf(std::ios::showbase);
getOutput().setf((std::ios::fmtflags)nAdjust, std::ios::adjustfield);
if (nWidth != -1)
nWidth = getOutput().width(nWidth);
chFill = getOutput().fill(chFill);
getOutput() << static_cast(nVal);
getOutput().flags(nFlags);
if (nWidth != -1)
getOutput().width(nWidth);
getOutput().fill(chFill);
return outputPrefix();
}
还可以增加浮点数、字符串等等的格式处理。
5、现在输入的格式字符串,在类里面只是使用const char * m_lpszFormat来保存,一旦传入的
lpszFormat所指的资源已经被释放,则会造成非法内存访问。例如:
std::string getString(void) { ..... }
stream.format(getString().c_str());
stream.arg(...);
如果要避免这种情况,应该在format_stream里面增加一个std::string成员,记录字符串指针。
又或者应该写成:
std::string strTemp = getString();
stream.format(strTemp.c_str());
stream.arg(...)
应该如何处理,还是看各人的习惯和需求了。
6、当输入参数比占位符多的时候,则多余的参数都会输出到格式字符串的后面。
7、当输入参数比占位符少的时候,输出则停留在第一个多余的占位符的前面。
8、对于字符串,可以使用stringstream来辅助:
stringstream str;
format_stream fmt(str);
fmt.format(".....").arg(...);
myFun(str.str());
当然,经过适当的改造,可以使类更方便使用,这里就不再多说,各位高手自己发挥了。