string
由于string创始初期没有参照导致的冗余问题,这里没办法完全的进行的总结,所以只会列出一些常用的。
string类
string是表示字符串的字符串类
这里出现这么多不同的string的原因是编码表的不同。string => UTF-8、u16string => UTF-16、u32string => UTF-32。
该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
不能操作多字节或者变长字符的序列。
string类构造
int main()
{
string s1;//默认构造1
string s2("hello world!");//拷贝构造2
string s3 = "hello world!";//const char*隐式类型转换为string 先构造再拷贝构造被优化成拷贝构造 4
string s4(s3, 6, 3);//substring 3
cout << s4 << endl;
string s5(s3, 6, 13);//太少的话取到末尾
cout << s5 << endl;
string s6(s3, 6);//不给的话,自动赋值-1,32亿9千万,极值
cout << s6 << endl;
string s7(s2, 3);//对s2的第三个字符开始向后复制 5
string s8(5,'x');//在s8中填充5个x 6
}
string类对象的容量操作
size和length
size 和length 功能是重叠的,原因是string过早,有些冗余。一般都使用size!
max_size
极值跟系统资源有关系,所以并没有什么实际用处。
append
添加进入一个字符串,一般不用push_back 和append ,而是使用 operator+= 。
小总结下size、capacity、append、operator+=
在string尾部追加字符时,s.push_back( c ) / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
int main()
{
string s1("hello world");
cout << s1.size() << endl;
cout << s1.length() << endl;//俩者并无差别,发展历程length先出现
cout << s1.max_size() << endl;//最大申请数量,意义不大
cout << s1.capacity() << endl;//实际上是16字符,但是\0不计入
s1.push_back(' ');
s1.push_back('!');
cout << s1 << endl;
s1.append("yes");
cout << s1 << endl;
cout << "_______________________" << endl;
s1 += ' ';
s1 += '!';
s1 += "yesyes";//实际我们使用+=多一点
cout << s1 << endl;
return 0;
}
这里需要注意s1.capacity是15个字节,但是后面是有一个’\0’,capacity并没有将’\0’算入其中,所以实际上是16个字节。
至于为什么是16字节,这与编译器的底层逻辑相关,不同编译器的大小不同,在后面附录1会详细讲。
resize
resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_tn, char c)用字符c来填充多出的元素空间。
注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。resize 是 扩容+初始化,如果数量比size小,那么会删除数据,但是不会修改capacity!
因为缩容是不支持原地缩容,都是异地缩容,由于缩容需要释放空间,所以会先开辟新空间,然后拷贝,再释放空间,会产生很大的资源消耗,所以一般不会进行缩容
int main()
{
// 扩容
string s1("hello world");
s1.reserve(100);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
// 扩容+初始化
string s2("hello world");
s2.resize(100, 'x');
cout << s2.size() << endl;
cout << s2.capacity() << endl;
// 比size小,删除数据,保留前5个
s2.resize(5);//缩容,但是不建议使用缩容,因为缩容是异地缩容
cout << s2.size() << endl;
cout << s2.capacity() << endl;
return 0;
}
reserve
前提:知道需要多少空间,可以帮助我们提前开空间,减少扩容,提升效率。
对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
int main()
{
// 观察扩容情况 -- 1.5倍扩容
string s;
size_t sz = s.capacity();
cout << "making s grow:\n";
cout << "capacity changed: " << sz << '\n';
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
初识迭代器
附录
1. vs下string结构的说明(解释前文为什么capacity是16而不是别的)
vs下string的结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放
当字符串长度大于等于16时,从堆上开辟空间
class string//猜测是这样子的
{
private:
char* _ptr;//如果小于16字节,那么存在在_buf中,但如果大于就会用_ptr指堆开辟的空间
char _buf[16];
size_t _size;
size_t _capacity;
};
一个指针+16字节空间+1个size_t字段保存字符串长度+1个size_t字段保存从堆上开辟空间总的容量
4+16+4+4=28