C++面向對象筆記(2):封裝篇(上)

1.類的三種訪問限定符

public、protected、private

2.實例化類的對象與訪問對象成員

定義一個TV類:

class TV{
    public:
    char name[20];
    int type;
    
    void changeVol();
    void power();
}

從棧中實例化:

int main(void){
    TV tv;
    TV tv[20];
    
    return 0;
}

從堆中實例化:

int main(void){
    TV *p = new TV();
    TV *q = new TV[20];
    
    // todo(如果從堆中,一定要手動釋放)
    delete p;
    delete []q;
    p = nullptr;
    q = nullptr;
    
    return 0;
}

注意:要區分是棧還是堆也很簡單,堆我們需要手動分配、釋放內存(new、delete)。

訪問對象成員(棧):

int main(void){
    TV tv;
    tv.type = 0;
    
    return 0;
}

訪問對象成員(堆):

int main(void){
    TV *p = new TV();
    p->type = 0;
    p->changeVol();
    
    delete p;
    p = nullptr;
    
    return 0;
}

訪問對象成員(數組):

int main(void){
    TV *p = new TV[5];
    for(int i = 0; i < 5; i++){
        p[i]->type = 0;
        p[i]->changeVol();
    }
    
    delete []p;
    p = nullptr;
    
    return 0;
}

3.String字符串的使用

常用方法:

s.empty()
s.size()
s1+s2

字符串連接:

string s1 = "hello";
string s2("world");
string s3 = s1 + s2;
string s4 = "hello" + s2;
string s5 = "hello" + s2 + "world";

// 錯誤
string s6 = "hello" + "world";

注意: 字符串連接,只能用於“字符串+變量”,不能“字符串+字符串”

4.new和delete運算符-內存的創建與釋放

new/delete 是C++的運算符;類似於malloc/free,程序運行(動態)得開闢內存空間(堆);

new 可以爲內置類型的變量開闢空間,數組變量,類的對象開闢空間。

這是在上開闢內存,返回一個指向該內存空間的地址。

new/delete會調用類的構造函數析構函數(malloc不會)。

5.在類的外部定義類函數

注意:類內定義的函數優先選擇編譯爲內聯函數(inline)。

class Car{
    public:
    void run();
    void stop();
    void changeSpeed();
};
void Car::run(){}
void Car::stop(){}
void Car::changeSpeed(){}

如果需要 分文件 進行定義:

car.h

// 存放類的聲明
class Car{
    public:
    void run();
    void stop();
    void changeSpeed();
};

car.cpp

// 存放類的定義,切記要包含(include)頭文件
void Car::run(){}
void Car::stop(){}
void Car::changeSpeed(){}

6.內存區塊與代碼之間的聯繫

棧區 int x = 0; int *p = nullptr;
堆區 int *p = new int[20];
全局區 存儲全局變量及靜態變量
常量區 string str = “hello”;
代碼區 存儲邏輯代碼的二進制

7.構造函數

構造函數一定是沒有返回值的。在參數上,如果沒有參數,我們都稱之其爲默認構造函數

如果是默認構造函數(不帶參數的構造函數),在實例化的時候,有如下幾種寫法:

// 在堆中創建
Coordinate *p1 = new Coordinate();
Coordinate *p2 = new Coordinate;

// 在棧中創建
Coordinate p2;

如果在堆中進行創建,那我們也需要手動進行釋放,代碼如下:

delete p1;
p1 = nullptr;// 不是必要,只是防止delete失敗,所以這裏再將指針置爲安全狀態

// 如果是類數組
delete[] p;
p1 = nullptr;

8.構造函數-初始化列表

初始化列表是連接到構造函數名後面的,初始化列表和構造函數一樣能夠初始化類中的屬性,但是對於const修飾的常量,只能用 初始化列表 進行初始化。即如果需要初始化,必須寫到 初始化列表 當中。

  1. 初始化列表先於構造函數執行
  2. 初始化列表,只能用於構造函數
  3. 初始化列表可以同時初始化多個數據成員

例子1,對const修飾的常量賦初值:

// 錯誤,採用構造函數
class Circle{
public:
    Circle():{m_dPi=3.14}
private:
    const double m_dPi;
};

// 正確,採用初始化列表
class Circle{
public:
    Circle():m_dPi(3.14){}
private:
    const double m_dPi;
};

例2,多參數賦值:

// ====== 類聲明START ======
class Teacher{
public:
    // 構造函數(沒有返回值),這裏設定了默認參數
    Teacher(string name = "Jim",int age = 1,int max = 50);

    // 操作類的屬性
    void setName(string name);
    string getName() const;
    void setAge(int age);
    int getAge();
private:
    // 屬性
    string m_strName;   // 名字
    int m_iAge;         // 年齡
};
// ====== 類聲明END ======

// ====== 類實現START ======
// 構造函數(在初始化列表中,初始化屬性)
Teacher::Teacher(string name,int age,int max):m_strName(name),m_iAge(age){
    cout << "This is the constructor!" << endl;
}
// 操作類的屬性
void Teacher::setName(string name) {m_strName = name;}
string Teacher::getName() const {return m_strName;}
void Teacher::setAge(int age) {m_iAge = age;}
int Teacher::getAge() {return m_iAge;}
// ====== 類實現END ======

int main(int argc,char *argv[]) {

    Teacher tea;
    cout << "Name:" << tea.getName() << endl;
    cout << "Age:" <<tea.getAge() << endl;

    return 0;
}

初始化列表的成員初始化順序:
C++初始化類成員時,是按照聲明的順序初始化的,而不是按照出現在初始化列表中的順序。

