類與對象的認識

類和對象

c是一門面向過程的語言關注的過程,c++作爲一門面向對象的語言關注的時對象,把事情拆分爲不同的對象,通過對象的交互來實現。

一.類和對象的初認識

1.類的引入
類用關鍵字class引出,與之前的結構體類似,不同的是結構體內不能定義函數而在類中可以定義函數啦(c++中結構體也可以定義函數哦)
2.類的定義
類中成員函數的兩種定義方式:可以在類中定義定義,但編譯器有可能將其當作內聯函數來處理故推薦第二種——將函數的聲明和定義分開。
3.類的訪問限定符
public、protected、private--->protected和private修飾的對象在類外不能直接訪問
class類默認是private,struct默認是public
面向對象的三大特性:封裝、繼承、多態,這裏我們說一下封裝的定義:將數據和操作數據的方法進行有機結合,隱藏對象的屬性和實現細節,僅對外公開接口來和對象進行交互。
4.類的作用域
類定義了一個新的作用域,若要訪問類中的成員要用作用域限定符::進行訪問。
在這裏我們已經接觸了四個作用域:全局作用域、函數內的局部作用域、類、命名空間。
5.類的實例化(對象)
類限定了有那些成員,類是沒有空間開闢的,用類創建出的對象纔有實際的內存空間。
到這裏我們要思考一個問題,一個類的大小該如何計算呢??類中可以包含成員函數,此時又該怎麼辦呢??
通過測試我們可以知道類的大小其實就是類的成員變量的大小,類中的成員函數被放在公共代碼區,要注意內存對齊的問題。如果是一個空類,或這個類中只有成員函數,呢麼這個類的大小是1,因爲創建對象時要有區分。
6.this指針
c++對每個成員函數增加了一個默認的指針參數,讓該指針指向當前對象(函數運行時調用該函數的對象),在函數體中所有成員變量的操作,都是通過該指針去訪問。只不過所有的操作對用戶是透明的,即用戶不需要來傳遞,編譯器自動完成。呢麼這個就相當於我們用戶自己在傳參時傳入地址,正因爲有this指針的存在所以函數才能準確的訪問到調用它的主體。

  • this指針的特性
  • this指針的類型:類類型 * const,故this指針的指向是不能修改的。
  • this指針作爲函數的形參,函數調用時將對象的地址傳給this形參,故對象中不存儲this指針

    二.類中的默認函數之構造函數

    一個類定義好的話裏面並不是什麼都沒有,會自動生成六個默認函數
    1.構造函數
    構造函數是幹什麼的呢?
    構造函數是一個特殊的成員函數,名字與類名相同,創建類類型對象時由編譯器自動調用,保證每個數據成員都有 一個合適的初始值,並且在對象的生命週期內只調用一次。也就是說構造函數完成了對象的初始化,它的特點是:函數名與類名相同、無返回值、對象實例化時編譯器自動調用對應的構造函數、構造函數可以重載。系統自動生成的是無參的構造函數,當然我們可以自己寫一個帶有參數的構造函數,那麼我們在定義對象的時候就可以直接初始化拉,看下面的代碼:
    class data<br/>{<br/>public:<br/>data(int year=1999,int month=1,int day=1)<br/>{<br/>_year = year;<br/>_month = month;<br/>_day = day;<br/>}<br/>private:<br/>int _year;<br/>int _month;<br/>int _day;<br/>};

那麼在這個地方我們就完成了構造函數的創建,呢麼在主函數內定義對象的時候就可以直接初始化對象:
data a (1999,10,13);

那麼我們知道系統給的是無參的構造函數,所以我們在定義無參構造函數的時候就不需要帶(),否則就變成了函數聲明。
上述的代碼就是我們顯式創建的構造函數,當有它存在的時候編譯器就不會再生成默認構造函數

還有一個注意的點:無參構造函數和全缺省構造函數都稱作默認函數,是不允許重複定義的,只允許存在一個,故這兩個是不能構成重載函數的。

呢麼構造函數的作用是什麼呢?-->因爲變量類型不只是固定的int或char這種,也有可能是用戶自定義類型,那麼構造函數就會調用這種自定義類型的成員函數,這就是它的作用。

2。構造函數賦初值
上述過程中在構造函數體內的操作並不是初始化,那麼我們習慣將用構造函數的初始化列表對其進行初始化。

初始化列表:以一個冒號開始,接着是一個以逗號分隔的數據成員列表,每個"成員變量"後面跟一個放在括號中的初始值或表達式。
public:
data(int year=1999,int month=1,int day=1)
:_year(year)
, _month(month)
, _day(day)
{

}

注意:1.就算沒有初始化列表系統也會自動生成一個初始化列表,每個變量只能初始化一次,而且當成員變量是引用類型、const修飾、類類型成員變量(沒有默認構造函數)這三種情況時必須使用初始化列表對其初始化,當一個沒有默認構造函數的類類型成員變量不初始化系統也不會自動識別該如何初始化,故會出錯。

2.成員變量的初始化順序是按照成員變量的聲明順序決定的,與初始化列表的順序無關,那麼我們在初始化列表儘量於聲明的順序相同,更不要用變量進行賦值,極容易出錯。

  1. explicit關鍵字
    在c++中發現了一個有趣的現象,data a = 100; 這條語句居然可以通過編譯,等號兩邊的操作數類型不同卻可以通過編譯,於是我們可以研究一下這個複製重載在調用時的過程,進過測試我們發現在進行賦值運算符重載時首先調用了一次構造函數,爲100構造出一個類!所以構造函數不光有構造的作用,對於單個參數(實參)的構造函數還有類型轉化的作用。
    但有時我們不希望有這種情況的發生,用explicit修飾在構造函數前就會禁止這種類型轉化。


