前言:

在C++中,对于一个类,C++的编译器都会为这个类提供四个默认函数,分别是:

A() //默认构造函数 ~A() //默认析构函数 A(const A&) //默认拷贝构造函数 A& operator = (const A &) //默认赋值函数。

这四个函数如果我们不自行定义,将由编译器自动生成这四个缺省的函数,下面让我们来看看这四个函数(重点是后两个)。

一. 构造函数

构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。构造函数的功能是由用户定义的,用户根据初始化的要求设计函数体和函数参数,可以是一个,也可以是多个,可以把构造函数理解为重载的一种(函数名相同,不会返回任何类型,也不可以是void类型,参数类型个数可不同)。

构造函数的作用就是对当前类对象起到一个初始化的作用,类对象不像我们基本类型那样,在很多时候都需要初始化一些成员变量。

二、默认构造函数

先看下面代码

代码内容很简单,定义了一个包含成员x,y的类Point。在序列的地方可以使用这个类

虽然我们并没有定义Point类的构造函数,我们依然可以定义Point类的pt对象并使用它,其原因是编译器会自动生成一个缺省的构造函数,其效果相当于

但是,一旦添加了其他有参数的构造函数,编译器就不再生成缺省的构造函数了

C++11的解决方案

C++11允许我们使用=default来要求编译器生成一个默认构造函数:

这样,我们就可以继续我们的美好生活了。

#include <stdio.h>

struct Point {
Point() = default;
Point(int _x, int _y) : x(_x), y(_y){}
int x;
int y;
}; int main() {
Point point;
printf("%d, %d", point.x, point.y);
}

如果是自己编写的无参构造函数的话,就需要指定成员的构造方式。默认构造函数会对数据成员进行默认初始化,不需要另外指定。这样可以省去一些麻烦。

由于整数是内置类型,而整数成员的默认初始化是不初始化,所以本例中的x,y还是需要类内初始化。这是另一个话题。

定义

默认构造函数:是无参调用的构造函数,包括两种:

  • 没有参数
  • 每个参数有初始值
class Box {
public:
Box() { /*执行任何必需的默认初始化步骤*/}
//所有参数都有默认值
Box (int w = 1, int l = 1, int h = 1): m_width(w), m_height(h), m_length(l){}
...
}

调用场合

默认构造函数在默认初始化值初始化中得到调用。

  • 默认初始化:在不使用初始化器构造变量时执行的初始化。
  • 值初始化:在以空初始化器构造对象时进行的初始化

说人话:如果构造函数在未指定参数或者提供了一个空初始化器列表,则会调用默认构造函数:

vector v1;
vector v2{};

说明

默认构造函数是一种特殊的成员函数。如果未在类中声明任何构造函数,则编译器将提供隐式的inline默认构造函数

#include <iostream>
using namespace std; class Box {
public:
int Volume() {return m_width * m_height * m_length;}
private:
int m_width { 0 };
int m_height { 0 };
int m_length { 0 };
}; int main() {
Box box1; //调用编译器生成的构造函数
cout << "box1.Volume: " << box1.Volume() << endl; // Outputs 0
}

如果声明了任何非默认构造函数、编译器不会提供默认构造函数:

class Box {
public:
Box(int width, int length, int height)
: m_width(width), m_length(length), m_height(height){}
private:
int m_width;
int m_length;
int m_height; }; int main(){ Box box1(1, 2, 3);
Box box2{ 2, 3, 4 };
Box box3; // C2512: no appropriate default constructor available
}

如果类中没有默认构造函数,将无法通过单独使用方括号语法来构造该类的对象数组。 例如,在前面提到的代码块中,框的数组无法进行如下声明:

Box boxes[3]; // C2512: no appropriate default constructor available

但是,可以使用一组初始值设定项列表来初始化 Box 对象的数组:

Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };

语法

语法:

语法 说明 o
类名 ( ) ; (1)  
类名 :: 类名 ( ) 函数体 (2)  
类名() = delete ; (3) C++11起
类名() = default ; (4) C++11起
类名 :: 类名 ( ) = default ; (5) C++11起
其中 类名 必须指名当前类(或类模板的当前实例化),或在命名空间作用域或友元声明中声明时,必须是有限定的类名。    

类定义中的默认构造函数声明

  1. struct Point {
    Point(){ x = 0; y = 0;};
    int x;
    int y;
    };

类定义之外的默认构造函数的定义(该类必须包含一条声明1)

struct Point {
Point(); //声明1
int x;
int y;
};
Point::Point(){
x = 10;
y = 0;
};

弃置的默认构造函数:若其被重载决议所选择,则程序编译失败。

struct Point {
Point() = delete ; // 错误:使用了被删除的函数‘Point::Point()’
int x;
int y;
};

预置的默认构造函数:即便其他构造函数存在,在某些情况下编译器会定义的隐式默认构造函数。

#include <stdio.h>

struct Point {
Point() = default;
Point(int _x, int _y) : x(_x), y(_y){}
int x;
int y;
}; int main() {
Point point;
printf("%d, %d", point.x, point.y);
}

