C++:02.類與對象:struct於class,拷貝構造,靜態成員,常對象,嵌套類,友元,類模板

struct與class:

在C語言中struct定義結構體,在C++中struct與class都是定義類,區別在struct默認public,class默認private。還有一種說法:沒有成員函數的 struct 還是稱作“結構”,結構變量不是對象;有成員函數的 struct 就是類。

在.c文件中訪問結構體,不可以省略struct

typedef struct Node
{
	int data;
	struct Node *next;//不可以省略struct
}Node,*Pstack;

但是再.cpp文件中struct也是類。可以省略

 並且.c文件中空結構體,在VS中不能通過編譯,在gcc下空結構體的大小爲0。在.cpp中空類的大小爲1,因爲類中有默認的構造函數等,調用合適的構造函數CGoods(CGoods *this)肯定會有this指針,而爲了放這個指針則肯定會至少有一字節空間。

一般來說:一個對象佔用的內存空間的大小等於其成員變量所佔用的內存空間的大小之和,也存在內存對齊。


類成員的訪問範圍:

private:用來指定私有成員。私有成員,不論是成員變量還是成員函數,都只能在該類的成員函數內部才能被訪問。

public:用來指定公有成員。一個類的公有成員在任何地方都可以被訪問。

protected:用來指定保護成員。這需要等介紹“繼承”之後再解釋。

尤其注意private:設置私有成員的機制叫作“隱藏”。“隱藏”的一個目的就是強制對成員變量的訪問一定要通過成員函數進行。這樣做的好處是易於代碼的維護。並且讓不希望對外開放的成員隱藏起來。


構造函數:

對象進行自動初始化。如果沒有自定義的構造函數,會使用默認構造函數,默認構造函數什麼都不幹。如果有了自定義的構造函數,編譯器就不會生成默認構造函數。

定義一個對象:1、開闢空間  2、調用構造函數       所以說構造函數執行時,對象的內存空間已經分配好了,構造函數的作用只是初始化這片空間。

構造函數可以添加任意的形參變量,所以構造函數可以重載。

調用對象默認構造函數的時候,不能在對象後面添加空的(),成函數聲明瞭。()裏有內容是可以的,直接調用有參數的構造函數。


拷貝構造函數:

調用:

一般加const,這樣能讓常量對象作爲參數,非常量對象也可以。
必須有&,沒有的話是值傳遞,在傳參的時候,同樣會調用拷貝構造,造成遞歸循環。並且值傳遞調用函數會產生
開銷,所以直接用引用就好。同時傳引用的缺點在於,如果形參改變,實參也會改變,所以我們加const確保實參
的值不會改變。
Complex(const Complex & c)
{
    real = c.real; imag = c.imag;
    cout<<"Copy Constructor called"<<endl ;
}

Complex c1(1,2);
Complex c2 (cl);調用拷貝構造。與Complex c2 = cl(初始化);是一個意思。

Complex c3;
c3 = c1;調用賦值(=)運算符重載。(賦值)

同樣沒有自定義的拷貝構造就會調用默認的拷貝構造,有自定義的就沒有默認的了。

注意:1、由於拷貝構造是自己寫的,所以實際上它是否真的是完全拷貝,取決於寫代碼的人怎麼寫的拷貝構造函數。

           2、對於一般函數來說,如果參數是類對象,調用該函數時,也會調用拷貝構造函數。

           經過上述兩點,會導致函數的形參的值並不一定等於函數調用時實參的值。


析構函數:

一個類有且僅有一個析構函數。

同樣如果沒寫析構函數,則編譯器生成默認析構函數。如果定義了析構函數,就不生成默認析構函數。

析構函數在對象消亡時即自動被調用。

先構造的後析構,後構造的先析構


靜態成員變量和靜態成員函數:

class CRectangle
{
public:
    static void PrintTotal ();  //靜態成員函數
private:
    int w,h;
    static int nTotalArea;  //靜態成員變量
    static int nTotalNumber;  //靜態成員變量
};


int CRectangle::totalNumber = 0;
int CRectangle::totalArea = 0;
類的靜態成員變量必須在定義類的文件中對靜態成員變量進行一次聲明或初始化,否則編譯能通過,鏈接不能通過。

普通成員變量:每個對象各自有一份。訪問普通成員時,要通過對象名.成員名等方式。

靜態成員變量:只有一份,被所有同類對象共享。所以在處理只需要一份的成員變量的時候設置爲靜態的,例如:單例模式。訪問靜態成員時,則可以通過類名::成員名的方式訪問。不需要指明被訪問的成員屬於哪個對象或作用於哪個對象。因此,甚至可以在還沒有任何對象生成時就訪問一個類的靜態成員。靜態成員變量並不是全局變量,私有類型同樣不能再成員函數外訪問。設置靜態成員的目的就是對齊進行封裝,更容易理解和維護。

普通成員函數:一定是作用在某個對象上的。

靜態成員函數:並不具體作用在某個對象上。訪問靜態成員函數兩種方式都可以,沒有區別。因爲靜態成員函數不具體作用於某個對象,所以靜態成員函數不能訪問非靜態成員變量,也不能調用非靜態成員函數。只能訪問靜態的。靜態成員函數沒有this指針。

靜態成員函數和變量都是獨立於類的實例對象之外的。

使用 sizeof 運算符不會將靜態成員變量計算在內。對上面的 CRectangle 類來說,sizeof(CRectangle) 的值是 8。


