当前位置: 首页 > news >正文

C++ string(reserve , resize , insert , erase)

目录

1. size和length

2. size和capacity

1. size(或length)

2.capacity 

 3. 总结:

3. reserve

1. reserve 的作用 

2. 示例 

4. clear

5. shrink_to_fit

6. resize

7. insert

1. 在指定位置插入单个字符

2. 在指定位置插入多个相同字符

3. 在指定位置插入另一个字符串

4. 在指定位置插入另一个字符串的部分内容

5. 在指定位置插入C风格字符串(以 '\0' 结尾的字符数组)

6. 在指定位置插入 C 风格字符串的部分内容

7. 缺点

8. erase

1. 删除指定位置的单个字符

2. 从指定位置删除指定长度的子串

3. 删除迭代器范围 [first, last) 内的字符

4. 底层原理与效率分析

5. 使用注意事项

6. 总结:

9. 总结:


1. size和length

在 C++ 的 string 类中 , size() 和 length() 几乎没有区别 , 它们的功能完全一致。

  • 相同点:两者都返回字符串当前实际存储的字符数量(不包含结尾的空字符 \0)。
  • 例如:string s = "hello" , s.size() 和 s.length() 的返回值都是 5。
  • 细微差异:
  1. length() 是早期 C++ 为了与 C 语言的字符串处理函数(如 strlen )保持风格一致而引入的。
  2. size() 是后来为了统一容器(如 vector , list 等)的接口而添加的 , 更符合 C++ 标准容器的命名习惯。

实际使用中 , 两者可以互换 , 选择哪个主要看个人或团队的编码风格。

2. size和capacity

在 C++ 的 string 类中 , size 和 capacity 是两个不同的成员函数 , 用于描述字符串的不同属性 , 核心区别如下:

1. size(或length)

  • 含义:返回字符串当前实际存储的字符数量(不包含结尾的空字符 \0)。
  • 示例:若 string s = "abc" , 则s.size() 的值为 3 , 因为实际存储了 'a'、'b'、'c' 三个字符。 

2.capacity 

  • 含义:返回字符串当前在内存中分配的存储空间能容纳的最大字符数量(同样不包含结尾的空字符) , 即当前缓冲区的大小。
  • 特点:capacity 通常大于或等于 size , 预留的空间是为了避免每次添加字符时都重新分配内存(提升效率)。
  • 示例:当 s.size() 为 3 时 , s.capacity() 可能是15(具体值取决于编译器实现) , 表示当前最多能存储 15 个字符而无需重新分配内存。

 3. 总结:

  • size 反映实际使用的字符数 , capacity  反映当前分配的总容量。
  • 当 size 超过 capacity 时 , string 会自动扩容(通常翻倍或按一定策略增加) , 此时 capacity 会变大。

3. reserve

1. reserve 的作用 

  • reserve 是 string 类的一个成员函数 , 它的主要作用是预先为字符串分配指定大小的存储空间。这样做的目的是为了避免后续添加字符时频繁地进行扩容操作 , 从而提高程序的效率。因为每次扩容都需要重新分配内存 , 复制原有数据 , 是比较耗时的操作。
  • reserve  不能进行缩容操作。reserve(n)  的核心作用是确保容器的容量(capacity)至少为 n
  • 如果当前容量小于 n , 则会进行扩容(重新分配更大的内存空间) , 将容量提升到 n 或更大(具体取决于实现)。
  • 如果当前容量大于或等于 n , 则不会做任何操作 , 既不会缩小容量 , 也不会改变容器的大小( size )和内容。 
  • 也就是说 , reserve 是一个“单向操作”——只能保证容量不小于指定值 , 无法主动缩小容量。如果需要缩容 , 需要借助  shrink_to_fit (C++11 及以上) , 它的作用就是尝试将容量缩小到与实际大小( size )匹配。

2. 示例 

