字符串的扩容
扩容是要付出代价的,如果是原地扩容的效率还可以,但如果是异地扩容的话效率会比较低!
- reverse --- 反转;
- reserve --- 保留;
reserve
例如,我知道了当前需要的空间为100,那么我可以通过reserve设置容量,从而避免了持续扩容降低效率;但是设置开辟空间大小为100,真正开辟的空间不一定为100!(与vs对齐等一些机制有关);
void TestPushBackReserve()
{
string s;
s.reserve(100);
size_t sz = s.capacity();
cout << sz << endl;
}
int main()
{
TestPushBackReserve();
return 0;
}
对于上面的代码,默认reserve想要开辟的空间为100,但是实际开辟的大小为111!
但是在g++的编译下,默认reserve申请的空间大小等于capacity!
当我们使用resreve进行缩容的时候:容器可以自由优化,使得capacity > n 即可!
void TestPushBackReserve()
{
string s;
s.reserve(100);
size_t sz = s.capacity();
cout << sz << endl;
// 尝试缩容
s.reserve(10);
sz = s.capacity();
cout << sz << endl;
}
int main()
{
TestPushBackReserve();
return 0;
}
我们发现:当我们想要其缩容到10的时候,但是capacity的大小为15!(在其他情况下编译器也可能出现不缩容的情况!)
底层的判断可能跟有效数据的个数有关,当调用clear清除有效数据的时候,大概率会执行缩容!
resize
作用:将字符串大小调整为n个字符的长度!
- 如果 n 小于当前字符串长度,则当前值将缩短为其前 n 个字符,并删除第 n个字符以外的字符。
- 如果 n 大于当前字符串长度,则通过在末尾插入所需数量的字符来扩展当前内容,以达到 n 的大小。如果指定了 c,则新元素将初始化为 c 的副本,否则,它们是值初始化字符(空字符)。(默认填充的是/0)
reverse是单纯的扩容/缩容,只对capacity进行改变;
但是resize会改变size和capacity!同时对值进行初始化 / 删除有效字符个数!
缩容实际上也是讲原来的空间释放掉!再找一份新的合适的空间!(因此考虑效率问题,编译器一般都不会轻易进行缩容! --- 系统不支持分段释放!)
注意点:
对于operator来说,第二个重载函数的const修饰的是*this,也就是说调用[]的对象的值不能改变!
at
作用:获取一个字符在字符串中的位置,同时,at也支持字符串的修改!与[]的作用一致!
但是两者的差别在于:当访问有效范围之外的时候:
- at会直接报异常;
- []是会进行断言;
int main()
{
//TestPushBackReserve();
string s1("hello world");
s1.at(1) = 'x';
cout << s1 << endl;
//s1.at(15);
s1[15];
return 0;
}
当使用at访问越界元素的时候: 报错!
当使用[]访问越界元素的时候:断言!
assign
将一个string重新赋值,替换原来的内容!
insert
作用:讲其他字符插入到pos位置上的字符的前面;
下面是insert的一些操作:
string s1("hello world");
s1.append("111");
cout << s1 << endl;
s1.insert(0, "xxxxx");
cout << s1 << endl;
s1.insert(5, "world");
cout << s1 << endl;
s1.insert(s1.begin(), 'q');
cout << s1 << endl;
s1.insert(s1.begin() + 10, 10, 'y');
cout << s1 << endl;
insert有效率的问题,因此用的时候需要谨慎对待!
erase
- 从pos位置删除len个字符(包含pos位置上的字符),如果len位置上不写,则默认删除pos之后的所有字符;
- 删除迭代器p位置上的字符;
示例代码:
int main()
{
string s1("hello world");
s1.erase(0, 1); // 头删
cout << s1 << endl;
s1.erase(s1.begin()); // 也是头删
cout << s1 << endl;
return 0;
}
谨慎使用!效率也不是很高!
replace
作用:从pos位置开始,len个位置之后的字符串内容替换为新的内容
应用:
假如说有一个题目: 将一个字符串所有的空格替换为20%
解法如下:
int main()
{
string s2("hello world");
string s3;
for (auto ch : s2)
{
if (ch != ' ')
{
s3 += ch;
}
else
{
s3 += "20%";
}
}
return 0;
}
c_str
作用:获取等效的C语言的字符串(返回一个指向数组的指针,该数组包含了以null结尾的字符序列,表示字符串对象的当前值)
int main()
{
string s2("hello world");
string s3;
for (auto ch : s2)
{
if (ch != ' ')
{
s3 += ch;
}
else
{
s3 += "20%";
}
}
cout << s3 << endl;
cout << typeid(s3).name() << endl;
cout << s3.c_str() << endl;
cout << typeid(s3.c_str()).name() << endl;
return 0;
}
这里的s3是一个类,但是s3.c_str()是一个C语言类型的字符串!
因为两个类型调用cout的实际意义不一样!
- s3调用cout实际上是运算符的重载(流插入),可以使用自定义类型;
- s3.c_str()是一个自定义类型!
c_str()是为了让字符串更好的与一些接口函数进行配合!
有些接口函数的参数必须是C语言类型的(指针!),不能是string类型的,因此此时需要c_str()这个函数!
find
作用:在字符串中查找指定的参数(参数为字符串 / 字符),并返回其下标 --- 返回第一个字符匹配的位置(没有找到返回42亿);
指定pos时,则默认从pos位置之后开始搜索(包含pos位置),没有指定pos则从头开始搜索!
substr
作用:返回一个从对象中提取的子字符串;
子字符串是从pos位置开始,提取长度为len的子字符串,且len位置为空,默认提取到结尾!
小测试:通过使用string中类的接口函数将一个网址分割:
int main()
{
string ur1("https://cplusplus.com/reference/string/string/?kw=string");
// 网站分为: 协议 域名 资源名
size_t pos = ur1.find("://");
string protocal; // 定义协议
if (pos != string::npos)
{
protocal = ur1.substr(0, pos);
}
cout << protocal << endl;
size_t pos2 = ur1.find( '/', pos + 3);
string domain; // 定义域名
string uri; // 定义资源名
if (pos2 != string::npos)
{
domain = ur1.substr(pos + 3, pos2 - pos - 3);
uri = ur1.substr(pos2 + 1);
}
cout << domain << endl;
cout << uri << endl;
return 0;
}
rfind
功能:其功能与find类似,但是区别是rfind是从后往前找的!
指定pos位置的时候,搜索只从pos位置之前开始找,pos位置之后不搜索!
find_first_of
作用:在字符串中搜索所有与参数匹配的所有字符!
// string::find_first_of
#include <iostream> // std::cout
#include <string> // std::string
#include <cstddef> // std::size_t
int main ()
{
std::string str ("Please, replace the vowels in this sentence by asterisks.");
std::size_t found = str.find_first_of("aeiou");
while (found!=std::string::npos)
{
str[found]='*';
found=str.find_first_of("aeiou",found+1);
}
std::cout << str << '\n';
return 0;
}
会将字符串中所有出现的"aeiou"替换为'*';
getline
作用:从is中提取字符串到str中,直到找到分隔符(默认不控制就是遇到换行符结束);
采用第一种形式可以控制结束符!
cout/cin默认:多个值默认通过空格或换行来间隔,遇到空格或者换行,就认为当前独立的值已经结束!
因此,如果要读取一个字符串,这个字符串中含有空格,那么使用cin/cout是读取不到的!
此时剩下的T还在缓冲区当中!
计算机显示的是字符,但是底层在计算机中存储的是ASCII的值;
ASCII码表无法显示汉语!
- gbk(中文编写)
- unicode(万维码)
一般情况下,两个字符编写一个汉字;
不同的string类型是为了更好的适应各个国家语言的文字!