【爱上C++】万字详解string类1:经典常用接口、操作

hello,今天咱们进入C++中string类的学习,今天这部分主要讲解string类的部分经典常用接口,其他很少使用的接口要学会及时查文档,现查现用。string类的模拟实现和深浅拷贝问题放在后面一篇讲解。


目录

  • 一.string类简介
  • 二.常见构造
  • 三.容量操作
  • 四.访问及遍历操作
    • 1.下标+[ ]
    • 2.at
    • 拓展:try,catch 异常处理
    • 3.迭代器
    • 4.范围for
    • begin+end,rbegin+rend
  • 五.修改、运算操作(重点)
    • 拓展:C风格字符串与string类型字符串
    • 1.字符串连接和追加
    • 2.字符串插入
    • 3.字符串删除
    • 4.字符串替换
    • 5.字符串交换
    • 6.字符串赋值
    • 7.字符串内容访问
    • 8.字符串查找
    • 9.字符串子序列提取
  • 六.非成员函数
    • 本篇文章思维导图

一.string类简介

string类是C++标准库中的一个类,用于处理字符串。它封装了字符数组(通常是char类型)的操作,提供了许多方便的方法来创建、修改、访问和操作字符串。使用string类可以简化字符串的编程工作,并减少常见的错误,如缓冲区溢出.

在使用string类时需要包含头文件#include<string> ,以及using namespace std;注意!不是C语言中是string.h
官方参考文档
string类的文档介绍
上面是string类的文档介绍,在学习C++过程中,我们需要慢慢提高看英文文献,文档的能力。

  1. 字符串是表示字符序列的类
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作
    单字节字符字符串的设计特性。
  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信
    息,请参阅basic_string)。
  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits
    和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个
    类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

总结:

  1. string是表示字符串的字符串类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>
    string;
  4. 不能操作多字节或者变长字符的序列。

二.常见构造

string类提供了多种构造函数,用于创建字符串对象。这些构造函数允许你使用不同的方式初始化字符串,如使用字符数组、其他字符串对象、单个字符、或者一定数量的字符来填充。

注意:string 类重载了流插入和流体取,所以可以直接cin,cout;

string();  //构造一个空字符串

string(const char* s);  //复制s所指的字符序列

string(const char* s, size_t n);  //复制s所指字符序列的前n个字符

string(size_t n, char c);  //生成n个c字符的字符串

string(const string& str);  //生成str的复制品

string(const string& str, size_t pos, size_t len = npos);  
//复制str中从字符位置pos开始并跨越len个字符的部分

string(const string& str, size_t pos); //复制str中从pos位置开始直到末尾的字符

使用示例:

string s1;							//构造空字符串
	string s2("hello world");			//复制hello world到s2
	string s3("hello,world", 3);		//复制hello world的前3个字符
	string s4(s2, 0, 4);				//从s2的0位置开始,复制4个
	string s5(s2, 3);					//从s2的3位置开始,一直复制到末尾
	string s6(s2);						
	string s7 = s2;						//两个都是拷贝构造,复制s2的值
	string s8(s2, 0, 10000);			//第三个参数大于字符串长度,就是一直复制直到末尾
	//注意区别s3和s5;
	string s9(9, 'a');					//填充9个'a'

//拓展:
	string s10 = s2;//拷贝赋值操作,将 s2 的内容复制到 s1 中。
	string s11 = "hello";
	//字符串字面值会隐式转换为 std::string 对象,然后再赋给 s1。这涉及到了转换构造函数和拷贝赋值操作
	string s12 = "x";
	//由于 std::string 类型重载了接受单个字符作为参数的赋值运算符 (operator=),这行代码将一个只包含字符 'x' 的字符串赋给了 s1
//注意:string s12='x';是错的
//在 C++ 中,使用单引号括起来的字符 'x' 表示一个字符字面值(char literal),而不是字符串。因此,将一个字符字面值直接赋给一个 std::string 对象会导致编译错误,因为类型不匹配。要将字符 'x' 转换为一个字符串,可以使用双引号将其括起来,例如:"x"。