类定义之外的预置的默认构造函数(该类必须包含一条声明 (1))。这种构造函数被当做是用户提供的(user-provided)(见下文以及值初始化)。

struct Point {
Point() ; // 错误:使用了被删除的函数‘Point::Point()’
int x;
int y;
}; Point::Point() = default;

隐式声明的默认构造函数

什么叫做隐式声明:用户没有声明、编译器声明

若不对类类型(struct、class 或 union)提供任何用户声明的构造函数,则编译器将始终声明一个作为其类的 inline public 成员的默认构造函数。

#include <stdio.h>

struct Point1 {
int x;
int y;
}; class Point2 {
public:
int x;
int y;
};
union Point3 {
int x;
int y;
}; int main() {
Point1 point1;
Point2 point2;
Point3 point3;
printf("%d, %d\n", point1.x, point1.y);
printf("%d, %d\n", point2.x, point2.y);
printf("%d, %d\n", point3.x, point3.y);
}

  • C++11起,当存在用户声明的构造函数时,用户仍可以关键词 default 强制编译器自动生成原本隐式声明的默认构造函数。

    #include <stdio.h>
    
    struct Point1 {
    Point1() = default; // 必须,当用户最定义了有参构造函数时编译器不会自己生成默认构造函数
    Point1(int _x, int _y) : x(_x), y(_y){}
    int x;
    int y;
    }; class Point2 {
    public:
    Point2() = default; // 必须,当用户最定义了有参构造函数时编译器不会自己生成默认构造函数
    Point2(int _x, int _y) : x(_x), y(_y){}
    int x;
    int y;
    };
    union Point3 {
    Point3() = default; // 必须,当用户最定义了有参构造函数时编译器不会自己生成默认构造函数
    Point3(int _x) : x(_x){}
    int x;
    int y;
    }; int main() {
    Point1 point1;
    Point2 point2;
    Point3 point3;
    printf("%d, %d\n", point1.x, point1.y);
    printf("%d, %d\n", point2.x, point2.y);
    printf("%d, %d\n", point3.x, point3.y);
    }

    • 隐式声明(或在其首个声明被预置)的默认构造函数,具有动态异常说明 (C++17 前)异常说明(C++17 起)中所描述的异常说明。

其他

内置类型被认为具有默认构造函数和拷贝构造函数。但是,对于内置类型的未初始化的非static变量,其默认构造函数不会被调用。内置整数类型的默认值为0,浮点数默认值为0.0,指针类型的默认值为nullptr

void f(){
int a0; //未初始化
int a2{}; //初始化为0
double d1{}; //初始化欸0.0
char *p{}; //p变成nullprt int *p1 = new int; //未初始化
int *p2 = new int{}; //初始化为0
}

内置类型的构造函数最常用于模板参数:

template<typename T>
struct Handle{
T * p;
Handle(T *pp = new T{}) :p{pp}{}
}; Handler<int> px; //会生成int{} --- 初始化为0

引用和const必须被初始化。因此,一个包含这些成员的类不能默认构造,除非程序员提供了类内成员初始化器或者定义了一个默认构造函数来初始化它们

int glob{9};

struct X{
const int a1{7}; //ok
const int a2; //错误:需要一个用户自定义构造函数
const int & r{9}; //ok
int& r1{glob}; //ok
int& r2; //错误,需要一个用户自定义构造函数
}; X x; //错误:X没有默认构造函数

实例

class A
{
public:
A(); //没有参数
};
class B
{
public:
explicit B(int x = 1, bool b = true); //每个参数有初始值
//explicit:阻止执行隐式转换,但是可以显示类型转换
};
class C
{
public:
explicit C(int c); //非默认构造函数
};




struct A
{
int x;
A(int x = 1): x(x) {} // 用户定义默认构造函数
}; struct B: A
{
// 隐式定义 B::B(),调用 A::A()
}; struct C
{
A a;
// 隐式定义 C::C(),调用 A::A()
}; struct D: A
{
D(int y): A(y) {}
// 不会声明 D::D(),因为存在另一构造函数
}; struct E: A
{
E(int y): A(y) {}
E() = default; // 显式预置,调用 A::A()
}; struct F
{
int& ref; // 引用成员
const int c; // const 成员
// F::F() 被隐式定义为弃置
}; int main()
{
A a;
B b;
C c;
// D d; // 编译错误
E e;
// F f; // 编译错误
}

搬运原文C/C++编程:默认构造函数_OceanStar的学习笔记的博客-CSDN博客_c++默认构造函数

