1. 程式人生 > >C++:類與物件(上)

C++:類與物件(上)

1.類與物件的初步認知

ç語言是面向過程的,關注的是過程,分析出求解問題的步驟,通過函式呼叫逐步解決問題。

C ++是基於面向物件的,關注的是物件,將一件事情拆分成不同的物件,靠物件之間的互動完成。

2.類的引入

Ç語言中,結構體中只能定義變數,在C ++中,結構體內不僅可以定義變數,也可以定義函式

struct Student
{
    void SetStudentInfo(const char* name, const char* gender, int age)
    {
        strcpy(_name, name);
        strcpy(_gender, gender);
        _age = age;
    }

    void PrintStudentInfo()
    {
        cout<<_name<<" "<<_gender<<" "<<_age<<endl;
    }

    char _name[20];
    char _gender[3];
    int _age;
};

int main()
{
    Student s;
    s.SetStudentInfo("Peter", "男", 18);
    return 0;
}

上面結構體的定義,在C ++中更喜歡用類來代替 

3.類的定義

class className
{
    // 類體:由成員函式和成員變數組成

}; // 一定要注意後面的分號

類為定義類的關鍵字,類名為類的名字,{}中為類的主體,注意類定義結束時後面分號。

類中的元素稱為類的成員:中類的資料稱為類的屬性或者成員變數; 中類的函式稱為類的方法或者成員函式。 

類的兩種定義方式:

1.宣告和定義全部放在類體中,需要注意:成員函式如果在類中定義,編譯器可能會將其當成行內函數處理。

2.宣告放在.h檔案中,類的定義放在.cpp檔案中

4.類的訪問限定符及封裝

4.1訪問限定符

C ++實現封裝的方式:用類將物件的屬性與方法結合在一塊,讓物件更加完善,通過訪問許可權選擇性的將其介面提供給外部的使用者使用。

【訪問限定符說明】

1. public修飾的成員在類外可以直接被訪問

2. protected和private修飾的成員在類外不能直接被訪問(此處protected和private是類似的)

3.訪問許可權作用域從該訪問限定符出現的位置開始直到下一個訪問限定符出現時為止

4. class的預設訪問許可權為private,struct為public(因為struct要相容C)

注意:訪問限定符只在編譯時有用,當資料對映到記憶體後,沒有任何訪問限定符上的區別

問題:

C ++中結構和類的區別是什麼?

解答:C ++需要相容C語言,所以C ++中結構可以當成結構體去使用。另外C ++中結還可以用來定義類。和類是定義類是一樣的,區別是結構的成員預設訪問方式是public, class是struct的成員預設訪問方式是private。

4.2封裝

【面試題】面向物件的三大特性:封裝,繼承,多型。

在類和物件階段,我們只研究類的封裝特性,那什麼是封裝呢?

封裝:將資料(類的屬性)和操作資料的方法進行有機結合,隱藏物件的屬性和實現細節,僅對外公開介面來和物件進行互動。 封裝本質上是一種管理:我們如何管理兵馬俑呢?比如如果什麼都不管,兵馬俑就被隨意破壞了。那麼我們 首先建了一座房子把兵馬俑給封裝起來。但是我們目的全封裝起來,不讓別人看。所以我們開放了售票通 道,可以買票突破封裝在合理的監管機制下進去參觀。類也是一樣,我們使用類資料和方法都封裝到一下。 不想給別人看到的,我們使用protected/private把成員封裝起來。開放一些共有的成員函式對成員合理的訪 問。所以封裝本質是一種管理。

第一層封裝:將資料和方法都封裝到類中

第一層封裝:使用訪問限定符

5.類的作用域

類定義了一個新的作用域,類的所有成員都在類的作用域中。在類體外定義成員,需要使用 :: 作用域解析符 指明成員屬於哪個類域。

class Person
{
    public:
    void PrintPersonInfo();
    private:
    char _name[20];
    char _gender[3];
    int _age;
};

// 這裡需要指定PrintPersonInfo是屬於Person這個類域
void Person::PrintPersonInfo()
{
    cout<<_name<<" "_gender<<" "<<_age<<endl;
}

