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。

相关推荐
David猪大卫9 小时前
【C++修炼】map 和 set 的使用
c语言·开发语言·数据结构·c++·经验分享·笔记·学习
HPtiger9 小时前
C++ 入门笔记:从 C 到 C++ 的核心过渡(命名空间、输入输出、缺省参数、重载、引用、内联)
c语言·c++·学习
努力努力再努力wz12 小时前
【Lua 进阶系列】从 C++ 对象到 Lua 变量:一文理解 userdata、Lua 栈与元表绑定机制
c++·junit·lua
慧都产品智能化12 小时前
Qt 表格升级攻略:复用已有编辑器,减少重复开发
c++·qt·编辑器·qt6.3
ShineWinsu12 小时前
对于Qt:QTableWidget常见属性以及QTableWidgetItem类的解析
开发语言·c++·qt·gui·客户端·表格·qtablewidget
ShineWinsu14 小时前
对于Qt:QListWidget常见属性以及QListWidgetItem类的解析
开发语言·c++·qt·客户端·列表·qlistwidget·qlistwidgetitem
坤坤子吖14 小时前
Linux网络编程:自定义应用层协议与 JSON 序列化
linux·服务器·网络·c++·笔记·json
沫璃染墨15 小时前
从零入门计算机网络系列(十八):TCP连接管理详解——从三次握手与四次挥手到TIME_WAIT机制
运维·服务器·网络·c++·网络协议·tcp/ip·tcp
自信1504130575917 小时前
重生之从0开始学习c++之——vector-中篇
c++·学习·算法
ShineWinsu20 小时前
对于Qt:QDial常见属性以及信号的解析
开发语言·c++·qt·gui·客户端·旋钮·qdial