npos:在这个示例中,npos 并没有直接出现在代码中,而是作为string类的一个静态成员变量,
表示字符串的末尾位置。它的值是一个特殊的常量 ,通常是一个 大整数值,用来表示字符
串中的无效位置或者末尾位置。在构造函数的默认参数中,len 的默认值就是npos,这意
味着如果不显式地指定长度,则会从起始位置一直复制到字符串的末尾。
总之,npos是string类来表示字符串末尾位置的特殊常量。

三.容量操作

容量操作

☆讲解:
1. size

2. length
size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一
致,一般情况下基本都是用size()。size() 和 length() 的计算不包含 \0。

	string s1("hello,bit");
	cout<<s1.size()<<endl;
	cout<<s1.length()<<endl;
	//结果都是9

3. capacity
返回的是当前字符串对象的存储空间大小.

	string s1("abcdef");
	cout << s1.capacity() << endl;
	 //我的VS下结果是15
  • 结果为什么是15呢???
    在很多实现中,string类采用动态数组作为底层实现,它会为字符串预留一些额外的存储空间,以减少频繁分配内存和释放内存的开销。值得注意的是,这个内部缓冲区的大小可以根据不同的标准库实现而异,因此并不是所有 C++ 标准库都会返回 15。此外,当你向字符串添加更多字符时,标准库会动态地重新分配内存,以适应更多的字符,所以 capacity() 的返回值可能会在运行时改变。
    4. empty
    empty()函数会返回一个布尔值,如果字符串为空,则返回true;如果字符串非空,则返回false。
    5. clear
    清空有效字符。clear()只是将string中有效字符清空,不改变底层空间(capacity)大小。
	string s("hello, bit!!!");
	cout << s.size() << endl;
	cout << s.length() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
	
	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();
	cout << s.size() << endl;
	cout << s.capacity() << endl;
//结果如下
//13
//13
//15
//hello, bit!!!
//0
//15

6. reserve

  • reserve 函数用于预分配足够的内存以容纳指定数量的字符。改变容量,但不改变字符串的内容和长度
  • 确定多少空间,就提前开好。
  • 如果预留的空间大小大于当前字符串的容量,则会增加字符串的容量以满足预留的大小。
  • 如果预留的空间大小小于或等于当前字符串的容量,则不会改变容量。
  • 预留内存空间可以提高字符串添加字符时的性能,避免频繁的内存重新分配。
  • 不能缩容

reserve用法展示

7. resize

  • resize()函数用于改变字符串的实际长度,可以增加或减少字符串的长度。它接受一个整数参数,表示新的字符串长度。既影响容量也影响数据
  • 改变字符串长度
    最基本的用法是改变字符串的长度。通过调用resize()函数,可以将字符串的长度设置为指定的大小。如果增加字符串的长度,新增的部分会用空字符(’\0’)填充
  • 截断字符串:(长度比当前字符串长度小)
    可以利用resize()函数截断字符串,即将字符串的长度缩短到指定大小。
  • 填充字符串:
    可以在改变字符串长度的同时,用指定的字符填充剩余的部分。
  • 保留原始内容:
    可以通过设置第二个参数为0来保留字符串的原始内容,而不进行填充。
    在这里插入图片描述

8. 拓展

  • max_size():返回容器可以容纳的最大元素数量。但在实践中没什么参考和使用价值
  • string类内存分配机制:

