string增删查改-insert-erase-replace与find加substr组合技

string 的增删查改:insert/erase/replace,与 find + substr 的组合技

我的github:(https://github.com/xcx55/ubuntu-linux-project)

感谢各位大佬参观我的github!!

string 最后一块:增删查改 。原则只有一条:"增删查改加遍历 "------这七个字就是 string 的全部。但接口看着几十个,真正高频的就那几个,这篇按"该用哪个"来理,顺带把 erase 那个容易写错的下标约定、以及 find + substr 的经典组合讲透。


一、修改类:到底该用哪个

string 的修改接口一大堆:push_back、append、operator+=、insert、erase、replace、assign、swap。按场景选,别纠结:

场景 用什么
尾插单个字符 push_back / +=
尾插字符串 append / +=
中间插入 insert
头插 insert(0, ...)(要挪数据)
删除 erase
替换区间 replace

选择顺序很简单:尾插用 +=(最直观),中间/头插用 insert,删除一律 erase。

append 的重载很多

append 的重载五花八门:支持字符串、字符、迭代器区间、n 个相同字符......这么多的重载是为了让它在任何手头的数据类型下都能直接用。但日常写:

当真正要尾插一些元素时,一般用 += operator 函数。

可读性最好,也不需要去翻那个重载表。

头插为什么贵

笔记里的那句话很到位:

在第几个字符插入就要挪动第几个字符------这就是头插的成本。

尾插是追加,不用动已有数据;头插要把整个字符串往后挪一位 ,O(n)。所以高频插入场景应该考虑反向写入或者 list------这也是后面学容器选型时的第一个分水岭。

二、erase:下标本身也算一个

erase 的签名是 erase(pos, len),从 pos 下标开始,往后删 len 个字符。

容易错的是 pos 的语义,记牢这句:

erase 从下标 pos 开始,往后删除 npos 个字符;下标本身也算一个。

也就是:

cpp 复制代码
s.erase(0, 1);   // 删下标 0 的字符(pos=0,删 1 个)
s.erase(5, 1);   // 删下标 5 的字符本身
s.erase(0);      // len 缺省为 npos → 删到结尾

len 的缺省值是 npos (即 size_t 的最大值,42 亿)------所以就算想删除所有字符,直接 erase() 传个 pos 就够了,它会一路删到底。

replace 的参数排布也是这个套路:replace(pos, len, str),不同之处是它第三个参数必须是字符串,用新内容替换掉 [pos, pos+len) 这一段。

三、find 与 substr:取文件的真实后缀

这部分是 string 最高频的组合技。

find

find 一般用于查找字符与字符串 ,只有两个参数:(找哪个字符,从哪里开始找 = 0)。

cpp 复制代码
size_t pos = s.find('x');        // 从头找字符
size_t pos = s.find("abc", 5);   // 从下标 5 开始找子串
if (pos == string::npos) { /* 没找到 */ }

找不到返回 npos。用返回值和 npos 比较来判断是否成功,别拿 -1 去比(npos 是无符号的 42 亿)。

substr

substr 函数:从 string 的下标位置开始,截取拷贝一部分出来返回。

签名 substr(pos, len):从 pos 开始,取 len 个字符。返回的是新构造出来的 string(涉及构造 + 拷贝的开销,传返回值时注意)。

那条通用的区间公式

左闭右开就是个数。

cpp 复制代码
// subs 里 j - i 就是长度
s.substr(i, j - i);   // 取 [i, j) 这一段

j - i 直接就是数组长度,这个公式在任何"左闭右开"区间的地方都能用------迭代器、[begin, end)、数组切片,全都一样。

实战:取一个多后缀文件的真实后缀

有些文件名有多个点,比如 test.tar.gz,想要最后那个 gz:

cpp 复制代码
string filename = "test.tar.gz";
size_t pos = filename.rfind('.');              // 反向找最后一个点
string suffix = filename.substr(pos);          // 从这里截到结尾
cout << suffix;                                // ".gz"

find 找第一个、rfind 找最后一个;再配 substr(pos)(len 缺省到结尾),一行搞定。

四、一个意外的收获:c_str 与 fopen

笔记里有个小问题挺有意思:为什么 fopen 可以直接用 .c_str() 的返回值?

cpp 复制代码
string filename = "data.txt";
FILE* fp = fopen(filename.c_str(), "r");

答案就一句:

因为 fopen 函数就是要文件名,也就是字符串;而 string 底层数组固定有 \0 符。

c_str() 返回的是兼容 C 的以 \0 结尾的字符指针 ,因为 string 内部始终在末尾维持一个 \0(这就是第一篇说的"capacity 不计算 \0"的那个 \0)。它存在的全部意义,就是让 C++ 的 string 能随时交还给 C 的那一套 API。

顺带:strcmp、printf("%s")、exec 系列这类只要 const char* 的地方,走的都是这条路。

五、其他接口的边角料

  • 字符串指针与 string 也能比较 :"abc" == s、s < "abc" 都可以,因为有非成员的运算符重载。有了 string 之后基本不需要再用 strcmp。
  • getline(cin, s) :读一整行,遇到空格不停------这是 cin >> s 做不到的事。第三个参数还能指定分隔符。
  • swap:两个 string 交换,底层只交换指针,O(1)。比自己写临时变量强得多。

至此 string 全部讲完:增删查改加遍历 七个字概括全部接口。下一篇把 string 这一个月收个尾(string 的底层模仿实现),然后进 vector。

相关推荐
·present·2 小时前
CSAPP(lecture 3)
c++
别动我齐刘海3 小时前
机器人 C++ / 系统联调补强2——异常处理、Protobuf
linux·c++·人工智能·学习·算法·机器学习·机器人
basketball6163 小时前
C++ 中字符串与常见类型的转换总结
开发语言·c++
等我调个Bug3 小时前
C++智能指针通俗讲解
开发语言·c++
kyle~5 小时前
时间同步 --- PTP 与 Scheduled Action Command
c++·数码相机·机器人·传感器
syagain_zsx7 小时前
WebSocket 教程:websocketpp 写 echo 服务端
linux·网络·c++·websocket·网络协议·工具库
大陈子编程7 小时前
C++ CSP-J 与 CSP-S 题单:洛谷 140 题知识点全覆盖
c++
张小姐的猫8 小时前
【MySQL】数据类型
android·linux·数据库·c++·mysql·adb·php