【C++】string类的基本使用

embedded/2024/9/23 18:22:11/

一、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类的基本使用,希望对大家有帮助,祝大家天天开心!


http://www.ppmy.cn/embedded/110885.html

相关文章

C语言-数据结构 弗洛伊德算法(Floyd)邻接矩阵存储

弗洛伊德算法相比迪杰斯特拉相似的地方都是遍历邻接矩阵不断调整最短路径的信息&#xff0c;并且两种算法面对多源最短路径的时间复杂度都是O(n^3)&#xff0c;Floyd采用的是动态规划而Dijkstra是采用贪心的思想。在Floyd中我们将创建两个数组进行辅助&#xff0c;一个path二维…

数据库系统 第52节 数据库日志和恢复

数据库日志和恢复是数据库管理系统&#xff08;DBMS&#xff09;中用于确保数据完整性和一致性的机制。以下是对这些关键技术的详细解释&#xff1a; 重做日志 (Redo Logs): 重做日志是数据库事务日志的一部分&#xff0c;它记录了所有对数据库所做的更改&#xff0c;特别是那些…

详细分析Spring cache的基本知识(Mysql + Redis)

目录 前言1. 基本知识2. 注解方式2.1 @Cacheable2.2 @CachePut2.3 @CacheEvict2.4 @Caching2.5 @CacheConfig3. 实战前言 此框架的基本知识为补充说明,针对Redis数据库,采用注解形式 基本的Java知识推荐阅读: java框架 零基础从入门到精通的学习路线 附开源项目面经等(超…

【解决】vue 弹窗后面页面可以滚动问题

做web端项目过程中&#xff0c;发现点击弹窗后&#xff0c;弹窗后面的页面还可以滚动。 复现如下&#xff1a; 【方法1】 step1&#xff1a;在弹框页面使用 mousewheel.prevent <divv-show"workShowMenu"mousewheel.prevent>// TO DO...弹框内容 </div&…

从自动化到智能化的研究

人类对世界的认识是从时间、空间规律开始的&#xff0c;这些规律蕴含了各种力量及其关系的存在。通常情况下&#xff0c;事实本身往往不会直接告诉我们什么是正确的什么是错误的&#xff0c;没有明确的概念&#xff0c;量得分析是毫无意义的。然而&#xff0c;人们在处理各种客…

BrainSegFounder:迈向用于神经影像分割的3D基础模型|文献速递--Transformer架构在医学影像分析中的应用

Title 题目 BrainSegFounder: Towards 3D foundation models for neuroimagesegmentation BrainSegFounder&#xff1a;迈向用于神经影像分割的3D基础模型 01 文献速递介绍 人工智能&#xff08;AI&#xff09;与神经影像分析的融合&#xff0c;特别是多模态磁共振成像&am…

【计网】计算机网络基础

当自律变成一种本能的习惯&#xff0c; 你就会享受到它的快乐。 --- 村上春树 --- 初识计算机网络 1 初识协议1.1 协议分层1.2 OSI七层模型1.3 TCP / IP协议 2 初识局域网2.1 什么是局域网2.2 MAC地址2.3 局域网通信 3 简单认识IP地址 1 初识协议 1.1 协议分层 首先&#…

第十六篇:走入计算机网络的传输层--传输层概述

1. 传输层的功能 ① 分割与重组数据 一次数据传输有大小限制&#xff0c;传输层需要做数据分割&#xff0c;所以在数据送达后必然也需要做数据重组。 ② 按端口号寻址 IP只能定位数据哪台主机&#xff0c;无法判断数据报文应该交给哪个应用&#xff0c;传输层给每个应用都设…