天天看點

c++的構造函數和析構函數

構造函數的需求

類内部的私有成員和保護成員在外界是不可以被通路的,這也正是類的封裝性的展現,是以在要為類的成員變量初始化時,這個任務就落在了類的成員函數。

class A
{
   private:
      int a;
      int b;
   public:
      void init(int a,int b)
      {
         this->a=a;
         this->b=b;
       }
};           

通過上述代碼,我們是可以通過調用init函數對類的成員變量進行指派,這意味着當我們建立一個對象,都要手動的調用init來初始化對象,這樣的類機制是不理想的,于是就想到在建立對象時,程式自動調用構造函數進行初始化。(析構函數同樣是程式自動調用的,它也是出于考慮到上述的原因)。另外考慮到類的唯一性和對象的多樣性,于是就打算用類名作為構造函數的函數名。(析構函數是使用~加類名作為函數名)。

構造函數的使用

#include<iostream>
using namespace std;

class A
{
private:
    int a;
    int b;
public:
    A(int a, int b)         //類的構造函數,函數名必須和類名相同。
    {
        this->a = a;     //構造函數和普通成員函數一樣,它會有一個預設的參數,指向對象本身,就是this指針
        this->b = b;
    }
    void print()        //輸出函數
    {
        cout << a << endl;
        cout << b << endl;
    }
};

int main()
{
    A a(10, 11);
    a.print();
    system("pause");
    return 0;
}           

輸出結果:

c++的構造函數和析構函數

由上述的例子我們可以發現,構造函數是程式自己調用的,是以我們就完成了對a和b的初始化。

(構造函數是沒有傳回值的,也不允許有,但可以有無值的return。)

析構函數

析構函數也是特殊的函數,沒有傳回值,沒有參數,不能随意調用,沒有重載。隻是在類對象生命周期結束的時候自動調用。其實我們把析構函數稱為“逆構造函數”,在構造函數名前加上運算符“~”。析構函數的作用就是釋放對象的所使用的記憶體資源。析構函數的調用順序和構造函數的調用順序相反。

eg:

#include<iostream>
using namespace std;

class Student
{
public :
    Student()
    {
        cout << "I'm a student" << endl;
    }
    ~Student()
    {
        cout << "I'm a student 的析構" << endl;
    }
};

class Teacher
{
public:
    Teacher()
    {
        cout << "I'm a teacher" << endl;
    }
    ~Teacher()
    {
        cout << "I'm a teacher 的析構" << endl;
    }
};

class School
{
private:
    /*
    此處以其他類的對象作為成員變量
    */
    Student s;
    Teacher t;
public:
    School()
    {
        cout << "I'm a school" << endl;
    }
    ~School()
    {
        cout << "I'm a school 的析構" << endl;
    }
};

int main()
{
    School();
    system("pause");
    return 0;
}           
c++的構造函數和析構函數

如上述代碼所示,當一個類把其他類的對象作為成員變量時候,當該類建立對象自動調用該類的構造函數時,它也會同時自動調用其成員變量中類對象的構造函數,此時的構造函數調用順序為:先調用成員變量中的類對象的構造函數,按照聲明的先後順序調用,最後調用自己的構造函數。而析構函數的調用就和構造函數的調用順序相反。于是我們就可以得到如圖的輸出結果。

重載構造函數

首先我們解釋一下重載的含義就是函數或方法有相同的名稱,但是參數清單不同的情形。構造函數作為函數的一種,當然也可以被重載。下面給出函數重載的樣例,(string就是一個類,其中就是通過構造函數的重載完成對string對象多種初始化方式)

class A
{
private:
    int a;
    int b;
public:
    /*
    構造函數的重載,定義對象時,隻是通過參數的個數決定使用哪個構造函數
    */
    A();
    A(int a);
    A(int a, int b);
};           

重載構造函數為類提供了多種初始化方式,在定義對象時,程式根據所提供的參數個數來決定調用哪個構造函數。

預設的構造函數

(1):c++規定每個類必須要有一個構造函數,沒有構造函數,就無法建立對象

(2):如果沒定義構造函數,c++會自動的為提供一個預設的無參構造函數,但是該構造函數什麼也沒有幹,隻是拿來建立對象的。

(3):隻要為一個類定義了一個構造函數(無論是有參還是無參的),系統都不會再自動的為類提供構造函數,如果需要使用無參構造函數,那就需要自己定義一個無參構造函數。

(4):建立對象和定義普通變量是一樣的,在用預設的構造函數建立對象時,如果建立的是全局對象或靜态對象,則對象的位模式為全0,否則,對象是随機指派的。

類成員初始化問題

在一個類中,如果有使用者自定的類對象做為其成員(如前面講析構函數和構造函數調用順序),那麼構造函數是如何工作的了?

(1):定義該類時隻需要使用無參構造函數時

#include<iostream>
using namespace std;

class Student
{
public :
    Student()
    {
        cout << "Student" << endl;

    }
};
class School
{
private:
    Student s;        //定義了一個類成員
public:
    School()
    {
        cout << "School" << endl;     //在調用School的構造函數之前會先調用Student的構造函數
    }
};

int main()
{
    School s;
    system("pause");
    return 0;
}           
c++的構造函數和析構函數

從輸出結果可以看出,當我們建立了一個school對象時,它會調用構造函數來初始化其成員,其中它的一個類成員–Student s,也将調用它自己構造函數來初始化自己。

(2):當需要使用自定義構造函數(含參數)來初始化對象時

#include<iostream>
using namespace std;

class Student
{
private:
    int a;
public :
    Student(int a)
    {
        this->a = a;
        cout << "Student a="<<this->a << endl;

    }
};
class School
{
private:
    Student s;        //定義了一個類成員
public:
    School(int a):s(a)                 //這是初始化成員變量的一種方式
    {
        cout << "School" << endl;     //在調用School的構造函數之前會先調用Student的構造函數
    }
};

int main()
{
    School s(10);
    system("pause");
    return 0;
}           
c++的構造函數和析構函數

冒号文法來初始化成員變量的方式隻要是為了解決:類成員調用有參構造函數來初始化自己,成員變量為常量時,隻有通過這種方式才可以對其進行初始化,因為c++定義,常量是不可以被指派的。另外一個就是引用的變量的初始化,因為引用和常量是有共同的性質的。

class School
{
private:
    const int age;   //聲明了一個常量
    int & i;         //聲明了一個引用
public:
    School(int & a) :age(10), i(a)   //初始化引用和常量
    {

    }
};           
#include<iostream>
using namespace std;


class A
{
public :
    A(int a)
    {
        cout << "調用: " ;
        cout << "a=" << a << endl;
    }
};
void fun(int a)
{
    static A example(a);             //聲明靜态對象
    A example1(a+1);                  //聲明非靜态對象
    cout << "fun a=" << a<<endl;
}
int main()
{
    fun(10);
    fun(20);
    system("pause");
    return 0;
}