c++第四章

Posted 歌咏^0^

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++第四章相关的知识,希望对你有一定的参考价值。

目录

一、拷贝构造函数

二、深拷贝与浅拷贝

三、this指针

四、异常机制


一、拷贝构造函数

        当我们用一个对象去初始化另外一个对象的时候,参数很明显是我的一个类,里面做的初始化很有可能是多种多样的,因此它的实现应该是和我简单的构造函数不一样的。如果我们的成员里面有指针,这个指针有可能是自己的一个独立空间

    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++第四章的主要内容,如果未能解决你的问题,请参考以下文章

c++面向对象程序设计 课后题 答案 谭浩强 第四章

C++笔记第四章--类型和声明

c++第四章

[c++]第四章概念题 | 运算符重载

数据结构(C++)——第四章 字符串和多维数组

第四次寒假作业