既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
注:string类对象支持直接用cin和cout进行输入和输出,因为重载了流插入>>和流提取<<操作符(后面说)
⚡取字符串前n个(用的非常少)
from sequence (5) string (const char\* s, size_t n);
⚡ 一键初始化
fill (6) string (size_t n, char c);
⚡析构函数(不重要)
析构我们不管,对象出了作用域,自动调用的
⚡赋值重载
string s1;
string s2 = "hello world";//构造+拷贝构造-》优化——直接构造
s1 = s2;
s1 = "xxxxx";
s1 = 'y';
如果现在让我们取实现逆置一个字符串,我们又不能拿的到str(私有),何况我们都不知道底层是_str 还是str_ ,不清楚底层实现,这就要引出[]
四.operator[ ]
重载了[]
,使得string类可以像数组一样访问字符。不同的是,数组访问本质是解引用,而这里是调用函数。
它提供了两个版本 ——
其大概的底层实现如下:
char& operator[](size_t pos)//传引用返回,别名
{
assert(pos<_size);
return _str[pos];
}
operator[]
是传引用返回,返回的是别名,这使得它可读可写。
这里不是为了减少拷贝,而是为了做输出型参数,支持修改返回值
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s("more than words");
// 1.可读
//for (size\_t i = 0; i < s.length(); i++) 二者都是求长度,string比较早,且不适合一些树的结构
for (size_t i = 0; i < s.size(); i++)
{
//等价于cout << s.operator[](i) << " " <<;
cout << s[i] << ' ' ;
}
cout << endl;
// 2.可写
for (size_t i = 0; i < s.size(); i++)
{
s[i] += 1;
}
cout << s << endl;
return 0;
}
对于const修饰的就不能修改了
const string s2("hello world");
for (size_t i = 0; i < s1.size(); ++i)
{
s2[i]++;//报错了
}
下面两个函数功能一致,(at的存在有历史原因)只不过二者检查越界的方式不同,推荐使用[]
——
五、Capacity 容量操作
⚡size vs length
➰字符串中有效字符长度,即不包含最后作为结尾标识符的\0
两者底层实现完全一致(length的存在是历史原因),但强烈推荐使用size
. 这是为了和后序各种容器接口保持一致(二叉树你不能用length吧)
capacity
➰ 容量:能存多少个有效字符(注意\0无效字符不算),要记得string类的底层是顺序表结构,初始值是15
⚡resize vs reverse
➰reserve 和 resize 都是改变容量,申请至少n个字符的空间(字符串涉及对齐问题,后续详谈) ,但有所不同 ——
🤞1. resize
- 开空间,并可以对空间初始化
翻译知道
- 如果是将元素个数减少,会把多出
size
的字符抹去,这不挺resize的吗(狗头) - 如果是将元素个数增多,
void resize (size_t n);
,用\0
来填充多出的元素空间,void resize (size_t n, char c);
用字符c
来填充多出的元素空间 - 注:resize在改变元素个数时,如果是将元素个数增多,可能会改变容量的大小;如果是将元素个数size减少,容量不变
void test\_string14()
{
string s1("JDG 总冠军");
s1.resize(5);//size缩小成5,capacity不变
string s2("JDG 总冠军");
s2.resize(100);//填充'\0',size——>100,capacity->111(自动扩容)
string s3("JDG 总冠军");
s3.resize(100,'~');//填充'\0',size——>100,capacity->111(自动扩容)
}
➰ 2. reserve - 开空间。在已知需要多少空间时,调用reserve,可以避免频繁增容的消耗
- 为字符串预留空间,改变容量。当然了不会改变有效元素个数size
- 给reserve的参数n小于string的容量时,是无效请求,并不会改变容量大小
#include<string>
using namespace std;
int main()
{
string s1;
s1.reserve(100); // size - 0,capcacity->111
string s2("more than words");
s2.reserve(5); // capacity和size仍为15
return 0;
}
⚡clear
➰ 清空有效字符,容量不变
⚡empty
➰ 检测字符串是否为空串
六、iterator 迭代器
第二种遍历的方法:迭代器,对于string类,无论正着还是倒着走,[下标]
的方法都足够好用,为什么还要有迭代器?
🤞🤞事实上,迭代器是一种通用的遍历方式,且用法类似,所有容器都可以使用迭代器这种方式去访问修改,而list、map/set不支持[下标]遍历。结论是,对于string类,我们得会用迭代器,但是我们更喜欢用[下标]
🌈正向迭代器
正向迭代器提供了两个函数——
🌈迭代器 iterator是像指针一样的类型,不确定是不是(薛定谔的猫),但它的用法像指针一样, 其区间[ }
左闭右开
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s("more than words");
// 1.可读
string::iterator it = s.begin();
while (it != s.end())
{
cout << \*it << " ";
it++;
}
cout << endl;
// 2.可写
it = s.begin();
while (it != s.end())
{
\*it += 1;
it++;
}
cout << s <<endl;
return 0;
}
- iterator依然提供了两个版本,第二个是const变量
- 关于遍历的时候
!=
能不能改成<=
,可以但是没必要,因为在string的物理空间是连续的,其他容器list等不一定连续。
🌈反向迭代器
也提供了两个成员函数
void test\_string5()
{
string s("hello");
string::reverse_iterator rit = s.rbegin();
//auto rit = s.rbegin();//auto 可以自动推导类型
while (rit != s.rend())
{
cout << \*rit << " ";
rit++;
}
cout << endl;
}
🌈const迭代器
所谓的const迭代器就是针对const的版本嘛
- 普通迭代器可读可写,相当于string类模板中,类型为
T*
- 而const迭代器不可写。这是因为是const成员函数,const修饰this指针指向的内容(相当于string类模板中,类型为
const T*
)
const迭代器也分正向迭代器和反向迭代器,且就是给const对象用
的。这是因为const对象才能调用这里的const成员函数,返回const迭代器,不可写。
使用情况如下:
void test\_string6()
{
string s("hello");
// const正向迭代器 - 可读不可写
string::const_iterator it = s.begin();
while (it != s.end())
{
cout << \*it << " ";
it++;
}
cout << endl;
// const反向迭代器 - 可读不可写
string::const_reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << \*rit << " ";
rit++;
}
cout << endl;
}
传参进func中,s是const对象,自动调用第二个接口,返回的是const_iterator,要用const迭代器类型接收,且不能修改
C++11
为了区分const迭代器和普通迭代器还提供了以下接口,不然调用时容易混淆
🌈范围for遍历
范围for是C++11提供的语法糖🍬,实际上底层编译器会替换成迭代器(反汇编里可以看出) 只能正向遍历
🍬依次取s
中的每个字符,赋值给ch
- 自动迭代
- 自动判断结束
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s("more than words");
for (auto& ch : s)
{
cout << ch << " ";
}
cout << endl;
for (auto& ch : s)
{
ch += 1;
}
cout << s << endl;
return 0;
}
ps:
- 若要修改,auto要加上&。因为
*it
会依次赋值给ch
,ch是*it的拷贝,*it改变不影响ch,所以要加上&
七、Modifiers 修改
🎨追加
+=
最好用也最常用,因为既可以追加字符、也可追加字符串 ,其实底层调用了append
和push_back
void test\_string7()
{
string s("hello");
s.push\_back('-');
s.push\_back('-');
s.append("world");
cout << s << endl;
string str("我来了");
s += '@';//字符
s += str;//字符串
s += "JDG总冠军";
cout << s << endl;
}
其中append的迭代器可以选择性的打印字符串内容(了解即可)
string s("hello");
string str("我来了");
s.append(++str.begin(), --str.end());
string copy(s.begin() + 5, s.end() -5);
下面来研究尾插扩容容量变化 ——
void test\_string8()
{
string s;
size_t sz = s.capacity();
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push\_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
我们可以看见在vs下增容,第一次是两倍,后面是1.5倍的增容
如果提前知道是多少空间,可以调用reserve
预留空间,避免频繁增容的消耗
s.resize(1000 , 'x'); //开空间+初始化
🎨插入和删除
🐋尽量少使用头部的插入和删除,因为要挪动,O(N)效率低
🤞1️⃣小练习:在字符串中空格的地方插入一个%
我的第一想法:遍历字符串,遇到' '
的时候,直接插入
void test\_string9()
{
//在空格的地方插入一个%
string str("JDG NB 总冠军");
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == ' ')
{
str.insert(i, "20%");
}
}
cout << str << endl;
}
但这样有没有问题呢?
那怎么样处理比较好呢?只需要在插入的地方i
额外的+3
,
void test\_string9()
{
//在空格的地方插入一个%
string str("JDG NB 总冠军");
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == ' ')
{
str.insert(i, "20%");
i += 3;
}
}
cout << str << endl;
}
2️⃣练习升级:把字符串中遇到的空格替换成20%
这就要引入erase
:删除字符串中的字符
void test\_string9()
{
//在空格的地方插入一个%
string str("JDG NB 总冠军");
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == ' ')
{
str.insert(i, "20%");
i += 3;
}
}
//再把空格删除掉哦
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == ' ')
{
str.erase(i, 1);
}
}
cout << str << endl;
}
ps:最好不要自行挪动数据,因为[]
会检查下标位置必须要小于size
的,如果真的要挪动,要resize
一下,增加长度
也有一种空间换时间的方法:创建新的串遍历,效率O(N)
void test\_string9()
{
string str("JDG NB 总冠军");
string newstr;
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] != ' ')
{
newstr += str[i];
}
else
{
newstr += "20%";
}
}
cout << newstr << endl;
}
八、String operations 字符串操作
打印字符串,都能打印,但意义不同 ——
🤞前者是string类的流插入运算符的重载,以对象size
为准,size是多少打印多少
后者是以常量字符串对象\0为准,遇到\0
就结束(符合c语言标准)
所以说
\0
不一定是结束标志,在string里会被忽视
主要作用还是与函数接口接合——
string file("test.txt");
FILE\* fout = fopen(s.c\_str(), "w");//打印文件

