一、程序設計概念等
結構化程序設計特點:
程序設計=數據結構+算法
程序內容=過程+過程調用
面向對象的程序設計方法:
程序=對象+消息
面向對象=對象+類+繼承+消息
任何對象都具有屬性(數據)和操作(方法)
具有強大的繼承性
關系:
- 聚集;2)繼承;3)實例化
類與抽象數據類型:對象是類的實例,對象可以使用類中的函數- 對類的定義:
(最低限度)類名(class name)、外部接口(external interface)、內部實現(implementation)
- 對類的定義:
-
外部接口:
公共成員變量
公共成員函數 -
內部實現:
- 類的內部實現對用戶是隱藏的
- 每一個操作符 對應內部實現的具體操作
- 內部實現是私有的
屬性:數據成員
方法:成員函數
- public實現類的接口,private隱藏類的實現
抽象
數據抽象:某類對象共有的屬性和狀態
行為抽象: 共有的行為或功能特征
繼承
使用多繼承機制,將增加命名沖突出現的可能性,表現形式:
- 派生類與某個基類之間發生命名沖突
- 基類和基類之間發生命名沖突
解決方法: 使用域解析符
接口與組件
- 接口的作用: 為不相關的類提供通用的處理服務
二、從 C 到 C++
區別
<iostream> 和 <iostream.h>不一樣,是兩文件,里面的代碼不一樣。當使用<iostream>時,相當于在c中調用庫函數,使用的是全局命名空間,也就是早期的c++實現;當使用<iostream.h> 時,該頭文件沒有定義全局命名空間,必須使用namespace std;這樣才能正確使用cout。
const 定義時,定義了常量的類型,define只是簡單的文本替換;使用const定義常量是一個說明語句,以分號結束,define是一個預處理命令,缺乏類型檢測機制。const是左結合的類型修飾符。
強制類型轉換:
static_cast: 強制類型轉換
const_cast: 去掉常數性
dynamic_cast: 用于繼承層次中的類型轉換
reinterpret_cast: 改變指針類型或將指針與整型轉換string 類型的索引都是從0開始的
-
內聯函數
- 從源代碼層看,有函數的結構,在編譯后,卻不具備函數的性質
- 避免函數調用的開銷,避免調用函數對棧內存開辟所帶來的消耗
int add(int x, int y=10) //right
int add(int x=10,int y) //error
-
常見編程錯誤:
- 操縱符作用于數據流中,除了控制域寬的操縱符(控制域寬的操縱符在輸出了一個字符串和數字后自動清0)之外,其他所有的操縱器所造成的影響具有持久性。不要誤以為在語句結束后所有的輸入輸出設置都恢復為默認值。
- 混用C、C++的輸出輸入功能,可能會導致不可預料的錯誤,使用函數 ios::sync_with_stdio()可消除這種隱患。
- 關鍵字inline用于函數聲明而不是函數定義。
三、類
基本規則
- 使用class關鍵字,類成員在默認狀態下是私有的;
使用struct關鍵字,類成員在默認狀態下則是公有的。 - 類成員函數的定義方法:
- 在類聲明之中聲明,在類聲明之外定義;
- 在類聲明之中聲明及定義(inline),即為內聯方式。
- 在進行成員函數聲明的時候使用inline關鍵字,可將原本定義在類聲明之外的成員函數強制變為內聯函數。
- 在程序中使用類:
- 通常將類聲明放到.h中,這樣在使用時通過#include將類聲明包含進來;
- 通常將成員函數的定義放到cpp中;
- 不要將類外定義的成員函數放在.h中某一位頭文件通過#include被多個不同的文件所包含的話可能出現函數重復定義錯誤。
- 對象可以采用傳值方式傳遞給函數,也可以采用引用的方式,一般來說應該采用引用方式進行對象的傳遞和返回,而不是采用傳值的方式來進行(*因為傳值方式來傳遞和返回對象時會降低效率并將面臨對象間的拷貝操作,從而使數據增大,浪費內存。
//引用方式比傳值方式效率高:
person a;
void f(person b) {......};
f(a); //該調用要將對象a復制到對象b中
void g(person& c) //引用方式
{......};
g(a); //該引用調用,不復制對象
將成員函數標記為const可以預防對該函數所屬對象的數據成員的誤寫,同時有些編譯器還可對這種情況進行一些優化;一個const成員函數僅能調用其它const成員函數。
某函數如果采用const返回,則其返回值只能賦給一個const類型的局部變量;
如果該const返回值是一個類的指針或者引用的話,則不能用該指針或引用調用該類的non-const成員函數,因為這些函數可能會改變該類的數據成員的值。
-
構造函數與析構函數
- 有些函數在調用時不需要顯式地提供函數名,編譯器會自動調用。而類構造函數可有多個,類析構函數最多一個。
- 構造函數不能有返回類型,固:
void Person(); //Error
- 一個類可以有多個構造函數,也就是說可以對構造函數進行重載,但每個構造函數必須擁有不用的函數簽名。
Person(){name="Unknown";}//無參數
Person(const string& n);//函數類型為const string引用
Person(const char* n);//為C風格字符串
4.如果類的設計者不提供拷貝構造函數,編譯器會自動生成一個:將源對象所有數據成員的值逐一賦值給目標對象相應的數據成員。
5.拷貝構造函數:創建一個新的對象,此對象是另外一個對象的拷貝
6.轉型構造函數:用于類型間的轉換,只有一個參數
7.拷貝構造函數的原型:必須是引用
Person(Person&)
Person(const Person&)
Person(Person); //Error
拷貝構造函數可以有多于一個的參數,但是第一個以后的所有參數都必須有默認值:
Person(const Person& p,bool married=false);
8.什么時候應該為一個類設計一個拷貝構造函數?
答:如果一個類包含指向動態村存儲空間指針類型的數據成員,則就應為這個類設計拷貝構造函數。
9.轉型構造函數是一個單參數的構造函數,它可以將一個對象從一種數據類型轉換為另一種數據類型。
10.轉型構造函數可替代函數重載機制。
11.對const類型的數據成員進行初始化時不能直接賦值。
對const類型的數據成員進行初始化時必須為構造函數添加一個 初始化列表。
>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.當使用動態方式為一個對象分配存儲空間時,C++操作符new和new[]比C函數malloc和calloc做得更好。因為操作符new在分配存儲空間的同時,還會調用相應的構造函數,而malloc和calloc無法完成這個任務。
13.使用關鍵字static修飾的類的成員,稱之為類成員(靜態成員),包括:類數據成員和類成員函數。這種成員屬于類本身,而不屬于類的對象。
類數據成員(靜態數據成員)特點:1)為同一個類的所有對象共享/2)類內聲明,類外定義(不是必須的)。
14.類數據成員(靜態數據成員)使用方式:對象明.靜態數據成員名;對象指針名->靜態數據成員名;類名::靜態數據成員名
15.錯誤的調用一個對象成員函數,把它作為類的類成員函數來使用:
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:?。恚海?is not static}
- 一個static數據成員在類的聲明內被聲明,錯誤地將static數據成員定義在程序塊內:
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成員函數中使用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.定義簡單派生類構造函數的一般形式:
<派生類構造函數名>(<總參數列表>):<基類構造函數名>(<參數表>)
{ <派生類新增數據成員初始化> };
在建立一個對象時,執行構造函數的順序是:
1)最先調用基類的構造函數,對基類數據成員初始化;對基類的構造函數的調用順序取決于這些基類在被繼承時的說明順序,于他么的初始化列表給出的順序無關;
2)再調用數據成員是類對象的構造函數,其調用次序按在類中定義的先后次序;
3)最后執行派生類構造函數的函數體,對派生類新增數據成員初始化。 ————先父母、后客人、最后自己——————
3.繼承下的析構函數。
- 和構造函數一樣,基類的析構函數派生類也不能繼承;
- 在聲明派生類時,可以根據需要定義自己的析構函數,用來對派生類中新增加的成員進行清理工作;
- 在執行派生類的析構函數時,系統會自動調用基類的析構函數,對基類進行清理;
- 派生類析構函數的執行順序于構造函數正好相反(原因:析構函數時用來釋放由構造函數分配的內存資源,這種次序,可以確保最近分配的額內存資源可以最先被釋放)
4.多繼承
- 單繼承基類和派生類組成樹結構,而多繼承基類和派生類組成有向圖結構;多繼承的派生類可以同時具有多個基類,它同時繼承了這些基類的所有成員;
- 派生類構造函數執行順序是先執行所有基類的構造函數,再執行派生類本身構造函數,在多繼承情況下,基類構造函數的執行順序按它們在被繼承時所聲明的順序(從左到右)一次調用,與它們在初始化列表中的順序無關。
- 基類的構造函數被先調用(按聲明時的順序),數據成員所在類的構造函數次之,最后執行派生類的構造函數。
- 多繼承機制下的命名沖突。
(轉)
虛函數
定義一個函數為虛函數,不代表函數為不被實現的函數。
定義他為虛函數是為了允許用基類的指針來調用子類的這個函數。
定義一個函數為純虛函數,才代表函數沒有被實現。
定義純虛函數是為了實現一個接口,起到一個規范的作用,規范繼承這個類的程序員必須實現這個函數。
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的指針,但是被調用的函數(foo)卻是B的!
return 0;
}
這個例子是虛函數的一個典型應用,通過這個例子,也許你就對虛函數有了一些概念。它虛就虛在所謂“推遲聯編”或者“動態聯編”上,一個類函數的調用并不是在編譯時刻被確定的,而是在運行時刻被確定的。由于編寫代碼的時候并不能確定被調用的是基類的函數還是哪個派生類的函數,所以被成為“虛”函數。
虛函數只能借助于指針或者引用來達到多態的效果。
C++純虛函數
一、定義
純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,但要求任何派生類都要定義自己的實現方法。在基類中實現純虛函數的方法是在函數原型后加“=0”
virtual void funtion1()=0
二、引入原因
1、為了方便使用多態特性,我們常常需要在基類中定義虛擬函數。
2、在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。
為了解決上述問題,引入了純虛函數的概念,將函數定義為純虛函數(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。
聲明了純虛函數的類是一個抽象類。所以,用戶不能創建類的實例,只能創建它的派生類的實例。
純虛函數最顯著的特征是:它們必須在繼承類中重新聲明函數(不要后面的=0,否則該派生類也不能實例化),而且它們在抽象類中往往沒有定義。
定義純虛函數的目的在于,使派生類僅僅只是繼承函數的接口。
純虛函數的意義,讓所有的類對象(主要是派生類對象)都可以執行純虛函數的動作,但類無法為純虛函數提供一個合理的缺省實現。所以類純虛函數的聲明就是在告訴子類的設計者,“你必須提供一個純虛函數的實現,但我不知道你會怎樣實現它”。
抽象類的介紹
抽象類是一種特殊的類,它是為了抽象和設計的目的為建立的,它處于繼承層次結構的較上層。
(1)抽象類的定義: 稱帶有純虛函數的類為抽象類。
(2)抽象類的作用:
抽象類的主要作用是將有關的操作作為結果接口組織在一個繼承層次結構中,由它來為派生類提供一個公共的根,派生類將具體實現在其基類中作為接口的操作。所以派生類實際上刻畫了一組子類的操作接口的通用語義,這些語義也傳給子類,子類可以具體實現這些語義,也可以再將這些語義傳給自己的子類。
(3)使用抽象類時注意:
? 抽象類只能作為基類來使用,其純虛函數的實現由派生類給出。如果派生類中沒有重新定義純虛函數,而只是繼承基類的純虛函數,則這個派生類仍然還是一個抽象類。如果派生類中給出了基類純虛函數的實現,則該派生類就不再是抽象類了,它是一個可以建立對象的具體的類。
? 抽象類是不能定義對象的。
總結:
1、純虛函數聲明如下: virtual void funtion1()=0; 純虛函數一定沒有定義,純虛函數用來規范派生類的行為,即接口。包含純虛函數的類是抽象類,抽象類不能定義實例,但可以聲明指向實現該抽象類的具體類的指針或引用。
2、虛函數聲明如下:virtual ReturnType FunctionName(Parameter);虛函數必須實現,如果不實現,編譯器將報錯,錯誤提示為:
error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"
3、對于虛函數來說,父類和子類都有各自的版本。由多態方式調用的時候動態綁定。
4、實現了純虛函數的子類,該純虛函數在子類中就編程了虛函數,子類的子類即孫子類可以覆蓋該虛函數,由多態方式調用的時候動態綁定。
5、虛函數是C++中用于實現多態(polymorphism)的機制。核心理念就是通過基類訪問派生類定義的函數。
6、在有動態分配堆上內存的時候,析構函數必須是虛函數,但沒有必要是純虛的。
7、友元不是成員函數,只有成員函數才可以是虛擬的,因此友元不能是虛擬函數。但可以通過讓友元函數調用虛擬成員函數來解決友元的虛擬問題。
8、析構函數應當是虛函數,將調用相應對象類型的析構函數,因此,如果指針指向的是子類對象,將調用子類的析構函數,然后自動調用基類的析構函數。
有純虛函數的類是抽象類,不能生成對象,只能派生。他派生的類的純虛函數沒有被改寫,那么,它的派生類還是個抽象類。
定義純虛函數就是為了讓基類不可實例化化
因為實例化這樣的抽象數據結構本身并沒有意義。
或者給出實現也沒有意義
實際上我個人認為純虛函數的引入,是出于兩個目的
1、為了安全,因為避免任何需要明確但是因為不小心而導致的未知的結果,提醒子類去做應做的實現。
2、為了效率,不是程序執行的效率,而是為了編碼的效率。