基础概念
std::string 是一个序列容器,它存储了字符序列,并提供了一系列的成员函数来操作这些字符。当 std::string >中的字符数量超过当前分配的内存大小时,就需要进行扩容。
扩容过程

  1. 初始状态:一个新创建的 std::string 对象通常有一个较小的初始容量,足以容纳一些字符而无需立即分配内存。
  2. 增长:当向 std::string 添加字符,使其大小超过当前容量时,会发生扩容。扩容通常涉及以下几个步骤:
    内存分配:分配一块新的、更大的内存块。这块内存的大小通常是当前容量的两倍(或者根据实现可能有不同的增长策略),以便于提供足够的空间来应对未来的增长,减少频繁分配内存的开销。
    数据复制:将旧内存块中的数据复制到新的内存块中。
    旧内存释放:释放原来较小的内存块。
  3. reserve 方法:虽然 std::string 会自动进行扩容,但可以通过调用 reserve 方法来手动预留更大的容量。这可以避免在添加大量数据时进行多次内存分配。

四.访问及遍历操作

1.下标+[ ]

operator[ ]函数允许直接通过索引访问字符串中的字符。并且该重载使用的是引用返回,所以我们可以通过[ ]+下标修改对应位置的元素。
在这里插入图片描述

char& operator[] (size_t pos);------------------------可读可写
const char& operator[] (size_t pos) const;--------只读

string str = "Hello";
str[0] = 'h'; // 可以访问、修改字符串中的字符

const string str = "Hello";
char ch = str[0]; // 可以读取字符串中的字符,但无法修改

2.at

at 是一个成员函数,同样用于获取或设置字符串中特定位置的字符。与 operator[] 不同,at 检查索引是否有效,如果索引无效,它将抛出一个 std::out_of_range 异常。
特点:

  • 与 operator[] 不同,at()函数会检查索引是否越界,如果索引超出字符串范围,会抛出 std::out_of_range 异常。
  • at 允许读写访问。
  • 由于提供了范围检查,因此更安全,但也可能会带来一定的性能损失

int main() {
    using namespace std;
    string s = "Hello, World!";
    try {
        // 访问并打印指定位置的字符
        cout << "Character at index "  << s.at(3) << endl;
    } catch (const out_of_range& e) {
        cout << "Error: " << e.what() << endl;
    }
    return 0;
}

拓展:try,catch 异常处理

这里先简单介绍一下C++中的异常,看不懂没关系,先了解皮毛,深入内容后续会进行讲解。

C++ 中的异常处理主要依赖于 try、catch 和 throw 语句。下面将详细介绍这些语句的用法和格式,以及它们在异常处理中的作用。

  1. throw 语句
    throw 语句用于抛出一个异常。当程序中出现某些特殊情况(如无效输入、资源不可用等)时,可以使用 throw 语句抛出一个异常。这个异常可以是内置类型(如 int、char 等),也可以是自定义的类型。
throw "Invalid input";  // 抛出一个字符串常量异常   
throw std::runtime_error("Invalid input");  // 抛出一个标准库中的异常类型
  1. try 和 catch 语句
    try 和 catch 语句一起用于捕获和处理异常。try 块中包含可能抛出异常的代码,而 catch 块则负责捕获并处理这些异常。
try {  
    // 可能抛出异常的代码块  
} catch (异常类型1 参数名) {  
    // 处理异常类型1的代码  
} catch (异常类型2 参数名) {  
    // 处理异常类型2的代码  
} catch (...) {  
    // 处理其他所有未被前面catch块捕获的异常  
}

示例:

#include <iostream>  
#include <stdexcept>  // 包含标准异常类  
  
int main() {  
    try {  
        throw std::runtime_error("Something went wrong");  // 抛出一个异常  
    } catch (const std::runtime_error& e) {  // 捕获 runtime_error 类型的异常  
        std::cout << "Caught an exception: " << e.what() << std::endl;  
    } catch (...) {  // 捕获其他所有类型的异常  
        std::cout << "Caught an unknown exception" << std::endl;  
    }  
    return 0;  
}