3.static成員

在c++中static可以修飾成員變量和成員函數,由static修飾的變量或函數稱作靜態成員變量或靜態成員函數
static修飾的爲所有類對象共享的!

區別:
| 靜態成員變量 | 普通成員變量 |
| 初始化在類外 | 類內初始化 |
| 所有對象共享的 | 每個對象都包含了一份 |
| 也可以通過類名::靜態成員名訪問 | 只能通過對象訪問 |
| 所有對象共享的 | 每個對象都包含了一份 |

靜態成員變量不會影響類的大小,也就是計算類的大小時不需要計算靜態成員的大小,他們用的是同一塊內存空間

| 靜態成員函數 | 普通成員函數 |
| 沒有隱藏的this指針 (不能訪問非靜態成員) | 有this指針 |
| 不能被const修飾 (因爲const修飾成員函數實際上修飾的時this指針) | 可以 |
| 可以不通過對象調用 | 必須通過對象調用 |

用static修飾的靜態成員函數只能訪問靜態成員變量,不能訪問普通成員變量。
而普通成員函數都可以訪問。

二.析構函數

析構函數與構造函數函數相對應,和構造函數構成函數重載,也就是說函數名也是類名,在函數名前加~代表析構函數,這個函數沒有參數和返回值,析構函數並不是對類對象進行銷燬,而是完成類的一些資源清理工作。對象在生命週期結束時會自動調用析構函數。

三.拷貝構造函數

拷貝構造函數:只有單個形參,該形參是對本類類型對象的引用(一般常用const修飾),在用已存在的類類型對象創建新對象時由編譯器自動調用。

注意:拷貝函數是對構造函數的一個重載,它的形參只有一個且必須船引用,如果傳值調用會無窮遞歸。

那麼有這個函數的存在我們在定義對象時就可以用已有的對象進行新對象的定義:
如:date d1; data d2(d1);

同樣的,編譯器也會爲我們生成一個默認的拷貝構造函數,但它完成的時淺拷貝工作,比如下面的情況:

class String
{
public:
    String(const char* str = "jack")
    {
        _str = (char*)malloc(strlen(str) + 1);
        strcpy(_str, str);
    }

    ~String()
    {
        cout << "~String()" << endl;
        free(_str);
    }
private:
    char* _str;
};

int main()
{
    String s1("hello");
    String s2(s1);
}

在構造函數內我們完成了動態創建內存空間,這種情況默認拷貝構造函數就會原封不動的把這塊地址拷貝到新對象d2中,這並不是我們想要的結果,d2和d1共用同一塊內存空間對d2進行修改d1也會改變。這叫做淺拷貝,所以我們有必要自定義一種深拷貝的構造函數。這個後期在做介紹。

四.賦值運算符重載

在談賦值運算符重載前我們有必要先談一下什麼是運算符重載,那麼兩個類是無法比較大小的,但是如果我們進行運算符重載,按照大小關係進行重載那麼類也就可以使用比較運算符了,其它運算符也是一樣的。

用關鍵字operator進行運算符重載
有一類特殊的運算符重載,就是前置++和後置++的重載,爲了有所區別

賦值運算符主要有四點:

  1. 參數類型
  2. 返回值
  3. 檢測是否自己給自己賦值
  4. 返回this//this作爲返回值是爲了解決連續賦值的問題
  5. 一個類如果沒有顯式定義賦值運算符重載,編譯器也會生成一個,完成對象按字節序的值拷貝。

同樣完成的也是淺拷貝,所以我們也有必要自己實現一個賦值運算符的重載。深拷貝的實現同樣也放在後面說。

最後兩種默認函數是普通變量和const變量的取地址,這兩種很少自己實現。

四.友元

如果我們對輸出運算符“<<”進行重載該怎麼做呢
ostream & operator<<(ostream & _cout)------>這是對<<重載的函數函數,如果這樣定義函數的話其中包含的this指針作爲第一個形參,所以在調用函數想要輸出一個類的時候就不能按照常規的cout<<d這種方式進行輸出,函數的第一個參數是this指針,所以調用時要d<<cout,這樣才能正常的輸出。
那麼我們怎麼能避免這種方式而是按照常規的方式對<<進行重載呢

這裏就要我們就要在全局作用域定義一個重載函數把兩個參數按照正常的順序傳入,但是在類中就不能不能引用這個函數的私有成員,我們只要在類中聲明這個函數用friend關鍵字修飾那麼這個函數就是這個類的友元函數也就可以在類內訪問了

注意:
友元函數是普通函數並不是類中的成員函數
友元函數可訪問類的私有成員,但不是類的成員函數
友元函數不能用const修飾(因爲const修飾的是this指針,而友元函數並沒有this指針)
友元函數可以在類定義的任何地方聲明,不受類訪問限定符限制
一個函數可以是多個類的友元函數
友元函數的調用與普通函數的調用和原理相同

同樣一個類也可以是另一個類的友元類,這個類就可以訪問其內部的私有成員了,但注意此過程是單向的且不能傳遞。

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