C++面向對象模型初探
C++對象模型可以概括爲以下2部分:
- 語言中直接支持面向對象程序設計的部分,主要涉及如構造函數、析構函數、虛函數、繼承(單繼承、多繼承、虛繼承)、多態等等。
- 對於各種支持的底層實現機制。
- 在c語言中,“數據”和“處理數據的操作(函數)”是分開來聲明的,也就是說,語言本身並沒有支持“數據和函數”之間的關聯性。
- 在c++中,通過抽象數據類型(abstract data type,ADT),在類中定義數據和函數,來實現數據和函數直接的綁定。
在C++類中有兩種成員數據:static、nonstatic;三種成員函數:static、nonstatic、virtual。
類、對象、成員變量、成員函數
類,是一個抽象數據類型
對象,我們用類去定義對象
成員變量,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++編譯器不會提供默認無參構造函數
- 默認拷貝構造函數成員變量簡單賦值
總結:只要你寫了構造函數,那麼你必須用。
構造析構階段性總結
- 構造函數是C++中用於初始化對象狀態的特殊函數
- 構造函數在對象創建時自動被調用
- 構造函數和普通成員函數都遵循重載規則
- 拷貝構造函數是對象正確初始化的重要保證
- 必要的時候,必須手工編寫拷貝構造函數
深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 使用格式
類對象的動態建立與釋放
使用類名定義的對象都是靜態的,在程序運行過程中,對象所佔的空間是不能隨時釋放的。但有時人們希望在需要用到對象時才建立對象,在不需要用該對象時就撤銷它,釋放它所佔的內存空間以供別的數據使用
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指針
- 在類外調用靜態成員函數用 “類名 :: ”作限定詞,或通過對象調用
疑難問題:靜態成員函數中,不能使用普通變量
靜態成員變量屬於整個類的,分不清楚,是那個具體對象的屬性。