在这个示例中,try 块中抛出了一个 std::runtime_error 类型的异常。这个异常被第一个 catch 块捕获,并输出异常信息。如果 try 块中抛出了其他类型的异常,它将被第二个 catch 块捕获。

  1. try / catch 的使用注意事项:
    异常类型匹配:当异常被抛出时,会按照 catch 块的顺序进行匹配。因此,更具体的异常类型应该放在前面,而更一般的异常类型(如 …)应该放在后面。
    异常传播:如果在 catch 块中没有处理某种类型的异常,或者使用了 throw; 语句而没有捕获该异常,该异常会继续传播并被外层的 catch 块所处理(如果有的话)。
    资源清理:在 try / catch 块中,通常还需要注意资源的清理工作,确保在异常发生时能够正确释放已分配的资源。这通常通过使用智能指针、RAII(Resource Acquisition Is Initialization)等技术来实现。
    异常安全:在设计异常安全的代码时,需要考虑如何在异常发生时保持数据的一致性和完整性。这通常涉及到使用事务、日志记录、回滚等操作。
    性能考虑:虽然异常处理为程序提供了强大的错误处理能力,但频繁地抛出和捕获异常可能会对性能产生影响。因此,在设计系统时应权衡异常处理的必要性和性能之间的关系。

3.迭代器

这里先简单介绍一下迭代器,然后再讲解如何使用。看不懂不必担心,后续会详细介绍
迭代器简单介绍:

在C++中,迭代器(Iterator)是一种抽象概念,用于提供一种方法来按顺序访问容器中的元素。迭代器允许算法和函数以一种通用的方式遍历各种类型的容器,而不需要关心容器的具体实现细节。迭代器在C++ STL(Standard Template Library)中扮演着非常重要的角色。
迭代器的分类:

  1. 输入迭代器(Input Iterator):允许你读取容器中的每个元素一次。
  2. 输出迭代器(Output Iterator):允许你向容器写入每个元素一次。
  3. 前向迭代器(Forward Iterator):结合了输入和输出迭代器的特性,并允许多次读取。
  4. 双向迭代器(Bidirectional Iterator):前向迭代器加上能够向前和向后移动的能力。
  5. 随机访问迭代器(Random Access Iterator):双向迭代器加上能够随机访问容器中任意位置元素的能力,还可以进行比较和数值运算。
    迭代器的基本操作
  • 解引用(Dereference):通过迭代器访问元素的值。
  • 递增(Increment):移动到容器的下一个元素。
  • 递减(Decrement):移动到容器的上一个元素(双向迭代器)。
  • 相等和不等比较:比较两个迭代器是否相等或不等。

示例1:使用迭代器遍历 vector

#include <iostream>
#include <vector>
int main() {
    using namespace std;
    vector<int> vec = {1, 2, 3, 4, 5};
    // 使用迭代器遍历vector
    for (vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        cout << *it << " "; // 解引用迭代器并打印元素
    }
    cout << endl;
    return 0;
}

示例2:使用迭代器修改 list 中的元素

#include <iostream>
#include <list>

