一文带你完全弄懂C++构造函数细节

 

目录

          零,前言

 一,构造函数特性

       基本特点

       其他特点

 二,构造函数的调用

          三,构造函数与初始化列表


         零,前言

       数据结构中我们使用C语言的结构体变量定义过顺序表,链表,栈等等一系列结构。我们会发现在进行数据结构学习时通常都要对结构体中定义的数据进行初始化的操作。究其原因是如果你不给结构体中的变量指定一个值,那么编译器会报错(原因会在后文解释)。而在C++引入类这个概念后几乎所有类都需要进行初始化,清理等操作。 C++的设计者为了解决程序员在构造类时总是将初始化,清理等操作遗忘,设定了六大成员函数,而构造函数是其中最重要最复杂的。

       构造函数,意思是在对象构造阶段所调用的函数,作用是给类对象进行初始化赋值操作。在程序员创建类对象时,构造函数将会由编译器自动调用,并且在每个对象周期内都会调用一次。 

         一,构造函数特性

    基本特点

1,构造函数名与类名相同

2,构造函数没有返回值

3,构造函数可以重载

4,类对象建立传参后,构造函数将会自动调用

       构造函数,实际上就是用来初始化的函数,在对象构造阶段所调用。即为对类内对象赋初始值。例如下图中Date d1,并且每个对象生命周期内只调用一次。

      由于构造函数可以重载,我们在类内可以编写多个参数不同的构造函数,多种不同构造函数可以同时存在,并根据需求选择相对应的构造函数

一文带你完全弄懂C++构造函数细节_第1张图片

一文带你完全弄懂C++构造函数细节_第2张图片

   

      其他特点

用户没有显式实现时,编译器会自动生成

    包括构造函数,所有的默认成员函数都有此特点

例如:

一文带你完全弄懂C++构造函数细节_第3张图片

       但事实上我们从上图得知,当我们没有显式定义构造函数时,即使C++自己写了一个默认构造函数,其类内参数还是随机值,那么是否可以认为,这个机制并没有什么用呢?我们先来看看C++构造函数的另一个机制:

C++编译器对于内置类型不做处理,对于自定义类型将会调用它的默认构造函数

    注意:此处的“默认构造函数”和“构造函数”不是同一个概念,默认构造函数是构造函数的一种。所有无参的构造函数或者全缺省的构造函数称为默认构造函数

  

       什么是内置类型?int/double/float....这些编译器提供给程序员使用的变量类型就是内置类型。那么相反,程序员自己定义的类,结构体称为自定义类型。在很多时候类中定义的变量不止有内置类型int,double等,还会有其他类成员嵌套在这类里面,因此编译器必须要这种机制对类内的类变量进行初始化操作,否则就会编译报错。

       此时很多人有疑问,为什么int/double..这类内置类型不对它进行初始化操作编译器会给他自动初始化,而类这种自定义类型必须要调用构造函数进行初始化呢?通俗的讲,这些内置类型本就是编译器提供给程序员使用的,编译器知道他们的底层是如何实现的,当然可以对他进行赋值。但是自定义类型是程序员自己定义的,编译器不知道他们的底层实现,就无法给他们赋值。

       二,构造函数的调用

        构造函数是一种默认成员函数,所以它是在编译器中可以由编译器自动生成,或者程序员自己创建。当我们在一个日期类中用编译器自动调用的构造函数时会发生什么呢?

一文带你完全弄懂C++构造函数细节_第4张图片一文带你完全弄懂C++构造函数细节_第5张图片

    我们看到编译器将年份,月份,日期都设定了一个非常奇怪的数字,显然编译器默认生成的构造函数不是不符合要求的,我们必须自己编写自己的构造函数,对于日期可以设定一个程序员需要的值。因此构造函数如何编写也是面向需求的。

       构造函数在创建类型对象时就会自动调用,并且构造函数的传参是在类对象后面

一文带你完全弄懂C++构造函数细节_第6张图片

 可以注意到在定义对象之前左边类内成员都是随机值。

一文带你完全弄懂C++构造函数细节_第7张图片

对象定义后类内成员被赋予了我所需要的值。 

  特殊类的构造函数的处理

        有些类中的初始化不仅需要进行赋值操作,还要动态开辟空间。例如在数据结构中的栈类型,它的构造函数如图所示:

class stack
{
public:
  stack(int capacity = 4)
     {
       _a = malloc(sizeof(int)*capacity);
          if(_a == NULL)
         {
           perror("fail");
           exit(-1);
         }
          
 
    _capacity = cpacity;
    _top = 0;
     }


private:
  int _top;
  int* _a;
  int _capacity;



};

三,构造函数与初始化列表

     首先来展示一下初始化列表的一般格式: 初始化列表一般在构造函数名之后,先以冒号开始,后面数据成员以逗号分隔。每个逗号前写上需要传参的值,后接一个括号写参数名。每个成员在初始化列表中只能出现一次。

class Date
{
  public:
    Date(int year, int month ,int day)
      :_year(year),_month(month),_day(day)
{

}
  



 private:
   int year;
   int month;
   int day;
}

    初始化列表只能实现赋值操作,若是初始化需要开辟空间,则仍需要使用构造函数。不过此时我们可以将初始化列表和构造函数混合使用,两者可以同时存在。例如:

class stack
{
public:
  stack(int capacity = 4)
  :_top_value(_top),_capacity_value(_capacity)
     {
       _a = malloc(sizeof(int)*capacity);
          if(_a == NULL)
         {
           perror("fail");
           exit(-1);
         }
          
 
     }


private:
  int _top;
  int* _a;
  int _capacity;



};

   赋值操作我们使用了初始化列表,而开辟空间我们使用的构造函数。

   引入初始化列表是为了解决构造函数无法解决的问题,以下三种情况必须用初始化列表进行初始化:

class A
{
public:
  A(int b)
{
}

private:
int b;
}

class Date
{
public:

private:
  const int year;//(1)
  int& month;//(2)
  A a;//(3)
}

(1)类变量中存在const变量

    由于const变量无法修改,并且只有一次初始化机会。初始化一次后其值将会固定。而如下图所示构造函数内赋值操作显然无法解决这个问题,报错为:未提供初始值设定。

一文带你完全弄懂C++构造函数细节_第8张图片

       在C++中规定:所有类在定义时都会进行初始化列表,初始化列表是所有类变量初始化的地方。即使没有显式写出来,也会进行初始化列表,只不过编译器会将变量都初始化一个随机值。这也是为什么上文所述编译器自动生成的构造函数只会给随机值。

   

(2)引用类型也必须在初始化列表进行初始化

    原因与(1)相似,引用变量也只有初始化一次的机会,一旦确定便不会改变。

一文带你完全弄懂C++构造函数细节_第9张图片

(3)当类内变量存在类对象,并且该类中没有默认构造函数时

一文带你完全弄懂C++构造函数细节_第10张图片

      此时A类内只有构造函数,不存在默认构造函数,而由于C++构造函数特性:

C++编译器对于内置类型不做处理,对于自定义类型将会调用它的默认构造函数

    显然A无法正常地进行初始化,只能使用初始化列表进行初始化。那么如果我们在Date类内用初始化列表:

一文带你完全弄懂C++构造函数细节_第11张图片

此时可以编译通过。

所以对于类内初始化方式而言,能使用初始化列表尽量都使用初始化列表,构造函数机制复杂容易出错,而用初始化列表初始化是永远不会出错的。


你可能感兴趣的:(C++,c++,开发语言)