【C++】认识标准库STL(2)

目录

1.string构造方式2.迭代器

3.reserve4.shrink_to_fit();

5.resize 6.at与[]

7.添加类函数 8.assign

9.replace


1.string构造方式

变量 构造方式 运行结果 说明
s1 默认构造 空字符串 创建一个长度为 0 的空字符串
s2 字符串字面量构造 "hello world" 用 C 风格字符串初始化 string
s3 拷贝构造 "hello world" 复制s2的全部内容
s4 子串构造 "ello w" s2的下标 1 开始,取 6 个字符
s5 子串构造 "ello world" s2的下标 1 开始,取 60 个字符(超出实际长度,自动取到末尾)
s6 子串构造 "ello world" s2的下标 1 开始,取到字符串末尾
s7 取前 n 个字符 "hello" str中取前 5 个字符
s8 n 个字符构造 100 个# 生成由 100 个#组成的字符串

2.迭代器

1.类型介绍

迭代器类型 作用 获取方式 能否修改元素
iterator 正向读写迭代器 s.begin() / s.end() ✅ 可以
const_iterator 正向只读迭代器 s.cbegin() / s.cend() ❌ 不可以
reverse_iterator 反向读写迭代器 s.rbegin() / s.rend() ✅ 可以
const_reverse_iterator 反向只读迭代器 s.crbegin() / s.crend() ❌ 不可以

1. 反向迭代器(倒序遍历)

复制代码
string s = "hello";

// 倒序遍历
for (auto it = s.rbegin(); it != s.rend(); ++it) {
    cout << *it;
}
// 输出:olleh

2. 只读迭代器(const_iterator)

复制代码
// 场景1:普通字符串只读遍历(推荐使用 cbegin()/cend())
string s = "hello";
for (auto it = s.cbegin(); it != s.cend(); ++it) {
    cout << *it;
    // *it = 'x'; // 报错!const_iterator 不允许修改元素
}

// 场景2:const 字符串必须使用 const_iterator
const string str = "world";
// string::iterator it = str.begin(); // 报错!const 字符串无法获取可写迭代器
string::const_iterator cit = str.begin();
cout << *cit << endl; // 输出 'w'

