C++知識點總結(面向對象2-構造函數, 初始化列表)

構造函數1(Constructor)

構造函數(也叫構造器), 在對象創建的時候自動調用, 一般用於完成對象的初始化工作.
特點:
(1)函數名與類同名, 無返回值(void都不能寫), 可以有參數, 可以重載, 可以有多個構造函數.
(2)一旦自定義了構造函數, 必須用其中一個自定義的構造函數來初始化對象.
注意:
(1)通過malloc分配的對象不會調用構造函數
(2)通過new分配的對象可以調用構造函數, 即在堆空間中的對象可以調用構造函數, 在棧空間的也可以, 在全局區(數據段)的也可以.只有malloc不會調用.因爲malloc在C語言就有了, 那時候還沒有構造函數.
所以new至少比malloc多做了調用構造函數這件事情.
(3)一個廣爲流傳的, 很多教程\書籍都推崇的錯誤結論:
默認情況下, 編譯器會爲每一個類生成空的無參的構造函數.
正確理解: 在某些特定的情況下, 編譯器纔會爲類生成空的無參的構造函數.

構造函數2

構造函數的調用:

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;

    Person() {
        m_age = 0;
        cout << "Person()" << endl;
    }

    Person(int age) {
        m_age = age;
        cout << "Person(int age)" << endl;
    }
};

Person person1(); // 函數聲明

Person person1() { // 函數實現
    return Person();
}

Person g_person0; // Person();
Person g_person1(); // 全局的函數聲明
// 返回值  函數名
Person g_person2(10); // Person(int)

int main()
{
    Person person0; // Person()
    Person person1(); // 局部的函數聲明
    Person person2(20); // Person(int)

    Person *p0 = new Person; // Person()
    Person *p1 = new Person(); // Person()
    Person *p2 = new Person(30); // personn(int)
    return 0;
}

成員變量的初始化(瞭解)

如果自定義了構造函數, 除了全局區, 其他內存空間的成員變量默認都不會被初始化, 需要開發人員手動初始化.
如果有很多個成員變量, 如何方便的初始化.

class Person {
	int m_age1;
	int m_age2;
	int m_age3;
	int m_age4;
	int m_age5;
	Person() {
		memset(this, 0, sizeof(Person));
		// 從person對象的地址開始, 它的所有字節都清0
	}
};

析構函數(Destructor)

析構函數(也叫析構器), 在對象銷燬(對象的內存被回收的時候)的時候自動調用, 一般用於完成對象的清理工作.
特點:
(1)函數名以~開頭, 與類同名, 無返回值(void都不能寫), 無參, 不可以重載, 有且僅有一個析構函數.
注意:
(1)通過malloc分配的對象free的時候不會調用析構函數.
(2)構造函數, 析構函數要聲明爲public, 才能被外界正常使用.所以構造函數和析構函數必須聲明爲public, 否則會報錯.

繼承

繼承, 可以讓子類擁有父類的所有成員(變量\函數)

成員訪問權限

成員訪問權限, 繼承方式有3種
public : 公共的, 任何地方都可以訪問(struct默認)
protected: 子類內部, 當前類內部可以訪問
private: 私有的, 只有當前類內部可以訪問(class默認)
結論:
子類內部訪問父類成員的權限, 是以下2項中權限最小的那個
1.成員本身的訪問權限
2.上一級父類的繼承方式
(1)所以一般都寫public繼承, 因爲public繼承可以將父類原有的訪問權限繼承下來.
(2)訪問權限不影響對象的內存佈局.

初始化列表1

特點:
(1)一種便捷的初始化成員變量的方式
(2)只能用在構造函數中
(3)初始化順序只跟成員變量的聲明順序有關

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;
    int m_height;

    /*Person(int age, int height) {
        m_age = age;
        m_height = height;
    }*/
    // 語法糖
    Person(int age, int height) : m_age(age), m_height(height) {

    }
    // 這個構造函數與上面的構造函數等價
};

