C++的构造函数与析构函数与拷贝构造函数

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

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

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

3.默认拷贝构造函数(系统默认生成的拷贝构造函数,只负责进行简单的赋值操作,即浅拷贝),对属性进行值拷贝

 

构造函数调用规则如下:

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

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

  • --------------------------------------------------------------------------------------------------------------------------------------------------------------------------

1.构造函数的作用:(初始化时构造函数就开始发挥作用)

构造函数用来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,默认无参数的构造函数不需要用户来调用它,而是在建立对象时自动执行。构造函数的功能是由用户定义的,用户根据初始化的要求设计函数体和函数参数。

2.构造函数的注意事项:

①、构造函数的名字必须与类名同名,不能随意命名,这样的话才能让编译器认为该函数是构造函数,而不是类的普通成员函数;
②、构造函数不具有任何类型,不返回任何值,连 void 都不是;
③、默认的构造函数不需要用户调用,它是对象建立的时候自动被系统调用,用来初始化刚刚建立的对象的;
④、如果用户没有定义自己的类的构造函数,那么系统会自动生成一个默认的构造函数,只不过该构造函数的函数体是空的,也就是什么都不做。

总结:构造函数是用来初始化值的,用于类对象的。

代码例子:

(1)调用无参构造函数

1.
Class Student{
    int age;
    string name;
 
}

int main(){

  Student stu[100];//这样会创建一百个对象,默认会调用空的构造函数,即会默认等价于下面这段函数。   
}

2. Class Student{
int age; string name; Student(){} } int main(){ Student stu[100];//这样会创建一百个对象,默认会调用空的构造函数,即会默认等价于上面这段函数。
//
Student stu[100]==Student stu[100]() 默认调用构造函数
 }

(2)调用有参构造函数

#include <iostream>
using namespace std;

class Student{
    public:
        int age;
        Student(){std::cout << "101" << std::endl;}
        Student(int age){
            this->age = age;
    std::cout << "你好" <<age<<"岁了"<< std::endl;  
  }
};
int main() {
Student stu0(60);//单个调用有参数的构造函数    
Student stu1[3]={Student(30),Student(30),Student(30)};//数组对象调用有参数的构造函数    
}

运行结果:

 2.析构函数(当生命周期快结束时才调用析构函数,可以理解构造函数与析构函数相反,一般用于内存释放,清理空间)

析构函数也是一个在类中跟构造函数类似的特殊功能的成员函数。只不过它的作用是与构造函数相反,是在对象的生命周期结束的时候会被自动调用的。在C++中析构函数的名字跟类名相同,并且前面带上一个取反的符号~,表达的意思也就是跟构造函数的过程相反。

默认情况下,如果类的设计者没有自己定义析构函数,那么编译器会自动为该类生成一个默认的析构函数,只不过函数体是空的,也就是什么都没做。所以,如果需要在对象被删除的时候做一些操作的话,那么就得自己定义析构函数喽。

以下几种情况会自动调用析构函数:
①、如果在一个函数中定义了一个局部变量的对象,那么当这个函数执行结束时也就是该变量对象生命周期结束的时候,所以析构函数会被自动调用;
②、全局变量或者static类型的变量,他们的生命周期一般是在程序退出的时候,这时候该对象的析构函数才会被调用;
③、如果是用new操作符动态的创建了一个对象,只有当用delete进行释放该对象的时候,析构函数才会被调用;

析构函数的作用:
先拿构造函数来说话,构造函数是新建对象吗?回答:不是,而是在对象被创建出来之后自动被调用的,用来初始化相关信息的函数。同理,析构函数也不是用来删除对象的,而是当对象被删除的时候自动会被调用的,用来做一些对象被删除之前的清理工作。只要对象的生命周期结束,那么程序就会自动执行析构函数来完成这个工作的。

析构函数的特点:
析构函数不返回任何值,没有函数类型,也没有任何函数的参数。由于上面这些特点,所以析构函数不能被重载,所以说一个类可以有多个构造函数,但只能有一个析构函数

#include <iostream>
using namespace std;

