一、string类相关博文
- 对于C++中string类的学习,为避免文章过长显得冗杂,我分成以下三个部分进行总结(可直接点击下方链接进行查看):
二、构造
- string为了符合C语言的字符串规范,它不仅存字符串,还在末尾存了
\0
,即使是空字符串,它也有存\0
;- size()只记录string的有效字符数,不记录
\0
- size()只记录string的有效字符数,不记录
string::npos
:存的是size_t npos = -1
;- -1以补码的方式存储,就是11…1(32个1),给了有符号int -1给了无符号size_t,就成为了整型最大值2^32-1=4294967295(42亿9千万)
- string构造函数部分给出的接口如下
- string构造示例:
string s1;//①无参构造(🔺)
string s2(s1);//②拷贝构造(🔺)
string s3("hello world!!!");//③
string s4(s3, 6, 15);//拷贝s3,从其下标为6处(w)开始==>world!!!
string s4(s3, 6);// 若第三个参数len大于后面字符长度或没传参,就拷贝到结尾
string s5 ("hello world!!!");//④带参构造(🔺)
string s5 = "hello world!!!";//也能用赋值(用到了隐式类型转换)
string s6("hello",3);//⑤s6="hel"(很少用到,不如直接少些几个字符)
string s7(100, 'x');//⑥用100个x初始化
- ⑦涉及迭代器(见本文后部分:五、迭代器)
三、析构
- 参考文档内容:string::~string - C++ Reference (cplusplus.com)
- 出了作用域会自动调用析构,我们不用怎么管,但是要懂得析构在被实现:
- 析构要把指向的空间给释放掉,指向的空间和顺序表一样在堆上
- 为什么在堆上?
- ==> 因为我们需要往string中插入字符,若空间不足则需要扩容,而只有堆上的空间才能很好地满足我们的扩容需求
四、赋值
- 参考文档内容:string::operator= - C++ Reference (cplusplus.com)
- string赋值提供的接口如下:
- 三种赋值(由于写的早,规范性不足,后两种没必要也用的很少),这三个都构成函数重载
- 赋值示例如下:
string s1;
string s2 = "hello world!!!"; // 构造+拷贝构造 ->优化 -> 直接构造
//赋值①:直接赋值(正常任何一个类都要支持,不写也是有默认的赋值重载)
s1 = s2;
//赋值②:字符串赋值
s1 = "xxxx"; //s1是已经存在对象,调的是赋值,不是构造
//赋值③:字符赋值
s1 = 'y';
五、迭代器
-
iterator
即迭代器的意思。iterator
像指针一样的类型,有可能就是指针,也有可能不是指针,但它用法像指针一样。
-
任何一个数据结构的迭代器都是在其内部里的,所以迭代器属于其类域
- 访问string的迭代器:
string::iterator
- 访问vector的迭代器:
vector<int>::iterator
(vector中显示实例化,内部存储数据类型为int) - 访问list的迭代器:
list<int>::iterator
- 访问string的迭代器:
-
迭代器的重要性:
iterator
是所有容器通用访问方式,且用法类似- string、vector等(这类底层物理空间是连续数组的)不喜欢用迭代器,因为
[]
更好用,可读可写;但list/map/set…只能用迭代器访问
- string、vector等(这类底层物理空间是连续数组的)不喜欢用迭代器,因为
-
四种迭代器类型
- 正向迭代器:iterator/ const_iterator
- 反向迭代器:reverse_iterator / const_reverse_iterator
1、正向迭代器
- 总体是一个左闭右开的区间:
[begin,end)
- string的正向迭代器示例
void test_string1()
{
string s("hello");
string::iterator it = s.begin();
while (it != s.end())//分析点①
{
(*it)++;
cout << *it << " ";
++it;
}
cout << endl;
/*for (auto ch : s)//分析点②
{
ch++;
cout << ch << " ";
}*/
for (auto& ch : s)//分析点③
{
ch++;
cout << ch << " ";
}
cout << endl;
cout << s << endl;
//遍历list
list<int> lt(10, 1);
list<int>::iterator lit = lt.begin();
while (lit != lt.end())
{
cout << *lit << " ";
++lit;
}
cout << endl;
for (auto e : lt)//分析点④
{
cout << e << " ";
}
cout << endl;
}
-
分析点①==>
iterator
为什么采用!=
来判断,而非<
的原因:string
、vector
此处可用it<s.end()
,因为它们是连续的物理空间,但list
就不行,不支持使用<
- 故,
it != s.end()
是一种通用的写法
-
分析点②~④ ==> 范围for
- 范围for可以自动迭代,自动判断结束==>依次取s中每个字符,赋值给ch
- 所有的容器,都可以采用范围for来遍历
- 范围for事实上并没有什么含金量,其底层就是迭代器(即,分析点②底层最终会转化成分析点①类似写法了)
- 若要修改,则需要采用引用
&
==>即分析点③
2、反向迭代器 rbegin
和rend
- 总体是一个右闭左开的区间:
[begin,end)
- ![[Pasted image 20221113145007.png]]
void test_string3()
{
string s("hello");
string::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
PrintString(s);
}
3、const迭代器
- 一般不会将迭代器const。通常只会发生在传参的过程中,会需要const迭代器
- const迭代器特点:可读不可写
- const迭代器之后还提供了专门的cbegin和cend(但原来的不便删除,故也保留)
//void PrintString(string str)//①拷贝一般不好故会采用引用,但不改变,则又加上const;②或者传入的参数就是const
void PrintString(const string& str)
{
//正向的const迭代器:
//string::const_iterator it = str.begin();//前期建议写这个,认全熟悉以下
auto it = str.begin();//熟悉后就能用auto写了:auto不用写类型,begin返回什么用什么
while (it != str.end())
{
//*it = 'x';
cout << *it << " ";
++it;
}
cout << endl;
//反向的const迭代器:
//auto rit = str.begin();
string::const_reverse_iterator rit = str.rbegin();
while (rit != str.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}