程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 全面回顧認識C++(十)

全面回顧認識C++(十)

編輯:關於C++

如果你覺得C++還不夠復雜,那你知道protected abstract virtual base pur virtual private destructor inheritance是什麼意思嗎?你上次用到它又是什麼時候呢?

-----Tom Cargill,C++ Journal 1990年秋

C++在軟件的復用性方面或許可以比以前的語言取得更大的成功。因為C++的繼承的風格基於對象,即允許數據的繼承,也允許代碼的繼承。

1985年以前,C++的名字是“C with classes”,但是現在人們已經在其中加入了非常非常多的特性,從當時的角度類看,“C with classes”是C語言的一個相當合理的擴展,很容易理解和解釋。隨即,人們對這種語言投入了極大的熱情,至今未曾衰減。現在的C++是一個相當龐大的語言。具體地說,一個C編譯器的前端大約有40000行代碼左右,而一個C++編譯器的前端的代碼可能是它的兩倍,甚至更多。

這一節主要是對C++語言特性和一些概念的概括總結,本來不打算寫了,因為具體的細節問題我在[C++ PP(Edit 6)]中也有具體的解釋,可能大部分有重復,但是為了小結和復習,回顧一下C++語言與C語言的區別和一些新的特性及其相關概念,決定還是寫下來,高手略過。

面向對象編程的特點是封裝、繼承和多態(動態綁定)。C++通過類的派生支持繼承,通過虛擬函數支持動態綁定。虛擬函數提供了一種封裝類體系實現細節的方法。

一、全面認識C++概念及注意的知識點:

(1)抽象(Abstruct):它是一個去除對象中不重要的細節的過程,只有那些描述了那些對象本質特征的關鍵點才被保留。抽象是一種設計活動,其他的概念都是提供抽象的OOP(Object-oriented paradigm)特性。

抽象的概念就是觀察一群事物,並認識到他們具有共同的主題。你可以忽略不重要的區別,只記錄能表現事物特性的關鍵數據。當你這樣做的時候,就是在進行抽象,所存儲的數據類型就是“抽象數據類型”。抽象聽上去像是一個艱深的數學概念,但不要被它糊弄--它只不過是對事物的簡化而已。

在軟件中,抽象是十分有用的,它允許程序員實現下列目標:

(1)隱藏不相關的細節,把注意力集中在本質特征上。

(2)向外部世界提供一個“黑盒子接口”,接口確定了施加在對象之上的有效操作集合,但它並不提示對象在內部是怎樣實現他們的。

(3)把一個復雜的系統分解成幾個相互獨立的組成部分。這可以做到分工明確,避免組件之間不符合規則的相互作用。

(4)重用和代碼共享。

抽象建立了一種抽象數據類型,C++使用l類(class)這個特性來實現它。

(2)類:就是用戶定義類型加上對該類型進行操作。

類是一種用戶定義的類型,就好像是int這樣的內置類型一樣。內置類型已經有了一套完善的針對它的操作(如算術運算)等,類機制也必須允許程序員規定他所定義的類能夠進行的操作。類裡面的任何東西被稱為類的成員。一個空類有:構造函數,析構函數,拷貝構造函數,賦值操作符operator=四個成員函數。下面會分別介紹。

C++的類機制實現了OOP的封裝要求,類就是封裝的軟件實現。類也是一種類型,就像是char,int,double和struct st*一樣。因此在使用之前必須要聲明類的變量,類和類型一樣,你可以對它進行很多操作,如取得它的大小或聲明它的變量等。對象和變量一樣,可以對它進行很多操作,如取得它的地址、把它作為參數傳遞、把它作為函數的返回值、使它成為常量值等。

(3)對象(Object):某個類的一個特定變量,就像j可能是int類型的一個變量一樣。對象也可以被稱作類的實例(instance)。

(4)封裝(encapsulation):把類型、數據和函數組合在一起,組成一個類。在C語言中,頭文件就是一個非常脆弱的封裝的實例。它之所以是一個微不足道的封裝例子,是因為它的組合形式是純詞法意義上的,編譯器並不知道頭文件是一個語義單位。

