概述
顾名思义,lexical_cast库进行”字面值“的转换,类似C中的atoi()函数,可以进行字符串与整数/浮点数之间的字面转换
- 标准C和c++库提供了许多用于执行此类转换的工具。但是,它们的易用性、可扩展性和安全性各不相同。
- 例如,C语言中的atoi()、atof()等函数,它们可以把字符串转换成数值,但是这种转换是不对称的,不存在比如itoa()这样的方向转换(C标准未提供,但有的编译期产生会提供),要想把数值转换为字符串,只能使用不安全的printf()。
- lexical_cast函数模板提供了一种方便且一致的形式,用于支持以文本形式表示的任意类型之间的公共转换。它提供的简化是这种转换在表达级上的便利。
- 对于更复杂的转换,例如需要比lexical_cast的默认行为提供更严格的精度或格式控制时,建议使用常规的std::stringstream方法。
- 在数值到数值的转换中,boost::numeric_cast可能提供比lexical_cast更合理的行为。
有关基于字符串的格式化所涉及的选项和问题的详细讨论,包括stringstream、lexical_cast和其他格式的比较,请参阅Herb Sutter的文章the String Formatters of Manor Farm。另外,请查看Performance部分。
摘要
boost/lexical_cast.hpp中定义的库特性:
namespace boost
{
class bad_lexical_cast;
template<typename Target, typename Source>
Target lexical_cast(const Source& arg);
template <typename Target>
Target lexical_cast(const AnyCharacterType* chars, std::size_t count);
namespace conversion
{
template<typename Target, typename Source>
bool try_lexical_convert(const Source& arg, Target& result);
template <typename AnyCharacterType, typename Target>
bool try_lexical_convert(const AnyCharacterType* chars, std::size_t count, Target& result);
} // namespace conversion
} // namespace boost
lexical_cast
lexical_cast使用类似C++标准转型操作符的形式给出了通用、一致、可理解的语法,它常用的两种形式是
// 标准形式,转换数字和字符
template<typename Target, typename Source>
Target lexical_cast(const Source& arg);
- 效果:将arg流式处理的结果返回到基于标准库字符串的流中,然后作为模板对象输出,如果模板是std::string或std::wstring,流将提取出字符串的全部内容,包括空格,而不是依赖于默认操作符>>的行为。如果转换失败,将引发bad_lexical_cast 异常
- 说明:它有两个模板参数,Targte需要我们手工指定,是转换后的模板类型,通常是数字类型或者std::string;而第二个参数Source不必写出,因为它可以通过函数参数推导出来,所以调用的形式是:
lexical_cast<Target>(...)
// 转换C字符串
template <typename Target>
Target lexical_cast(const AnyCharacterType* chars, std::size_t count);
- 效果:接受计数字符数组作为输入参数,并将它们作为Target对象流输出。如果转换不成功,将引发bad_lexical_cast异常。这个调用对于处理非零终止字符数组或处理字符数组的某些部分可能有用。
- 说明:它是专门用来处理C字符串的,除了const char外还支持unsigned char、const wchar_t
*
等其他字符类型,它只接受一个模板参数Target,指明转换后的目标类型。参数chars和counts则标记了要转换的字符串的范围
转换对象的要求
虽然lexical_cast的用法看起来像转型操作符,但它仅仅是在用法上模仿了转型操作而已,实际上是一个模板函数
lexcial_cast的内部使用了标准的流操作,因此它对转换对象有如下要求:
- Source是可流输出的(OutputStreamable),即定义了一个操作符<<
- Target是可流输入的(InputStreamable),即义了一个操作符>>
- Target是可复制的
- Target是可默认构造的,这意味着可以默认初始化该类型的对象
C++的内建类型和std::string都满足上面的条件,它们也是lexcial_cast最常用的工作搭档
对于标准容器和其他自定义类型,这些函数一般都不满足,所以不能使用lexcial_cast(除非做特殊处理)
基础流的字符类型被假定为char,除非Source或Target需要宽字符流,在这种情况下,基础流使用wchar_t。以下类型也可以使用char16_t或char32_t进行宽字符流:
- 单字符:char16_t, char32_t
- 字符数组:
char16_t *, char32_t *, const char16_t *, const char32_t *
- 字符串: std::basic_string, boost::containers::basic_string
boost::iterator_range<WideCharPtr>
:其中WideCharPtr是一个指向宽字符的指针或指向const宽字符的指针boost::array<CharT, N> 、std::array<CharT, N>, boost::array<const CharT, N> 、std::array<const CharT, N>
如果需要对转换进行更高程度的控制,则std::stringstream和std::wstringstream提供了更合适的路径。当需要非基于流的转换时,不要lexical_cast,它不是这种场景的专用工具。
注意:许多编译器和运行时库无法使用新的Unicode字符进行转换。在使用新类型之前,请确保以下代码编译并输出非零值:
std::cout
<< boost::lexical_cast<std::u32string>(1.0).size()
<< " "
<< boost::lexical_cast<std::u16string>(1.0).size();
用法
使用lexical_cast可以很容易的在数值和字符串之间转换,只需要在目标参数里面指出要转换的目标类型即可:
#include <boost/lexical_cast.hpp>
#include <iostream>
using namespace boost;
int main(int /*argc*/, char * argv[]) {
int x = lexical_cast<int>("100");
long y = lexical_cast<long>("2000");
float pai = lexical_cast<float>("3.14159e5");
double e = lexical_cast<double>("2.71828");
double f = lexical_cast<double>("1.414.x", 5);
std::cout << x << "\t" << y << "\t" << pai << "\t" << e << "\t" << f << "\n";
std::string str = lexical_cast<std::string>(345);
std::cout << str << "\n";
std::cout << lexical_cast<std::string>(0.618) << "\t" << lexical_cast<std::string>(0x10);
return 0;
}
- 使用lexical_cast时要注意,待转换成数字的字符串只能有数字和小数点,不能出现字母或者其他非数字字符(表示指数e/E除外),业就是说,lexical_cast不能抓换比如”123L“,”0x123“这样的字符串。而且lexical_cast不支持高级的格式控制,不能把数字转换成其他格式的字符串,如果需要更高级的搁置控制,则应该用std::stringstream或者boost::format
- 除了转换数值与字符串,lexical_cast也可以转换bool类型,但功能很有限,不能使用true/false字字面值,只能使用1或者0,比如:
lexical_cast<bool>(1);
异常处理:bad_lexical_cast
- 当lexical_cast无法执行转换操作时会抛出异常bad_lexical_cast ,它是std::bad_cast的派生类
class bad_lexical_cast : public std::bad_cast
{
public:
... // same member function interface as std::exception
};
- 为了使得程序更加健壮,在使用lexcial_cast转换时我们应该使用try/catch块来保护代码:
try
{
cout << lexical_cast<int>("0x100");
cout << lexical_cast<double>("HelloWorld");
cout << lexical_cast<long>("1000L");
cout << lexical_cast<bool>("false") << endl;
}
catch (bad_lexical_cast& e)
{
cout << "error:" << e.what() << endl;
}
- lexical_cast在名字空间boost::conversion提供try_lexical_convert()函数,可以避免抛出异常,它以bool返回值表示是否转换成功,函数的声明是:
template <typename Target, typename Source>
inline bool try_lexical_convert(const Source& arg, Target& result)
template <typename Target, typename CharacterT>
inline bool try_lexical_convert(const CharacterT* chars, std::size_t count, Target& result)
- 使用try_lexical_convert()可以安全的转换字面值
int x;
assert(!conversion::try_lexical_convert("0x100", x));
- 我们可以利用try_lexical_convert来验证数字字符串的合法性,实现一个模板函数num_valid:
template<typename T>
bool num_valid(const char *str)
{
T tmp;
return conversion::try_lexical_convert(str, tmp);
}
- 使用方法如下:
assert( num_valid<double>("3.14"));
assert(!num_valid<int>("3.14"));
assert( num_valid<int>("65535"));
- 这个小函数在验证用户输入有效性时会很有用
应用于自定义类
- 如果我们想要将lexical_cast应用于自定义的类,把类转换为可理解的字符串描述(类似于Java中的Object.toString()),只需要满足lexical_cast的要求即可。准确的说,需要实现流操作符operator
#include <boost/lexical_cast.hpp>
#include <iostream>
using namespace boost;
using namespace std;
class demo_class
{
friend std::ostream& operator<<(std::ostream& os, const demo_class& x)
{
os << "demo_class's Name";
return os;
}
};
int main(int /*argc*/, char * argv[]) {
cout << lexical_cast<string>(demo_class()) << endl;
return 0;
}
这段代码具有通用性,值得把它提取为一个模板类。我们可以仿照boost.operator库,定义一个模板类outable,以简化<<的重置。注意,这里没有使用基类链技术,不能用于operator的基类串联,但可以很容易的添加这个功能:
template<typename T>
struct outable
{
friend std::ostream& operator<<(std::ostream& os, const T& x)
{
os << typeid(T).name();
return os;
}
};
这样,任何继承outable的类,就会自动获得流输出操作符或者lexcial_cast的支持:
class demo_class : public outable<demo_class>{};
int main(int /*argc*/, char * argv[]) {
cout << lexical_cast<string>(demo_class()) << endl;
return 0;
}
这里只是用来typeid(T)来输出类的名字,如果想要扩展outable< T>的功能,可以考虑模板类型T提供to_string()或者print()之类的函数来输出特定信息。
try_lexical_convert
-
在大多数情况lexical_cast就可以满足需求。然而,一些开发人员希望创建自己的转换函数,重用boost::lexical_cast的所有优化。这就是boost::conversion::try_lexical_convert函数的作用所在。
-
如果转换成功,Try_lexical_convert返回true,否则返回false。如果转换失败且返回false,则结果输出变量的状态为未定义。
-
实际上,boost::lexical_cast是使用try_lexical_convert实现的:
template <typename Target, typename Source>
inline Target lexical_cast(const Source &arg)
{
Target result;
if (!conversion::try_lexical_convert(arg, result))
throw bad_lexical_cast();
return result;
}
try_lexical_convert放宽了Target类型的CopyConstructible和DefaultConstructible要求。对目标和来源仍有以下要求:
- Source必须是OutputStreamable,这意味着定义了一个操作符<<,它的左边是一个std::ostream或std::wostream对象,右边是实参类型的一个实例。
- Target必须是InputStreamable,这意味着定义了一个操作符>>,它的左边是一个std::istream或std::wistream对象,右边是结果类型的一个实例。
对比标准
- C++标准增强了字符串和数字的互操作性,提供了stoX()和to_string()函数实现了std::string与数字之间的转换,部分函数的声明如下:
- 标准转换函数的用法与lexical_cast相似,优点是无需写模板参数,而且运行字符串里出现非数字字符—它们会忽略其实的空白空格,直到遇到无法转换的字符为止:
assert(stoi(" 42 ") == 42); //允许有空格
assert(stol("100L") == 100L); //允许L等后缀
assert(stol("1000 9") == 1000L); //后面的被忽略
assert(stod("3.14ispai") == 3.14); //遇到无效字符时停止
assert(to_string(776ul) == "776");
- 如果字符串不是以空白字符、数字开头,或者超出了数字类型的范围,那么这些函数会抛出std::invalid_argument或者std::out_of_range异常:
cout << stoul("x100"); // std::invalid_argument
cout << stoi("9999999999"); // std::out_of_range
- 很可惜,C++标准没有采用类似lexcial_cast的模板函数的方式,所以我们可以使用lexcial_cast来包装它们以方便使用:
template<typename T>
T std_lexical_cast(const std::string& s);
template<>
int std_lexical_cast<int>(const std::string& s)
{
return stoi(s);
}
template<>
long std_lexical_cast<long>(const std::string& s)
{
return stol(s);
}
void case5()
{
assert(std_lexical_cast<int>(" 10 ") == 10);
assert(std_lexical_cast<long>("100L") == 100L);
}
例子
字符串到数字的转换
下面的示例将命令行参数视为数字数据序列
#include <boost/lexical_cast.hpp>
#include <vector>
#include <iostream>
int main(int /*argc*/, char * argv[])
{
using boost::lexical_cast;
using boost::bad_lexical_cast;
std::vector<short> args;
while (*++argv){
try {
args.push_back(lexical_cast<short>(*argv));
} catch (const bad_lexical_cast&) {
args.push_back(0); //如果转换失败则塞入一个0
}
}
for(auto i : args){
std::cout << i << "\t";
}
}
数字到字符串的转换
下面的例子在字符串表达式中使用数字数据:
#include <boost/lexical_cast.hpp>
#include <iostream>
void log_message(const std::string &i){
std::cerr << i;
};
void log_errno(int yoko)
{
log_message("Error " + boost::lexical_cast<std::string>(yoko) + ": " + strerror(yoko));
}
int main(int /*argc*/, char * argv[])
{
log_errno(1);
}
转换为没有动态内存分配的字符串
以下示例转换一些数字并将其放入文件:
void number_to_file(int number, std::FILE* file)
{
typedef boost::array<char, 50> buf_t; // You can use std::array if your compiler supports it
buf_t buffer = boost::lexical_cast<buf_t>(number); // No dynamic memory allocation
std::fputs(buffer.begin(), file);
}
转换部分字符串
下面的例子接受字符串的一部分并将其转换为int:
int convert_strings_part(const std::string& s, std::size_t pos, std::size_t n)
{
return boost::lexical_cast<int>(s.data() + pos, n);
}
通用编程(Boost.Fusion)
在本例中,我们将创建一个stringize方法,该方法接受序列,将序列的每个元素转换为字符串,并将该字符串附加到结果中。
示例是基于Antony Polukhin的Boost c++ Application Development Cookbook中的示例
- 步骤1:创建一个函子,可以将任何类型转换为字符串,并存入result:
#include<boost/lexical_cast.hpp>
struct stringize_functor{
private:
std::string& result;
public:
explicit stringize_functor(std::string& res) : result(res){}
template<typename T>
void operator() (const T& v) const{
result += boost::lexical_cast<std::string>(v);
}
};
- 步骤2:对序列中的每个元素应用stringize_functor:
#include <boost/fusion/include/for_each.hpp>
template <class Sequence>
std::string stringize(const Sequence& seq) {
std::string result;
boost::fusion::for_each(seq, stringize_functor(result));
return result;
}
- 步骤3:使用不同类型的stringize:
#include <boost/fusion/adapted/boost_tuple.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
int main(int /*argc*/, char * argv[])
{
boost::tuple<char, int, char, int> decim('-', 10, 'e', 5);
if (stringize(decim) != "-10e5") {
return 1;
}
std::pair<int, std::string> value_and_type(270, "Kelvin");
if (stringize(value_and_type) != "270Kelvin") {
return 2;
}
}
泛型编程(Boost.Variant)
在这个例子中,我们将创建一个to_long_double方法来转换 Boost.Variant到long double
#include <boost/lexical_cast.hpp>
#include <boost/variant.hpp>
struct to_long_double_functor: boost::static_visitor<long double> {
template<typename T>
long double operator() (const T& v) const{
// lexical_cast转换有许多优化,包括对通常发生在泛型编程中的情况的优化,如std::string到std::string或算术类型到算术类型的转换。
return boost::lexical_cast<long double>(v);
}
};
// 如果变量的值不能转换为' long double ',抛出' boost::bad_lexical_cast '
template <class Variant>
long double to_long_double(const Variant& v) {
return boost::apply_visitor(to_long_double_functor(), v);
}
int main(int /*argc*/, char * argv[]) {
boost::variant<char, int, std::string> v1('0'), v2("10.0001"), v3(1);
const long double sum = to_long_double(v1) + to_long_double(v2) + to_long_double(v3);
if (11 < sum && sum < 11.1) {
return 0; // OK, as expected
};
return 1; // FAIL
}