class Student{
    public:
        int age;
        Student(){std::cout << "101" << std::endl;}
        Student(int age){
            this->age = age;
    std::cout << "你好" <<age<<"岁了"<< std::endl;  
  }
        ~Student(){cout<<"我是析构函数,程序结束前时调用"<<endl;}
};
int main() {
Student stu0(60);//单个调用有参数的构造函数    
Student stu1[3]={Student(30),Student(30),Student(30)};//数组对象调用有参数的构造函数    
}

 3.拷贝构造函数

C++中拷贝构造函数调用时机通常有三种情况

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

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

  • 以值方式返回局部对象

class Person {
public:
    Person() {
        cout << "无参构造函数!" << endl;
        mAge = 0;
    }
    Person(int age) {
        cout << "有参构造函数!" << endl;
        mAge = age;
    }
    Person(const Person& p) {
        cout << "拷贝构造函数!" << endl;
        mAge = p.mAge;
    }
    //析构函数在释放内存之前调用
    ~Person() {
        cout << "析构函数!" << endl;
    }
public:
    int mAge;
};

//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {

    Person man(100); //p对象已经创建完毕
    Person newman(man); //调用拷贝构造函数
    Person newman2 = man; //拷贝构造

    //Person newman3;
    //newman3 = man; //不是调用拷贝构造函数,赋值操作。因为newman3是先存在后,再赋值的,此时只是简单的赋值操作,初始化操作时才会调用拷贝构造函数
}

//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() {
    Person p; //无参构造函数
    doWork(p);//调用了拷贝构造函数,参数p为上面p的副本
}

//3. 以值方式返回局部对象
Person doWork2()
{
    Person p1;
    cout << (int *)&p1 << endl;
    return p1;//此时返回的值是Person P1的一个副本里保存的值。是调用了拷贝构造函数重新形成的值,是拷贝了p1,形成副本
}

void test03()
{
    Person p = doWork2();
    cout << (int *)&p << endl;
}


int main() {

    //test01();
    //test02();
    test03();

    system("pause");

    return 0;
}

归根到底就是,只要进行了初始化赋值操作就会调用拷贝构造函数。

重点:一般编译器初始化形成的拷贝函数只会进行浅拷贝,这样在使用拷贝操作时由于进行了浅拷贝(简单的赋值操作),在结束时由于析构函数会释放空间,会引发一些问题,比如指向同一地址的指针会进行多次析构,而造成出错。

从而编译器自己形成的浅拷贝的构造函数一般是不用的,而是自己进行复写拷贝函数,解决浅拷贝多次调用析构函数后形成的内存错误问题,自己写的构造函数称为深拷贝构造函数。

**深拷贝与浅拷贝

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

 

深拷贝:在堆区重新申请空间,进行拷贝操作(目的就是为了浅拷贝和析构函数多次析构同一内存地址引起来的内存多次释放错误)

例子:

class Person {
public:
    //无参(默认)构造函数
    Person() {
        cout << "无参构造函数!" << endl;
    }
    //有参构造函数
    Person(int age ,int height) {
        
        cout << "有参构造函数!" << endl;

        m_age = age;
        m_height = new int(height);
        
    }
    //深拷贝构造函数  
    Person(const Person& p) {
        cout << "拷贝构造函数!" << endl;
        //如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
        m_age = p.m_age;
        m_height = new int(*p.m_height);//重新申请内存,避免浅拷贝的简单赋值
        //系统默认生成的是m_height = m_height,简单赋值操作的浅拷贝,因为m_height是指针,此时会简单进行地址赋值操作,析构时会多次释放同一地址
    }

    //析构函数
    ~Person() {
        cout << "析构函数!" << endl;
        if (m_height != NULL)
        {
            delete m_height;
        }
    }
public:
    int m_age;
    int* m_height;
};

void test01()
{
    Person p1(18, 180);

    Person p2(p1);

    cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;

    cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}

int main() {

    test01();

    system("pause");

    return 0;
}

穷则独善其身,达则兼济天下……
原文地址:https://www.cnblogs.com/hmy-666/p/14413207.html