c++第四章
Posted 歌咏^0^
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++第四章相关的知识,希望对你有一定的参考价值。
目录
一、拷贝构造函数
当我们用一个对象去初始化另外一个对象的时候,参数很明显是我的一个类,里面做的初始化很有可能是多种多样的,因此它的实现应该是和我简单的构造函数不一样的。如果我们的成员里面有指针,这个指针有可能是自己的一个独立空间
class people
{
int * a;
public:
people():a(new int(1024)){}
~people(){delete a;}
}
通过这个类定义的对象去初始化另外一个对象的时候,另外的那个对象需求这个a的地址,如果是简单的值得赋值,那么就会遇到一个问题 -> 两个指针指向了同样的一个空间,那么由people是存在这个析构函数的 , 我在上方的这个对象里面,会析构这个地址,而现在用这个对象创建出来的这个对象也需要析构这个地址,因此这个初始化很明显会存在两种不同的操作:
1 简单的成员赋值
2 我们需要新开空间
因此里面的操作就跟原先的构造函数不一样,所以这种初始化我们就应该要另寻机制 -> 拷贝构造函数
声明:
拷贝构造函数编译器在你没有写拷贝构造函数的时候,会默认给你生成一个拷贝构造函数,里面做的事情是逐成员赋值。当我们自己写了自己的拷贝构造函数,那么编译器就不会给你生成了
拷贝构造函数也是一个构造函数,实际上也是对我们的对象进行初始化的,只是这个初始化的值来源于另外一个对象 ,因此它的参数只有一个,就是本类的一个引用
语法:
类名(类名 &){}
我们在使用的时候注意,由于是一个对象初始化另外一个对象,仅此而已,因此我们是不能将我传进来的这个对象的值改变的,因此我们需要加const
类名(const 类名 &){}
里面的操作看自己的实现,更多的时候我们是直接赋值,少数的时候我们需要开空间
void func(const char * p)
{
printf("%s\\n",p);
*p = 'j';//p指向的这个空间不可更改
char * q = (char *)p;//c语言里面是可以做的
}
int main()
{
char arr[] = "hehe";
func(arr);
}
区别于c的const c++里面的const就一定是const声明了就不能更改,除非你不声明,因此 const 类名 & 和类名 & -> 在c++里面实际上参数不一样
建议不要这么做,初始化就是初始化,没有那么多的不一样的地方,建议写拷贝构造的时候建议加上const
void func(const char * p)
{
printf("%s\\n",p);
*p = 'j';//p指向的这个空间不可更改
char * q = (char *)p;//c语言里面是可以做的
}
int main()
{
char arr[] = "hehe";
func(arr);
}区别于c的const c++里面的const就一定是const
声明了就不能更改,除非你不声明
因此 const 类名 & 和类名 & -> 在c++里面实际上参数不一样
建议不要这么做,初始化就是初始化,没有那么多的不一样的地方
建议写拷贝构造的时候建议加上const
struct people
{
people(int a = 0,char b = 0):a(a),b(b){}
people(const people & p)
{
a = p.a;
b = p.b;
}
void show()
{
cout << a << " " <<b << endl;
}
private:
int a;
char b;
}
int main()
{
people p(1024,'q');
p.show();
people p1(p);
p1.show();
}
二、深拷贝与浅拷贝
1浅拷贝逐成员赋值
但是有的时候会出现问题,有指针 很有可能这个指针指向的是同一个空间,并且一改 两个人都会改, 一释放就多次释放,这个时候我们就应该让这个指针指向自己应该需要的空间
这个时候我们就应该要重新申请另外的空间 -> 这种情况我们叫深拷贝
2深拷贝
申请的空间,要将原空间里面的内容拷贝过来
注意:是完全的复制
memcpy
不一定所有的指针都是需要用深拷贝的,具体的事情具体对待,有的时候浅拷贝 有的时候是深拷贝
struct people
{
people(int a = 0,char b = 0):a(a),b(b),ptr(new int(a)){cout << "构造函数" << endl;}
people(const people & p)
{
a = p.a;
b = p.b;
//ptr = p.ptr;
//根据深拷贝的原则 我们这里就需要申请新的空间
ptr = new int;
*ptr = *(p.ptr);
cout << "拷贝构造函数" << endl;
}
~people()
{
if(ptr != nullptr)
{
cout << ptr << endl;
delete ptr;
ptr = nullptr;
}
cout << "析构函数" << endl;
}
void show()
{
cout << a << " " << b << " " << *ptr << endl;
}
private:
int a;
char b;
int * ptr;
};
people func()
{
people p(111,'s');//构造函数必然会在这里调用
return p;
}
int main()
{
/*
people p(1024,'q');
people p1(p);
people p2 = p;
people p3 = func();//按照道理来说是要调用拷贝构造的 但是最后
//只调用了一次构造函数 编译器会在这里优化
//有的时候优化不一定好 我需要不优化
//编译的时候后面加上 -fno-elide-constructors
*/
people q(1024,'r');//q.ptr == 0x01 -> delete q.ptr ->delete 0x01
//q.ptr = nullptr
q.show();
people q1(q);//q1.ptr == 0x01 -> delete q1.ptr ->delete 0x01
//当q1结束生命的时候 会再一次free 这个时候就多次free一个地方了
//不允许
q1.show();
}
三、this指针
有的时候我们需要在类里面使用自己本身,实际上就是对象使用对象自己本身,类里面会封装方法,而这些方法是属于这个对象的,我们在使用这个方法的时候必须要将这个类实例化,而在这些方法里面我们就有可能会使用指针->包括这个对象自己的地址
如果像方法一样带我自己的地址进去,好像是可以,但是有一个时候它绝对不行 -> 初始化的时候,按照道理来说我应该是可以去使用自己的,为了解决这个需求,在每一个类里面都会隐藏一个指针 -> this
this不是在类的 -> 没有实例化的时候实际上就没有实体,没有实体哪来地址,因此这个this一定是属于对象自己本身的
this就是对象自己的地址,对象不一样,那么this就不一样,谁调用我this就是谁
people p;
&p -> this,得到这个this我们就可以用这个对象里面的成员了
使用方式跟在外面使用指针是一样的
this -> 成员 这个this肯定是在类里面用得
class people
{
int a;
int b;
public:
people(int a,int b):a(a)
{
this ->a = a;
this ->b = b;
}
void setdata(int a,int b)
{
this -> a = a;
}
}
四、异常机制
c++里面错误分为两种
1 编译错误(这种问题是最简单的)
2 运行错误(这种问题就非常让人不好受)
c++里面为了优化运行错误而有了异常处理机制,所谓的异常,就是在程序的运行过程中,由于系统条件、操作不当等原因引起的程序崩溃,这种情况我们就叫异常
常见的异常:除数为0,内存分配失败,你要操作一个没有打开的文件,我们往一个没有读端的管道里面写......
异常一旦产生,我们的程序就会崩溃,对我们来说是一种毁灭性的打击。为了提高我们程序的健壮性,加强它的容错能力,我们需要对这些异常,进行处理,防止我们的程序意外终止。在c语言里面我们实际上也经常在干这个事情
if(p == NULL)//这个时候实际上就是在做异常处理
//发现这个错误我们及时处理就可以了
但是用c语言里面的这一套我们需要在各种时间里面都需要去判断,这样就有点不美好,c++为了继续去完成这一套,设置了异常处理,这个机制由三个部分去组成:
1 抛出异常
2 捕捉这个异常
3 处理这个异常
c++为了这三个步骤引出了三个关键字:throw try catch
try:检测这个异常
throw:抛出这个异常
catch:关联这个异常
try
{
//检测异常 实际上还是c语言里面的那一套
if(a == 0)
throw 异常值;//抛出这个异常
}
catch(异常值类型1)
{
//这里就可以进行错误处理
}
catch(异常值类型2)
{
//这里就可以进行错误处理
}
catch(...)//其余的类型都往这个地方来
{
}
...这三个点必须写在最后面,前面没有匹配到,那么就匹配这里
eg.
//有一个功能去做除法运算
double function(double a,double b)
{
if(b > -0.0000001 && b < 0.0000001)//项目里面判断一个double是否等于0必须这么来
{
cout << "异常出现了" << endl;
throw 123;//抛出异常
}
else
{
return a / b;
}
}
int main()
{
double a = 250;
double b = 0;
double c = 0;
try
{
//调用可能出现异常的功能的代码
//我们就应该让它处于try里面
c = function(a,b);
}
//catch(int)//由于上方抛出的是异常值 那么我们实际上是可以接收这个值
//直接定义变量 如果你不定义变量相当于不获取这个值 只关联这个类型
catch(int hehe)//这个hehe就可以去获取你抛出的异常值
{
cout << "你的除数为0 = " << hehe << endl;
}
catch(char)
{
cout << "兄弟,你是what异常" << endl;
}
catch(...)
{
cout << "姐妹们,其它的都在这里" << endl;
}
return 0;
}
我们发现上述的代码好像就是脱裤子放屁,把他变得复杂了。实际上工程里面异常都是用的这种机制,这种机制最大的优势 -> 错误和错误处理分开进行,异常处理机制是面向对象的一种设计思维,我们以后的程序错误处理操作应该要遵循,实际在项目里面有非常多问题 -> 异常处理一定要做关注,将main函数里面的东西全部包含在try 貌似可以,实际也可以,但是效率低下,我们的这种机制是为防止错误,不是为了防止正确的 -> 正确的地方该怎么跑就怎么去跑
以上是关于c++第四章的主要内容,如果未能解决你的问题,请参考以下文章