C++ STL篇(二) —— string精讲

  书接上回【C++ STL篇(一)】耗时3天整理!C++ STL string万字剖析(上篇)新手也能看懂!,本篇文章讲继续讲解string常用的接口,万字干货!绝不白读! ദ്ദി˶ー̀֊ー́ )✧

文章目录


1. 元素访问接口

1.1 operator[] —— 下标访问(无边界检查)

在这里插入图片描述
功能: 返回位于pos 处的字符的引用。可以通过这个引用读取或修改该字符

参数: pos 为字符在字符串中的位置索引,从 0 开始计数。

敲黑板:

  • 不进行边界检查:如果 pos 超出了字符串的有效范围(pos >= size()),行为是未定义的(通常会导致程序崩溃或读取到乱码)。
  • 返回的是引用,因此可以放在赋值号左侧进行修改。
  • const 重载版本,用于只读访问 const string 对象。

看代码:

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

int main() 
{
    string s = "Hello";

    // 读取字符
    cout << s[0] << endl;  // 'H'
    cout << s[4] << endl;  // 'o'

    // 修改字符
    s[0] = 'h';
    s[4] = '!';
    cout << s << endl;     // "hell!"

    // 危险操作:越界访问(未定义行为)
    // cout << s[10] << endl;  // 可能崩溃或输出垃圾值

    // const 对象只能用 const 版本
    const string cs = "World";
    cout << cs[0] << endl;  // OK,只读
    // cs[0] = 'w';          // 错误!不能修改 const 对象

	string s1("hello world");
	//遍历字符串 下标+[]
	for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << " ";
	}
	cout << endl;

    return 0;
}

1.2 at() —— 带边界检查的安全访问

在这里插入图片描述

功能:operator[] 相同,返回索引 pos 处字符的引用。但增加了边界检查。

  • 如果 pos >= size(),会抛出 std::out_of_range 异常。
  • 相比于 operator[]at() 有轻微的运行时开销(检查边界),但换来了更高的安全性。
#include <iostream>
#include <string>
using namespace std;

int main() 
{
    string s = "Hello";

    // 正常访问
    cout << s.at(1) << endl;  // 'e'
    s.at(1) = 'a';
    cout << s << endl;        // "Hallo"

    // 越界访问会抛出异常
    try {
        cout << s.at(10) << endl;
    } catch (const out_of_range& e) {
        cerr << "越界访问!" << e.what() << endl;
    }

    return 0;
}

输出:
在这里插入图片描述


1.3 front() —— 访问第一个字符

在这里插入图片描述

功能: 返回字符串第一个字符的引用。

  • 字符串不能为空(!empty())。对空字符串调用 front() 是未定义行为。
  • s.front() 等价于 s[0]s.at(0)(但无边界检查)。
#include <iostream>
#include <string>
using namespace std;

int main() 
{
    string s = "Hello";

    cout << "第一个字符:" << s.front() << endl;  // 'H'

    // 修改第一个字符
    s.front() = 'Y';
    cout << s << endl;  // "Yello"

    // 空字符串调用 front() 是危险的
    string emptyS;
    // cout << emptyS.front() << endl;  // 未定义行为,可能崩溃

    return 0;
}

1.4 back() —— 访问最后一个字符

在这里插入图片描述

功能: 返回字符串最后一个字符的引用。

  • 字符串不能为空。对空字符串调用 back() 是未定义行为。
  • s.back() 等价于 s[s.size() - 1]s.at(s.size() - 1)(但无边界检查)。
#include <iostream>
#include <string>
using namespace std;

int main() 
{
    string s = "Hello";

    cout << "最后一个字符:" << s.back() << endl;  // 'o'

    // 修改最后一个字符
    s.back() = 'p';
    cout << s << endl;  // "Hellp"
    
    return 0;
}

1.5 只读访问与修改

所有这些接口都提供了两种重载:

  • 非常量版本:返回 char&,可读可写。
  • 常量版本:返回 const char&,仅可读。
  • 对于 const string 对象,只能调用常量版本,从而保证了字符串内容的不可变性。

2. 修改相关的接口

2.1 push_back() —— 在末尾追加单个字符

在这里插入图片描述