string s2;
// 确定知道需要多少空间,提前开好,避免扩容,提高效率
s2.reserve(100);
  1. 首先定义了一个空的 string 对象  s2 。
  2. 然后调用 s2.reserve(100) , 这一步是预先为 s2 分配能容纳 100 个字符的存储空间(不包含结尾的 \0) , 此时 , s2 的 capacity (容量)会至少变成 100 (具体可能因编译器等因素略有差异 , 但会满足至少能存 100 个字符) , 而 size (实际字符数)还是 0 , 因为还没有添加字符。
  3. 接下来的循环:
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();}
}
  1. 先记录下调用 reserve 后的初始容量 old 并输出。
  2. 然后循环 100 次 , 每次向 s2 中添加一个字符  'x' 。
  3. 在循环中 , 判断当前 s2 的容量是否发生变化。由于之前已经用 reserve 预先分配了至少能存 100 个字符的空间 , 所以在添加这 100 个字符的过程中 , s2 不需要再进行扩容 , capacity 不会发生变化 , 所以内部的 if 语句块不会执行(除非编译器的 reserve 实现分配的容量刚好是 100 , 且严格按这个容量来 , 不过通常会预留一定冗余 , 但这里代码逻辑是演示如果提前 reserve 足够空间 , 就不会扩容)。这就体现了 reserve 避免扩容、提高效率的作用。如果没有调用 reserve(100) , 直接循环添加 100 个字符 , s2  会多次扩容 , 每次扩容都要消耗额外的时间和资源。

 补充说明:

  1. reserve 只改变字符串的容量(capacity) , 不改变实际存储的字符数量(size)。
  2. 当需要向字符串中添加大量字符 , 且能提前预估所需空间时 , 使用 reserve 可以显著提升性能 , 减少内存分配和数据复制的开销。

4. clear

string s1("123456");
cout << s1 << endl;
s1.clear();
cout << s1 << endl;
  1. 首先输出 s1 原本的内容(s1 被初始化为 "123456" , 所以会输出 123456)。
  2. 然后调用 s1.clear() , clear 函数的作用是清空 s1 中实际存储的字符(即让 s1 的 size 变为0) , 但不会释放 s1 之前分配的容量( capacity 保持不变)。
  3. 最后再次输出 s1, 此时 s1 已经被清空 , 所以输出空字符串。

5. shrink_to_fit

shrink_to_fit 是 C++ 标准库中 string  类(以及 vector、deque 等容器)的成员函数 , 作用是尝试将容器的容量( capacity )缩小到与实际存储的元素数量( size )相匹配 , 从而释放多余的内存空间。
 
核心逻辑:

  1. 容器(如 string )的容量( capacity )是指当前分配的 , 能容纳的最大元素数量;大小( size )是指当前实际存储的元素数量。通常 , 为了避免频繁扩容(扩容需重新分配内存、复制数据 , 开销大) , 容器会预先分配比 size 更大的容量。
  2. 当容器中的元素被大量删除 , size 远小于 capacity 时 , shrink_to_fit 会尝试重新分配内存 , 让 capacity 尽可能接近 size , 从而回收未被利用的内存。

演示 string 不会自动缩容及 shrink_to_fit 的作用:
 

string s2;
// 确定知道需要多少空间,提前开好,避免扩容,提高效率
s2.reserve(100);
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();}
}// 不会缩容,缩容要重新开空间,代价很大
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
//s2.clear();
for (size_t i = 0; i < 50; i++)
{s2.pop_back();
}
s2.shrink_to_fit();
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
  1. 首先输出 s2 的 size (此时 s2 经过前面的循环 , 已经添加了 100 个 'x' , 所以 size 是100)和 capacity (是之前 reserve(100) 后分配的容量 , 比如 100 或更大的值) , 注释说明 string 不会自动缩容 , 因为缩容需要重新分配内存空间 . 代价很大。
  2. 然后通过循环 , 使用 pop_back 函数弹出 50 个  'x' , 此时 s2 的 size 变为 100 - 50 =50 , 但 capacity 仍然保持之前的大小(因为没有自动缩容)。
  3. 接着调用 s2.shrink_to_fit() , 这个函数的作用是尝试将 s2 的容量缩小到与实际 size 相匹配的大小(即让 capacity 尽可能接近 size)。
  4. 最后输出 s2 经过弹出操作和 shrink_to_fit 后的 size (50)和 capacity(此时 capacity 会被调整为接近或等于 50 的值 , 具体取决于编译器 , 但会比之前的容量小很多)。

