一、为什么学习 string 类
1、C语言中的字符串
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列
的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户
自己管理,稍不留神可能还会越界访问
2、在日常中
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、
快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数
二、标准库中的string类
1.string 类
string类的文档介绍
https://cplusplus.com/stringhttps://cplusplus.com/string在使用string类时,必须包含#include头文件 #include<string>
以及using namespace std;
2 auto和范围for
(1)auto关键字
在这里补充2个C++11的小语法,方便我们后面的学习。
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型
指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期
推导而得。
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际
只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
auto不能直接用来声明数组
#include<iostream>
using namespace std;int func1()
{return 10;
}不能做参数
//void func2(auto a)
//{}// 可以做返回值,但是建议谨慎使用
auto func3()
{return 3;
} int main()
{int a = 10;auto b = a;//自动推导为int 类型auto c = 'a'; //自动推导为char类型auto d = func1();//函数返回值编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项//auto e;cout << typeid(b).name() << endl;cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;int x = 10;auto y = &x; //y为int 类型auto* z = &x;//z为指针类型auto& m = x; //m为引用类型cout << typeid(x).name() << endl;cout << typeid(y).name() << endl;cout << typeid(z).name() << endl;//auto aa = 1, bb = 2;编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型//auto cc = 3, dd = 4.0;// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型/*auto array[] = { 4, 5, 6 };*/return 0;
}
运行结果:
说明:auto声明引用类型时则必须加&,否则就是原来变量的类型。
一个例子说明auto的方便
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange","橙子" }, {"pear","梨"} };// auto的用武之地//std::map<std::string, std::string>::iterator it = dict.begin();auto it = dict.begin();while (it != dict.end()){cout << it->first << ":" << it->second << endl;++it;}return 0;
}
(2)范围for
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
范围for可以作用到数组和容器对象上进行遍历
范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到
//范围for C++11
//自动取容器的数据赋值给左边的对象
//自动++,自动判断结束
//原理:范围for的底层是迭代器
遍历数组:
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{int array[] = { 1, 2, 3, 4, 5 };// C++98的遍历for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i){array[i] *= 2;} for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i){cout << array[i] <<" ";} cout << endl;// C++11的遍历for (auto& e : array)//冒号左边是范围内用于迭代的变量。右边是被迭代的范围e *= 2;for (auto e : array)cout << e << " " ;return 0;
}
结果:
注意:如果使用范围for的遍历时,想改变数组的值,冒号左边用于迭代的变量的类型要是:
引用类型,才可以改变值,否则无法改变(因为改变的只是拷贝,并不是其本身)
for (auto& e : array)//冒号左边是范围内用于迭代的变量。右边是被迭代的范围
当不加&符号时
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{int array[] = { 1, 2, 3, 4, 5 };// C++11的遍历for (auto e : array)//冒号左边是范围内用于迭代的变量。右边是被迭代的范围e *= 2;for (auto e : array)cout << e << " " ;return 0;
}
发现数组的值并没有发生改变
遍历字符数组:
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{string str("hello world");for (auto ch : str){cout << ch << " ";}return 0;
}
(3)迭代器
begin指向第一个字符,end指向字符串末尾的'\0'
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{string str("hello world");//迭代器//string::iterator it1 = str.begin();auto it1 = str.begin();//可以使用auto,自动识别类型//begin指向字符h,end指向字符串末尾的'\0'while (it1 != str.end())//it1类似于指针{(*it1)++; //改变字符数组的字符it1++;}//范围for C++11//自动取容器的数据赋值给左边的对象//自动++,自动判断结束//原理:范围for的底层是迭代器for (auto ch : str)//自动识别类型{cout << ch << " ";}return 0;
}
结果:
hello world 的每个字符都加了1
(4)反向迭代器:
//string::reverse_iterator it = s1.rbegin();//reverse是翻转的意思
rbegin指向字符串的末尾那个字符
rend指向字符串首个字符的前一个位置
#include<iostream>
using namespace std;// // 反向迭代器
// //rbegin指向字符串的末尾那个字符
// //rend指向字符串首个字符的前一个位置
int main()
{string s1 = "hello zhouzisong";//string::reverse_iterator it = s1.rbegin();//reverse是翻转的意思auto it = s1.rbegin();//也可使用自动识别while (it != s1.rend()){cout << *it << " ";it++; //反向迭代器也是使用++(从后往前移动)}return 0;
}
结果:
是从字符串的末尾开始移动的
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{string str("hello world");//迭代器//string::iterator it1 = str.begin();auto it1 = str.begin();//可以使用auto,自动识别类型//begin指向字符h,end指向字符串末尾的'\0'while (it1 != str.end())//it1类似于指针{(*it1)++; //改变字符数组的字符it1++;}//范围for C++11//自动取容器的数据赋值给左边的对象//自动++,自动判断结束//原理:范围for的底层是迭代器for (auto ch : str)//自动识别类型{cout << ch << " ";}//反向迭代器//rbegin指向字符串的末尾那个字符//rend指向字符串首个字符的前一个位置string::reverse_iterator rit= str.rbegin();//auto rit = str.rbegin();while (rit != str.rend()){(*rit)--;rit++; //反向迭代器也是使用++(从后往前移动)}cout << endl;for (auto ch : str)//自动识别类型{cout << ch << " ";}return 0;
}
结果:
首先是正向的遍历字符串,将每个字符加1。后面又反向的遍历字符串,又将每个字符减1,回到原来的字符
(5)const 修饰时的迭代器
string::const_iterator it = s2.begin();
#include<iostream>
using namespace std;int main()
{string s1 = "hello world";const string s2=s1;//const修饰时string::const_iterator it = s2.begin();//auto it = s2.begin();//也可使用自动识别while (it != s2.end()){cout << *it << " ";it++; }return 0;
}
注意:不能修改字符串
3. string类的常用接口说明(注意下面只讲解最常用的接口)
(1) string类对象的常见构造
(2). string类对象的容量操作
#include<iostream>
#include<string>
using namespace std;//capacity empty clear reserve resize
int main()
{string s1 = "abcdefg";string s2 = "";int size = s1.size();int length = s1.length();int capacity = s1.capacity();int s1_empty = s1.empty();int s2_empty = s2.empty();cout << "s1.size=" << size << endl << "s1.length=" << length << endl<<"s1.capacity="<<capacity<<endl;//size等于length,都是元素个数cout << "s1=" << s1_empty << endl;//结果为0,说明字符串s1不为空cout << "s2=" << s2_empty << endl;//结果为1,说明字符串s2为空s1.clear();cout << "调用clear()之后s1.size=" << s1.size() << endl;cout << "调用clear()之后s1.capacity=" << s1.capacity() << endl;s1 = "abcdefg";cout << s1 << endl;cout << "s1.capacity(原大小) =" << s1.capacity() << endl;s1.reserve(2);//括号里面是一个小于原字符串大小的数字(预留空间小了)cout << "s1.capacity(小) =" << s1.capacity() << endl;s1.reserve(20);//括号里面是一个小于原字符串大小的数字(预留空间大了)cout << "s1.capacity(大) =" << s1.capacity() << endl;string str("I like to code in C");//长度为19cout << str << '\n';unsigned sz = str.size();str.resize(sz + 2,'+'); //将会在原字符串后面添加2个+std::cout << str << '\n';str.resize(14); //长度小于原字符串,会被截断,原字符串只剩下14个字符std::cout << str << '\n';return 0;
}
结果:
(3)string类对象的访问及遍历操作
operator[ ]的用法
#include<iostream>
#include<string>
using namespace std;
int main()
{string str = "abcd";int i;for (i = 0; str[i] != '\0'; i++){str[i]++; //每个字符都加1cout << str[i];}return 0;
}
其他的函数用法见上:二、2
(4). string类对象的修改操作
push_back 和 append 和operator+=的用法
#include<iostream>
#include<string>
using namespace std;
int main()
{string str1("abcde");string str2("abcde");string str3("abcde");string str4("abcde");cout<<"str1追加字符前" << str1 << endl;str1.push_back('s');cout << "str1追加字符后"<<str1 << endl;cout << "str2追加字符串前" << str2 << endl;str2.append("zzshewzh");cout << "str2追加字符串后" << str2 << endl;cout << "str3追加字符串str4前" << str3<< endl;str3 += str4;cout << "str3追加字符串str4后" << str3<< endl;return 0;
}
c_str()函数的用法:
c_str()函数返回一个指向正规C字符串的指针常量, 内容与本string串相同。
这是为了与c语言兼容,在c语言中没有string类型,故必须通过string类对象的成员函数c_str()把string 对象转换成c中的字符串样式。
注意:一定要使用strcpy()函数 等来操作方法c_str()返回的指针
c_str函数的返回值是const char*的,不能直接赋值给char*
#include<iostream>
#include<string>
using namespace std;int main()
{char c[20];string s = "1234";strcpy(c, s.c_str());//c_str()返回的是字符串的地址cout << c << endl; //打印字符数组cprintf("%s", s.c_str()); //直接根据地址,打印字符串return 0;
}