(5)單繼承(ingheritance):這是一個很大的概念--允許類從一個更簡單的基類中接收數據結構和函數。派生類獲得基類的數據和操作,並可以根據需要對它們進行改寫,也可以在派生類中增加新的數據和函數成員。在C語言裡不存在繼承的概念,沒有任何東西可以模擬這個特性。當一個類沿用或定制它的唯一基類的數據結構和成員函數時,它就成了單繼承。不要把在一個類內部嵌套另一個類與繼承混淆。嵌套只是把一個類嵌入另一個類的內部,稱內部類。

(6)多繼承:多重繼承允許把兩個類組合成一個類,這樣的 結果類對象的行為類似於這兩個類的對象中的一個。

(7)虛繼承:主要解決在多繼承中,訪問不明確的問題。虛繼承中所有的子類都指向同一份數據空間。

(8)虛基類:虛繼承的父類稱之為虛基類,它表示積累是被多個多重繼承的類所共享。(編程的時候一般避免虛繼承)

 

\

 

(9)內部類(Inner):內部類與外部類的關系:與編譯器有關,VC6.0他們是兩個完全不同的類,不能用對方的東西,如果要用,可以定義為友元類;VS2005編譯器,內部類可以用外部類的東西,外部類不能用內部類的東西。

內部類的構造函數運行的順序:先調用外部類的構造函數,如果有父類則先調用父類的構造函數。然後再調用自己的構造函數。

(10)純虛函數(pure virtual function):純虛函數是一種特殊的虛函數,在許多情況下,基類不能對虛函數給出有意義的實現,而把它申明為純虛函數,它的實現留給基類派生的類去做。這就是純虛函數的作用(類似於java中的接口)。同時含有純虛函數的類稱為抽象類(至少含有一個純虛函數),它不能生成對象。

class father

{

public:

virtual void show()=0;//純虛函數;

}

(11)模板(template):這個特性支持參數化類型。是一種泛型技術,用不變的代碼實現可變的算法。

#include

using namespace std;

template//函數模板不能給默認值;

T Min(T a,T b)

{

return a>b?b:a;

}

template

void show(T a,R b)

{

cout<<"a:"<

}

void show1(T c)//一個函數模板也不能被多個函數來使用;

{

cout<

}

template// 只有類模板才可以給默認值;

class father

{

public:

T a;

R b;

};

template

class Son:public father//在父類的後面加上表示子類用S來給父類進行初始化;

{

//初始化;

};

int main()

{

cout<

show(23,"kongyin");

system("pause");

return 0;

}

(12)內聯函數(inline):程序員可以規定某個特定的函數在行內以指令流的形式展開(就像宏一樣),而不是產生一個函數調用,避免了函數調用的過程,提高了程序的執行效率。

(13)異常處理(exception):異常通過發生錯誤時把處理自動切換到程序中用於處理錯誤代碼的那部分代碼。C標准庫提供兩個特殊的函數:setjmp() 及 longjmp(),這兩個函數是結構化異常的基礎,正是利用這兩個函數的特性來實現異常。setjmp和longjmp的主要作用是錯誤恢復,前面有詳細的講過。只要還沒有從函數返回,一旦發現一個不可恢復的錯誤,可以把控制轉移到主輸入循環,並從那裡重新開始運行。

try{} cathch{}異常處理是C++特有的。實例如下:

#include

using namespace std;

void show(int num)

{

switch(num)

{

case 1:

cout<<"throw:";

throw num;

case 2:

cout<<"throw:";

throw "ERROR";

}

}

int main()

{

try

{

show(1);

}

catch (int a)//拋出是什麼類型的就要用什麼類型的來接收。

{

cout<<"catch:"<

}

catch (char *str)

{

cout<<"catch:"<

}

system("pause");

return 0;

}

(14)友元函數(friend):屬於friend的函數不屬於類的成員函數,但可以像成員函數一樣訪問類的private和protected成員。friend可以是一個函數,也可以是一個類。

(15)成員函數(member function):調用一個類的對象的成員函數相當於面向對象編程語言所使用的“向對象發送一條信息”這個術語。每個成員都有一個this指針參數,它是隱式的賦給該函數的,它允許對象在成員函數內部引用對象本身。注意,在成員函數內部,你應該發現this指針並未顯示出現,這也是語言本身所涉及的。

#include

using namespace std;

class father

{

private:

int number;

public:

void show()

{

cout<<"this的地址:"<

}

};

