个人主页 : zxctscl
如有转载请先通知
文章目录
- 1. 前言
- 2. 迭代器
- 2.1 反向迭代器
- 2.2 const对象迭代器
- 3. Capacity
- 3.1 size和length
- 3.2 max_size
- 3.3 capacity
- 3.4 clear
- 3.5 shrink_to_fit (了解即可)
- 3.6 reserve
- 3.7 resize
- 4. Element access
- 4.1 operator[]
- 4.2 at
- 5. Modifiers
- 5.1 push_back
- 5.2 append
- 5.3 operator+=
- 5.4 assign(了解即可)
- 5.5 insert
- 5.6 erase
- 5.7 replace
- 5.8 swap
- 6. String operations
- 6.1 c_str
- 6.2 find 和 substr
- 6.3 rfind
- 6.4 compare
- 7. Non-member function overloads
- 7.1 operator+
- 7.2 getline
1. 前言
在之前的博客中初步介绍了string一下: 【C++】string类初步介绍,那么这次来看看它的实现。
2. 迭代器
string类对象的访问及遍历操作:
函数名称 | 功能说明 |
---|---|
operator[] (重点) | 返回pos位置的字符,const string类对象调用 |
begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
rbegin + rend | rbegin反向迭代器以反向开始,end将迭代器返回到末尾 |
2.1 反向迭代器
在前面的一篇中已经提到了前面三个,这次来看看反向迭代器。
reverse_iterator:将给的字符串反向逆置。
void test_string3()
{
string s1("hello,world");
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit <<" ";
rit++;
}
cout << endl;
}
rbegin给rit,rit!=rend,然后加加rit。它本身就是反过来的,所以得用加加。
2.2 const对象迭代器
这里还有const对象的迭代器,他们两个有什么区别呢?
const要用const迭代器(const_iterator),不能修改对象值。
**iterator是可读可写,const_iterator只读。**会根据自己属性去调用。
string::iterator it2 = s1.begin();
while (it2 != s1.end())
{
*it2 += 3;
cout << *it2 << " ";
it2++;
}
cout << endl;
const string s3("hello world");
string::const_iterator it3 = s3.begin();
while (it3 != s3.end())
{
cout << *it3 << " ";
it3++;
}
cout << endl;
rbegin同样有两种。
总共有四种迭代器:正向反向iterator,和正向反向const_iterator。用到最多是正向iterator。
3. Capacity
3.1 size和length
这里的size和length有什么区别呢?
直接用代码来测试一下:
void test_string4()
{
string s1("hello world");
cout << s1.size() << endl;
cout << s1.length() << endl;
}
很显然,它们没有区别。有两个的原因是:string产生得比较早,没有出数据结构规范,在字符串长度取名字时候取的是length,后来stl出来之后,就增加了一个size。length是一个局限的取名,用size更统一。C++是两种都是兼容的。
3.2 max_size
在x86环境下来看看max_size
有多大:
但是不同平台的max_size
可能有所不同。
3.3 capacity
来看看capacity大小:
发现会比size要大
来看看string的扩容机制:
先取string当前的capacity,然后push_back,如果空间不够可能会引起capacity的变换;每次插入前获取新的capacity和旧的相比较看看相不相等,如果不相等,就把新的capacity赋值给旧的,并输出新的capacity。
string s;
size_t sz = s.capacity();
cout << "making s grow:\n";
cout << sz << endl;
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
第一次扩容的是原基础的2倍,剩下的都是1.5倍
与linux的扩容机制是不同的。
来看看linux的扩容机制:
g++扩容是两倍扩:
STL是一个规范,规定功能,没有规定实现细节。
3.4 clear
clear是指的清数据,空间不一定清理:
string s1("hello world");
cout << s1.size() << endl;
cout << s1.length() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
cout << s1.capacity() << endl;
s1.clear();
cout << s1 << endl;
cout << s1.capacity() << endl;
capacity并没有改变。
就算将s1多输入字符,它的的capacity在clear后也不会改变;
3.5 shrink_to_fit (了解即可)
如果想要缩容用就要用shrink_to_fit :
string s1("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
cout << s1.size() << endl;
cout << s1.length() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
cout << s1.capacity() << endl;
cout << s1.size() << endl;
s1.clear();
cout << s1 << endl;
cout << s1.capacity() << endl;
cout << s1.size() << endl;
s1.shrink_to_fit();
cout << s1 << endl;
cout << s1.capacity() << endl;
cout << s1.size() << endl;
这里缩容到15
3.6 reserve
注意区分:
reserve是保留
reverse是反转,翻转
reserve是用来扩容的。
string s;
s.reserve(100);
size_t sz = s.capacity();
cout << "making s grow:\n";
cout << sz << endl;
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
在linux里面:
reserve会不会缩容呢?
来看看代码:
string s1("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.reserve(20);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
reserve是不会缩容的。
reserve比capacity大才扩容。
3.7 resize
resize改变size。
resize有三种情况。
resize从三个角度来对它进行分析:
假设这里size是17,capacity是32
- resize给的比size小,会删除
string s2("hello worldxxxx");
cout << s2.size() << endl;
cout << s2.capacity() << endl;
s2.resize(10);
cout << s2.size() << endl;
cout << s2.capacity() << endl;
- resize给的在size和capacity之间,插入
string s2("hello worldxxxx");
cout << s2.size() << endl;
cout << s2.capacity() << endl << endl;
/*s2.resize(10);*/
s2.resize(20);
cout << s2.size() << endl;
cout << s2.capacity() << endl;
默认插入\0
- resize给的比capacity大,扩容+插入
总之:
所以知道要插入多少数据,就提前开好空间,避免了扩容,提高了效率。
4. Element access
4.1 operator[]
在上一篇博客中已经提过了,有需要可以看看【C++】string类初步介绍
在用[]越界是断言错误:
4.2 at
string s1("hello world");
cout << s1[6] << endl;
cout << s1.at(6)<< endl;
at与[]的越界报错不一样。
用at越界时候报的是非法
5. Modifiers
5.1 push_back
尾插一个字符
想尾插一个字符:
void test_string7()
{
string s1("hello world");
s1.push_back('!');
cout << s1 << endl;
}
5.2 append
append尾插,可以插入一个字符,也可以插入字符串。
string s1("hello world");
/*s1.push_back('!');*/
s1.append("!");
cout << s1 << endl;
s1.append("abcd");
cout << s1 << endl;
一般用得最多的就是:
5.3 operator+=
+=用起来就比较方便
来看个例子:
string s1("hello world");
s1 += ' ';
s1 += "abc";
cout << s1 << endl;
5.4 assign(了解即可)
assign赋值,字符覆盖
可以把当前字符覆盖:
string s1("hello world");
cout << s1 << endl;
s1.assign("xxxxx");
cout << s1 << endl;
5.5 insert
insert都是在当前位置的前面插入
常用的就是:
举个例子:
string s1("hello world");
cout << s1 << endl;
s1.insert(0, "abc");
cout << s1 << endl;
5.6 erase
erase删除
常用:
如果这个内容太短小于npos,就全部删除。
举个例子:
string s1("hello world");
cout << s1 << endl;
/*s1.insert(0, "abc");*/
s1.erase(5,10);
cout << s1 << endl;
erase不给值就直接删空了:
5.7 replace
replace替换
把pos位置,一个字符替换成两个xx:
string s2("hello world");
cout << s2 << endl;
s2.replace(6, 1, "xx");
cout << s2 << endl;
一般结合find()使用:
来看个代码:
string s2("hello world hello abcd");
size_t pos = s2.find(' ');
while (pos != string::npos)
{
s2.replace(pos, 1, "%20");
pos = s2.find(' ');
}
cout << s2 << endl;
insert erase replace要少用,因为基本上都要挪动数据,效率不高。
像替换这里还可以用范围for:
string s3;
for (auto ch : s2)
{
if (ch != ' ')
{
s3 += ch;
}
else
{
s3 += "%20";
}
}
cout << s3 << endl;
5.8 swap
来看一个例子:把空格位置换成20%:
void test_string9()
{
string s2("hello world hello abcd");
string s3;
s3.reserve(s2.size());
for (auto ch : s2)
{
if (ch != ' ')
{
s3 += ch;
}
else
{
s3 += "20%";
}
}
cout << s3 << endl;
s2.swap(s3);
cout << s2 << endl;
}
6. String operations
6.1 c_str
在C语言中有打开文件的操作,在c++里面要打开文件就要用到c_str,让它来兼容C语言。
来个例子:
void test_string10()
{
string s1("hello world");
string filename("test.cpp");
FILE* fout = fopen(filename.c_str(), "r");
}
6.2 find 和 substr
find查找
substr去一个字符串的字串。
如果想要拿到一个文件的后缀,就用find,但要将后缀拷贝下来就得用到substr。
来看看简单的实现:
void test_string10()
{
string s1("file.cpp");
size_t pos1= s1.find('.');
if (pos1 != string::npos)
{
string suffix = s1.substr(pos1);
cout << suffix << endl;
}
else
{
cout << "没有后缀" << endl;
}
}
6.3 rfind
如果查最后一个序列怎么办呢?
用rfind,从后往前找
void test_string10()
{
string s1("file.cpp.tar.zip");
size_t pos1= s1.rfind('.');
if (pos1 != string::npos)
{
string suffix = s1.substr(pos1);
cout << suffix << endl;
}
else
{
cout << "没有后缀" << endl;
}
}
如果给的网站很多怎么按协议,域名,网址分开呢?
协议到:,域名从i+3的位置开始,到第一个/就结束。网址就是剩下的部分
void test_string10()
{
string url1("https://legacy.cplusplus.com/reference/string/string/substr/");
string protocol, domain, uri;//协议,域名,网址
size_t i1 = url1.find(':');
if (i1 != string::npos)
{
protocol = url1.substr(0, i1 - 0);
cout << protocol << endl;
}
size_t i2 = url1.find('/',i1+3);
if (i2 != string::npos)
{
domain = url1.substr(i1+3, i2-(i1+3));
cout << domain << endl;
uri = url1.substr(i2+1);
cout << uri << endl;
}
}
6.4 compare
compare是按照ascii比较
string str1("green apple");
string str2("red apple");
cout << (str1 < str2) << endl;
r的ASCII比g的ASCII小:
7. Non-member function overloads
7.1 operator+
来用代码实现一下:
string ss1 = "xxx";
string ss2 = "yyy";
string ret = ss1 + ss2;
cout << ret << endl;
还支持这样的写法:
string ret1 = ss1 + "yyy";
string ret2 = "yyy"+ss2;
cout << ret1 << endl;
cout << ret2 << endl;
7.2 getline
getline获取一行。
举个例子:获得一个字符串里面最后一个单词的长度
#include<iostream>
#include<string>
using namespace std;
int main()
{
string line;
// 不要使用cin>>line,因为会它遇到空格就结束了
// while(cin>>line)
while (getline(cin, line))
{
size_t pos = line.rfind(' ');
cout << line.size() - pos - 1 << endl;
}
return 0;
}
有问题请指出,大家一起进步!!!