C/C++ 关于默认构造函数的更多相关文章

  1. C++的默认构造函数与构造函数

    今天看书,忽然发现自己对默认构造函数/构造函数的理解很模糊,在实际项目中写类时,这些细节问题并没有涉及到.因此,就专门对着<C++ Primer Plus>将默认构造函数/构造函数这一块简 ...

  2. C++ 合成默认构造函数的真相

    对于C++默认构造函数,我曾经有两点误解: 类如果没有定义任何的构造函数,那么编译器(一定会!)将为类定义一个合成的默认构造函数. 合成默认构造函数会初始化类中所有的数据成员. 第一个误解来自于我学习 ...

  3. C++中默认构造函数中数据成员的初始化

    构造函数的任务是初始化数据成员的,在类中,如果没有显示定义任何构造函数,编译器将为我们创建一个构造函数,称为合成的默认构造函数,合成的默认构造函数使用与变量初始化相同的规则来初始化成员.即当类中的数据 ...

  4. [C++]默认构造函数

    默认构造函数(default constructor)就是在没有显示提供初始化式时调用的构造函数.它由不带参数的构造函数,或者为所有的形参提供默认实参的构造函数定义.若个定义某个类的变量时没有提供初始 ...

  5. 【C++对象模型】构造函数语意学之一 默认构造函数

    默认构造函数,如果程序员没有为类定义构造函数,那么编译器会在[需要的时候]为类合成一个构造函数,而[需要的时候]分为程序员需要的时候和编译器需要的时候,程序员需要的时候应该由程序员来做工作,编译器需要 ...

  6. C++关于编译器合成的默认构造函数

    有两个常见的误解: 1.任何类如果没有定义默认构造函数,就会被合成出一个来. 2.编译器合成的默认构造函数会显式地设定类内每一个数据成员的默认值. 对于第一个误解,并不是任何类在没有显式定义默认构造函 ...

  7. Swift—默认构造函数-备

    结构体和类的实例在构造过程中会调用一种特殊的init方法,称为构造函数.构造函数没有返回值,可以重载.在多个构造函数重载的情况下,运行环境可以根据它的外部参数名或参数列表调用合适的构造函数.默认构造函 ...

  8. C++默认构造函数的一点说明

    大多数C++书籍都说在我们没有自己定义构造函数的时候,编译器会自动生成默认构造函数.其实这句话我一直也是 深信不疑.但是最近看了一些资料让我有了一点新的认识. 其实我觉得大多数C++书籍之所以这样描述 ...

  9. C++结构体:默认构造函数,复制构造函数,重载=运算符

    C++结构体提供了比C结构体更多的功能,如默认构造函数,复制构造函数,运算符重载,这些功能使得结构体对象能够方便的传值. 比如,我定义一个简单的结构体,然后将其作为vector元素类型,要使用的话,就 ...

  10. java中创建对象中使用默认构造函数的注意点

    public class Test3 {   private int n;   Test3() {      System.out.println("调用默认构造器");   }  ...

随机推荐

  1. 数码管动态显示Verilog实现(参考小梅哥教程)(视觉暂留)

    一个数码管有九个引脚,控制八段二极管的亮灭,用以显示需要的数字. 当有N个数码管时,一个一个控制的话需要N x 9 个引脚,消耗资源较多. 因此可以利用动态显示的方案通过人眼的视觉暂留特性达到静态显示 ...

  2. MyBatis-Plus联表查询的短板,终于有一款工具补齐了

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 哈喽大家好啊,我是Hydra. mybatis-plus作为mybatis的增强工具,它的出现极大的简化了开发中的数据库操作,但是长久以来,它的 ...

  3. C#常见的集合

    3中数组式的 Array 在内存上是连续分配的,而且元素类型是一样的 特点:读取快,可以坐标访问,增删慢.长度不变. ArrayList 不定长,连续分配的,元素没有类型限制,任何元素都当成Objec ...

  4. 在半小时内从无到有开发并调试一款Chrome扩展(Chrome插件/谷歌浏览器插件)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_120 就在不久之前,我们目前这个毕业班的班长那日同学和我说,他正在公司开发Chrome扩展,看起来很高大上的技术,实际开发却非常简 ...

  5. 使用Django2.0.4集成钉钉第三方扫码登录

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_124 钉钉作为阿里旗下的一款免费移动通讯软件,受众群体越来越多,这里我们使用Django来集成一下钉钉的三方账号登录,首先注册钉钉 ...

  6. 【原创】Python 网易易盾滑块验证

    本文仅供学习交流使用,如侵立删! 记一次 网易易盾滑块验证分析并通过 操作环境 win10 . mac Python3.9 selenium.PIL.numpy.scipy.matplotlib 分析 ...

  7. redis安装与连接

    安装(centos7): yum install redis 启动与停止: systemctl start redis. service systemctl stop redis.service 修改 ...

  8. 从零开始Blazor Server(9)--修改Layout

    目前我们的MainLayout还是默认的,这里我们需要修改为BootstrapBlazor的Layout,并且处理一下菜单. 修改MainLayout BootstrapBlazor已经自带了一个La ...

  9. SVN:取消对代码的修改

    取消对代码的修改分为两种情况: 第一种情况:改动没有被提交(commit). 这种情况下,使用svnrevert就能取消之前的修改. svn revert用法如下: #svn revert[-R] s ...

  10. Linux的OpenLava配置

    OpenLava OpenLava是基于LSF早期的开源版本发展而来,其免费.开源.兼容IBM LSF的工作负载调度器.当你需要执行某项业务时候(比如跑渲染之类的),当有服务器处于空闲状态时候,可以直 ...