C++|C++STL详解(一)(string类的介绍以及基本使用)


文章目录

        • 为什么要学习string类
          • C语言的字符串
        • 字符的一些补充知识
        • 标准库中的string类
          • string类
          • string类对象的常见构造函数
          • string的三种遍历方式
          • string类对象的修改操作
          • string类对象的容量操作
          • string类对象的查找等一些其他操作

为什么要学习string类 C语言的字符串
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神就有可能越界访问。
并且在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
字符的一些补充知识 众所周知英文字符等符号在计算机中采用ASCII码的方式存储。
那么对于中文,日文,其他国家的文字计算机又该如何存储呢?对于中文等其他字符,ASCII码则表示不了,因为一个字节存不下,对于这些字符,则采用Unicode编码标准:它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。Unicode标准下每个中文是两个字节,这样就能存储大部分的中文了。
同时也有新的字符型wchar_t来存储两个字节的字符,它能更好的表示unicode编码
//char ascall编码->英文 //如何显示中文?日文?其他国家的文字计算机如何存储呢?unicode utf-8 utf-16 utf-32 gbk int main() { char ch1 = 'a'; char ch2 = 97; cout << ch1 << endl; cout << ch2 << endl; char str1[] = "中国"; cout << strlen(str1) << endl; wchar_t wch; //宽字节,2byte,能更好的表示unicode编码 char ch; cout << sizeof(wch) << endl; cout << sizeof(ch) << endl; return 0; }

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

在正式讲string之前我给大家推荐一个网站: string类
这个是C++的官方文档,如果对于C++STL容器或者其他不清楚的地方就可以去这上面查一下。
标准库中的string类 string类
  1. 字符串是表示字符序列的类
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
总结:
  1. string是表示字符串的字符串类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;
  4. 不能操作多字节或者变长字符的序列。
    在使用string类时,必须包含#include头文件以及using namespace std;
string类对象的常见构造函数 在C++98中有以下7种构造函数
C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

(constructor)函数名称 功能说明
string() (重点) 构造空的string类对象,即空字符串
string(const char* s) (重点) 用C-string来构造string类对象
string(const stirng& s) (重点) 拷贝构造函数
string(size_t n,char c) string类对象中包含n个字符c
string(const string& str, size_t pos, size_t len = npos) 从str对象中的pos位置开始截取len个长度的字符,如果len>str的长度,那么截取完str就结束
string(const char* s,size_t n) 从s指向的字符数组中复制前n个字符
int main() { string s1; string s2("hello world"); string s3(s2); string s4 = "hello world!!!"; cout << s1 << endl; cout << s2 << endl; cout << s3 << endl; string s5(s4, 3, 5); cout << s5 << endl; char* url = "http://www.cplusplus.com/reference/string/string/string/"; string s6(url, 4); cout << s6 << endl; string s7(10, 'x'); cout << s7 << endl; s7 = s2; cout << s7 << endl; return 0; }

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

string的三种遍历方式 一、下标[]
C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

string s2("hello world"); //1.下标[] for (size_t i = 0; i < s2.size(); i++) { cout << s2[i] << " "; } cout << endl;

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

这里的size()是一个公共成员函数,返回string类对象的长度,和length()相同
C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

通过上面图片可以看到库里面重载了两个operator[]函数,一个是可读可写的,一个是只读的。因此这里不光可以遍历s2,还可以修改s2里面的值。
string s2("hello world"); //三种遍历 //1.下标[] for (size_t i = 0; i < s2.size(); i++) { cout << s2[i] << " "; } cout << endl; for (size_t i = 0; i < s2.size(); i++) { //s2.operator[](i) s2[i] = 'x'; } cout << endl; for (size_t i = 0; i < s2.size(); i++) { cout << s2[i] << " "; } cout << endl;

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

二、迭代器遍历
迭代器iterator是string中的类,所以要加域作用限定符
string s2("hello world"); string::iterator it = s2.begin(); while (it != s2.end()) { cout << *it << " "; ++it; } cout << endl;

【C++|C++STL详解(一)(string类的介绍以及基本使用)】C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