// 構造函數的聲明
class CMyClass {
    CMyClass(int x, int y);
    int m_x;
    int m_y;
};
// 構造函數的實現
CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y){
    
}

你可能以爲上面的代碼將會首先做m_y=y,然後做m_x=m_y,最後m_xm_y有相同的值。

但是編譯器先初始化m_x,然後是m_y,因爲它們是按這樣的順序 聲明 的。

結果是m_x將有一個不可預測的值。

有兩種方法避免它,一個是總是按照你希望它們被初始化的順序聲明成員;第二個是,如果你決定使用初始化列表,總是按照它們 聲明的順序 羅列這些成員。這將有助於消除混淆。

轉載自:必須在構造函數初始化式裏進行初始化的數據成員有哪些?

9.拷貝構造函數

格式:類名(const類名&變量名)變量名可寫可不寫

拷貝構造函數和構造函數一樣,也可以在函數名後面添加初始化列表,如果沒有去實現它,系統也會自動生成一個默認的。

拷貝構造函數的參數是確定的,不能重載。

在下面的3中情況下,系統自動調用 拷貝構造函數 ,此時不會調用 構造函數

  1. 一個對象作爲函數參數,以值傳遞的方式傳入函數體;
  2. 一個對象作爲函數返回值,以值傳遞的方式從函數返回;
  3. 一個對象用於給另外一個對象進行初始化(常稱爲賦值初始化);

例1,使用 賦值初始化 來創建新的對象 :

class stu{
public:
    // 構造函數
    stu(){
        cout << "stu" << endl;
    }
    // 拷貝構造函數
    stu(const stu& stu01){
        cout << "stu01" << endl;
    }
};

int main(int argc,char *argv[]){
    stu stu01;			// 調用構造函數
    stu stu02 = stu01;	// 調用拷貝構造函數
    stu stu03(stu01);	// 調用拷貝構造函數
    return 0;
}

/* 輸出:
stu
stu01
stu01
*/

這裏創建了3個實例,但是實際上只有第一次調用了構造函數,之後兩次調用的都是拷貝構造函數。

例2,當類作爲 函數參數 傳遞時,將觸發拷貝構造函數:

class stu{
public:
    // 構造函數
    stu(){
        cout << "stu" << endl;
    }
    // 拷貝構造函數
    stu(const stu&){
        cout << "stu01" << endl;
    }
};

// 當作爲函數參數傳遞時
void test(stu t){
}

int main(int argc,char *argv[]){
    stu stu01;		// 調用構造函數 
    test(stu01);	// 調用拷貝構造函數
    return 0;
}
/* 輸出:
stu
stu01
*/

例3,當對象作爲 返回值 時,觸發拷貝構造函數:

// 請看筆記中的封裝篇(下)中的第3章:對象指針 
// 其中有相關的實例代碼

拷貝構造函數中,如何獲取引用&的值、如何手寫拷貝構造函數:

在本例中,部分類的屬性操作方法的後面添加const,將其變爲const成員函數(常對象函數),以便我們在拷貝構造函數中使用 &引用 來調用他們獲取數據。

// ====== 類聲明START ======
class Teacher{
public:
    // 構造函數(沒有返回值),這裏設定了默認參數
    Teacher(string name = "Jim",int max = 50);
    // 拷貝構造函數
    Teacher(const Teacher& tea);

    // 操作類的屬性
    void setName(string name);
    string getName() const;
    int getMax() const;

private:
    // 屬性
    string m_strName;   // 名字
    const int m_iMax;   // 最多帶的學生數
};
// ====== 類聲明END ======

// ====== 類實現START ======
// 構造函數(在初始化列表中,初始化屬性)
Teacher::Teacher(string name,int max):m_strName(name),m_iMax(max){
    cout << "02" << endl;
}

// 拷貝構造函數(在初始化列表中,初始化屬性)
Teacher::Teacher(const Teacher &teaRaw):m_strName(teaRaw.getName()),m_iMax(teaRaw.getMax()) {
    cout << "03" << endl;
    cout << "teaRaw.getName() = " << teaRaw.getName() << endl;
    cout << "teaRaw.getMax() = " << teaRaw.getMax() << endl;
}

// 操作類的屬性
// name
void Teacher::setName(string name) {m_strName = name;}
string Teacher::getName() const {return m_strName;}
// max
int Teacher::getMax() const {return m_iMax;}
// ====== 類實現END ======

int main(int argc,char *argv[]) {

    // 將調用構造函數
    Teacher tea;
    cout << "Name:" << tea.getName() << endl;
    cout << "Max:" << tea.getMax() << endl;

    // 將調用拷貝構造函數(在這一步中,將展示如何獲取引用&的值)
    Teacher tea01 = tea;
    cout << "Name:" << tea01.getName() << endl;
    cout << "Max:" << tea01.getMax() << endl;

    return 0;
}

/* 輸出:
02
Name:Jim
Max:50
03
teaRaw.getName() = Jim
teaRaw.getMax() = 50
Name:Jim
Max:50
*/

這樣我們就能夠在 拷貝構造函數 中獲取到tea這個對象的值了。

這裏需要再次強調,如果在類的屬性由const修飾,意味着我們只能在 初始化 的時候爲其賦值,其它的時刻任何賦值操作都會報錯,所以我們不能在構造函數/拷貝構造函數的{ }爲其賦值,只能在 初始化列表 中賦值,且之後再無法修改。

注意: 關於const 成員函數的聲明,const 關鍵字只能放在函數聲明的尾部,看起來確實是怪怪的。

10.析構函數

格式:~類名(),不能加任何參數。

成員函數除了析構函數之外,都可以有重載。

本篇爲視頻教程筆記,視頻如下:

C++遠征之封裝篇(上)

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