构造函数与析构函数的问题总结

简介: 构造函数与析构函数的问题总结

C++中有哪些构造函数

构造函数的定义

构造函数被使用来对象创造时初始化工作,即在构造函数中为对象赋初值

C11前是不允许成员变量定义时就初始化的操作(C11之后是允许的,原理请参考C11中类成员变量定义时初始化问题),例如

class Solution {
public:
  int arg = 1; //C11之前是会报错  (C11标标准出来后在类中是允许这样初始化的)
}

因此就设计通过构造函数还在实例化对象时初始化成员变量。如下几种常见的构造函数

构造函数的分类

  • 默认构造函数
  • 列表初始化构造函数
  • 拷贝构造函数
  • 移动构造函数(move和右值引用)

程序例如:

#include <iostream>
using namespace std;
class Solution {
public:
  Solution(){//默认构造函数
      this->arg = 1;
      cout << "default constructor" << endl;
  }
  Solution(int num):arg(num){  //列表初始化构造函数
      cout << "list initialization constructor" << endl;
  } 
  Solution(const Soulution& s) { //拷贝构造函数
      this->arg = s.arg;
      cout << "copy constructor" << endl;
  }
  Solution(Solution &&s) : arg(s.arg) {  //移动构造函数    输入参数是一个右值,&&表示右值引用
      cout << "move constructor" << endl;
  }
public:
  int arg;
};
int main()
{
    cout << "-------------------------1-------------------------" << endl;
    Solution a;  //调用默认构造函数
    cout << "-------------------------2-------------------------" << endl;
    Solution b(1); //调用列表初始化
    cout << "-------------------------3-------------------------" << endl;
    Solution c(a);  //调用拷贝构造
    cout << "-------------------------4-------------------------" << endl;
    Solution d(move(a));  //使用move函数,将a对象转为右值,调用移动构造
}

输出结果:

构造函数相关问题

问题一:什么情况下会调用拷贝构造函数?

除了上面初始化类对象时通过引用传递对象会调用拷贝构造函数的情况,还有以下几种特殊情况也会调用拷贝构造函数。

情况1:使用“=”,用类的一个实例化对象去初始化另一个对象的时候

情况2:函数参数是类的对象,并且不是引用传递(此处是函数参数,不是类对象初始化)

情况3:函数的返回值是函数体内的局部类对象时 ,且返回方式是值传递,会在返回值的地方调用拷贝构造函数

例如:

#include <iostream>
using namespace std;
class Solution {
public:
    Solution(){//默认构造函数
        this->arg = 1;
        cout << "default constructor" << endl;
    }
    Solution(const Solution& s) { //拷贝构造函数
        this->arg = s.arg;
        cout << "copy constructor" << endl;
    }
public:
    int arg;
};
void function1(Solution s) {  //值传递,不是引用传递
    cout << "function1()" << endl;
}
int main()
{
    Solution a;
    cout << "-------------------------1-------------------------" << endl;
    Solution b = a; //情况1
    cout << "-------------------------2-------------------------" << endl;
    function1(b);  //情况2
    return 0;
}

结果:

从结果可以看到Solution b = a; 和function1(b);都调用了拷贝构造函数。

情况3这种情况,如下面这种形式:

Solution function2() { //函数的返回值是类对象
    cout << "function2()" << endl;
    Solution s;
    return s;
}

这种情况在不同的平台会有不同的结果,在Linux中使用g++时返回局部对象不会调用拷贝构造函数;在windows中使用visual studio 2019时,返回局部对象,且是值传递会调用拷贝构造函数。

问题二:赋值初始化和列表初始化的区别?

默认构造函数拷贝构造函数这类使用赋值初始化操作的是通过在函数体内进行赋值初始化;列表初始化在冒号后使用初始化列表进行初始化。 主要区别在于数据初始化的时间不同: 赋值初始化是在所有的数据成员被分配内存空间后才进行的。 列表初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式(此表达式必须是括号赋值表达式),就赋值。分配一个数据的空间就初始化赋值。

注意:列表初始化时成员变量的初始化顺序是以数据成员的声明顺序一致。与列表初始化的顺序无关

问题三:构造函数和析构函数的执行顺序 ?

构造函数:

作为一个继承了父类的派生子类,子类实例化对象时构造函数的执行顺序如下:

① 虚拟基类的构造函数(多个虚拟基类则按照继承的顺序执行构造函数)。

② 基类的构造函数(多个普通基类也按照继承的顺序执行构造函数)。

③派生类类型的成员对象的构造函数(按照初始化顺序)

④ 派生类自己的构造函数。

析构函数:

① 调用派生类的析构函数;

② 调用派生类成员类对象的析构函数;

③ 调用基类的析构函数。

④ 调用虚拟基类的析构函数。

问题四:构造函数能否声明为虚函数或者纯虚函数,析构函数呢?

构造函数:

构造函数不能定义为虚函数。在构造函数中可以调用虚函数,不过此时调用的是正在构造的类中的虚函数,而不是子类的虚函数,因为此时子类尚未构造好。

原因:虚函数对应一个vtable(虚函数表),类中存储一个vptr指向这个vtable。如果构造函数是虚函数,就需要通过vtable调用,可是对象没有初始化就没有vptr,无法找到vtable,所以构造函数不能是虚函数。

析构函数:

