zoukankan      html  css  js  c++  java
  • 9.C++-对象的构造函数(详解)

    大家都定义struct或class时,不能给成员直接赋值,那么对象中成员变量的初始值是多少?

    对于局部对象变量而言,其成员是个随机值,因为该变量是被分配在栈上,对于其它局部变量也是这样.

    对于全局对象变量而言,其成员都为0,因为该变量是被分配在静态存储区上,对于const修饰就是分配在只读静态存储区上.

    对于使用malloc分配的对象变量而言,其成员是个随机值,因为用户分配的地址是存在堆上

    对于使用new分配的对象变量而言,其成员也为随机,因为用户分配的地址是存在堆上

    所以:

    • 栈上创建对象时,成员变量为随机值
    • 堆上创建对象时,成员变量初始为随机值
    • 静态存储区创建对象时,成员变量初始为0

     

    构造函数

    一般而言,对象创建时都会需要一个确定的初始状态

    所以在C++中,引入了一个特殊函数-构造函数

    • 构造函数的名字必须与类名相同
    • 构造函数可以带参数,但是没有任何返回类型的声明,
    • 构造函数在创建对象时,会被自动调用

    参考下面示例:

    class Test
    {
    private:
        int i;
        int j;
    public:
        int getI() { return i; }
        int getJ() { return j; }
        Test()                                    //构造函数
        {
            i = 1;
            j = 2;
        }
    };
    
    Test t;                         //创建全局对象t,并自动调用Test()来初始化 i=1  j=2

    多个重载的构造函数

    由于构造函数可以带参数,所以一个类可以存在多个重载的构造函数

    例如:

    class Test
    {
    public:
        Test(){    }
        Test(int i){  }
        Test(int i,float t){ }
    };

    多个重载构造函数的调用

    在之前小节,分析到构造函数是用来初始化对象的.如果有多个重载的构造函数,又如何来调用呢?

    参考下面示例:

    #include <stdio.h>
    class Test
    {
    private:
             int  m_val;
    public :
             Test()
             {
                       m_val=0;
                       printf("Test() 
    ");
             }
             Test(int i)
             {
                       m_val=i;
                       printf("Test(int i)  i=%d 
    ",i);
             }
             Test(float t,int i)
             {
                       m_val=i;
                       printf("Test(float t,int i)  t=%f i=%d
    ",t,i);
             }
    };
    
    int main()
    {
             Test t1;                              //调用Test()初始化
             Test t2(2);                          //调用Test(int i) 初始化
             Test t4=1;                           //调用Test(int i) 初始化
             Test t3(1.5f,2);                     //调用Test(float t,int i) 初始化
    
             Test t4=Test(3,4);       //调用Test(3,4)返回一个临时对象,然后通过拷贝构造函数来初始化t4
    
             t1=t4;                              //赋值操作,所以不会调用Test()初始化
             return 0;  
    }

    为什么使用Test t4=1 能调用Test (4)?

    当构造函数的参数只有一个时,并且参数是其它类型,该构造函数便称为转换构造函数

    所以编译Test t4=1时,编译器会通过1来查找哪个构造函数的参数满足它,若没找到则编译报错.

     

    同样在C++中,也可以通过()来初始化变量,比如:

    int i(100);                   //转换为 int i=100;

    对象数组之手工调用构造函数

    还是以上个Test类为例:

    Test Tarray[3]={ Test(),Test(1), Test(2)};        //初始化对象数组里的m_val值分别为0,1,2; 

    从上面可以看出,一个构造函数其实是有返回值的,返回的是一个临时对象,然后赋值给Tarray[]数组里。

    这个临时对象仅仅在调用时有效,执行下个代码时,就会被注销。

    临时对象在后面第11章会讲到:11.C++-临时对象分析

    特殊的构造函数

    -无参数构造函数

    当类中没有定义构造函数时,编译器会默认提供一个函数体为空的无参构造函数,

    -拷贝构造函数 (参数为: const class_name&)

    当类中没有定义拷贝构造函数时,编译器会默认提供一个拷贝构造函数,简单的进行成员变量的复制

    1.接下来证明无参构造函数的存在,参考下面出错的示例

    #include <stdio.h>
    
    class Test
    {
    private:
             int  m_val;
    public :
             int getm(void)
             {
                return m_val;
             }
    
             Test(int val)          
             {
                m_val=val;
             }       
    };
    
    int main()
    {
             Test t1;                                        
             return 0;
    }

    编译时, 报错:

    test.cpp:21: error: no matching function for call to ‘Test::Test()’

    提示说, 定义Test t1时,没有没匹配到Test()无参构造函数.

    这是因为我们提供了构造函数,所以编译器就不再提供无参构造函数了,从而编译报错。

    也可以将上面的Test(int val)改为:

       Test(int val=0)          
      {
         m_val=val;
      }  

    这样,就相当于提供了两个函数: Test(int val), Test().

    当我们调用Test(1)时,则val=1.

    当我们调用Test()时,则val=0.

    这是C++新加的特性,在C里是没有该功能 

    2.接下来来证明拷贝构造函数的存在,参考下面示例

    #include <stdio.h>
    class Test
    {
    private:
             int  m_val;
    public :
             int getm(void)
             {
               return m_val;
             }
    
    //      Test()
    //      { 
    //      }
    //      Test(const Test& t)           //定义一个拷贝构造函数
    //      {
    //      printf("set m_val=%d
    ",t.m_val);
    //         m_val= t.m_val;
    //      }
    
    };
    
    int main()
    {
             Test t1;                                //调用Test()初始化
             Test t2=t1;                         
             printf("t1.m_val=%d  t2.m_val=%d 
    ",t1.getm(),t2.getm());              
             return 0;
    }

    运行打印:

    t1.m_val=-1078151848  t2.m_val=-1078151848

    可以发现打印的数据t1.m_valt2.m_val的值是一摸一样的,这是因为执行Test t2=t1;时,由于Test类里没有提供拷贝构造函数,所以编译器提供了一个拷贝构造函数。

    我们取消上面示例的屏蔽,使用自定义的拷贝构造函数:

    运行打印:

    set m_val=-1076378568                            
    
    t1.m_val=-1076378568  t2.m_val=-1076378568

    从打印的数据上看到,执行Test t2=t1; 时,明显调用了我们自定义的Test::Test(const Test& t)拷贝函数.

    所以当类中没有定义拷贝构造函数时,编译器会默认提供一个拷贝构造函数,进行简单的成员变量拷贝.

    深入理解拷贝构造函数 

    拷贝构造函数分为两种:

    -浅拷贝(编译器提供的)

      拷贝后对象的物理状态相同

    -深拷贝(指自己定义的)

      拷贝后对象的逻辑状态相同

    接下来看浅拷贝和深拷贝的区别,参考下面示例:

    #include <stdio.h>
    
    class Test
    {
    private:
             int  m_val;
             int  *p;
    public :
    int getm()
             {
                       return m_val;
             }
    
    int* getp()
             {
                       return p;
             }
    
            void free()
            {
              delete p;      
            }  
    
            Test(int i)
             {
           delete p;   p
    = new int(i); m_val=0; } // Test(const Test& obj) // { // p=new int; // // m_val=t.m_val; // *p=*obj.p; // } }; int main() { Test t1(2); //调用Test(int i)初始化 Test t2=t1; //调用编译器提供的拷贝构造函数,进行浅拷贝 printf("t1.m_val=%d t1.p=%p *t1.p=%d ",t1.getm(),t1.getp(),*t1.getp()); printf("t2.m_val=%d t2.p=%p *t2.p=%d ",t2.getm(),t2.getp(),*t2.getp());
    t1.free(); t2.free();
    return 0; }

    运行打印:

    t1.m_val=0 t1.p=0x9fd1008 *t1.p=2        
    
    t2.m_val=0 t2.p=0x9fd1008 *t2.p=2        
    
    *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x09fd1008 ***  

    从打印结果看出,进行浅拷贝时,两个对象的成员指针都指向同一个地址0x9fd1008,可以发现当我们释放了t1对象的成员指针后,就不能继续使用t2对象的成员指针了.

    接下来,我们取消上面示例的屏蔽,使用深拷贝,便能解决这类问题了.

    那么什么时候需要进行深拷贝?

    -当对象成员有指针时

    -当对象成员需要打开文件时

    -需要链接数据库时

    总结:

    既然,浅拷贝可以实现成员变量拷贝,所以,只要自定义拷贝构造函数,必然里面会实现深拷贝.

    下章继续学习:10.C++-构造函数初始化列表、对象构造顺序、析构函数

  • 相关阅读:
    存储过程参数不能使用函数
    .gitignore git已跟踪文件不生效
    Css选择器-层次选择器(关系选择器)
    Mysql自定义变量的作用
    jQuery 鼠标滑过Div变色
    DataTable导出excel 设置单元格格式
    layui table 详细讲解
    npm 常用命令详解
    SQL Server 2008下轻松调试T-SQL语句和存储过程
    帆软报表常用功能
  • 原文地址:https://www.cnblogs.com/lifexy/p/8580635.html
Copyright © 2011-2022 走看看