6.類的例項化

用類型別建立物件的過程,稱為類的例項化

1. 類只是一個模型一樣的東西,限定了類有哪些成員,定義出一個類並沒有分配實際的記憶體空間來儲存它

2. 一個類可以例項化出多個物件,例項化出的物件 佔用實際的物理空間,儲存類成員變數

3. 做個比方。類例項化出物件就像現實中使用建築設計圖建造出房子,類就像是設計圖,只設計出需要什 麼東西,但是並沒有實體的建築存在,同樣類也只是一個設計,例項化出的物件才能實際儲存資料,佔用物理空間

7.類的物件大小的計算

// 類中既有成員變數,又有成員函式
class A1 
{
public:
    void f1(){}
private:
    int _a;
};

// 類中僅有成員函式
class A2 
{
public:
    void f2() {}
};

// 類中什麼都沒有---空類
class A3
{};

sizeof(A1) : 4 

sizeof(A2) : 1

sizeof(A3) : 1

結論:一個類的大小,實際就是該類中”成員變數”之和,當然也要進行記憶體對齊。

注意空類的大小,空類比較特殊,編譯器給了空類一個位元組來唯一標識這個類。

所以,類物件的模型是:只儲存成員變數,成員函式存放在公共的程式碼段。

7.3 結構體記憶體對齊規則

1. 第一個成員在與結構體偏移量為0的地址處。

2. 其他成員變數要對齊到某個數字(對齊數)的整數倍的地址處。 注意:對齊數 = 編譯器預設的一個對齊數 與 該成員大小的較小值。 VS中預設的對齊數為8,gcc中的對齊數為4

3. 結構體總大小為:最大對齊數(所有變數型別最大者與預設對齊引數取最小)的整數倍。

4. 如果嵌套了結構體的情況,巢狀的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含巢狀結構體的對齊數)的整數倍。

問題:

1. 結構體怎麼對齊? 為什麼要進行記憶體對齊

對齊規則如上,記憶體對齊以後使得記憶體按照整數位置訪問,提高了訪問效率,但是會浪費記憶體空間(以空間換時間)

2. 如何讓結構體按照指定的對齊引數進行對齊

3. 如何知道結構體中某個成員相對於結構體起始位置的偏移量

4. 什麼是大小端?如何測試某臺機器是大端還是小端,有沒有遇到過要考慮大小端的場景

8.類成員函式的this指標

8.1 this指標的引出

class Date
{
public :
    void Display ()
    {
        cout <<_year<< "-" <<_month << "-"<< _day <<endl;
    }
    void SetDate(int year , int month , int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};

int main()
{
Date d1, d2;
d1.SetDate(2018,5,1);
d2.SetDate(2018,7,1);
d1.Display();
d2.Display();
return 0;
}

對於上述類,有這樣的一個問題:

Date類中有SetDate與Display兩個成員函式,函式體中沒有關於不同物件的區分,那當d1呼叫SetDate函式 時,該函式是如何知道應該設定d1物件,而不是設定d2物件呢?

C++中通過引入this指標解決該問題,即:C++編譯器給每個“成員函式“增加了一個隱藏的指標引數,讓該指標指向當前物件(函式執行時呼叫該函式的物件),在函式體中所有成員變數的操作,都是通過該指標去訪問。只不過所有的操作對使用者是透明的,即使用者不需要來傳遞,編譯器自動完成。

8.2 this指標的特性

1. this指標的型別:類型別* const

2. 只能在“成員函式”的內部使用

3. this指標本質上其實是一個成員函式的形參,是物件呼叫成員函式時,將物件地址作為實參傳遞給this 形參。所以物件中不儲存this指標。

4. this指標是成員函式第一個隱含的指標形參,一般情況由編譯器通過ecx暫存器自動傳遞(windows下是這樣),不需要使用者傳遞

【問題】

這個指標存在哪裡?

因為這個指標是成員函式的形參,所以在棧上,在優化之後會放在暫存器裡

這個指標可以為空嗎? 

可以