《C++高级编程》读书笔记(二:使用string和string_view)

1、参考引用

  • C++高级编程(第4版,C++17标准)马克·葛瑞格尔

2、建议先看《21天学通C++》 这本书入门,笔记链接如下

  • 21天学通C++读书笔记(文章链接汇总)

1. 动态字符串

1.1 C 风格的字符串

  • 在 C 语言中,字符串表示为字符的数组,字符串中的最后一个字符是 null 字符(‘\0’),null 和 NULL 指针是两回事
  • 使用 C 字符串时最常犯的错误是忘记为 ‘\0’ 字符分配空间。例如:字符串 “hello” 看上去只有 5 个字符长,但在内存中需要 6 个字符的空间才能保存这个字符串的值
  • C++ 包含一些来自 C 语言的字符串操作函数,在 头文件中定义。通常这些函数不直接操作内存分配
    • 例如,strpy() 函数有两个字符串参数。这个函数将第二个字符串复制到第一个字符串,而不考虑第二个字符串能否恰当地填入第一个字符串
    char *copyString(const char *str) {
        char *result = new char[strlen(str) + 1]; // +1 是为了给 '\0' 字符分配空间
        strcpy(result, str);
    
        return result;
    }
    
    • 如果函数接收 3 个字符串参数,并返回一个由这 3 个字符串串联而成的字符串
    char *appendStrings(const char *str1, const char *str2, const char *str3) {
        char *result = new char[strlen(str1) + strlen(str2) + strlen(str3) + 1];
        strcpy(result, str1);
        strcat(result, str2); // strcat() 表示串联拼接
        strcat(result, str3);
    
        return result;
    }
    
  • C 和 C++ 中的 sizeof() 操作符可用于获得给定数据类型或变量的大小。例如 sizeof(char) 返回 1,因为字符的大小是 1 字节。但在 C 风格的字符串中,sizeof() 和 stlen() 是不同的,绝对不要通过 sizeof() 获得字符串的大小
    • 如果 C 风格的字符串存储为 char[],则 sizeof() 返回字符串使用的实际内存,包括 ‘\0’ 字符
    • 如果 C 风格的字符串存储为 char*,sizeof() 就返回指针的大小

1.2 字符串字面量

  • C++ 程序中编写的字符串要用引号包围
    cout << "hello" << endl;
    
  • 上面代码中的 “hello” 是一个字符串字面量,因为这个字符串以值的形式写出,而不是一个变量
    • 与字符串字面量关联的真正内存位于内存的只读部分
    • 通过这种方式,编译器可重用等价字符串字面量的引用,从而优化内存的使用。也就是说,即使一个程序使用了 500 次 “helo” 字符串字面量,编译器也只在内存中创建一个 hello 实例,这种技术称为字面量池
    • 字符串字面量可赋值给变量,但因为字符串字面量位于内存的只读部分,且使用了字面量池,所以这样做会产生风险
  • 一种更安全的编码方法是在引用字符串常量时,使用指向 const 字符的指针
    const char *ptr = "hello";
    ptr[1] = 'a'; // 错误,只读不可修改
    
  • 还可将字符串字面量用作字符数组 (char[]) 的初始值。这种情况下,编译器会创建一个足以放下这个字符串的数组,然后将字符串复制到这个数组。因此,编译器不会将字面量放在只读的内存中,也不会进行字面量的池操作
    char arr[] = "hello";
    arr[1] = 'a'; // 可以修改
    
1.2.1 原始字符串字面量
  • 原始字符串字面量是可横跨多行代码的字符串字面量,不需要转义嵌入的双引号
  • 原始字符串字面量以 R"( 开头,以 )” 结尾
    // 普通字符串
    const char *str = "Hello "World"!"; // 错误!必须使用转义双引号
    const char *str = "Hello \"World\"!"; // 正确
    // 原始字符串字面量
    const char *str = R"(Hello "World"!)"; // 以 R"( 开头,以 )” 结尾
    
    // 普通字符串 
    const char *str = "Line 1\nLine 2"; // 包含多行,需要 \n 转义序列
    // 原始字符串字面量
    const char *str = R"(Line 1
    Line 2)";
    

1.3 C++ std::string 类