int main() {
    using namespace std;

    list<int> lst = {1, 2, 3, 4, 5};

    // 使用迭代器修改list中的元素
    for (list<int>::iterator it = lst.begin(); it != lst.end(); ++it) {
        *it += 10; // 解引用迭代器并增加10
    }

    // 打印修改后的list
    for (list<int>::iterator it = lst.begin(); it != lst.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;

    return 0;
}

示例3:使用 advance 函数移动迭代器(随机访问迭代器)

	vector<int> vec = {1, 2, 3, 4, 5};
    vector<int>::iterator it = vec.begin();
    // 使用std::advance移动迭代器
    advance(it, 3); // 将迭代器移动到第四个元素
    cout << "The fourth element is: " << *it << endl;

☆正文 ——用迭代器遍历:

//使用迭代器遍历 std::string

//std::string 提供了 begin() 和 end() 成员函数,分别返回指向字符串第一个字符和末尾(字符串最后一个字符之后的位置)的迭代器。
	string str = "Hello, World!";
	string::iterator it = s.begin();
	while (it != s.end())
	{
		*it+=1;
		cout << *it << endl;
		++it;
	}
	
//使用const_iterator遍历字符串(推荐做法)
    //for (string::const_iterator it = str.begin(); it != str.end(); ++it)
    for (auto  it = str.begin(); it != str.end(); ++it) {
    {    
        cout << *it;
    }

//使用迭代器遍历 std::vector
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	vector<int>::iterator vit = v.begin();//迭代器都是左闭右开!
	while (vit != v.end())
	{
		cout << *vit << " ";
		++vit;
	}
	cout << endl;
//使用迭代器遍历 std::list
	list<double> lt;
	lt.push_back(1.2);
	lt.push_back(1.3);
	lt.push_back(1.4);
	lt.push_back(1.5);
	list<double>::iterator lit = lt.begin();
	while (lit != lt.end())
	{
		cout << *lit << " ";
		lit++;
	}
	cout << endl;

//迭代器的一些其他用法:

	//使用reverse函数逆置string
	reverse(s1.begin(), s1.end()); // 从begin到end逆置字符串
	cout << s1 << endl; // 打印逆置后的字符串

	//逆置vector
	reverse(v.begin(), v.end()); // 逆置vector中的元素
	vit = v.begin(); // 重置迭代器到vector的开始
	while (vit != v.end()) // 再次遍历vector
	{
   	 	cout << *vit << " "; // 打印逆置后的元素
   	 	++vit; // 移动迭代器
	}
	cout << endl; 

4.范围for

for 循环的范围版本是 C++11 引入的一种方便的循环结构,它允许直接遍历容器中的元素,并且在处理字符串时也非常实用。

范围 for 循环的基本语法

for (declaration : expression) {
    // 循环体
}

declaration 是循环变量的类型和名称,expression 是一个容器,其元素会被遍历
☆综合使用:

#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "hello";
    // 1. 遍历访问字符串中的字符
    for (char c : str) {
        cout << c << " "; // 输出当前字符
    }
  

    // 2. 修改字符串中的字符
    for (char &c : str) { // 引用类型,可以直接修改字符串中的字符
        c = toupper(c); // 将字符转换为大写形式
    }
    
    // 3. 遍历访问字符串中的索引和字符
  	size_t index = 0;
    for (auto c : str) {
        cout << "Index: " << index++ << ", Character: " << c << endl;
    }

    // 4. 遍历访问字符串中的字符(只读)
    const string const_str = "world";
    for (char c : const_str) { // 注意字符串为 const,因此迭代器也是 const 的
        cout << c << " "; // 输出当前字符
    }

    // 5. 遍历访问字符串中的字符(逆序)
    string reversed_str = "dlrow";
    for (char c : string(reversed_str.rbegin(), reversed_str.rend())) { // 使用逆向迭代器构造逆序字符串
        cout << c << " "; // 输出当前字符
    }
    return 0;
}

begin+end,rbegin+rend

  • begin() 返回指向字符串首字符的迭代器。这个位置是包含的(左闭)。
  • end() 返回指向字符串尾后字符的迭代器。这个位置是不包含的(右开)。
  • rbegin() 返回指向字符串最后一个字符的反向迭代器。这个位置是包含的(左闭)
  • rend() 返回指向字符串首前字符的反向迭代器。这个位置是不包含的(右开)。
    string s = "Hello, World!";  
    // 使用begin()和end()遍历字符串  
    for (auto it = s.begin(); it != s.end(); ++it) {  
        cout << *it; // 输出字符串中的每个字符  
    }  

	// 使用rbegin()和rend()逆序遍历字符串  
    for (auto rit = s.rbegin(); rit != s.rend(); ++rit) {  
        cout << *rit; // 逆序输出字符串中的每个字符  
    }  

在这里插入图片描述
给定字符“Hello, World!”,以上四个迭代器分别指向什么?
在这里插入图片描述
begin():指向字符串的第一个字符 ‘H’。
end():指向字符串最后一个字符之后的位置(即字符串结尾的’\0’之后的位置,但并不是指向’\0’,因为std::string内部并不以’\0’结尾,这里的end()是一个逻辑上的尾后位置)。
rbegin():指向字符串的最后一个字符(不包括字符串结尾的’\0’),在这个例子中是 ‘!’。
rend():指向字符串第一个字符之前的位置,也就是反向迭代器的“尾后”位置。

五.修改、运算操作(重点)

拓展:C风格字符串与string类型字符串

  1. C风格字符串,一般参数都是const char *s。
  • 以null终止:字符串以空字符 \0 结尾,这个字符不是字符串的一部分,但用来标识字符串的结束
  • 字符数组:C风格字符串实际上是一个字符数组,数组中的最后一个元素是空字符。
  • 不安全:由于不包含长度信息,C风格字符串容易受到缓冲区溢出等安全问题的影响。
  • 操作限制:C风格字符串的操作通常需要使用C语言标准库中的字符串处理函数,如 strlen(), strcpy(), strcat() 等。
	const char* s1 = "Hello,world!";
	cout << s1 << endl;
  1. C++ string 类(const string& str)
  • 对象类型:std::string 是一个完整的类类型,封装了字符串数据和操作- 字符串所需的函数。
  • 自动内存管理:std::string 自动管理内存,避免了缓冲区溢出的风险。
  • 内置函数:std::string 提供了丰富的成员函数,如 append(), substr(), find(), replace() 等,用于执行各种字符串操作。
  • 类型安全:std::string 是类型安全的,它不允许修改字符串的内部表示,只能通过成员函数进行操作。
  • 支持泛型编程:std::string 可以与C++的模板算法一起使用,如 std::sort(), std::for_each() 等。
	string s1="Date";
	cout<<s1<<endl;

1.字符串连接和追加

  • append: 将另一个字符串或字符序列添加到当前字符串的末尾。
	string s1;
	string s2("hello");
	const char * s3="Date";
	s1.append(s2);//将字符串Date添加到s1的末尾
	s1.append(s3)//将字符串hello添加到s1的末尾
	s1.append(s2,0,4);//追加s2的从0开始的4个字符到s2
	s1.append(s2,0);//追加s2的从0开始到末尾的所有字符到s2
	s1.append(s3,4);//仅追加s3的前4个字符到s1末尾
	s1.append(10,'x');//追加十个'x';
  • operator+=: 将一个字符串或字符序列添加到当前字符串的末尾。
	string s1="Date";
	str+="Hello";// 将字符串"Append"添加到s1的末尾
	str+='!';//在s1末尾追加单个字符'!'
	string s2 = "World!";
	s1+=s2;// 将字符数组s2追加到s1末尾

2.字符串插入

  • insert: 在字符串的指定位置插入字符、字符串或字符序列。
	string s="Hello";
	s.insert(2.‘ ’);
	s.insert(6,"World");//在位置6插入字符串“World”
	s.insert(12,3,'!');//在12的位置插入三个!
	s.insert(s1.begin(),'y');

3.字符串删除

  • erase: 删除字符串中指定位置的字符或指定范围内的字符序列。
	string str("hello world");
	s1.erase(5,4)//删除位置5开始的四个字符
	s1.erase(5);//删除位置5开始一直到末尾的字符。

  • push_back: 在字符串末尾追加单个字符(虽然 push_back 通常与容器相关,但在这里它相当于在字符串末尾追加一个字符)。
	string s;
// 在字符串末尾追加字符'H'
	s.push_back('H');

4.字符串替换

  • replace: 用另一个字符串或字符序列替换字符串中的一部分。
    replace效率很低,能不用就不用。
	string str = "Hello, world!";
  	str.replace(7, 5, "everyone");
  // 在字符串 str中位置 7(从0开始计数,对应于空格后面的 'w')开始的 5 个字符("world")替换为 "everyone",

5.字符串交换

  • swap: 交换两个字符串的内容。

6.字符串赋值

  • assign: 替换当前字符串的内容为另一个字符串、子字符串、字符数组或多个重复字符。

7.字符串内容访问

  • c_str: 返回一个指向以空字符终止的字符数组(C风格字符串)的指针。

FILE *fopen (const char *filename , const char *mode);

当我们试图使用C++的std::string来调用fopen时,会遇到一个问题:std::string不是一个以空字符结尾的字符数组,它是一个类,拥有自己的内部表示和内存管理。因此,我们不能直接将std::string对象传递给fopen函数。

为了解决这个问题,C++的std::string类提供了一个成员函数c_str(),它的作用是返回一个指向以空字符结尾的字符数组的指针,这个数组的内容与std::string对象相同。这样,我们就可以使用这个指针来调用期望C风格字符串的C语言库函数了。

int main()
{
	string filename("test1.cpp");
	//FILE* fout = fopen(filename, "r");
	FILE* fout = fopen(filename.c_str(), "r");
	char ch = fgetc(fout);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fout);
	}
	return 0;
}