int main()

{

father ff;

cout<<"對象的地址:"<<&ff<

ff.show();

system("pause");

return 0;

}

運行結果:

 

\

 

(16)構造函數(Constructor):絕大多數類至少有一個構造函數。當類的一個對象被創建時,構造函數被隱式的調用,它負責對象的初始化。構造函數是必要的,因為類通常包含一些結構,二結構又包含很多字段。這就需要復雜的初始化。當類的一個對象被創建時,構造函數會被自動調用。

(17)拷貝構造函數(Copy Constructor):拷貝構造函數也是一種特殊的構造函數,函數的名稱和類名一致,它的唯一一個參數是本類型的一個引用。a,當用一個已初始化的自定義類型的對象去初始化另一個新構造的對象的時候;b,當函數的參數是類的對象的時候;c,函數的返回值是類的對象的時候,拷貝構造函數就被調用。

(18)深拷貝和淺拷貝(位拷貝):簡單理解:深拷貝,發生對象的復制,資源的復制;淺拷貝只是指針的復制,沒有資源的復制。

(19)析構函數(DesConstructor):與構造函數相對應的,類也存在一個清理函數,稱為析構函數。當對象被銷毀(超出其生命周期或進行delete操作,回收它所使用的堆內存)時,析構函數被自動調用。有人把析構函數當作一種保險方法來確保當對象離開適當的范圍時,同步鎖總能夠釋放。所以它們不僅能清楚對象,還清理 對象所持有的鎖。構造函數和析構函數都是必要的,因為類外部的任何函數都不能訪問類的私有成員。因此,你需要類內部有一個特權函數來創建一個對象並對其進行初始化。但是構造函數和析構函數都違背了C語言中“一切工作自己負責”的原則。他們可以使大量的工作在程序運行時被隱式的調用完成一些功能,減輕程序員的負擔,這也違背了C語言的哲學,也就是語言的任何部分都不應該通過隱藏的運行時程序來實現。

(20)重載(overload):就是簡單的復用一個現存的名字,但使它操作一個不同的類型。可以是函數的名字,也可以是一個操作符。該函數的返回值、參數的個數、類型和順序都可以不同。重載不能是私有的,不能降低訪問權限,否則不是重載。重載都是在編譯器解析。

(21)重寫(overwrite):函數的名稱、參數和返回值一樣。僅限於父子之間的返回類型可以不同。多態是在重寫的基礎上實現的。

(22)多態(polymorphism):源於希臘語,意思是“多種形狀”。根據不同類型調用不同的函數的能力,允許父類的指針可以指向子類的成員函數,允許對象與適當的成員函數在運行時進行綁定,運行時根據對象的類型選擇正確的成員函數。也稱遲後編譯和滯後編譯(late binding)。基類有多少子類,父指針就有多少形態。主要通過虛函數來實現,關鍵字為virtual。它用來通知編譯器,該派生類的成員函數有可能取代基類的同名函數。

原理是:單繼承通常通過在每個對象內包含一個vptr指針來實現虛擬函數。vptr指針指向一個叫做vtbl的函數指針向量(稱為虛擬函數表,也稱V表)。每個類都有這樣一個向量,類中的每個虛擬函數在該向量中都有一條記錄。使用這種方法,該類的所有對象共享實現代碼。通過此虛表來實現。是一種泛型技術。虛表的首地址是對象的首地址。

(23)虛函數(virtual):不能被聲明為虛函數的是:普通函數,類的靜態成員函數,inline函數,friend函數(不是類的成員函數,C++不支持)和構造函數。能聲明為虛函數的條件是:(1)能被繼承;(2)類的成員函數

(24)靜態成員函數:靜態成員函數是類的組成部分,但是不是任何對象的組成部分,所有對象共享一份,沒有必要動態綁定,也不能被繼承【效果能,但機理不能。靜態成員函數就一份實體,在父類裡;子類繼承父類時也無需拷貝父類的靜態函數,但子類可以使用父類的靜態成員函數】,並且靜態成員函數只能訪問靜態變量。

(25)new和delete操作符:用於取代free和malloc函數。這兩個操作符用起來更方便一點。如能夠自動完成sizeof的計算工作,並能調用合適的構造函數和析構函數。new能真正的建立一個對象,則malloc只是簡單的分配內存。

