ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

使用string和string_view(二)——数值转换、std::string_view和非标准字符串

使用string和string_view(二)——数值转换、std::string_view和非标准字符串 目录1. 数值转换1.1 高级数值转换函数1.1.1 数值转换为字符串1.1.2 字符串转换为数值1.2 低级数值转换函数1.2.1 数值转换为字符串1.2.2 字符串转换为数值2. std::string_view2.1 引入2.2 string_view转换成string2.3 std::string_view和临时字符串2.4 std::string_view字面量3. 非标准字符串参考1. 数值转换C标准库同时提供了高级数值转换函数和低级数值转换函数。1.1 高级数值转换函数1.1.1 数值转换为字符串下面的函数可用于将数值转换为字符串T可以是(unsigned) int、(unsigned) long、unsigned) long long、float、double以及long double。所有这些函数都负责内存分配它们会创建一个新的string对象并返回。std::string to_string(T val);这些函数的使用非常简单直观。例如下面的代码将long double值转换为字符串。longdoubled{3.14L};std::string s{std::to_string(d)};1.1.2 字符串转换为数值通过下面这组同样在std名称空间中定义的函数可以将字符串转换为数值。在这些函数原型中str表示要转换的字符串idx是一个指针这个指针接收第一个未转换的字符的索引base表示转换过程中使用的进制。idx指针可以是空指针如果是空指针则被忽略。如果不能执行任何转换这些函数会抛出invalid_argument异常。如果转换的值超出返回类型的范围则抛出out_of_range异常。int stoi(const string str, size_t* idx0, int base10);long stol(const string str, size_t* idx0, int base10);unsigned long stoul(const string str, size_t* idx0, int base10);long long stoll(const string str, size_t* idx0, int base10);unsigned long long stoull(const string str, size_t* idx0, int base10);float stof(const string str, size_t* idx0);double stod(const string str, size_t* idx0);long double stold(const string str, size_t* idx0);示例如下conststd::string toParse{ 123USD};size_t index{0};intvalue{stoi(toParse,index)};std::coutstd::format(Parse value: {}\n,value);std::coutstd::format(First non-parsed character: {}\n,toParse[index]);输出如下Parsed value:123First non-parsed character:Ustoi()、stol()、stoul()、stoll()和stoull()接收整数值并且有一个名为base的参数表明了给定的数值应该用什么进制来表示。base的默认值为10采用数字为0-9的十进制base为16表示采用16进制。如果base被设为0函数会按照以下规则自动计算给定数字的进制。1. 如果数字以0x或0X开头则被解析为十六进制数字。2. 如果数字以0开头则被解析为八进制数字。3. 其他情况下被解析为十进制数字。1.2 低级数值转换函数C也提供了许多低级数值转换函数这些都在charconv头文件中定义。这些函数不执行内存分配也不直接使用std::string而使用由调用者分配的缓存区。此外它们还针对性能进行了优化并且与语言环境无关。最终的结果是这些函数可以比其他高级数值转换函数快几个数量级。这些函数也是为所谓的完美往返而设计的这意味着将数值序列化为字符串表示然后将结果字符串反序列为数值结果与原始值完全相同。如果希望实现高性能、完美往返、独立于语言环境的转换则应当使用这些函数。例如在数值数据与人类可读格式(如JSON、XML等)之间进行序列化/反序列化。1.2.1 数值转换为字符串要将整数转换为字符可使用下面一组函数。to_chars_result to_chars(char* first, char* last, IntegerT value, int base10);这里IntegerT可以是任何有符号或者无符号的整数类型或char类型。结果是to_chars_result类型类型定义如下。structto_chars_result{char*ptr;std::errc ec;};如果转换成功ptr成员将等于所写入字符尾后一位置的指针。如果转换失败(即ecstd::errc::value_too_large)则它等于last。示例如下(out.data()返回的是指向out中第一个字符的指针)constsize_t BufferSize{50};std::stringout(BufferSize, );// a string of BufferSize space character.autoresult{std::to_chars(out.data(),out.data()out.size(),12345)};if(result.ecstd::errc{}){/* conversion successful. */std::coutout\n;}使用结构化绑定可以将其写成constsize_t BufferSize{50};std::stringout(BufferSize, );auto[ptr,error]{std::to_chars(out.data(),out.data()out.size(),12345)};if(result.ecstd::errc{}){/* conversion successful. */std::coutout\n;}类似地下面的一组转换函数可用于浮点类型。to_chars_resultto_chars(char*first,char*last,FloatT value);to_chars_resultto_chars(char*first,char*last,FloatT value,chars_format format);to_chars_resultto_chars(char*first,char*last,FloatT value,chars_format format,intprecision);这里FloatT可以是float、double或long double。可使用chars_format标志的组合指定格式enumclasschars_format{scientific,// Style: (-)d.ddde加减ddfixed,// Style: (-)ddd.dddhex,// Style: (-)h.hhhp加减dgeneralfixed|scientific//看下面};默认格式是chars_format::general这将导致to_chars()将浮点数转换为(-)ddd.ddd的十进制表示形式或(-)d.ddde加减dd形式的十进制指数表示形式得到最短的表示形式小数点前至少有一位数字(如果存在)。如果指定了格式但未指定精度将为给定格式自动确定最简短的表示形式最大精度为6个数字。例如constsize_t BufferSize{50};doublevalue{0.314};std::stringout(BufferSize, );auto[ptr,error]{std::to_chars(out.data(),out.data()out.size(),value)};if(errorstd::errc{}){/* conversion successful */std::coutout\n;}1.2.2 字符串转换为数值对于相反的转换即将字符串转换为数值可使用下面的一组函数。from_chars_resultfrom_chars(constchar*first,constchar*last,IntegerTvalue,intbase10);from_chars_resultfrom_chars(constchar*first,constchar*last,FloatTvalue,chars_format formatchars_format::general);from_chars_result的类型定义如下structfrom_chars_result{constchar*ptr;std::errc ec;};结果类型的ptr成员指向第一个未转换字符的指针如果所有字符转换成功则它等于last。如果所有字符都未转换则ptr等于first错误代码的值将为std::errc::invalid_argument。如果解析后的值过大无法由给定类型表示则错误代码的值将是std::errc::result_out_of_range。注意from_chars不会忽略任何前导空白。to_chars()和from_chars()的完美往返特性可以表示如下constsize_t BufferSize{50};doublevalue1{0.314};std::stringout(BufferSize, );auto[ptr1,error1]{std::to_chars(out.data(),out.data()out.size(),value1)};if(error1std::errc{}){std::coutout\n;}doublevalue2{};auto[ptr2,error2]{std::from_chars(out.data(),out.data()out.size(),value2)};if(error2std::errc{}){if(value1value2){std::coutPerfect roundtrip\n;}else{std::coutNo Perfect roundtrip?!?\n;}}2. std::string_view2.1 引入在C17之前为接收只读字符串的函数选择形参一直是一件进退两难的事情它应当是const char*吗那这样的话如果客户使用std::string则必须调用其上的c_str()或者data()来获取const char*。更糟糕的是函数将失去std::string良好的面向对象的方面以及其良好的辅助方法。或许形参应改用const std::string这种情况下始终需要std::string。如果传递一个字符串字面量编码器将默认创建一个临时字符串对象(其中包含字符串字面量的副本)并将该对象传递给函数因此会增加一点开销。有时人们会编写同一函数的多个重载版本一个接收const char*另一个接收const std::string但显然这并不是一个优雅的解决方案。在C17中通过引入std::string_view类解决了所有这些问题std::string_view类是std::basic_string_view类模板的实例化在string_view中定义。string_view基本上就是const std::string的简单替代品但不会产生开销。它从不复制字符串string_view支持与std::string类似的接口。一个例外是缺少c_str()但是data()是可用的。另外string_view添加了remove_prefix(size_t)和remove_suffix(size_t)方法前者将起始指针前移给定的偏移量来收缩字符串后者则将结尾指针倒退给定的偏移量来收缩字符串。如果知道如何使用std::string那么使用std::string_view将变得十分简单如下面的代码片段所示。extractExtension()函数提取给定文件名的扩展名(包括点号)并返回。注意通常按值传递string_view因为它的复制成本极低只包含指向字符串的指针以及字符串的长度。std::string_viewextractExtension(std::string_view filename){returnfilename.substr(filename.rfind(.));// rfind locates the last character . in the string.}该函数可用于所有类型的不同字符串usingnamespacestd::literals;std::string filename{R(c:\temp\my file.ext)};std::coutstd::format(C String: {}\n,extractExtension(filename));constchar*cString{R(c:\temp\my file.ext)};std::coutstd::format(C String: {}\n,extractExtension(cString));std::coutstd::format(Literal: {}\n,extractExtension(R(c:\temp\my file.ext)));在对extractExtension()的所有调用中没有进行一次复制。extractExtension()函数的filename()参数只是指针和长度该函数的返回类型也是如此这十分高效。还有一个string_view构造函数它接收任意原始缓冲区和长度。这可用于从字符缓冲区(并非以NUL终止)构建string_view。如果确实有一个以NUL终止的字符串缓冲区但你已经知道了字符串的长度构造函数不必再次统计字符数目这也是有用的。constchar*raw{/* ... */};size_t length{/* ... */};std::coutstd::format(Raw: {}\n,extractExtension({raw,length}));最后一行代码也可以i写成这样std::cout std::format(Raw: {}\n, extractExtension(std::string_view { raw, length } ));注意在每当函数需要将只读字符串作为一个参数时可使用std::string_view替代const std::string或const char*。2.2 string_view转换成string无法从string_view隐式构建一个string。要么使用一个显示的string构造函数要么使用string_view::data()成员。例如假设有以下接收const std::string的函数。voidhandleExtension(conststd::stringextension){/* ... */}不能采用如下方式调用该函数handleExtension(extractExtension(my file.ext));下面是两个可供使用的选项handleExtension(extractExtension(my file.ext).data());// data() methodhandleExtension(std::string{extractExtension(my file.ext)});// explicit constructor由于同样的原因无法连接一个string和string_view。下面的代码将无法编译std::string str{Hello};std::string_view sv{world};autoresult{strsv};你可以对string_view使用data()方法如下所示。auto result1 { str sv.data() };或者你可以使用append()std::string result2{str};result2.append(sv.data(),sv.size());警告返回字符串的函数应返回const std::string或std::string但不应该返回string_view。返回string_view会带来使返回的string_view无效的风险例如当它指向的字符串需要重新分配时。警告将const string或string_view存储为类的数据成员需要确保它们指向的字符串在对象的生命周期内保持有效状态。存储std::string更安全。2.3 std::string_view和临时字符串永远不要使用string_view保存临时字符串的视图考虑以下示例std::string s{Hello};std::string_view sv{s World!};std::coutsv\n;此代码段具有未定义的行为即运行代码时发生的情况取决于编译器和编译器设置。它可能会打印ello World!“等等。为什么是未定义的行为字符串视图sv的初始化表达式将生成一个临时字符串其中包含”Hello World!”。然后string_view存储指向此临时字符串的指针。在第二行代码的末尾这个临时字符串被销毁留下一个悬空指针的string_view。2.4 std::string_view字面量可使用标准用户定义的字面量sv将字符串字面量解释为std::string_view。例如auto sv { My string_viewsv };标准用户定义的字面量sv需要以下几条using命令之一usingnamespacestd::literals::string_view_literals;usingnamespacestd::string_view_literals;usingnamespacestd::literals;usingnamespacestd;3. 非标准字符串许多C程序员都不使用C风格的字符串这有几个原因。一些程序员只是不知道有string类因为它并不总是C规范的一部分。其他程序员发现CString没有提供他们需要的行为或他们不喜欢std::string对字符编码不感知这一事实所以开发了自己的字符串类型。也许最常见的原因是开发框架和操作系统有自己的表达字符串的方式例如Microsoft MFC中的CString类。它常用于向后兼容或解决遗留问题。在C中启动新项目时提前确定团队如何表达字符串是重要的。务必注意以下几点1. 不应当使用C风格的字符串表示。2. 可对自己所使用框架中可用的字符串功能进行标准化如MFC、QT内置的字符串功能。3. 如果为字符串使用std::string应当使用std::string_view将只读字符串作为参数传递给函数否则看一下你的框架是否支持类似于string_view的类。参考[比] 马克·格雷戈勒著 程序喵大人 惠惠 墨梵 译 C20高级编程(第五版)
返回列表