P05面向對象設計思想_第1頁
P05面向對象設計思想_第2頁
P05面向對象設計思想_第3頁
P05面向對象設計思想_第4頁
P05面向對象設計思想_第5頁
已閱讀5頁,還剩78頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

面向對象程序設計江漢大學數學與計算機科學學院韓海程序設計思想1主要的程序設計理念運算符重載——針對對象實現類似于原運算符的功能繼承與組合——如何利用已有的類建立更復雜的對象虛函數——指針指向類族中的哪個對象,就調用該對象相應的方法成員抽象類——類中只規定有哪些行為,但并不具體實現多態——讓同一種行為應用于各種對象、各種情況模板——提高編碼的效率異常處理——設置錯誤處理代碼,應對可能出現的錯誤const——保護數據,防止被意外破壞流——把不同類型的信息視為一個數據序列泛型——忽略數據元素在存儲上的差異,強調行為上的共性,強化代碼的通用性2類是規定行為的集合 基本類型 自定義類集合示例 int

CMyEmployee元素示例 15 2010053134,張三,男,1982.3.5.

運算 算術運算 規定的行為(方法)

類是基本數據類型的擴展,兩者的核心都是集合及集合上的運算,基本類型的運算表現為“+”、“-”、“*”、“/”等算術運算,是固有運算,可直接使用。定義類中包含哪些數據元素相對簡單。類的核心問題是定義類的方法成員,通過方法成員表現出類的元素(對象)具有哪些行為。3對象的屬性與操作對象的行為通常可以分為計算類和非計算類。非計算類包括顯示、存儲、傳輸等行為,比如關于圖形處理建立的區域類族大體上會有三角形、方形、多邊形等自定義類,各個對象都有在屏幕上畫出該形狀這一行為,而且通常都用同一個成員名來描述這個行為,如“對象.Draw()”。這一現象將在下一節“抽象類”當中討論。

計算類行為是指獲取對象的相關信息,或者通過對象及其它數據計算得到一些信息。比如上述關于區域的類族,各個對象通常都需要對外公布(允許外部訪問,public)寬度和高度兩個數據,這樣的信息稱為“對象的屬性”,通常也有相對固定的成員名,例如取對象的寬度通常寫作“對象.GetWidth()”。這一類行為當中包含運算符重載。4為什么要有運算符重載?起因(在上一單元已述,重現):

(1)常規的運算符只運用于基本數據類型,并且有固定的用法,基本上與數學上的用法一致。——好記

(2)類是數據與處理(稱為“方法”)的結合體,有很多處理與常規的運算有直接聯系,或者是常規運算在意思上的延伸,比如CString類的加法延伸為拼接。

——好理解

(3)設計一個用于實現加法的函數Add,帶有兩個入口參數分別記作a和b,則調用該函數寫作“Add(a,b)”,這一寫法當然不如“a+b”更簡潔易懂。——好寫目標:

希望為類設計一個與常規運算符在意義上接近的處理或者計算,并且沿用常規運算符原有的寫法5運算符重載方式重載方式用成員函數重載用友元函數重載 特 點“=”等少量的幾個運算符必須用成員函數的方式重載重載二元運算符時,右側的操作數可以是各種類型,但左側的操作數必須是類的對象成員函數的形參數目等于運算符操作數的數量減1(1)通常用全局函數實現(2)需要訪問類的私有成員時才需要定義成友元函數6不能重載的運算符 以下運算符不能被重載:

成員訪問

.

域限制符 ::

條件運算 ?: 取字節數 sizeof7經常被重載的運算符運算符 分類 重載方式+,-,*,/,% 算術運算 兩種均可>,>=,<,<=,==,!= 關系運算 兩種均可= 賦值 成員函數+=,-=,*=等 復合賦值 兩種均可,建議成員函數++,-- 自增自減 兩種均可,建議成員函數[],-> 下標,指向 成員函數<<,>> 移位/流 兩種均可new與delete 內存管理 兩種均可特別地,C++規定圓括號“()”也可以重載已述,本節將只講解++、[]和()這幾個運算符的重載8為CClock類定義前置“++”前述CClock類的聲明如下:classCClock