6. resize

resize 成员函数。resize 主要用于调整字符串的大小( size ) , 并可以指定调整后新增字符的默认值。
 
作用与行为:

  1. 调整大小:如果指定的新大小 n 大于当前字符串的 size , string 会在末尾添加足够的字符 , 使 size 变为 n;如果 n 小于当前 size , 则会删除末尾的字符 , 使 size 变为 n。
  2. 指定默认字符:当需要扩容时 , 可以指定第二个参数(字符类型) , 新增的字符会被初始化为该字符;如果不指定 , 默认新增字符是 '\0'(空字符)。

示例:

	string s2("123456");cout << s2 << endl;cout << "size:" << s2.size() << endl;cout << "capacity:" << s2.capacity() << endl;// 插入数据,让size到n个// n > capacity > size;s2.resize(20, 'x');cout << s2 << endl;cout << "size:" << s2.size() << endl;cout << "capacity:" << s2.capacity() << endl;// capacity > n > size;//s2.resize(25, 'x');s2.resize(25);  // 插入的是\0s2.push_back('y');cout << s2 << endl;cout << "size:" << s2.size() << endl;cout << "capacity:" << s2.capacity() << endl;// 删除数据// n < size;s2.resize(5);cout << s2 << endl;cout << "size:" << s2.size() << endl;cout << "capacity:" << s2.capacity() << endl;

这段代码主要围绕 string 类的 resize 和 push_back (尾插)操作 , 展示字符串在大小 , 容量方面的变化:

1. 初始字符串与基本属性输出
 

string s2("123456");
cout << s2 << endl;
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
  • 定义 string 对象 s2 并初始化为 "123456"。
  • 输出 s2 的内容、size (实际字符数 , 这里是 6)和 capacity (当前分配的容量 , 能容纳的最大字符数 , 通常大于 size , 具体值由编译器决定)。

2. resize 扩容(为尾插做准备 , 或直接改变大小)
 

s2.resize(20, 'x');
cout << s2 << endl;
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
  • resize(20, 'x') :把 s2 的 size 调整为 20。因为原 size (6)小于 20 , 所以会在字符串末尾添加 20 - 6 = 14  个 'x'。
  • 此时 s2 内容变为 "123456xxxxxxxxxx" , size 是 20。若原 capacity 小于 20 , string 会扩容(重新分配更大内存) , capacity 会增大;若原 capacity 已大于等于 20 , 则 capacity 不变。

3. resize 与 push_back 结合的尾插
 

s2.resize(25);  // 插入的是\0
s2.push_back('y');
cout << s2 << endl;
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
  • s2.resize(25) :把 s2 的 size 调整为 25。原 size (20)小于 25 , 会在末尾添加 25 - 20 = 5 个字符。由于没指定第二个参数 , 默认添加空字符 '\0'。
  • s2.push_back('y') :在字符串末尾再插入一个 'y' , 此时 size 变为 25 + 1 = 26。
  • 若 26 超过当前 capacity , string 会扩容 , capacity 增大;否则 capacity 不变。

4. resize 缩容(不影响尾插的“潜力” , 因为容量还在)
 

s2.resize(5);
cout << s2 << endl;
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
  1. s2.resize(5) :把 s2 的 size 调整为 5。原 size (26)大于 5 , 会删除末尾 26 - 5 = 21 个字符 , s2 内容变为 "12345" , size 是 5。
  2. 注意:resize 缩容时 , 不会改变 capacity (缩容需要重新分配内存 , 代价大 , string 不会自动缩容)。所以后续若要尾插 , 只要新增字符数不超过 capacity - size , 就不需要扩容 , 效率更高。

 尾插相关核心总结:

  1. -push_back 是在字符串末尾插入单个字符 , 每次插入会让 size 加 1。
  2. 若插入前 size 接近 capacity , push_back 会触发扩容(重新分配更大内存 , 复制原数据 , 开销大);若提前用 resize 或 reserve 预留足够容量 , 能减少扩容次数 , 提升尾插效率。
  3. resize 可以先调整字符串大小(相当于批量尾插或删尾) , 后续再用 push_back 精细尾插 , 且能通过控制容量优化性能。