(26)傳引用調用(call-by-reference):相當於傳址調用,C語言只使用傳值調用(call-by-value)。C++在語言中引入了傳引用調用,可以把對象的引用作為參數傳遞。

二、C++對C語言的改進

(1)類型轉換既可以寫成像float(i)這樣看上去更順眼的形式,也可以寫成像(float)i這樣稍顯怪異的C語言風格的形式。

(2)C++允許一個常量整數來定義數組的大小;

const int size =128;

char str[size];

在C++中這是允許的,但是在C中是不允許的。

(3)聲明可以穿插在語句之間。在C語言中,一個語句塊中所有的聲明都必須放在所有的語句的前面。C++去掉了這個專橫的限制,聲明可以出現在語句可以出現的任何地方。

雖然C++顯的復雜,但是它是對C語言唯一成功的改造方案,擁有大群的支持者。

三、C++小知識點匯集(參考 錢能 著《C++程序設計教程(修訂版)》)<知識點可能有重復>

1.class裡默認的情況下的成員是private;struct默認是public;結構體中不能有成員函數;

2.::叫作用域區分符。黨成員函數在類外實現時,前面必須附加此前綴“::”,它仿佛在大聲吶喊,“嗨,我很重要,我表示有一些東西屬於這個類”,

指明一個函數和成員屬於哪個類。::可以不跟類名,表示全局函數,即非成員函數;

3.類中定義的成員函數都是內聯inline函數,這樣在調用的時候就不會拷貝一份,而是指針直接指向函數的入口地址,直接調用,提高了調用的效率;

4.成員函數一般規模都比較小,所以switch語句不允許用;

5.函數聲明一般在頭文件,而函數的定義不能再頭文件,因為它們將被編譯多次;使用#pragma once 表示只調用一次;

6.inline函數對編譯器而言必須是可見的,以便它能夠在調用點內展開該函數。與非inline函數不同的是,inline函數不是一次函數的跳轉,

而是指令的展開(從而提高執行效率)。如果內聯函數過大,就會導致目標碼過大,增加額外的換頁行為,降低指令高速緩存裝置的擊中率。

所以編譯器會自動變為非內聯;

7.由於類中的成員函數都是內聯inline的,所以避免了不能被包含早頭文件的問題;

8.因為類名是成員函數的一部分,所以一個類的成員函數與另一個成員函數重名也不能認為是重載;

9.類的對象所占的空間由他的數據成員所占據的空間綜合決定,類的成員函數不占據對象的空間;

10.類的對象有全局對象,局部對象,靜態對象和堆對象--指針對象;

11.構造函數是類的成員函數,可以有任意多個參數,可以重載;所以可以放在類外定義;沒有返回類型,當然也沒有返回值;但可以有無值返回語句return;

沒有構造函數就不能創建任何對象;

12.析構函數也是特殊的類成員函數,它沒有返回類型,沒有參數,不能重載,對象生命周期結束的時候,系統自動調用;

13.類中對象和函數的定義與聲明:

class Tdate{}

Tdate day();這是聲明了一個day的普通函數,返回的Tday類對象;

Tdate day(int);創建了一個函數;

Tdate day(10);創建對象;

14.類的析構函數將自動地為各個具有析構函數的數據成員調用析構函數;

15.類的定義是不分配空間和初始化的;

16.類是一個抽象的概念,並不是一個實體,並不含有屬性值,而只有對象才占有一定的空間,含有明確的屬性值。

17.在構造函數的後面,冒號表示要對類的數據成員的構造函數進行調用;冒號語法是的常量數據成員和應用數據成員的初始化成為可能;因為常量是不能被賦值的,引用變量也是不可重新指派的,初始化之後,其值就固定不變了。

class student{

public:

student(int &i):ten(10),refi(i){}

protected:

const int ten;//常量數據成員;

int &refi; //引用數據成員;

student()

{

ten=10; //error:常量不能賦值;

refi=i; //error:引用不能重新指派;

}

}

18.對於類的的數據成員是一般變量的情況,則放在冒號後面與放在函數體重初始化都一樣;

19.創建對象的唯一途徑是調用構造函數;

20.局部和靜態對象是指塊作用域和文件作用域的對象;

