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 语言的字符串操作函数,在 <cstring> 头文件中定义。通常这些函数不直接操作内存分配
- 例如,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 类
- 这个类支持 <cstring> 中的许多功能,还能自动管理内存分配。string 类在 std 名称空间的 <string> 头文件中定义
- 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 <string> #include <iostream>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&,但显然,这并不是一个优雅的解决方案
- 1、它应当是 const char* 吗?
-
在 C++17 中,通过引入 std:string_view 类解决了所有这些问题
- std:string_view 类是 std::basic string_view类模板的实例化,在 <string_view> 头文件中定义
- string_view 基本上就是 const string& 的简单替代品,但不会产生额外开销
- 它从不复制字符串,string_view 支持与 std:string 类似的接口
- 一个例外是缺少 c_str(),但 data() 是可用的
- 无法连接一个 string 和一个 string_view
- std:string_view 类是 std::basic string_view类模板的实例化,在 <string_view> 头文件中定义
-
通常按值传递 string_view,因为它们的复制成本极低,它们只包含指向字符串的指针以及字符串的长度
#include <iostream> #include <string> #include <string_view> #include <cstddef>using namespace std; // 提取给定文件名的扩展名并返回 string_view extractExtension(string_view fileName) {return fileName.substr(fileName.rfind('.')); }int main() {// 该函数可用于所有类型的不同字符串// C++ std::stringstring fileName = R"(c:\temp\my file.ext)";cout << "C++ string: " << extractExtension(fileName) << endl;// C-style stringconst char *cString = R"(c:\temp\my file.ext)";cout << "C string: " << extractExtension(cString) << endl;// String literalcout << "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;