7. insert

在 C++ 的 string 类中 , insert 函数用于在字符串的指定位置插入字符或子字符串 , 它有多种重载形式 , 以下是一些常见的用法:

1. 在指定位置插入单个字符

函数原型:

string& insert (size_t pos, char c);

参数说明:

  • pos :表示要插入字符的位置 , 从 0 开始计数。如果 pos 大于字符串的长度 , 会抛出 out_of_range 异常。
  • c :要插入的字符。

示例:

#include <iostream>
#include <string>int main() 
{std::string str = "hello";str.insert(2, 'w');  // 在索引为 2 的位置(即 'l' 前面)插入字符 'w'std::cout << str << std::endl;  // 输出 "hewllo"return 0;
}

2. 在指定位置插入多个相同字符

函数原型:

string& insert (size_t pos, size_t n, char c);

参数说明:

  1. pos :插入置 , 从 0 开始计数。
  2. n :要插入字符 c 的数量。
  3. c :要插入的字符。

示例:

#include <iostream>
#include <string>int main() 
{std::string str = "world";str.insert(1, 3, '*');  // 在索引为 1 的位置(即 'o' 前面)插入 3 个 '*'std::cout << str << std::endl;  // 输出 "w***orld"return 0;
}

3. 在指定位置插入另一个字符串

函数原型:

string& insert (size_t pos, const string& str);

参数说明:

  1. pos :插入位置 , 从 0 开始计数。
  2. str :要插入的字符串。

示例:

#include <iostream>
#include <string>int main(){std::string str1 = "hello";std::string str2 = " world";str1.insert(5, str2);  // 在 str1 索引为 5 的位置(即末尾)插入 str2std::cout << str1 << std::endl;  // 输出 "hello world"return 0;
}

4. 在指定位置插入另一个字符串的部分内容

函数原型:

string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);

参数说明:

  • pos :在目标字符串中要插入的位置 , 从 0 开始计数。
  • str :源字符串。
  • subpos :在源字符串 str 中开始提取子串的位置 , 从 0 开始计数。
  • sublen :要从源字符串 str 中提取的子串长度。

示例:
 

#include <iostream>
#include <string>int main() 
{std::string str1 = "hello";std::string str2 = " world, nice to meet you";// 在 str1 索引为 5 的位置(即末尾)插入 str2 中从索引 6 开始,长度为 4 的子串str1.insert(5, str2, 6, 4); std::cout << str1 << std::endl;  // 输出 "hello nice"return 0;
}

5. 在指定位置插入C风格字符串(以 '\0' 结尾的字符数组)

函数原型:

string& insert (size_t pos, const char* s);

参数说明:

  1. pos :插入位置 , 从 0 开始计数。
  2. s :指向 C 风格字符串的指针。

示例:

#include <iostream>
#include <string>int main() 
{std::string str = "hello";const char* cstr = " world";str.insert(5, cstr);  // 在 str 索引为 5 的位置(即末尾)插入 cstr 指向的 C 风格字符串std::cout << str << std::endl;  // 输出 "hello world"return 0;
}

6. 在指定位置插入 C 风格字符串的部分内容

函数原型:

string& insert (size_t pos, const char* s, size_t n);

参数说明:

  1. pos :插入位置 , 从 0 开始计数。
  2. s :指向 C 风格字符串的指针。
  3. n :要从 C 风格字符串 s 中插入的字符数量。

示例:

#include <iostream>
#include <string>int main(){std::string str = "hello";const char* cstr = " world, nice to meet you";str.insert(5, cstr, 6);  // 在 str 索引为 5 的位置(即末尾)插入 cstr 前 6 个字符std::cout << str << std::endl;  // 输出 "hello world"return 0;
}

