【C++】string_c++string
个人主页:NiKo
C++专栏:C++程序设计
目录
一、标准库中的string类
二、string的遍历
三、string容量
四、string修改
一、标准库中的string类
1、string类
C语言中,字符串是以\'\\0\'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
在使用string类时,必须包含#include头文件以及using namespace std;
#include using namespace std;
2、string的构造方法
string一共有5种构造方法。
- string()
- 无参构造,初始化为空串
string str1; //空串
- string(const string& str)
用str拷贝构造
string str2(\"hello world\"); //用\"hello world\"拷贝构造str2
- string(size_t n,char c)
- 用n个字符c初始化
string str3(4, \'a\'); //用4个字符\'a\'初始化
- string(const char* s,size_t n)
- 用字符串s的前n个字符初始化
string str4(\"hello world\", 5); //用字符串\"hello world\" 前5个字符初始化
- string(const string& str,size_t pos,size_t len=npos)
- 将字符串str,从下标pos位置开始,选取长度为len个的字符,来初始化
- 注:上面的缺省值npos,定义为:size_t npos=-1. npos为最大值,表示不传参数时,会用str中pos位置开始后的所有字符来初始化
string str5(str2, 5, 6); //用str2中,从下标为5位置开始,长度为6的字符串初始化
二、string的遍历
1、auto和范围for(C++11支持)
补充auto的相关语法:
1、在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
int a = 10;auto b = a;auto c = \'a\';auto d = func1(); //auto会自动推导变量的类型auto e; // 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项(必须初始化)cout << typeid(b).name() << endl; //intcout << typeid(c).name() << endl; //charcout << typeid(d).name() << endl; //int
2、用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&;当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
int x = 10;auto y = &x;auto* z = &x; //用auto声明指针类型时,用auto和auto*没有任何区别auto& m = x; //用auto声明引用类型时则必须加&cout << typeid(x).name() << endl; //intcout << typeid(y).name() << endl; //int*cout << typeid(z).name() << endl; //int*auto aa = 1, bb = 2;//当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错auto cc = 3, dd = 4.0;// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
3、auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
// 不能做参数 error:报错void func2(auto a){}// 可以做返回值,但是建议谨慎使用(建议不要使用)auto func3(){return 3;}
4、auto不能直接用来声明数组
// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型auto array[] = { 4, 5, 6 };
5、auto的用武之地std::map dict = { { \"apple\", \"苹果\" },{ \"orange\",\"橙子\" }, {\"pear\",\"梨\"} };//auto的用武之地:在某些情况下,可以减小代码书写的难度,例如迭代器的定义//写法一:std::map::iterator it = dict.begin();//写法二:auto it = dict.begin();
范围for:
对于一个有范围的集合 而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的 for 循环。 for 循环后的括号由冒号 “ : ” 分为两部分:第一部分是范围 内用于迭代的变量,第二部分则表示被迭代的范围 ,自动迭代,自动取数据,自动判断结束。范围for 可以作用到数组和容器对象上进行遍历。
int array[] = { 1, 2, 3, 4, 5 };// C++98的遍历for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i){cout << array[i] << endl; // 1 2 3 4 5}// C++11的遍历for (auto& e : array)e *= 2; // 如果要修改容器内的元素,需要在变量类型后加上&for (auto e : array)cout << e << \" \" << endl; // 2 4 6 8 10// 字符串的遍历string str(\"hello world\");for (auto ch : str){cout << ch << \" \"; // h e l l o w o r l d}cout << endl;
2、迭代器(iterator)
- string 中的迭代器主要分为正向迭代器和反向迭代器,其中又可以细分为 const 和 非const类型的,共计四种。
- const迭代器仅定义方式与非const不同,且在循环内部不能对元素进行修改,其余的都相同
正向迭代器
- 非const
string str(\"hello world\");/** 步骤:1.获取容器开头的迭代器2.以容器结尾的迭代器作为循环结束条件3.通过*(迭代器变量名)获取容器内部的元素4.移动迭代器的位置*/// 或 auto sit = str.begin(); string::iterator sit = str.begin();// 1while (sit != str.end()) {// 2cout << *sit << \' \';// 3sit++;// 4}
- const
string::const_iterator sit = str.begin();// 1while (sit != str.end()) {// 2cout << *sit << \' \';// 3sit++;// 4}
反向迭代器
- 非const
string str(\"hello world\");/** 步骤:1.获取容器结尾的迭代器(rbegin)2.以容器开头的迭代器作为循环结束条件(rend)3.通过*(迭代器变量名)获取容器内部的元素4.移动迭代器的位置*/string::reverse_iterator sit = str.rbegin();// 1while (sit != str.rend()) {// 2cout << *sit << \' \';// 3sit++;// 4}
- const
string::const_reverse_iterator sit = str.rbegin();// 1while (sit != str.rend()) {// 2cout << *sit << \' \';// 3sit++;// 4}
3、operator[]
- 通过索引访问类中的字符数据
MyString str(\"Hello, World!\");str[0] = \'h\'; // 修改第一个字符为\'h\'str[7] = \'w\'; // 修改第八个字符为\'w\'(将\'W\'改为小写) // 输出修改后的字符串for (size_t i = 0; i < str.getLength(); ++i) { cout << str[i];} cout << endl; // hello world
三、string容量
1、length和size
- 返回字符串中有效字符的长度
- size()与length()方法底层实现原理完全相同,二者没有本质的区别,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
string str(\"hello world\");cout << \"str length:\" << str.length() << endl;cout << \"str size:\" << str.size() << endl;//str length:11//str size:11
2、capacity
- 返回字符串存储有效字符的空间大小(不包含\'\\0\')
string str(\"hello world\");cout << \"str capacity:\" << str.capacity() << endl;//str capacity:15(不包含\'\\0\')
- capacity的扩容机制
capacity()在不同平台上的扩容方式不同。先展示Windows下capacity的扩容过程。
string s;size_t sz = s.capacity();// 记录最开始的容量cout << \"make s grow:\" << endl;cout << \"init capacity:\" << s.capacity() << endl;for (int i = 0; i < 100; i++) {s.push_back(\'c\');// 每次向s中添加一个字符if (sz != s.capacity()) {sz = s.capacity();// 判断容量是否变化并打印信息cout << \"capacity changed:\" << sz << endl;}}/*Windows result:make s grow: (不包含\'\\0\') (包含\'\\0\')init capacity: 15 16capacity changed: 31 32 capacity changed: 47 48 capacity changed: 70 71 capacity changed: 105 106运行环境:Windows11 VS2022*/
在Windows系统上,使用Visual Studio 2022编译器时,容器的capacity首次扩容遵循二倍增长策略(例如,从16扩展到32),而随后的扩容则调整为1.5倍增长。
相比之下,在Linux系统上,当采用g++ 4.8编译器且演示代码保持完全一致的情况下,容器的初始
capacity
被设置为1,并且无论是首次还是后续的扩容操作,均严格遵循二倍增长模式。/*Linux result:make s grow:init capacity: 1 capacity changed: 2 capacity changed: 4 capacity changed: 8 capacity changed: 16 capacity changed: 32 capacity changed: 64 capacity changed: 128 运行环境:Linux g++ 4.8*/
3、reserve
- 用于预分配足够的内存空间以容纳指定数量的字符。这个函数不会改变字符串的内容或长度,但它可以影响字符串的
capacity
string s;s.reserve(100); // 提前扩容100个空间(不包含\'\\0\')size_t sz = s.capacity();// 记录最开始的容量cout << \"make s grow:\" << endl;cout << \"init capacity:\" << s.capacity() << endl;for (int i = 0; i < 100; i++) {s.push_back(\'c\');// 每次向s中添加一个字符if (sz != s.capacity()) {sz = s.capacity();// 判断容量是否变化并打印信息cout << \"capacity changed:\" << sz << endl;}}/*Windows VS result:make s grow:init capacity:111Linux g++ result: make s grow:init capacity:100*/
reserve()
函数在字符串管理中的主要作用是预分配足够的内存空间,以减少因后续操作(如添加字符)导致的频繁扩容,从而提升性能。这一功能在不同平台上可能因标准库实现的具体细节而有所差异。在Visual Studio环境下,当传入的参数n大于字符串当前的容量时,
reserve()
函数会确保字符串的容量增加至至少n
个字符或更大。如果n
小于或等于当前容量,调用reserve(n)
通常不会有任何效果,因为字符串已经有足够的空间来存储n
个字符。在Linux平台上,使用g++等编译器时,
reserve()
函数的行为通常与Visual Studio相似。当n
大于当前容量时,它会将字符串的容量调整为至少n
个字符。然而,与Visual Studio不同的是,在Linux平台上,如果n
小于或等于当前容量,字符串的容量将会缩减(至少为size)。
四、string修改
1、push_back
- void push_back (char c);
- 在字符串的末尾添加一个字符
string myString = \"Hello\";myString.push_back(\'!\'); // 在字符串末尾添
2、append
- string& append (const string& str);
- 在字符串的末尾添加另一个字符串的内容
string myString = \"Hello, \";string appendedString = \"world!\";myString.append(appendedString); // 在 myString 末尾添加 appendedString 的内容
3、operator+=
- string& operator+= (const string& str);
- 将右侧字符串的内容追加到左侧字符串的末尾
string greeting = \"Hello\";string suffix = \", world!\";greeting += suffix; // 使用 += 运算符将 suffix 追加到 greeting 的末尾
4、insert
- string& insert (size_t pos, const string& str);
- 在字符串的指定位置插入另一个字符串的内容
string myString = \"Hello world\";string toInsert = \", beautiful \";myString.insert(6, toInsert); // 在 myString 的第 6 个位置插入 toInsert 的内容
5、erase
- string& erase (size_t pos = 0, size_t len = npos);
- 从字符串中删除字符
string str = \"Hello, World!\";str.erase(7, 5); // 从下标7开始删除5个字符
6、find
size_t find (const string& str, size_t pos = 0)
- 在string中查找字符并返回指定字符串的下标(如果找不到则会返回-1)
string st1(\"babbabab\");// 若省略第2个参数,则默认从位置0(即第1个字符)起开始查找cout << st1.find(\'a\') << endl; //1cout << st1.find(\'a\', 0) << endl;//1cout << st1.find(\'a\', 1) << endl;//1 cout << st1.find(\'a\', 2) << endl;//4// 找不到if(str1.find(\'a\') == -1) cout << \"cannot find from str1\";