C++学习(c++17)——2.X.使用string和string_view


原帖地址LeoRanbom的博客

博主:LeoRanbom

只在原帖地址的博客上发布,其他地方看到均为爬取。

如果觉得不错希望来点个赞。

前言

结束了前2天水了一个基础的小程序,现在开始深入学习。本节我将从字符串入手开始复习,将涉及到C语言风格的字符串、C++的字符串、还有C++17在字符串方面提出的新标准新特性。

1.动态字符串

在接触了java、Python等面向对象的语言,它们的字符串有很多方便的特性:可拓展至任意大小,提取或替换子字符串。但转过头来看c语言就会感觉特别地蛋疼。它连边界检测都没有。

1.1.C风格的字符串

众所周知,C语言的字符串是char类型的数组。通常我们会以char*的形式来给它动态分配内存。

缺点:

  1. C字符串不会记录自己的长度信息,获取字符串长度时,会遍历字节数组,直到遇到null字符,复杂度O(n)。(官方将null字符('\0')定义为NUL,只有一个L)
  2. C字符串不记录长度,修改时也不会判断内存空间是否足够。若不够,会造成缓冲区溢出。

虽然用的是C++,但是经常会调用一些用C编写的接口。因此我们也要去了解C风格的字符串

1.1.1.易错点

C字符串经常会被遗漏掉'\0'。举个例子,"hello"是5个字母,但是却由'h','e','l','l','o','\0'这6个字符组成,所以在内存空间中其也是6个字节。而C语言对字符串的操作strcpy()和strcat()函数是极其不安全的,他们不会对字符串的长度进行检测。

1.1.2.strcpy()

这是对字符串进行复制的函数。它有2个字符串参数,将第二个字符串复制到第一个字符串,但是却不会考虑空间是否足够。

这里用函数包装改良一下来解决原有的问题:

char* copyString(const char* str){
    char* newStr = new char[strlen(str) + 1];//strlen()函数可以获得字符串的长度,但是不算\0
    strcpy(newStr,str);
    return newStr;
}//记得使用完后释放copyString()分配的堆内存

1.1.3.strcat()

这里是对字符串进行拼接的函数。它也是2个字符串参数,将第二个字符串赋值到第一个后面,并储存在第一个。同样不会判断内存空间。

这里也用函数来包装一下,让它能拼接3个字符串,并且自动扩充空间。

char* addStrings(const char* str1,const char* str2,const char* str3){
    char* newStr = new char[strlen(str1) + strlen(str2) + strlen(str3) + 1];
    strcpy(newStr,str1);
    strcat(newStr,str2);
    strcat(newStr,str3);
    return newStr;
}

1.1.4.对字符串用sizeof()关键字或者strlen()函数的区别

在上面的1.1.2与1.1.3中能看到,我是用strlen()来获取字符串的长度。sizeof是获取字节数,按道理也可以表示长度啊。

这里我来排一下坑(实验室组织的某次考核中,我因为概念不清,稀里糊涂地用混了,但王学长私下告诉我那次是第一,并且让我继承了他的电路书,这电路书是我疫情期间接触过的唯一一本课本,感激涕零)

在C风格的字符串里,sizeof()会根据字符串的存储方式来返回不同的大小。如果是char[],则返回实际存储的类型,包括'\0';而如果是char*,则sizeof()会根据平台的不同,返回你的系统下指针的内存大小(32位是4,64位是8)

1.1.5.安全C库

VS里用C风格的字符串函数时会出现警告,说是已经废弃了。使用strcpy_s()和strcat_s()这些“安全C库”即可避免。不过还是最好切换到C++的std::string类。

1.2.字符串字面量

1.2.1.字面量

cout<<"hello"<<endl;

这样包含字符串本身,而不是包含字符串变量。它本身是一个字符串字面量(string literal),以值的形式写出,而不是以变量的形式。而与字面量相关联的内存位于内存只读区。通过这种机制,编译器可以对相同的字符串字面量进行复用,从而优化内存使用。e.g.一个程序用了几百次"hello"字符串字面量,但其实只创建了一个hello实例,这就是字面量池(literal pooling)