8.字符串查找

  • find + npos: 从字符串的特定位置开始搜索给定字符,直到找到或到达字符串末尾,返回字符位置,未找到时返回 std::string::npos。
  • rfind: 从字符串末尾开始向前搜索给定字符,直到找到或到达字符串开始位置,返回字符位置,未找到时返回 std::string::npos。
	string str = "Hello, world! Hello, C++!";
	size_t pos=s.find('l');//查找字符'l'的第一个出现位置
    size_t pos = s.find("World", 6); // 从位置6开始查找"World"
    
// 使用 find 函数查找子字符串的位置
    size_t found = str.find("Hello");
    if (found != string::npos) {
        cout << found << endl;
    }
    
    std::string s = "Hello, World!";
    const char* cstr = "llo Worl";
    size_t pos = s.find(cstr, 0, 5); 
//是从 s 的位置 0 开始,查找 cstr 的前5个字符是否在 s 中出现。如果找到,它返回匹配的起始位置
    
    
    size_t pos = s.rfind('l'); // 查找字符'l'的最后一个出现位置
    // 使用 rfind 函数查找子字符串最后一次出现的位置
    size_t rfound = str.rfind("Hello");
    if (rfound != string::npos) {
        cout<< rfound << endl;
    }

  • find_first_of: 查找当前字符串中第一次出现指定字符集中的任何字符的位置,返回该位置,未找到时返回 std::string::npos。
  • find_first_not_of: 查找当前字符串中第一次出现不在指定字符集中的任何字符的位置,返回该位置,未找到时返回 std::string::npos。
  • find_last_of: 查找当前字符串中最后一次出现指定字符集中的任何字符的位置,返回该位置,未找到时返回 std::string::npos。
  • find_last_not_of: 查找当前字符串中最后一次出现不在指定字符集中的任何字符的位置,返回该位置,未找到时返回 std::string::npos。
    string s = "Hello, World! This is a test string with multiple words.";
    // 查找第一个不在 "o" 中的字符
    size_t pos1 = s.find_first_not_of("o");
    cout << "First non-'o' character at: " << pos1 << endl;
