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

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

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“ 需要调用拷贝构造函数,而拷贝构造函数需要传递实参,实参中又要调用拷贝构造函数,会一直递归。所以拷贝构造不允许使用值传递。

目录
相关文章
|
5月前
|
人工智能 Linux API
阿里云/本地一键部署 OpenClaw 与千问/Coding Plan API配置指南+YouMind六大思维Skill,解锁AI创意思维
在日常工作与学习中,想法混乱、学习低效、创意枯竭、方案经不起推敲、汇报逻辑杂乱等问题频繁出现,成为效率提升的关键阻碍。与此同时,AI智能体框架OpenClaw(Clawdbot)凭借轻量化部署、多平台兼容、大模型无缝对接等优势,成为个人与小型团队实现自动化交互、消息处理、任务执行的重要工具,但不少零基础用户仍面临云端与本地环境配置复杂、API对接困难、报错无法排查等问题。
494 0
|
机器学习/深度学习
小尺度信道建模 | 带你读《大规模天线波束赋形技术原理与设计 》之二十六
小尺度衰落是指无线电信号在短时间或短距离(若干波长)传播后其幅度、 相位或多径时延的快速变化。这种衰落是由于同一传输信号沿不同的路径传播, 由不同时刻(或相位)到达接收机的信号互相叠加所引起的,这些不同路径到 达的信号称为多径信号,接收机的信号强度取决于多径信号的强度、相对到达 时延以及传输信号的带宽。
10734 1
 小尺度信道建模  | 带你读《大规模天线波束赋形技术原理与设计 》之二十六
|
11月前
|
JavaScript 算法 前端开发
【Docker项目实战】使用Docker部署paopao-ce微社区
【Docker项目实战】使用Docker部署paopao-ce微社区
712 84
【Docker项目实战】使用Docker部署paopao-ce微社区
|
机器学习/深度学习 算法 安全
差分隐私机器学习:通过添加噪声让模型更安全,也更智能
本文探讨在敏感数据上应用差分隐私(DP)进行机器学习的挑战与实践。通过模拟DP-SGD算法,在模型训练中注入噪声以保护个人隐私。实验表明,该方法在保持71%准确率和0.79 AUC的同时,具备良好泛化能力,但也带来少数类预测精度下降的问题。研究强调差分隐私应作为模型设计的核心考量,而非事后补救,并提出在参数调优、扰动策略选择和隐私预算管理等方面的优化路径。
786 3
差分隐私机器学习:通过添加噪声让模型更安全,也更智能
|
安全 Cloud Native 数据中心
Windows Server 2019 中文版、英文版下载 (2025 年 7 月更新)
Windows Server 2019 中文版、英文版下载 (2025 年 7 月更新)
1701 2
|
算法 数据挖掘 调度
【调度算法】NSGA III(1)
【调度算法】NSGA III
2897 0
|
存储 关系型数据库 分布式数据库
PolarDB 开源基础教程系列 1 架构解读
PolarDB 是阿里云研发的云原生分布式数据库,基于 PostgreSQL 开源版本,旨在解决传统数据库在大规模数据和高并发场景下的性能和扩展性问题。其主要特点包括: 1. **存储计算分离架构**:通过将计算与存储分离,实现极致弹性、共享一份数据以降低成本、透明读写分离。 2. **HTAP 架构**:支持混合事务处理和分析处理(HTAP),能够在同一系统中高效执行 OLTP 和 OLAP 查询。 3. **优化的日志复制机制**:采用只复制元数据的方式减少网络传输量,优化页面回放和 DDL 锁回放过程。 4. **并行查询与索引创建**:引入 MPP 分布式执行引擎。
761 8
大尺度信道建模 | 带你读《大规模天线波束赋形技术原理与设计 》之二十四
本节将介绍 3D 信道模型中 不同传输场景的大尺度衰落模型,包括路损计算、穿透损耗、直射径概率、阴 影衰落等。
大尺度信道建模  | 带你读《大规模天线波束赋形技术原理与设计 》之二十四
详解Java中的protected修饰的访问权限
详解Java中的protected修饰的访问权限
|
C语言
C语言取整方法详解
C语言取整方法详解
3820 0

热门文章

最新文章