insert 函数在处理字符串拼接 , 修改等操作时非常灵活 , 合理使用可以高效地对字符串进行各种编辑。

7. 缺点

  • 底层数据挪动导致效率问题:
  1. C++ 中 string 类的 insert 函数在执行插入操作时 , 如果插入位置之后还有数据 , 那么从插入点开始 , 后面的所有字符都需要向后移动 , 为新插入的字符或子字符串腾出空间。而且当插入导致字符串长度超过当前已分配的容量(capacity)时 , 还会触发内存重新分配 , 要把原字符串数据复制到新的内存空间 , 这进一步加大了开销 , 所以很容易导致效率低下。
  •  时间复杂度 : 假设要在长度为 n 的字符串中插入长度为 m 的子字符串 , 通常情况下:
  1. 普通插入且不涉及扩容:当不发生容量重新分配时 , 在字符串中间插入数据 , 需要将插入位置之后的字符依次向后挪动 , 时间复杂度为 O(n) 。因为最坏情况下 , 需要移动插入位置之后的所有字符 , 移动次数与原字符串长度相关 。
  2. 插入导致扩容:如果插入操作导致了内存重新分配 , 除了移动原有字符 , 还涉及新内存的分配和旧数据的整体复制 , 时间复杂度会达到 O(n + m) , 这里 n 是原字符串长度 , m 是要插入的子字符串长度。
  • 不过 , 在一些特殊场景 , 比如在字符串末尾插入(不涉及数据挪动 , 仅增加长度) , 时间复杂度可能会是 O(m) , 但这属于比较理想的情况 , 总体来说 , insert 函数在常规插入操作下时间复杂度较高 , 存在效率瓶颈。

8. erase

在 C++ 中 , erase 是 string  类(以及容器如 vector , list 等)提供的成员函数 , 用于删除字符串中的字符或子串
 
erase 的常见用法 : string 的 erase 主要有三种重载形式 , 适用于不同的删除需求:

1. 删除指定位置的单个字符

函数原型:(C++11 前返回 void , C++11 后返回指向删除位置下一个字符的迭代器)

 iterator erase(iterator pos); 

功能:删除迭代器  pos  指向的字符。


示例:

#include <iostream>
#include <string>
using namespace std;int main()
{string str = "hello";// 删除索引为 1 的字符('e')str.erase(str.begin() + 1); cout << str << endl; // 输出 "hllo"return 0;
}

2. 从指定位置删除指定长度的子串

函数原型:

 string& erase(size_t pos = 0, size_t count = npos); 
  • pos 是起始位置 
  • count 是要删除的字符数;
  • 若 count 超过剩余字符数 , 则删除到字符串末尾

功能:从索引 pos 开始 , 删除 count 个字符。


示例:

string str = "abcdefg";
str.erase(2, 3); // 从索引 2 开始删除 3 个字符('c','d','e')
cout << str << endl; // 输出 "abfg"

3. 删除迭代器范围 [first, last) 内的字符

函数原型:

 iterator erase(iterator first, iterator last); 
  • 返回指向 last 位置的迭代器

功能:删除  [first, last)  范围内的所有字符(包含 first , 不包含 last)。


示例

string str = "123456";
// 删除从第 2 个字符到第 5 个字符(即 '2','3','4')
str.erase(str.begin() + 1, str.begin() + 4); 
cout << str << endl; // 输出 "156"

4. 底层原理与效率分析

  • 底层数据挪动:
  1. 与 insert 类似 , erase 也会导致删除位置后的字符向前挪动(覆盖被删除的部分) , 以保证字符串的连续性。例如 , 删除字符串中间的字符后 , 其右侧的所有字符都需要向左移动 , 填补空缺。
  • 时间复杂度:假设字符串长度为 n , 删除 k 个字符:
  1. 若删除位置在中间 , 需要挪动 n - pos - k 个字符 , 时间复杂度为 O(n)(与字符串长度成正比)。
  2. 若删除末尾字符(如 erase(str.size() - 1, 1)) , 无需挪动数据 , 时间复杂度为 O(1)(仅修改长度)。
  3. 内存分配:
  4. erase 不会主动释放字符串的容量( capacity ) , 只会减少长度( size )。若需释放多余内存 , 可结合 shrink_to_fit()(C++11 后)使用。