{
string str("test.cpp");
//找出后缀
size_t pos = str.rfind('.');//面对多后缀的最好用 rfind
if (pos != string::npos)
{
//string buff = str.substr(pos, str.size() - pos);
string buff = str.substr(pos);//因为是取到结束
cout << buff << endl;
}
}
但是这样写有没有说明问题呢?如果后缀是test.cpp.tar.zip
呢?要取最后一个后缀
这样就要将find
——》 rfind
,即可解决
💢解析出网址的这三个部分:协议 - 域名 - 资源
#include<iostream>
#include<string>
using namespace std;
int main()
{
string url = "https://cplusplus.com/reference/string/string/substr/";
size_t pos1 = url.find("://");
if (pos1 == string::npos)
{
cout << "非法字符串" << endl;
return;
}
//取协议
string protocol = url.substr(0, pos1);
cout << protocol << endl;
size_t pos2 = url.find('/', pos1 + 3);
if (pos1 == string::npos)
{
cout << "非法字符串" << endl;
return;
}
string domain = url.substr(pos1 + 3, pos2 - pos1 - 3);//取域名
cout << domain << endl;
string uri = url.substr(pos2 + 1);//取资源
cout << uri << endl;
}
💢find 和find_first_of 区别
find_first_of:只要出现要寻找的串里的任意字符都找出来
吐槽一下:find_first_of
更应该叫find_any_of,但我们要尊重语法
九、非成员函数重载
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
bstr(pos2 + 1);//取资源
cout << uri << endl;
}

#### 💢find 和find\_first\_of 区别

find\_first\_of:只要出现要寻找的**串里的任意字符**都找出来
吐槽一下:`find_first_of` 更应该叫find\_any\_of,但我们要尊重语法

### 九、非成员函数重载
[外链图片转存中...(img-1fXS7efQ-1715796861908)]
[外链图片转存中...(img-R8hkIh3m-1715796861908)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**
**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**
**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618679757)**