C++标准指出字符串字面量是const char的数组。它可以被赋值给变量,但在用指针指向的时候会产生风险。如:

char* ptr = "hello";//声明一个字符串变量(指针指向字符串字面量)
ptr[1] = 'a';//Undefined Behaviour

本身字面量是const不允许修改,但这里硬是修改的话很明显就是一个ub。它根据编译器的不同,可能会导致程序崩溃,可能会继续运行;可能会产生改变,可能会忽视改变。

更安全一点的写法习惯则是:

const char* ptr = "hello";
ptr[1] = 'a';//ERROR,attempt to write to read-only memory

这样子写的话,编译器会因为“尝试向只读区域写入”而报错。

注:如果用的是字符数组char[]的形式,开辟了对应大小的内存。那么这个字面量不会放在只读的内存区,也不会使用字面量池。当然也就可以自由改变。

1.2.2.原始字符串字面量

原始字符串字面量是不对转义字符进行转换的处理方式。它以R"(开头,以)"结尾。

cout<<R"(hello "world"!\n)";

控制台会输出

hello "world"!\n

而如果想要换行的话,在原始字符串字面量里直接打回车就可以了。

但因为结尾是)",所以其中不能有)"存在,否则会报错。解决方法是使用拓展的原始字符串字面量语法——可选的分隔符序列。

R"d-char-sequence(lalala)d-char-sequence"

分隔符序列最多能有16个字符。

1.3.C++ std::string类

C++中,std::string是一个类(实际上是basic_string模板类的一个实例),这个类支持 中提供的很多功能,还可以自动管理内存分配。在 头文件中被定义。

1.3.1.有C的字符串,为什么还有C++的字符串?

C风格字符串的优势和劣势

优势:

  • 简单,只用到基本数据类型(char)和数组结构
  • 轻量,由于结构简单,可以只占用所需的内存。
  • 低级,可以按照操作原始内存的方式来对字符串进行操作。
  • 能够被C程序员理解

劣势:

  • 为了模拟字符串,要花很多努力。
  • 使用难度大,容易产生bug,不安全
  • 没有用到C++的oop思想
  • 要求程序员理解底层

1.3.2.使用string类

尽管string是一个类,但我更喜欢把它看做一个简单的数据类型。通过运算符重载,让对string的操作更为直观和简单。

string A("12");
string B("34");
string C;
C = A + B;//C is "1234"

+=,==,!=,<,[]等都被重载了。

而C语言中字符串的不安全(如果一个是char[],一个是char*,则会返回false,因为它比较的是指针的值,而非字符串内容。需要用strcmp(a,b)0来比较)

为了达到兼容的目的,可以用string类的c_str()方法来获得一个C风格的const字符指针。不过一旦string进行内存重分配,或者对象被销毁,这个指针也会跟着失效。

注意,不要从函数中返回在基于堆栈的string上调用c_str()方法得来的结果。

在C++17中,data()方法在非const字符调用时,会返回char(而14或更早的版本始终返回const char)

1.3.3.std::string字面量

源代码中的字符串仍是const char*,但如果"xxx"s,那么可以把字面量变成std::string。

但需要std::string_literals或者std命名空间。

1.3.4.高级数值转换

std名称空间中有很多辅助函数,以便完成数值和字符串的转换

  • string to_string(int val);
  • string to_string(unsigned val);
  • string to_string(long val);
  • string to_string(unsigned long val);
  • string to_string(long long val);
  • string to_string(unsigned long long val);
  • string to_string(float val);
  • string to_string(double val);
  • string to_string(long double val);

还有将字符串转换为数值,其中str是要转换的字符串,idx是一个指针,接收第一个未转换字符的索引,base表示转换过程中使用的进制数。指针可以是空指针,如果是空指针,则被忽略。如果不能执行任何转换,则会跑出invalid_argument异常,如果超出返回类型的范围,则抛出out_of_range异常

  • int stoi(const string& str, size_t *idx=0,int base=0);
  • long stol(const string& str, size_t *idx=0,int base=0);
  • unsigned long stoul(const string& str, size_t *idx=0,int base=0);
  • longlong stoll(const string& str, size_t *idx=0,int base=0);
  • unsigned long long stoull(const string& str, size_t *idx=0,int base=0);
  • 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);

