目录
一.为什么学习string类?
1.1 C语言的字符串
二. 标准库中的string类
2.1 string类(了解)
2.2 string类成员函数
● string类对象的常见构造
● string类析构函数
● 赋值重载
2.3 string的迭代器
<1>正向迭代器 Iterator
<2> 反向迭代器 reverse_iterator
<3> 迭代器访问const类型容器
2.4 string类的容量操作
2.5 string类的访问修改
<1>元素访问
<2>元素修改
<3>子字符串
2.6 string类的查找
2.7 string类的非成员函数
● 运算符重载+
● 比较运算符重载
● getline(将线从流转换为字符串)
一.为什么学习string类?
1.1 C语言的字符串
C语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合面向对象编程(OOP)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、 快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
string类相比C语言的字符串更安全、更方便、更高效的使用,是面向对象编程的思想体现
二. 标准库中的string类
2.1 string类(了解)
string类的文档介绍
在使用string类时,必须包含头文件#include<string>以及using namespace std;
2.2 string类成员函数
● string类对象的常见构造
<1>string(); 无参构造,空的 string 只有 "\0"
<2>string (const string& str); 拷贝构造函数
<3>string (const string& str, size_t pos, size_t len = npos);
(其他负数也可以起到一样的效果)
拷贝构造,在指定下标 pos 位置拷贝 len 个字符 默认值为 npos 类型为无符号整数类型,因此 npos 为无符号整数最大值,库规定 len 大于字符串长度,则拷贝整个 str 到结束
<4>string (const char* s); 将字符串拷贝给 string 类
<5>string (const char* s, size_t n); 将字符串的前 n 个拷贝给 string 类
<6>string (size_t n, char c); 将 n 个字符 c 拷贝给 string 类
int main(){string s1;//无参构造 只有\0string s11{};//同上 string s1(); 编译器可能认为声明string s2("hello world");//将括号内字符串拷贝给s2string s3(s2, 0, 2);//将s2从第0个位置拷贝2个元素给s3string s4(s2);//拷贝构造 将s2类拷贝给s4string s5(s2, 5);//拷贝构造 将s2类的前5个字符拷贝给s5string s6(8, 'a');//拷贝n个字符a给s6cout << s1 << endl;cout << s11 << endl;cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;cout << s5 << endl;cout << s6 << endl;return 0;}
● string类析构函数
当字符串生命周期结束,会自动调用析构函数。
● 赋值重载
2.3 string的迭代器
<1>正向迭代器 Iterator
提供了一种通用的(所用)访问容器的方式
string s1("hello");// it 作用类似指针string::iterator it = s1.begin();while (it != s1.end())//end 返回最后一个字符下一个位置 左闭右开{*it += 2;//可以修改内容cout << *it << ' ';++it;}cout << endl;
● begin 返回 指向 string 第一个字符
● end 返回 指向 string 最后一个有效字符下一个位置 \0
<2> 反向迭代器 reverse_iterator
string s1("hello");// it 作用类似指针string::reverse_iterator it = s1.rbegin();//反向指向string的最后一个有效字符 视为开始端auto end = s1.rend();//指向 string的第一个有效字符前一个位置 视为结尾端while (it != s1.rend()){cout << *it << ' ';++it;}cout << endl;
● rbegin
● end
<3> 迭代器访问const类型容器
普通的迭代器是可读可写的,const迭代器访问const修饰的容器只能读不能写(指向的内容)
Tip:const迭代迭代器不是指const 修饰迭代器,因为迭代器本身可以修改,而它指向的内容不能修改!
string s1("hello");// it 作用类似指针string::const_iterator it = s1.cbegin(); //也可以写成 begin 相当于权限缩小while (it != s1.end()){/**it += 2;*/cout << *it << ' ';++it;}cout << endl;
三种访问方式:
string s1("hello world");cout << s1 << endl;for (int i = 0; i < s1.size(); i++){cout << s1[i] << ' ';}cout << endl;string::iterator it = s1.begin();auto it2 = s1.begin();while (it2 != s1.end()){cout << *it2 << ' ';it2++;}cout << endl;//自动推导类型 自动赋值 自动迭代结束 底层迭代器//本质拷贝赋值 不能修改原数据 将*it值 赋值给 cfor (auto c : s1){cout << c << ' ';}cout << endl;//引用 可以修改for (auto& c : s1){cout << c << ' ';}cout << endl;
2.4 string类的容量操作
std::string 里内部结构可能优化了,比如 char[] _buff 在这种情况下,短字符串可能直接存储在 std::string 对象的内存空间内,而不是在堆上分配。但是,当字符串增长超出了这个内部空间时,就会触发堆上的内存分配和扩容。
连续尾插测试扩容机制:
int main(){string s;size_t sz = s.capacity();cout << "capacity changed:" << sz << '\n';cout << "making a grow:\n";for (int i = 0; i < 100; i++){s.push_back('c');if (sz != s.capacity()){sz = s.capacity();cout << "capacity changed:" << sz << '\n';}}return 0;}
可以看到 由于 _buff 的存在 初始容量为15个字节,再继续尾插之后该编译器出现了 1.5倍的扩容机制以减少将来可能的内存重新分配次数,提高性能。
容量只显示有效字符的个数,内存会比容量多一个空间(存储‘ \0 ’)
● 总结:
<1> size() 与 length() 方法底层实现原理完全相同,引入size() 的原因是为了与其他容器的接
口保持一致, 一般情况下基本都是用size() 。
<2> capacity() 返回有效数据空间大小,不包含 \0 实际需要多开一个空间
<3> clear() 将string中有效字符清空,不改变底层空间大小。
<4> resize(size_t n) 与 resize(size_t n, char c) 都是将字符串中有效字符个数改变到n个 ,不
同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char
c) 用字符c来填充多出的元素空间 。
注意: resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
<5>reserve(size_t res_arg=0) 为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
vs下,有效元素个数小于预留空间大小时,不缩容。
g++下,有效元素个数小于预留空间大小时,缩容。
int main(){string s2("hello xxxxxxxxxxxxxxxxxx");cout << s2.size() << endl;cout << s2.capacity() << endl << endl;//给一个小于 size的值s2.reserve(20); //结果不变cout << s2.size() << endl;cout << s2.capacity() << endl << endl;//给一个 size与 capacit 中间值s2.reserve(28); //结果不变cout << s2.size() << endl;cout << s2.capacity() << endl << endl;//给一个 大于 capacit 值s2.reserve(40); //扩容cout << s2.size() << endl;cout << s2.capacity() << endl << endl;s2.clear();//清楚数据 不清容量cout << s2.size() << endl;cout << s2.capacity() << endl << endl;return 0;}
2.5 string类的访问修改
<1>元素访问
● 运算符重载[ ] 返回pos位置字符的引用
模拟实现:
char& operator[](size_t pos){assert( pos>=0 &&pos < _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos >= 0 && pos < _size);return _str[pos];}
● at 获取字符串中的字符
at 函数自动检查 pos 是否是字符串中字符的有效位置(即 pos 是否小于字符串长度),如果不是,则抛出 out_of_range 异常。
<2>元素修改
● push_back 尾部插入一个字符c
● append 在字符串后追加字符串str
● 赋值重载+= 在字符串后追加字符串str
int main(){string s2("world");string s("hello worold");s.push_back(' ');//单个字符s.push_back('x');s.append(" ");s.append(s2);//字符串cout << s << endl;s += ' ';s += "ccccccc";cout << s << endl;return 0;}
注意:
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c' 三种的实现方式差不多,一般情况下string类的 += 操作用的比较多,+= 操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
● insert(插入) 在pos或p位置之前插入字符串
● erase(删除)pos位置删除字符串
● replace(替换)
int main(){string s("hello worold");//模拟头插 需要移动数据 效率相对低s.insert(0, "hello xc ");cout << s << endl;//模拟头删 需要移动数据s.erase(0, 1);cout << s << endl;s.erase(s.begin());cout << s << endl;//模拟尾删s.erase(--s.end());cout << s << endl;s.erase(s.size() - 1, 1);cout << s << endl;//将空格替换string ss("hello world");cout << ss << endl;ss.replace(5, 1, "%%");cout << ss << endl;cout << endl;return 0;}
<3>子字符串
● c_str(兼容C)返回一个指向字符数组的指针 数组内容即 string 对象
// string 不能直接与 文件操作对接string file;cin >> file;FILE* fout = fopen(file.c_str(),"r");char ch = fgetc(fout);while (ch != EOF){cout << ch;ch= fgetc(fout);}fclose(fout);
● data(类似于c_str)
● substr(子串)在str中从pos位置开始,截取n个字符,然后将其返回
2.6 string类的查找
● find(正向) 在字符串里查找字符数据 pos位置开始 n个数据
//将字符串的空格替换成百分号//法一 移动数据频繁 效率低string sss("hello world hello bit");cout << sss << endl;size_t pos = sss.find(' ');while (pos != string::npos){sss.replace(pos, 1, "%%");pos = sss.find(' ',pos+2);}cout << sss << endl<<endl;//法二 构造新串 空间换时间string tmp;tmp.reserve(sss.size());for (auto ch : sss){if (ch == ' ')tmp += "%%";elsetmp += ch;}cout << tmp << endl;sss.swap(tmp);// 交换 字符指针cout << sss << endl << endl;
● rfind(倒着找) 与find 参数一致意义 倒着查找
//找出文件后缀string s2("test.cpp.zip");size_t pos2 = s2.rfind('.');string suffix2 = s2.substr(pos2);cout << suffix2 << endl;
● find_first_of 在字符串中搜索与其参数中指定的任何字符匹配的第一个字符。
int main(){string s3("hello world hello xc");cout << s3 << endl;size_t pos3 = s3.find_first_of("abcd");// find_first_of 给定一个需要查找的字符串,一个一个查找,返回索引 while (pos3 != string::npos){s3[pos3] = '*';pos3 = s3.find_first_of("abcd", pos3 + 1);}cout << s3 << endl;return 0;}
● find_last_of(倒着找)倒序查找
void SplitFilename(const std::string& str){std::cout << "Splitting: " << str << '\n';std::size_t found = str.find_last_of("/\\");std::cout << " path: " << str.substr(0, found) << '\n';std::cout << " file: " << str.substr(found + 1) << '\n';}//文件路径分离string str1("/usr/bin/man");string str2("c:\\windows\\winhelp.exe");SplitFilename(str1);SplitFilename(str2);
● find_first_not_of 查找字符串中与参数中指定的任何字符都不匹配的第一个字符
● find_last_not_of 倒叙查找字符串中与参数中指定的任何字符都不匹配的第一个字符
2.7 string类的非成员函数
● 运算符重载+
返回一个新构造的字符串对象,其值是lhs中字符的连接,后跟rhs中的字符。
int main(){string s1("hello");string s2 = s1 + " wrold";string s3 = s1 + s2;cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;return 0;}
● 比较运算符重载
在字符串对象lhs和rhs之间执行适当的比较操作
● getline(将线从流转换为字符串)
从is中提取字符并将其存储到str中,直到找到分隔符delim
找最后一个单词长度:
#include <iostream>using namespace std; int main() { string str; //cin >> str;// cin 和 scanf 将空格和换行默认成分割 getline(cin,str);// 无定界默认遇到换行才停止输入 size_t pos = str.rfind(' '); cout << str.size() - (pos + 1) << endl;}
可以自定义分隔符
getline(cin,str,"*");//流提取,直到遇到*才停止