C++面向對象

C++面向對象模型初探

C++對象模型可以概括爲以下2部分:

  • 語言中直接支持面向對象程序設計的部分,主要涉及如構造函數、析構函數、虛函數、繼承(單繼承、多繼承、虛繼承)、多態等等。
  • 對於各種支持的底層實現機制。

  • 在c語言中,“數據”和“處理數據的操作(函數)”是分開來聲明的,也就是說,語言本身並沒有支持“數據和函數”之間的關聯性。
  • 在c++中,通過抽象數據類型(abstract data type,ADT),在類中定義數據和函數,來實現數據和函數直接的綁定。

在C++類中有兩種成員數據:static、nonstatic;三種成員函數:static、nonstatic、virtual。
C++類

類、對象、成員變量、成員函數

類,是一個抽象數據類型
對象,我們用類去定義對象
成員變量,C++中用於表示類屬性的變量
成員函數,C++中用於表示類行爲的函數

封裝、多態、繼承

封裝

  • 把屬性和方法進行封裝,對屬性和方法進行訪問控制
  • 對類的訪問控制:
    • Public修飾成員變量和成員函數可以在類的內部和類的外部被訪問
    • Private修飾成員變量和成員函數只能在類的內部被訪問

struct 與 class的區別

  • 在用struct定義類時,所有成員的默認屬性爲public
  • 在用class定義類時,所有成員的默認屬性爲private

構造函數與析構函數

構造函數:

  • constructor來處理對象的初始化
  • 構造函數是一種特殊的成員函數,與其他成員函數不同,不需要用戶來調用它,而是在建立對象時自動執行。

constructor的調用

  • 自動調用:一般情況下C++編譯器會自動調用構造函數
  • 手動調用:在一些情況下則需要手工調用構造函數
  • 沒有任何返回類型的聲明

析構函數:

  • C++中的類可以定義一個特殊的成員函數清理對象,這個特殊的成員函數叫做析構函數
  • 析構函數需要照顧對象的屬性的內存生命週期
  • 析構函數沒有參數也沒有任何返回類型的聲明
  • 析構函數在對象銷燬時自動被調用
  • C++編譯器自動調用
copy構造函數
    Test4() //無參數構造函數
    {
        m_a = 0;
        m_b = 0;
        cout<<"無參數構造函數"<<endl;
    }
    Test4(int a, int b) //有參數構造函數 //3種方法
    {
        m_a = a;
        m_b = b;
        cout<<"有參數構造函數"<<endl;
    }
    //賦值構造函數 (copy構造函數) //
    Test4(const Test4& obj )
    {
        cout<<"我也是構造函數 " <<endl;
        m_b = obj.m_b + 100;
        m_a = obj.m_a + 100;
    }
默認構造函數

2個特殊的構造函數

  • 默認無參構造函數
    當類中沒有定義構造函數時,編譯器默認提供一個無參構造函數,並且其函數體爲空
  • 默認拷貝構造函數
    當類中沒有定義拷貝構造函數時,編譯器默認提供一個默認拷貝構造函數,簡單的進行成員變量的值複製

構造函數調用規則

  • 當類中沒有定義任何一個構造函數時,c++編譯器會提供默認無參構造函數和默認拷貝構造函數
  • 當類中定義了拷貝構造函數時,c++編譯器不會提供無參數構造函數
  • 當類中定義了任意的非拷貝構造函數(即:當類中提供了有參構造函數或無參構造函數),c++編譯器不會提供默認無參構造函數
  • 默認拷貝構造函數成員變量簡單賦值

總結:只要你寫了構造函數,那麼你必須用。

構造析構階段性總結

  1. 構造函數是C++中用於初始化對象狀態的特殊函數
  2. 構造函數在對象創建時自動被調用
  3. 構造函數和普通成員函數都遵循重載規則
  4. 拷貝構造函數是對象正確初始化的重要保證
  5. 必要的時候,必須手工編寫拷貝構造函數

深copy與淺copy

淺拷貝

  • 指針變量被賦值,但是指針變量所指向的內存空間未被賦值。

深拷貝

  • 把對象的所有屬性值和內存空間都拷貝

淺拷貝原因

  • 因爲obj2只是copy對象obj1的屬性值,和指針值,所以爲指針在堆區裏面再分配內存空間,即obj1與obj2的指針指向的內存空間是同一個,故在調用析構函數時,先析構掉obj2指針所指向的內存空間,這時obj1的指針就變爲了野指針。而再調用obj1的析構函數時,會發現指針指向的內存空間已經被析構掉了,這樣就會發生core dump。
    淺拷貝原因分析
    等號操作
    對象的等號賦值
    解決淺拷貝的辦法就是深拷貝,C++默認的拷貝構造函數與等號操作都是淺拷貝

對象初始化列表

  • 如果我們有一個類成員,它本身是一個類或者是一個結構,而且這個成員它只有一個帶參數的構造函數,沒有默認構造函數。這時要對這個類成員進行初始化,就必須調用這個類成員的帶參數的構造函數,如果沒有初始化列表,那麼他將無法完成第一步,就會報錯。
    錯誤原因與淺拷貝原因一致
    解決辦法是重載等號操作符或者調用對象初始化列表
  • 當類成員中含有一個const對象時,或者是一個引用時,他們也必須要通過成員初始化列表進行初始化,因爲這兩種對象要在聲明後馬上初始化,而在構造函數中,做的是對他們的賦值,這樣是不被允許的。

語法規則
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
// some other assignment operation
}

  • 注意概念
    初始化:被初始化的對象正在創建
    賦值:被賦值的對象已經存在