[begin(),end()) end()返回的不是最后一个数据的位置,返回的是最后一个数据的下一个位置,需要要注意的是,C++中凡是给迭代器一般都是给的[)左闭右开的区间。
这个时候可能就会有人问了:那么迭代器的意义是什么呢?
迭代器意义:像string,vector支持[]遍历,但是list,map等等容器不支持下标[]我们就要用迭代器遍历,所以迭代器是一种统一使用的方式。
vector v = { 1, 2, 3, 4 }; vector::iterator vit = v.begin(); while (vit != v.end()) { cout << *vit << " "; ++vit; } cout << endl; list lt = { 1, 2, 3, 4 }; list::iterator ltit = lt.begin(); while (ltit != lt.end()) { cout << *ltit << " "; ++ltit; } cout << endl;

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

反向迭代器
//反向迭代器 string s3("123456"); string::iterator it3 = s3.begin(); while (it3 != s3.end()) { cout << *it3 << " "; ++it3; } cout << endl; string::reverse_iterator rit = s3.rbegin(); while (rit != s3.rend()) { cout << *rit << " "; ++rit; } cout << endl;

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

三、范围for
C++11中提供范围for,特点:写起来简洁
依次取容器中的数据,赋值给e,自动判断结束
范围for实现的本质就是迭代器,因此也支持其他容器的遍历
string s3("123456"); vector v = { 1, 2, 3, 4 }; list lt = { 1, 2, 3, 4 }; for (auto&e : s3) { e += 1; } cout << endl; for (auto e : s3) { cout << e << " "; } cout << endl; for (auto x : v) { cout << x << " "; } cout << endl; for (auto x : lt) { cout << x << " "; } cout << endl;

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

string类对象的修改操作 C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

