前文
C语言中,字符串是以’\0’结尾的字符集合。C标准库提供了一系列关于字符串的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。至于C++选择了string类而不是C语言中的字符串库函数,在本章最后揭晓。
一、标准库中的string类
在使用string类过程中,必须包括#include头文件以及using namespace std
。string类对象支持直接使用cin和cout进行输入和输出。
- string表示字符串类,字符串表示字符序列的类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
- string在底层实际为basic_string模板类的别名,typedef basic_stringstring
- string类是使用char,即作为它的字符类型,使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数
- 不能操作多字节或者变长字符的序列
接下来接收string类的常用接口
二、string类对象的常见构造
(constructor)函数名称 | 功能说明 |
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
2.1 string()
string str;
功能:构造空string类对象,其中不存在成员对象
2.2 string(const char* s)
int main() { //第一种写法,清晰明了 const char* s = "hello world"; string str1(s);// //第二种写法,比较简洁,常使用 string str2("hello world"); return 0; }
功能:使用C-string构造string类对象。在非空字符串中,从s指向位置拷贝一份字符串。
2.3 string(size_t,char c)
int main() { string str1(5, 'x'); cout << str1 << endl;//xxxxx return 0; }
功能:string类对象初始化n个字符c。从C-string的n个连续字符拷贝填充string类对象。
2.4 string(const string& s)
int main() { string str1("hello world"); string str2(str1);//拷贝构造str1 return 0; }
2.5 string(const string& str,size_t pos,size_t len = npos)
int main() { string str1("helloo world"); string str2(str1, 5, 6); cout << str2 << endl; return 0; }
功能:从str中pos指向位置先后拷贝len长度字符。出现两种结果:拷贝到str最后一个字符或没有达到最后一个字符完成拷贝。
说明:第三个参数len类型为size_t,而缺省值npos == -1导致了npos为最大值128(涉及到编码那块)。对于当没有明确len数值,默认是从pos位置拷贝字符串到最后一个字符。
三、string类对象的容量操作
函数名称 | 功能说明 |
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串释放为空串,是返回true,否则返回false |
clear (重点) | 清空有效字符 |
reserve (重点) | 为字符串预留空间 |
resize (重点) | 将有效字符的个数该成n个,多出的空间用字符c填充 |
3.1 Size与length
int main() { string str1("hello world"); cout << str1.size() << endl;//11 cout << str1.length() << endl;//11 return 0; }
功能:返回字符串有效字符长度。
3.1.1 关于size与length相关问题
- 至于出现两个功能类似接口的原因:
由于当时string只考虑字符串,同时使用length表示字符串长度是最合理的。但是这样没有考虑到其他类型,导致具有局限性,在树形结构等数据结构情况中不太适合length表示元素大小,STL添加size表示元素大小。length合理,size统一更规范。
- 为什么不删除length,只保留size呢?或者在string容器中只存在length表示大小呢?
在语言中库,一般遵守向前兼容,只错不能改。如果修改会导致之前代码就编译失败,对此不能删除length。其他容器都有size,就你string容器没有,是不是有点不太合适呀。
3.2 capacity
int main() { string str1("hello world"); cout << str1.size() << endl; cout << str1.capacity() << endl; return 0; } 输出结果:11 15
功能:返回空间总大小,一般情况下capacity返回大小中不包含’\0’
3.2.1 capacity返回值比size大
在C++中,std::string底层属于动态数组,数组大小是不固定,根据实际需要进行调正。由于经常性出现频繁插入字符的清空,只存在size情况下,会导致频繁地向系统申请空间,性能降低。
capacity可以有效地解决这问题,直接申请大于size空间大小,避免在每次追加字符中重新分配内存,直接使用capacity空间,减少向系统申请内存次数,提高性能。
3.2.2 capacity扩容机制
在C++中,std::string类中向字符串添加字符。如果出现容量不足去容纳新字符,会自动扩容(不需要手动扩容)。扩容的逻辑通常按照某种策略增加容量,具体实现会跟编译器和指标因子的不同有所差异。虽然string扩容机制没有明确的规定细节,但是不会影响功能。
- VS:扩容机制是第一次扩容到原来空间的两倍左右,之后则扩容当前空间的1.5倍
- GCC:扩容机制是以当前空间的两倍
3.3 empty
int main() { string str1; if (str1.empty())//判断释放为空 cout << "为空" << endl; else cout << "非空" << endl; return 0; }
功能:检测字符串是否释放为空,是空返回true,否则返回false
3.4 clear
int main() { string str1("hello world"); cout << str1.size() << endl; cout << str1.capacity() << endl; str1.clear();//清空有效字符 cout << str1.size() << endl; cout << str1.capacity() << endl; return 0; }
功能:清空string有效字符资源,不改变底层空间大小。影响有效元素size,不会影响空间容量大小capacity
3.5 shrink_to_fit
int main() { string str("hello world"); cout << str.size() << endl; cout << str.capacity() << endl; cout << endl; str.resize(100); cout << str.size() << endl; cout << str.capacity() << endl; cout << endl; str.shrink_to_fit(); cout << str.size() << endl; cout << str.capacity() << endl; cout << endl; return 0; }
功能:向系统请求字符串缩容到适合大小,但是该函数对于字符串的长度和内容是没有影响的
如果使用shrink_to_fit后,容量没有发生改变,可能字符串对象可能已经使用内存管理策略去避免频繁的内存分配和释放。
3.6 reserve(重要)
int main() { string str1; cout << str1.capacity() << endl;//15 str1.reserve(100); cout << str1.capacity() << endl;//111 string str2(10, 'x'); cout << str2.capacity() << endl;//10 str2.reserve(); cout << str2.capacity() << endl;//10 return 0; }
功能:向系统申请预留空间,属于手动扩容
3.6.1 关于reserve与扩容问题
- 编译器会根据capacity容量自动扩容,那么为什么还需要reserve实现手段扩容呢?
- 理由:扩容是需要付出代价的,如果是异地扩容,付出代价更大,需要进行空间开辟和数据拷贝。
- 如果事先知道所需要的空间大小,使用reverse开辟足够使用的空间,减少频繁对内存的重分配,就算后期出现空间不足,也有自动扩容的机制,不需要担心大小是固定的。虽然自动扩容可以解决容量不足的情况,但是手段扩容可以减少频繁自动扩容的代价,属于一种优化手段。
- reverse要求100个字节空间,但却开辟了111个字节空间呢?
- 理由:在不同编译器下机制是不同的,但是确保了至少满足所需空间。有些编译器开辟多个空间,是对reserve开辟的空间进行了二次开辟,可以灵活调用内存空间分配,在后继需要小空间,避免扩容。
- reserve参数部分小于当前空间大小,提出申请空间请求,但是空间大小并没有发生改变
- 理由:reserve进行扩容必须参数部分比当前空间大,才会改变string的底层空间总大小,否则就是无效扩容。
3.7 resize(重要)
功能:改变字符串的实际长度
3.7.1 resize改变字符串的实际长度有三种情况
第一种:字符串变短(n>size)
int main() { string str1("hello world");//长度为11 cout << str1.size() << endl;//11 cout << str1.capacity() << endl;//15 str1.resize(2); cout << str1 << endl; cout << str1.size() << endl;//2 cout << str1.capacity() << endl;//15 return 0; }
第二种:字符串在容量内变长(capacity>=n>size)
int main() { string str1("hello world");//长度为11 cout << str1.size() << endl;//11 cout << str1.capacity() << endl;//15 str1.resize(13); cout << str1 << endl; cout << str1.size() << endl;//13 cout << str1.capacity() << endl;//15 return 0; }
如果需要保留字符串前几个字符(不包括‘\0’),可以使用这个接口。
第三种:字符串修改长度超出容量(n>capcity)
int main() { string str1("hello world");//长度为11 cout << str1.size() << endl;//11 cout << str1.capacity() << endl;//15 str1.resize(50); cout << str1 << endl; cout << str1.size() << endl;//50 cout << str1.capacity() << endl;//63 return 0; }
当resize修改长度超过capacity,capacity会进行自动扩容。至于最后capacity的值为什么不是50,在reserve中解释了不同编译器扩容机制是不同的。
resize有两个函数重载:resize(size_t n)与resize(size_t n,char c),功能都是将字符串中有效字符个数改变到n个。
不同点:当字符个数增多时
- resize(n):用’\0’来填充都出的元素空间
- resize(size_t n,char c):用字符c来填充多出的元素空间
虽然resize的功能很丰富,但是reserve比较多使用,可以提前开好空间,避免频繁扩容,提高了性能。
【C++】C++ STL 探索:String的使用与理解(二)https://developer.aliyun.com/article/1617332