注意

  • 成員變量的初始化順序與聲明的順序相關,與在初始化列表中的順序無關
  • 初始化列表先於構造函數的函數體執行

class ABC{
public:
         ABC(int a, int b, int c)
         {
                  this->a = a;
                  this->b = b;
                  this->c = c;
                  printf("a:%d,b:%d,c:%d \n", a, b, c);
                  printf("ABC construct ..\n");
         }
         ~ABC()
         {
                  printf("a:%d,b:%d,c:%d \n", a, b, c);
                  printf("~ABC() ..\n");
         }
protected:
private:
         int a;
         int b;
         int c;
};
class MyD
{
public:
        //初始化成員列表
         MyD():abc1(1,2,3),abc2(4,5,6),m(100)
         //MyD()
         {
                  cout<<"MyD()"<<endl;
         }
         ~MyD()
         {
                  cout<<"~MyD()"<<endl;
         }
protected:
private:
         ABC abc1; //c++編譯器不知道如何構造abc1
         ABC abc2;
         const int m;
};
int main()

{
         MyD myD;
         return 0;
}

對象的動態建立與釋放

new 與 delete

  • 在軟件開發過程中,常常需要動態地分配和撤銷內存空間,例如對動態鏈表中結點的插入與刪除。
  • 在C語言中是利用庫函數malloc和free來分配和撤銷內存空間的。
  • C++提供了較簡便而功能較強的運算符new和delete來取代malloc和free函數

注意new和delete是運算符,不是函數,因此執行效率高
new運算符的例子

new int;  //開闢一個存放整數的存儲空間,返回一個指向該存儲空間的地址(即指針)
new int(100);  //開闢一個存放整數的空間,並指定該整數的初值爲100,返回一個指向該存儲空間的地址new char[10];  //開闢一個存放字符數組(包括10個元素)的空間,返回首元素的地址
new int[5][4];  //開闢一個存放二維整型數組(大小爲5-4)的空間,返回首元素的地址;
float *p=new float (3.14159);  //開闢一個存放單精度數的空間,並指定該實數的初值爲//3.14159,將返回的該空間的地址賦給指針變量p

new 與 delete 使用格式
new 與 delete

類對象的動態建立與釋放

使用類名定義的對象都是靜態的,在程序運行過程中,對象所佔的空間是不能隨時釋放的。但有時人們希望在需要用到對象時才建立對象,在不需要用該對象時就撤銷它,釋放它所佔的內存空間以供別的數據使用


C++中,可以用new運算符動態建立對象,用delete運算符撤銷對象
比如:

Box *pt;  //定義一個指向Box類對象的指針變量pt    
pt=new Box;  //在pt中存放了新建對象的起始地址在程序中就可以通過pt訪問這個新建的對象。
cout<<pt->height;  //輸出該對象的height成員    
cout<<pt->volume( );  //調用該對象的volume函數,計算並輸出體積

C++還允許在執行new時,對新建立的對象進行初始化.

Box *pt=new Box(12,15,18);

這種寫法是把上面兩個語句(定義指針變量和用new建立新對象)合併爲一個語句,並指定初值,這樣更精煉。
新對象中的height,width和length分別獲得初值12,15,18。調用對象既可以通過對象名,也可以通過指針。
在執行new運算時,如果內存量不足,無法開闢所需的內存空間,目前大多數C++編譯系統都使new返回一個0指針值。只要檢測返回值是否爲0,就可判斷分配內存是否成功。

ANSI C++標準提出,在執行new出現故障時,就“拋出”一個“異常”,用戶可根據異常進行有關處理。但C++標準仍然允許在出現new故障時返回0指針值。當前,不同的編譯系統對new故障的處理方法是不同的。
在不再需要使用由new建立的對象時,可以用delete運算符予以釋放.

delete pt; //釋放pt指向的內存空間
這就撤銷了pt指向的對象。此後程序不能再使用該對象。
如果用一個指針變量pt先後指向不同的動態對象,應注意指針變量的當前指向,以免刪錯了對象。在執行delete運算符時,在釋放內存空間之前,自動調用析構函數,完成有關善後清理工作。

靜態成員變量成員函數

靜態成員變量

  • 關鍵字 static 可以用於說明一個類的成員,靜態成員提供了一個同類對象的共享機制
  • 把一個類的成員說明爲 static 時,這個類無論有多少個對象被創建,這些對象共享這個 static 成員
  • 靜態成員局部於類,它不是對象成員
    靜態成員變量
#include<iostream>
using namespace std;
class  counter
{     
static  int  num ; //聲明與定義靜態數據成員
  public :
      void  setnum ( int i ) { num = i ; }        //成員函數訪問靜態數據成員
      void  shownum() { cout << num << '\t' ; }
} ;
int  counter :: num = 0 ;//聲明與定義靜態數據成員
void main ()
{   counter  a , b ;
    a.shownum() ; //調用成員函數訪問私有靜態數據成員
    b.shownum() ;
    a.setnum(10) ;
    a.shownum() ;
    b.shownum() ;
}

靜態成員函數

  • 靜態成員函數數冠以關鍵字static
  • 靜態成員函數提供不依賴於類數據結構的共同操作,它沒有this指針
  • 在類外調用靜態成員函數用 “類名 :: ”作限定詞,或通過對象調用
    靜態成員函數

疑難問題:靜態成員函數中,不能使用普通變量
靜態成員變量屬於整個類的,分不清楚,是那個具體對象的屬性。
靜態成員函數的使用

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