sprintf()
使用stdio.h
的sprintf()
可以实现数据的类型转换。
1
2
3
4
5
6
|
char s[5];
memset(s, 0, sizeof(s));
double d = 3.14;
sprintf(s, "%f", d);
cout << s << endl; // print "3.140000"
|
使用 sprintf() 经常会出现 2 个问题:
缓冲区溢出
格式符
stringstream
使用 C++ 标准库的<sstream>
提供的stringstream
对象可以简化类型转换。
- 转换结果保存在 stringstream 对象的内部缓冲区,不必担心缓冲区溢出。
- 传入参数和目标对象的类型能被自动推导出来,不需要考虑格式化符,可以实现任意类型的转换,而这一点是使用
sprintf()
或atoi()
等是很难做到的。
在多次转换中可以重复使用同一个 stringstream 对象,避免多次的对象构造和析构,但要记得每次转换前使用clear()
。
如以下函数可以实现 R 类型对象到 L 类型对象的转化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
template <class L, class R>
void convert(L& left, const R& right)
{
stringstream ss;
if (!(ss << right)) // 向流中传值
return;
ss >> left; // 将流中的值写入到left
}
...
const char* cstr = "3.14159";
double d;
convert(d, cstr);
cout << d << endl; // print "3.14159"
|
但同时有个问题,例如从int
到long
类型的转换,可以直接使用operator=
进行赋值,这样反而把问题变复杂了,因此需要判断出这样的情况。
下面通过模板的类型推导和重载函数的不同返回值,可以确定 R 类型是否能转换到 L 类型,而且这在编译期就完成了判断。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
template <class L, class R>
struct can_convert
{
// test()的两个重载
static int64_t test(L); // 指定类型L
static int8_t test(...); // 变参
static R getR();
enum { value = (sizeof(test(getR())) == sizeof(int64_t)) };
};
...
cout << can_convert<int, long>::value << endl; // print "1"
cout << can_convert<int, string>::value << endl; // print "0"
|
但改成下面这样会无法通过编译,因为if...else...
是运行期的分发,我们需要解决编译期的分发。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
template <class L, class R>
void convert(L& left, const R& right)
{
if (can_convert<L, R>::value)
left = right;
else {
stringstream ss;
if (!(ss << right))
return;
ss >> left;
}
}
...
// error: cannot convert ‘const char* const’ to ‘double’ in assignment
// left = right;
convert(d, cstr);
|
可以使用 bool 模板来进行编译期的分发。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
template <bool>
struct convert_dispatch
{
template <class L, class R>
static void dispatch(L& left, const R& right)
{ left = right; }
};
template <>
struct convert_dispatch<false>
{
template <class L, class R>
static void dispatch(L& left, const R& right)
{
std::stringstream ss;
if (!(ss << right)) return;
ss >> left;
}
};
template <class L, class R>
void convert(L& left, const R& right)
{
convert_dispatch<can_convert<L, R>::value>::dispatch(left, right);
}
|
boost::lexical_cast<>()
boost
里的lexical_cast
的内部实现也是stringstream
,并且若转换失败,会抛出bad_lexical_cast
异常。