c++源碼高級反思
2015/12/04 16:22
瀏覽264
迴響0
推薦0
引用0
結構體默認情況下,其成員是共有(pubilic)的,類默認情況下,其成員是私有的,這是結構體和類的區別之一。
Visual c++中類名的風格是,所有類的名字都以大寫字母C開頭,以表示這個類的名字。
C++中類體被分成三類:
共有成員:以關鍵字public指明
私有成員:以關鍵字private指明
保護成員:以關鍵字protected指明
類的共有成員,在程序的任何位置都能夠以正確的方式引用它
類的私有成員只能被其自身成員所訪問,即私有成員的名字只能出現在所屬類類體、成員函數中,不能出現在其它函數中。
類的保護成員只能在該類的派生類類體中使用
類的說明通常放在一個以.h為擴展名的文件中,稱為頭文件,其中定義類的接口(iterface),可以同其他類的說明同其他類的說明同放在一個文件。
如果類的說明程序行較多,那麽應該將其他放在一個獨立文件中,visual C++的風格是以主文件為類名去掉前面的字符C,例如Cbook的類說明可以放在文件book.h中,將類體的定義放於一個以.cpp為擴展名的文件中, 這稱為類的實現文件。在這個文件的開始部分應該用文件包含指令將類說明文件包含進來。
對象之間的聯系通過消息來傳遞,消息機制是對象之間相互聯系和相互作用的方式。
對象數組是以數組元素為對象的數組。該數組中若幹個元素必須是同一個類的若幹個對象。
構造函數(constructor)是在類中定義的一種特殊的函數,它的函數的名稱和類的名稱相同。構造函數的主要功能是為對象分配空間,也可用來為類成員變量賦初值,因此構造函數不能有返回類型,甚至不能有return語句。
構造函數可以在類中聲明,在外部定義。
在類的外部定義構造函數時,需要在函數前加上類名和域運算符,否則在編譯時系統不能識別其屬於哪個類。
實際應用中,一般都要給類定義構造函數,如果沒有定義,編譯系統就自動生成一個默認的構造函數,這個默認的構造函數不帶任何參數,只能給對象開辟一個存儲空間,而不能為對象中的數據成員賦初值。
不帶參數的構造函數對象的初始化是固定的,如希望在建立對象時通過參數初始化數據成員,應使用帶參數的構造函數
當構造函數帶有參數時,在定義對象時必須給構造函數傳遞參數,否則,構造函數將不被執行。
創建對象調用的構造函數是否帶參數,其創建的對象成員變量的初始化程度是不一樣的。
一個類可以有多個不同參數形式的構造函數。
C++允許對構造函數重載,即定義多個參數及參數類型不同的構造函數,用多種方法對構造函數進行初始化。
拷貝構造函數是一種特殊的構造函數,它的作用是用一個已經存在的對象來初始化該類的新對象,用戶可根據需要定義拷貝構造函數,也可以由系統生成一個默認的拷貝構造函數。
在創建對象時,調用的是構造函數還是拷貝構造函數,這是由編譯系統根據需要創建對象的參數來確定的
析構函數作用時釋放分配給對象的內存空間,並作一些善後工作,析構函數的名字必須與類名相同,但在名字的前面要加波浪號(“~”),析構函數沒有參數,沒 有返回值,不能重載,在一個類中只能有一個析構函數。當被撤銷對象時,系統會自動調用析構函數完成空間的釋放和善後工作。
在使用析構函數中,需要註意一下幾個問題:
每個類必須有一個析構函數,若沒有顯式地定義,則系統會自動生成一個默認的析構函數,它是一個空函數。
對於大多數類而言,默認的析構函數就能滿足要求,但如果對象在完成操作前需要做內部處理,則應顯式地定義析構函數。
構造函數和析構函數的常見的用法是,在構造函數中用new運算對為對象分配空間,在析構函數中用delete運算符釋放空間。
C++中為了使得類的私有成員和保護成員能夠被其他類或其他成員函數訪問,引入了友元的概念。友元提供了不同類或對象的成員函數之間、類的成員函數與一般 函數之間進行數據共享的機制。如果友元是一般成員函數或類的成員函數,則稱為友元函數,如果友元是一個類,則稱為友元類,友元類的所有成員函數都稱為友元 函數。
友元函數與普通成員函數不同,它不是當前類的成員函數,而是獨立於當前類的外部函數;它可以是普通函數或其他類的成員函數。友元函數定義後可以訪問該類的所有對象的成員,包括私有成員、保護成員和公有成員。
友元函數使用前必須要在類定義時聲明,聲明時在其函數名前加上關鍵字friend。該聲明可以放在公有成員中,也可以放在私有成員中。
友元函數不是類的成員函數,因此,在類外定義友元函數時,不必像成員函數那樣,在函數名前加“類名::”
友元函數不是類的成員,因而不能直接引用對象成員的名字,也不能通過this指針引用對象的成員,必須通過作為入口參數傳遞進來的對象名或對象指針來引用 該對象的成員。為此,友元函數一般都有一個該類的入口參數。如distance(point &p1,point &p2)
當一個函數需要訪問多個類時,應該把這個函數同時定義為這些類的友元函數,這樣,這個函數才能訪問這些類的數據。
如果一個類的成員函數是另一個類的友元函數,則稱這個成員函數為友元函數。
通過友元成員函數,不僅可以訪問自己所在類對象中的私有和公有成員,還可以訪問有關鍵字friend聲明語句所在類對象中的私有和公有成員,從而可使兩個類可以互相訪問,從而共同完成某個任務。
當一個類的成員函數作為另一個類的友元函數時,必須先定義成員函數所在的類
如果在類定義前要使用到該類的成員,需要事先在使用前對該類進行聲明。
當一個類做為另一個類的友元時,稱這個類為友元類。當一個類成為另一個類的友元類時,這個類的所有成員函數都成為另一個類的友元函數,因此,友元類中的所 有成員函數都可以通過對象名直接訪問另一個類中的私有成員,從而實現不同類之間的數據共享。friend class <友元類名>;或friend <友元類名>
友元關系是不能傳遞的。類B是類A的友元,類C是類B的友元,類C與類A之間,除非特別說明,沒有任何關系,不能進行數據的共享。友元關系是單向的。類B 是類A的友元,類B的成員函數可以訪問類A的私有成員和保護成員,反之,類A的成員函數卻不可以訪問類B的私有成員和保護成員
繼承是指一個類除了得到另外一個類的所有性質,還具有自身獨特的性質,則該類稱為派生類,而另一個類稱為基類,這種行為稱為繼承
class <派生類名>:<派生方式><基類名>
{
派生類聲明;
}
繼承方式關鍵字為:private、public和protected,分別表示私有繼承、公有繼承和保護繼承。默認的繼承方式是私有繼承,。繼承方式規定了派生類成員和類外對象訪問基類成員的權限。
派生類成員是指除了從基類繼承來的成員外,新增的數據成員和成員函數。
派生類訪問基類的能力:
基類中的私有成員在派生類中是隱藏的,只能在基類內部訪問
派生類中的成員不能訪問基類中的私有成員,但可以訪問基類中的共有成員和保護成員
派生類從基類公有繼承時,基類的共有成員和保護成員在派生類中仍為公有成員和保護成員。
派生類從基類私有繼承時,基類的公有成員和保護成員在派生類中都改變為私有成員
派生類從基類保護繼承時,基類的公有成員在派生類中該變為保護成員,基類的而保護成員在派生類中 則認為保護成員
如果要訪問類中的私有成員和保護成員,必須通過公有成員函數來訪問
派生類不能直接訪問基類的私有成員,若要訪問,必須使用基類的接口,即通過其成員函數。實現方法如下:
在基類的聲明中增加保護成員,將基類中提供給派生類訪問的私有成員定義為保護成員
將需要訪問基類私有成員的派生類成員函數聲明為友元
C++中規定,基類成員的初始化工作由基類的構造函數完成,而派生類的初始化工作由派生類的構造函數完成。
在構建派生類的構造函數和析構函數時,需要註意一下原則:
基類的構造函數和析構函數不能被派生類繼承。
如果基類沒有定義構造函數,派生類也可以不定義構造函數,全都采用默認的構造函數,此時,派生類新增成員的初始化工作可用其他公有函數來完成。
如果基類定義了帶有行參表的構造函數,派生類就必須定義一個新的構造函數,提供一個將參數傳遞給基類構造函數的途徑,以便保證在基類進行初始化時能獲得必需的數據。
如果派生類的基類也是派生類,則每個派生類只需負責其直接基類的構造,不負責自己的簡介基類的構造。
派生類是否要定義析構函數與所屬的基類無關,如果派生類對象在撤銷時需要做清理善後工作,就需要定義新的析構函數。
派生類的數據成員由所有基類的數據成員和派生類新增的數據成員共同組成,如果派生類新增成員中還有對象成員,那派生類的數據成員中間接含有這些對象的數據 成員,應此要對派生類對象初始化,就要對基類數據成員、新增數據成員和對象成員的數據進行初始化,這樣,派生類的構造函數需要以合適的初值作為參數,隱含 調用基類的構造函數和新增對象成員的構造函數來初始化各自的數據成員,再用新加的語句新增數據成員進行初始化。
派生類構造函數聲明的形式:
<派生類名>::<派生類名>(參數總表):基類名(參數表),對象成員名1(參數表1),................,對象成員名n(參數表n){
//派生類新增成員的初始化語句
}
使用作用域運算符來指明調用的某個成員屬於哪個基類是很直觀的方法,因此在多重繼承中使用非常廣泛。
引入虛基類的主要原因是為了解決基類中由於同名成員的問題而產生的二義性問題。
一般來說,在多重繼承中,當派生類有多條路徑去訪問基類,即非虛基類時,編譯系統會給出錯誤問題。
虛基類的初始化與一般的多繼承的初始化在語法上是一樣的,但構造函數的執行順序不同:
虛基類的構造函數的執行在非虛基類的構造函數之前。
若同一層次中包含多個虛基類,這些虛基類的構造函數按對他們說明的先後順序執行
若虛基類由非虛基類派生而來,則仍然先執行基類的構造函數,再執行派生類的構造函數
只有能夠解決多態問題的語言,才是真正支持面向對象的開發語言。
在面向對象的過程中,多態性是指不同對象接受到相同消息時,根據對象的不同而產生不同的動作。多態性提供了同一個接口可以用多種方法進行調用的機制,從而可以通過相同的接口訪問不同的函數,具體說,就是同一個函數名稱,作用在不同的對象將產生不同的操作。
簡單的說,多態就是“一個接口,多種實現”
面向對象的多態性可以嚴格的分為四類:重載多態、強制多態、包含多態和參數多態。前兩種稱為專用多態,後面兩種稱為通用多態
包含多態是指:定義於不同類中的同名成員函數的多態行為,主要通過虛函數來實現。
參數多態於類模板相關聯,其是一個可以參數化的模板,其中包含的操作所涉及的類型必須用類型參數進行實例化。這樣,由類模板實例化的各類都具有相同的操作,而操作對象的類型卻各不相同。
多態從實現的角度來講可以劃分為兩類,編譯時的多態和運行時的多態。前者是在編譯的過程中確定了同名操作的具體操作對象,而後者則是在程序運行過程中才動態地確定操作所針對的具體對象,這種確定操作的具體對象的過程就是聯編。
聯編是指計算機程序自身彼此關聯的過程,即把一個標識符名和一個存儲地址聯系在一起的過程,用面向對象的術語講,就是把一條消息和一個對象的方法相結合的過程。按照聯編進行階段的不同,可以分為兩種不同的聯編方法,靜態聯編和動態聯編。
多態性提供了把接口與實現分開的另一種方法,提供了代碼的組織性和可讀性,更重要的是提高了軟件的可擴充性。
//***********************************************************************************
#include
using namespace std;
class animal
{
public:
void sleep()
{
cout<<"animal sleep"< }
void breathe()
{
cout<<"animal breathe"< }
};
class fish:public animal
{
public:
void breathe()
{
cout<<"fish bubble"< }
};
int main()
{
fish fh;
fh.breathe();
animal *pAn=&fh;
pAn->breathe();
fish *pBn=&fh;
pBn->breathe();//you should know why the result is .
return 0;
}
//*******************************************************************************************
由靜態聯編支持的多態性稱為編譯時的多態性或靜態多態性,也就是說,確定同名操作的具體操作對象的過程是在編譯過程中完成的。在C++中,可以用函數重載和運算符重載來實現編譯時的多態性。
由動態聯編支持的多態性稱為運行時的多態性或動態多態性,也就是說,確定同名操作的具體操作對象的過程是在運行過程中完成的。在C++中,可以用繼承和虛函數來實現運行時的多態性。
函數的重載也稱為多態函數,是實現編譯時的多態性的形式之一,它使程序能用同一個名字來訪問一組相關的函數,提高了程序的靈活性。函數重載時,函數名相同,但函數所帶的參數個數或數據類型不同,編譯系統會根據參數來決定調用哪個同名函數。
面向對象程序設計中,函數的重載表現為兩種情況:
1、第一種是參數個數或類型有所差別的重載。
2、第二種是函數的參數完全相同但屬於不同的類。
當函數的參數完全相同但屬於不同的類時,為了讓編譯能正確區分調用哪個類的同名函數,采用以下兩種方法:
對象名區別:在函數名前加上對象名來限制。如對象名.函數名
用類名和作用域運算符::加以區別;在函數名前加“類名::”來限制,如類名::函數名
//***************************************************************************************
#include
using namespace std;
class point
{
int x,y;
public:
point(int xx,int yy)
{
x=xx;
y=yy;
}
double area()
{
return 0.0;
}
};
class circle:public point
{
int r;
public:
circle(int xx,int yy,int rr):point(xx,yy)
{
r=rr;
}
double area()
{
return 3.1415*r*r;
}
};
int main()
{
point pob(15,15);
circle cob(20,20,10);
cout<<"pob.area()= "< cout<<"cob.area()= "< cout<<"cob.point::area()= "< return 0;
}
//*********************************************************************************
虛函數是重載的另一種形式,實現的是動態地重載,即函數調用與函數體之間的聯系是在運行時才建立,也就是動態聯編。
一般對象的指針之間沒有聯系,彼此獨立,不能混用。但派生類是由基類派生出來的,它們之間有繼承關系,因此,指向基類和派生類的指針之間的指針之間也有一定的聯系。如果使用不當,將會出現一些問題。
//**********************************************************
#include
using namespace std;
class base
{
private:
int x,y;
public:
base(int xx=0,int yy=0)
{
x=xx;
y=yy;
}
void disp()
{
cout<<"base: "< }
};
class base1:public base
{
private:
int z;
public:
base1(int xx,int yy,int zz):base(xx,yy)
{
z=zz;
}
void disp()
{
cout<<"base1:"< }
};
int main()
{
base obj(3,4),*objp;
base1 obj1(1,2,3);
objp=&obj;
objp->disp();
objp=&obj1;
objp->disp();
return 0;
}
//****************************************************************************************
聲明為指向基類對象的指針可以指向它的共有派生類的對象,但不允許指向它的私有派生類的對象。
允許聲明為指向基類對象的指針指向它的公有派生類的對象,但不允許將一個聲明為指向派生類對象的指針指向基類的對象。
聲明為指向基類對象的指針,當其指向它的派生類的對象時,只能直接訪問派生類中從基類繼承下來的成員,不能直接訪問公有派生類中定義的成員,要訪問其公有派生類中的成員,可將基類指針用顯式類型轉換為派生類指針。
在C++中引入虛函數的概念主要是為了解決在調用既有繼承關系的類的成員函數時能夠正確地被執行。
虛函數的定義時在基類中進行的,即把基類中需要定義為虛函數的成員函數聲明為virtual。當基類中的某個成員被聲明為虛函數後,其就可以在派生類中被 重新定義。在派生類中重新定義時,其函數原型,包括返回類型、函數名、參數個數和類型、參數的順序都必須與基類中的原型完全一致。
//******************************************************************************************
#include
using namespace std;
class animal
{
public:
void sleep()
{
cout<<"animal sleep"< }
virtual void breathe()
{
cout<<"animal breathe"< }
};
class fish:public animal
{
public:
void breathe()
{
cout<<"fish bubble"< }
};
int main()
{
fish fh;
fh.breathe();
animal *pAn=&fh;
animal fs;
fs.breathe();
pAn->breathe();
fish *pBn=&fh;
pBn->breathe();
return 0;
}
//************************************************************************************
虛函數的定義必須在基類中進行,即只有基類中的函數才能被定義為虛函數,在派生類中不能定義某個函數為虛函數。
在派生類中被重新定義的基類中的虛函數,是函數重載的另一種形式,但它與函數重載又有如下的區別:一般函數的重載,要求其函數的參數或參數類型必須有所不 同,函數的返回類型也可以不同,但重載一個虛函數時,要求函數名、返回類型、參數個數、參數的類型和參數的順序必須與基類中的虛函數的原型完全相同。
如果僅返回類型不同,其余相同,則系統會給出錯誤信息。
如果函數名相同,而參數個數、參數的類型或參數的順序不同,系統認為是普通的函數重載,虛函數的特性將丟失。
//**********************************************************************************
#include
using namespace std;
class base
{
private:
int x,y;
public:
base(int xx=0,int yy=0)
{
x=xx;
y=yy;
}
virtual void disp()
{
cout<<"base:"< }
};
class base1:public base
{
private:
int z;
public:
base1(int xx,int yy,int zz):base(xx,yy)
{
z=zz;
}
void disp()
{
cout<<"base1:"< }
};
int main()
{
base obj(3,4),*objp;
base1 obj1(1,2,5);
objp=&obj;
objp->disp();
objp=&obj1;
objp->disp();
return 0;
}
//*******************************************************************************************
對於虛函數調用來說,每一個對象內部都有一個虛表指針,該虛表指針被初始化為本類的虛表。所以在程序中,不管你的對象類型如何轉換,但該對象內部的虛表指針式固定的,所以才能實現動態地對象函數調用,這就是C++多態性實現的原理
一個虛函數無論被繼承多少次,仍保持其虛函數的特性,與繼承的次數無關。
//******************************************************************************************
#include
using namespace std;
class base
{
public:
virtual ~base(){};
virtual void func() const
{
cout<<"base output!"< }
};
class derive1:public base
{
public:
void func() const
{
cout<<"derive1 output!"< }
};
class derive2:public derive1
{
public:
void func() const
{
cout<<"derive2 output!"< }
};
void test(const base & rBase)
{
rBase.func();
};
int main()
{
base bObj;
derive1 d1Obj;
derive2 d2Obj;
test(bObj);
test(d1Obj);
test(d2Obj);
return 0;
}
//******************************************************************************************
在使用虛析構函數時,要註意一下幾點:
只要基類的析構函數被聲明為虛函數,則派生類的析構函數,無論是否使用virtual關鍵字進行聲明,都自動成為虛函數。
如果基類的析構函數為虛函數,則當派生類未定義析構函數時,編譯器所生成的析構函數也為虛函數。
使用虛函數時應註意如下問題:
虛函數的聲明只能出現在類函數原型的聲明中,不能出現在函數體實現的時候,而且,基類中只要保護成員或公有成員才能被聲明為虛函數。
在派生類中重新定義虛函數時,關鍵字virtual可以寫也可以不寫,但在容易引起混亂時,應寫上該關鍵字。
動態聯編只能通過成員函數調用或通過指針、引用來訪問虛函數,如果以對象名的形式來訪問虛函數,將采用靜態聯編。
抽象類是一種特殊的類,其為類提供統一的操作界面。建立抽閑類的原因就是為了通過抽象類多態使用其中的成員函數,抽象類是帶有純虛函數的類
當在基類中不能為虛函數給出一個有意義的實現時,可以將其聲明為純虛函數,純虛函數的實現可以留給派生類完成,純虛函數的作用是為派生類提供一個一致的接 口,一般來說,一個抽象類帶有至少一個純虛函數,純虛函數是愛一個基類中說明的虛函數,它在該基類 中沒有具體的操作內容,要求各派生類在重新定義時根據自己的需要定義實際的操作內容。
Virtual<虛函數><函數名>(參數表)=0
//************************************************************************
#include
using namespace std;
class point
{
public:
point(int i=0,int j=0)
{
x0=i;
y0=j;
}
virtual void set()=0;
virtual void draw()=0;
protected:
int x0,y0;
};
class line:public point
{
public:
line(int i=0,int j=0,int m=0,int n=0):point(i,j)
{
x1=m;
y1=n;
}
void set()
{
cout<<"line::set() called. ";
}
void draw()
{
cout<<"line::draw() called. ";
}
protected:
int x1,y1;
};
class ellipse:public point
{
public:
ellipse(int i=0,int j=0,int p=0,int q=0):point(i,j)
{
x2=p;
y2=q;
}
void set()
{
cout<<"ellipse::set() called. ";
}
void draw()
{
cout<<"ellipse::draw() called. ";
}
protected:
int x2,y2;
};
void drawobj(point *p)
{
p->draw();
}
void setobj(point *p)
{
p->set();
}
int main()
{
line *lineobj=new line;
ellipse *elliobj=new ellipse;
drawobj(lineobj);
drawobj(elliobj);
cout< setobj(lineobj);
setobj(elliobj);
cout<<" Redraw the object... ";
drawobj(lineobj);
drawobj(elliobj);
return 0;
}
你可能會有興趣的文章:
限會員,要發表迴響,請先登入


