目录
前言(类的基本概念)
1、现实世界的事物所具有的共性就是每个事物都具有自身的属性, - -些自身具有的行为,例如一个学生有姓名、性别、年龄等属性,吃饭睡觉玩游戏等行为。C+ +提供了类的概念,可以将某一类事物的所有属性和行为封装在一个class中。
2、类对于某个事物的描述其实还是抽象的,例如有一-类事物有姓名、性别、年龄等属性,可以吃饭睡觉玩游戏等行为,如果只是这么描述的话其实我们是不知道这个事物具体是什么样的,因为这个类没有告诉我们每个属性具体的值是多少(这个事物的姓名是什么,年龄是多少),所以类只是对某一类事物的一个描述而已。实际应用中我们需要操作某类事物中一个或者多个具体的事物。那么这个具体的事物我们就称之为对象。
3、类是抽象的,对象是具象的。
4、对象是怎么来的呢?由类实例化而来。因此我们也说通过一个类实例化-个对象。
一、类的基本使用
1.类的定义和实例化
1)类的定义
属性:变量
行为:函数/方法
类的定义:class+类名{
访问控制符:
成员变量//属性
成员函数//行为
};
访问控制符有三种:public,private,protected.
实列:创建一个动物类
class CAnimal{//,在工程中我们习惯将类名首字母大写
public://访问控制符
char name[32];//定义一个名字属性
int age;//定义一个年龄属性
public:
void func(char *voice){//成员函数,也称方法
cout<<name<<":"<<voice<<endl;
}
};
这里先使用访问控制符public,后面再详细介绍访问控制符。
2)类的实列化
实列化一个普通对象
类名 +对象名
CAnimal a;//就简单实列化一个对象了
当进行实列化了后,a就拥有CAnimal的公有属性了。
那么接下来我们就对这个对象的属性进行赋值。
CAnimal a;//实列化一个对象
a.age=10;//对年龄进行赋值
cout<<a.age<<endl;//输出年龄
memset(a.name,0,sizeof(a.name));//将a.name进行清空
strcpy(a.name,"xiaohua");//对a.name进行赋值
a.func("gu lu gu lu");//调用a的方法
cout<<a.name<<endl;//输出a的名字
10
xiaohua:gu lu gu lu
xiaohua
这样我们就给a这个对象的属性赋值,查看属性和调用方法。
那么我们下面来学习定义一个指针变量
类名 *对象名
CAnimal *p;//定义一个指针
注意:定义一个指针变量,但是本质还是指针,不是实列化的对象。
那么接下来就让我们来看看指针变量的用法吧
CAnimal a;
a.age=10;
cout<<a.age<<endl;
memset(a.name,0,sizeof(a.name));
strcpy(a.name,"xiaohua");
a.func("gu lu gu lu");
cout<<a.name<<endl;
CAnimal *p;//定义一个指针变量
p=&a;//让指针指向a
p->age=100;//通过指针修改a中的属性
cout<<p->age<<endl;
p->func("hulalala");//通过指针来调用方法
10
xiaohua:gu lu gu lu
xiaohua
100
xiaohua:hulalala
2.访问控制符
C++中给予成员函数和成员变量访问权限。
public:公有的,修饰成员函数和成员变量只能在类里类外均可以访问。
protected:保护的,修饰成员函数和成员变量只能在类里可以访问。
private: 私有的,修饰成员函数和成员变量只能在类里可以访问。
就这么说着大家都应该不是很明白吧。什么叫类内,什么叫类外?
class Teacher{
public://
char name[32];
private://在私有属性下定义的
int _age;//[Error] 'int Teacher::_age' is private
char _sex;
};
int main()
{
Teacher t;
int age;
char m;
memset(t.name,0,sizeof(t.name));
strcpy(t.name,"wanglaoshi");
t._age=34;
t._sex='m';
return 0;
}
大家看报错原因,_age在private中,在Teacher这个类外是不能进行访问的。
[Error] 'int Teacher::_age' is private
虽然是private,但是如果我们要对其进行访问又该怎么进行操作的呢?这个时候我们就在类内进行修改,private允许在类内进行访问,那么在类内又该怎么进行赋值呢?下面进行代码展示:
class Teacher{
public://
char name[32];
void set_age(int age) {_age=age;}
int get_age() {return _age;}
void set_sex(char sex) {_sex=sex;}
char get_sex() {return _sex;}
private:
int _age;//[Error] 'int Teacher::_age' is private
char _sex;//一般在private中的属性前加_
};
如上面代码显示,在类的public中创建两个函数为在其他函数的访问提供接口,一个set函数给私有属性赋值,一个get函数将私有属性的值拿出来。
那么有没有小伙伴会这么想,反正都能访问private里面的值,那么这个private有什么用呢?乍一看好像没啥用,但是小伙伴们想想啊,万一我们在给private里的属性赋值的时候会不会赋错,或者赋一些奇葩的数字上去
t._age=10000;
t>_sex='p';
如上代码,但是在我们的现实生活中没有这样的人存在,明显不满足客观规律。这时候private配合着set函数就可以轻松的解决了,上面那个代码大家看看该怎么改才能达到目的
void set_age(int age){
if(age<0||age>200){
cout<<"error age"<<endl;
return ;
}
_age=age;
}
void set_sex(char sex){
if(sex!='m'&&sex!='n'){
cout<<"error sex"<<endl;
return ;
}
_sex=sex;
}
我们在set函数里面加给条件判断,这样我们就可以对数据进行合理性检验,保证代码的健壮性。
虽然我们进行了赋值,但是为了代码的健壮性我们可以对代码进行优化改进
bool set_age(int age){
if(age<0||age>200){
cout<<"error age"<<endl;
return false;
}
_age=age;
return true;
}
利用bool类型这样我们就可以在调用函数时发现问题了。
二.构造函数及析构函数
我们为什么需要构造函数?
我们需要多个对象且对象的各个属性都一样时,用构造函数可以在实例化每一个对象的时可以给每个对象都赋初值,这样可以简化工作。但是大家可以想想为什么不在定义属性的时候就赋值呢?这是因为啊,类其实是没有占内存空间的,从这个角度看,给一个没有内存空间的数赋值就不太可能了。
1.无参构造函数
C++编译器在实例一个对象,都执行了一次无参构造函数,这个无参构造函数是编译器自动提供的,但是无参构造函数里面没有写什么东西,所以我们在实例化一个对象是看不出来的。构造函数名字和类名相同,且没有返回值。
下面我们看看无参构造函数的具体实现
无参构造函数 类名()
Box(){
cout<<"Box()"<<endl;//这个语句为了验证构造函数的存在
}
下面我们验证一下,无参构造函数是否被执行到
#include<iostream>
using namespace std;
class Box{//类是没有存储空间的,只有实列化的对象才有存储空间
public://这就是为什么我们在类里面不能初始化
Box(){//从而引入了构造函数
cout<<"Box()"<<endl;//构造函数系统会默认生成,但必须没有写构造函数
}//默认的构造函数记得写,不然容易出问题
private:
double _x;
double _y;
double _z;
};
int main(){
Box box1,box2,box[3];
return 0;
}
Box()//实例化了5个BOX类的对象
Box()//每实例一次,构造函数被调用一次
Box()
Box()
Box()
当然构造函数主要的作用就是初始化数据
Box(){
_x=10;_y=20;_z=30;//每执行构造函数就会初始化
cout<<"Box()"<<endl; }
这样我们就能给每一个对象进行初始化,但是大家可能会有问题,在现实生活中哪有这么多一模一样的对象,所以无参构造函数肯定不能满足我们现实的生活。我们引入了有参构造函数。
2.有参构造函数
和无参构造函数一样,函数名和类名相同并且没有返回值,不同的是多了参数。
有参构造函数: 类名(数据类型 参数1 ,数据类型 参数2...)
有参构造和无参构造可以同时写出来的,在C语言里函数名是不能相同的,但是在C++中是允许的,这个便是函数的重载。
Box(double x,double y,double z){
_x=x;_y=y;_z=z;
}
Box box3(10,20,30);
不同于无参构造函数,有参构造函数实例化一个对象的时候必须传参,这样就可以为每一个赋不同的值了。
如果我们没有使用构造函数候是怎么进行赋值的,是用set函数来一个一个赋值,或者据点符号来赋值。
bool set_age(int age){
if(age<0||age>200){
cout<<"error age"<<endl;
return false;
}
_age=age;
return true;
}
通过set函数
t._age=34;
t._sex='m';
通过据点符号
明显看出构造函数的优点。
3.构造函数列表初始化
格式 : 类名(参数类别):初始化化对象(值)
public:
Box(int x,int y,int z):_x(x),_y(y),_z(z)//初始化_x,_y,_z
{
// _x=x;//这些叫做赋值,而不叫初始化
// _y=y;
// _z=z;
}
private:
int _x;
int _y;
int _z;
在构造函数里面执行的代码可不是初始化,而是进行了赋值操作。从这里看来好像列表初始化有点鸡肋,但其实不然,下面举出他的作用。
1.如果在类里面存在着引用,但是引用又必须初始化。
public:
Box():p(_x)//P(10)不能这么写,引用不能指向常量 int &p=10;
{
cout<<"Box()" <<endl;
}
Box(int x,int y,int z):_x(x),_y(y),_z(z),p(_x)
{
//p(_x)想这么写,必须先给_x进行初始化
}
private:
int _x; int _y; int _z;
int &p;//定义一个引用,引用在定义的时候必须初始化
在最新的版本的C++里面,想给一个引用赋值,除了这么写,还有这样的操作
public:
Box()
{
p=_x;//这样也可以修改p的值
cout<<"Box()" <<endl;
}
Box(int x,int y,int z)
{
_x=x;_y=y;_z=z;
p=_x;//修改p的值
}
private:
int _x;int _y;
int _z; //为了能在定义的时候初始化成想要的数,用构造函数的列表初始化
int b=10;//引用又必须初始化
int &p=b; //不初始化语法错误,为了解决这俩个问题,
但是不建议这么写。
2.类里面有只读变量,const修饰的常量
private:
const int c;//只读变量必须初始化 ,并且是一次性赋值 ,不能改了,
// //就是说如果在这里初始化化了就不能更改了,改就错了
// const int c=0; //为了能在定义的时候初始化成想要的数,用构造函数的列表初始化
public:
Box():c(_x)
{
cout<<"Box()" <<endl;
}
3.类的嵌套定义
class Box2{
public:
Box2()
{
}
Box2(int x)
{
_x=x;
}
private:
int _x;
};
class Box{
public:
Box():_x(x),box(2)//初始化
{
}
Box(int x):_x(x),box(2)//初始化
{
}
private:
int _x;
Box2 box;//在一个类里面定义一个类
};
在工作中假设有3个写了3个类,将自己的写的类封装成头文件发送给对方,但是自己没有写无参构造函数,又写了有参构造函数,那么在对方的类里用我的类定义时会报错,如果我们在定义的时候初始化,可以避开无参构造函数。
二、析构函数
1.析构函数:
~类名()
注意:析构函数没有返回值,没有参数(不能重载)
析构函数在销毁时自动调用
析构函数调用机制----c++编译器自动调用
public:
~Test(){
cout<<"~Test()"<<endl;
}
2.析构函数的功能
如果我们在类里面申请了一个指针并且为他开辟了一片堆上的内存空间,而堆上的内存空间又需要手动申请手动释放。如果我们没有释放就会导致内存泄漏。所以我们可以将释放指针空间的操作放在析构函数中。
public:
char *p=new char('a');
~Test(){
delete p;//c++释放空间的运算符
cout<<"~Test()"<<endl;
}
3.析构函数和构造函数的顺序
是先构造还是先析构呢?应该是先有构造后有析构,可以这么理解,先构造出,才能销毁掉嘛,下面我们来验证下吧
public:
Test(){
cout<<"Test()"<<endl;
}
~Test(){
delete p;
cout<<"~Test()"<<endl;
}
char *p=new char('a');
Test()
~Test()
输 出 量
从输出量可以明显看出,函数的执行顺序是先执行构造后执行析构。
4.多个构造和析构
当类中的成员变量是另一个类的实例化对象时,这个对象就叫做成员对象。
当成员对象所属的类没有无参构造函数时,需要使用初始化成员列表化
class Test{
public:
Test(){//无参构造函数
cout<<"Test()"<<endl;
}
Test(int x,int y):_x(x),_y(y)//有参构造函数
{
cout<<"Test(int x,int y)"<<endl;
}
~Test(){//析构函数
cout<<"~Test()"<<endl;
}
private:
int _x;
int _y;
};
class Test1{
public:
Test1(){
cout<<"Test1()"<<endl;
}
~Test1(){
cout<<"~Test1()"<<endl;
}
private:
Test abc1;//成员对象
Test abc2;
};
因为这里有默认构造函数,所以在执行Test1中Test实例化abc1时并没有报错
class Test{
public:
Test(int x,int y):_x(x),_y(y)//有参构造函数
{
cout<<"Test(int x,int y)"<<endl;
}
~Test(){//析构函数
cout<<"~Test()"<<endl;
}
private:
int _x;
int _y;
};
class Test1{
public:
Test1():abc1(10,20),abc2(20,30){//需要进行初始化对象列表
cout<<"Test1()"<<endl;
}
~Test1(){
cout<<"~Test1()"<<endl;
}
private:
Test abc1;//成员对象
Test abc2;//编译器不认识
};
Test1():abc1(10,20),abc2(20,30)这行代码的出现本质上是因为Test类中没有默认构造函数,而对于Test abc1;这条代码而编译器不认识,为了解决这个问题C++提供了初始化列表操作Test1():abc1(10,20),abc2(20,30)这里abc1里面的参数是类Test中的构造函数Test(int x,int y):_x(x),_y(y)所确定的
2.构造函数和析构函数的执行顺序
在有成员对象的类里面往往先执行成员对象的构造函数,在执行自己的构造函数
而析构函数则相反,先执行自己的析构函数,在执行成员对象的析构函数
class Test{
public:
Test(int x,int y):_x(x),_y(y)
{
cout<<"Test(int x,int y)"<<endl;
}
~Test(){
cout<<"~Test()"<<endl;
}
private:
int _x;
int _y;
};
class Test1{
public:
Test1():abc1(10,20){
cout<<"Test1()"<<endl;
}
~Test1(){
cout<<"~Test1()"<<endl;
}
private:
Test abc1;
};
Test(int x,int y)
Test1()
~Test1()
~Test()
输 出 量
由这个输出量就可以来啦
三.拷贝函数
1.对象赋值
能不能利用一个初始化了的对象在对一个新的对象进行初始化?c++编译器又是怎么进行这样的操作的呢?如果
#include<iostream>
using namespace std;
class A{
public:
int a;
int b;
int *p=new int[4];
A(int x,int y):a(x),b(y)
{
}
~A(){
//delete[] p;//这条语句对对堆上的空间进行释放,但是会导致错误访问内存空间
cout<<"~A()"<<endl;
}
};
int main(){
A a(10,20);
A b=a;//利用初始化好了的a去初始化b
for(int i=0;i<4;i++)
a.p[i]=i;
cout<<a.a<<endl;
cout<<b.a<<endl;//看b里面的值是否与a里面的值相同
for(int i=0;i<4;i++)
cout<<b.p[i]<<endl;
cout<<&a.p<<endl;//输出a对象中p的地址
cout<<&b.p<<endl;//输出b对象中p的地址
return 0;
}
10
10
0
1
2
3
0x6ffe08
0x6ffdf8
输 出
从输出可以看出来a.a与b.a完全相同,而对象a.p与b.p中的值完全相同,连地址都完全相同。就意味着a.p与b.p指向了相同的内存空间,那么更改a.p里面的值势必会改变b.p里面的值,这个不是我们希望看到,我们需要的是两个独立的对象。而且在函数的最后必将两个对象都进行销毁,会对对象里面的指针的堆空间进行释放两次,对内存空间进行非法访问。
有两个方法可以解决这个问题
1.用memcpy函数
A a(10,20);
A b;//从新定义了一个对象
for(int i=0;i<4;i++)
a.p[i]=i;
b.a=a.a;
b.b=a.b;
memcpy(b.p,a.p,4*sizeof(int));//利用这个函数将a.p中的内容拷贝到b.p中
虽然解决这个问题,但是不得不说这样的方法有点鸡肋,假设要初始化很多个对象我们不能一个一个去赋值吧,那么有没有更加简单的方法呢
2.拷贝函数
拷贝函数: 类名(const 类名 & 对象)
同构造函数和析构函数一样,拷贝函数是由编译器默认实现的,我们来验证一下。
#include<iostream>
#include<Cstring>
using namespace std;
class A{
public:
int a;
int b;
int *p=new int[4];
A(int x,int y):a(x),b(y)
{
}
~A(){
delete[] p;
cout<<"~A()"<<endl;
}
A(const A &t){//
cout<< "A(const A &a)"<<endl;
a=t.a;
b=t.b;
p=new int[4];//
memcpy(p,t.p,4*sizeof(int));//将t.p中的内容拷贝到p里面
}
};
int main(){
A a(10,20);
for(int i=0;i<4;i++)
a.p[i]=i;
A b=a;
cout<<a.a<<endl;
cout<<b.a<<endl;
for(int i=0;i<4;i++)
cout<<b.p[i]<<endl;
cout<<&a.p<<endl;
cout<<&b.p<<endl;
return 0;
}
A(const A &a)
10
10
0
1
2
3
0x6ffdf8
0x6ffde8
~A()
~A()
对拷贝函数进行深度解剖
A(const A &t)首先是这个函数名为什么用const,我们希望将初始化好的对象a传到拷贝函数里面,但是不希望将a中的数据改变(a.a=10就是非法操作),所以用const。
那么为什么用&呢?为什么不能直接传a过来呢?这是因为将a传过来就变成了A(const A t)而相当于执行了t=a;就会一直调用拷贝函数,进入死循环中。
a=t.a;拷贝函数是将A b=a;的右值传过去,就相当于a=t.a和A b=a;左值对应左值,右值对应右值。
p=new int[4];//
memcpy(p,t.p,4*sizeof(int));
这两行代码可以说是拷贝函数的灵魂,在堆上开辟了新的空间,然后用memcpy函数将t.p里面的内容拷贝到p中
浅拷贝和深拷贝
浅拷贝:
1.同一对象之间可以相互赋值,使得两个成员变量的值相同,但是对象的指针指向同一片空间,这样的拷贝构造函数称为浅拷贝。
2.一般来说浅拷贝是没有副作用的,但是类里面有指针的时候就会使得指针都指向了同一片内存空间,在执行析构函数的时候就会多次释放空间,导致非法操作空间。
3.当我们没写拷贝函数时就会调用默认构造函数,默认构造函数是浅拷贝函数。
深拷贝:
在实现拷贝构造函数时,对指针进行申请空间,使得指针指向不同的内存空间,在将右值中的值复制到左值中去。
四.引用在函数中的作用
void Test(A t)//A是类名 执行Test t=a;
Test(a);
当在执行这条代码时,编译器将自动执行Test t=a;调用了拷贝构造函数,而且在函数执行结束后会再调用析构函数,降低了函数执行的效率。为了避免这种调用建立临时变量浪费的时间,我们采用引用。
void Test(const A &t)
Test(a);
当采用引用的方法可大大提高程序执行的效率。
当然,如果不需要改变引用的值可以用const修饰。
五.C++中的内存模型
在c语言中并没直接将数据和函数进行关联,但是在C++中通过抽象数据类型,在类中将函数和数据进行关联。
那么在内存中函数和数据是怎么存储的呢?
可以打印出对象的大小从而判断出函数是否和数据在同一空间
#include<iostream>
#include<Cstring>
using namespace std;
class A{
public:
int a;
int b;
A(int x,int y):a(x),b(y)
{
}
int get(){
return a;
}
};
int main(){
A a(10,20);
cout<<sizeof(a)<<endl;
return 0;
}
8
可以看出数据其实和函数没有在同一个区域。
函数是在代码区的,而数据是在栈上,静态变量放在数据区的
在c语言中字符串是用字符数组来实现的,而对于应用层而言,将会多次使用字符串,如果在使用字符数组就会降低效率,所以在C++标准,用类重新定义了字符串。
一、string类
头文件#include<string>
注意:头文件没有.h
string支持字符串大小比较
string支持字符串的拼接
string支持字符串的查找和替换
string也具备数组的灵活性,可以使用[]来访问1.构造方法
#include<iostream>
#include<string>
using namespace std;
int main(){
string s1;//构造一个空的字符串
cout<<s1<<endl;
cout<<s1.size()<<endl;//调用字符串对象的函数求长度
cout<<s1.length()<<endl;//调用字符串对象的函数求长度
string s2="hello";//字符串初始化成hello
cout<<s2<<endl;//hello
string s3(s2);//将s3的内容初始化s2
cout<<s3<<endl;//hello
string s4(4,'a');//初始化s4为4个字符a
cout<<s4<<endl;//aaaa
string s5(s2,1,4);//将s5的内容初始化为s2下标为1开始长度为3的字符串
cout<<s5<<endl;//ello
string s6(s2,1,10);//当最后一个参数的大小大于s2的大小时就最多将从下标为1开始的所有拷贝
cout<<s6<<endl;//ello
cout<<s6.length()<<endl;//4
// char *p="hello world";C++编译器对类型有着严格的要求
//左边是char * 右边是const char*
const char *p="hello world";
string s7(p); //将s7的内容初始化为p的内容
cout<<s7<<endl;//hello world
return 0;
}
2.赋值方法
1.利用char*类型的变量和常量,以及char类型的变量和常量对其进行赋值。
2.利用对象的成员函数 assign
#include<iostream>
#include<string>
using namespace std;
int main(){
string s1;//定义
s1="hello world";//赋值
string s2;
s2=s1;//赋值
cout<<s2<<endl;// hello world
s2='A';//虽然赋值成'A'但是编译器会将他转化成字符串"A"
cout<<s2<<endl; //A
// assign()进行赋值,用法类似于初始化字符串
s2.assign(s1);//将s2的值赋值为s1
cout<<s2<<endl;//hello world
s2.assign(3,'a');//将s2的赋值为3个字符a
cout<<s2<<endl;//aaa
s2.assign(s1,1,4);//将s2赋值为s1下标从1开始,长度为4的字符串
cout<<s2<<endl;//ello
return 0;
}
assign方法与初始化的方法类似。
3.求长度方法
利用成员函数size和length
cout<<s1.size()<<endl;//调用字符串对象的函数求长度
cout<<s1.length()<<endl;//调用字符串对象的函数求长度
4.字符串的拼接
C++中字符串的拼接有两种方式
1.用运算符‘+’;
2.用成员函数 append
#include<iostream>
#include<string>
using namespace std;
int main(){
string s1="hello ";//赋值
string s2="world";
/*
string s3;
s3=s1+s2;
cout<<s3<<endl;//hello world
*/
s1.append(s2);//将s2的值追加到s1
cout<<s1<<endl;//hello world
s1.append(s2,1,3);//将s2下标从1开始,长度为4的字符串追加到s1
cout<<s1<<endl;//hello worldorl
s1.append(4,'a');//对s1追加4个字符a
cout<<s1<<endl;//hello worldorlaaaa
return 0;
}
append函数的返回是一个引用,而且append函数相当于追加,append函数的用法和之前讲过的用法类似。
4.字符串的比较
字符串的比较有两种方法:4.字符串的比较
1.可以使用=,>,<,!=等比较运算符,对string类型进行计较。
2.可以使用成员函数compare进行比较。
#include<iostream>
#include<string>
using namespace std;
int main(){
string s1="hello ";//赋值
string s2="hellow";
bool ret;
ret=s1>s2;
if(ret==true)
cout<<"s1>s2"<<endl;
else if(ret==false)
cout<<"s1<s2"<<endl;
int tem;
tem=s1.compare(s2);`在这里插入代码片`
if(tem>0)
cout<<"s1>s2"<<endl;
else if(tem==0)
cout<<"s1==2"<<endl;
else if(tem<0)
cout<<"s1<s2"<<endl;
//tem=s1.compare(s2,2,4);没有这个用法
tem=s1.compare("hellow");//将s1与hellow进行比较
cout<<tem<<endl;//-1
tem=s1.compare(1,2,s2);//将s1的下标为1长度为2的字符串与s2进行比较
cout<<tem<<endl;//-1
//tem=s1.compare(s1,s2,2,4);没有这个用法
tem=s1.compare(1,2,s2,1,2);//将s1从下标为1长度为2的字符串与s2下标为1长度为2的字符串进行比较
cout<<tem<<endl;//0
return 0;
}
运算符的返回类型是bool类型,返回true为真,返回值为false为假。
compare的返回有三中情况,等于0的情况相等,大于0就大于,小于0小于。compare的用法于前面的函数用法类似。
5.求子串和交换
子串:
用substr函数
string s2=s1.substr(2,4);求下标2开始长度为4的字符串
string s1="it is ok";
string s2=s1.substr(2,4);// is
交换:
用swap函数
string s1="it is ok";
string s3="hello world";
s1.swap(s3);
cout<<s1<<endl;//hello world
cout<<s3<<endl;//it is ok
6.查找和替换
1.查找
string类有一些查找子串和字符的成员函数,他们的返回值都是字符或者子串的在字符串的位置。如果找不到,则返回string::npos。string::npos是在string类中定义的一个静态常态。这些函数如下:
find:从前往后查找子串或字符出现的位置。
rfind:从后往前查找子串或字符出现的位置。
if(s1.find("e")!=string::npos){
cout<<s1.find("e")<<endl;//1
}
if(s1.rfind("l")!=string::npos){
cout<<s1.rfind("l")<<endl;//9
}
2.替换
replace成员函数可以对string对象中的子串进行替换,返回值为自身的引用。
string s1="hello world";
s1.replace(1,3,"12345",2,4);//将"12345"的子串从下标为2到4的子串去替换 s1下标为1到3
cout<<s1<<endl;//h345o world
string s2("12345");
s2.replace(2,3,5,'o');//将s2的子串从下标为2到3的子串去替换为5个'o'
cout<<s2<<endl;//12ooooo
int n=s2.find("ooooo");
s2.replace(n,5,"XXX");//将s2的子串从下标为n到5的子串去替换为'XXX'
cout<<s2<<endl; //12XXX
return 0;
7.删除和替换
删除:
erase成员函数可以删除string对象中的子串,返回值为对象自身的引用。列如:
string s1="hello world";
s1.erase(1,3);//删除s1下标为1到3的子串
cout<<s1<<endl;//ho world
s1.erase(5);//删除s1下标为5之后的子串
cout<<s1<<endl;//ho wo
插入字符串:
insert成员函数可以在string对象中插入一个字符串,返回自身引用。
string s1="hello world";
s1.insert(2,"123");//在s1下标为3插入字符串"123"
cout<<s1<<endl;//he123llo world
s1.insert(3,5,'X');//在s1下标为3插入5个'X'
cout<<s1<<endl;//he1XXXXX23llo world