靜態對象只被構造一次:文件作用域的靜態對象在主函數開始運行前全部構造完畢。塊作用域的靜態對象,則在首次進入到定義該靜態對象的函數時,進行構造;

class student{

public:

int n;

student( int m_n)

{

cout<<"construct student"<

}

}

void fn(int m)

{

static student st(m); //局部靜態構造函數;

cout<<"fn: "<

}

int main()

{

fn(10);

fn(20);

}

運行結果:

construct student 10; //構造函數只執行一次;

fn: 10;

fn: 20;

21:成員變量和成員對象的初始化和構造順序以其在類中聲明的順序構造;不是按照構造函數冒號後面的順序執行;

22.面向對象程序設計基於兩個原則:抽象和分類;---結構化程序設計;效率的比較:

一個人體力很好:他可以騎車穿越大街小巷到達目的地,效率是比較高,但是要是兩地相隔甚遠,那麼就不那麼容易了,,就要選擇火車,飛機,只要付錢坐上去就可以了,不用考慮中間的過程,這就是面相對象;

23.程序運行的效率是指占盡可能少的資源【存儲空間】而運行的速度相對較快,不能片面的看某一方面,要綜合比較時間和空間客觀的評價一個程序運行的效率;

24.全局數據區:全局變量,靜態數據,常量;

代碼區:類的成員函數和非成員代碼;

棧區:為運行函數兒分配的局部變量,函數參數,返回數據,返回地址;

堆區:余下的空間放在堆區;

25 類中需要new和delete的原因:

因為malloc在分配空間的時候不能調用構造函數,類對象的建立是分配空間,構造結構以及初始化的三位一體,他們統一是由構造函數完成;而malloc只是獲得一個含有隨機數據的類對象空間而已;對應的對象空間中的值不確定,為此,必須在內存分配之後再進行初始化;這從根本上來說,不是類對象的創建,它繞過了構造函數;

指針對象只有在調用delete時,才調用析構函數;如果是局部對象,則在該局部對象退出作用域時【結束語句},或者return時】,自動調用析構函數;

26.在分配對象數組時,類型的後面只能跟[元素的個數],不能再跟構造函數的參數,所以,從堆上分配對象數組,只能調用默認構造函數,不能調用其他;如果該類沒有默認的構造函數則不能分配兌現該數組;

student st[4];

student *st[4];

delete[] st; //其中的[]是告訴c++這是一個數組,填上數字,編譯器會忽略;但是如果不寫,就會產生錯誤;

27.一個空類的默認的函數:構造函數,拷貝構造函數,析構函數,運算符重載函數,操作符重載函數operator=;

28.實例對象,指針對象【堆對象】,臨時對象,無名對象

class student

{

int numnber;

}

studnet fn()

{

studnet ms(200);

return ms;

}

student st(100);//實例對象

studnet *st =new studnet(100);//指針對象

student(100);//無名對象

studnet &sts=fn();//非常危險,不再有效;

29,無名對象的典型三種用法:注意初始化引用和初始化對象的區別,還有賦值和初始化的區別;

void fn(studnet &s);

int main()

{

student &refs=studnet("Jenny"); //初始化引用; 在函數內部,無名對象作為局部對象產生在棧空間中; studnet refs=Jenny;

student s=student("Jenny");//初始化對象定義; 用無名對象去拷貝構造一個對象s;studnet s=Jenny;省略創建無名對象這一步;按理說:C++先調用構造函數 “studnet (char *);”創建一個無名對象,然後再調用一個拷貝構造函數 studnet (student &)來創建對象S;但是由於是用無名對象去拷貝構造,完事兒後該無名對象就失去了意義,所以省略了創建無名對象這一步;

fn(studnet("Jenny")); //函數參數; 無名對象作為實參傳遞給形參s,先調用構造函數創建一個無名對象,然後將該無名對象初始化給引用參數s對象,有於實參是在主函數中,所以無名對象是在主函數的棧區中創建,函數fn()的形參s引用的是主函數棧中的一個對象;

student s("Jenny");

fn(s);

}

30.由於C++默認的構造函數只是對對象進行了淺拷貝復制,如果對象的數據成員包括指向對空間的指針(堆空間),就不能用這種方式,必須自己定義拷貝構造為其分配空間,也就是深拷貝,資源的拷貝;


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved