5.指定位置的修改操作
1.insert
关于单参数隐式类型转换的问题,请看这篇博客当中的explicit关键字部分的介绍
C++类和对象下(初始化列表,静态成员,explicit关键字,友元)
下面我们来演示一下insert的用法
string s("[hello world]"); s.insert(0, 1, 'w');//从0位置头插一个字符:'w' s.insert(0, "20231122");//从0位置头插一个字符串 cout << s << endl;
string s("[hello world]"); s.insert(2, "0123456789", 3, 4);//在s串的下标为2的位置开始插入一个子串 //这个子串是"0123456789"的从下标为3的位置开始,长度为4的子串:也就是3456 cout << s << endl;//[h3456ello world]
因为insert的插入效率不高(因为string是顺序表,物理空间是连续的,插入一个字符或者删除一个字符会造成大量数据的挪动,因此效率不高)
所以insert能少用就少用
2.erase
下面我们来演示一下:
string s("0123456789"); s.erase(3, 4);//从3号下标位置开始删除4个字符,也就是删除了3456 cout << s << endl; s.erase(2);//默认从2号下标开始的删除所有字符 cout << s << endl; s.erase();//默认删除所有字符 cout << s << endl;
跟insert一样,效率低,能少用就少用
3.replace
其实replace并不好用,因为效率低
频繁挪动数据,偶尔用一次就算了
但是不建议一次性使用很多次,效率会非常低,因为会造成数据频繁重复性地挪动
我们介绍完find之后会结合find跟replace来介绍一个场景
在那里我们将会对replace的低效性,重复性有更深的理解
下面我们先来演示一下replace的用法
string s1 = "ABCDEFGHI"; string s2 = "abcdefghi"; s1.replace(2, 3, s2);//把s1的从下标为2位置开始长度为3的字符替换为字符串s2 //也就是把CDE替换为abcdefghi cout << s1 << endl; s2.replace(s2.begin() + 2, s2.begin() + 4, "------");//把s2的[2,4)区间内的字符(也就是cd)替换为"------" cout << s2 << endl;
6.查找,交换,截取操作
1.find
string s1("abcd 1234 xxxx"); size_t index1 = s1.find("cd", 1);//从1号下标开始查找字符串cd cout << index1 << endl; size_t index2 = s1.find("cdef", 1, 2);//从1号下标开始查找字符串cdef的前2个字符:cd cout << index2 << endl; size_t index3 = s1.find(' ', 1);//从1号下标开始查找字符' ' cout << index3 << endl; //查不到的情况: size_t index4 = s1.find("abcd", 1);//从1号下标开始查找字符串abcd -> 查不到,返回npos cout << index4 << endl;//无符号整形最大值
2.其他跟find相关的函数
用法相同,注意跟find的区别即可
1.rfind
下面的这几个大家知道有这么个函数即可
不常用
2.了解即可
下面是cplusplus网站上的几个用例
大家可以了解一下
//这个功能就是把所有的aeiou都替换为空格 string str("abcdefghigklmnopqrstuvwxyz"); size_t found = str.find_first_of("aeiou"); while (found != std::string::npos) { str[found] = ' '; found = str.find_first_of("aeiou", found + 1); } cout << str << endl;
void SplitFilename(const std::string& str) { std::cout << "Splitting: " << str << '\n'; std::size_t found = str.find_last_of("/\\"); std::cout << " path: " << str.substr(0, found) << '\n'; std::cout << " file: " << str.substr(found + 1) << '\n'; } 注意: 1.C语言中'\'是转义字符 比如'\0','\n'等等,但是'\0','\n'都是一个字节,也就是说被转义字符修饰的字符仍然是一个字符,而不是两个 如果我们想要表示这个字符的话,就要对这个转义字符进行转义 也就是说'\\'这个才是真正的'\' 2.文件路径分隔符 Windows下的文件路径分隔符是'\' Linux下的文件路径分隔符是'/' 而这个SplitFilename函数的作用就是既可以分割Windows下的文件路径,也可以分割/Linux下的文件路径 int main() { std::string str1("/usr/bin/man"); std::string str2("c:\\windows\\winhelp.exe"); SplitFilename(str1); SplitFilename(str2); return 0; }
3.find和replace的应用场景
《剑指offer》上面有这么一道题:
下面我们先使用find和replace来实现一下:
string s("We are happy"); cout << s << endl; size_t pos = s.find(' '); while (pos != string::npos) { s.replace(pos, 1, "%20"); pos = s.find(' '); } cout << s << endl;
下面我们调试看看过程
我们可以看出,每次执行replace,对应空格之后的字符都要向后挪动
这就会导致出现频繁的空格移动
如果是比较长的字符:像是这样的
那这个效率就太低了
大家也可以用计算空格数来扩容后移的方法去做
但是那样还是比较麻烦的
下面介绍一种非常好用的方法:
空间换时间:
string s("We are happy and i am wzs and today is 2023 11 22"); cout << s << endl; string s1; for (auto& e : s) { if (e == ' ') { s1 += "%20"; } else { s1 += e; } } s = s1; cout << s << endl;
效率高,而且非常好实现
4.swap
这个swap有两个,
一个是成员函数
一个是非成员函数
我们常用作为成员函数的那个swap
5.substr
下面我们来演示一下:
6.find和substr的应用场景
find和substr也能够很好地放到一起使用
比方说下面这个场景
对于这个网址来说:
http://www.baidu.com/index.html?name=mo&age=25#dowell
我们想要截取它的
1.协议:http
2.域名:www.baidu.com
3.剩下的这个 index.html?name=mo&age=25#dowell
(包括:端口、路径(虚拟路径)、携带的参数、哈希值)
大家感兴趣的话可以看看这位大佬的文章
这些东西我们以后会介绍的
下面我们回归这个需求,开始实现一下
string s("http://www.baidu.com/index.html?name=mo&age=25#dowell"); string substr1, substr2, substr3; //我们的目标是: //substr1:http //substr2:www.baidu.com //substr3:index.html?name=mo&age=25#dowell size_t pos1 = s.find(':', 0);//从0下标开始出发查找':' substr1 = s.substr(0, pos1 - 0);//[0,pos1):左闭右开的区间:长度是右区间-左区间 也就是pos1-0 size_t pos2 = s.find('/',pos1 + 3);//pos1位置此时是':' 我们下一次要从pos1+3的位置开始查找 也就是第一个'w'的位置 substr2 = s.substr(pos1 + 3, pos2 - (pos1 + 3));//[pos1+3,pos2):这个区间内的子串 substr3 = s.substr(pos2 + 1);//从pos2+1开始一直截取到最后即可 cout << substr1 << endl; cout << substr2 << endl; cout << substr3 << endl;
还有一个场景:
取文件后缀名
这是我随便编的一个文件
string s("filename.cpp.txt.zip"); //它的真实后缀名是zip //我现在就是只想要它的真实后缀名 //你给我放到str1这个string中 size_t pos = s.rfind('.'); string str1 = s.substr(pos); cout << str1 << endl;
7.string类型转为const char*类型
1.c_str
我们在文件操作的时候提到过fopen函数
今天我们想这样去读取一个文件:
这时c_str就排上用场了
这时关于fgetc函数的使用:读取一个文件的内容
关于C语言文件操作的内容,大家可以看我的这一篇博客:
这样我们就成功读取了
2.data
8.非成员函数
1.比较运算符重载
2.+运算符重载
3.getline
要介绍这个getline
我们可以通过一道题目来深刻理解getline的价值
#include <iostream> using namespace std; int main() { string s; cin>>s; size_t pos=s.rfind(' '); //没有找到,返回npos //说明该字符串只有一个单词,返回size即可 if(pos==string::npos) { cout<<s.size()<<endl; } //找到了 //hello nowcoder //pos指向空格 size指向'\0' 最后一个单词是:(pos,size)左开右开区间内的 //长度是右侧-左侧-1 也就是size-pos-1 else { cout<<s.size()-pos-1<<endl; } return 0; }
我们发现,我们输出的答案总是第一个单词的长度,为什么呢?
因为:
string s; getline(cin,s);//getline的用法
#include <iostream> using namespace std; int main() { string s; getline(cin,s);//getline的用法 size_t pos=s.rfind(' '); if(pos==string::npos) { cout<<s.size()<<endl; } else { cout<<s.size()-pos-1<<endl; } return 0; }
4.<< 和 >>
对于流插入和流提取
我们只需要知道可以直接对string容器进行cout和cin即可
9.其他不太重要的函数
1.assign
2.copy
三.揭秘string容器的迭代器
1.string容器迭代器的本质
其实对于string容器来说
我们完全可以使用一个char*指针去自己实现这个迭代器
也就是这样:
typedef char* iterator iterator begin() { return _str; } iterator end() { return _str + _size; }
然后我们就可以使用了
2.iterator迭代器使用
在这里我先模拟实现了string的构造函数,析构函数,还有这个迭代器
关于string的模拟实现,我们以后会单独出一篇博客的
namespace wzs { class string { public: //构造函数 string(const char* str = "") { _size = strlen(str); _capacity = _size; _str = new char[_capacity + 1]; strcpy(_str, str); } //析构函数 ~string() { delete[]_str; _str = nullptr; _size = 0; _capacity = 0; } //迭代器和范围for typedef char* iterator; iterator begin() { return _str; } iterator end() { return _str + _size; } private: char* _str; int _size; int _capacity; }; void test() { string s2 = "hello iterator"; string::iterator it = s2.begin(); while (it != s2.end()) { (*it)++; cout << *it << " "; it++; } cout << endl; for (auto& e : s2) { cout << e << " "; } } } int main() { wzs::test(); return 0; }
然后我们就可以自己去玩这个迭代器了
我们仅仅使用我们的这个string类,就可以实现iterator和范围for了
3.范围for的本质
其实范围for就是迭代器
我们可以通过查看反汇编看到begin()和end()的身影
为什么说范围for式"傻瓜式"的替换呢?
因为我们只有当我们遵守迭代器的规范
把迭代器命名为begin(),end()时才会有用
否则就没用了
比如我现在给它改名为Begin
4.iterator的补充
但是所有的iterator都是指针吗?
并不是这样的
string容器之所以可以使用char*来当作迭代器是因为string容器在物理空间上是连续的
对于那些物理空间并不连续的容器来说,迭代器就不是这么简单了
对于那些容器的迭代器我们以后会说明的
补充最前面的构造函数的最后一个重载版本
了解了string容器的迭代器之后
我们再来谈一下最后一个构造函数的重载版本
这个其实就是传入迭代器区间进行构造
注意:get_allocator以后会介绍
这个get_allocator涉及到适配器的知识,我们以后会介绍的
以上就是C++ 带你吃透string容器的使用的全部内容,希望能对大家有所帮助!