5. 使用注意事项

1. 边界检查:pos不能超过字符串的长度( size()) , 否则会触发未定义行为(通常是程序崩溃)。建议使用前判断:

if (pos < str.size()) 
{str.erase(pos, count);
}

2. 迭代器失效:
执行 erase 后 , 指向被删除字符及之后的迭代器会失效 , 需通过返回值获取新的有效迭代器:

auto it = str.begin();
while (it != str.end()) 
{if (*it == 'a') {it = str.erase(it); // 用返回值更新迭代器} else {++it;}
}

3. 与其他函数配合:
常与 find 结合 , 删除指定子串。例如 , 删除字符串中第一个出现的 "ab":

size_t pos = str.find("ab");
if (pos != string::npos) 
{str.erase(pos, 2); // "ab" 长度为 2
}

6. 总结:

erase 是 C++ 中处理字符串删除操作的核心函数 , 支持按位置 , 长度或迭代器范围删除 , 但其效率受底层数据挪动影响(中间删除为 O(n) , 末尾删除为 O(1))。使用时需注意迭代器失效和边界检查 , 常与 find 配合处理复杂删除需求。

9. 总结:

本文详细介绍了C++中string类的常用操作及其底层原理。主要内容包括:size()和length()的功能一致性;size与capacity的区别,前者表示实际字符数,后者代表内存容量;reserve()用于预分配空间提升效率;clear()清空内容但不释放内存;shrink_to_fit()释放多余内存;resize()调整字符串大小;insert()的各种插入操作及其效率问题;erase()的多种删除方式及其时间复杂度。这些操作涉及字符串的内存管理 , 扩容机制和性能优化 , 是高效使用string类的基础。

感谢大家的观看!

http://www.dtcms.com/a/341162.html

相关文章:

  • Clonezilla live 再生龙还原系统各个版本的不同
  • Sklearn 机器学习 房价预估 拆分训练集和测试集
  • Pydantic介绍(基于Python类型注解的数据验证和解析库)(BaseModel、校验邮箱校验EmailStr、BaseSettings)
  • SeaweedFS深度解析(五):裸金属集群部署(上)
  • Java 集合超详细教程
  • 循环神经网络(RNN)、LSTM 与 GRU (一)
  • 基于深度学习的订单簿异常交易检测与短期价格影响分析
  • 【深度学习】PyTorch中间层特征提取与可视化完整教程:从零开始掌握Hook机制与特征热力图
  • lua入门以及在Redis中的应用
  • 【ElasticSearch实用篇-03】QueryDsl高阶用法以及缓存机制
  • Java程序启动慢,DNS解析超时
  • 基于STM32的APP遥控视频水泵小车设计
  • K8S-Pod资源对象——标签
  • 【AI学习100天】Day08 使用Kimi每天问100个问题
  • 【指纹浏览器系列-绕过cdp检测】
  • 数据预处理:机器学习的 “数据整容术”
  • nginx-下载功能-状态统计-访问控制
  • 【数据结构】线性表——顺序表
  • 循环神经网络(RNN, Recurrent Neural Network)
  • Effective C++ 条款52:写了placement new也要写placement delete
  • 使用acme.sh自动申请AC证书,并配置自动续期,而且解决华为云支持问题,永久免费自动续期!
  • Spring Boot 定时任务与 xxl-job 灵活切换方案
  • 层在init中只为创建线性层,forward的对线性层中间加非线性运算。且分层定义是为了把原本一长个代码的初始化和运算放到一个组合中。
  • B站 韩顺平 笔记 (Day 24)
  • C++ std::optional 深度解析与实践指南
  • 当 AI 开始 “理解” 情绪:情感计算如何重塑人机交互的边界
  • linux报permission denied问题
  • Advanced Math Math Analysis |01 Limits, Continuous
  • uniapp打包成h5,本地服务器运行,路径报错问题
  • PyTorch API 4