C++ STL string容器全解析
一、引言
在 C++ 编程的广阔领域中,字符串处理是一项极为基础且频繁的操作。从简单的文本解析,到复杂的文件读取与处理,字符串几乎无处不在。而 C++ 中的 string 容器,就像是一把瑞士军刀,为我们处理字符串提供了强大而便捷的工具。
与传统 C 语言中使用以 '\0' 结尾的字符数组来表示字符串不同,C++ 的 string 容器将字符串操作封装成了一个类,这使得字符串的处理变得更加直观、安全且高效 。在 C 语言中,我们需要手动管理字符数组的内存,一不小心就可能会出现内存泄漏、数组越界等问题。例如,在使用strcpy函数复制字符串时,如果目标数组的空间不足,就会导致缓冲区溢出,进而引发程序崩溃或安全漏洞。而 string 容器则自动管理内存,我们无需再为这些底层细节操心,可以将更多的精力放在业务逻辑的实现上。
string 容器还提供了丰富的成员函数,涵盖了字符串的拼接、查找、替换、比较等各种常见操作。例如,当我们需要将多个字符串连接在一起时,只需使用+运算符或append函数即可轻松实现,而无需像在 C 语言中那样手动分配足够的空间并逐个字符地复制。在查找特定子串时,find函数能快速定位子串的位置,大大提高了开发效率。在处理复杂的文本处理任务,如文本分析、词法分析等场景中,string 容器的这些特性更是发挥得淋漓尽致。
在实际的项目开发中,string 容器的应用极为广泛。在 Web 开发中,我们经常需要处理用户输入的各种字符串,如用户名、密码、搜索关键词等,string 容器可以方便地对这些字符串进行验证、格式化和处理。在游戏开发中,文本的显示、剧情的加载等也离不开字符串处理,string 容器能够高效地完成这些任务。在数据处理和分析领域,string 容器同样发挥着重要作用,我们可以使用它来解析和处理各种格式的数据文件。
接下来,让我们深入探索 C++ string 容器的奥秘,从基础的创建与初始化,到各种常用操作,再到高级应用与性能优化,逐步掌握这一强大工具的使用技巧,提升我们的 C++ 编程能力。
二、string 容器初相识
2.1 基本概念
在 C++ 中,string是一种表示字符串的类类型,它是 C++ 风格字符串的代表,本质上是一个封装了字符数组及其操作的类 ,定义在<string>头文件中,并位于std命名空间下。这意味着在使用string时,我们需要包含<string>头文件,并且通常使用std::string来引用它,或者通过using namespace std;语句后直接使用string。
与传统的char*(字符指针)相比,string具有更高的安全性和便利性。char*只是一个指向字符数组的指针,需要手动管理内存,容易出现内存泄漏、越界访问等问题。例如,在使用char*时,如果我们忘记分配足够的内存,或者在释放内存时出现错误,就会导致程序崩溃或产生难以调试的错误。而string类内部封装了对字符数组的管理,自动分配和释放内存,大大减少了这些风险。同时,string类还提供了丰富的成员函数,用于字符串的操作,如拼接、查找、替换等,使字符串处理变得更加简单和高效。
2.2 构造函数大揭秘
string类提供了多种构造函数,以满足不同的初始化需求。下面我们来详细了解一下:
- 默认构造函数:string(),用于创建一个空字符串。
std::string str1; // 创建一个空字符串
- 使用 C 风格字符串初始化:string(const char* s),使用一个以'\0'结尾的 C 风格字符串来初始化string对象。
const char* c_str = "Hello, C++!";
std::string str2(c_str); // 使用C风格字符串初始化
- 拷贝构造函数:string(const string& str),使用一个已有的string对象来初始化另一个string对象。
std::string str3(str2); // 拷贝构造
- 使用 n 个字符初始化:string(int n, char c),创建一个包含n个字符c的字符串。
std::string str4(5, 'a'); // 创建一个包含5个'a'的字符串,即"aaaaa"
- 使用子串初始化:string(const string& str, size_t pos, size_t len = npos),从str中指定位置pos开始,截取长度为len的子串来初始化新的string对象。如果不指定len,则截取到str的末尾。这里的npos是string类中定义的一个静态成员,表示size_t类型的最大值,通常用于表示字符串的末尾位置。在实际使用中,当我们需要获取一个字符串从某个位置开始的剩余部分时,就可以利用这个特性,直接省略len参数。
std::string original = "Hello, World!";
std::string str5(original, 7, 5); // 从位置7开始截取5个字符,得到"World"
- 使用字符数组的一部分初始化:string(const char* s, size_t n),使用 C 风格字符串s的前n个字符来初始化string对象。
const char* partial_c_str = "Hello, C++!";
std::string str6(partial_c_str, 5); // 使用前5个字符,得到"Hello"
- 使用迭代器范围初始化:template <class InputIterator> string (InputIterator first, InputIterator last),使用迭代器first和last指定的范围来初始化string对象,其中[first, last)是一个左闭右开区间。这在需要从其他容器中提取字符序列来创建string时非常有用,比如从vector<char>中创建string。
#include <vector>
std::vector<char> char_vec = {'H', 'e', 'l', 'l', 'o'};
std::string str7(char_vec.begin(), char_vec.end()); // 使用vector<char>的迭代器范围初始化
通过这些构造函数,我们可以根据具体的需求灵活地创建和初始化string对象,为后续的字符串操作打下基础。
三、赋值操作:轻松赋予字符串新内涵
在 C++ 的 string 容器中,赋值操作是一项基础且常用的功能,它允许我们为字符串对象赋予新的内容。string 类提供了多种赋值方式,以满足不同的编程需求。
3.1 赋值函数原型
- operator= 重载:
-
- string& operator=(const char* s);:将 C 风格字符串s赋值给当前string对象。
-
- string& operator=(const string& s);:将另一个string对象s赋值给当前string对象。
-
- string& operator=(char c);:将单个字符c赋值给当前string对象。
- assign 函数:
-
- string& assign(const char* s);:将 C 风格字符串s赋值给当前string对象。
-
- string& assign(const char* s, size_t n);:将 C 风格字符串s的前n个字符赋值给当前string对象。
-
- string& assign(const string& s);:将另一个string对象s赋值给当前string对象。
-
- string& assign(size_t n, char c);:将n个字符c赋值给当前string对象。
3.2 代码示例
#include <iostream>#include <string>int main() {// 使用operator=进行赋值std::string str1;str1 = "Hello, C++!"; // 将C风格字符串赋值给str1std::cout << "str1: " << str1 << std::endl;std::string str2;str2 = str1; // 将str1赋值给str2std::cout << "str2: " << str2 << std::endl;std::string str3;str3 = 'A'; // 将单个字符'A'赋值给str3std::cout << "str3: " << str3 << std::endl;// 使用assign进行赋值std::string str4;str4.assign("Programming is fun"); // 将C风格字符串赋值给str4std::cout << "str4: " << str4 << std::endl;std::string str5;str5.assign("C++ Primer", 6); // 将C风格字符串的前6个字符赋值给str5std::cout << "str5: " << str5 << std::endl;std::string str6;str6.assign(str5); // 将str5赋值给str6std::cout << "str6: " << str6 << std::endl;std::string str7;str7.assign(5, 'x'); // 将5个字符'x'赋值给str7std::cout << "str7: " << str7 << std::endl;return 0;}
在上述代码中,我们首先使用operator=重载进行赋值操作。通过str1 = "Hello, C++!";将一个 C 风格字符串直接赋值给str1,这是一种简洁直观的方式,在日常编程中经常用于初始化或更新string对象的内容。str2 = str1;则展示了如何将一个string对象的值赋给另一个string对象,这在需要复制字符串内容时非常有用。str3 = 'A';将单个字符赋值给string对象,这种方式适用于创建只包含一个字符的字符串。
接着,我们使用assign函数进行赋值。str4.assign("Programming is fun");与使用operator=将 C 风格字符串赋值类似,但assign函数提供了更多的灵活性。str5.assign("C++ Primer", 6);通过指定字符个数,将 C 风格字符串的前 6 个字符赋值给str5,这在需要截取部分字符串时非常实用。str6.assign(str5);同样实现了字符串对象之间的赋值。str7.assign(5, 'x');创建了一个包含 5 个字符x的字符串,这种方式在需要生成特定重复字符的字符串时非常方便。
通过这些赋值操作,我们可以灵活地为string对象赋予不同的内容,无论是 C 风格字符串、其他string对象还是单个字符及其重复序列 ,都能轻松应对,为后续的字符串处理和操作奠定基础。
四、拼接操作:字符串的奇妙组合
在实际的字符串处理中,拼接操作是非常常见的。比如在日志记录中,我们需要将时间戳、日志级别、日志内容等多个部分拼接成一条完整的日志信息;在生成 HTML 页面时,我们需要将各种标签和文本内容拼接在一起。C++ 的 string 容器提供了两种主要的拼接方式:重载的+=操作符和append函数,它们各有特点,能够满足不同场景下的拼接需求。
4.1 重载 += 操作符
string类重载了+=操作符,使其可以方便地用于字符串的拼接。通过+=操作符,我们可以将字符、字符串常量以及string对象直接拼接到另一个string对象的末尾。这种方式简洁直观,代码可读性高,在日常编程中被广泛使用。
#include <iostream>#include <string>int main() {std::string str1 = "Hello";// 拼接字符str1 += ',';std::cout << "拼接字符后: " << str1 << std::endl;// 拼接字符串常量str1 += " World!";std::cout << "拼接字符串常量后: " << str1 << std::endl;std::string str2 = " Nice to meet you!";// 拼接string对象str1 += str2;std::cout << "拼接string对象后: " << str1 << std::endl;return 0;}
在上述代码中,首先定义了str1并初始化为 "Hello",然后通过str1 += ',';将字符','拼接到str1的末尾,输出结果为 "Hello,"。接着,使用str1 += " World!";将字符串常量 "World!" 拼接到str1,此时str1变为 "Hello, World!"。最后,定义str2为 "Nice to meet you!",通过str1 += str2;将str2拼接到str1,最终str1的值为 "Hello, World! Nice to meet you!"。这种使用+=操作符的拼接方式非常简洁,代码易于理解和编写。
4.2 append 函数的强大功能
append函数是string类提供的另一种用于字符串拼接的成员函数,它具有多种重载形式,功能十分强大 。通过append函数,我们不仅可以拼接完整的字符串,还可以拼接指定长度的子串以及指定位置开始的子串,这在处理复杂字符串拼接需求时非常有用。
#include <iostream>#include <string>int main() {std::string str1 = "I like ";// 拼接字符串str1.append("programming.");std::cout << "拼接字符串后: " << str1 << std::endl;std::string str2 = "C++ is a powerful language.";// 拼接指定长度子串str1.append(str2, 0, 7);std::cout << "拼接指定长度子串后: " << str1 << std::endl;// 拼接指定位置子串str1.append(str2, 10, 9);std::cout << "拼接指定位置子串后: " << str1 << std::endl;return 0;}
在这段代码中,首先定义str1为 "I like",然后使用str1.append("programming.");将字符串 "programming." 拼接到str1,输出结果为 "I like programming."。接着,定义str2为 "C++ is a powerful language.",通过str1.append(str2, 0, 7);从str2的起始位置(下标 0)开始,截取 7 个字符(即 "C++ is")拼接到str1,此时str1变为 "I like programming.C++ is"。最后,使用str1.append(str2, 10, 9);从str2的下标 10 开始,截取 9 个字符(即 "powerful")拼接到str1,最终str1的值为 "I like programming.C++ is powerful"。通过这些示例,可以看到append函数在拼接字符串时的灵活性和强大功能,能够满足各种不同的拼接需求。
五、查找和替换:精准定位与灵活修改
在字符串处理中,查找和替换是非常常见的操作。C++ 的string容器提供了丰富的函数来实现这些功能,使得我们能够高效地在字符串中查找特定的字符或子串,并进行替换操作。
5.1 查找函数全解析
string类提供了find和rfind等函数来进行查找操作,它们的原型如下:
- size_t find(const string& str, size_t pos = 0) const;:从位置pos开始查找子串str第一次出现的位置。
- size_t find(const char* s, size_t pos = 0) const;:从位置pos开始查找 C 风格字符串s第一次出现的位置。
- size_t find(const char* s, size_t pos, size_t n) const;:从位置pos开始查找 C 风格字符串s的前n个字符第一次出现的位置。
- size_t find(char c, size_t pos = 0) const;:从位置pos开始查找字符c第一次出现的位置。
- size_t rfind(const string& str, size_t pos = npos) const;:从位置pos开始反向查找子串str最后一次出现的位置。
- size_t rfind(const char* s, size_t pos = npos) const;:从位置pos开始反向查找 C 风格字符串s最后一次出现的位置。
- size_t rfind(const char* s, size_t pos, size_t n) const;:从位置pos开始反向查找 C 风格字符串s的前n个字符最后一次出现的位置。
- size_t rfind(char c, size_t pos = 0) const;:从位置pos开始反向查找字符c最后一次出现的位置。
这些函数返回值为size_t类型,表示找到的位置。如果未找到,返回string::npos,这是一个特殊的常量,表示size_t类型的最大值,通常用于表示字符串的末尾位置或查找失败的情况。
下面通过代码示例来演示这些函数的用法:
#include <iostream>#include <string>int main() {std::string str = "Hello, World! Hello, C++!";// 从左往右查找子串size_t pos1 = str.find("World");if (pos1 != std::string::npos) {std::cout << "找到子串 World,位置是: " << pos1 << std::endl;} else {std::cout << "未找到子串 World" << std::endl;}// 从指定位置开始查找字符size_t pos2 = str.find('C', 10);if (pos2 != std::string::npos) {std::cout << "从位置10开始找到字符 C,位置是: " << pos2 << std::endl;} else {std::cout << "从位置10开始未找到字符 C" << std::endl;}// 从右往左查找子串size_t pos3 = str.rfind("Hello");if (pos3 != std::string::npos) {std::cout << "找到子串 Hello,最后一次出现的位置是: " << pos3 << std::endl;} else {std::cout << "未找到子串 Hello" << std::endl;}// 从指定位置开始反向查找指定长度子串const char* sub_str = "llo";size_t pos4 = str.rfind(sub_str, 15, 3);if (pos4 != std::string::npos) {std::cout << "从位置15开始反向找到长度为3的子串 llo,位置是: " << pos4 << std::endl;} else {std::cout << "从位置15开始反向未找到长度为3的子串 llo" << std::endl;}return 0;}
在上述代码中,首先定义了字符串str。然后使用find函数从左往右查找子串 "World",并输出其位置。接着从位置 10 开始查找字符 'C',展示了从指定位置查找字符的用法。再使用rfind函数从右往左查找子串 "Hello",输出其最后一次出现的位置。最后从位置 15 开始反向查找长度为 3 的子串 "llo",展示了反向查找指定长度子串的用法。通过这些示例,可以清晰地看到find和rfind函数在不同场景下的应用。
5.2 替换操作详解
string类的replace函数用于替换字符串中的子串,其原型如下:
- string& replace(size_t pos, size_t len, const string& str);:从位置pos开始,替换长度为len的子串为str。
- string& replace(size_t pos, size_t len, const char* s);:从位置pos开始,替换长度为len的子串为 C 风格字符串s。
下面通过代码示例来演示替换操作:
#include <iostream>#include <string>int main() {std::string str = "Hello, World!";// 替换子串str.replace(7, 5, "C++");std::cout << "替换后的字符串: " << str << std::endl;// 替换指定位置和长度的子串为C风格字符串str.replace(0, 5, "Hi");std::cout << "再次替换后的字符串: " << str << std::endl;return 0;}
在这段代码中,首先定义了字符串str为 "Hello, World!"。然后使用replace函数从位置 7 开始,替换长度为 5 的子串(即 "World")为 "C++",输出替换后的字符串。接着从位置 0 开始,替换长度为 5 的子串(即 "Hello")为 "Hi",再次输出替换后的字符串。通过这两个示例,展示了replace函数替换指定位置和长度子串的基本操作。
六、字符串比较:判断字符串的异同
在 C++ 编程中,判断两个字符串是否相同或者它们之间的大小关系是一项常见的操作。C++ 的string容器提供了compare函数来实现字符串的比较,它为我们在各种字符串处理场景中提供了精确判断的能力。
6.1 函数原型剖析
string类的compare函数有以下几种常见的原型:
- int compare(const string& s) const;:将当前字符串与字符串s进行比较。
- int compare(const char* s) const;:将当前字符串与 C 风格字符串s进行比较。
6.2 比较规则深入理解
compare函数会按照字符的 ASCII 码值,从左到右依次比较两个字符串对应位置的字符。具体的返回值遵循以下规则:
- 如果当前字符串与被比较的字符串相等,返回 0。
- 如果当前字符串大于被比较的字符串,返回一个大于 0 的值。这里的 “大于” 是指在字典序(按照 ASCII 码顺序)中,当前字符串的字符在被比较字符串对应字符之后出现。例如,字符串 "bcd" 大于 "abc",因为在第一个不同字符处,'b' 的 ASCII 码值大于 'a' 的 ASCII 码值。
- 如果当前字符串小于被比较的字符串,返回一个小于 0 的值。例如,字符串 "abc" 小于 "abd",因为在第一个不同字符处,'c' 的 ASCII 码值小于 'd' 的 ASCII 码值。
6.3 代码示例实战
下面通过具体的代码示例来展示compare函数的用法:
#include <iostream>#include <string>int main() {std::string str1 = "Hello";std::string str2 = "Hello";std::string str3 = "World";const char* c_str = "Hello";// 比较两个string对象int result1 = str1.compare(str2);if (result1 == 0) {std::cout << "str1 等于 str2" << std::endl;} else if (result1 > 0) {std::cout << "str1 大于 str2" << std::endl;} else {std::cout << "str1 小于 str2" << std::endl;}// 比较string对象和C风格字符串int result2 = str1.compare(c_str);if (result2 == 0) {std::cout << "str1 等于 c_str" << std::endl;} else if (result2 > 0) {std::cout << "str1 大于 c_str" << std::endl;} else {std::cout << "str1 小于 c_str" << std::endl;}// 比较两个不同的string对象int result3 = str1.compare(str3);if (result3 == 0) {std::cout << "str1 等于 str3" << std::endl;} else if (result3 > 0) {std::cout << "str1 大于 str3" << std::endl;} else {std::cout << "str1 小于 str3" << std::endl;}return 0;}
在上述代码中,首先定义了三个string对象str1、str2和str3,以及一个 C 风格字符串c_str。然后分别使用compare函数进行比较。对于str1和str2,它们的内容相同,所以compare函数返回 0,输出 "str1 等于 str2"。对于str1和c_str,虽然一个是string对象,一个是 C 风格字符串,但内容相同,同样返回 0,输出 "str1 等于 c_str"。而str1和str3内容不同,在字典序中,"Hello" 的首字符 'H' 在 "World" 的首字符 'W' 之前,所以str1小于str3,compare函数返回一个小于 0 的值,输出 "str1 小于 str3"。通过这些示例,可以清晰地看到compare函数在不同字符串比较场景下的应用和返回值的含义。
七、字符存取:深入字符串内部
在处理字符串时,我们常常需要访问和修改字符串中的单个字符。C++ 的string容器提供了两种主要的方式来实现这一操作:[]操作符和at方法。这两种方式各有特点,适用于不同的场景。
7.1 [] 操作符
string类重载了[]操作符,使其可以像访问数组元素一样访问字符串中的指定位置字符。[]操作符的语法非常简洁直观,通过string对象[索引值]的形式,即可获取或修改对应位置的字符 。这里的索引值从 0 开始,例如str[0]表示字符串str的第一个字符。
#include <iostream>#include <string>int main() {std::string str = "Hello, World!";// 读取指定位置字符char ch1 = str[7];std::cout << "位置7的字符是: " << ch1 << std::endl;// 修改指定位置字符str[0] = 'h';std::cout << "修改后的字符串: " << str << std::endl;return 0;}
在上述代码中,首先定义了字符串str为 "Hello, World!"。然后通过str[7]读取了位置 7 的字符,即 'W',并将其赋值给ch1,输出结果为 "位置 7 的字符是: W"。接着,通过str[0] = 'h';将字符串的第一个字符 'H' 修改为 'h',输出修改后的字符串为 "hello, World!"。这种使用[]操作符的方式简单直接,在已知索引位置且确保索引不会越界的情况下,是一种高效的字符存取方式。
7.2 at 方法
at方法是string类提供的另一种用于访问字符串中特定位置字符的成员函数 。它的语法为string对象.at(索引值),与[]操作符类似,但at方法在访问字符时会进行边界检查。当索引值超出字符串的有效范围时,at方法会抛出std::out_of_range异常,这使得程序在访问越界时能够进行更安全的处理,避免出现未定义行为。
#include <iostream>#include <string>#include <exception>int main() {std::string str = "Hello, World!";try {// 安全访问字符char ch2 = str.at(5);std::cout << "位置5的字符是: " << ch2 << std::endl;// 故意越界访问char ch3 = str.at(20);} catch (const std::out_of_range& e) {std::cerr << "捕获到异常: " << e.what() << std::endl;}return 0;}
在这段代码中,首先通过str.at(5)安全地访问了位置 5 的字符,即 ',',输出结果为 "位置 5 的字符是: ,"。然后故意尝试访问位置 20 的字符,由于索引超出了字符串的长度,at方法抛出了std::out_of_range异常,通过catch块捕获并输出异常信息 "捕获到异常: basic_string::at: __n (which is 20) >= this->size () (which is 13)",这表明程序在遇到越界访问时能够及时捕获异常并进行处理,提高了程序的健壮性。
八、插入和删除:改变字符串的结构
在字符串处理过程中,我们常常需要对字符串的内容进行结构上的调整,比如在特定位置插入新的字符或子串,或者删除不需要的部分。C++ 的string容器提供了强大的插入和删除操作函数,让这些操作变得简单高效。
8.1 插入操作
string类的insert函数用于在字符串的指定位置插入字符或字符串,它具有多种重载形式,以满足不同的插入需求。以下是几种常见的重载形式及其说明:
- string& insert(size_t pos, const char* s);:在位置pos处插入 C 风格字符串s。这里的pos是一个size_t类型的值,表示插入的起始位置,从 0 开始计数。例如,str.insert(3, "world");会将 "world" 插入到str字符串中索引为 3 的位置之前。
- string& insert(size_t pos, const string& str);:在位置pos处插入另一个string对象str。这种形式在需要插入一个已有的string对象时非常方便,比如str1.insert(2, str2);,会将str2插入到str1中索引为 2 的位置之前。
- string& insert(size_t pos, size_t n, char c);:在位置pos处插入n个字符c。例如,str.insert(1, 3, 'x');会在str字符串中索引为 1 的位置之前插入 3 个字符'x'。
下面通过代码示例来演示这些插入操作:
#include <iostream>#include <string>int main() {std::string str = "Hello";// 插入C风格字符串str.insert(2, "world");std::cout << "插入C风格字符串后: " << str << std::endl;std::string insert_str = " C++";// 插入string对象str.insert(5, insert_str);std::cout << "插入string对象后: " << str << std::endl;// 插入多个字符str.insert(0, 2, '*');std::cout << "插入多个字符后: " << str << std::endl;return 0;}
在上述代码中,首先定义了字符串str为 "Hello"。然后使用str.insert(2, "world");在索引 2 的位置插入 C 风格字符串 "world",此时str变为 "He worldllo"。接着,定义insert_str为 "C++",通过str.insert(5, insert_str);将insert_str插入到索引 5 的位置,str变为 "He wor C++ldllo"。最后,使用str.insert(0, 2, '*');在字符串开头插入 2 个字符'*',最终str的值为 "**He wor C++ldllo"。通过这些示例,可以清晰地看到insert函数在不同场景下的插入操作。
8.2 删除操作
string类的erase函数用于删除字符串中的字符,其函数原型为string& erase(size_t pos = 0, size_t n = npos);,表示从位置pos开始删除n个字符。如果不指定n,则默认删除从pos开始到字符串末尾的所有字符 。这里的npos是string类中定义的一个静态成员,表示size_t类型的最大值,通常用于表示字符串的末尾位置。
下面通过代码示例来演示删除操作:
#include <iostream>#include <string>int main() {std::string str = "Hello, World!";// 删除指定位置和长度的字符str.erase(7, 5);std::cout << "删除指定位置和长度的字符后: " << str << std::endl;// 删除从指定位置到末尾的字符str.erase(5);std::cout << "删除从指定位置到末尾的字符后: " << str << std::endl;return 0;}
在这段代码中,首先定义了字符串str为 "Hello, World!"。然后使用str.erase(7, 5);从索引 7 的位置开始删除 5 个字符,即删除 "World",此时str变为 "Hello,!"。接着,使用str.erase(5);从索引 5 的位置开始删除到字符串末尾的所有字符,str最终变为 "Hello"。通过这两个示例,展示了erase函数删除指定位置和长度字符以及删除从指定位置到末尾字符的基本操作。
九、获取子串:提取字符串的片段
在字符串处理中,经常会遇到需要从一个完整的字符串中提取特定部分的情况,这就用到了获取子串的操作。C++ 的string容器提供了substr函数来实现这一功能,它就像是一把精准的 “剪刀”,能够按照我们的需求从字符串中裁剪出所需的片段。
9.1 函数原型剖析
substr函数用于从字符串中提取子串,其函数原型为:string substr(size_t pos = 0, size_t len = npos) const;。其中,pos参数指定了子串的起始位置,从 0 开始计数,默认值为 0,表示从字符串的开头开始提取;len参数指定了子串的长度,默认值为npos,表示从起始位置pos开始一直截取到字符串的末尾 。这里的npos是string类中定义的一个静态成员,表示size_t类型的最大值,通常用于表示字符串的末尾位置或查找失败的情况。
9.2 代码示例展示
下面通过具体的代码示例来演示substr函数的用法:
#include <iostream>#include <string>int main() {std::string str = "Hello, World! This is a test.";// 从指定位置提取到字符串末尾std::string sub_str1 = str.substr(7);std::cout << "从位置7提取到末尾的子串: " << sub_str1 << std::endl;// 从指定位置提取指定长度子串std::string sub_str2 = str.substr(7, 5);std::cout << "从位置7提取长度为5的子串: " << sub_str2 << std::endl;return 0;}
在上述代码中,首先定义了字符串str。然后使用str.substr(7);从位置 7 开始提取子串,由于未指定长度,所以一直提取到字符串的末尾,得到的子串为 "World! This is a test.",并输出结果 "从位置 7 提取到末尾的子串: World! This is a test."。接着,使用str.substr(7, 5);从位置 7 开始提取长度为 5 的子串,得到的子串为 "World",输出结果为 "从位置 7 提取长度为 5 的子串: World"。通过这两个示例,可以清晰地看到substr函数在提取子串时的灵活性和实用性。
9.3 实际应用场景
在实际编程中,获取子串的操作有着广泛的应用。比如在处理文件路径时,我们可能需要从完整的路径中提取文件名或文件扩展名。在网络编程中,解析 URL 时也常常需要提取其中的各个部分,如协议、主机名、路径等。在文本分析中,我们可能需要从一段文本中提取特定的单词或短语。例如,在一个简单的邮箱地址解析程序中,我们可以使用substr函数结合find函数来提取邮箱地址中的用户名:
#include <iostream>#include <string>int main() {std::string email = "user@example.com";size_t pos = email.find('@');if (pos != std::string::npos) {std::string username = email.substr(0, pos);std::cout << "邮箱地址中的用户名: " << username << std::endl;} else {std::cout << "邮箱地址格式错误" << std::endl;}return 0;}
在这段代码中,首先使用find函数查找'@'字符的位置,然后通过substr函数从字符串的开头(位置 0)开始,截取到'@'字符之前的部分,即得到了邮箱地址中的用户名。如果未找到'@'字符,则输出 “邮箱地址格式错误”。通过这样的方式,我们可以轻松地从复杂的字符串中提取出所需的信息,展示了substr函数在实际应用中的强大功能。
十、实际应用案例:string 容器的实战演练
10.1 解析邮箱地址
在日常的网络编程和数据处理中,解析邮箱地址是一个常见的任务。我们可以利用string容器的查找和获取子串功能来轻松实现这一操作。下面是一个简单的示例代码:
#include <iostream>#include <string>void parseEmail(const std::string& email) {size_t pos = email.find('@');if (pos != std::string::npos) {std::string username = email.substr(0, pos);std::string domain = email.substr(pos + 1);std::cout << "用户名: " << username << std::endl;std::cout << "域名: " << domain << std::endl;} else {std::cout << "邮箱地址格式错误" << std::endl;}}int main() {std::string email = "user@example.com";parseEmail(email);return 0;}
在上述代码中,首先使用find函数查找'@'字符的位置,这是邮箱地址中用户名和域名的分隔符。如果找到了'@'字符,就通过substr函数分别提取出用户名和域名。substr(0, pos)从字符串的开头(位置 0)开始,截取到'@'字符之前的部分,得到用户名;substr(pos + 1)从'@'字符的下一个位置开始,截取到字符串末尾,得到域名。如果未找到'@'字符,则输出 “邮箱地址格式错误”。
10.2 处理文件路径
在文件系统操作中,经常需要对文件路径进行处理,比如提取文件名、文件扩展名或者目录路径等。string容器的各种操作函数可以帮助我们高效地完成这些任务。以下是一个处理文件路径的示例代码:
#include <iostream>#include <string>void processFilePath(const std::string& path) {size_t pos = path.find_last_of('/');if (pos == std::string::npos) {pos = path.find_last_of('\\');}if (pos != std::string::npos) {std::string directory = path.substr(0, pos);std::string filename = path.substr(pos + 1);std::cout << "目录路径: " << directory << std::endl;std::cout << "文件名: " << filename << std::endl;pos = filename.find_last_of('.');if (pos != std::string::npos) {std::string fileExtension = filename.substr(pos + 1);std::cout << "文件扩展名: " << fileExtension << std::endl;} else {std::cout << "该文件没有扩展名" << std::endl;}} else {std::cout << "路径格式错误" << std::endl;}}int main() {std::string path = "/home/user/documents/file.txt";processFilePath(path);return 0;}
在这段代码中,首先使用find_last_of函数查找路径中的'/'或'\\'字符,这两个字符在不同的操作系统中用于分隔目录和文件。如果找到了分隔符,就通过substr函数提取出目录路径和文件名。接着,再对文件名进行处理,使用find_last_of函数查找'.'字符,以提取文件扩展名。如果找到了'.'字符,就通过substr函数提取出扩展名;如果未找到,则输出 “该文件没有扩展名”。如果在路径中未找到'/'或'\\'字符,则输出 “路径格式错误”。
通过以上两个实际应用案例,可以看到string容器在处理常见的字符串任务时的强大功能和便捷性。无论是解析邮箱地址还是处理文件路径,string容器提供的查找、获取子串等操作都能帮助我们快速准确地完成任务,提高编程效率 。在实际的项目开发中,还有许多其他的应用场景,如日志分析、网络协议解析、文本处理等,string容器都发挥着重要的作用。
相关文章:
C++ STL string容器全解析
一、引言 在 C 编程的广阔领域中,字符串处理是一项极为基础且频繁的操作。从简单的文本解析,到复杂的文件读取与处理,字符串几乎无处不在。而 C 中的 string 容器,就像是一把瑞士军刀,为我们处理字符串提供了强大而便…...
React基础之项目创建
项目创建 create-react-app 项目名(小写) 运行 pnpm run start 在React中,使用的语法格式是jsx,也就是js与html相结合的形式 import logo from ./logo.svg; import ./App.css; function App() { return ( <div className"App"> <head…...
迷你世界脚本道具接口:Item
道具接口:Item 彼得兔 更新时间: 2023-04-26 10:26:18 继承自 Actor 具体函数名及描述如下: 序号 函数名 函数描述 1 getItemName(...) 获取道具名称 2 getItemId(...) 获取actor对应的道具ID,如球类等 3 getDropItemNum(...) …...
Unity摄像机跟随物体
功能描述 实现摄像机跟随物体,并使物体始终保持在画面中心位置。 实现步骤 创建脚本:在Unity中创建一个新的C#脚本,命名为CameraFollow。 代码如下: using UnityEngine;public class CameraFollow : MonoBehaviour {public Tran…...
计算机毕业设计SpringBoot+Vue.js青年公寓服务平台(源码+文档+PPT+讲解)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
vue实现日历签到效果
在工作任务进行时,有一个签到日历的功能需求要实现,经过文档查询和样式优化实现了需求,在此记录一下。 技术背景:vue2vant(样式控件) less 一个公共样式文件 html实现部分: <div class"calenderB…...
(十 八)趣学设计模式 之 观察者模式!
目录 一、 啥是观察者模式?二、 为什么要用观察者模式?三、 观察者模式的实现方式四、 观察者模式的优缺点五、 观察者模式的应用场景六、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,…...
笔记:在Git中.gitmodules文件的功能和作用和如何使用
一、目的:简单介绍下在Git中.gitmodules文件的功能和作用已经 .gitmodules 文件是 Git 子模块(submodule)功能的一部分,用于管理和配置子模块。子模块允许一个 Git 仓库包含另一个 Git 仓库作为其子目录,这对于管理依赖…...
Swift 常量
Swift 常量 引言 Swift 是一种由苹果公司开发的编程语言,主要用于 iOS、macOS、watchOS 和 tvOS 等平台的应用开发。在 Swift 中,常量是一种不可变的变量,它用于存储固定不变的值。了解和使用常量是 Swift 编程的基础,本文将详细介绍 Swift 常量的概念、类型、声明以及使…...
Ubuntu20.04双系统安装及软件安装(七):Anaconda3
Ubuntu20.04双系统安装及软件安装(七):Anaconda3 打开Anaconda官网,在右侧处填写邮箱(要真实有效!),然后Submit。会出现如图示的Success界面。 进入填写的邮箱,有一封Ana…...
Google AI概览升级,AI模式全新登场!
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
【智能体架构:Agent】LangChain智能体类型ReAct、Self-ASK的区别
1. 什么是智能体 将大语言模型作为一个推理引擎。给定一个任务, 智能体自动生成完成任务所需步骤, 执行相应动作(例如选择并调用工具), 直到任务完成。 2. 先定义工具:Tools 可以是一个函数或三方 API也…...
nginx 配置403页面(已亲测)
问题:GET请求访问漏洞url即可看到泄露的内网ip 解决方式: 1.配置nginx 不显示真实Ip 2.限制接口只能是POST请求 具体配置: 编写一个403.html 在nginx的配置文件中,配置location参数: location /api/validationCode…...
安卓基础组件Looper - 02 native层面的剖析
文章目录 native使用使用总结创建Looper构造函数创建(不推荐)使用举例源代码 Looper::prepare 获取Looper可忽略初始化Looper主动休眠 pollAll主动唤醒 wake 发送消息 sendMessage轮询消息 native使用 Android Native Looper 机制 - 掘金 (juejin.cn) /system/core/libutils/…...
nodejs关于后端服务开发的探究
前提 在当前的环境中关于web server的主流开发基本上都是java、php之类的,其中java spring系列基本上占了大头,而python之流也在奋起直追,但别忘了nodejs也是可以做这个服务的,只是位置有点尴尬,现在就来探究下nodejs…...
QTday4
1:是进度条通过线程自己动起来 mythread.h #ifndef MYTHREAD_H #define MYTHREAD_H #include <QThread>class mythread : public QThread {Q_OBJECT public:mythread(QObject* parent nullptr); protected:virtual void run() override; private: signals:virtual voi…...
服务器时间同步
方法一 [rootbogon hwh-ansible]# cat time-sync.sh #!/bin/bash # NTP 服务器信息 NTP_SERVER"192.168.42.12" PASSWORD"123456" # 多个 IP 地址 HOSTS("192.168.42.8" "192.168.42.9" "192.168.42.10" "192.168.42…...
蓝桥杯备赛日记【day1】(c++赛道)
一、裁纸刀问题(2022、规律、思维、省赛) 解法思路: 参考题目给出的例子发现。不管要裁剪多少次。最外围的四次是固定的。然后通过观察发现,我们的行的裁剪次数为(m-1) 次,而每行都需要裁剪列数…...
DeepSeek大模型 —— 全维度技术解析
DeepSeek大模型 —— 全维度技术解析 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家!点我试试!! 文章目录 DeepSeek大模型 —— 全维度技术解析一、模型架构全景解析1.1…...
嵌入式开发:傅里叶变换(5):基于STM32,实现CMSIS中的DSP库
目录 步骤 1:准备工作 步骤 2:创建 Keil 项目,并配置工程 步骤 3:在MDK工程上添加 CMSIS-DSP 库 步骤 5:编写代码 步骤 6:配置时钟和优化 步骤 7:调试与验证 步骤 8:优化和调…...
Ubuntu 24.04 配置ODBC连接ORACLE 11G数据库
1. 安装必要工具和驱动 1.1 安装unixODBC和依赖库 # apt update # apt install unixodbc unixodbc-dev libaio1 执行失败,报错 libaio1包找不到,先跳过,安装其他两个。 # apt install unixodbc unixodbc-dev 安装成功 1.2 下载Oracle…...
upload-labs靶场 1-21通关
目录 1.Pass-01 前端绕过 分析 解题 2.Pass-02 服务器端检测--修改IMME 分析 解题 3.Pass-03 黑名单绕过 分析 解题 4.Pass-04 .htaccess绕过 分析 解题 5.Pass-05 . .绕过和.user.ini绕过 分析 解题 6.Pass-06 大小写绕过 分析 解题 7.Pass-07 空格绕过 分…...
Docker新手入门(持续更新中)
一、定义 快速构建、运行、管理应用的工具。 Docker可以帮助我们下载应用镜像,创建并运行镜像的容器,从而快速部署应用。 所谓镜像,就是将应用所需的函数库、依赖、配置等应用一起打包得到的。 所谓容器,为每个镜像的应用进程创建…...
c语言笔记 指针篇(上)
1.指针 在计算的存储器中有很多的存储单元,我们的操作系统把这些存储单元以字节为单位进行编号,也就是每个存储单元(字节),都有编码。这些编码在我们内存中就称为地址。一个字节有八位,位是存储信息的最小单…...
要查看 SQLite 数据库中的所有表,可以通过查询 SQLite 的系统表 sqlite_master
要查看 SQLite 数据库中的所有表,可以查询 SQLite 的系统表 sqlite_master。 每个 SQLite 数据库都包含一个名为 sqlite_master 的系统表。该表定义了数据库的模式,存储了数据库中所有表、索引、视图和触发器等对象的信息。 通过查询 sqlite_master&am…...
C#释放内存空间的方法
目录 前言释放 C# 对象内存的六种方法1、手动释放内存空间2、使用 Using 语句3、使用 垃圾回收器4、GC.Collect() 方法5、GC.WaitForPendingFinalizers() 方法6、WeakReference 类 注意 前言 当不再需要对象时释放内存空间对于防止内存泄漏和提高应用程序性能至关重要。C# 提供…...
mapbox基础,使用点类型geojson加载symbol符号图层,用于标注文字
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️symbol符号图层样式二、🍀使用点类型…...
Java数组详解/从JVM理解数组/数组反转/随机排名/数组在计算机如何存储
本文详细讲解了数组的定义、数组的访问方法、数组的遍历、静态数组和动态数组、以及数组中的自动类型转换、引用类型指向数组的地址、以及从JVM理解数组、空指针异常、数组反转、随机排名的案例。 数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下标索…...
网络安全wireshark题目
一、填空题: 网络安全的目标是在计算机网络的信息传输、存储与处理的整个过程中,提高 物理逻辑上 的防护、监控、反应恢复和 对抗 的能力。SSL协议是在网络传输过程中,提供通信双方网络信息 保密性 和 可靠性 。TCP/IP网络安全管理…...
TomcatServlet
https://www.bilibili.com/video/BV1UN411x7xe tomcat tomcat 架构图,与 jre,应用程序之前的关系 安装使用 tomcat 10 开始,api 从 javax.* 转为使用 jakarta.*,需要至少使用 jdk 11 cmd 中默认 gbk 编码,解决控制…...
Seurat - Guided Clustering Tutorial官方文档学习及复现
由于本人没有使用过Seurat4.0,而是直接使用的最新版。所以本文都是基于Seurat5.2.0(截止2025/3/6)来进行撰写。 参考的官方教程来进行学习(上图中的 Guided tutorial-2.700 PBMCs),肯定没有官方文档那么全面…...
Python数据分析面试题及参考答案
目录 处理 DataFrame 中多列缺失值的 5 种方法 批量替换指定列中的异常值为中位数 使用正则表达式清洗电话号码格式 合并两个存在部分重叠列的 DataFrame 将非结构化 JSON 日志转换为结构化表格 处理日期列中的多种非标准格式(如 "2023 年 12 月 / 05 日") 识…...
极狐GitLab 正式发布安全版本17.9.1、17.8.4、17.7.6
本分分享极狐GitLab 补丁版本 17.9.1、17.8.4、17.7.6 的详细内容。这几个版本包含重要的缺陷和安全修复代码,我们强烈建议所有私有化部署用户应该立即升级到上述的某一个版本。对于极狐GitLab SaaS,技术团队已经进行了升级,无需用户采取任何…...
【JavaSE-7】方法的使用
1、方法的概念和使用 1.1、什么是方法 方法(method)是程序中最小的执行单元,类似于 C语言中的函数,方法存在的意义: 是能够模块化的组织代码(当代码规模比较复杂的时候).做到代码被重复使用, 一份代码可以在多个位置…...
阿里推出全新推理模型(因果语言模型),仅1/20参数媲美DeepSeek R1
阿里Qwen 团队正式发布了他们最新的研究成果——QwQ-32B大语言模型!这款模型不仅名字萌萌哒(QwQ),实力更是不容小觑!😎 QwQ-32B 已在 Hugging Face 和 ModelScope 开源,采用了 Apache 2.0 开源协议。大家可通过 Qwen C…...
C语言笔记(通讯录)
目录 1.通讯录的架构 2.通讯录的功能 3.实现静态通讯录的功能步骤 3.1.创建通讯录数组 3.2.显示功能菜单 3.3.初始化通讯录 3.4.添加联系人的信息 3.5.显示联系人的信息 3.6.查找某个人的信息 3.7.删除某一个联系人信息 3.8.修改某一联系人的信息 3.9.按名字对联系…...
【Manus资料合集】激活码内测渠道+《Manus Al:Agent应用的ChatGPT时刻》(附资源)
DeepSeek 之后,又一个AI沸腾,冲击的不仅仅是通用大模型。 ——全球首款通用AI Agent的破圈启示录 2025年3月6日凌晨,全球AI圈被一款名为Manus的产品彻底点燃。由Monica团队(隶属中国夜莺科技)推出的“全球首款通用AI…...
Python 面向对象高级编程-定制类
目录 __str__ __iter__ __getitem__ __getattr__ __call__ 小结 看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。 __slots__我们已经知道怎么用了,__len__()方法我们也知道是为了能让class作用于len()…...
安装remixd,在VScode创建hardhat
在终端,以管理员身份,cmd 需要科学上网 npm install -g remix-project/remixd 在vscode插件中,安装solidity插件,是暗灰色那款 1.将nodeJs的版本升级至18以上 2.在vscode打开一个新的文件,在终端输入 npx hardhat 3.…...
开发环境搭建-02.后端环境搭建-熟悉项目结构
一.后端环境搭建...
Linux(Centos 7.6)命令详解:vi
1.命令作用 vi/vim 是Linux 系统内置不可或缺的文本编辑命令,vim 是vi 的加强版本,兼容vi 的所有指令,不仅能编辑文本,而且还具有shell 程序编辑的功能,可以不同颜色的字体来辨别语法的正确性。 2.命令语法 usage: …...
Ubuntu 20.04下配置VSCode以支持Eigen库开发
这里写目录标题 1. 安装Eigen库2. 配置VSCode的C开发环境3. 配置c_cpp_properties.json4. 编写代码并测试5. 配置tasks.json(可选)6. 运行程序总结 在VSCode中配置Eigen库(用于线性代数、矩阵和向量运算的C库)的步骤如下ÿ…...
正点原子[第三期]Arm(iMX6U)Linux移植学习笔记-2.1 uboot简介
前言: 本文是根据哔哩哔哩网站上“Arm(iMX6U)Linux系统移植和根文件系统构键篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。 引用: …...
Android14 OTA差分包升级报kPayloadTimestampError (51)
由于VF 架构, 所以镜像的打包时间可能存在偏差, 如 boot.img 和 客制化的一些镜像打包 可能会在 vendor 侧进行打包。 而 与system 侧进行merge 时,时间戳比较乱,为了解决这个问题,让时间戳进行统一。 使用adb方式验证…...
PPT 技能:巧用 “节” 功能,让演示文稿更有序
在制作PPT时,你是否遇到过这样的情况:幻灯片越来越多,内容越来越杂,找某一页内容时翻得眼花缭乱?尤其是在处理大型PPT文件时,如果没有合理的结构,编辑和调整都会变得非常麻烦。这时候࿰…...
Mysql创建库、表练习
创建库 #创建 create database gc_novels default charsetutf8mb4;#default charsetutf8mb4 指定编码为utf-8#使用 use gc_novels#查看当前所在位置 select database(); 结果: 创建表 #创建 create table heros(-> id int,-> name varchar(50),-&…...
【原创】Ollama Test API For Linux/MacOS/Unix
安装Json解析工具 Linux/Unix sudo apt-get install jq -yMacOS brew install jq -y设置环境变量 export IP"192.168.250.229" export PORT"8080" export MODEL"deepseek-r1:7b"检查Ollama版本 curl http://"$IP":"$PORT&qu…...
盛铂科技 SLMF315频率综合器200MHz至15GHz 国产频综模块
在当今科技飞速发展的时代,射频技术在众多领域发挥着关键作用,从通信、雷达系统到科研实验,对频率综合器的性能要求日益严苛。以下是关于盛铂科技的 SLMF315 超低相位噪声频率综合器的介绍: SLMF315超低相位噪声0.2至15GHz频率综合…...
AI-前端开发webcrumbs.ai/frontend-ai,Fitten Code提高开发效率
1.webcrumbs.ai/frontend-ai Frontend AI by Webcrumbshttps://app.webcrumbs.ai/frontend-ai 可选择某一模版进行生成对应版本代码,减少开发量。 2.Fitten Code Vue AI 编程助手 Vue AI 编程助手 | 菜鸟教程https://www.runoob.com/vue2/fitten-code-vue2.htm…...
DeepSeek开源Day4:DualPipeEPLB技术详解
2 月 24 日,DeepSeek 启动 “开源周”,第四个开源的代码库为 DualPipe 与 EPLB(一下发布了两个)。DualPipe 与 EPLB 依然使用了大量与 Hopper 架构绑定的技术。 DualPipe 是由 DeepSeek-AI 团队开发的一种双向流水线并行通信算法&…...