一、string类的由来
在C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列
的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户
自己管理,稍不留神可能还会越界访问。
面向过程编程OPP:Procedure Oriented Programming,是一种以事物为中心的编程思想。主要关注“怎么做”,即完成任务的具体细节。
面向对象编程OOP:Object Oriented Programming,是一种以对象为基础的编程思想。主要关注“谁来做”,即完成任务的对象。
于是在C++设计过程中,就添加了string类,用来专门管理字符串。
string这个类是被typedef出来的,basic_string是一个类模板,string内部原理可以理解为是一个动态开辟的顺序表,每个元素是一个char类型的字符。我们知道一说到字符串一定牵扯到编码问题,比如我们汉字的编码(unicode)和英文字母(ascii)的编码方式可能就会不同,常见的编码方式有ASCII,UTF-8,UTF-16,UTF-32等等,string是用模板实例化出来的一个类,它的编码方式就是常见的UTF-8。UTF-8编码的一个主要优点是它向后兼容ASCII,即任何ASCII字符在UTF-8中都使用相同的单字节表示。所以,它的成员变量都是char类型。有人会问为什么不直接搞个ASCII编码的字符串类出来,那就忽略了一个问题,汉字字符串怎么办?
u16string也是通过basic_string模板实例化出来的一个类,它的编码方式是UTF-16。
u32string同样是通过basic_string模板实例化出来的一个类,它的编码方式是UTF-32。
这里我们主要讲string这个类。
二、string类的基本使用
在使用string类时,必须包含#include<string>这个头文件以及using namespace std;如果不加using namespace std;创建对象时必须指明命名空间(std::string)。本篇主要讲如何使用,深层的东西不过多涉及。
1、构造函数
C++98版本下有7个构造函数,我们这里只说C++98,不谈论C++11。
接下来我们来看看它们是怎么使用的:
int main()
{string s1; //(1)默认构造函数,空串string s2("hello world"); //(4)传参构造string s3(s2); //(2)拷贝构造cout << s1 << endl; //重载了流插入,能够打印输出string类型的对象cout << s2 << endl;cout << s3 << endl;//cin >> s1; //也重载了流提取,能够向string类型的对象中输入值//cout << s1 << endl; string s4(s2, 6, 5);//(3)s2中,下标为6的字符向后拷贝5个字符初始化给s2//假设s2下标为6的字符后的字符不够5个,则拷贝到结尾即可cout << s4 << endl;string s5(s2, 6);//(3)//我们可以看到库中第三个参数有个缺省值npos(size_t类型)//它是类中静态成员变量,值为-1,-1在内存中存储就是32个1,//因为npos是size_t类型也就是无符号整形,即npos就是整数的最大值//它表示的意思就是从某个下标位置开始拷贝到结尾cout << s5 << endl;string s6("hello world",5);//(5)拷贝第一个参数字符串的前5个字符初始化给s6cout << s6 << endl;string s7(3,'x'); //(6)用3个'x'字符初始化给s7cout << s7 << endl;return 0;
}
运行结果:
2、析构函数
析构函数我们不需要使用,因为编译器会自动帮我们调用来释放空间。构造函数需要我们写是因为初始化的形式是多样的。
3、赋值重载
int main()
{string s1("hello world");string s2("xxx");cout << s1 << endl;cout << s2 << endl;s1 = s2; //(1)对象参数类型重载cout << s1 << endl;cout << s2 << endl;s1 = "hah"; //(2)字符串参数类型重载cout << s1 << endl;cout << s2 << endl;s2 = 'q'; //(3)字符参数类型重载cout << s1 << endl;cout << s2 << endl;return 0;
}
4、重载[]
string可以像其他内置类型一样直接用下标引用操作符[]来访问内部元素。像下面这样:
int main()
{string s1("hello world");cout << s1[0] << endl; //hcout << s1[1] << endl; //ereturn 0;
}
我们知道自定义类型不能直接通过下标引用操作符来访问内部元素,而string的底层其实是在类内对下标引用操作符[]进行了重载。我们可以简单想象一下它的实现:
class string
{
public:char& operator[](int i){assert(i <= _size);return _str[i];}
private:char* _str; //指向空间的起始位置int _size; //记录元素个数int _capacity; //由于要扩容,所以这里需要记录容量大小
};
能用引用返回吗?为什么要引用返回呢?
开辟空间是在堆上开辟的,调用[]结束后,空间还在,所以能用引用返回。至于为什么要用引用返回,第一,减少一次拷贝构造;第二,也是最重要的一点,可以修改变量的值。这也和下标引用操作符的功能进行了重合。
int main()
{string s1("hello world");cout << s1[0] << endl; //hcout << s1[1] << endl; //es1[0] = 'x'; //支持修改,也印证了我们的想象s1[1] = 'x';cout << s1[0] << endl; //xcout << s1[1] << endl; //xreturn 0;
}
内置类型越界访问数组程序不会崩溃,也不会报错。
int main()
{int a[3] = { 1,2,3 };cout << a[5] << endl; //越界访问return 0;
}
运行结果:
说明了越界访问,编译器也不管,但我们在类中重载下标引用操作符时,如果越界访问,直接报错,这样就会更好。所以我们加了一句"assert(i < _size);",可以避免发生越界情况。
我们可以来验证一下我们的猜想是否正确:
由此可见,string底层就是有这一机制的。
在此,介绍几种遍历成员的方式:
int main()
{string s("hello world");//方式1for (size_t i = 0;i < s.size();i++){cout << s[i] << " ";}cout << endl;//方式2//iterator是STL六大组件之一的迭代器//使用迭代器必须指明在哪个容器的类域,每个容器都有自己的迭代器,它们的名字相同,用法相同但内部结构可能"天差地别",用法相同说明了在其他容器中也可以用这种方式来遍历成员//用迭代器定义出来的对象,功能上像指针,可能是指针也可能不是指针,这里暂且理解为指针,也可以理解为像指针的东西//begin()是返回这段空间开始位置的迭代器,end()是返回最后一个有效元素的下一个位置的迭代器string::iterator it = s.begin();while (it != s.end()){cout << *it << " "; //这里可能就有人说了,不是指针怎么解引用,不是指针可能会对*进行重载,这里的*it如果改变就会修改s中字符的值it++; //这里同样也是,若不是指针,就会对++进行重载}cout << endl;//方式3(C++11)//范围for:从s这个容器中自动取值给e,直到取完为止//auto自动推导类型,这里的auto也可以写成char,但一般都写auto//自动赋值,自动迭代,自动判断结束//它的底层其实是迭代器,*it的值赋给e,支持迭代器就支持范围forfor (auto e : s) //写起来更简单{cout << e << " "; //这里的e只是s中每个字符的拷贝,修改e的值不影响s中字符的值,若想修改在auto后面加上引用&}cout << endl;return 0;
}
这3种方式在性能上没有区别,都是遍历,只是写法上不同。
(1)、auto(C++11语法)
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,会自动释放,后来局部变量都能自动释放,所以auto在这里就不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
如果一个对象的类型太长,我们就可以用auto来简化代码。比如:
map<string, string> dict;map<string, string>::iterator mit = dict.begin();auto mit = dict.begin();
虽然auto可以简化代码,但在一定程度上"牺牲了"可读性。
int main()
{int a = 10;auto b = a;cout << typeid(b).name() << endl; //打印int,typeid可以查看对象类型return 0;
}
auto不能去定义数组。auto不能做参数但可以做返回值,auto做返回值建议谨慎使用。auto可以同时定义两个对象,但对象的类型必须一致,否则会报错。
auto后跟*,代表是指针,必须给地址,否则会报错。
(2)、范围for(C++11语法)
范围for主要用于容器。
for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
范围for可以作用到数组和容器对象上进行遍历,范围for的底层很简单,容器遍历实际就是替换为迭代器。
范围for用来遍历数组也是很方便的:
int main()
{int arr[] = { 1,2,3,4,5 };for (auto e : arr){cout << e << " ";}cout << endl;return 0;
}
(3)、迭代器
int main()
{string s1("hello world");//1、正向迭代器string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " "; it++; }cout << endl;//2、反向迭代器string::reverse_iterator rit = s1.rbegin();while (rit != s1.rend()){cout << *rit << " ";rit++; //逆向走,++被重载了}cout << endl;const string s2("day day up");//3、const迭代器,只能读,不能写(修改指针指向的内容)//cbegin和cend专门用于const迭代器,它们的功能和begin/end一样//这里用begin/end来替代cbegin/cend也可以string::const_iterator cit = s2.cbegin();while (cit != s2.cend()){cout << *cit << " ";cit++; }cout << endl;//4、const反向迭代器,只能读,不能写(修改指针指向的内容)//crbegin和crend专门用于const反向迭代器,它们的功能和rbegin/rend一样//这里用rbegin/rend来替代crbegin/crend也可以//string::const_reverse_iterator crit = s2.crbegin();auto crit = s2.crbegin();while (crit != s2.crend()){cout << *crit << " ";crit++;}cout << endl;return 0;
}
begin指向第一个有效元素,end指向最后一个有效元素的后一个位置。
rbegin指向最后一个有效元素,rend指向第一个有效元素的前一个位置。
5、成员函数
string类中成员函数有许多,在这里只写一部分常用到的,对于所写的每个成员函数我会写一些用法代码帮助大家理解,但有些成员函数有多个重载,我不会把每个重载的用法都写一遍,只挑选一些来写,希望大家理解。
(1)、size() / length()
这两个成员函数的功能都是返回字符串的长度,但不包括'\0';
(2)、max_size()
它的功能是返回最大字符串的长度(这里是整形的最大值)。
(3)、capacity()
它的功能是返回申请容量大小。这个大小不包括'\0',假设capacity的初始值是15字节,字符串中有15个字符,它是不会扩容的,当字符串中有16个字符它才扩容。也就是说实际的空间是比容量多一个字节的,这一个字节用来存放'\0'。
int main()
{string s1("a");cout << s1.capacity() << endl; //容量为15字节s1 = "aaaaaaaaaaaaaaa"; //15个字符cout << s1.capacity() << endl; //容量为15字节s1 = "aaaaaaaaaaaaaaaa"; //16个字符cout << s1.capacity() << endl; //容量为31字节
}
运行结果:
我们可以用一个例子来测试容量的变化:
void TestPushBack()
{string s;size_t sz = s.capacity();cout << "capacity of start:" << sz << endl;//扩容前容量cout << "making s grow:" << endl;for (int i = 0;i < 100;++i) //循环控制插入100个字符{s.push_back('c'); //尾插一个字符'c'if (sz != s.capacity()){sz = s.capacity(); //扩容后新的容量给szcout << "capacity changed:" << sz << endl;}}
}
int main()
{TestPushBack();return 0;
}
运行结果:
我们知道容量是只包含有效数据不包含'\0',但实际空间要比capacity的值多1用来存放'\0',我们在运行结果的基础上每个值都加1才是实际的空间。
加1后我们发现第一次空间是扩二倍,接下来差不多都是1.5倍左右扩容。
这里的原因是什么呢?
VS2019在这里自己做了单独的处理,当size小于16时,它将元素存放在一个buff数组中而不是直接存放在堆上。
//VS下多了一个类似_buff的一个数组
class string
{
private:char _buff[16];char* _str;int _size;int _capacity;
};
如果_size小于16,就会存放在_buff中,在没有数据时先给你开16字节的空间,capacity大小是15(加上'\0'就是16);大于16就全部存放在_str指向的堆中,同时清空_buff,但_buff这个空间还在,_size大于16首次扩容就会扩到32,这是单独处理的。后续扩容就是1.5倍左右。
我们可以看一下一个string类对象的大小是多少:
int main()
{string s;cout << s.capacity() << endl;cout << sizeof(s) << endl; return 0;
}
运行结果:
这里的15就是没有数据时,capacity的大小,验证了我们上面说的在没有数据时先给你开16字节的空间,capacity大小是15(加上'\0'就是16)。
而28就是_buff占16字节,char*占4字节,_size占4字节,_capacity占4字节,一共占28字节。
同样一段代码在Linux环境下的结果是不同的:
我们可以看到,在Linux下它是严格的二倍扩容,它没有_buff这一说,因为开始时capacity的值为0。
为什么在两种不同的环境下会有差异呢?
C++标准规定,string类必须实现什么功能,但怎么实现的是靠编译器来决定的。比如扩容,C++标准规定string类要实现自动扩容,但怎么扩容,扩多大是每个编译器自己实现的,这里VS和Linux下的扩容机制就不大相同。
(4)、reserve()
它的功能是改变容量的,也就是改变capacity的大小,它可以避免频繁扩容。
假设参数是n(就是改变后的容量大小),分3种情况:
1、n < size
首先会不会缩容,这个问题是根据编译器的,有些编译器会缩容,有些编译器不会缩容。如果缩容,则最多缩到size,不能把我的size也给缩没了。
在VS2019下是不缩容的,即容量保持不变,当然size也不会改变,其中的元素也不会改变。
在Linux下可能缩容。
2、size < n < capacity
有些编译器会缩容,有些编译器不会缩容。
在VS2019下是不缩容的,即容量保持不变,当然size也不会改变,其中的元素也不会改变。
在Linux下可能缩容。
3、n > capacity
会扩容,至少扩到n,也可能更多,这是不确定的。在vs下通常会扩的更多一些,而在Linux下通常就扩到n。
int main()
{string s1("aaaaa");cout << s1.size() << endl; //元素个数为5个cout << s1.capacity() << endl; //容量为15字节//1、n < size s1.reserve(3);cout << s1.size() << endl; //元素个数为5个cout << s1.capacity() << endl; //容量为15字节//2、size < n < capacitys1.reserve(10);cout << s1.size() << endl; //元素个数为5个cout << s1.capacity() << endl; //容量为15字节//3、n > capacitys1.reserve(50);cout << s1.size() << endl; //元素个数为5个cout << s1.capacity() << endl; //会扩容,容量为63字节}
(5)、resize()
功能就是将元素个数设置为n。分3种情况:
int main()
{string s1("hah");cout << s1.size() << endl; //3cout << s1.capacity() << endl; //15//1.n < sizes1.resize(1);cout << s1.size() << endl; //1cout << s1.capacity() << endl; //15//2.size < n <capacity s1.resize(10);cout << s1.size() << endl; //10cout << s1.capacity() << endl; //15//3. n > capacitys1.resize(30);cout << s1.size() << endl; //30cout << s1.capacity() << endl; //31return 0;
}
在VS2019下运行的结果:
总结一句话,通过resize,设置n为多少,size就跟着变为多少,如果n > capacity,则capacity跟size一起变化,否则只有size变为n,capacity通常不变(取决于编译器)。
如果n小于size,则size个数就会变成n,size中多余的数据被删除。如果n大于size,则size扩大到n,新增加的数据初始化为'\0'。resize也可以传第二个参数,指定一个字符,新增加的数据初始化为你传过去的字符。
(6)、clear()
功能是清除数据,但通常不清除容量。
int main()
{string s("hah");cout << s.size() << endl;cout << s.capacity() << endl;s.clear();cout << s.size() << endl;cout << s.capacity() << endl;return 0;
}
运行结果:
(7)、empty()
功能是判断字符个数是否为空。
int main()
{string s1("hah");if (s1.empty())cout << "s1 -> null" << endl;elsecout << "s1 -> not null" << endl;string s2;if (s2.empty())cout << "s2 -> null" << endl;elsecout << "s2 -> not null" << endl;return 0;
}
运行结果:
(8)、shrink_to_fit()
功能是缩容,将capacity减小到适应它的size。这不是强制的。
int main()
{string s1("hello");cout << s1.size() << endl;cout << s1.capacity() << endl;s1.shrink_to_fit();cout << s1.size() << endl;cout << s1.capacity() << endl;return 0;
}
运行结果:
这里并没有缩容。
(9)、at()
at的功能和重载[]几乎一样,只不过重载[]如果越界会断言报错,at越界会抛出out_of_range的异常。
int main()
{string s("hah");cout << s.at(0) << endl;cout << s.at(1) << endl;cout << s.at(2) << endl;return 0;
}
运行结果:
(10)、front()
功能是返回第一个字符。
int main()
{string s1("hello");cout << s1.front() << endl; //hreturn 0;
}
(11)、back()
功能是返回最后一个有效字符。
int main()
{string s1("hello");cout << s1.back() << endl; //oreturn 0;
}
(12)、push_back()
功能是在原有字符串的基础上追加一个字符。
int main()
{string s("hello");cout << s << endl;s.push_back(' ');s.push_back('w');s.push_back('o');cout << s << endl;return 0;
}
运行结果:
(13)、pop_back()
功能是删除字符串的末尾的一个有效字符。
int main()
{string s("hello");cout << s << endl;s.pop_back();cout << s << endl;return 0;
}
运行结果:
(14)、append()
它的功能是在原有字符串的基础上追加字符串,但不能追加字符。
这里以第三个重载函数为例:
int main()
{string s("hello");cout << s << endl;s.append(" world");cout << s << endl;return 0;
}
运行结果:
(15)、重载+=
它的功能也是在原有字符串的基础上追加字符串,也能追加字符。
这里以第二个重载函数为例:
int main()
{string s("hello");cout << s << endl;s += " world";cout << s << endl;return 0;
}
运行结果:
(16)、assign()
它的功能是给一个字符串对象赋值,若之前字符串有内容则就覆盖掉原来的内容,同时size也会改变。
这里以第三个重载函数为例:
int main()
{string s1 = "hah";cout << s1.size() << endl; //size为3cout << s1.capacity() << endl; //capacity为15s1.assign("x");cout << s1.size() << endl; //这里让size小于原来的size,size=1cout << s1.capacity() << endl; //capacity保持不变,capacity=15string s2 = "xix";cout << s2.size() << endl; //size为3cout << s2.capacity() << endl; //capacity为15s2.assign("xxxxxxxxxxxxxxxxxxxxxxxxxx");cout << s2.size() << endl; //这里让size大于原来的capacity,size=26cout << s2.capacity() << endl;//这里的capacity就会扩容到31string s3 = "pip";cout << s3.size() << endl; //size为3cout << s3.capacity() << endl; //capacity为15s3.assign("xxxxx");cout << s3.size() << endl; //这里让size大于原来的size,小于原来的capacity,size=5cout << s3.capacity() << endl;//capacity保持不变,capacity=15return 0;
}
运行结果:
(17)、insert()
它的功能是在指定位置前(这个位置必须有效,否则运行时会崩溃)插入字符或字符串。
这里以第三个重载函数为例:
int main()
{string s("world");cout << s << endl;s.insert(0, "hello "); //在字符串中下标为0的位置插入"hello "cout << s << endl;return 0;
}
运行结果:
(18)、erase()
它的功能是删除指定位置的字符串或字符。
这里以第一个重载函数为例:
int main()
{string s("hello world");cout << s << endl;s.erase(6, 1); //在下标为6的位置删除1个字符,如果不写第二个参数,默认值是npos,它是int类型最大值,可以理解为从第一个参数位置开始后面全删cout << s << endl;return 0;
}
运行结果:
(19)、replace()
它的作用是将字符串中某一段替换成另外一段。
这里以第三个重载函数为例:
int main()
{string s("hello world");cout << s << endl;s.replace(5, 1, "%%"); //从下标为5的位置开始往后1个字符替换成"%%"cout << s << endl;return 0;
}
运行结果:
用2个字符替换1个字符也是可以的。多替换少也是可以的。
replace()尽量不要频繁使用,因为它底层牵扯到扩容问题,有时需要挪动大量数据。
(20)、find()
它的功能是从某个位置开始查找某个字符或字符串,若找到则返回第一个被找到的位置的起始位置下标,否则返回npos。npos是string类中的静态成员变量。
这里以第四个重载函数为例:
int main()
{string s("hello wor ld");size_t pos1 = s.find(' '); //从下标为0的位置开始找' ',若有多个' ',则返回第一个位置的下标cout << pos1 << endl;size_t pos2 = s.find(' ', pos1 + 1); //从下标为pos1 + 1的位置开始找' ',若有多个' ',则返回第一个位置的下标cout << pos2 << endl;return 0;
}
运行结果:
我们可以结合replace()来实现一个小功能:将一个字符串中所有空格换成'%'
int main()
{string s("h el lo wo r ld");cout << "replace before:" << s << endl;size_t pos = s.find(' ');while (pos != string::npos){s.replace(pos, 1, "%");pos = s.find(' ', pos + 1);}cout << "replace after: " << s << endl;return 0;
}
运行结果:
还用另外一种实现方法:
int main()
{string s("h el lo wo r ld");cout << s << endl;string tmp;for (auto e : s){if (e == ' ')tmp += '%';elsetmp += e;}cout << tmp << endl;return 0;
}
运行结果:
(21)、swap()
它的功能是交换两个string类型对象的成员变量的值。
int main()
{string s1("hah");string s2("pip");cout << "s1 = " << s1 << endl;cout << "s2 = " << s2 << endl;s1.swap(s2);cout << "s1 = " << s1 << endl;cout << "s2 = " << s2 << endl;return 0;
}
运行结果:
通过调试时的监视窗口,也可以看出它们的交换情况:
交换前:
交换后:
不难看出只有buff没变,其余都交换了。
(22)、c_str()
它的功能是返回底层字符串的指针。它的出现就是兼容C语言的,比如一些C语言的函数参数是char*类型的,不能直接传string类型,必须传char*,我们就可以调用c_str()。转换为char*后末尾会放一个‘\0’。
int main()
{string file;cin >> file;FILE* fout = fopen(file.c_str(), "r"); //fopen第一个参数是const char*,所以这里必须要转换一下char ch = fgetc(fout);while (ch != EOF){cout << ch;ch = fgetc(fout);}fclose(fout);return 0;
}
(23)、substr()
它的功能是从某个位置开始,取长度为n的字串,构造一个string类型的对象进行返回。
int main()
{string s1("hello world");string s2 = s1.substr(0, 5); //拷贝构造,取下标为0的位置开始向后5个字符给s2cout << s2 << endl;return 0;
}
运行结果:
(24)、rfind()
它的功能是从某个位置开始从后往前找。找到第一个符合条件的就返回对应的下标。
这里以第四个重载函数为例:
int main()
{//获取文件名的后缀string s("Test.txt");size_t pos = s.rfind('.');//从后向前找string tmp = s.substr(pos);cout << tmp << endl;return 0;
}
运行结果:
这时候就有人说了,将rfind换成find也行啊,rfind感觉没什么用,那么请看下面一种情况:
int main()
{//目的:打印.zipstring s("Test.txt.zip");size_t pos = s.rfind('.');//从后向前找,如果这里是find,就不行了string tmp = s.substr(pos);cout << tmp << endl;return 0;
}
运行结果:
(25)、 find_first_of()
它的功能是从指定位置开始查找任意个字符(查找的范围是参数中的所有字符),找到返回对应下标。
int main()
{string str("Please, replace the vowels in this sentence by asterisks.");size_t found = str.find_first_of("aeiou"); //找出串中的'a','e','i','o','u'任意首个出现的字符的下标while (found != string::npos){str[found] = '*';found = str.find_first_of("aeiou", found + 1);}std::cout << str << '\n';return 0;
}
运行结果:
这段代码的功能是将串中的所有'a','e','i','o','u'替换成'*'。
(26)、find_last_of()
它的功能与find_first_of()一样,只不过是从后往前找。这里就不赘述了。
(27)、find_first_not_of()
它的功能是从指定位置开始查找任意个字符(查找的范围是除了参数之外的所有字符),找到返回对应下标。
int main()
{string str("Please, replace the vowels in this sentence by asterisks.");size_t found = str.find_first_not_of("aeiou"); //找出串中不是'a','e','i','o','u'任意首个出现的字符的下标while (found != string::npos){str[found] = '*';found = str.find_first_not_of("aeiou", found + 1);}std::cout << str << '\n';return 0;
}
运行结果:
这段代码的功能是将串中的所有不是'a','e','i','o','u'的字符替换成'*'。
(28)、find_last_not_of()
它的功能与find_first_not_of()一样,只不过是从后往前找。这里就不赘述了。
6、非成员函数
(1)重载+
这里以第二个重载函数为例:
int main()
{string s1("hello");string s2 = s1 + " world"; //(2)string s3 = "world " + s1; //(2)cout << s2 << endl;cout << s3 << endl;return 0;
}
运行结果:
(2)重载关系运算符
支持两个字符串比较大小,比较规则是根据ASCII码值的大小。这里就不举例了。
(3)重载流插入(<<)和流提取(>>)
重载后就支持输入和输出string类型的字符串了。
(4)getline()
当cin一个字符串对象时,它是取不到空格的:
int main()
{string s;cin >> s;cout << s << endl;return 0;
}
当我们输入AAA B时,它只会取到AAA:
因为cin默认在遇到换行或空格时停止在缓冲区中继续读数据。
getline就可以解决这个问题,cin默认在遇到换行时停止在缓冲区中继续读数据。
int main()
{string s;getline(cin,s);cout << s << endl;return 0;
}
当我们输入AAA B时,它会取到AAA B:
它有两个重载函数:
它默认是以换行为终止符,我们也可以自己设置终止符,对应的是第一个构造函数。
三、总结
本篇到这里就结束了,主要讲了string类的基本使用,希望对大家有帮助,祝大家天天开心!