【C++】string进一步介绍

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:将给的字符串反向逆置。

代码语言:javascript
复制
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只读。**会根据自己属性去调用。

代码语言:javascript
复制
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有什么区别呢? 直接用代码来测试一下:

代码语言:javascript
复制
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。

代码语言:javascript
复制
    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是指的清数据,空间不一定清理:

代码语言:javascript
复制
string s1("hello world");
	cout << s1.size() << endl;
	cout << s1.length() << endl;
	cout << s1.capacity() << endl;
cout &lt;&lt; s1 &lt;&lt; endl;
cout &lt;&lt; s1.capacity() &lt;&lt; endl;

s1.clear();
cout &lt;&lt; s1 &lt;&lt; endl;
cout &lt;&lt; s1.capacity() &lt;&lt; endl;</code></pre></div></div><p>capacity并没有改变。

就算将s1多输入字符,它的的capacity在clear后也不会改变;

3.5 shrink_to_fit (了解即可)

如果想要缩容用就要用shrink_to_fit :

代码语言:javascript
复制
string s1("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
	cout << s1.size() << endl;
	cout << s1.length() << endl;
	cout << s1.capacity() << endl;
cout &lt;&lt; s1 &lt;&lt; endl;
cout &lt;&lt; s1.capacity() &lt;&lt; endl;
cout &lt;&lt; s1.size() &lt;&lt; endl;

s1.clear();
cout &lt;&lt; s1 &lt;&lt; endl;
cout &lt;&lt; s1.capacity() &lt;&lt; endl;
cout &lt;&lt; s1.size() &lt;&lt; endl;

s1.shrink_to_fit();
cout &lt;&lt; s1 &lt;&lt; endl;
cout &lt;&lt; s1.capacity() &lt;&lt; endl;
cout &lt;&lt; s1.size() &lt;&lt; endl;</code></pre></div></div><p>这里缩容到15

3.6 reserve

注意区分: reserve是保留 reverse是反转,翻转

reserve是用来扩容的。

代码语言:javascript
复制
string s;
	s.reserve(100);
	size_t sz = s.capacity();
	cout << "making s grow:\n";
	cout << sz << endl;
for (int i = 0; i &lt; 100; ++i)
{
	s.push_back(&#39;c&#39;);
	if (sz != s.capacity())
	{
		sz = s.capacity();
		cout &lt;&lt; &#34;capacity changed: &#34; &lt;&lt; sz &lt;&lt; &#39;\n&#39;;
	}
}</code></pre></div></div><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722870094557316508.png" /></div></div></div></figure><p>

在linux里面:

reserve会不会缩容呢? 来看看代码:

代码语言:javascript
复制
string s1("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
s1.reserve(20);
cout &lt;&lt; s1.size() &lt;&lt; endl;
cout &lt;&lt; s1.capacity() &lt;&lt; endl;</code></pre></div></div><p>reserve是不会缩容的。

reserve比capacity大才扩容。

3.7 resize

resize改变size。

resize有三种情况。

resize从三个角度来对它进行分析: 假设这里size是17,capacity是32

  1. resize给的比size小,会删除
代码语言:javascript
复制
string s2("hello worldxxxx");
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;
	s2.resize(10);
cout &lt;&lt; s2.size() &lt;&lt; endl;
cout &lt;&lt; s2.capacity() &lt;&lt; endl;</code></pre></div></div><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722870096218857916.png" /></div></div></div></figure><ol class="ol-level-0"><li>resize给的在size和capacity之间,插入</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">   string s2(&#34;hello worldxxxx&#34;);
cout &lt;&lt; s2.size() &lt;&lt; endl;
cout &lt;&lt; s2.capacity() &lt;&lt; endl &lt;&lt; endl;
/*s2.resize(10);*/
s2.resize(20);

cout &lt;&lt; s2.size() &lt;&lt; endl;
cout &lt;&lt; s2.capacity() &lt;&lt; endl;</code></pre></div></div><p>默认插入\0

  1. resize给的比capacity大,扩容+插入

总之:

所以知道要插入多少数据,就提前开好空间,避免了扩容,提高了效率。

4. Element access

4.1 operator[]

在上一篇博客中已经提过了,有需要可以看看【C++】string类初步介绍

在用[]越界是断言错误:

4.2 at

代码语言:javascript
复制
    string s1("hello world");
	cout << s1[6] << endl;
	cout << s1.at(6)<< endl;

at与[]的越界报错不一样。 用at越界时候报的是非法

5. Modifiers

5.1 push_back

尾插一个字符

想尾插一个字符:

代码语言:javascript
复制
void test_string7()
{
	string s1("hello world");
	s1.push_back('!');
	cout << s1 << endl;
}

5.2 append

append尾插,可以插入一个字符,也可以插入字符串。

代码语言:javascript
复制
    string s1("hello world");
	/*s1.push_back('!');*/
s1.append(&#34;!&#34;);
cout &lt;&lt; s1 &lt;&lt; endl;

s1.append(&#34;abcd&#34;);
cout &lt;&lt; s1 &lt;&lt; endl;</code></pre></div></div><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722870101049524562.png" /></div></div></div></figure><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722870101292480905.png" /></div></div></div></figure><p>

一般用得最多的就是:

5.3 operator+=

+=用起来就比较方便

来看个例子:

代码语言:javascript
复制
   string s1("hello world");
	s1 += ' ';
	s1 += "abc";
	cout << s1 << endl;

5.4 assign(了解即可)

assign赋值,字符覆盖

可以把当前字符覆盖:

代码语言:javascript
复制
	string s1("hello world");
	cout << s1 << endl;
s1.assign(&#34;xxxxx&#34;);
cout &lt;&lt; s1 &lt;&lt; endl;</code></pre></div></div><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722870102799048023.png" /></div></div></div></figure><h4 id="bfsno" name="5.5-insert">5.5 insert</h4><p>insert都是在当前位置的前面插入

常用的就是:

举个例子:

代码语言:javascript
复制
string s1("hello world");
	cout << s1 << endl;
s1.insert(0, &#34;abc&#34;);
cout &lt;&lt; s1 &lt;&lt; endl;</code></pre></div></div><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722870103699076437.png" /></div></div></div></figure><h4 id="9ot1" name="5.6-erase">5.6 erase</h4><p>erase删除

常用:

如果这个内容太短小于npos,就全部删除。

举个例子:

代码语言:javascript
复制
string s1("hello world");
	cout << s1 << endl;
/*s1.insert(0, &#34;abc&#34;);*/

s1.erase(5,10);
cout &lt;&lt; s1 &lt;&lt; endl;</code></pre></div></div><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722870104694790753.png" /></div></div></div></figure><p>

erase不给值就直接删空了:

5.7 replace

replace替换

把pos位置,一个字符替换成两个xx:

代码语言:javascript
复制
    string s2("hello world");
	cout << s2 << endl;
	s2.replace(6, 1, "xx");
	cout << s2 << endl;

一般结合find()使用:

来看个代码:

代码语言:javascript
复制
string s2("hello world hello abcd");
size_t pos = s2.find(&#39; &#39;);
while (pos != string::npos)
{
	s2.replace(pos, 1, &#34;%20&#34;);
	pos = s2.find(&#39; &#39;);
}
cout &lt;&lt; s2 &lt;&lt; endl;</code></pre></div></div><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722870106465802970.png" /></div></div></div></figure><p>

insert erase replace要少用,因为基本上都要挪动数据,效率不高。

像替换这里还可以用范围for:

代码语言:javascript
复制
string s3;
for (auto ch : s2)
{
if (ch != ' ')
{
s3 += ch;
}
else
{
s3 += "%20";
}
}
cout << s3 << endl;

5.8 swap


来看一个例子:把空格位置换成20%:

代码语言:javascript
复制
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语言。

来个例子:

代码语言:javascript
复制
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。 来看看简单的实现:

代码语言:javascript
复制
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,从后往前找

代码语言:javascript
复制
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的位置开始,到第一个/就结束。网址就是剩下的部分

代码语言:javascript
复制
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(&#39;/&#39;,i1+3);
if (i2 != string::npos)
{
	domain = url1.substr(i1+3, i2-(i1+3));
	cout &lt;&lt; domain &lt;&lt; endl;

	uri = url1.substr(i2+1);
	cout &lt;&lt; uri &lt;&lt; endl;
}

}

6.4 compare

compare是按照ascii比较

代码语言:javascript
复制
    string str1("green apple");
	string str2("red apple");
cout &lt;&lt; (str1 &lt; str2) &lt;&lt; endl;</code></pre></div></div><p>r的ASCII比g的ASCII小:

7. Non-member function overloads

7.1 operator+

来用代码实现一下:

代码语言:javascript
复制
    string ss1 = "xxx";
	string ss2 = "yyy";
	string ret = ss1 + ss2;
	cout << ret << endl;

还支持这样的写法:

代码语言:javascript
复制
   string ret1 = ss1 + "yyy";
	string ret2 =  "yyy"+ss2;
	cout << ret1 << endl;
	cout << ret2 << endl;

7.2 getline

getline获取一行。

举个例子:获得一个字符串里面最后一个单词的长度

代码语言:javascript
复制
#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;
}

有问题请指出,大家一起进步!!!