函数名称 功能说明
operator+=str(重点) 在字符串末尾追加一个字符串
push_back(ch) 在字符串末尾插入一个字符
append(str) 在字符串末尾追加一个字符串
insert(pos,str) insert(pos,n,ch) 在pos位置插入一个字符串,在pos位置插入n个ch字符。尽量少用insert,因为底层实现是数组,头部或者中间插入需要挪动数据
erase(pos,len) 从pos位置开始删除len个长度的字符 如果不给pos与len则默认从开头的位置向后全删除
pop_back(ch) 在字符串末尾删除一个字符
int main() { string s1; s1.push_back('h'); s1.push_back('e'); s1.push_back('l'); s1.push_back('l'); s1.push_back('o'); s1.append("world"); cout << s1 << endl; string s2("!!!"); //s1.append(s2); s1.append(s2.begin(), s2.end()); cout << s1 << endl; //实际中最喜欢用这个+= s1 += ' '; s1 += "凌峰"; s1 += s2; cout << s1 << endl; //尽量少用insert,因为底层实现是数组,头部或者中间插入需要挪动数据 s1.insert(0, "x"); cout << s1 << endl; s1.insert(3, "yyyy"); cout << s1 << endl; s1.insert(0, "yyyy"); cout << s1 << endl; s1.erase(0, 1); cout << s1 << endl; s1.erase(0, 3); cout << s1 << endl; s1.erase(3, 10); cout << s1 << endl; s1.erase(3); cout << s1 << endl; }

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

string类对象的容量操作
函数名称 功能说明
size(重点) 返回字符串有效字符长度
length 返回字符串有效字符长度
capacity 返回空间总大小
empty (重点) 检测字符串是否为空串,是返回true,否则返回false
clear (重点) 清空有效字符(’\0’不是有效字符,它是标识字符)
reserve (重点) 为字符串预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
resize (重点) 将有效字符的个数变成n个,多出的空间用字符c填充,如果不给字符c的话默认是’\0’。如果有效元素的个数是变大的,那么此时capacity有可能会改变,如果是有效元素的个数是减少的,那么capacity大小不变。
string s1; cout << "size:" << s1.size() << endl; cout << "capacity:" << s1.capacity() << endl; cout << s1 << endl; s1.resize(20, 'x'); cout << "size:" << s1.size() << endl; cout << "capacity:" << s1.capacity() << endl; cout << s1 << endl; string s2("hello world"); s2.resize(20, 'x'); cout << s2 << endl; cout << "size:" << s2.size() << endl; cout << "capacity:" << s2.capacity() << endl; s2.resize(5); cout << s2 << endl; cout << "size:" << s2.size() << endl; cout << "capacity:" << s2.capacity() << endl; string s3; s3.resize(10); cout << "size:" << s3.size() << endl; cout << "capacity:" << s3.capacity() << endl; s3.reserve(40); cout << "size:" << s3.size() << endl; cout << "capacity:" << s3.capacity() << endl;

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

下面我们再来通过代码来看一下resize与reserve的区别
string s4; s4.resize(127); int oldCp = s4.capacity(); for (char ch = 0; ch < 127; ++ch) { s4 += ch; if (oldCp != s4.capacity()) { cout << "增容:" << oldCp << "->" << s4.capacity(); oldCp = s4.capacity(); } } cout << s4 << endl;

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

resize是把对象的有效字符个数变成n个,而+=ch是在对象的末尾追加字符ch,因此后面空间不够了还是需要增容的。
string s4; s4.reserve(127); //s4.resize(127); int oldCp = s4.capacity(); for (char ch = 0; ch < 127; ++ch) { s4 += ch; if (oldCp != s4.capacity()) { cout << "增容:" << oldCp << "->" << s4.capacity(); oldCp = s4.capacity(); } } cout << s4 << endl;

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

reserve是为string对象预留空间,我们如果知道这次操作需要多大的空间,那么只需要reserve一下我们就不需要增容了。
string类对象的查找等一些其他操作
函数名称 功能说明
find + npos(重点) 从字符串pos位置开始从前往后找一个字符或者字符串,返回该字符或者字符串第一次在当前字符串中出现的位置
rfind 从字符串pos位置开始从后往前找一个字符或者字符串,返回该字符或者字符串第一次在当前字符串中出现的位置
c_str(重点) 返回C格式字符串
substr 在str中从pos位置开始,截取n个字符,然后将其返回
getline(非成员函数) 获取一行字符串
operator>>(非成员函数) (重点) 输入运算符重载
operator<<(非成员函数) (重点) 输出运算符重载
下面再来说一下string的成员函数c_str()
string s1("hello world"); cout << s1 << endl; //调用operator<<(cout,s1) cout << s1.c_str() << endl; //调用operator<<(cout,const char* str) s1.resize(20); s1 += "!!!!"; cout << s1 << endl; //调用operator<<(cout,s1) cout << s1.c_str() << endl; //调用operator<<(cout,const char* str) cout << strlen(s1.c_str()) << endl; cout << s1.size() << endl << endl;

  • 调用内置类型的operator<<(cout,const char* str)输出字符串的时候遇到’\0’就停止了,
  • 调用自定义类型的operator<<(cout,const string& s) 输出字符串的时候遇到‘\0’不会停止,它会将该字符串的内容全部都打印出来
C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

大家可能会比较好奇string的find操作运用在哪些场景下呢?
一、假设要求取出文件名的后缀
//假设要求取出文件名的后缀 //string filename = "test.txt"; //size_t pos = filename.find('.'); string filename = "test.txt.zip"; size_t pos = filename.rfind('.'); if (pos != string::npos) { //string suff(filename, pos, filename.size() - pos); string suff(filename, pos); cout << suff << endl; }

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

二、要求写一个程序分别取出域名和协议名
//取出域名 string GetDomain(const string& url) { //查找"://",找到返回该字符串的起始下标位置 size_t pos = url.find("://"); if (pos != string::npos) { size_t start = pos + 3; size_t end = url.find('/',start); if (end != string::npos) { //由于是左闭右开区间[start,end),所以只需要end-start就能计算出域名的长度 return url.substr(start, end - start); } else { //找不到则返回空串 return string(); } } else { //找不到则返回空串 return string(); } }//取出协议名 string GetProtocol(const string& url) { //查找"://",找到返回该字符串的起始下标位置 size_t pos = url.find("://"); if (pos != string::npos) { //由于是左闭右开区间[0,pos),所以只需要pos - 0就能计算出协议名的长度 return url.substr(0, pos - 0); } else { //找不到则返回空串 return string(); } }int main() { //要求写一个程序分别取出域名和协议名 string ur11 = "http://www.cplusplus.com/reference/string/string/rfind/"; string ur12 = "https://tower.im/users/sign_in"; string ur13 = "tower.im/users/sign_in"; cout << GetDomain(ur11) << endl; cout << GetProtocol(ur11) << endl; cout << GetDomain(ur12) << endl; cout << GetProtocol(ur12) << endl; }

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

如果我们想输入"asadg bc"这样的字符串应该怎么办呢?
scanf,cin,gets这些好像都不能帮我们解决问题,因为当他们读到空格或者回车键的时候就会停止读取
那么有什么办法来帮我们读取它呢
下面再来说一个getline函数:获取一行字符串
int main() { string s1; getline(cin,s1); cout << s1 << endl; }

C++|C++STL详解(一)(string类的介绍以及基本使用)
文章图片

可以看到通过使用getiline函数就达到了我们想要的结果

    推荐阅读