//0
    // 查找第一个在 "HW " 中的字符
    size_t pos2 = s.find_first_of("HW ");
    cout << "First character in 'HW ' at: " << pos2 << endl;
//0
    // 查找最后一个在 "l" 中的字符
    size_t pos3 = s.find_last_of("l");
    cout << "Last 'l' character at: " << pos3 << endl;
//49
    // 查找最后一个不在 "t " 中的字符
    size_t pos4 = s.find_last_not_of("t ");
    cout << "Last non-'t ' or non-space character at: " << pos4 << endl;
//48,字符d

9.字符串子序列提取

  • substr: 返回字符串中从指定位置开始、指定长度的子字符串

string substr(size_t pos = 0, size_t len = npos) const

    string str = "Hello, world!";
    // 使用 substr 函数提取子字符串
    string sub1 = str.substr(7); // 从位置 7 开始到字符串末尾的所有字符
    string sub2 = str.substr(0, 5); // 从位置 0 开始,长度为 5 的子字符串
 
}



六.非成员函数

非成员函数

  1. operator+

string operator+(const string& lhs, const string& rhs);
缺点:每次连接都会创建一个新的字符串对象,效率较低,尤其是当连接次数较多时会导致内存分配和释放的开销。

	string s1="Hello, ";
	string s2="World!";
	string result=s1+s2;
  1. operator>>

  2. operator<<
    我们可以直接cin>>,cout<< string类字符串

  3. getline
    我们都知道,cin 和 scanf 在默认情况下将空格和换行符视为分隔符。这意味着它们在读取输入时会以空格或换行符为界限将输入分割成不同的部分。当我想输入一行字符串"ABCD E",作为一个整体的时候,系统自动把他们分为ABCD和E两个部分,此时getline就能派上用场了。
    以下面这道题为例子
    在这里插入图片描述
    在这里插入图片描述
    应该输出1为什么输出4呢?因为cin>>默认以空格为分隔符,因此它会将字符串中的第一个单词 “ABCD” 读取到变量 str 中,而将第二个单词 “T” 忽略。我们把cin>>str换成getline(cin, str);就可以了

