• 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字符('')定义为NUL,只有一个L)
    2. C字符串不记录长度,修改时也不会判断内存空间是否足够。若不够,会造成缓冲区溢出。

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

    1.1.1.易错点

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

    1.1.2.strcpy()

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

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

    char* copyString(const char* str){
        char* newStr = new char[strlen(str) + 1];//strlen()函数可以获得字符串的长度,但是不算
        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[],则返回实际存储的类型,包括'';而如果是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"! )";

    控制台会输出

    hello "world"!

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

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

    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内置的字符串,还是用自己开发的字符串,都应为项目或团队确立一个标准。

  • 相关阅读:
    函数传参的方式
    统计一个文本中单词频次最高的 10 个单词?
    统计一个文本中单词频次最高的 10 个单词?
    python每日练习0801
    关键字驱动小练习
    Fiddler抓包从入门到不放弃
    pycharm2019.1.3激活方法--激活码可以用到2089年
    Selenium+Python调Chrome浏览器时报Traceback (most recent call last): File "C:/Users/EDZ/Desktop/selenium_demo/demo001.py", line 12, in <module>
    JMeter服务端压测,人人都会的小白版本
    XShell命令大全
  • 原文地址:https://www.cnblogs.com/ranbom/p/12675229.html
Copyright © 2020-2023  润新知