C++类和对象的构造与析构函数、深拷贝浅拷贝问题

一、构造函数与析构函数

C++利用了了构造函数和析构函数解决了上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作

对象的初始化和清理是编译器强制做的事情,不提供构造和析构函数,编译器会提供空实现的构造函数与析构函数。

1.1构造函数作用:

主要作用在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用

语法:

类名(){}

1.构造函数,没有返回值,也不写void

2.函数名称与类名相同

3.构造函数可以有参数,可以发生重载

4.程序在调用对象时会自动调用构造,无须手动调用,而且只会调用一次

例子:

#include <iostream>
using namespace std;
class Person
{
public:
        Person()
        {
                cout  <<  "构造函数的调用" << endl;
        }
 
};
void test01()
{
        Person  p;
}
int main()
{
        test01();
        return 0;
}
<<构造函数的调用

1.2析构函数作用:

主要作用在于对象销毁前系统自动调用,执行一些清理工作

语法:

~类名(){}

1.析构函数,没有返回值,也不写void

2.函数名称与类名相同,在名称前加上符号~

3.析构函数不可以有参数,因此不能发生重载

4.程序在对象销毁前会自动调用析构函数,无须手动调用,而且只会1调用一次

#include <iostream>
using namespace std;
class Person
{
public:
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
};
void test01()
{
        Person  p;
}
int main()
{
        test01();
        return 0;
}
<<析构函数调用

二、构造函数的分类与调用方法

构造函数可以按不同的类型分类

按参数可以分为有参构造与无参构造

还可以按类型分为拷贝构造与普通构造

拷贝构造就是把一个对象属性完全赋值给另外一个对象,但是原对象不能改变,因此要加const

拷贝构造函数语法:

类名(const类名 &对象名){}