功能: 将字符 c 添加到字符串的末尾,使 size() 增加 1。

  • 如果当前 capacity() 足够,则直接在末尾构造字符,时间复杂度 O(1)。
  • 如果容量不足,则会先重新分配更大的内存,再追加字符。
string s("hello world");
s.push_back(' ');
s.push_back('x');
cout << s << endl;  // 输出 "hello world x"

2.2 append() —— 追加字符串或子串

append()string 中重载最丰富的成员函数之一,用于在末尾追加各种形式的内容。
在这里插入图片描述

说明:

  • string& append(const string& str) 追加整个 str
  • string& append(const string& str, size_t subpos, size_t sublen) 追加 str 的子串
  • string& append(const char* s) 追加 C 风格字符串
  • string& append(const char* s, size_t n) 追加 C 字符串的前 n 个字符
  • string& append(size_t n, char c) 追加 n 个字符 c
  • string& append(InputIterator first, InputIterator last) 追加迭代器范围内的字符
string s("hello");
s.append(" world");          // s = "hello world"
s.append(3, '!');            // s = "hello world!!!"
string str = " C++";
s.append(str, 1, 3);      // 追加 str 中从下标1开始的3个字符 -> " C++"
cout << s << endl;           // "hello world!!! C++"

2.3 operator+= —— 追加的简洁写法

在这里插入图片描述

功能: 将右侧的内容追加到当前字符串末尾,并返回 *this 的引用。

特点:

  • 语法简洁,与基本类型的 += 用法一致。
  • 支持链式调用:s += "a" += "b";(注意结合顺序)。
string s = "hello";
s += ' ';           // 追加单个字符
s += "world";       // 追加 C 字符串
string str = "!";
s += str;        // 追加 string 对象
cout << s << endl;  // "hello world!"

日常开发中 operator+= 的使用频率最高,因为它最符合直觉。•̀֊•́


2.4 assign() —— 重新分配内容

assign用于替换整个字符串的内容。
在这里插入图片描述

说明:

  • string& assign(const string& str) 将当前字符串的内容替换为另一个字符串 str 的完整内容,并返回对当前字符串的引用
  • string& assign(const string& str, size_t subpos, size_t sublen) 用另一个字符串的指定子串替换当前字符串
  • string& assign(const char* s) 用整个C风格字符串替换当前字符串
  • string& assign(const char* s, size_t n)用C风格字符串的前n个字符替换当前字符串。
  • string& assign(size_t n, char c) 用n个相同的字符c替换当前字符串。
  • string& assign(InputIterator first, InputIterator last) 用迭代器范围内的字符序列替换当前字符串。
string s;
s.assign("Hello World", 5);  // s = "Hello"
s.assign(10, '*');           // s = "**********"

2.5 insert() —— 在指定位置插入内容

在这里插入图片描述
功能: 在指定位置 pos(或迭代器 p 指向的位置)之前插入字符或字符串。

敲黑板:

  • 插入操作会导致插入点之后的所有字符向后移动,以腾出空间。
  • 如果插入导致容量不足,还会触发内存重新分配和整体拷贝。
  • 在字符串开头插入是最低效的操作,因为需要移动整个字符串!
  • 一定要谨慎使用!! o((⊙﹏⊙))o
string s("hello world");
s.insert(0, "say: ");          // 在开头插入
cout << s << endl;             // "say: hello world"

s.insert(6, "222222 ");        // 在索引6处插入
cout << s << endl;             // "say: 222222 hello world"

// 插入单个字符的多种方式
s.insert(0, 1, 'x');           // 插入1个字符 'x'
s.insert(s.begin(), 'y');      // 使用迭代器插入
cout << s << endl;             // "yxsay: 222222 hello world"

输出:
在这里插入图片描述


2.6 erase() —— 删除字符或子串

在这里插入图片描述

删除指定位置的若干字符
string& erase(size_t pos = 0, size_t len = npos);

  • 从索引 pos 开始,删除 len 个字符。
  • 如果 lennpos(默认值)或超过剩余长度,则删除从 pos 开始到末尾的所有字符。

删除迭代器指向的单个字符
iterator erase(const_iterator p);

删除迭代器范围内的字符
iterator erase(const_iterator first, const_iterator last);

string s("hello world");

// 删除下标6处的1个字符(空格)
s.erase(6, 1);
cout << s << endl;             // "helloworld"