int main() {
    string str;
    getline(cin, str);
    size_t pos = str.rfind(' ');
    if (pos != string::npos)
    {
        cout << str.size() - pos - 1 << endl;//注意这里的位置
    }
    else {//找不到的时候(只有一串没有空格的字符串)
        cout << str.size() << endl;
    }
}
//cin 和scanf默认用换行和空格进行分割

  1. relational operators
    在C++中,std::string 类重载了关系运算符,允许你比较两个字符串。这些运算符包括 ==(等于)、!=(不等于)、<(小于)、<=(小于等于)、>(大于)和 >=(大于等于)

本篇文章思维导图

C++ std::string 类详解
|
├── 一、string类简介
│ ├── 定义:C++标准库中的类,用于处理字符串
│ ├── 封装:字符数组的操作
│ ├── 头文件:#include
│ ├── 命名空间:using namespace std;
│ └── 底层实现:basic_string<char, char_traits, allocator>模板类的实例化
|
├── 二、常见构造
│ ├── 构造函数:多种方式初始化字符串对象
│ ├── 示例:使用不同构造函数创建字符串
│ └── npos:表示字符串末尾位置的特殊常量
|
├── 三、容量操作
│ ├── size() / length():返回字符串长度
│ ├── capacity():返回字符串对象的存储空间大小
│ ├── empty():检查字符串是否为空
│ ├── clear():清空字符串
│ ├── reserve():预分配内存
│ └── resize():改变字符串长度
|
├── 四、访问及遍历操作
│ ├── 下标 []:直接访问字符串中的字符
│ ├── at():安全访问字符串中的字符
│ ├── 迭代器:begin() / end() / rbegin() / rend()
│ └── 范围for:方便遍历字符串
|
├── 五、修改、运算操作
│ ├── 字符串连接和追加:append() / operator+=
│ ├── 字符串插入:insert()
│ ├── 字符串删除:erase()
│ ├── 字符串替换:replace()
│ ├── 字符串交换:swap()
│ ├── 字符串赋值:assign()
│ ├── 字符串内容访问:c_str()
│ ├── 字符串查找:find() / rfind() / find_first_of() 等
│ └── 字符串子序列提取:substr()
|
└── 六、非成员函数
├── operator+:连接两个字符串
├── operator>> / operator<<:输入输出运算符重载
├── getline:从输入流中读取一行字符串
└── relational operators:比较字符串

在这里插入图片描述

由于作者水平有限,本文有错误和不准确之处在所难免,
本人也很想知道这些错误,恳望读者批评指正!欢迎交流学习!
  • 139
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 130
    评论
评论 130
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值