析构函数可以为虚函数,并且一般情况下基类析构函数要定义为虚函数。

原因:从上面析构函数的调用顺序可知,首先要调用派生子类的析构函数。因此只有在基类析构函数定义为虚函数时,调用操作符delete销毁指向对象的基类指针时,才能准确调用派生类的析构函数(从该级向上按序调用虚函数),才能准确销毁数据。

析构函数可以是纯虚函数,含有纯虚函数的类是抽象类,此时不能被实例化。但派生类中可以根据自身需求重新改写基类中的纯虚函数。

问题五:构造函数、析构函数、可否声明为内联函数inline

首先,将这些函数声明为内联函数,在语法上没有错误。但是《Effective C++》指出:将构造函数和析构函数声明为inline是没有什么意义的,编译器并不真正对声明为inline的构造和析构函数进行内联操作,因为编译器会在构造和析构函数中添加额外的操作(申请/释放内存,构造/析构对象等),致使构造函数/析构函数并不像看上去的那么精简,因此不会使用inline功能。

问题六:构造函数和析构函数可以调用虚函数吗?

在C++中,可以调用,但不提倡

① 我们调用虚函数,一般就是使用其动态联编的功能。但是构造函数和析构函数调用虚函数时都不使用动态联编,如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本;

② 构造函数时,因为父类对象会在子类之前进行构造,此时子类部分的数据成员还未初始化,因此C++不会进行动态联编;

③析构函数是用来销毁一个对象的,在销毁一个对象时,先调用子类的析构函数,然后再调用基类的析构函数。所以在调用基类的析构函数时,派生类对象的数据成员已经销毁,也没有使用动态联编。

问题七:什么情况会自动生成默认构造函数?

前提:如果类没有构造函数,编译器不一定会生成默认的构造函数,只有在构造函数真正被需要的时候才会生成;

① 如果一个类没有任何构造函数,但它含有一个带有默认构造函数的成员对象,那么在实例化该类时,编译器就为该类合成出一个默认构造函数。 原因:如果一个类A含有多个成员类对象的话,编辑器在给A类生成无参的默认构造函数的初始化列表的位置调用类对象的构造函数,完成对对象成员的初始化。且必须按照类对象在类A中的声明顺序进行;

class A {
public:
  A() {//默认构造函数
    cout << "A()" << endl;
  }
};
class B {
public:
  A a;   //含有A类的成员对象
  int num;
};
int main() {
  B b;//实例化B需要调用A类的默认构造函数。没有给出显示定义的构造函数,但是编辑器会自动生成一个默认(无参)的构造函数
  return 0;
}

② 带有默认构造函数的基类,如果一个没有构造函数的派生类继承自一个带有默认构造函数基类,那么该派生类会合成一个构造函数调用上一层基类的默认构造函数;

class Base {
public:
  Base() {  //默认构造函数
    cout << "Base()" << endl;
  }
};
class Son:public Base {
public:
  int d;  //没有构造函数
};
int main()
{
  Son s;  //实例化Son,编译器合成的默认构造函数
  return 0;
}

③带有一个虚函数的类

原因:含有虚函数的类都拥有一个虚指针vptr。编译器需要设置vptr。因此需要生成默认构造函数,设置vptr的初值。

④ 带有一个虚基类的类

问题八:为什么拷贝构造函数必须传引用不能传值?

例如:

Solution(const Soulution& s) { //拷贝构造函数  传递本类对象必须是引用
      this->arg = s.arg;
      cout << "copy constructor" << endl;
  }
Solution(Soulution s) { //传递本类对象非引用传递,错误
      this->arg = s.arg;
      cout << "copy constructor" << endl;
  }

原因:如果用传值的方式进行传参数,那么构造实参 ”Soulution s“ 需要调用拷贝构造函数,而拷贝构造函数需要传递实参,实参中又要调用拷贝构造函数,会一直递归。所以拷贝构造不允许使用值传递。

目录
相关文章
|
6月前
|
安全 编译器 C++
C++一分钟之-构造函数与析构函数
【6月更文挑战第20天】C++中的构造函数初始化对象,析构函数负责资源清理。构造函数有默认、参数化和拷贝形式,需注意异常安全和成员初始化。析构确保资源释放,避免内存泄漏,要防止重复析构。示例代码展示了不同构造函数和析构函数的调用情况。掌握构造和析构是有效管理对象生命周期和资源的关键。
51 2
|
7月前
|
C++ Linux
|
6月前
|
编译器 C语言 C++
【C++】:构造函数和析构函数
【C++】:构造函数和析构函数
49 0
|
7月前
|
编译器 C++
【c++】构造函数和析构函数
【c++】构造函数和析构函数
【c++】构造函数和析构函数
|
7月前
|
编译器 C语言 C++
C++构造函数,析构函数
C++构造函数,析构函数
|
存储 Java 编译器
|
搜索推荐 编译器 C++
【C++构造函数与析构函数】
【C++构造函数与析构函数】
派生类构造函数和析构函数详解
派生类构造函数和析构函数详解
139 0
什么是C++编程的构造函数与析构函数
【attribute】可用于为函数或者数据声明赋属性值。 给函数分配属性的主要目的是为了让编译程序可以优化处理。 分配给函数的属性位于函数原型的声明中。
102 0
什么是C++编程的构造函数与析构函数