{public:

CClock(int

h,int

m,ints);

~CClock();

int

GetHour(); //取小時數

int

GetMinit(); //取分鐘數

CString

GetTime(); //取當前時間字符串

voidStepup(); //令時鐘走一步(1秒)

CClockoperator++();private:

int

m_hour,m_minit,m_second;//時分秒};CClock類的對象是時鐘,“++”的功能顯而易見。用成員函數為CClock類定義前置“++”,首先需要在類中添加相應的函數聲明。注意,類中已有Stepup函數可以利用9編寫“operator++”利用已有的Stepup函數,前置自增功能很容易實現:CClock

CClock::operator++(){ Stepup(); return*this;}C++以“是否有形參”來區分前置自增和后置自增,后置自增的重載函數如下:CClock

CClock::operator++(int){ CClockx(*this); //創建臨時對象

Stepup(); //即this->Stepup() returnx;}注意:(1)后置自增函數帶有一個int型形參,這是區分前置后置的標記,并無其它含義,函數體內也不使用該參數值,甚至可以沒有形參的名字;(2)不要忘記在類中添加相應的函數聲明10友元函數定義前置“++”CClock類的聲明如下:classCClock

{public:

CClock(int

h,int

m,ints);

~CClock();

int

GetHour(); //取小時數

int

GetMinit(); //取分鐘數

CString

GetTime(); //取當前時間字符串

voidStepup(); //令時鐘走一步(1秒)

friendCClock

operator++(CClock&x);private:

int

m_hour,m_minit,m_second;//時分秒};CClock

operator++(CClock

&x){

c.Stepup(); returnx;}11友元函數定義前置“++”這是前置自增的友元函數聲明:

friendCClock

operator++(CClock

&x);后置自增的友元函數聲明為:

friendCClock

operator++(CClock

&x,int);相應的函數代碼:CClock

operator++(CClock

&x,int){

CClock

m(x);//記得嗎,這將調用拷貝構造函數

c.Stepup(); returnm;}最初編寫代碼的時候忘記了引用,沒有引用是不行的12為CClock定義下標[]設x是CClock類的對象,即一個時鐘,原本x[?]是沒有意義的,但不妨人為地做如下定義:

x[0]---時鐘的當前小時數

x[1]---時鐘的當前分鐘數

x[2]---時鐘的當前秒數

x[i]----1,i不是0、1、2時根據運算符重載的有關規則,下標運算“[]”必須用成員函數實現,則在寫法上“x[i]”是函數調用“x.operator[](i)”的變形,“k=x[i]”也可以寫成:

k=x.operator[](i)13為CClock類定義“[]”在CClock類中添加關于[]的成員函數如下:classCClock{public:

CClock(int

h,int

m,ints);

~CClock();

int

GetHour(); //取小時數

int

GetMinit(); //取分鐘數

CString

GetTime(); //取當前時間字符串

voidStepup(); //令時鐘走一步(1秒)

CClockoperator[]();private:

int

m_hour,m_minit,m_second;//時分秒};14CClock類的“operator[]”int

CClock::operator[](inti){

if(i==0) returnm_hour;

if(i==1) returnm_minit;

if(i==2) returnm_second; return-1;};15重載“[]”的意義設x是一個對象,通過運算符重載,可以把原本沒有意義的寫法“x[?]”賦予確定的含義,這與前述的運算符重載有著重大差異。

既然如此,對于任意一個允許重載的運算符,也可以賦予它與原運算符完全沒有關聯的功能。但是極少有人這樣用,因為運算符重載的目的在于借用運算符原有的含義、用原有的寫法實現對象的某些處理功能,如果這些功能與被重載的運算符相去甚遠,則重載的效果只會讓人造成概念上的混亂,不利于軟件開發。正如在定義一個函數時,通常都以與函數功能相關的英文單詞或者編寫作為函數名。16CClock類的“operator()”對于對象x,運算符重賦予了“x[?]”一定的意義,對于同樣原本沒有意義的“x(?)”也可以依照此例處理。實際上,把對“[]”重載時所有的“[”換成“(”、所有的“]”換成“)”,前述代碼同樣可以編譯通過。很多資料上把“operator()”稱為函數調用運算符,這容易在概念上造成混亂。建議:不用管它叫什么名字,因為對于對象x而言,“x(?)”原本沒有意義,現在通過運算符重載規定了該寫法的含義。17繼承還是組合面向對象程序設計的基本設計單位是“類”,類的本質是規定了對象的數據信息和行為。設計更復雜的程序時,可以利用已有的類,在利用方法上就有了本頁的標題:繼承還是組合?——本節只考慮public繼承設X和Y是兩個類,x是X的對象,y是Y的對象繼承(“isa”關系)——如果X是Y的派生類,則:

xisay. (見下頁圖元類族示例)組合(“hasa”關系)——如果y是x的一個子對象,則:

xhasay.例如:CMyEmployee類中包含兩個CString子對象,

Theemployeehasanumber. Theemployeehasaname.18繼承描述“isa”關系Point(點)isaelement(圖元)Rigion(區域)isaelement(圖元)Circle(方形)isaRigion(區域)...... 圖元顏色,尺寸,邊界畫圖,擦除,取邊界點線起點,終點,線型區域填充模式,透明度直線弧線......曲線......方形......圓形......多邊形......注意,不論是繼承還是組合,在x中都包含一個y的對象作為x的一部分!繼承與組合到底有什么差別?19繼承與組合的差異xisay(繼承)(1)編寫X類的成員函數代碼時,可以訪問y的public成員和protected成員,包括方法成員也包括數據成員(2)在類的外部編寫代碼時,可以訪問x的public成員,也能訪問y的public成員(3)CX的構造函數以“:CY(...)”指明如何調用基類的構造函數xhasay(組合)(1)編寫X類的成員函數代碼時,只能訪問y的public成員,包括方法成員和數據成員(2)在類的外部編寫代碼時,只能訪問x的public成員(3)CX的構造函數以“:子對象名(...)”指明如何調用子對象的構造函數20測試1,派生類內部CY類的聲明:classCY{public:f1();

intd1;protected:f2();

intd2;private:f3();

intd3;}CX類的聲明:classCX:publicCY//繼承{public:f_x1();

intd_x1;protected:f_x2();

intd_x2;private:f_x3();

intd_x3;}編寫CX的函數代碼時,允許訪問CX和CY的哪些成員?21測試2,派生類外部CY類的聲明:classCY{public:f1();

intd1;protected:f2();

intd2;private:f3();

intd3;}CX類的聲明:classCX:publicCY//繼承{public:f_x1();

intd_x1;protected:f_x2();

intd_x2;private:f_x3();

intd_x3;}x是CX的對象,“x.?”是合法的訪問?22測試3,組合類內部CY類的聲明:classCY{public:f1();

intd1;protected:f2();

intd2;private:f3();

intd3;}編寫CX的函數代碼時,允許訪問CX和CY的哪些成員?CX類的聲明:classCX //組合{public:f_x1();CYy1;protected:f_x2();CYy2;private:f_x3();CYy3;}訪問CY的成員時,必須指明訪問哪一個子對象的成員,比如“y1.f1()”、“y2.f1()”、“y3.d1=5”23測試4,組合類外部CY類的聲明:classCY{public:f1();

intd1;protected:f2();

intd2;private:f3();

intd3;}CX類的聲明:classCX //組合{public:f_x1();CYy1;protected:f_x2();CYy2;private:f_x3();CYy3;}x是CX的對象,“x.?”是合法的訪問?x.f_x1() x.y1.f1() x.y1.d1=5;24多用組合少用繼承如本頁標題所示,在建立新的類時,多用組合少用繼承。并且把子對象置于private保護之下。原因:面向對象的核心思想之一是封裝,即允許外部訪問對象的哪些成員。以繼承的方式建立新的類,編寫代碼的人往往容易忽略可以從外部訪問基類的public成員,從而導致一些預料之外的信息暴露。當然,最根本的還是根據“isa”還是“hasa”關系來選用繼承和組合。25繼承導致類族轉換話題:面向對象的程序設計模式中經常會設計一系列有繼承與派生關系的類,從一個基類開始往下派生出的所有的類形成一個“類族”。類族的設計思想顯然是為了代碼重用。在一個類族中,對象盡管屬于不同的類,但通常都有一些相同的特征或者行為。即:類族中任意一個類的對象都擁有基類的數據成員(雖然有可能因為繼承方式而不能訪問),視作具有相同的特征信息;類族中的對象通常也具有一些同名的函數成員,視作具有相同的行為,這些函數的功能相同或相似,但實現的具體代碼可以不同。26類族對象的共性例如,以“圖元”為基類的類族具有顏色、尺寸等共同的數據成員,以及畫、擦等同名的方法成員;除此之外,以“區域”為基類的類族還具有填充模式、透明度這兩個共同的數據成員。圖元顏色,尺寸,邊界畫圖,擦除,取邊界點線起點,終點,線型區域填充模式,透明度直線弧線......曲線......方形......圓形......多邊形......27對圖元類族確定名稱為了后續敘述的方便,為各個類及各個成員命名CElementcolor,size,borderDraw,Erase,...CPointCSegmentstart,end,styleCAreapattern,transparencyCLineCArc......CCurve......CSquare......CCircle......CPolygon......28創建對象與調用方法設有如下的創建對象:

CElementx1; //圖元

CArea x2; //區域

CSegmentx3; //線

CCirclex4; //圓則,以下調用非常明確是調用哪個對象的哪個方法:

x1.Draw(); x2.Draw(); x3.Draw(); x4.Draw();原本沒有疑問的用法會因為指針而產生歧義,見下頁。29重要規則C++規定:

派生類的地址可以賦值給指向基類的指針變量你如何理解這個規則?請先看下面的例子為了說明問題的簡便,改用下面的最簡單的類聲明:classCA{public: voidfun() {cout<<"FunofCA\n";};};classCB:publicCA{public: voidfun() {cout<<"FunofCB\n";}; voidfb(){cout<<"Thisisfb\n";};};記住各函數的顯示效果!30類族中的指針指向指出下面代碼中的錯誤:main(){ CAx,*p; CBy,*q; p=&y; q=&x; p->fun(); p->fb(); q->fun();}派生類地址可以賦值給指向基類的指針變量,反之不行這一行是對的,不用寫“p=(CA*)&y;”p的類型決定了通過p只能訪問CA的成員,fb是CB的成員但不是CA的成員31指針指向類族中的對象指出下面代碼中的運行結果:main(){ CAx,*q; CBy;

x.fun(); q=&x; q->fun();

y.fun(); q=&y; q->fun();}最后一行顯示值得探討:q是指向基類CA的指針變量,但此時指向派生類對象y,“q->fun()”到底調用哪個函數?另外,編程者希望它調用哪個函數?32再談規則上述規則的意義:(1)因為代碼重用而產生了類族,類族中的對象都有相同或者相似的行為。在編程者看來,類族中的對象都是“差不多”的。(2)編程者希望借用一個指針變量,不論該指針指向類族中的哪一個對象,都能正確地訪問相應的成員。如果派生類對基類的某個方法成員編寫了新代碼(覆蓋),則希望指針指向派生類對象時,能夠訪問新代碼。(3)以“基類*q”定義指針變量,則通過q只能訪問基類的成員,而不能訪問派生類新增的成員,從而保證不會出現訪問一個不存在的成員的現象。派生類地址可以賦值給指向基類的指針變量,反之不行前述示例說明沒能做到這一點33虛函數虛函數專門用于解決上述問題:如前例,基類中定義了方法成員fun,派生類更新了該方法。以“CA*q;”定義指針變量,希望當q指向基類CA的對象時,“q->fun()”訪問基類CA的成員函數fun;當q指向派生類CB的對象時,“q->fun()”訪問派生類CB更新之后的成員函數fun。簡言之:希望q指向誰就調用誰的成員函數對于上述需求,需要在基類中把該方法成員定義成“虛函數”。聲明虛函數的格式如下:

virtual

返回值類型函數名(形參表);34聲明虛函數下面是定義CA和CB兩個類,在適當的位置加上虛函數標記virtual:classCA{public: voidfun() {cout<<"FunofCA\n";};};classCB:publicCA{public: voidfun() {cout<<"FunofCB\n";}; voidfb(){cout<<"Thisisfb\n";};};virtual35有關虛函數的說明類的靜態成員函數和內聯函數不能聲明為虛函數定義成員函數為虛函數,并不代表該函數是“虛”的,而是為了通過指針變量能夠訪問正確的方法成員,通過對象名訪問成員與虛函數無關一旦在一個類中定義了虛函數,則以該類為起點的類族中該函數都是虛函數,派生類中的相應函數不再需要用virtual說明構造函數不能聲明為虛函數,析構函數往往聲明為虛函數基類中聲明虛函數時必須明確形參,派生類中相應函數不僅要同名,也要求形參相同36測試classCX{public:virtualint

fa();

int

fb();}classCY:publicCX{public:virtualint

fb();}classCZ:publicCY{public:virtualint

fa(intn);

int

fb(intn);}classCW:publicCZ{public:virtualint

fa();

int

fb();}說明各個類的對象能訪問哪些函數成員?哪些類的哪些函數構成一組虛函數畫出層次結構圖37虛函數的實現原理面向對象程序設計允許函數重載(overload)與函數覆蓋(override),前者導致函數同名但形參不同,后者則是同名同形參。前面已有若干示例說明后者的有關規則,核心問題是當代碼中出現一個函數調用時,究竟是調用哪一段具體的函數代碼。解決問題的方法是對虛函數采用動態聯編方式。動態聯編——也稱動態綁定,是與靜態聯編相對而言的。當類族中含有虛函數時,為類族的每個對象安排虛函數表(是各個虛函數入口地址的列表,與數據成員安排在一起)。對于通過指向基類的指針訪問函數成員的情況,編譯時處理成“從虛函數表中找函數的入口地址”。38設計圖元類族的方法成員Q:請設計CElement類的Draw方法的功能及代碼CElementcolor,size,borderDraw,Erase,...CPointCSegmentstart,end,styleCAreapattern,transparencyCLineCArc......CCurve......CSquare......CCircle......CPolygon......Draw的功能好說,就是在指定位置畫出這個圖元。代碼沒法寫,因為在CElement類中并不知道圖元的具體情況。如果真的要寫代碼,只能是空代碼,什么也不做!39純虛函數與抽象類采用繼承與派生的層次式設計構建一個類族時,越上層的類就越抽象,越下層的類就越具體。有時上層的方法根本無法明確行為,比如“圖元”的Draw

在C++中,允許一個類只規定方法成員的首部而不編寫函數體,這樣的方法稱為“純虛函數”,含有純虛函數的類稱為“抽象類”。純虛函數是在類聲明中用如下形式定義函數成員:

virtual函數值類型函數名(形參表)=0;注意,上述格式中的“=0”表示該函數是純虛函數。雖然在語法上virtual不是必須的,但一般只有類族中基類的虛函數才設計成不編寫函數體。40抽象類的作用抽象類中至少含有一個純虛函數,試圖調用純虛函數將導致沒有相應的代碼可以執行,所以,不允許創建抽象類的對象!抽象類的作用表現在規定由此向下的一個類族(或者類族分支)中有哪些共同的方法成員,并統一這些方法成員的函數名稱及參數形式。比如,關于“圖元”的類族中,“圖元”定義方法成員“Draw”,“線”定義方法成員“GetLength”,區域定義方法成員“GetArea”,這些方法都無法編寫代碼,但在相應的類族中有相同的名稱,并且有相同的形參列表。41什么是多態多態(Polymorphism)——簡言之,“一個接口,多種實現”接口,是指函數,包括普通函數和成員函數一個接口,是指同名函數,顯然不是一個函數而是一組函數多種實現,是指同一個函數名之下有多種不同的代碼以應對不同的參數、對象、環境等因素接口,泛指實體與外界聯系的方式、通道。對象與外界聯系的方式可以有數據成員和函數成員,但“封裝”導致對于多數類而言,外界不能直接訪問對象的數據成員,只能通過類所提供的方法成員獲取信息,或者令對象產生相應的行為。所以,面向對象中的“接口”通常指成員函數。42多態的種類有資料把多態劃分為通用多態和特定多態,前者包括參數多態和包含多態,后者包括重載多態和強制多態,但這樣的劃分值得商榷,原因見下頁1.參數多態——由函數模板產生的模板函數(同名函數處理不同類型的數據),由類模板產生的模板類,除了數據類型不同,其它部分相同2.包含多態——類族中同名成員函數,在指針指向某對象時,動態綁定相應的函數成員3.重載多態——函數重載、運算符重載,以同名函數處理不同數量、不同類型的數據4.強制多態——某些運算符具有自動數據類型轉換功能,如“int+double”時,先把int轉換成double,再進行加法運算多態的四種形式43歸并一下多態的種類多態的作用在于以相同或者相似的形式處理不同類型、不同數量的數據,或者處理不同類的對象。分析前述劃分,可以再歸并一下,得到三個種類:1.函數重載(overload),以同一范圍內的一組同名函數應對不同數量、類型的數據2.函數覆蓋(override),以類族中的同名函數應對類族中的各個對象3.自動轉換(含運算符重載),基本運算符應對不同類型的數據/對象只有類模板不在此列。先看看什么是類模板,什么是模板類,似曾相識的概念。44函數模板:由template開始編寫的一段函數定義,表示一組函數,除了類型標記不同、其它部分都相同模板函數:由函數模板生成的函數函數模板并不是函數定義,為什么可以直接調用? 因為由函數模板可以生成相應的函數定義由誰來生成函數定義代碼?在什么時候生成? 編譯器在首次遇到對模板函數調用的時候生成生成函數代碼時其中未定的類型怎么處理? 根據調用時參數的類型將相應的類型標識符代替模板中的類型標記復習:函數模板與模板函數函數模板:模板函數:函數模板并不是函數定義,為什么可以直接調用?由誰來生成函數定義代碼?在什么時候生成?生成函數代碼時其中未定的類型怎么處理?45類模板:由template開始編寫的一段類聲明(含其中的成員函數代碼),其中除了含有特定的類型標記,表示一組類聲明,除了類型標記不同、其它部分都相同模板類:由類模板生成的類類模板并不是類,為什么可以直接定義它的對象? 因為由類模板可以生成相應的類定義,即類聲明由誰來生成類聲明的代碼?在什么時候生成? 編譯器在首次遇到使用模板類的時候生成生成類聲明的代碼時其中未定的類型怎么處理? 使用模板類時需要指明類模板中的類型標記用什么具體的類型標識符代替類模板與模板類46一個簡單的類模板先來個最簡單的。如果想根據下面的類來定義一個類模板,讓其中的int可以是各種數據類型:

classCA { public: CA(int

n){mm=n;}; voidf(){cout<<mm<<endl;};

intmm; };template<typename

T>TTCA<T>如果想在類聲明之后再寫函數體,怎么辦?47template<typenameT>classCA{public: CA<T>(Tn){mm=n;}; voidf(){cout<<mm<<endl;}; Tmm;};類模板后面寫成員函數代碼template<typenameT>voidCA<T>::f(){cout<<mm<<endl;}不就是把這個函數體移到后面去嗎?;48template<typenameT>classCA{public: CA<T>(Tn){mm=n;}; voidf(); Tmm; //演示!}; template<typenameS>voidCA<S>::f(){ cout<<mm<<endl;}voidmain(){ CA<int>x(3);

x.f(); CA<double>y(4.567);

y.f();}看一看效果49含雙參數的類模板如果類模板中需要兩個不同類型的參數,怎么辦?classCA{public:CA(inta,doubleb){mm=a;nn=b;};voidf(){cout<<mm<<','<<nn<<endl;};

intmm;

double

nn;};template<typename

S,typename

T>SSTTCA<S,T>把這個函數體移出去也不難;template<typename

S,typenameT>voidCA<S,T>::f(){cout<<mm<<','<<nn<<endl;}50template<typename

S,typename

T>classCA{public: CA<S,T>(Sa,T

b){mm=a;nn=b;}; voidf();private: Smm; Tnn; };template<typename

X,typename

Y>voidCA<X,Y>::f(){ cout<<mm<<'('<<sizeof(mm)<<")\n";

cout<<nn<<'('<<sizeof(nn)<<")\n";}voidmain(){ CA<char,int>t1('1',2); CA<double,int>t2(3.45,6); CA<int,double>t3(7,8.9); t1.f(); t2.f(); t3.f();}再看看效果51類模板只聲明了生成若干個類的可能性,只有在編譯器遇到對類的實際使用時(比如定義類的對象),才會生成相應的類——稱為類模板的實例化類模板實例化時必須顯式指明模板所含參數的類型類模板中除了在class后面首次出現的類名之外,其它用到類名的時候都要寫“類名<參數表>”的完整寫法,但構造函數、析構函數則可以省略為只用“類名”在類聲明之后編寫成員函數代碼時,需要重新寫template及參數,參數的數量必須相同,參數名稱可變,而且類限制符“::”前面的類名必須用完整寫法類模板規則要點52針對下面的要求設計類模板CArray:能夠存放一批數據成員函數GetData能夠根據輸入情況確定存放多少個數據,并從鍵盤上讀取這一批數據成員函數Display能夠顯示當前存放的數據成員函數Sort能夠對當前存放的數據排序(升序)為了測試,安排主函數如下,并要求替換其中的double為int、float、char等常用類型多次測試

voidmain() {CArray<double>x;

x.GetData();

x.Display();

x.Sort();

x.Display(); }復雜一點的類模板53template<typenameT>classCArray{public:

CArray(){m_arr=NULL;m_count=0;};

~CArray(){if(m_arr)delete[]m_arr;};

int

GetData(); voidDisplay(); voidSort();private: T*m_arr; //根據需求申請存儲空間

int

m_count;};定義類模板54template<typenameT>int

CArray<T>::GetData(){ int

i,n;

cout<<"Howmanynumbers:";

cin>>n;

if(n<=0) return0;//沒有數據需要存儲

if(m_arr) delete[]m_arr;//刪除原數據

m_arr=newT[n];

if(!m_arr) return-1;//申請內存失敗

m_count=n;

for(i=0;i<n;i++) { cout<<"X["<<i<<"]=";//操作提示

cin>>m_arr[i]; } returnn;}編寫GetData55template<typenameT>voidCArray<T>::Display(){ inti;

if(m_count<=0)

cout<<"Arrayisempty.\n"; else {cout<<m_count<<"numbersinArray.\n";

for(i=0;i<m_count;i++) { cout.width(8);

cout<<m_arr[i]; }

cout<<"\n\n"; }}編寫Display56template<typenameT>voidCArray<T>::Sort(){ int

i,j; Tt;

for(i=1;i<m_count;i++)

for(j=0;j<m_count-i;j++)

if(m_arr[j]>m_arr[j+1]) { t=m_arr[j];

m_arr[j]=m_arr[j+1]; m_arr[j+1]=t; }}//終于編寫完了,測試一下吧編寫Sort57高手編的軟件不會死下面是一個常見現象:通常,要么等待數秒之后軟件被關閉,要么就這么一直等下去。如何看待這一現象?58軟件中的BUG什么原因造成了軟件崩潰?

(1)非法命令 (2)錯誤的語序例,指出以下代碼中存在的問題或者錯誤:voidmain(){int

i,n;double*x;x=newdouble[n];

cin>>n;

for(i=n;i>0;i--){ x[i]=i/(i-1); out<<i<<','<<x[i]<<endl;}}有借無還對不存在的變量賦值除以0次序錯誤還有兩個可能的問題:輸入到n的值不合適,比如負數;用new申請內存空間可能失敗59錯誤種類這里的“非法命令”不是指語法錯誤(語法錯誤由編譯器處理),而是指命令計算機執行一個不正確的操作,包括以下幾種情況:非法訪問內存。比如前例中對x[n]賦值非法操作。比如前例中可能存在的除以0非法訪問硬件。連接到計算機的各種設備各有各的使用特點,有些只讀,有些只寫,有些要求先做A再做B,等等“非法語序”往往會造成變量中存儲的數據不正確,從而使得后續語句達不到預期的執行效果60處理可能的錯誤代碼中不允許出現語法錯誤和邏輯錯誤,比如前例中對x[n]賦值,這是編碼、調試、測試階段必須解決的對可能出現問題的命令要有應對措施,例如:

cin>>n;

if(n<MIN||n>MAX)cout<<"..."; else {x=newdouble[n];

if(x==NULL)cout<<"..."; else

for(i=n-1;i>=0;i--) {x[i]=...; out<<i<<','<<x[i]<<endl; } }61函數調用造成的困難func_A(){......}func_B(){......

func_A();......}func_C(){......

func_B();......}main(){......

func_C();......}一旦最底層函數調用時出了問題,比如問題在func_A中,通常需要以函數值的方式逐層“上報”,在某一層決定最終如何處理這個問題62異常處理機制出現問題逐級上報的處理方法代碼較大,異常處理機制是解決該問題的另一種思路:不論哪一級出現問題,只需要“報告一下”(不一定是對直接上級),稱為“拋出異常”。在某個函數中設置一段代碼來統一處理這些報告。統一處理異常由try和一組catch構成,格式見下頁;拋出異常則相對簡單:

throw(參數);其中,表示異常情況的參數可以是任何類型的數據,也可以是某個類的對象,用途是向處理機制報告出現了什么樣的異常。63處理異常的編寫格式處理異常的代碼部分由兩段構成:try{可能拋出異常的代碼}catch(形參A){處理A}catch(形參B){處理B}catch(形參C){處理C}......這一段可能有問題,沒關系,做就是了,有問題就報告根據不同的錯誤報告采取相應的處理措施工作方式:在執行try后面的代碼的過程中,只要有異常拋出,立即轉到catch部分,根據拋出的異常的類型執行對應的處理代碼,如果沒有對應的代碼則產生運行錯誤。處理結束后執行catch后面的語句64仍然可能造成軟件崩潰voidfunca(){intn;

cout<<"InputinA:";

cin>>n;

if(n<0)throw("A");}voidfuncb(){doublex;

funca();

cout<<"InputinB:";

cin>>x;

if(x<0)throw(x);elseif(x==0)

throw("B");

funca();}voidmain(){try{ funcb(); }

catch(char*x){cout<<x<<endl;}

catch(intx){ cout<<"x="<<x<<endl;}

cout<<"==========\n";}看看以上代碼在輸入不同的數據時的運行結果65異常處理不是萬能的異常處理機制僅僅是把編寫代碼時在各個函數中對出現的問題進行處理改為在一個函數中統一處理,如果代碼中出現非法訪問內存、除以0等現象仍將導致軟件崩潰,另一種導致崩潰的原因是對拋出的異常沒有安排相應的處理代碼。66防止意外修改數據被意外修改是另一個安全隱患。以編寫函數為例,為了減少參數傳遞的開銷,有時以引用作為形參,但這樣做的后果是函數內部對形參的修改將直接導致實際數據的改變。例如:doublefunc(double

&x){if(x>0) x=(int)(x*100+0.5)*0.01;else x=(int)(x*100-0.5)*0.01;returnx;}沒有“&”是常規用法。如果用引用作為形參,在完成求值計算的同時會改變實際參數的值。這樣的修改往往并非有意為之,而是一種疏忽。67const有關規則A用const可以限制數據被修改,前述“常變量”是const的最初應用。const的主要用法:(1)定義普通變量,限定不允許改變變量的值,即前述“常變量”:

const類型變量名=初值;或者 類型const變量名=初值;(2)定義普通對象,限定不允許訪問對象的任何成員,不論是public、protected還是private:

const類對象名(初值);或者 類const對象名(初值);如此定義的對象又稱“常對象”,用作初始化其它對象、給其它對象賦值等68const有關規則B(3)const用于定義指針變量時有所不同:① const類型*變量名=初值;不允許改變指針所指變量的值,如果指針指向對象,也不允許訪問對象的任何成員,但可以令指針變量指向其它位置② 類型*const變量名=初值;不允許改變指針變量所指位置,但可以改變指針所指變量的值,如果指針指向對象,允許訪問對象的public成員③ const

類型*const變量名=初值;兩者都不允許69const有關規則C(4)const用于定義引用:

const類型&引用名=目標;不允許通過引用改變目標的值,如果是對象的引用,也不允許通過引用訪問對象的任何成員(5)const用于函數形參這種用法除了是在參數傳遞時確定初值之外,與前述相應各種情況的規則對應相同70什么是“流”這里所說的“流”是以字節為單位進行串行數據傳輸的形式。計算機中還有一種是以“位”為單位的流,稱“比特流”。對于一般的傳輸,總是需要有建立連接、傳輸、撤除連接三個步驟,這里的“流”加上一個特點:單向。對于面向對象的編程而言,最常見的“流”是文件和標準輸入輸出設備。71“流”在文件上的應用“文件”是存放在外存上的數據集合。最早的“文件”完全按照“流”的方式設計:(1)創建文件/打開文件。建立內存與文件的連接,并且要規定數據傳輸方向,因此有“為讀打開”、“為寫打開”等等。(2)讀/寫文件。在最早的文件系統中,讀和寫是分離的,“為讀打開”的文件就只能讀,“為寫打開”的文件就只能寫。以寫為例,先在內存中把數據準備好,即內存緩沖區,然后把這些數據一字節一字節地送到文件中存放。(3)關閉文件。撤除第(1)步建立的連接。現在的文件系統除了允許同時為“讀”和“寫”建立一個連接之外,其它方面沒有改變。72cin與cout

cin是標準輸入設備流,一般直接對應鍵盤,流的方向是由鍵盤向內存;cout是標準輸出設備流,一般直接對應顯示器,流的方向是由內存向顯示器。計算機開機后,由系統建立cin、cout和內存的連接,并且始終處于連接狀態,使用完畢后不需要也不能關閉。73cin、cout與類型轉換

cin和cout都按“文本形式”工作,并帶有數據類型轉換功能。以cout為例,cout<<x,不論變量x是int、char、double等哪一種類型,cout都能夠把數據的內存形式轉換成文本的ASCII形式,再逐個符號地傳送到顯示器上。

cin和cout具有可擴展性、對于類、結構體等不是基本數據類型的情況,可以通過對“<<”和“>>”進行運算符重載,使得cin、cout支持自定義類型。當然,類通常會設計輸入、輸出對應的方法成員完成相應的工作。74“在一批數據中找一個滿足的”,這是非常常用的操作,可以設計出針對各種類型的數組進行查找的函數模板:

template<typenameT>

int

Find(Tx[],intn,Tkey) { inti;

for(

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論