3.reserve和resize的用法
void Test_String3() { string s1; s1.reserve(100);//开辟100个空间 string s2; s1.resize(100); //s1.resize(100,'x');//开辟100个字节的空间,并全部初始化为字符x /* reserve---开空间,影响的是容量 resize---开空间,对这些空间给一个初始值'\0',也可以自己给值,进行初始化 */ string s3("hello world"); s3.reserve(100); string s4("hello world"); s4.resize(100, 'x'); //以上不会对hello world进行修改,即当增容的量比原始的容量大时,小时会删除数据 }
注意:
1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。
注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
3.string类对象的访问及遍历操作
1.元素的访问——[ 下标 ]
当我们想要将一个字符串逐个字符打印出来时,通常想到是利用下标去访问
void Test_String1() { string s1("hello world"); //打印字符串 for (size_t i = 0; i < s1.size(); ++i) { cout << s1[i] << " ";// s1[i] 等价于 s1.operator[](i) } cout << endl; //修改字符串 for (size_t i = 0; i < s1.size(); ++i) { s1.at(i) += 1; //或s1.operator[](i) += 1; //或s1[i] += 1; } cout << s1.front() << endl; //打印字符串第一个字符 cout << s1.back() << endl; //打印字符串最后一个字符 cout << endl; }
operator[ ] 和 at 的效果是一样的,两者区别在于检查机制:
operator[ ]:当发生越界访问时,会直接assert报错;
at:当发生越界访问时,会直接抛异常;
2.元素的访问——迭代器
访问string对象除了利用下标的方式还可以使用迭代器(iterator),迭代器是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象;
在使用迭代器时,需要指定类域,它是在类里面定义的;
void Test_String1() { /**********正向迭代器**********/ string s1("hello world"); string::iterator it = s1.begin(); //打印字符串 while (it != s1.end()) { cout << *it << " "; ++it; } cout << endl; string::iterator it = s1.begin(); //对字符串进行修改 while (it != s1.end()) { *it -= 1; ++it; } /**********反向迭代器**********/ string s2("hello world"); string::reverse_iterator rit = s2.rbegin(); cout << *rit << " "; auto rit = s2.rbegin();//反向迭代器的类型名比较长,我们可以使用auto自动推导类型 while (rit != s2.rend()) { cout << *rit << " "; ++rit; } cout << endl; } /******************************************************************/ void func(const string& s) { /**********const反向迭代器**********/ string::const_reverse_iterator rit = s.rbegin(); //auto rit = s.rbegin(); while (rit != s.rend()) { //*rit -= 1;//不可以修改 cout << *rit << " "; ++rit; } cout << endl; /**********const迭代器**********/ string::const_iterator it = s.begin(); while (it != s.end()) { //*it -= 1;//不可以修改 cout << *it << " "; ++it; } } void Test_String3() { const string cstr("hello world"); func(cstr); } int main() { Test_String1(); Test_String2(); Test_String3(); return 0; }
迭代器遍历的意义是什么呢?
所有容器都可以使用迭代器这种方式去访问修改;
对于string,无论是正着遍历,倒着遍历,下标+[]都足够好用,为什么还要迭代器呢?
对于string,下标+[]都足够好用,确实可以不用迭代器。但是如果其他容器(数据结构)呢?比如:list、map/set(二叉树)是不支持下标遍历的。
结论:对于string,你得会用迭代器,但是一般我们还是喜欢下标+[]
3.元素的访问——范围for
//范围for,自动往后迭代,自动判断结束 string s1("hello world"); for (auto e : s1) { cout << e << " "; } cout << endl; for (auto& e : s1)//当需要对字符串进行遍历修改是,需要引用 { e -= 1; }
范围for被称为语法糖,简单方便;但是其本质还是被换成了迭代器;
4.string类对象的修改操作
//插入 void Test_String5() { string s("hello world"); string s1("mlxgyyds"); //尾插 s += ' '; //+=1个空字符 s += "!!!!"; //+=1个字符串"!!!!" s.push_back('c'); //尾插1个字符'c' s.append(2, 's'); //尾插2个字符's' s.append("www"); //尾插1个字符串"www" s.append(s1); //尾插1个对象s1 s.append(s1, 4, 4); //尾插s1字符串从下标4位置开始向后4个字符 cout << s << endl; //头插----效率O(N),尽量少用 s.insert(0, 1, 'x'); //在下标0的位置前插入1个字符'x' cout << s << endl; s.insert(s.begin(), 'q'); //在正向迭代器的起始位置前插入字符'q' cout << s << endl; s.insert(0, "test "); //在下标0的位置前插入字符串"test " cout << s << endl; //中间位置插入,尽量少用 s.insert(4, " *****"); //在下标4的位置前插入字符串" *****" cout << s << endl; } //删除 void Test_String6() { string s("hello world"); //尽量少用头部和中间的删除,因为要挪动数据,效率低 cout << s << endl; s.erase(0, 1); //删除下标0位置的字符 cout << s << endl; s.erase(s.size() - 1, 1); //删除最后一个字符 cout << s << endl; s.erase(3); //删除下标3(包括3)后面的所有字符 s.erase(3, 100); //删除下标3(包括3)后面的所有字符 s.erase(); //全部删除 //删除中间 s.erase(s.begin() + 1, s.end() - 2);//删除区间字符串 cout << s << endl; } int main() { Test_String5(); Test_String6(); return 0; }
5.string类对象的查找操作
void Test_String4() { string s("hello world"); cout << s << endl; cout << s.c_str() << endl; /*虽然都可以实现,但是前者是重载的流插入、流提取操作符,进行打印*/ /*后者把s识别为char*,进行打印*/ /*****************************一************************************/ string file("test.txt"); FILE* fout = fopen(file.c_str(), "w");//当要打开一个文件时,这种函数接口就非常好,适配C语言语法 //要求你取出文件的后缀 size_t pos = file.find("."); //找到.的位置 if (pos != string::npos) { //string suffix = file.substr(pos, file.size() - pos); string suffix = file.substr(pos);//不给npos传值,npos用缺省值,默认从pos位置向后取,有多少取多少 cout << suffix << endl; } /******************************二***********************************/ string file("test.txt.zip"); FILE* fout = fopen(file.c_str(), "w"); //要求你取出文件的后缀 size_t pos = file.rfind(".");//rfind倒着找 if (pos != string::npos) { //string suffix = file.substr(pos, file.size() - pos); string suffix = file.substr(pos);//不给npos传值,npos用缺省值,默认从pos位置向后取,有多少取多少 cout << suffix << endl; } /********************************三*********************************/ // 取出url中的域名 string url("http://www.cplusplus.com/reference/string/string/find/"); size_t pos1 = url.find(':');//从起始位置向后找':' string protocol = url.substr(0, pos1 - 0);//取出协议 cout << protocol << endl; size_t pos2 = url.find('/', pos1 + 3);//从'w'位置向后找'/' string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));//取域名 cout << domain << endl; string uri = url.substr(pos2 + 1);//取剩下的部分 cout << uri << endl; } int main() { Test_String4(); return 0; }
6.string类非成员函数重载
relational operators函数重载了==, >=, <=, >, <, != 这些关系运算
string s1("ABCDE"),s2("ABCDF"); if (s1 > s2) cout << s1.c_str() << endl; else cout << s2.c_str() << endl; //少用 cout << ("hhhhh" < s2) << endl; cout << (s1 < "hhhhh") << endl;
当你想要获取一个连续的字符串时(含有空格),如果采用cin是无法实现的;此时就需要getline函数。
string s1, s2, s3; cin >> s1; //获取字符串(不能含空格) cout << s1.c_str() << endl; getline(cin, s2); //获取字符串(可以包含空格) cout << s2.c_str() << endl; getline(cin, s3, '#'); //获取字符串(可以包含空格,遇到'#'号字符自动结束) cout << s3.c_str() << endl;
7.string类的其他函数
int val = stoi("1234");//将31.4转换为整数 cout << val << endl; string str = to_string(3.14);//把31.4转换为字符串 cout << str << endl;