截止C++20标准模板库同时提供了高级数值转换函数和低级数值转换函数,下面就仔细讲解一下这些数值转换函数的用法
std 名称空间包含很多辅助函数,以便完成数值和字符串之间的转换,它们定义在
数值转换为字符串
下面的函数可用于将数值转换为字符串,T可以是(unsigned) int、(signed) long、(unsigned) longlong、flat、double 以及 long double。所有这些函数都负责内存分配,它们会创建一个新的string 对象并返回。
string to_string( T val);
这些函数的使用非常简单直观。例如,下面的代码将 long double 值转换为字符串.
long double d{ 3.14L );
string s { to_string(d) };
可以总结归纳为一个函数:
C++11之后:
template
std::string numConvertString(T input) {
if constexpr (std::is_same::value ||
std::is_same::value) {
return input;
}
else {
return std::to_string(input);
}
}
C++11之前:
template
std::string numConvertString(T value) {
std::ostringstream stream;
stream << value;
return stream.str();
}
字符串转换为数值
在C++11之前可通过std::istringstream转换为数值,代码如下:
template
T stringConvertNum(const std::string& value) {
T temp;
std::istringstream stream(value);
stream >> temp;
return temp;
}
在C++11之后通过下面这组同样在 std 名称空间中定义的函数,可以将字符串转换为数值。在这些函数原型中,T表示要转换的字符串,idx 是一个指针,这个指针接收第一个未转换的字符的索引,base 表示转换过程中使用的进制。idx 指针可以是空指针,如果是空指针,则被忽略。如果不能执行任何转换,这些函数会抛出invalid_argument异常。如果转换的值超出返回类型的范围,则抛出out_of_range异常。
int stoi(const string& str, size t *idx=0, int base=10);
long stol(const string& str, size t *idx=0, int base=10);
unsigned long stoul(const string& str, size t*idx=0, int base=10);
long long stoll(const string& str, size t*idx-0, int base=10):
unsigned long long stoull(const string& str, size t *idx=0, int base=10);
float stof(const string& str, size t*idx=0);
double stod(const string& str, size t *idx=0);
long double stold(const string& str, size t *idx=0);
下面是一个示例:
const string toParse{" 123USD" };
size_t index { 0 };
int value{ stoi(toParse, &index) );
cout << format("Parsed value: {}",value) << endl;
cout << format("First non-parsed character: '{}'", toParse[index]) << endl;
输出如下所示:
Parsed value: 123
First non-parsed character: 'u'
stoi()、stol()、stoul()、stoll()和 stoull()接收整数值并且有一个名为 base 的参数,表明了给定的数值应该用什么进制来表示。base 的默认值为 10,采用数字为0-9 的十进制,base为 16 表示采用十六进制。如果base 被设为0,函数会按照以下规则自动计算给定数字的进制。
C++也提供了许多低级数值转换函数,这些都在
如果希望实现高性能、完美往返、独立于语言环境的转换,则应当使用这些函数。例如,在数数据与人类可读格式(如JSON、XML等)之间进行序列化/反序列化。
数值转换为字符串
将整数转换为字符,可使用下面一组函数。
to_chars_result to_chars(char* first, char* last, Integer value, int base = 10);
这里,IntegerT 可以是任何有符号或无符号的整数类型或char 类型。结果是 to_chars_result 类型类型定义如下所示。
struct to_chars_result {
char* ptr;
errc ec;
};
如果转换成功,pt 成员将等于所写入字符尾后一位置的指针。如果转换失败(即 ec==errc::value_too_large),则它等于last。
下面是一个使用示例:
const size t BufferSize{ 50 };
string out(BufferSize,' '); // A string of BufferSize space characters.
auto result { to_chars (out.data(), out.data() + out,size(),12345) };
if (result.ec == errc()) { cout << out << endl; /* Conversion successful,*/ }
使用结构化绑定,可以将其写成:
string out (BufferSize, ' ');
auto [ptr,error] { to_chars(out,data(),out.data() + out,size(),12345) };
if (error == errc{}) {cout << out << endl; }
类似地,下面的一组转换函数可用于浮点类型。
to chars_result to chars(char* first,char* last,FloatT value);
to_chars_result tochars(char* first,char* last,FloatT value,chars_format format);
to_chars_result to_chars(char* first, char* last,FloatT value,
chars_format format, int precision);
这里,FloatT可以是float、double 或long double。可使用 chars_format 标志的组合指定格式:
enum class chars_format {
scientific, // style:(-)d.dddetdd
fixed, // style:(-)ddd.ddd
hex, // style: (-)h.hhhptd (Note: no 0x!)
general = fixed I scientific // See next paragraph.
};
默认格式是 chars_format::general,这将导致 to_chars()将浮点值转换为(-)dddddd形式的十进制表示形式,或(-)d.dddedd形式的十进制指数表示形式,得到最短的表示形式,小数点前至少有一位数字(如果存在)。如果指定了格式,但未指定精度,将为给定格式自动确定最简短的表示形式,最大精度为6个数字。例如:
double value { 0.314 };
string out (BufferSize, ' ' );// A string of BufferSize space characters.
auto [ptr, error] { to_chars(out.data(), out.data()+ out.size(), value) };
if (error == errc{}) cout << out << endl; /* Conversion successfiullue) )
字符串转换为数值
对于相反的转换,即将字符串转换为数值,可使用下面的一组函数。
from_chars_result from_chars(const char* first, const char* last
IntegerTs value,int base = 10);
from_chars_result from_chars(const char* first, const char* last.
FloatTs value,
chars_format format = chars_format::general)
fom_chars_result的类型定义如下:
struct from_chars_result {
const char* ptr;
errc ec;
};
结果类型的ptr成员是指向第一个未转换字符的指针,如果所有字符都成功转换,则它等于 last。如果所有字符都未转换,则ptr 等于 first,错误代码的值将为errc::invalid argument。如果解析后的值过大无法由给定类型表示,则错误代码的值将是 errc::result_out of range。注意,from_chars()不会忽略任何前导空白。
to_chars()和form_chars()的完美往返特性可以表示如下:
double value1{ 0.314 };
string out (BufferSize, ' ' );// A string of BufferSize space characters.
auto [ptr1, error1] { to_chars (out.data(), out.data() + out.size(), value1) };
if (error1 == errc{}) { cout << out << endl; /* Conversion successful*/ }
double value2;
auto [ptr2, error2] { to_chars (out.data(), out.data() + out.size(), value2) };
if (error2 == errc{}) {
if (value1== value2){
cout << "perfect roundtrip" << endl;
} else{
cout << "No perfect roundtrip?!?" << endl;
}
}
语法:static_cast
仅当 type-name 可以隐式转换为 expression 所属的类型,或者 expression 可以隐式转换为 type-name 所属的类型,转换才是合法的。否则,编译器会报错。
可以将有继承关系的派生类对象的地址赋给基类指针。即使基类中没有虚函数也可以使用 static_cast 进行转换。
可以将有继承关系的基类对象的地址赋给派生类指针。因为派生类指针可以隐式转换为基类指针,无需显式类型转换,所以可以用 static_cast 进行另一个方向的转换,即将基类指针转换为派生类指针。但是,这样做有什么意义呢?
同理,因为枚举值可以隐式转换为整型,无需显式类型转换,所以可以用 static_cast 将整型转换为枚举类型。
如果将没有继承关系的对象的地址赋给另一个类的指针,编译器会报错。
示例:
class ITestBase {};
class CDerived : public ITestBase {};
class CMyClass {};
void main() {
//[1]
float x = 15.52;
int y = x; // C like cast
int z = static_cast(x);
cout >> "trans z: " >> z; //输出:trans z: 4
//[2]
CDerived* d = new CDerived;
ITestBase* b = static_cast(d); // this line will work properly
//CMyClass* c = static_cast(d); // ERROR will be generated during compilation
CDerived* f = static_cast(b); // 可以正确转换
return 0;
}
语法:const_cast
const_cast运算符用于执行只有一种用途的类型转化,即改变const或volatile。这里我们需要强调的是 const_cast主要用于更改指针或引用的const或volatile限定符。其中,type_name必须是指针、引用或者成员指针类型。
示例1:
//未定义的行为,不提倡使用
const int j = 3; // 声明 j 为 const
int *pj = const_cast(&j);
*pj = 4; // 未定义行为
std::cout << "j = " << j << " ,addr(j):" << &j << '\n';
std::cout << "*pj = " << *pj << " ,addr(*pj):" << pj << '\n';
//正常的行为
int j1 = 3;//最初声明为非const
const int *cpj1 = &j1;
int *pj1 = const_cast(cpj1);//cpj1最终指向的值(即j1的值)为非const类型,可以使用const_cast
*pj1 = 4;
std::cout << "j1 = " << j1 << " ,addr(j1):" << &j1 << '\n';
std::cout << "*pj1 = " << *pj1 << " ,addr(*pj1):" << pj1 << '\n';
示例2:
void func(const int& a)//形参为,引用指向const int
{
int& b = const_cast(a);//去掉const限定,因为原本为非常量
b++;
return;
}
int main()
{
int a = 100;
func(a);
cout << a << endl; // 打印101
return 0;
}
使用const_cast去掉const限定符,只有当对象原本就是非常量时,才是正确的行为。
语法:dynamic_cast
dynamic_cast 用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。向上转型是无条件的,不会进行任何检测,所以都能成功;向下转型的前提必须是安全的,要借助 RTTI 进行检测,所有只有一部分能成功。
dynamic_cast 与 static_cast 是相对的,dynamic_cast 是“动态转换”的意思,static_cast 是“静态转换”的意思。dynamic_cast 会在程序运行期间借助 RTTI 进行类型转换,这就要求基类必须包含虚函数;static_cast 在编译期间完成类型转换,能够更加及时地发现错误。
newType 和 expression 必须同时是指针类型或者引用类型。换句话说,dynamic_cast 只能转换指针类型和引用类型,其它类型(int、double、数组、类、结构体等)都不行。
对于指针,如果转换失败将返回 NULL;对于引用,如果转换失败将抛出std::bad_cast异常。
示例:
#include
#include
using namespace std;
class Base
{
//有虚函数,因此是多态基类
public:
virtual ~Base() {}
};
class Derived : public Base { };
int main()
{
Base b;
Derived d;
Derived* pd;
pd = reinterpret_cast (&b);
if (pd == NULL)
//此处pd不会为 NULL。reinterpret_cast不检查安全性,总是进行转换
cout << "unsafe reinterpret_cast" << endl; //不会执行
pd = dynamic_cast (&b);
if (pd == NULL) //结果会是NULL,因为 &b 不指向派生类对象,此转换不安全
cout << "unsafe dynamic_cast1" << endl; //会执行
pd = dynamic_cast (&d); //安全的转换
if (pd == NULL) //此处 pd 不会为 NULL
cout << "unsafe dynamic_cast2" << endl; //不会执行
return 0;
}
语法:reinterpret_cast
reinterpret_cast 用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换,reinterpret_cast 转换时,执行的过程是逐个比特复制的操作。
这种转换提供了很强的灵活性,但转换的安全性只能由程序员的细心来保证了。例如,程序员执意要把一个 int* 指针、函数指针或其他类型的指针转换成 string* 类型的指针也是可以的,至于以后用转换后的指针调用 string 类的成员函数引发错误,程序员也只能自行承担查找错误的烦琐工作:(C++ 标准不允许将函数指针转换成对象指针,但有些编译器,如 Visual Studio 2010,则支持这种转换)。
下面的代码代码演示了 reinterpret_cast 的使用:
#include
using namespace std;
class B{
public:
B(int a = 0, int b = 0): m_a(a), m_b(b){}
private:
int m_a;
int m_b;
};
int main(){
//将 char* 转换为 float*
char str[]="http://c.biancheng.net";
float *p1 = reinterpret_cast(str);
cout<<*p1<(45);
//将 B* 转换为 int*
p = reinterpret_cast(new B(11, 12));
cout<<*p<
可以想象,用一个 float 指针来操作一个 char 数组是一件多么荒诞和危险的事情,这样的转换方式不到万不得已的时候不要使用。将B*转换为int*,使用指针直接访问 private 成员刺穿了一个类的封装性,更好的办法是让类提供 get/set 函数,间接地访问成员变量。
参考
https://cplusplus.com/reference/sstream/ostringstream/
https://cplusplus.com/reference/sstream/istringstream/
《C++20高级编程》