1.3.5.低级转换(C++17)

C++17中提供了许多低级数值转换函数,在 头文件中。这些函数不执行内存分配,而使用调用者分配的内存。对他们进行优化,可以实现高性能,并独立于本地化。与高级转换相比,性能更高,速度也要快几个数量级。(如果要求更高,需要进行独立于本地化的转换,则应使用这些函数;如,在数值数据与可读格式【json、xml等】之间序列化/反序列化)

若要将整数转换为字符,可以使用下面这组函数:to_char_result to_chars(char* first, char* last, IntegerT value, int base = 10);。这里IntegerT可以是任何整数类型或字符类型。返回结果是to_char_result类型,看看定义:

struct to_char_result{
    char* ptr;
    errc ec;
};

如果成功转换,ptr成员将等于所写入字符的下一个位置(one-past-the-end)的指针,如果转换失败(即ec == errc.value_to_large),则它等于last。

下面举个示例:

低级转换.png

类似地,浮点数也可以被转换:

to_chars_result to_chars(char* first,char* last, FloatT value);
to_chars_result to_chars(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);

可以通过修改chars_format来修改浮点数的数据类型

enum class chars_float{
    scientific, 	//Style:(-)d.ddde±dd
    fixed,			//Style:(-)ddd.ddd
    hex,			//Style:(-)h.hhhp±d (NoteL no 0x)
    general = fixed|scientific
}

默认格式是chars_format::general,将导致to_chars()将浮点值转换为fixed的十进制表示形式或者scientific的十进制表示形式,得到最短的表示形式,小数点前至少有以为存在。如果指定了格式,但是没有指定精度,那么会自动确定最简短的表示形式。最大精度是6个数字。

而对于相反的转换(即数->字符串),则有一下一组函数:

from_chars_result from_chars(const char* first, const char* last, IntegerT& value, int base = 10);
from_chars_result from_chars(const char* first, const char* last, FloatT& value, chars_format format = chars_format::general);

Here ,from_chars_result is a type defined as follows:

struct from_chars_result{
	const char* ptr;
    errc ec;
};

from_chars不会忽略任何前导空白。ptr指向未转换的第一个字符。如果全部转换,那么指向last,如果全没转换,指向first,ec将为errc::invalid_argument。如果值过大,则为errc::result_out_of_range。

1.4.std::string_view类(C++17)

​ 在之前,为了接受只读字符串的函数选择形参类型一直让人很犹豫。不知道是用const string&还是const char*。C++17中引入了std::string_view类来解决这类问题。

​ 它是std::basic_string_view类模板的实例化,在<string_view>头文件中定义。是const string&的简易替代品。不会额外复制字符串,所以不会额外产生内存开销,它支持和string类似的接口,但是缺少c_str()。并且添加了remove_prefix(size_t)和remove_suffix(size_t)方法。前者将起始指针 前移一定偏移量来收缩字符串,后者则将结尾指针倒退一定的偏移量来收缩。

注意:无法连接一个string和string_view,无法编译(解决方法:使用string_view.data()这一方法)

当形参是string_view时,你就可以传入string、 char*、字符串字面量(常量).

而如果以const string&为参数,则不能传入字符串字面量常量和 char*。只能用string。(string_view转换为string类方法:

1.xxx.data();

2.string(xxx)//explicit ctor。

1.4.1.string_view字面量

可使用"xxxxx"sv来让字面量解释为std::string_view。需要命名空间std::string_view_literals或者直接std。

1.5.非标准字符串

很多人不喜欢用C++风格的字符串,有些是因为不知道,有些则是不合口味。但无论是用MFC、QT内置的字符串,还是用自己开发的字符串,都应为项目或团队确立一个标准。

猜你喜欢

转载自www.cnblogs.com/ranbom/p/12675229.html