一、程序設(shè)計(jì)概念等
結(jié)構(gòu)化程序設(shè)計(jì)特點(diǎn):
程序設(shè)計(jì)=數(shù)據(jù)結(jié)構(gòu)+算法
程序內(nèi)容=過程+過程調(diào)用
面向?qū)ο蟮某绦蛟O(shè)計(jì)方法:
程序=對象+消息
面向?qū)ο?對象+類+繼承+消息
任何對象都具有屬性(數(shù)據(jù))和操作(方法)
具有強(qiáng)大的繼承性
關(guān)系:
- 聚集;2)繼承;3)實(shí)例化
類與抽象數(shù)據(jù)類型:對象是類的實(shí)例,對象可以使用類中的函數(shù)- 對類的定義:
(最低限度)類名(class name)、外部接口(external interface)、內(nèi)部實(shí)現(xiàn)(implementation)
- 對類的定義:
-
外部接口:
公共成員變量
公共成員函數(shù) -
內(nèi)部實(shí)現(xiàn):
- 類的內(nèi)部實(shí)現(xiàn)對用戶是隱藏的
- 每一個操作符 對應(yīng)內(nèi)部實(shí)現(xiàn)的具體操作
- 內(nèi)部實(shí)現(xiàn)是私有的
屬性:數(shù)據(jù)成員
方法:成員函數(shù)
- public實(shí)現(xiàn)類的接口,private隱藏類的實(shí)現(xiàn)
抽象
數(shù)據(jù)抽象:某類對象共有的屬性和狀態(tài)
行為抽象: 共有的行為或功能特征
繼承
使用多繼承機(jī)制,將增加命名沖突出現(xiàn)的可能性,表現(xiàn)形式:
- 派生類與某個基類之間發(fā)生命名沖突
- 基類和基類之間發(fā)生命名沖突
解決方法: 使用域解析符
接口與組件
- 接口的作用: 為不相關(guān)的類提供通用的處理服務(wù)
二、從 C 到 C++
區(qū)別
<iostream> 和 <iostream.h>不一樣,是兩文件,里面的代碼不一樣。當(dāng)使用<iostream>時(shí),相當(dāng)于在c中調(diào)用庫函數(shù),使用的是全局命名空間,也就是早期的c++實(shí)現(xiàn);當(dāng)使用<iostream.h> 時(shí),該頭文件沒有定義全局命名空間,必須使用namespace std;這樣才能正確使用cout。
const 定義時(shí),定義了常量的類型,define只是簡單的文本替換;使用const定義常量是一個說明語句,以分號結(jié)束,define是一個預(yù)處理命令,缺乏類型檢測機(jī)制。const是左結(jié)合的類型修飾符。
強(qiáng)制類型轉(zhuǎn)換:
static_cast: 強(qiáng)制類型轉(zhuǎn)換
const_cast: 去掉常數(shù)性
dynamic_cast: 用于繼承層次中的類型轉(zhuǎn)換
reinterpret_cast: 改變指針類型或?qū)⒅羔樑c整型轉(zhuǎn)換string 類型的索引都是從0開始的
-
內(nèi)聯(lián)函數(shù)
- 從源代碼層看,有函數(shù)的結(jié)構(gòu),在編譯后,卻不具備函數(shù)的性質(zhì)
- 避免函數(shù)調(diào)用的開銷,避免調(diào)用函數(shù)對棧內(nèi)存開辟所帶來的消耗
int add(int x, int y=10) //right
int add(int x=10,int y) //error
-
常見編程錯誤:
- 操縱符作用于數(shù)據(jù)流中,除了控制域?qū)挼牟倏v符(控制域?qū)挼牟倏v符在輸出了一個字符串和數(shù)字后自動清0)之外,其他所有的操縱器所造成的影響具有持久性。不要誤以為在語句結(jié)束后所有的輸入輸出設(shè)置都恢復(fù)為默認(rèn)值。
- 混用C、C++的輸出輸入功能,可能會導(dǎo)致不可預(yù)料的錯誤,使用函數(shù) ios::sync_with_stdio()可消除這種隱患。
- 關(guān)鍵字inline用于函數(shù)聲明而不是函數(shù)定義。
三、類
基本規(guī)則
- 使用class關(guān)鍵字,類成員在默認(rèn)狀態(tài)下是私有的;
使用struct關(guān)鍵字,類成員在默認(rèn)狀態(tài)下則是公有的。 - 類成員函數(shù)的定義方法:
- 在類聲明之中聲明,在類聲明之外定義;
- 在類聲明之中聲明及定義(inline),即為內(nèi)聯(lián)方式。
- 在進(jìn)行成員函數(shù)聲明的時(shí)候使用inline關(guān)鍵字,可將原本定義在類聲明之外的成員函數(shù)強(qiáng)制變?yōu)閮?nèi)聯(lián)函數(shù)。
- 在程序中使用類:
- 通常將類聲明放到.h中,這樣在使用時(shí)通過#include將類聲明包含進(jìn)來;
- 通常將成員函數(shù)的定義放到cpp中;
- 不要將類外定義的成員函數(shù)放在.h中某一位頭文件通過#include被多個不同的文件所包含的話可能出現(xiàn)函數(shù)重復(fù)定義錯誤。
- 對象可以采用傳值方式傳遞給函數(shù),也可以采用引用的方式,一般來說應(yīng)該采用引用方式進(jìn)行對象的傳遞和返回,而不是采用傳值的方式來進(jìn)行(*因?yàn)閭髦捣绞絹韨鬟f和返回對象時(shí)會降低效率并將面臨對象間的拷貝操作,從而使數(shù)據(jù)增大,浪費(fèi)內(nèi)存。
//引用方式比傳值方式效率高:
person a;
void f(person b) {......};
f(a); //該調(diào)用要將對象a復(fù)制到對象b中
void g(person& c) //引用方式
{......};
g(a); //該引用調(diào)用,不復(fù)制對象
將成員函數(shù)標(biāo)記為const可以預(yù)防對該函數(shù)所屬對象的數(shù)據(jù)成員的誤寫,同時(shí)有些編譯器還可對這種情況進(jìn)行一些優(yōu)化;一個const成員函數(shù)僅能調(diào)用其它c(diǎn)onst成員函數(shù)。
某函數(shù)如果采用const返回,則其返回值只能賦給一個const類型的局部變量;
如果該const返回值是一個類的指針或者引用的話,則不能用該指針或引用調(diào)用該類的non-const成員函數(shù),因?yàn)檫@些函數(shù)可能會改變該類的數(shù)據(jù)成員的值。
-
構(gòu)造函數(shù)與析構(gòu)函數(shù)
- 有些函數(shù)在調(diào)用時(shí)不需要顯式地提供函數(shù)名,編譯器會自動調(diào)用。而類構(gòu)造函數(shù)可有多個,類析構(gòu)函數(shù)最多一個。
- 構(gòu)造函數(shù)不能有返回類型,固:
void Person(); //Error
- 一個類可以有多個構(gòu)造函數(shù),也就是說可以對構(gòu)造函數(shù)進(jìn)行重載,但每個構(gòu)造函數(shù)必須擁有不用的函數(shù)簽名。
Person(){name="Unknown";}//無參數(shù)
Person(const string& n);//函數(shù)類型為const string引用
Person(const char* n);//為C風(fēng)格字符串
4.如果類的設(shè)計(jì)者不提供拷貝構(gòu)造函數(shù),編譯器會自動生成一個:將源對象所有數(shù)據(jù)成員的值逐一賦值給目標(biāo)對象相應(yīng)的數(shù)據(jù)成員。
5.拷貝構(gòu)造函數(shù):創(chuàng)建一個新的對象,此對象是另外一個對象的拷貝
6.轉(zhuǎn)型構(gòu)造函數(shù):用于類型間的轉(zhuǎn)換,只有一個參數(shù)
7.拷貝構(gòu)造函數(shù)的原型:必須是引用
Person(Person&)
Person(const Person&)
Person(Person); //Error
拷貝構(gòu)造函數(shù)可以有多于一個的參數(shù),但是第一個以后的所有參數(shù)都必須有默認(rèn)值:
Person(const Person& p,bool married=false);
8.什么時(shí)候應(yīng)該為一個類設(shè)計(jì)一個拷貝構(gòu)造函數(shù)?
答:如果一個類包含指向動態(tài)村存儲空間指針類型的數(shù)據(jù)成員,則就應(yīng)為這個類設(shè)計(jì)拷貝構(gòu)造函數(shù)。
9.轉(zhuǎn)型構(gòu)造函數(shù)是一個單參數(shù)的構(gòu)造函數(shù),它可以將一個對象從一種數(shù)據(jù)類型轉(zhuǎn)換為另一種數(shù)據(jù)類型。
10.轉(zhuǎn)型構(gòu)造函數(shù)可替代函數(shù)重載機(jī)制。
11.對const類型的數(shù)據(jù)成員進(jìn)行初始化時(shí)不能直接賦值。
對const類型的數(shù)據(jù)成員進(jìn)行初始化時(shí)必須為構(gòu)造函數(shù)添加一個 初始化列表。
>class C{
public:
C(){x=0;//OK
c=0;//****ERROR:c is const
C():c(0) {x=-1;} //right(添加初始化列表)}
private:
int x; const int c;}
12.當(dāng)使用動態(tài)方式為一個對象分配存儲空間時(shí),C++操作符new和new[]比C函數(shù)malloc和calloc做得更好。因?yàn)椴僮鞣鹡ew在分配存儲空間的同時(shí),還會調(diào)用相應(yīng)的構(gòu)造函數(shù),而malloc和calloc無法完成這個任務(wù)。
13.使用關(guān)鍵字static修飾的類的成員,稱之為類成員(靜態(tài)成員),包括:類數(shù)據(jù)成員和類成員函數(shù)。這種成員屬于類本身,而不屬于類的對象。
類數(shù)據(jù)成員(靜態(tài)數(shù)據(jù)成員)特點(diǎn):1)為同一個類的所有對象共享/2)類內(nèi)聲明,類外定義(不是必須的)。
14.類數(shù)據(jù)成員(靜態(tài)數(shù)據(jù)成員)使用方式:對象明.靜態(tài)數(shù)據(jù)成員名;對象指針名->靜態(tài)數(shù)據(jù)成員名;類名::靜態(tài)數(shù)據(jù)成員名
15.錯誤的調(diào)用一個對象成員函數(shù),把它作為類的類成員函數(shù)來使用:
class C{
public:
void m(){/*...*/} //nonstatic: object method
static void s() {/*...*/} //static: class method
};
int main()
{
C c1; c1.m(); // OK
c1.s(); //OK
C::s(); //OK ,s is static
C::m(); //***ERROR: m:m is not static}
- 一個static數(shù)據(jù)成員在類的聲明內(nèi)被聲明,錯誤地將static數(shù)據(jù)成員定義在程序塊內(nèi):
class C{static int c;//declared};
int main(){ int C::x; //****ERROE:defined inside a block!}
//right way
int C::x; //define static data member
int main(){} //即使x是私有地,也要按同樣的方式來定義它。
17.錯誤地針對一個指向?qū)ο蟮闹羔榿硎褂贸蓡T選擇操作符
class C{public: void m(){}};
int main(){
C c1;//define a C object
C* p;// define a pointer to a C object
p = &c1; // p points to c1
p.m(); //*** ERROR: member operator illegal!
c1.m(); //OK,c1 is an object}
void f(C& r)
{ r->m(); //***ERROR: r is reference ,not a pointer
r.m();//OK :r is reference can use 成員選擇操作符. }
即:對象和對象引用不能使用指針操作符->!!
成員選擇操作符僅能由對象活對象引用所使用,指向?qū)ο蟮闹羔樋梢允褂弥羔槻僮鞣?>來訪問他們的成員;即上改為:
p->m(); //OK,p a pointer to an object
17.在static成員函數(shù)中使用this時(shí)錯誤的。
四、繼承
1.使用using聲明可以改變成員在派生類中的訪問限制,如:
//基類中的共有成員一般情況下被繼承為共有成員,但使用using聲明可將其改為私有成員(或保護(hù)成員)
class BC {
public:
void set_x(float a) {x=a;}
private:
float x;
};
class DC : public BC {
public:
void set_y ( float b ) { y = b;}
private:
float y;
using BC::set_x;
};
//這樣的話,無法直接通過DC類的任何對象調(diào)用set_x
2.定義簡單派生類構(gòu)造函數(shù)的一般形式:
<派生類構(gòu)造函數(shù)名>(<總參數(shù)列表>):<基類構(gòu)造函數(shù)名>(<參數(shù)表>)
{ <派生類新增數(shù)據(jù)成員初始化> };
在建立一個對象時(shí),執(zhí)行構(gòu)造函數(shù)的順序是:
1)最先調(diào)用基類的構(gòu)造函數(shù),對基類數(shù)據(jù)成員初始化;對基類的構(gòu)造函數(shù)的調(diào)用順序取決于這些基類在被繼承時(shí)的說明順序,于他么的初始化列表給出的順序無關(guān);
2)再調(diào)用數(shù)據(jù)成員是類對象的構(gòu)造函數(shù),其調(diào)用次序按在類中定義的先后次序;
3)最后執(zhí)行派生類構(gòu)造函數(shù)的函數(shù)體,對派生類新增數(shù)據(jù)成員初始化。 ————先父母、后客人、最后自己——————
3.繼承下的析構(gòu)函數(shù)。
- 和構(gòu)造函數(shù)一樣,基類的析構(gòu)函數(shù)派生類也不能繼承;
- 在聲明派生類時(shí),可以根據(jù)需要定義自己的析構(gòu)函數(shù),用來對派生類中新增加的成員進(jìn)行清理工作;
- 在執(zhí)行派生類的析構(gòu)函數(shù)時(shí),系統(tǒng)會自動調(diào)用基類的析構(gòu)函數(shù),對基類進(jìn)行清理;
- 派生類析構(gòu)函數(shù)的執(zhí)行順序于構(gòu)造函數(shù)正好相反(原因:析構(gòu)函數(shù)時(shí)用來釋放由構(gòu)造函數(shù)分配的內(nèi)存資源,這種次序,可以確保最近分配的額內(nèi)存資源可以最先被釋放)
4.多繼承
- 單繼承基類和派生類組成樹結(jié)構(gòu),而多繼承基類和派生類組成有向圖結(jié)構(gòu);多繼承的派生類可以同時(shí)具有多個基類,它同時(shí)繼承了這些基類的所有成員;
- 派生類構(gòu)造函數(shù)執(zhí)行順序是先執(zhí)行所有基類的構(gòu)造函數(shù),再執(zhí)行派生類本身構(gòu)造函數(shù),在多繼承情況下,基類構(gòu)造函數(shù)的執(zhí)行順序按它們在被繼承時(shí)所聲明的順序(從左到右)一次調(diào)用,與它們在初始化列表中的順序無關(guān)。
- 基類的構(gòu)造函數(shù)被先調(diào)用(按聲明時(shí)的順序),數(shù)據(jù)成員所在類的構(gòu)造函數(shù)次之,最后執(zhí)行派生類的構(gòu)造函數(shù)。
- 多繼承機(jī)制下的命名沖突。
(轉(zhuǎn))
虛函數(shù)
定義一個函數(shù)為虛函數(shù),不代表函數(shù)為不被實(shí)現(xiàn)的函數(shù)。
定義他為虛函數(shù)是為了允許用基類的指針來調(diào)用子類的這個函數(shù)。
定義一個函數(shù)為純虛函數(shù),才代表函數(shù)沒有被實(shí)現(xiàn)。
定義純虛函數(shù)是為了實(shí)現(xiàn)一個接口,起到一個規(guī)范的作用,規(guī)范繼承這個類的程序員必須實(shí)現(xiàn)這個函數(shù)。
1、簡介
假設(shè)我們有下面的類層次:
class A
{
public:
virtual void foo()
{
cout<<"A::foo() is called"<<endl;
}
};
class B:public A
{
public:
void foo()
{
cout<<"B::foo() is called"<<endl;
}
};
int main(void)
{
A *a = new B();
a->foo(); // 在這里,a雖然是指向A的指針,但是被調(diào)用的函數(shù)(foo)卻是B的!
return 0;
}
這個例子是虛函數(shù)的一個典型應(yīng)用,通過這個例子,也許你就對虛函數(shù)有了一些概念。它虛就虛在所謂“推遲聯(lián)編”或者“動態(tài)聯(lián)編”上,一個類函數(shù)的調(diào)用并不是在編譯時(shí)刻被確定的,而是在運(yùn)行時(shí)刻被確定的。由于編寫代碼的時(shí)候并不能確定被調(diào)用的是基類的函數(shù)還是哪個派生類的函數(shù),所以被成為“虛”函數(shù)。
虛函數(shù)只能借助于指針或者引用來達(dá)到多態(tài)的效果。
C++純虛函數(shù)
一、定義
純虛函數(shù)是在基類中聲明的虛函數(shù),它在基類中沒有定義,但要求任何派生類都要定義自己的實(shí)現(xiàn)方法。在基類中實(shí)現(xiàn)純虛函數(shù)的方法是在函數(shù)原型后加“=0”
virtual void funtion1()=0
二、引入原因
1、為了方便使用多態(tài)特性,我們常常需要在基類中定義虛擬函數(shù)。
2、在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。
為了解決上述問題,引入了純虛函數(shù)的概念,將函數(shù)定義為純虛函數(shù)(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重寫以實(shí)現(xiàn)多態(tài)性。同時(shí)含有純虛擬函數(shù)的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。
聲明了純虛函數(shù)的類是一個抽象類。所以,用戶不能創(chuàng)建類的實(shí)例,只能創(chuàng)建它的派生類的實(shí)例。
純虛函數(shù)最顯著的特征是:它們必須在繼承類中重新聲明函數(shù)(不要后面的=0,否則該派生類也不能實(shí)例化),而且它們在抽象類中往往沒有定義。
定義純虛函數(shù)的目的在于,使派生類僅僅只是繼承函數(shù)的接口。
純虛函數(shù)的意義,讓所有的類對象(主要是派生類對象)都可以執(zhí)行純虛函數(shù)的動作,但類無法為純虛函數(shù)提供一個合理的缺省實(shí)現(xiàn)。所以類純虛函數(shù)的聲明就是在告訴子類的設(shè)計(jì)者,“你必須提供一個純虛函數(shù)的實(shí)現(xiàn),但我不知道你會怎樣實(shí)現(xiàn)它”。
抽象類的介紹
抽象類是一種特殊的類,它是為了抽象和設(shè)計(jì)的目的為建立的,它處于繼承層次結(jié)構(gòu)的較上層。
(1)抽象類的定義: 稱帶有純虛函數(shù)的類為抽象類。
(2)抽象類的作用:
抽象類的主要作用是將有關(guān)的操作作為結(jié)果接口組織在一個繼承層次結(jié)構(gòu)中,由它來為派生類提供一個公共的根,派生類將具體實(shí)現(xiàn)在其基類中作為接口的操作。所以派生類實(shí)際上刻畫了一組子類的操作接口的通用語義,這些語義也傳給子類,子類可以具體實(shí)現(xiàn)這些語義,也可以再將這些語義傳給自己的子類。
(3)使用抽象類時(shí)注意:
? 抽象類只能作為基類來使用,其純虛函數(shù)的實(shí)現(xiàn)由派生類給出。如果派生類中沒有重新定義純虛函數(shù),而只是繼承基類的純虛函數(shù),則這個派生類仍然還是一個抽象類。如果派生類中給出了基類純虛函數(shù)的實(shí)現(xiàn),則該派生類就不再是抽象類了,它是一個可以建立對象的具體的類。
? 抽象類是不能定義對象的。
總結(jié):
1、純虛函數(shù)聲明如下: virtual void funtion1()=0; 純虛函數(shù)一定沒有定義,純虛函數(shù)用來規(guī)范派生類的行為,即接口。包含純虛函數(shù)的類是抽象類,抽象類不能定義實(shí)例,但可以聲明指向?qū)崿F(xiàn)該抽象類的具體類的指針或引用。
2、虛函數(shù)聲明如下:virtual ReturnType FunctionName(Parameter);虛函數(shù)必須實(shí)現(xiàn),如果不實(shí)現(xiàn),編譯器將報(bào)錯,錯誤提示為:
error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"
3、對于虛函數(shù)來說,父類和子類都有各自的版本。由多態(tài)方式調(diào)用的時(shí)候動態(tài)綁定。
4、實(shí)現(xiàn)了純虛函數(shù)的子類,該純虛函數(shù)在子類中就編程了虛函數(shù),子類的子類即孫子類可以覆蓋該虛函數(shù),由多態(tài)方式調(diào)用的時(shí)候動態(tài)綁定。
5、虛函數(shù)是C++中用于實(shí)現(xiàn)多態(tài)(polymorphism)的機(jī)制。核心理念就是通過基類訪問派生類定義的函數(shù)。
6、在有動態(tài)分配堆上內(nèi)存的時(shí)候,析構(gòu)函數(shù)必須是虛函數(shù),但沒有必要是純虛的。
7、友元不是成員函數(shù),只有成員函數(shù)才可以是虛擬的,因此友元不能是虛擬函數(shù)。但可以通過讓友元函數(shù)調(diào)用虛擬成員函數(shù)來解決友元的虛擬問題。
8、析構(gòu)函數(shù)應(yīng)當(dāng)是虛函數(shù),將調(diào)用相應(yīng)對象類型的析構(gòu)函數(shù),因此,如果指針指向的是子類對象,將調(diào)用子類的析構(gòu)函數(shù),然后自動調(diào)用基類的析構(gòu)函數(shù)。
有純虛函數(shù)的類是抽象類,不能生成對象,只能派生。他派生的類的純虛函數(shù)沒有被改寫,那么,它的派生類還是個抽象類。
定義純虛函數(shù)就是為了讓基類不可實(shí)例化化
因?yàn)閷?shí)例化這樣的抽象數(shù)據(jù)結(jié)構(gòu)本身并沒有意義。
或者給出實(shí)現(xiàn)也沒有意義
實(shí)際上我個人認(rèn)為純虛函數(shù)的引入,是出于兩個目的
1、為了安全,因?yàn)楸苊馊魏涡枰鞔_但是因?yàn)椴恍⌒亩鴮?dǎo)致的未知的結(jié)果,提醒子類去做應(yīng)做的實(shí)現(xiàn)。
2、為了效率,不是程序執(zhí)行的效率,而是為了編碼的效率。