2.1括号法

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void test01()
{
        Person  p1;
        Person  p2(10);
        Person  p3(p2);
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用
析构函数的调用

输出p3的年龄

cout << "p3的年龄:" << p3.age << endl;
<<p3的年龄:10

Ps:构造默认函数使用括号法不能加括号,因为这样会被认为是函数声明

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void test01()
{
        Person  p1();
        Person  p2(10);
        Person  p3(p2);
        cout  <<  "p3的年龄:" << p3.age << endl;
}
int main()
{
        test01();
        return 0;
}
<<
有参构造函数的调用
拷贝构造函数的调用
p3的年龄:10
析构函数的调用
析构函数的调用

2.2显示法

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void test01()
{
        Person  p1;
        Person  p2 = Person(10);
        Person  p3 = Person(p2);
        cout  <<  "p3的年龄:" << p3.age << endl;
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
拷贝构造函数的调用
p3的年龄:10
析构函数的调用
析构函数的调用
析构函数的调用

PS1:匿名对象:直接写Person(10);称为匿名对象,特点:当前行结束,系统会立即回收掉匿名对象,也即立即调用析构函数

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void test01()
{
        Person  p1;
        Person(10);
        cout  << "--------------------------"  << endl;
        
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
析构函数的调用
--------------------------
析构函数的调用

Ps2:不要使用拷贝函数初始化匿名对象,编译器会认为Person (p3) 相当于 Person p3调用的是默认构造也即无参构造

2.3隐式转换法

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void test01()
{
        Person  p1;
        Person  p2 = 10;
        Person  p3 = p2;        
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用
析构函数的调用

三、拷贝构造函数使用时机

3.1使用一个已经创建完毕的对象来初始化一个新对象

这点前面已经有了,就是直接调用拷贝构造函数

3.2值传递的方式给函数参数传值

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void do_work(Person p)
{
 
}
void test01()
{
        Person  p;
        do_work(p);
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用

3.3以值方式返回局部对象

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
Person do_work()
{
        Person  p1;
        return p1;
}
void test01()
{
          Person p = do_work();
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用

PS:不用的编译器可能结果不一样,在gcc中就不会出现拷贝函数的调用,打印地址也会发现地址一样

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
Person do_work()
{
        Person  p1(10);
        cout  << (int*)&p1  << endl;
        return p1;
}
void test01()
{
          Person p = do_work();
          cout << (int*)&p  << endl;
          cout << p.age  << endl;
        
}
int main()
{
        test01();
        return 0;
}
<<
有参构造函数的调用
0x7ffebf3bf96c
0x7ffebf3bf96c
10
析构函数的调用

四、构造函数调用规则

默认情况下:c++编译器会至少给一个类构造函数添加3个函数

1.默认构造函数(无参,函数体为空)

2.析构函数(无参,函数体为空)

3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

1.用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造

2.用户定义拷贝构造,c++不会再提供其他构造函数

可以记为:拷贝构造>有参构造>无参构造

4.1注释掉拷贝构造,提供自定义的有参和无参构造

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        //Person(const Person &p)
        //{
        //        age  = p.age;
        //        cout  << "拷贝构造函数的调用" << endl;
        //}
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
 
void test01()
{
          Person p1(10);
          Person p2(p1);
          cout << "p2的年龄:" << p2.age << endl;
        
}
int main()
{
        test01();
        return 0;
}
<<
有参构造函数的调用
p2的年龄:10
析构函数的调用
析构函数的调用

与前面有自定义的拷贝构造相比,可以看到它提供了默认的拷贝构造,只不过少了自己写的输出“拷贝构造函数调用”,析构函数照样调用了

可以理解为每一个对象用完都会调用析构函数

4.2注释掉拷贝构造,无参构造,提供自定义的有参构造

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        //Person()
        //{
        //        cout  << "无参构造函数的调用" << endl;
        //}
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        //Person(const Person &p)
        //{
        //        age  = p.age;
        //        cout  << "拷贝构造函数的调用" << endl;
        //}
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
 
void test01()
{
          Person p;
}
int main()
{
        test01();
        return 0;
}
<<会报错,提示你没有默认的无参构造

综合4.1和4.2可以明白,提供自定义有参构造,默认拷贝还会有,但是默认无参不会再有了

4.3注释无参构造与有参构造,提供自定义拷贝构造

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        // Person()
        // {
        //          cout << "无参构造函数的调用"  << endl;
        // }
        //有参构造函数
        // Person(int a)
        // {
        //          age = a;
        //          cout << "有参构造函数的调用"  << endl;
        // }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
 
 
void test01()
{
          Person p;
          person p1(10);
}
int main()
{
        test01();
        return 0;
}
<<35,36行都报错

这种情况下提供了自定义拷贝函数,那么默认无参构造与有参构造都不在提供

五、深拷贝与浅拷贝

5.1什么是深拷贝与浅拷贝?

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请空间,进行拷贝操作

5.2为什么需要深拷贝?

在堆区申请完空间后,需要程序员手动释放分配的空间,这是如果不使用深拷贝会带来内存重复释放问题

例子:

#include <iostream>
using namespace std;
class Person
{
public:
    
    //无参构造函数
    Person()
    {
        cout << "无参构造函数的调用" << endl;
    }
    //有参构造函数
    Person(int a, int  height)
    {
        age = a;
        m_height  = new int(height);
        cout << "有参构造函数的调用" << endl;
    }
    
    //析构函数
    ~Person()
    {
        cout << "析构函数的调用" << endl;
    }
    int age;
    int *m_height;
};
void test01()
{
     Person p1(18, 170);
     cout <<  "p1的年龄:" <<  p1.age << "  p1的身高:" << *p1.m_height  << endl;
     Person p2(p1);
     cout <<  "p2的年龄:" <<  p2.age << "  p2的身高:" << *p2.m_height  << endl;
}
int main()
{
    test01();
    return 0;
}
<<
有参构造函数的调用
p1的年龄:18  p1的身高:170
p2的年龄:18  p2的身高:170
析构函数的调用
析构函数的调用

如果不手动释放,没有报错

但是为了规范我们需要手动释放,则会报错,手动释放过程写在析构函数里面,遵循先进后出,先释放p2,再释放p1

原因分析:在使用Person p2(p1)相当于利用编译器自动提供的拷贝函数,做浅拷贝操作,最终会先释放p2的m_height内存再释放p1的m_height,这样就导致了重复释放问题

5.3解决方法:使用深拷贝

具体自定义拷贝构造函数

#include <iostream>
using namespace std;
class Person
{
public:
    
    //无参构造函数
    Person()
    {
        cout << "无参构造函数的调用" << endl;
    }
    //有参构造函数
    Person(int a, int  height)
    {
        age = a;
        m_height  = new int(height);
        cout << "有参构造函数的调用" << endl;
    }
    //自定义拷贝构造函数解决浅拷贝重复释放内存空间问题
    Person(const Person &p)
    {
        cout << "拷贝构造函数调用" << endl;
        age = p.age;
        //m_height  = p.m_height;这行其实就是默认拷贝构造函数的身高浅拷贝
        m_height  = new int(*p.m_height);
    }
    //析构函数
    ~Person()
    {
        if(m_height  != NULL)
        {
             delete m_height;
             m_height = NULL;
        }
        cout << "析构函数的调用" << endl;
    }
    int age;
    int *m_height;
};
void test01()
{
     Person p1(18, 170);
     cout <<  "p1的年龄:" <<  p1.age << "  p1的身高:" << *p1.m_height  << endl;
     Person p2(p1);
     cout <<  "p2的年龄:" <<  p2.age << "  p2的身高:" << *p2.m_height  << endl;
}
int main()
{
    test01();
    return 0;
}
<<
有参构造函数的调用
p1的年龄:18  p1的身高:170
拷贝构造函数调用
p2的年龄:18  p2的身高:170
析构函数的调用
析构函数的调用

Ps:有属性在堆区开辟,一定自己提供拷贝构造函数,防止浅拷贝带来的问题。

猜你喜欢

转载自blog.csdn.net/SL1029_/article/details/129556509