1.3.1 使用 string 类
  • 这个类支持 中的许多功能,还能自动管理内存分配。string 类在 std 名称空间的 头文件中定义
  • string 运算符重载
    string A("12");
    string A("34");
    string C;
    C = A + B; // C 为 “1234”
    
    string A("12");
    string A("34");
    A += B; // A 为 “1234”
    
  • C 字符串的另一个问题是不能通过 ==、<、<=、>= 运算符进行比较,而 string 都重载了这些运算符,这些运算符可以操作真正的字符串字符,单独的字符可通过运算符 operator[] 访问
    // C 字符串需要通过 strcmp() 根据字符串的字典顺序返回 -1、0、1 的值判断
    if (strcmp(a,b) == 0)
    
  • 当需要扩展 string 时,string 类能够自动处理内存需求,因此不会再出现内存泄漏情况(所有这些 string 对象都创建为堆栈变量,string 类的析构函数会在 string 对象离开作用域时清理内存)
    #include 
    #include 
    
    using namespace std;
    
    int main() {
        string myString = "hello";
        myString += ", there"; // 字符串拼接
        
        string myOtherString = myString; // = 号用于复制字符串
        if (myString == myOtherString) {
            myOtherString[0] = 'H'; // 单独的字符可通过运算符 operator[] 访问
        }
        
        cout << myString << endl;
        cout << myOtherString << endl;
        
        return 0;
    }
    
  • 为达到兼容的目的,还可应用 string 类的 c_str() 方法获得一个表示 C 风格字符串的 const 字符指针
    • 不过一旦 string 执行任何内存重分配或 string 对象被销毁了,返回的这个 const 指针就失效了。应该在使用结果之前调用这个方法,以便它准确反映 string 当前的内容
    • 永远不要从函数中返回在基于堆的 string 上调用 c_str() 的结果
    • 还有一个 data() 方法,在 C++14 及更早的版本中,始终与 c_str() 一样返回 const char*。从 C++17 开始,在非 const 字符上调用时,data() 返回 char*
1.3.2 std::string 字面量
  • 源代码中的字符串字面量通常解释为 const char*。用户定义的标准字面量 s 可把字符串字面量解释为 std::string
    auto string1 = "Hello World"; // string1 是 const char*
    auto string2 = "Hello World"s; // string2 是 std::string
    
1.3.3 数值转换
  • 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 表示使用的进制
    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);
    
  • 示例
    // 数值转换为字符串
    long double d = 3.14L;
    string s = to_stirng(d); // 将 long double 值转换为字符串
    
    // 字符串转换为数值
    const string toParse = "   123USD";
    size_t index = 0;
    int value = stoi(toParse, &index);
    

1.4 std::string_view 类

  • 在 C++17 之前,为接收只读字符串的函数选择形参类型一直是一件进退两难的事情

    • 1、它应当是 const char* 吗?
      • 那样的话,如果客户端可使用 std:string,则必须调用其上的 c_str() 或 data() 来获取 const char*
    • 2、改用 const std:string& ?
      • 这种情况下,始终需要 std:string。例如,如果传递一个字符串字面量,编译器将自动创建一个临时字符串对象(其中包含字符串字面量的副本),并将该对象传递给函数,因此会增加开销
    • 现有解决方案:编写同一函数的多个重载版本,一个接收 const char*,另一个接收 const string&,但显然,这并不是一个优雅的解决方案
  • 在 C++17 中,通过引入 std:string_view 类解决了所有这些问题

    • std:string_view 类是 std::basic string_view类模板的实例化,在 头文件中定义
      • string_view 基本上就是 const string& 的简单替代品,但不会产生额外开销
      • 它从不复制字符串,string_view 支持与 std:string 类似的接口
      • 一个例外是缺少 c_str(),但 data() 是可用的
      • 无法连接一个 string 和一个 string_view
  • 通常按值传递 string_view,因为它们的复制成本极低,它们只包含指向字符串的指针以及字符串的长度

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    // 提取给定文件名的扩展名并返回
    string_view extractExtension(string_view fileName) {
        return fileName.substr(fileName.rfind('.'));
    }
    
    int main() {
        // 该函数可用于所有类型的不同字符串
        // C++ std::string
        string fileName = R"(c:\temp\my file.ext)";
        cout << "C++ string: " << extractExtension(fileName) << endl;
        
        // C-style string
        const char *cString = R"(c:\temp\my file.ext)";
        cout << "C string: " << extractExtension(cString) << endl;
        
        // String literal
        cout << "Literal: " << extractExtension(R"(c:\temp\my file.ext)") << endl;
        
        // Raw string buffer with given length.
        const char* raw = "test.ext";
        size_t length = 8;
        cout << "Raw: " << extractExtension(string_view(raw, length)) << endl;
        // 无法从 string view 隐式构建一个 string
        // 要么使用一个显式的 string 构造函数,要么使用 string_view:data() 成员
        string extension = extractExtension(fileName).data();
    
        return 0;
    }
    
  • std::string_view 字面量

    using namespace std;
    
    auto sv = "My string view"sv;
    

你可能感兴趣的:(C++进阶学习笔记,学习,c++,算法,笔记)