一、程序設計概念等
結構化程序設計特點:
程序設計=數(shù)據(jù)結構+算法
程序內(nèi)容=過程+過程調用
面向對象的程序設計方法:
程序=對象+消息
面向對象=對象+類+繼承+消息
任何對象都具有屬性(數(shù)據(jù))和操作(方法)
具有強大的繼承性
關系:
- 聚集辆飘;2)繼承丈莺;3)實例化
類與抽象數(shù)據(jù)類型:對象是類的實例跨释,對象可以使用類中的函數(shù)- 對類的定義:
(最低限度)類名(class name)钱贯、外部接口(external interface)、內(nèi)部實現(xiàn)(implementation)
- 對類的定義:
-
外部接口:
公共成員變量
公共成員函數(shù) -
內(nèi)部實現(xiàn):
- 類的內(nèi)部實現(xiàn)對用戶是隱藏的
- 每一個操作符 對應內(nèi)部實現(xiàn)的具體操作
- 內(nèi)部實現(xiàn)是私有的
屬性:數(shù)據(jù)成員
方法:成員函數(shù)
- public實現(xiàn)類的接口措伐,private隱藏類的實現(xiàn)
抽象
數(shù)據(jù)抽象:某類對象共有的屬性和狀態(tài)
行為抽象: 共有的行為或功能特征
繼承
使用多繼承機制特纤,將增加命名沖突出現(xiàn)的可能性,表現(xiàn)形式:
- 派生類與某個基類之間發(fā)生命名沖突
- 基類和基類之間發(fā)生命名沖突
解決方法: 使用域解析符
接口與組件
- 接口的作用: 為不相關的類提供通用的處理服務
二侥加、從 C 到 C++
區(qū)別
<iostream> 和 <iostream.h>不一樣叫潦,是兩文件,里面的代碼不一樣。當使用<iostream>時矗蕊,相當于在c中調用庫函數(shù)短蜕,使用的是全局命名空間,也就是早期的c++實現(xiàn)傻咖;當使用<iostream.h> 時朋魔,該頭文件沒有定義全局命名空間,必須使用namespace std;這樣才能正確使用cout卿操。
const 定義時警检,定義了常量的類型,define只是簡單的文本替換害淤;使用const定義常量是一個說明語句扇雕,以分號結束,define是一個預處理命令窥摄,缺乏類型檢測機制镶奉。const是左結合的類型修飾符。
強制類型轉換:
static_cast: 強制類型轉換
const_cast: 去掉常數(shù)性
dynamic_cast: 用于繼承層次中的類型轉換
reinterpret_cast: 改變指針類型或將指針與整型轉換string 類型的索引都是從0開始的
-
內(nèi)聯(lián)函數(shù)
- 從源代碼層看崭放,有函數(shù)的結構哨苛,在編譯后,卻不具備函數(shù)的性質
- 避免函數(shù)調用的開銷币砂,避免調用函數(shù)對棧內(nèi)存開辟所帶來的消耗
int add(int x, int y=10) //right
int add(int x=10,int y) //error
-
常見編程錯誤:
- 操縱符作用于數(shù)據(jù)流中建峭,除了控制域寬的操縱符(控制域寬的操縱符在輸出了一個字符串和數(shù)字后自動清0)之外,其他所有的操縱器所造成的影響具有持久性决摧。不要誤以為在語句結束后所有的輸入輸出設置都恢復為默認值亿蒸。
- 混用C、C++的輸出輸入功能掌桩,可能會導致不可預料的錯誤边锁,使用函數(shù) ios::sync_with_stdio()可消除這種隱患。
- 關鍵字inline用于函數(shù)聲明而不是函數(shù)定義拘鞋。
三砚蓬、類
基本規(guī)則
- 使用class關鍵字,類成員在默認狀態(tài)下是私有的盆色;
使用struct關鍵字灰蛙,類成員在默認狀態(tài)下則是公有的。 - 類成員函數(shù)的定義方法:
- 在類聲明之中聲明隔躲,在類聲明之外定義摩梧;
- 在類聲明之中聲明及定義(inline),即為內(nèi)聯(lián)方式宣旱。
- 在進行成員函數(shù)聲明的時候使用inline關鍵字仅父,可將原本定義在類聲明之外的成員函數(shù)強制變?yōu)閮?nèi)聯(lián)函數(shù)。
- 在程序中使用類:
- 通常將類聲明放到.h中,這樣在使用時通過#include將類聲明包含進來笙纤;
- 通常將成員函數(shù)的定義放到cpp中耗溜;
- 不要將類外定義的成員函數(shù)放在.h中某一位頭文件通過#include被多個不同的文件所包含的話可能出現(xiàn)函數(shù)重復定義錯誤。
- 對象可以采用傳值方式傳遞給函數(shù)省容,也可以采用引用的方式抖拴,一般來說應該采用引用方式進行對象的傳遞和返回,而不是采用傳值的方式來進行(*因為傳值方式來傳遞和返回對象時會降低效率并將面臨對象間的拷貝操作腥椒,從而使數(shù)據(jù)增大阿宅,浪費內(nèi)存。
//引用方式比傳值方式效率高:
person a;
void f(person b) {......};
f(a); //該調用要將對象a復制到對象b中
void g(person& c) //引用方式
{......};
g(a); //該引用調用笼蛛,不復制對象
將成員函數(shù)標記為const可以預防對該函數(shù)所屬對象的數(shù)據(jù)成員的誤寫洒放,同時有些編譯器還可對這種情況進行一些優(yōu)化;一個const成員函數(shù)僅能調用其它const成員函數(shù)滨砍。
某函數(shù)如果采用const返回往湿,則其返回值只能賦給一個const類型的局部變量;
如果該const返回值是一個類的指針或者引用的話惨好,則不能用該指針或引用調用該類的non-const成員函數(shù)煌茴,因為這些函數(shù)可能會改變該類的數(shù)據(jù)成員的值随闺。
-
構造函數(shù)與析構函數(shù)
- 有些函數(shù)在調用時不需要顯式地提供函數(shù)名日川,編譯器會自動調用。而類構造函數(shù)可有多個矩乐,類析構函數(shù)最多一個龄句。
- 構造函數(shù)不能有返回類型,固:
void Person(); //Error
- 一個類可以有多個構造函數(shù)散罕,也就是說可以對構造函數(shù)進行重載分歇,但每個構造函數(shù)必須擁有不用的函數(shù)簽名。
Person(){name="Unknown";}//無參數(shù)
Person(const string& n);//函數(shù)類型為const string引用
Person(const char* n);//為C風格字符串
4.如果類的設計者不提供拷貝構造函數(shù)欧漱,編譯器會自動生成一個:將源對象所有數(shù)據(jù)成員的值逐一賦值給目標對象相應的數(shù)據(jù)成員职抡。
5.拷貝構造函數(shù):創(chuàng)建一個新的對象,此對象是另外一個對象的拷貝
6.轉型構造函數(shù):用于類型間的轉換误甚,只有一個參數(shù)
7.拷貝構造函數(shù)的原型:必須是引用
Person(Person&)
Person(const Person&)
Person(Person); //Error
拷貝構造函數(shù)可以有多于一個的參數(shù),但是第一個以后的所有參數(shù)都必須有默認值:
Person(const Person& p,bool married=false);
8.什么時候應該為一個類設計一個拷貝構造函數(shù)?
答:如果一個類包含指向動態(tài)村存儲空間指針類型的數(shù)據(jù)成員仅政,則就應為這個類設計拷貝構造函數(shù)魁淳。
9.轉型構造函數(shù)是一個單參數(shù)的構造函數(shù),它可以將一個對象從一種數(shù)據(jù)類型轉換為另一種數(shù)據(jù)類型冈钦。
10.轉型構造函數(shù)可替代函數(shù)重載機制郊丛。
11.對const類型的數(shù)據(jù)成員進行初始化時不能直接賦值。
對const類型的數(shù)據(jù)成員進行初始化時必須為構造函數(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.當使用動態(tài)方式為一個對象分配存儲空間時厉熟,C++操作符new和new[]比C函數(shù)malloc和calloc做得更好导盅。因為操作符new在分配存儲空間的同時,還會調用相應的構造函數(shù)揍瑟,而malloc和calloc無法完成這個任務认轨。
13.使用關鍵字static修飾的類的成員,稱之為類成員(靜態(tài)成員)月培,包括:類數(shù)據(jù)成員和類成員函數(shù)嘁字。這種成員屬于類本身,而不屬于類的對象杉畜。
類數(shù)據(jù)成員(靜態(tài)數(shù)據(jù)成員)特點:1)為同一個類的所有對象共享/2)類內(nèi)聲明纪蜒,類外定義(不是必須的)。
14.類數(shù)據(jù)成員(靜態(tài)數(shù)據(jù)成員)使用方式:對象明.靜態(tài)數(shù)據(jù)成員名此叠;對象指針名->靜態(tài)數(shù)據(jù)成員名纯续;類名::靜態(tài)數(shù)據(jù)成員名
15.錯誤的調用一個對象成員函數(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.錯誤地針對一個指向對象的指針來使用成員選擇操作符
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 成員選擇操作符. }
即:對象和對象引用不能使用指針操作符->!!
成員選擇操作符僅能由對象活對象引用所使用倦炒,指向對象的指針可以使用指針操作符->來訪問他們的成員;即上改為:
p->m(); //OK,p a pointer to an object
17.在static成員函數(shù)中使用this時錯誤的软瞎。
四逢唤、繼承
1.使用using聲明可以改變成員在派生類中的訪問限制,如:
//基類中的共有成員一般情況下被繼承為共有成員涤浇,但使用using聲明可將其改為私有成員(或保護成員)
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類的任何對象調用set_x
2.定義簡單派生類構造函數(shù)的一般形式:
<派生類構造函數(shù)名>(<總參數(shù)列表>):<基類構造函數(shù)名>(<參數(shù)表>)
{ <派生類新增數(shù)據(jù)成員初始化> };
在建立一個對象時,執(zhí)行構造函數(shù)的順序是:
1)最先調用基類的構造函數(shù)只锭,對基類數(shù)據(jù)成員初始化著恩;對基類的構造函數(shù)的調用順序取決于這些基類在被繼承時的說明順序,于他么的初始化列表給出的順序無關蜻展;
2)再調用數(shù)據(jù)成員是類對象的構造函數(shù)喉誊,其調用次序按在類中定義的先后次序;
3)最后執(zhí)行派生類構造函數(shù)的函數(shù)體铺呵,對派生類新增數(shù)據(jù)成員初始化裹驰。 ————先父母、后客人片挂、最后自己——————
3.繼承下的析構函數(shù)幻林。
- 和構造函數(shù)一樣贞盯,基類的析構函數(shù)派生類也不能繼承;
- 在聲明派生類時沪饺,可以根據(jù)需要定義自己的析構函數(shù)躏敢,用來對派生類中新增加的成員進行清理工作;
- 在執(zhí)行派生類的析構函數(shù)時整葡,系統(tǒng)會自動調用基類的析構函數(shù)件余,對基類進行清理;
- 派生類析構函數(shù)的執(zhí)行順序于構造函數(shù)正好相反(原因:析構函數(shù)時用來釋放由構造函數(shù)分配的內(nèi)存資源遭居,這種次序啼器,可以確保最近分配的額內(nèi)存資源可以最先被釋放)
4.多繼承
- 單繼承基類和派生類組成樹結構,而多繼承基類和派生類組成有向圖結構俱萍;多繼承的派生類可以同時具有多個基類端壳,它同時繼承了這些基類的所有成員;
- 派生類構造函數(shù)執(zhí)行順序是先執(zhí)行所有基類的構造函數(shù)枪蘑,再執(zhí)行派生類本身構造函數(shù)损谦,在多繼承情況下,基類構造函數(shù)的執(zhí)行順序按它們在被繼承時所聲明的順序(從左到右)一次調用岳颇,與它們在初始化列表中的順序無關照捡。
- 基類的構造函數(shù)被先調用(按聲明時的順序),數(shù)據(jù)成員所在類的構造函數(shù)次之话侧,最后執(zhí)行派生類的構造函數(shù)栗精。
- 多繼承機制下的命名沖突。
(轉)
虛函數(shù)
定義一個函數(shù)為虛函數(shù)掂摔,不代表函數(shù)為不被實現(xiàn)的函數(shù)术羔。
定義他為虛函數(shù)是為了允許用基類的指針來調用子類的這個函數(shù)赢赊。
定義一個函數(shù)為純虛函數(shù)乙漓,才代表函數(shù)沒有被實現(xiàn)。
定義純虛函數(shù)是為了實現(xiàn)一個接口释移,起到一個規(guī)范的作用叭披,規(guī)范繼承這個類的程序員必須實現(xiàn)這個函數(shù)。
1玩讳、簡介
假設我們有下面的類層次:
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的指針,但是被調用的函數(shù)(foo)卻是B的!
return 0;
}
這個例子是虛函數(shù)的一個典型應用熏纯,通過這個例子同诫,也許你就對虛函數(shù)有了一些概念。它虛就虛在所謂“推遲聯(lián)編”或者“動態(tài)聯(lián)編”上樟澜,一個類函數(shù)的調用并不是在編譯時刻被確定的误窖,而是在運行時刻被確定的叮盘。由于編寫代碼的時候并不能確定被調用的是基類的函數(shù)還是哪個派生類的函數(shù),所以被成為“虛”函數(shù)霹俺。
虛函數(shù)只能借助于指針或者引用來達到多態(tài)的效果柔吼。
C++純虛函數(shù)
一、定義
純虛函數(shù)是在基類中聲明的虛函數(shù)丙唧,它在基類中沒有定義愈魏,但要求任何派生類都要定義自己的實現(xiàn)方法。在基類中實現(xiàn)純虛函數(shù)的方法是在函數(shù)原型后加“=0”
virtual void funtion1()=0
二想际、引入原因
1培漏、為了方便使用多態(tài)特性,我們常常需要在基類中定義虛擬函數(shù)胡本。
2北苟、在很多情況下,基類本身生成對象是不合情理的打瘪。例如友鼻,動物作為一個基類可以派生出老虎、孔雀等子類闺骚,但動物本身生成對象明顯不合常理彩扔。
為了解決上述問題,引入了純虛函數(shù)的概念僻爽,將函數(shù)定義為純虛函數(shù)(方法:virtual ReturnType Function()= 0;)虫碉,則編譯器要求在派生類中必須予以重寫以實現(xiàn)多態(tài)性。同時含有純虛擬函數(shù)的類稱為抽象類胸梆,它不能生成對象敦捧。這樣就很好地解決了上述兩個問題。
聲明了純虛函數(shù)的類是一個抽象類碰镜。所以兢卵,用戶不能創(chuàng)建類的實例,只能創(chuàng)建它的派生類的實例绪颖。
純虛函數(shù)最顯著的特征是:它們必須在繼承類中重新聲明函數(shù)(不要后面的=0秽荤,否則該派生類也不能實例化),而且它們在抽象類中往往沒有定義柠横。
定義純虛函數(shù)的目的在于窃款,使派生類僅僅只是繼承函數(shù)的接口。
純虛函數(shù)的意義牍氛,讓所有的類對象(主要是派生類對象)都可以執(zhí)行純虛函數(shù)的動作晨继,但類無法為純虛函數(shù)提供一個合理的缺省實現(xiàn)。所以類純虛函數(shù)的聲明就是在告訴子類的設計者搬俊,“你必須提供一個純虛函數(shù)的實現(xiàn)紊扬,但我不知道你會怎樣實現(xiàn)它”曲饱。
抽象類的介紹
抽象類是一種特殊的類,它是為了抽象和設計的目的為建立的珠月,它處于繼承層次結構的較上層扩淀。
(1)抽象類的定義: 稱帶有純虛函數(shù)的類為抽象類。
(2)抽象類的作用:
抽象類的主要作用是將有關的操作作為結果接口組織在一個繼承層次結構中啤挎,由它來為派生類提供一個公共的根驻谆,派生類將具體實現(xiàn)在其基類中作為接口的操作。所以派生類實際上刻畫了一組子類的操作接口的通用語義庆聘,這些語義也傳給子類胜臊,子類可以具體實現(xiàn)這些語義,也可以再將這些語義傳給自己的子類伙判。
(3)使用抽象類時注意:
? 抽象類只能作為基類來使用象对,其純虛函數(shù)的實現(xiàn)由派生類給出。如果派生類中沒有重新定義純虛函數(shù)宴抚,而只是繼承基類的純虛函數(shù)勒魔,則這個派生類仍然還是一個抽象類。如果派生類中給出了基類純虛函數(shù)的實現(xiàn)菇曲,則該派生類就不再是抽象類了冠绢,它是一個可以建立對象的具體的類。
? 抽象類是不能定義對象的常潮。
總結:
1弟胀、純虛函數(shù)聲明如下: virtual void funtion1()=0; 純虛函數(shù)一定沒有定義,純虛函數(shù)用來規(guī)范派生類的行為喊式,即接口孵户。包含純虛函數(shù)的類是抽象類,抽象類不能定義實例岔留,但可以聲明指向實現(xiàn)該抽象類的具體類的指針或引用夏哭。
2、虛函數(shù)聲明如下:virtual ReturnType FunctionName(Parameter)贸诚;虛函數(shù)必須實現(xiàn)方庭,如果不實現(xiàn),編譯器將報錯酱固,錯誤提示為:
error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"
3、對于虛函數(shù)來說头朱,父類和子類都有各自的版本运悲。由多態(tài)方式調用的時候動態(tài)綁定。
4项钮、實現(xiàn)了純虛函數(shù)的子類班眯,該純虛函數(shù)在子類中就編程了虛函數(shù)希停,子類的子類即孫子類可以覆蓋該虛函數(shù),由多態(tài)方式調用的時候動態(tài)綁定署隘。
5宠能、虛函數(shù)是C++中用于實現(xiàn)多態(tài)(polymorphism)的機制。核心理念就是通過基類訪問派生類定義的函數(shù)磁餐。
6违崇、在有動態(tài)分配堆上內(nèi)存的時候,析構函數(shù)必須是虛函數(shù)诊霹,但沒有必要是純虛的羞延。
7、友元不是成員函數(shù)脾还,只有成員函數(shù)才可以是虛擬的伴箩,因此友元不能是虛擬函數(shù)。但可以通過讓友元函數(shù)調用虛擬成員函數(shù)來解決友元的虛擬問題鄙漏。
8嗤谚、析構函數(shù)應當是虛函數(shù),將調用相應對象類型的析構函數(shù)怔蚌,因此呵恢,如果指針指向的是子類對象,將調用子類的析構函數(shù)媚创,然后自動調用基類的析構函數(shù)渗钉。
有純虛函數(shù)的類是抽象類,不能生成對象钞钙,只能派生鳄橘。他派生的類的純虛函數(shù)沒有被改寫,那么芒炼,它的派生類還是個抽象類瘫怜。
定義純虛函數(shù)就是為了讓基類不可實例化化
因為實例化這樣的抽象數(shù)據(jù)結構本身并沒有意義。
或者給出實現(xiàn)也沒有意義
實際上我個人認為純虛函數(shù)的引入本刽,是出于兩個目的
1鲸湃、為了安全,因為避免任何需要明確但是因為不小心而導致的未知的結果子寓,提醒子類去做應做的實現(xiàn)暗挑。
2、為了效率斜友,不是程序執(zhí)行的效率炸裆,而是為了編碼的效率。