// 头删(删除第一个字符)
s.erase(s.begin());
cout << s << endl;             // "elloworld"

// 尾删(删除最后一个字符)
s.erase(--s.end());// --s.end()才是有效字符
cout << s << endl;             // "elloworl"

// 删除从下标5开始的所有字符
string ss("hello world");
ss.erase(5);
cout << ss << endl;            // "hello"

注意:insert 类似,erase 在删除非尾部位置时也会引发后续字符的向前移动,频繁操作也会影响性能 !


2.7 replace() —— 替换子串


功能: 将指定范围内的字符替换为新内容。

性能杀手警告:\(`Δ’)/

  • replace 是最重量级的修改操作之一。
  • 它结合了 eraseinsert 的代价:先删除旧内容,再插入新内容,并伴随数据的两次移动(或重新分配)。
  • 如果替换前后的长度不一致,还会引发后续字符的整体偏移。
string s("hello world");
s.replace(5, 1, "%%");   // 将索引5处的1个字符替换为 "%%"
cout << s << endl;       // "hello%%world"

实际案例:替换字符串中的所有空格

低效写法(直接使用 replace):

string s("hello world hello world");
size_t pos = s.find(' ');//find() 是查找接口
while (pos != string::npos) 
{
    s.replace(pos, 1, "%%");
    pos = s.find(' ', pos + 2);
}
cout << s << endl;

  这种方式在每次替换时都会引发后续字符的移动,对于长字符串效率较低。

高效替代方案(空间换时间):

string tmp;
for (auto ch : s) 
{
    if (ch == ' ') 
    {
        tmp += "%%";
    } else {
        tmp += ch;
    }
}
s.swap(tmp);  // 交换 tmp 和 s,高效且无拷贝
cout << s << endl;

  通过构建新字符串,避免了在原字符串上反复移动数据,时间复杂度从 O(n²) 降至 O(n)。


2.8 swap() —— 高效交换两个字符串的内容

在这里插入图片描述

功能: 交换当前字符串与 str 的内部数据指针、大小和容量信息,不进行任何字符拷贝。

复杂度: O(1),极其高效。

string a = "Hello";
string b = "World";
a.swap(b);
cout << a << " " << b << endl;  // "World Hello"

2.9 pop_back() —— 删除最后一个字符(C++11 起)

在这里插入图片描述

功能: 移除字符串的最后一个字符,将 size() 减少 1。

前置条件: 字符串不能为空,否则行为未定义。

复杂度: O(1),不会重新分配内存。

string s = "hello!";
s.pop_back();
cout << s << endl;  // "hello"

3. 字符串操作接口

3.1c_str() —— 获取 C 字符串(只读)

在这里插入图片描述
功能: 返回一个指向以空字符结尾的字符数组的指针,该数组包含与 string 相同的内容。返回的指针在 string 对象被修改或销毁前有效。

string s = "Hello";
const char* p = s.c_str();
printf("%s\n", p);  // 输出 Hello
string filename = "data.txt";
FILE* fp = fopen(filename.c_str(), "r");   // 标准用法

3.2 data() —— 获取原始字符数据

在这里插入图片描述

  • C++11 前:data() 返回的数组不保证以空字符结尾,因此不能直接当作 C 字符串使用。
  • C++11 起:data()c_str() 效果相同,均保证以 ‘\0’ 结尾。
string s = "hello";
const char* p = s.data();        // 只读访问
char* p2 = s.data();           
p2[0] = 'H';                     // 修改首个字符

3.3 copy() —— 拷贝字符到外部缓冲区

在这里插入图片描述
功能:string 中从 pos 开始的 len 个字符复制到 s 指向的缓冲区。

  • 不会自动添加空字符,需自行保证 s 足够大,必要时手动添加 ‘\0’。
  • 返回实际复制的字符数。

3.4 get_allocator() —— 获取内存分配器

在这里插入图片描述
功能: 返回 string 使用的分配器对象,通常用于需要自定义内存管理的高级场景。
日常使用频率较低,了解即可


3.5 查找与定位相关接口

3.5.1 find() —— 正向查找子串/字符

在这里插入图片描述

  • 在字符串中从位置 pos 开始向后搜索,寻找第一次出现的目标子串或字符。
  • 如果你不指定 pos,就从开头查找,如果你指定了 pos,就从那个位置开始查找
  • 找到:返回第一次出现的起始索引。
  • 未找到:返回 string::npos(一个静态常量,通常为 size_t 的最大值)。
string s = "test.cpp";
size_t dotPos = s.find('.');          // 返回 4
size_t cppPos = s.find("cpp");        // 返回 5
3.5.2 rfind() —— 反向查找

在这里插入图片描述

  • pos 位置开始向前搜索(默认为字符串末尾),返回最后一个匹配项的起始索引。
  • 非常适合处理带有多个分隔符的字符串,如提取文件扩展名。
string s = "hello world hello";
size_t pos = s.rfind("hello");
cout << pos << endl;  // 12 (第二个 hello 的起始位置)
3.5.3 find_first_of() —— 查找集合中任意字符首次出现位置

在这里插入图片描述

  • 在字符串中从 pos 开始查找 str 中任意一个字符 的首次出现位置。
string s = "aaaa123bbbbb";
size_t idx = s.find_first_of("0123456789");  // 返回 5(第一个数字 '1')
3.5.4 find_last_of() —— 查找集合中任意字符最后一次出现位置

在这里插入图片描述

  • 查找 str 中任意字符在字符串中最后一次出现的位置。
string str1 ("/usr/bin/man");
size_t found = str1.find_last_of("/\\");  // 同时处理 Unix 和 Windows 分隔符
3.5.5 find_first_not_of() —— 查找不在集合中的首个字符

在这里插入图片描述

  • pos 开始查找第一个不属于 str 中任意字符的位置。
  • 常用于跳过空白字符或分隔符。
string s = "    hello";
size_t firstNonSpace = s.find_first_not_of(' ');  // 返回 4
3.5.6 find_last_not_of() —— 查找不在集合中的最后一个字符

在这里插入图片描述

  • pos 开始向前查找最后一个不在 str 中的字符。
string s = "hello   ";
size_t lastNonSpace = s.find_last_not_of(' ');   // 返回 4('o' 的位置)

3.6 substr() —— 截取子串

在这里插入图片描述

  • 返回从 pos 开始、长度为 len的子串。若len 超过剩余长度,则返回剩余全部字符。
  • pos 越界,抛出 std::out_of_range 异常。
string s = "Hello World!";
string sub1 = s.substr(6, 5);   // "World"
string sub2 = s.substr(6);      // "World!" (len 默认为 npos)
cout << sub1 << " " << sub2 << endl;

3.7 compare() —— 字符串比较

在这里插入图片描述

  • 返回整数值:
  • 0: 相等
  • < 0:本字符串字典序小于参数字符串
  • > 0: 本字符串字典序大于参数字符串
string a = "apple";
string b = "banana";
if (a.compare(b) < 0) 
{
    std::cout << "apple comes before banana\n";
}

// 部分比较
int result = a.compare(2, 2, "pl");   // 比较 a.substr(2,2) 与 "pl",返回 0 相等

4. 非成员函数重载接口

4.1 operator+

在这里插入图片描述
  每个 operator+ 重载都会创建一个新的 std::string 临时对象,将两个操作数的内容合并后返回。对于大量拼接操作,建议使用 += 成员函数以减少临时对象的创建。

#include <iostream>
#include <string>

int main() 
{
    std::string s1("hello");

    // string + const char*
    std::string s2 = s1 + " world";
    std::cout << s2 << std::endl;          // 输出 "hello world"

    // const char* + string
    std::string s3 = "world " + s1;
    std::cout << s3 << std::endl;          // 输出 "world hello"

    // char + string
    std::string s4 = '!' + s1;
    std::cout << s4 << std::endl;          // 输出 "!hello"

    return 0;
}

4.2 Relational Operators——关系运算符

在这里插入图片描述

4.3 swap——交换

在这里插入图片描述

  • 高效交换两个字符串的内容
  • 通常只交换内部指针、长度等成员,不复制字符数据,时间复杂度为 O(1)。

4.4 operator<< 与 operator>>

在这里插入图片描述
  将 str 的内容写入输出流 os,不附加空字符,写入的字符数等于 str.size()

string str1 = "Hello, World!";
cout << str1 << endl;         

在这里插入图片描述

  • 从输入流中读取字符,存入 str
  • 跳过前导空白字符(空格、制表符、换行等),然后连续读取非空白字符,遇到下一个空白字符时停止。
  • 这意味着默认的 >> 不能读取包含空格的字符串
string str1;
cin >> str1;
cout << str1;        // 输入 "Hello World",str1 只得到 "Hello"

在这里插入图片描述

4.5 getline —— 读取整行

在这里插入图片描述
  getline 是专门为读取一整行(包括空格)而设计的全局函数,它解决了 operator>> 无法读取空格的问题。

从输入流 is 读取字符,存入 str,直到遇到以下情况之一:

  • 指定的分隔符(默认为换行符 ‘\n’)
  • 到达文件末尾(EOF

重要区别:

  • 分隔符会被读取并丢弃,不会存入 str
  • 不会跳过前导空白字符,会如实读取空格和制表符。
int main()
{
	string s1;
	getline(cin, s1);//遇到换行符才会停止
	cout << s1;

	return 0;
}

效果:
在这里插入图片描述

当然了,我们也可以自定义分隔符。

int main()
{
	string s = "aaa,bbb,ccc";
	stringstream flow(s);
	string str;

	while (getline(flow, str, ','))//用逗号做分隔符
	{
		cout << "拆分内容:" << str << endl;
	}
	return 0;
}

在这里插入图片描述


5. 数字转字符——to_string

to_string 是定义在 头文件中的全局函数模板族,用于将各种算术类型转换为对应的 std::string 对象。
在这里插入图片描述
这些重载覆盖了所有基本数值类型,调用时编译器会根据参数类型自动选择正确的版本。


6. 实战演练

6.1 反转字母

ど°0°う♡ 传送门
在这里插入图片描述
解答示例:

class Solution {
public:
    bool isLetter(char ch)//判断字符是不是英文字母
    {
        if(ch >= 'a' && ch <= 'z')
            return true;
        if(ch >= 'A' && ch <= 'Z')
            return true;

        return false;
    }
    string reverseOnlyLetters(string s) {
        int left = 0,right = s.size()-1;
        while(left<right)
        {
            while(left<right && !isLetter(s[left]))//还要考虑到越界问题
            {
                left++;
            }
            while(left<right && !isLetter(s[right]))
            {
                right--;
            }
            swap(s[left++],s[right--]); //交换完了之后跳到下一个字符的位置
        }
        return s;
    }
};

6.2 字符串中的第一个唯一字符

ど°0°う♡ 传送门
在这里插入图片描述
解答示例:

class Solution {
public:
    int firstUniqChar(string s) {
        int count[26]; // 数组大小为26,对应26个小写字母
        for(auto ch : s)
        {
            count[ch - 'a']++;// 将字符转换为数组索引,并增加计数
        }

        for(size_t i = 0;i<s.size();i++)
        {
            // 如果当前字符的出现次数为1,说明是第一个不重复的字符
            if(count[s[i] - 'a'] == 1)
                return i;
        }
        return -1;
    }
};

6.3 字符串相加

ど°0°う♡ 传送门
在这里插入图片描述
解答示例:

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1 = num1.size()-1,end2 = num2.size()-1;
        int next = 0;
        string str;
        while(end1 >= 0 || end2 >= 0 )
        {
            int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;//减去'0',变成字符-字符就能得到整型的值
            int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;

            int ret = val1 + val2 + next;

            next = ret / 10;  //得到十位上的数字
            ret = ret % 10;   //得到个位上的数字

            str += (ret + '0'); //将整数转换为对应的字符,然后拼接到字符串末尾
        }
        
        if(next == 1)//最后一次相加可能还需要进位
        {
            str += '1';
        }
        reverse(str.begin(),str.end());//把倒序变为正序

        return str;
    }
};

6.4 字符串最后一个单词的长度

ど°0°う♡ 传送门
在这里插入图片描述
解答示例:

#include <iostream>
using namespace std;

int main() 
{
    string s;
    getline(cin,s);//这里不可以直接用cin

    size_t pos = s.rfind(' ');
    size_t len = s.size() - (pos+1);

    cout << len << endl;
    return 0;
}

结语:

  今天的内容到这里就结束了,希望你能有所收获~

代码无bug,学习不迷路,我们下篇再见!(•̀ᴗ•́)و

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