Bootstrap

类构造函数相关知识

一、构造函数是什么

        构造函数是面向对象程序中一个特殊的函数,它没有返回值,它的名字与类的名字相同,并且构造函数是在一个对象被创建时自动调用,它的作用往往是对类中其它数据成员进行初始化,但它同时也可进行初始化以外的操作。

构造函数必须是public类型的,否则在创建类时将无法调用。

class A {
    int a;
    int b;
    A():b(5),a(6) {

    }
    A(int b){
        a=b;
    }
};

二、构造函数的初始化表

       上述代码中展示了两个简单的构造函数,其中在"A()"之后的" : "之后," { "之前的内容是初始化表。在这里要注意,在构造函数函数体内对类数据成员所进行的操作不是初始化,那应该被称作赋值。参数初始化表所进行的操作才能被称为初始化。使用初始化表的格式为 :构造函数名 ():数据成员1(数据1初始化的目标值,数据成员2(数据2初始化的目标值) ……{   构造函数函数体   }。初始化列表各个成员以“ ,”隔开。在进行初始化时并不是按照初始化表的书写顺序进行初始化的,而是按照类数据成员的声明成员进行初始化的。上图中虽然初始化表中b变量在前,但在初始化时是a先初始化,然后才轮到b。

三、构造函数的调用

        构造函数是可以重载的,它可分为有参和无参两种类型。

#include<iostream>
using namespace std;
class A {
    int a;
    A():a(6) {

    }
 A(int b){
        a=b;
    }
};

int main(){

    A a();        //创建A类对象a,调用无参构造函数
    A aa(6);      //调用有参构造函数    
}

要调用有参构造函数就要向此构造函数传递实参,在创建对象时,在对象名称后面加 " () ",实参由"    () "包围,这一点与函数的调用类似,当你所调用的构造函数为无参构造函数时," () "括号可以省略,

你也可以写为

 A a = 6;

这种情况与 A a(6)效果相同,它们两个最后转化为的汇编代码是完全相同的。

四、默认构造函数

        当你程序中没有任何自己书写构造函数时,编译器会自动生成一个构造函数,可以把此函数理解为初始化表与函数体同时为空的无参构造函数。不过一旦你的程序中出现了构造函数,无论此函数有参无参,程序都将不会再生成默认构造函数。


五、当类对象为某个类的数据成员时

class B {
private:
    int z;
public:
    B() :z(555){

    }
    B(int x){
        z=x;
    }
};
class A {
    B b;
    int a;
    A() {

    }
};
int main(){

    A a;

}

        上述代码中类A的一个数据成员为类b的对象,对象a创建时,在初始化类A之前,首先会去调用类B的构造函数,因为并没有特殊指示,在这里会去调用类b的无参构造函数,并进行相应的初始化。若想要调用类B的有参构造函数,需要按以下写法:

class B {
private:
    int z;
public:
    B() :z(555){

    }
    B(int x){
        z=x;
    }
};
class A {
    B b;
    int a;
    A():b(6) {

    }
};

在初始化列表对象b后面的括号里,写上需要向对象B构造函数传递的参数,这表明在创建A类对象时要去调用对象b中含有相应参数的构造函数。由于对象b的构造函数的调用是在对象A的初始化之前的,所以在这里不能将int a作为实参传递过去,因为在这时a是还没有初始化的。同样,若含有多个其它类数据成员,仍是按照声明顺序去调用构造函数的,不会按照初始化列表的书写顺序。

五、拷贝构造函数

        拷贝构造函数是一种特殊的构造函数,当想要通过复制一个已有的对象来生成新对象时需要调用拷贝构造函数。它常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象。

  • 复制对象把它作为参数传递给函数。

  • 复制对象,并从函数返回这个对象。

拷贝构造函数以引用或常量引用为参数,(大多数情况下都是以常量引用为参数,因为该引用可以防止你通过引用修改所引用对象的值,同时可以接收const类型对象)

class A {
public:
    int n;
    A(const A& b) {      //拷贝构造函数
        n = b.n;
       
        cout << "gouzao";
    }
    A() {
        n = 0;
    }
};

如果你未定义拷贝构造函数,那么编译器将会自动生成一个拷贝构造函数去帮助你完成对应操作,所以当我们自己写拷贝构造函数时往往还希望做一些拷贝以外的事情,因为单纯的拷贝对象系统提供的拷贝构造函数完全足够。

但是当你所拷贝的对象拥有指针成员时需要注意

class A {
private:
    int* a;
    ~A() {
        delete a;
    }
};

出现类似这种情况时,如果你进行拷贝,执行系统默认拷贝构造函数,两个对象的数据成员a将会指向同一片内存,这样在执行析构函数时会对同一片内存delete两次,这是绝对会出现错误的。所以在这种情况下,我们往往会自己写拷贝构造函数,用new为a重新分配内存空间,并将对应的内容赋值过来。

class A {
private:
    int* a;
public:
    ~A() {
        delete a;
    }
    A(const A& p) {
        a = new int;
        *a = *p.a;
    }
  
};

 拷贝构造函数进行的是成员间的拷贝复制,并不是字节间的拷贝复制,所以当数据成员中出现其它类的对象时,在进行拷贝构造时,同样会调用该其他类数据成员的拷贝构造函数,进行这个数据成员的拷贝构造。

在类的成员函数中是不能以该类对象为形参的。

还有一点在这里提一下,类的private是针对类的,而不是针对对象的,所以在类成员函数中可以调用另一个同类对象的私有成员。

在某些时候编译器会优化掉一些不必要的拷贝构造函数,这个之后再讨论。

;