2.关键边界说明

  • begin():指向字符串的第一个字符

  • end():指向字符串最后一个字符的下一个位置(不指向有效元素,仅作为结束标志)

  • rbegin():指向字符串的最后一个字符(反向遍历的起点)

  • rend():指向字符串第一个字符的前一个位置(反向遍历的结束标志

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

    int main() {
    string s = "hello world";

    复制代码
      // 写法1:显式迭代器遍历
      string::iterator it;
      for (it = s.begin(); it != s.end(); ++it) {
          cout << *it; // *it 解引用,获取当前迭代器指向的字符
      }
      cout << endl;
    
      // 写法2:auto 简化(C++11 及以上推荐)
      for (auto it = s.begin(); it != s.end(); ++it) {
          cout << *it << " ";
      }
      cout << endl;
    
      return 0;

    }

iterator 支持读写,你可以通过 *it 修改指向的字符

复制代码
string s = "hello";
string::iterator it = s.begin();
*it = 'H';       // 修改第一个字符为 'H'
*(it + 1) = 'E'; // 修改第二个字符为 'E'

cout << s << endl; // 输出:HEllo

3.迭代器的实际应用

复制代码
#include <iostream>
#include <string>
#include <algorithm> // 包含 STL 算法,如 reverse
using namespace std;

int main() {
    string s = "hello, iterator!";

    // 1. 正向遍历并修改所有小写字母为大写
    cout << "原字符串:" << s << endl;
    for (auto it = s.begin(); it != s.end(); ++it) {
        if (islower(*it)) {
            *it = toupper(*it);
        }
    }
    cout << "转大写后:" << s << endl;

    // 2. 用反向迭代器倒序输出
    cout << "倒序输出:";
    for (auto it = s.rbegin(); it != s.rend(); ++it) {
        cout << *it;
    }
    cout << endl;

    // 3. 用 const_iterator 只读遍历
    const string const_s = "read-only string";
    cout << "只读遍历:";
    for (auto it = const_s.cbegin(); it != const_s.cend(); ++it) {
        cout << *it;
    }
    cout << endl;

    // 4. 配合 STL 算法:用迭代器反转字符串
    reverse(s.begin(), s.end());
    cout << "算法反转后:" << s << endl;

    return 0;
}
方式 优点 缺点 适用场景
迭代器 通用(所有容器都能用)、支持算法库、可反向遍历 写法略复杂 跨容器通用代码、需要反向 / 随机访问、配合 STL 算法
下标 [] 写法简单、直观 仅适用于 string/vector 等支持随机访问的容器 简单遍历、已知索引的场景
范围 for 循环(C++11+) 写法极简、自动处理边界 无法获取当前位置的迭代器,不能修改容器结构 纯遍历 / 只读遍历、不需要修改容器的场景

3. reserve

1. 作用

  • new_cap > capacity()→ 重新开辟更大内存,把旧字符拷贝过去,容量变大。
  • new_cap <= capacity()→ 什么都不做(不会缩容)。
  • 全程不改变 size,不增加 / 删除字符,不初始化空位置。

2. 意义

1. 避免频繁扩容,提升效率

string 默认追加字符(+= /push_back)时:

空间不够 → 自动扩容 → 重新申请内存 + 拷贝数据 → 效率低。

如果你提前知道要存大量字符:

复制代码
string s;
s.reserve(1000);   // 一次性开好空间
// 后续大量 += 不会反复扩容

2. 减少内存碎片

提前开好固定容量,避免多次内存申请释放。

3. 配合迭代器(重点)

reserve 扩容会导致 所有迭代器全部失效因为底层内存地址整体更换。

4. reserve VS resize(必考区别)

函数 作用 改 size 改 capacity 影响内容
reserve(n) 预留容量 完全不变
resize(n) 修改有效长度 增删字符、补默认值

5. 特殊用法:手动缩容(shrink)

注意:不太靠谱,不同编译器,效果不同。

复制代码
// 把容量压缩到刚好等于 size,释放多余空间
s1.reserve(s1.size());

4. shrink_to_fit()

作用:强制把 string 的容量(capacity)缩小到 和 长度(size)完全一样。

注意:reserve和shrink_to_fit都是扩大缩小到(>=)size的值,因为编译器处理不同,可能会存在内存对齐。

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s("1231241");

    cout << "初始状态:\n";
    cout << "size = " << s.size() << endl;     // 7
    cout << "capacity = " << s.capacity() << endl;

    s.reserve(100);  // 强行开大容量
    cout << "\nreserve(100) 后:\n";
    cout << "size = " << s.size() << endl;     // 不变 7
    cout << "capacity = " << s.capacity() << endl; // 100+

    s.shrink_to_fit(); // 缩容!
    cout << "\nshrink_to_fit() 后:\n";
    cout << "size = " << s.size() << endl;     // 7
    cout << "capacity = " << s.capacity() << endl; // 7+

    return 0;

5. resize

1. 函数原型

复制代码
void resize(size_t n);
void resize(size_t n, char c);

2. 三种执行情况

设原:size=oldLen

场景 条件 size 变化 capacity 变化 字符串内容变化
拉长 n>oldLen ✅ 变大为 n 空间不足则扩容 末尾补充:• resize(n):补\0resize(n,c):补字符c
截短 n<oldLen ✅ 缩小为 n ❌ 不变 截断尾部,只保留前 n 个字符
不变 n=oldLen ❌ 无变化 ❌ 无变化 内容完全不变
操作 执行后内容 size 核心行为
s.resize(3) "123" 3 截断尾部
s.resize(10,'#') "1231241###" 10 末尾补#
s.resize(10) "1231241\0\0\0" 10 末尾补空字符

6. at与[]

1. 区分对照

特性 s.at(pos) s[pos]
越界检查 ✅ 有 ❌ 没有
越界行为 抛出异常 未定义行为(崩溃 / 乱码)
效率 略低(检查耗时) 更高(无检查)
安全性
使用场景 需要安全访问、不确定下标是否合法 确定下标合法、追求效率

2. 作用

代码 结果 说明
s.at(0) '1' 访问第 0 个字符
s.at(3) '1' 访问第 3 个字符
s.at(6) '1' 访问最后一个字符
s.at(10) 抛异常 越界,程序不会崩,能捕获
s.at(2) = 'A' s → "12A1241" 可修改字符

3. 特殊作用

复制代码
// 越界测试
    try {
        cout << s.at(100) << endl;
    } catch (const out_of_range& e) {
        cout << "越界啦!" << endl;
    }

7. 添加类函数

包含:push_back / += / append / insert

1. push_back

功能

只能在字符串末尾添加 单个字符

特点

  1. 只能传 char,不能传字符串

  2. 末尾追加

  3. 空间不足自动扩容

    void push_back(char c);

2. operator+= (最常用)

功能

尾部拼接,万能追加

复制代码
string& operator+=(const string& str);   // 加string
string& operator+=(const char* s);        // 加C字符串
string& operator+=(char c);              // 加单个字符

3. append(专业尾部追加,重载最多)

特点

  • 只在尾部

  • 功能比 += 更细,适合批量、截取追加

    // 1. 追加 string 对象
    string& append(const string& str);

    // 2. 追加 C风格字符串
    string& append(const char* cstr);

    // 3. 追加 n 个相同字符
    string& append(size_t n, char ch);

    // 4. 截取str从pos开始len个字符追加
    string& append(const string& str, size_t pos, size_t len);

    // 5. 追加 cstr 前 n 个字符
    string& append(const char* cstr, size_t n);

4. insert(任意位置插入)

功能

在指定下标 / 迭代器位置插入内容可以:头部、中间、尾部插入

复制代码
// 1. 在pos位置插入字符串
string& insert(size_t pos, const char* s);

// 2. 在pos插入n个字符ch
string& insert(size_t pos, size_t n, char ch);

// 3. 在pos插入string
string& insert(size_t pos, const string& str);

// 4. 迭代器版本:在it位置插入字符
iterator insert(iterator it, char c);

string s = "123";

s.insert(0, "ab");     // 下标0插入 → ab123
s.insert(2, 3, '0');   // 下标2插3个0 → ab000123

// 迭代器插入
auto it = s.begin();
s.insert(it + 1, 'X');

5. 四种方法对比

函数 插入位置 支持内容 优点 缺点
push_back 仅末尾 单个 char 轻量、简单 不能加字符串
+= 仅末尾 char / 字符串 /string 写法最简,日常首选 无精细截取功能
append 仅末尾 多种重载(串、n 个字符、子串) 追加功能最全 只能尾插
insert 任意位置 串、n 个字符、单个字符 可中间 / 头部插入 效率偏低,易迭代器失效

8.assign

1、核心作用

重新赋值、覆盖原有全部内容直接清空旧字符串,用新内容替换。和 = 赋值类似,但 重载更多、功能更强。

复制代码
// 1. 赋值 string 对象
string& assign(const string& str);

// 2. 赋值 C风格字符串
string& assign(const char* s);

// 3. 赋值 n 个相同字符
string& assign(size_t n, char c);

// 4. 截取某串 [pos, len] 赋值
string& assign(const string& str, size_t pos, size_t len);

// 5. 取 C串 前 n 个字符
string& assign(const char* s, size_t n);

2. 实例运用

复制代码
string s;

// 1. 整体赋值
s.assign("12345");   // s = "12345"

// 2. n 个相同字符覆盖
s.assign(3, 'a');    // s = "aaa"

// 3. 截取子串赋值
string t = "abcdef";
s.assign(t, 2, 3);   // 从下标2取3个:cde

// 4. 只取前n个字符
s.assign("hello", 2);// he

9. replace

1. 作用

replace = 删除指定区间 + 插入新内容

直接替换字符串中某一段内容,不是只替换单个字符。

2. 最常用函数原型(重点)

复制代码
   s.replace(位置pos, 长度len, 新内容);
// 1. 从 pos 位置开始,长度为 len,替换成 str
string& replace(size_t pos, size_t len, const string& str);

// 2. 替换成 n 个 ch 字符
string& replace(size_t pos, size_t len, size_t n, char ch);

// 3. 迭代器版本:替换 [first, last) 区间
string& replace(iterator first, iterator last, const string& str);

3. 实例运用

复制代码
string s = "Hello World";

// 从下标 6 开始,长度 5 替换成 "C++"
s.replace(6, 5, "C++");
Hello C++

// 从下标 0 开始,长度 5 替换成 3 个 '*'
s.replace(0, 5, 3, '*');
*** World

// 替换 [begin, begin+5) 区间
s.replace(s.begin(), s.begin()+5, "Hi");
Hi World

4. 例题实用

复制代码
// 所有空格替换为%%
	string s5("            hello world hello bit");
	//size_t pos = s5.find(' ');
	//while (pos != string::npos)
	//{
	//	s5.replace(pos, 1, "%%");
	//	pos = s5.find(' ', pos+2);
	//}
	//cout << s5 << endl;
//相同写法
	string s6;
	s6.reserve(s5.size());
	for (auto ch : s5)
	{
		if (ch == ' ')
			s6 += "%%";
		else
			s6 += ch;
	}
	cout << s6 << endl;

10. reverse

用法场景 代码 原字符串 反转后结果 说明
1. 反转整个字符串 reverse(s.begin(), s.end()); "123456" "654321" 最常用
2. 反转前 N 个字符 reverse(s.begin(), s.begin()+3); "abcdef" "cbadef" 反转前 3 个
3. 反转后 N 个字符 reverse(s.end()-3, s.end()); "123456" "123654" 反转最后 3 个
4. 反转中间一段 reverse(s.begin()+2, s.begin()+5); "abcdefg" "abedcfg" 反转下标 [2,5)
5. 反向迭代器反转 reverse(s.rbegin(), s.rend()); "hello" "olleh" 等价全反转
容器类型 代码 说明
string 字符串 reverse(s.begin(), s.end()); 反转字符串
vector 向量 reverse(v.begin(), v.end()); 反转数组元素
普通数组 reverse(arr, arr + 5); 反转数组
list 链表 reverse(l.begin(), l.end()); 反转链表

11.题目运用

415. 字符串相加https://leetcode.cn/problems/add-strings/

给定两个字符串形式的非负整数 num1num2 ,计算它们的和并同样以字符串形式返回。

你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。

题解:

思考题解的运行效率,利用上面介绍的所有方法,可不可以进行优化呢?

复制代码
class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1=num1.size()-1;
        int end2=num2.size()-1;
        string s;
        int sum=0;
        int carry=0;
        while(end2>=0||end1>=0)
        {
            sum=0;
            int x1=end1>=0?num1[end1]-'0':0;
            int x2=end2>=0?num2[end2]-'0':0;
            sum=x1+x2+carry;
            carry=sum/10;
            end1--;
            end2--;
            sum=sum%10;
            s.insert(0,1,sum+'0');
        }
        if(carry==1)
        {
            s.insert(0,1,1+'0');
        }
        return s;
    }
};

