C++ STL 完整入门笔记[1]:string 核心底层机制与高频算法实战

前言

std::string 是 C++ 最常用的字符串容器,很多开发者只会简单调用 push_backsubstr,却不了解其容量扩容、迭代器分类、内存伸缩、性能优化底层逻辑;同时字符串大数运算也是面试高频算法题。本文结合图示拆解 string 底层特性,并对比两种大数相加写法的性能差距。

一、string 迭代器体系:正向、反向、const 区分

1. 正向迭代器 begin /end

string s = "123456";
auto it = s.begin(); // 指向首字符'1'
while(it != s.end()) // end指向末尾'\0',不可解引用
{
    cout << *it;
    ++it;
}

内存示意图: [1,2,3,4,5,6,\0] begin → 首元素地址,end → 末尾占位符,区间 [begin, end) 左闭右开。

2. const_iterator 常量迭代器

  • iterator:可修改迭代器指向的数据 *it = 'x'

  • const_iterator:仅可读,禁止修改容器内字符,常用于 const string& 参数场景。

3. 反向迭代器 rbegin /rend

反向迭代器用于逆序遍历字符串,逻辑和正向完全相反:

string s1 = "123456";
string::reverse_iterator it3 = s1.rbegin();
while (it3 != s1.rend())
{
    cout << *it3 << " ";
    ++it3;
}
// 输出:6 5 4 3 2 1

内存逻辑:

  • rbegin():指向最后一个有效字符 6

  • rend():指向第一个字符前的占位;

  • 自增 ++it 等价于正向迭代器 --it,实现倒序遍历。

二、string 容量机制:size、capacity、扩容规则

两个核心成员变量:

  • _size:当前有效字符长度,s.size() 获取;

  • _capacity:底层数组总容量,包含预留空闲空间,s.capacity() 获取; 扩容本质:底层是动态字符数组,空间耗尽时开辟新数组、拷贝旧数据、释放旧内存,开销极大。

1. 不同编译器扩容策略对比

① VS2019(MSVC)

初始空串容量 15;首次填满后,后续按 1.5 倍 扩容:

15 → 22 → 33 → 49 ...

测试代码:

string s2;
size_t old = s2.capacity();
cout << "capacity:" << old << endl;
for (size_t i = 0; i < 100; i++)
{
    s2.push_back('x');
    if (s2.capacity() != old)
    {
        cout << "capacity:" << s2.capacity() << endl;
        old = s2.capacity();
    }
}
② g++(GCC Linux)

初始容量 15,扩容采用 2 倍扩容

15 → 30 → 60 → 120 ...

2. 缩容:shrink_to_fit

string 默认不会自动缩容pop_backclear 只会修改 _size,底层 capacity 不变; 若需要释放空闲内存,手动调用 shrink_to_fit(),会重新开辟等于 size 的数组拷贝数据,代价高:


// 先填满100个字符 for (size_t i = 0; i < 100; i++) s2.push_back('x'); // 删除50个字符,size=50,capacity仍为120 for (size_t i = 0; i < 50; i++) s2.pop_back(); // 手动缩容,capacity变为50 s2.shrink_to_fit();

设计取舍:以空间换时间,频繁增删字符串时,保留冗余容量避免反复扩容拷贝;只有确定不再扩容时才缩容。

3. reserve 预分配空间(性能优化核心)

已知字符串最大长度时,提前用 reserve(n) 开辟足够容量,全程不会触发扩容,大幅提升效率:

// 先填满100个字符
for (size_t i = 0; i < 100; i++) s2.push_back('x');
// 删除50个字符,size=50,capacity仍为120
for (size_t i = 0; i < 50; i++) s2.pop_back();
// 手动缩容,capacity变为50
s2.shrink_to_fit();

面试考点:处理长字符串拼接、大数运算时,优先 reserve 预分配,避免 insert 头插带来的 O (N²) 时间复杂度。

4. resize 修改有效长度

resize(n, c='\0') 两种行为:

  1. n < size:截断字符串,保留前 n 个字符,减小 _sizecapacity 不变;

  2. n > size:尾部填充字符 c,增大有效长度,空间不足则触发扩容。

三、高频算法:415. 字符串相加(LeetCode)

题目要求

输入两个字符串格式非负整数 num1="456"num2="77",输出和字符串 "533"; 禁止转 long long/int(数值过长会溢出),只能逐位模拟竖式加法。

思路:竖式加法

  1. 双指针分别指向两个字符串末尾(个位);

  2. 每次取对应位数字相加,加上进位 carry

  3. 计算当前位值、新进位;

  4. 处理剩余未遍历字符;

  5. 最后处理最高位进位。

写法 1:头插 insert(低效 O (N²))

class Solution {
public:
    string addStrings(string num1, string num2) {
        string str;
        int end1 = num1.size()-1, end2 = num2.size()-1;
        int carry = 0;
        while(end1 >= 0 || end2 >= 0)
        {
            int x1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int x2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int val = x1 + x2 + carry;
            carry = val / 10;
            val = val % 10;
            // 头插:每次在字符串最前面插入字符
            str.insert(str.begin(), ('0' + val));
        }
        if(carry == 1)
            str.insert(str.begin(), '1');
        return str;
    }
};

性能缺陷: string::insert(begin(), ch) 会让已有字符全部向后挪动一位,长度为 N 时总操作次数 1+2+3+...+N = N(N+1)/2,时间复杂度 O(N²),超长数字会超时。

写法 2:尾插 + 反转(高效 O (N),工程推荐)

class Solution {
public:
    string addStrings(string num1, string num2) {
        string str;
        // 预分配最大所需容量,避免扩容
        str.reserve(max(num1.size(), num2.size()) + 1);
        int end1 = num1.size()-1, end2 = num2.size()-1;
        int carry = 0;
        while(end1 >= 0 || end2 >= 0)
        {
            int x1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int x2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int val = x1 + x2 + carry;
            carry = val / 10;
            val = val % 10;
            // 尾插,仅追加,无数据挪动 O(1)均摊
            str += ('0' + val);
        }
        if(carry == 1)
            str += '1';
        // 反转字符串得到正确顺序
        reverse(str.begin(), str.end());
        return str;
    }
};

优化点拆解:

  1. reserve 预分配空间,全程无扩容拷贝;

  2. push_back/+= 尾插仅追加字符,均摊 O (1);

  3. 仅最后一次全局反转,总时间复杂度 O(N)

  4. 工业代码标准写法,处理超大数字无性能瓶颈。

四、总结开发最佳实践

  1. 迭代器:只读场景优先 const_iterator,逆序遍历用 rbegin/rend

  2. 容量优化:已知字符串最大长度必须调用 reserve,杜绝频繁扩容;

  3. 增删性能:避免 begin() 位置 insert/erase,优先尾部操作,需要逆序结果先尾插再 reverse

  4. 内存伸缩pop_back/clear 不会释放容量,大量空闲空间不再使用时调用 shrink_to_fit

  5. 算法选型:字符串大数、长文本拼接场景,一律采用「尾插 + 反转」代替头插,规避 O (N²) 性能陷阱。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

影视飓风TIM

无限进步

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值