int main()
{
    Person person(18, 180);   
    cout << person.m_age << endl;
    cout << person.m_height << endl;
    return 0;
}

初始化列表與默認參數配合使用
#include <iostream>
using namespace std;

class Person {
public:
    int m_age;
    int m_height;
    Person(int age = 0, int height = 0) : m_age(age), m_height(height) {

    }
    // 一個構造函數相當於寫了3個構造函數
};

int main()
{
    Person person1;
    Person person2(19);
    Person person3(20, 180);
    return 0;
}


如果函數聲明和實現是分離的
初始化列表只能寫在函數的實現中
默認參數只能寫在函數聲明中

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;
    int m_height;

    Person(int age = 0, int height = 0);
};

Person::Person(int age, int height) : m_age(age), m_height(height) {
    
}

int main()
{
    Person person;
    return 0;
}


構造函數的互相調用

結論:構造函數調用構造函數要在初始化列表裏面寫.
例如:

#include <iostream>
using namespace std;

class Person {
public:
   int m_age;
   int m_height;

   Person() : Person(0, 0) {}
   Person(int age, int height) : m_age(age), m_height(height) {}
};

int main()
{
   Person person; 
   return 0;
}


注意:下面的寫法是錯誤的, 初始化的是一個臨時對象.

class Person {
   int m_age;
   int m_height;
   Person() {
   	Person(0, 0);
   }
   Person(int age, int height) : m_age(age), m_height(height)  {}
};

初始化列表3-父類的構造函數

(1)子類的構造函數默認會調用父類的無參構造函數
(2)如果子類的構造函數顯式地調用了父類的有參構造函數, 就不會再去默認調用父類的無參構造函數

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;

    Person() {
        cout << "Person::Person()" << endl;
    }

    Person(int age) {
        cout << "Person::Person(int age)" << endl;
    }
};

class Student : public Person {
public:
    int m_no;
    
    Student() : Person(10) {
        cout << "Student::Student()" << endl;
    }
};

int main()
{
    Student student;

    return 0;
}

(3)如果父類缺少無參構造函數(但有有參構造函數), 子類的構造函數必須顯式調用父類的有參構造函數.不然會報錯. 因爲子類的構造函數默認會調用父類無參的構造函數, 如果父類缺少無參的構造函數, 就報錯.

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;

    Person(int age) {
        cout << "Person::Person(int age)" << endl;
    }
};

class Student : public Person {
public:
    int m_no;

    Student() : Person(10) {
        cout << "Student::Student()" << endl;
    }
    //父類缺少無參的構造函數, 子類的構造函數
    //必須顯式的調用父類的有參構造函數.
};

int main()
{
    Student student;
    return 0;
}


(4)如果父類什麼構造函數都沒有, 那子類的構造函數就不調用了.
(5)價值:

#include <iostream>
using namespace std;

class Person {
private:
    int m_age;

public:
    Person(int age) : m_age(age){  }
    int getAge() {
        return m_age;
    }
};

class Student : public Person {
private:
    int m_no;

public:
    Student(int age, int no) : Person(age), m_no(no) {  }
    int getNo() {
        return m_no;
    }
};

int main()
{
    Student student(10, 20);
    cout << student.getAge() << '\n' <<
        student.getNo() << endl;
    return 0;
}


其他C++系列文章:

C++知識點總結(基礎語法1-函數重載, 默認參數)
C++知識點總結(基礎語法2-內聯函數, const, 引用)
C++知識點總結(面向對象1-類和對象, this指針, 內存佈局)
C++知識點總結(面向對象2-構造函數, 初始化列表)

C++知識點總結(面向對象3-多態)

C++知識點總結(面向對象4-多繼承, 靜態成員static)
C++知識點總結(面向對象5-const成員, 拷貝構造函數)
C++知識點總結(面向對象6-隱式構造, 友元, 內部類, 局部類)
C++知識點總結(其他語法1-運算符重載)
C++知識點總結(其他語法2-模板, 類型轉換, C++11新特性)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章