常對象和常成員函數:

常對象:對象的值初始化之後就不會在被改變。在定義對象時可以加const。

常對象無法調用普通成員函數,只能調用常成員函數(函數加const)。同樣常函數也不能使用其他普通成員變量。

兩個成員函數的名字和參數表相同,但一個是 const 的,一個不是,則它們算重載。

class Sample
{
public:
    void GetValue() const;  //常成員函數
};
void Sample::GetValue() const  //常成員函數
{
}
int main()
{
    const Sample o;
    o.GetValue();  //常量對象上可以執行常量成員函數
    return 0;
}

基本上,如果一個成員函數中沒有調用非常量成員函數,也沒有修改成員變量的值,那麼將其寫成常量成員函數是好的習慣。

補充:

在C中const定義的是常變量,不可以定義數組。除了不能做左值,其他與一般變量一致。
不能直接更改值,但可以通過訪問內存來改值

在C++中const定義的常量,可以定義數組
編譯方式:所有使用常量名字的地方,都被常量的值替換了

const: 常變量  就看初始值是不是一個立即數 10 20 30
eg:int a = 10;
       coust int b = a;常變量

       coust int b = 10;常量

       const修飾的  常量&常變量  保證它不能被直接或者間接的修改


封閉類:

一個類的成員變量是另一個類的對象。例如:A類的成員有B類的對象。

初始化的時候如果初始化A類對象,也會對B類對象初始化。我們使用初始化列表進行初始化。

class CTyre  //輪胎類
{
public:
    CTyre(int r, int w) : radius(r), width(w) { }
private:
    int radius;  //半徑
    int width;  //寬度
};

class CCar   //汽車類  封閉類
{  
public:
    CCar(int p, int tr, int tw);
private:
    int price;  //價格
    CTyre tyre;
};

CCar::CCar(int p, int tr, int tw) : price(p), tyre(tr, tw)//參數列表
{
};

int main()
{
    CCar car(20000, 17, 225);
    return 0;
}

封閉類對象生成時,先執行所有成員對象的構造函數,然後才執行封閉類自己的構造函數。成員對象構造函數的執行次序和成員對象在類定義中的次序一致,與它們在構造函數初始化列表中出現的次序無關。所以初始化鏈表成員之間不要相互依賴。

當封閉類對象消亡時,先執行封閉類的析構函數,然後再執行成員對象的析構函數。

對於封閉類來說,如果涉及到類模板,那麼再寫封閉類裏的函數的時候注意成員對象加上<T>。

 


嵌套類:

一個類裏定義了另一個類

template< typename T >
class Link
{
public:
    Link();
    ~Link();
    void insert(const T &val);
    void remove(const T &val);
    void show();
private:
    // 嵌套類
    struct Node
    {
        給了默認值的構造函數,T()保證了T不論是哪個類型,初始化都是0,float是0.0,bool是false。這樣不需再寫默認構造函數
        Node(T val = T()):_data(val),_next(NULL){}
        T _data;
        Node *_next;
    };
    // 指向頭結點的指針
    Node *_head;
};

 Node(T val = T()):_data(val),_next(NULL){}

T()保證了T不論是哪個類型,初始化都是0。這是一個很巧妙的方法。


友元函數和友元類:friend

友元函數:1、聲明全局函數爲一個類中的友元,則這個全局函數可以使用這個類中的私有成員變量。

                  2、聲明類A中的函數爲類B中函數的友元函數,則該函數可以使用類B的私有成員變量。

class B;因爲類A中使用了類B,所以需要提前聲明類B。
class A
{
public:
    void Fun(B *p);
private:
    int num;
};

class B
{
private:
    int a;
    int b;
    friend void A::Fun(B *p);標明所屬類。
};

void A::Fun(B *p)必須在類外定義   並且在訪問類B的時候需要通過指針
{
    cout << num;說明這個是有this指針的。
    cout << p->a << p->b <<endl;不清楚是什麼原因,在VS中顯示標紅(說你使用了類B私有成員),但其實不會發生錯誤
}

爲什麼必須在類外定義:
    如果在類A中定義,但這時候你類B並沒有定義,會報錯。
    如果在類B中定義,你函數都說了是類A的函數,咋能在類B定義,會報錯。

友元類:類 A 可以將類 B 聲明爲自己的友元,類 B 的所有成員函數就都可以訪問類 A 對象的私有成員。

class A
{
private:
    int num;
    friend class B;  //聲明 B爲友元類
};

class B
{
public:
    void Fun() 
    {
        a.num += 1000;  因B是A的友元類,故此處可以訪問類A的私有成員
    }
private:
    A a;
};

注意:

  1. 友元關係不能被繼承。
  2. 友元關係是單向的,不具有交換性。若類B是類A的友元,類A不一定是類B的友元。
  3. 友元關係不具有傳遞性。若類B是類A的友元,類C是B的友元,類C不一定是類A的友元。

 類模板:

意義:可以定義類型變量,來接收用戶傳入的類型

template<> : 定義模板參數列表
                               <>裏:typename T, typename E, typename R: 類型變量    用,號分割

類模板  =》  實例化   =》  模板類

注意:模板的代碼,在沒有實例化之前,是不編譯的。

類模板的選擇性實例化   實例化哪部分,編譯哪部分  可以通過實例化檢查是否有錯誤。

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