✨✨小新课堂开课了,欢迎欢迎~✨✨
🎈🎈养成好习惯,先赞后看哦~🎈🎈
所属专栏:C++:由浅入深篇
小新的主页:编程版小新-CSDN博客
1.为什么会有string类
C 语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便, C 标准库中提供了一些 str 系列的库函数,但是要实现对字符串的增删查改等操作还是很麻烦的,而且稍不留神可能还会越界访问,所以我们封装了一个string类来处理字符串的各种问题。
2.标准库里的string类
2.1什么是string类
string类的特性:我们可以动态地存储和操作字符串,无需担心内存管理问题,因为它会自动处理字符串的存储和释放,并且支持各种字符串操作,如拼接,比较,查找,替换等。
在使用string类时,必须包含#include头文件以及using namespace std;
下面我们就通过代码的示例了解一些string类的常见用法。
2.2string类的常用接口
1.string类对象的常见构造
void string1()
{string s1; //构建一个空的string对象cout << s1 << endl;string s2("hello world");//直接用常量字符串来构造cout << s2 << endl;string s3(5,'x');//用5(n)个字符来构造string对象cout << s3 << endl;string s4(s2);cout << s4 << endl;//拷贝构造}
运行结果:
2.string类对象的容量操作
函数名称 | 功能说明 |
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串释放为空串,是返回true,否则返回false |
clear (重点) | 清空有效字符 |
reserve (重点) | 为字符串预留空间** |
resize (重点) | 将有效字符的个数该成n个,多出的空间用字符c填充 |
void string2()
{string s1("hello world");//string s1("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");cout << "s1:" << s1 << endl;cout <<"size:" << s1.size() << endl;cout << "length:" << s1.length() << endl;//size和length没有什么区别,只是size更具有通用性,更推荐使用cout << "capacity:" << s1.capacity() << endl;//class string//{//private:// char _buff[16];// char* _str;//// size_t _size;// size_t _capacity;//};//capcacity就是我们之前所熟知的容量//这里补充说明一点:当容量不够时会自动扩容,通常是按1.5倍扩,这个和环境有关//在VS中,还特地设有buff存储数据当大于buff时就会将数据拷贝给str,这个过程是按2倍扩的,自后的都是按1.5倍扩cout <<"empty:"<< s1.empty() << endl;//0表示非空,1表示空s1.clear(); //清空字符串,不改变底层空间大小
cout << "s1:" << s1 << endl;s1.reserve(100); //制定开一定的空间(一般都会多开)
cout << "capacity:" << s1.capacity() << endl;s1.resize(5, 'x');
cout << "s1:" << s1 << endl;//j将有效字符的个数改成5(n)个,多出来的空间,用x填充
cout << "size:" << s1.size() << endl;s1.resize(20, 'k');
cout << "s1:" << s1 << endl;//j将有效字符的个数改成20(n)个,多出来的空间,用k填充
cout << "size:" << s1.size() << endl;}
运行结果:
注意:
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, charc)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
小扩展:
我们再来扩展一下与capacity相关的小细节,上面我们即提到1.5倍扩,又提到了2倍扩。具体情况是这样的。
buff的空间开的是16个字节,有一个是‘\0’的位置,我们只能存15个有效字符,当我们要储存的数据小于15字符,该字符串会存到buff里面,而不是str里。
当要储存的字符串较大时,就会以buff的容量大小为基础进行扩容,首次是扩容2倍扩,如果空间还是不够,就会1.5倍的进行扩容直至开够足够的空间。
3. string类对象的访问及遍历操作
函数名称 | 功能说明 |
operator[] (重 点) | 返回pos位置的字符,const string类对象调用 |
begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位 置的迭代器 |
rbegin + rend | rbegin获取容器最后一个字符 + rend指向容器“反向的开头”,在实际的容器范围之外,它在第一个元素之前。 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
void string3()
{string s1("hello world");//三种遍历方法//1.下表+[]//2.迭代器(正向迭代器 反向迭代器)//3.范围for(看起来很高级,底层还是用迭代器实现的)//1.下表+[]for (int i = 0; i < s1.size(); i++){//s[i]+=2;cout << s1[i] << ' ';}//[]能直接访问,修改字符串的内容,把他当成我们所熟知的[]使用即可。cout << endl;//2.正向迭代器string::iterator it = s1.begin();while (it != s1.end()){cout << *it << ' ';it++;}cout << endl;//3.反向迭代器string::reverse_iterator rit = s1.rbegin();while (rit != s1.rend()){cout << *rit << ' ';rit++;}cout << endl;//范围for-自动赋值,自动迭代,自动判断结束for (auto& ch : s1){cout << ch << ' ';}cout << endl;
}
运行结果:
在这里补充2个C++11的小语法。
auto关键字
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。(auto的功能简而言之就是能够自动推导出类型)
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&,当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
auto不能直接用来声明数组
int func1()
{return 10;
} //不能做参数
//void func2(auto a)
//{} error// 可以做返回值,但是建议谨慎使用
auto func3()
{return 3;
}void test()
{int a = 10;auto b = a;auto c = 'a';auto d = func1();// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项//auto e; 推断不出来e的类型 errorcout << typeid(b).name() << endl;//打印类型名cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;int x = 10;auto y = &x;auto* z = &x;auto& m = x;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// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型//auto array[] = { 4, 5, 6 }; error}
运行结果:
使用场景举例:
auto在一个变量的类型名很长的时候,就发挥了很大的作用,很便捷,但是也有弊端。
auto自动推断类型
map<string, string> dict;
map<string, string>::iterator mit = dict.begin();
auto mit = dict.begin();//自动推导出mit的类型
使用起来更加便捷,但是代码的可读性会降低
范围for
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。
for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
范围for可以作用到数组和容器对象上进行遍历,范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
void test2()
{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 << " " ;cout << endl;string str("hello world");for (auto ch : str){cout << ch << " ";} cout << endl;}
运行结果:
4.string类对象的修改操作
函数名称 | 功能说明 |
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= (重点) | 在字符串后追加字符串str |
c_str(重点) | 返回C格式字符串 |
find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
void string4()
{string s1("hello world");s1.push_back('x');cout << "push_back后:";cout << s1 << endl; //尾插一个字符cs1.append("abc");cout << "append后:";cout << s1 << endl;//尾插一个字符串s1.operator+=("hello");cout << "operator+=后:";cout << s1<<endl;//尾插一个字符串cout << "c_str:";const char* str = s1.c_str();cout << str << endl;//返回C格式字符串//c_str主要是用来将string对象转化为C风格的字符串指针,用于传给const char*类型参数的函数cout << "find:";//size_t find (char c, size_t pos = 0) const;int num1 = s1.find('e');cout << num1 << endl;//pos位置开始往后找字符c,返回该字符在字符串中的位置(正着找)cout << "rfind:";//ize_t rfind (char c, size_t pos = npos) const;//npos static const size_t npos = -1;//his constant is defined with a value of - 1, which because size_t is an unsigned integral type,// it is the largest possible representable value for this type.//总结:npos是一个很大数,该类型的最大值int num2 = s1.rfind('e');cout << num2 << endl;//pos位置开始往前找字符c,返回该字符在字符串中的位置(倒着找)cout << "s1:" << s1 << endl;cout << "substr:";//string substr (size_t pos = 0, size_t len = npos) const;string tmp = s1.substr(2, 5);//从pos位置开始,截取n和字符,然后返回cout << tmp << endl;}
运行结果:
注意:
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
5.string类非成员函数
函数 | 功能说明 |
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> (重点) | 输入运算符重载 |
operator<< (重点) | 输出运算符重载 |
getline (重点) | 获取一行字符串 |
relational operators (重点) | 大小比较 |
void string5()
{string s1("hello world");string s2("xiaoxin");//string operator+ (const string & lhs, const string & rhs);cout << "operator+:";cout << operator+(s1, s2) << endl;//涉及深拷贝,效率低下,尽量少用cout << "operator+>>";//输入运算符重载string s3;operator>>(cin, s3);cout << s3 << endl;cout << "operator<<";operator<<(cout,s1) << endl;//getline 获取一行字符//getline与比如getchar,scanf之类的区别在于,后者的两个一般都是以空格,换行符分界//如果输入带空格的字符串,后者就会将其当成两个字符串,从而读不到空格//getline就能读到中间的空格,将其当成一个字符串//istream& getline(istream & is, string & str, char delim);// 第一种,delim默认是遇到换行符截止//istream& getline(istream & is, string & str);//这一种是遇到str截止//relational operators//这个函数库里包含了各种比较大小的函数,函数类型为bool类型//知道是这样的就可以std::string foo = "alpha";std::string bar = "beta";if (foo == bar) std::cout << "foo and bar are equal\n";if (foo != bar) std::cout << "foo and bar are not equal\n";if (foo < bar) std::cout << "foo is less than bar\n";if (foo > bar) std::cout << "foo is greater than bar\n";if (foo <= bar) std::cout << "foo is less than or equal to bar\n";if (foo >= bar) std::cout << "foo is greater than or equal to bar\n";}
运行结果:
3.总结
以上介绍的都是string类的主要接口,还有一些string的接口不常用,感兴趣的可以可以自己学一下,下面是我自己整理的,还比较全面,希望有所帮助。下一篇我们就来介绍string类的模拟实现。
感谢各位的观看~
#include<iostream>
#include<string>
#include<vector>
using namespace std;//class string
//{
//private:
// char _buff[16];
// char* _str;
// size_t size;
// size_t capacity;
//
//};//构造
void string1()
{string s1;string s2("hello world");string s3(s2);cout << s1 << endl;cout << s2 << endl;cout << s2 << endl;//string (const string& str, size_t pos, size_t len = npos);//有多少拷贝多少string s4(s2, 6, 15);cout << s4 << endl;//npos是有缺省值的,可以不传string s5(s2, 6);cout << s5 << endl;//拷贝前n个字符string s6("hello world", 5);cout << s6 << endl;string s7(10, 'x');cout << s7 << endl;
}//三种遍历方式
void string2()
{string s1("hello world");//小标+[]for (size_t i = 0; i < s1.size(); i++){cout << s1[i] << ' ';}cout << endl;//迭代器string::iterator it = s1.begin();while (it != s1.end()){cout << *it << ' ';it++;}cout << endl;//范围for-自动赋值,自动迭代,自动判断结束//底层其实就是迭代器for (auto& ch : s1){cout << ch << ' ';}cout << endl;//auto自动推断类型//map<string, string> dict;//map<string, string>::iterator mit = dict.begin();//auto mit = dict.begin();//使用起来更加便捷,但是代码的可读性会降低}//迭代器
void string3()
{//iterator begin();string s2("hello world");string::iterator it = s2.begin();while (it != s2.end()){*it += 2;cout << *it << ' ';it++;}cout << endl;string::reverse_iterator rit = s2.rbegin();while (rit != s2.rend()){cout << *rit << ' ';rit++;}cout << endl;//const_iterator begin() const;const string s3("hello world");string::const_iterator cit = s3.begin();while (cit != s3.end()){//*cit += 2;cout << *cit << " ";++cit;}cout << endl;string::const_reverse_iterator rcit= s3.rbegin();while (rcit != s3.rend()){// *rcit += 2;cout << *rcit << " ";++rcit;}cout << endl;//只读不写//const_iterator cbegin() const noexcept;string::const_iterator csit = s3.cbegin();while (csit != s3.cend()){cout << *csit << " ";++csit;}cout << endl;string::const_reverse_iterator csrit = s3.crbegin();while (csrit != s3.crend()){cout << *csrit << " ";++csrit;}cout << endl;//begin()返回一个普通的迭代器,可读可写;cbegin()返回一个常量迭代器//只读不写
}void TestPushBack()
{// reverse 反转 逆置// reserve 保留、预留string s;// 提前开空间,避免扩容,提高效率s.reserve(10);size_t sz = s.capacity();//15cout << "capacity changed: " << sz << '\n';cout << "making s 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';}}//31 47 70 105//刚开始是二倍扩,后面大概是1.5倍扩//因为刚开始是有个buff存在的,小于buff空间大小就存在buff里面//当大于buff的容量时,就会存到_str里面,地方发生转变的这次是2倍扩,后面在堆上的时候就//大概是1.5倍扩了
}void string4()
{string s2("hello world");//size和length没有区别,只是size更具有通用性,更推荐使用cout << s2.size() << endl;cout << s2.length() << endl;//都不包含\0cout << s2.max_size() << endl;cout << s2.capacity() << endl;//resize:将字符串的长度调整为n个字符//void resize(size_t n);//void resize(size_t n, char c);s2.resize(5);//多的部分用X填充s2.resize(12, 'x');cout << s2 << endl;string s1("hello worldxxxxxxxxxxxxx");cout << s1.size() << endl;//24//不够会扩容,一般是以1.5倍进行扩容cout << s1.capacity() << endl << endl;//31//在VS下一般不会缩容,但是不同的平台不一样,例如g++就会缩容s1.reserve(20);cout << s1.size() << endl;//24cout << s1.capacity() << endl << endl;//31//这里就没有缩容//缩容也有规定,缩容不会缩的比size小s1.reserve(28);cout << s1.size() << endl;//24cout << s1.capacity() << endl << endl;//31//扩容s1.reserve(40);cout << s1.size() << endl;//24cout << s1.capacity() << endl << endl;//47s1.clear();//清空字符串,但是容量不变cout << s1.size() << endl;//0cout << s1.capacity() << endl << endl;//47//判断字符串是否为空std::string str1 = "";if (str1.empty()) {std::cout << "字符串为空" << std::endl;}else {std::cout << "字符串不为空" << std::endl;}//std::string::shrink_to_fit//与reserve相比,这个更有约束力一些,该函数会请求减少string占用的空间//以适用于当前的内容大小,但是也不一定string s4("hello");s4.shrink_to_fit();cout << s4.size() << endl;//5cout << s4.capacity() << endl << endl;//15//原本大小就小于15的,这里就没有缩容std::string str(100, 'x');std::cout << "1. capacity of str: " << str.capacity() << '\n';//111str.resize(10);std::cout << "2. capacity of str: " << str.capacity() << '\n';//111str.shrink_to_fit();std::cout << "3. capacity of str: " << str.capacity() << '\n';//15//这里就缩容了
}//元素访问
void string5()
{//operator[]和at用法上没有区别//oparator[]里面是断言,越界会报错;但是at越界会抛异常,抛out_of_range的异常//back()函数用于获取字符串的最后一个字符//front用于获取字符串的第一个字符}//调节器modifier
void string6()
{//operator+=string s("hello world");s.operator+=('x');s.operator+=("hxxxxxxx");s.operator+=(s);s += 'y';s += "3333333";s += s;cout << s << endl;cout << endl << endl;//append//append除了和operator+=上面相同的用法外,还有下面的用法//1.string& append (const string& str, size_t subpos, size_t sublen);//用于将一个字符串str,从指定的subpos位置开始,长度为sublen追加到当前字符串的末尾string s1("hello world");s1.append(s1, 2, 4);cout << s1 << endl;//string& append (const char* s, size_t n);s1.append("xxxxx", 3);cout << s1 << endl;//string& append (size_t n, char c);s1.append(3,'h');cout << s1 << endl;//template <class InputIterator>//string& append(InputIterator first, InputIterator last);std::vector<char> vec = { 'w','r','o','l','d' };s1.append(vec.begin(), vec.end());cout << s1 << endl;//补充一点:first和last不一定就是起始位置和终止位置;也可以是自己指定位置//但是string类里只给了begin和end,这就需要自定义迭代器,现在我还不会//void push_back (char c);s1.push_back('l');//assign - 用来给字符串赋予新的值//string& assign(const string & str);string s3("hello world");s3.assign(s1);cout << s3 << endl;//string& assign(const string & str, size_t subpos, size_t sublen);s3.assign(s1, 5, 7);cout << s3 << endl;//string & assign(const char* s);s3.assign("xxxxx");cout << s3 << endl;//xxxxx//string& assign(const char* s, size_t n);s3.assign("hello world", 5);cout << s3 << endl;//string& assign(size_t n, char c);s3.assign(5, 's');cout << s3 << endl;//template <class InputIterator>//string& assign(InputIterator first, InputIterator last);std::vector<char> charvec = { 'h','e','l','l','o' };s3.assign(charvec.begin(), charvec.end());cout << s3 << endl;//insert//用法总结:在指定位置插入单个字符、字符串,另一个字符串的一部分//使用迭代器进行插入操作//replace//用于替换字符串里的一部分内容//erase//string& erase (size_t pos = 0, size_t len = npos);//有缺省值可以不给s3.erase(0, 3);//从第几个位置删除几个字符cout << s3 << endl;s3.erase(s3.begin());//删除第一个字符cout << s3 << endl;s3.erase(--s3.end());//尾删cout << s3 << endl;//end()并不是指向最后一个字符,而是最后一个字符的下一个位置//swap//void swap(string & str);s3.swap(s1);//交换s1与s3的内容cout << s3 << endl;cout << s1 << endl;//pop_back//删除最后一个字符}void string7()
{//c_str-返回字符串的指针//data也是返回一个字符串的指针//copy和substr都可用于拷贝字符或者字符串//size_t copy (char* s, size_t len, size_t pos = 0) const;//copy是将string里的内容拷贝到外部的字符串数组s中;// string substr (size_t pos = 0, size_t len = npos) const;//而substr是将string里提取子字符串返回一个新的string对象//find是找一个字符,字符串,string对象;rfind是倒着找// 找到返回索引;找不到返回string::npos//find_first_ofstd::string str("Please, replace the vowels in this sentence by asterisks.");std::cout << str << '\n';std::size_t found = str.find_first_of("abcdef");//找到任意一个都返回索引while (found != std::string::npos){str[found] = '*';found = str.find_first_not_of("abcdef", found + 1);}//find_last_of是倒着找//find_first_not_of是找不到返回索引;find_last_not_of是倒着找,找不到返回索引//cpmpare不参与,用的比较多的运算符重载//get_allocator还不会}void string8()
{//oparator+ 用于字符串的拼接// 可以连接两个string对象;string对象和字符串字面量;字符串字面量和string对象//这里解释一下要实现字符串字符串字面量+string对象的底层;该函数不是成员函数,或者将其设为全局函数//举个例子,双目操作符的左操作数默认为类对象,字符串字面量+string对象这个就行不通string s1("hello");string s2 = s1 + "world";cout << s2 << endl;string s3 = "world" + s1;cout << s3 << endl;//swap//这个和上面的一个的使用方式不同,两个参数//void swap (string& x, string& y);//getline//getline与比如getchar,scanf之类的区别在于,后者的两个一般都是以空格,换行符分界//如果输入带空格的字符串,后者就会将其当成两个字符串,从而读不到空格//getline就能读到中间的空格,将其当成一个字符串//istream& getline(istream & is, string & str, char delim);// 第一种,delim默认是遇到换行符截止//istream& getline(istream & is, string & str);//这一种是遇到str截止
}int main()
{string8();//TestPushBack();return 0;
}
///// 范围for和auto的扩展//auto不能做参数,但是可以做返回值,不过不建议使用
//范围for适用于容器和数组//int func1()
//{
// return 10;
//}
//不能做参数
void func0(auto a = 0)
{}//但是能做返回值,不过不建议使用
//auto func2()
//{
// //...
// return func1();
//}
//
//auto func3()
//{
// //...
// return func2();
//}//int main()
//{
// int a = 10;
// auto b = a;
// auto c = 'a';
// auto d = func1();
// // 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项
// //auto e;
// cout << typeid(b).name() << endl;
// cout << typeid(c).name() << endl;
// cout << typeid(d).name() << endl;
//
// auto不能用于数组
// //auto array[] = { 4, 5, 6 };
//
// auto ret = func3();
//
// 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] << endl;
// }
//
// // yyds
// // 范围for适用于容器 和 数组
// // C++11的遍历
// for (auto& e : array)
// e *= 2;
//
// for (auto e : array)
// cout << e << " " << endl;
//
// return 0;
//}