优化:

复制代码
class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1=num1.size()-1;
        int end2=num2.size()-1;
        string s;
        s.reserve(max(num1.size(),num2.size())+1);
        int sum=0;
        int carry=0;
        while(end2>=0||end1>=0)
        {
            sum=0;
            int x1=end1>=0?num1[end1]-'0':0;
            int x2=end2>=0?num2[end2]-'0':0;
            sum=x1+x2+carry;
            carry=sum/10;
            end1--;
            end2--;
            sum=sum%10;
            s+=(sum +'0');
        }
        if(carry==1)
        {
            s+=(1 +'0');
        }
        reverse(s.begin(),s.end());
        return s;
    }
};

1.利用reverse,成功避免了insert头插时,遍历字符串后移问题,提高了效率。

2.利用reserve,根据字符串大小开足空间,避免了后期的过量扩容问题。

相关推荐
故事还在继续吗1 小时前
STL 容器算法手册
开发语言·c++·算法
啊我不会诶1 小时前
2023西安邀请赛vp补题
c++·算法
唠玖馆1 小时前
c++ list详解
c++
khalil10201 小时前
代码随想录算法训练营Day-38动态规划06 | 322. 零钱兑换、279.完全平方数、139.单词拆分、多重背包、总结
数据结构·c++·算法·leetcode·动态规划
techdashen1 小时前
Cloudflare 用 Rust 实现 QUIC 协议:quiche 是怎么设计的
开发语言·后端·rust
wuxianda10301 小时前
苹果App上架4.3a问题3天解决方案汇报总结
开发语言·javascript·uni-app·ecmascript·ios上架·苹果上架
無斜1 小时前
【CAPL实用开发】--- CAPL调用 .NET DLL
开发语言·c#·capl·canoe
石榴树下的七彩鱼1 小时前
OCR API价格对比2026:身份证/发票/医疗票据识别哪家性价比最高?含Python对接+成本公式
开发语言·人工智能·python·ocr·图像识别·文字识别·api接口