版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
PARTIII
面
向
對
象
的
程
序
設
計引
言概
要
回
顧基于過程的程序設計基于對象的程序設計面向對象的程序設計程序的主體:函數類的生成、設計類對象的使用C++過程化語言基礎程序的主體:類問
題
的
回顧俄羅斯方塊的設計與實現(xiàn)基于過程的設計與實現(xiàn)基于對象的設計與實現(xiàn)方塊對象:七種方塊對象圖框對象:負責繪制俄羅斯方塊運行的圖框界面判定對象:負責消行、統(tǒng)計分數、方塊到頂游戲終結基
于
對
象
的
解
決
方
案基
于
對
象
的
解
決
方
案
分
析七種方塊:公有屬性:顏色、速度、……不同屬性:形狀、……共同操作:右移、左移、下移、變形各種操作由于形狀的不同而有不同的實現(xiàn)這7種方塊的移動和變形的實現(xiàn)方法不同,顯示也不同,因此不能用同一個類來描述設計7個類分別描述7種方塊7個方塊類中又有很多相同的,如顏色等數據屬性,都要四種移動操作,只是各自的實現(xiàn)方法有所不同問題1:共同的數據屬性在7個類中各自描述一次,顯得繁瑣,重復,能夠簡化為統(tǒng)一描述?問題2:相同的操作能否在統(tǒng)一描述的同時方便地根據方塊類型調用不同的實現(xiàn)?
——繼承和多態(tài)結
論僅僅支持基于類的設計、生成和類對象的使用是不能稱為面向對象的程序設計面向對象程序設計的四大主要特點抽象封裝繼承多態(tài)基于對象的程序設計面向對象的程序設計PARTIII綱
要010203繼承與派生(重點)多態(tài)性與虛函數(重點)輸入輸出流(常用)04C++工具繼
承
與
派
生問
題
回
顧
與
分
析共同的數據屬性在7個方塊類中各自描述一次,顯得繁瑣,重復,能夠簡化為統(tǒng)一描述?分析先統(tǒng)一描述所有方塊的共性以及對全體方塊的處理功能;描述某一種方塊時,首先說明它是其中之一,然后再逐一描述這種方塊的個性——面向對象的程序設計中類的繼承與派生繼
承
與
派
生類
的
繼
承派生類可以獲得基類的已有特性派生類繼承了基類的所有數據成員和成員函數類的繼承是層次結構的基類是派生類的抽象類的派生基類產生了一個和具有基類各種特性的新的子類派生類可以對成員作必要的增加和調整派生類又可以作為基類再派生出新的派生類派生類是基類的具體化繼承派生在一個已經存在的類(基類/父類)的基礎之上建立一個新的類(派生類/子類)繼
承
與
派
生
的
基
本
形
式單
繼
承
和
多
重
繼
承單繼承一個派生類只能由一個基類派生而來一個子類只有一個父類多重繼承一個派生類由多個(>=2)基類派生而成一個子類有兩個或者兩個以上的父類派
生
類
的
聲
明一般形式class<派生類名>:<繼承方式><基類名>{<派生類新定義成員>};派
生
類
實
例基類(普通學生類)classstudent{private:intID;char*name;floatscore[11];public:student(intn1,char*nm1){……}~student(){deletename;}voidprint(){…..}voidget_name(){……}……};派生類聲明(大學生類)classundergstudent
:publicstudent{private:char*major;//新增加的數據成員floatscore[40];public:……voidget_data{……}print_major(){……}//新增加的成員函數voidprint(){……}}基類(普通學生類)派生類聲明(大學生類)大學生類(未考慮訪問屬性)
intID;char*name;
floatscore[40];
char*major;
print_major(){……}
voidprint(){…..}…….派
生
類
的
構
成兩大部分從基類繼承來的成員聲明派生類時增加的部分每一部分都分別包括數據成員和成員函數構成方式并非基類的成員和派生類增加成員的簡單加和接收基類的全部成員調整從基類接收的成員聲明派生類時增加的成員根據需要增加成員定義派生類的構造函數和析構函數(不能從基類繼承而來)基
類
成
員
的
接
收
和
調
整無條件地全部接收不接收析構函數和構造函數接收到的基類成員可以調整通過指定繼承方式改變成員的訪問屬性可以聲明同名成員加以覆蓋派
生
類
的
繼
承
方
式三種繼承方式publicprivateprotected不同的繼承方式決定了基類成員在派生類中訪問屬性派
生
類
成
員
的
訪
問
屬
性分情況處理基類的成員函數訪問基類成員
?派生類成員函數訪問派生類自己增加的成員
?基類的成員函數訪問派生類的成員
?派生類外訪問派生類的成員根據成員的訪問屬性判定能否由類外訪問該成員派生類的成員函數訪問基類的成員派生類外訪問基類的成員核心問題:如何確定基類成員在派生類中的訪問屬性基類成員聲明的訪問屬性派生類對基類的繼承方式三
種
繼
承
方
式
下
派
生
類
中
基
類
成
員
的
訪
問
控
制
權
限
繼承
方式基類成員公有繼承私有繼承保護繼承公有成員公有私有保護私有成員派生類成員不可訪問派生類成員不可訪問派生類成員不可訪問保護成員保護私有保護公
有
繼
承基類的私有成員并沒有成為派生類的私有成員基類的私有成員僅僅只有基類的成員函數才能應用基類的私有成員是派生類的不可訪問的成員基類的私有成員只能通過基類的公有成員函數加以訪問實例:基類student派生類undergstudent(公有繼承)假設一創(chuàng)建一個對象freshman是一個大一新生(Bob,10009,CS,……)如何能夠正確獲取name、ID、major等信息?方
案
一主函數調用freshman的公有成員函數get_data派生類中get_data如下定義:cout<<name;cout<<ID;cout<<major;原因:name和ID是基類的私有成員,在派生類中不可訪問方
案
二基類中公有成員函數get_namecout<<name;基類中公有成員函數get_IDcout<<ID;派生類中公有成員函數get_majorcout<<major;主函數中分別調用freshman的get_name、get_ID和get_major完成信息的獲取公有繼承下,基類的公有成員在派生類中仍為公有,可在派生類外訪問方
案
三主函數調用freshman的公有成員函數get_data派生類中get_data如下定義:get_name;//調用基類的成員函數get_ID;//調用基類的成員函數cout<<major;基類中公有成員函數get_name定義如下:cout<<name;基類中公有成員函數get_ID定義如下:cout<<ID;公
有
繼
承
的
特
點
和
延
伸特點:較好地保留了基類的特征幾乎具有基類的全部功能公有繼承方式下的公有派生類是基類的子類型基類和公有派生類之間的類型轉換回顧:不同數據類型之間在一定條件下可以進行類型的轉換——賦值兼容example:整型數據可以復制給雙精度型的變量基類對象和子類(公有派生類)之間也有賦值兼容關系基
類
和
子
類
間
的
類
型
轉
換派生類對象向基類對象賦值賦值時舍棄派生類自己的成員賦值后基類對象的成員并未擴充//set_major是undergstudent的公有成員函數undergstudentugS1(Bob,10009,CS,….);studentS1;S1=ugS1;//?S1.set_major(“EE”);//?ugS1.set_major(“EE”);//?不能用基類對象對子類對象賦值同一基類的不同子類對象之間不能賦值派生類對象可以向基類對象的引用進行賦值或者初始化回顧:變量x的引用是變量x的別名,和變量x共享同一存儲空間studentS1;undergstudentugS1(Bob,10009,CS,….);student&S1_ref=S1;S1_ref=ugS1;//?,用子類對象ugS1對S1_ref進行賦值student&S2_ref=ugS1;/*?,定義了一個student對象的應用S2_ref,并用ugS1進行初始化*/S1_ref.set_major(“EE”);//?S1_ref和S2_ref仍然只是基類對象的引用,并不是ugS1(子類對象)的別名,S1_ref、S2_ref和ugS1只共享了ugS1中屬于基類部分的存儲空間如果函數的參數是基類對象或者基類對象的引用,相應的實參可以用子類對象參數傳遞時完成自動類型轉換派生類對象的地址可以賦給指向基類對象的指針變量指向基類對象的指針變量也可以指向派生類對象指向基類對象的指針變量只能訪問派生類中的基類成員,而不能訪問派生類增加的成員studentS1;undergstudentugS1(Bob,10009,CS,….);student*S_ptr=&S1;S_ptr=&ugS1;/*?,子類對象ugS1的地址可以賦值給指向基類對象的指針變量*/S_ptr->set_major(“EE”);//*?不能訪問派生類增加的成員私有繼承私有基類的公有成員和保護成員相當于派生類中的私有成員派生類的成員可以訪問私有基類的公有成員和保護成員,但是派生類外不能訪問實例:情況與前述相同,唯一的區(qū)別是派生類undergstudent以私有繼承的方式由student類派生而成方
案
一主函數調用freshman的公有成員函數get_data派生類中get_data如下定義:cout<<name;cout<<ID;cout<<major;原因:name和ID是基類的私有成員,公有繼承下在派生類中已不可訪問,私有繼承下依然不可行方
案
二基類中公有成員函數get_namecout<<name;基類中公有成員函數get_IDcout<<ID;派生類中公有成員函數get_majorcout<<major;主函數中分別調用freshman的get_name、get_ID和get_major完成信息的獲取原因:私有繼承下,基類的公有成員等同于派生類的私有成員,無法在派生類外訪問方
案
三主函數調用freshman的公有成員函數get_data派生類中get_data如下定義:get_name;//調用基類的成員函數get_ID;//調用基類的成員函數cout<<major;基類中公有成員函數get_name定義如下:cout<<name;基類中公有成員函數get_ID定義如下:cout<<ID;保
護
繼
承保護成員受保護的成員不能為類外訪問,但是可以被派生類的成員函數所引用基類(普通學生類)classstudent{private:intID;char*name;floatscore[11];public:……};派生類聲明(大學生類)classundergstudent
:publicstudent{private:char*major;//新增加的數據成員floatscore[40];public:……}classstudent{protected:intID;char*name;floatscore[11];public:……};保
護繼承保護基類的公有成員和保護成員相當于派生類中的保護成員派生類的成員可以訪問保護基類的公有成員和保護成員,但是派生類外不能訪問實例:情況與前述相同,但是派生類undergstudent以保護繼承的方式由student類派生而成第
一
種
情
形(ID和name為私有數據)基類(普通學生類)classstudent{private:intID;char*name;floatscore[11];public:student(intn1,char*nm1){……}~student(){deletename;}voidprint(){…..}voidget_name(){……}……};派生類聲明(大學生類)classundergstudent
:protectedstudent{private:char*major;//新增加的數據成員floatscore[40];public:……voidget_data{……}print_major(){……}//新增加的成員函數voidprint(){……}}方
案
一主函數調用freshman的公有成員函數get_data派生類中get_data如下定義:cout<<name;cout<<ID;cout<<major;原因:name和ID是基類的私有成員,保護繼承下在派生類中依然不可訪問方
案
二基類中公有成員函數get_namecout<<name;基類中公有成員函數get_IDcout<<ID;派生類中公有成員函數get_majorcout<<major;主函數中分別調用freshman的get_name、get_ID和get_major完成信息的獲取原因:保護繼承下,基類的公有成員等同于派生類的保護成員,無法在派生類外訪問方
案
三主函數調用freshman的公有成員函數get_data派生類中get_data如下定義:get_name;//調用基類的成員函數get_ID;//調用基類的成員函數cout<<major;基類中公有成員函數get_name定義如下:cout<<name;基類中公有成員函數get_ID定義如下:cout<<ID;第二種情形
(ID和name為保護成員數據)基類(普通學生類)classstudent{protected:
intID;char*name;private:floatscore[11];public:student(intn1,char*nm1){……}~student(){deletename;}voidprint(){…..}voidget_name(){……}……};派生類聲明(大學生類)classundergstudent
:protectedstudent{private:char*major;//新增加的數據成員floatscore[40];public:……voidget_data{……}print_major(){……}//新增加的成員函數voidprint(){……}}方
案
一主函數調用freshman的公有成員函數get_data派生類中get_data如下定義:cout<<name;cout<<ID;cout<<major;原因:name和ID是基類的保護成員,公有繼承下和保護繼承下派生類中可以訪問方
案
二基類中公有成員函數get_namecout<<name;基類中公有成員函數get_IDcout<<ID;派生類中公有成員函數get_majorcout<<major;主函數中分別調用freshman的get_name、get_ID和get_major完成信息的獲取原因:保護繼承下,基類的公有成員等同于派生類的保護成員,無法在派生類外訪問方
案
三主函數調用freshman的公有成員函數get_data派生類中get_data如下定義:get_name;//調用基類的成員函數get_ID;//調用基類的成員函數cout<<major;基類中公有成員函數get_name定義如下:cout<<name;基類中公有成員函數get_ID定義如下:cout<<ID;保
護
成
員
和
保
護
繼
承
的
效
用在類的層次繼承結構中找到數據共享與成員隱蔽之間的最佳均衡如果需要在派生類中引用基類的某些成員,應當將基類中的這些成員聲明為protected,而非private以student類中的ID和name為例如果聲明成為private,將造成派生類無法訪問的局面,使得派生類的使用很不方便如果聲明成為public,使得基類的數據隱蔽性受到一定的損傷如果聲明成為protected,派生類可以訪問從而使得基類和派生類之間可以實現(xiàn)數據共享,基類和派生類的外部都不能訪問,保障了數據的隱蔽性多
級
派
生
下
的
訪
問
屬
性類的層次繼承結構導致了類的多級派生基類基類的派生類派生類的派生類……直接派生類vs.間接派生類直接基類vs.間接基類多級派生情況下各成員的訪問屬性在基類和其直接派生類之間按照一級派生訪問屬性的原則確定各成員的訪問屬性后逐級向下遷移派
生
類
的
構
造
函
數回顧:構造函數的作用對類中的數據成員進行初始化派生類的構造函數的特殊性派生類的數據成員構成較為特殊從基類中接收來的數據成員派生類自己增加的數據成員派生類并沒有繼承基類的構造函數派生類的構造函數的初始化工作基類數據成員的初始化工作
派生類增加的數據成員的初始化工作派
生
類
實
例基類(普通學生類)classstudent{private:intID;char*name;floatscore[11];public:student(intn1,char*nm1){……}~student(){deletename;}voidprint(){…..}voidget_name(){……}……};派生類聲明(大學生類)classundergstudent
:publicstudent{private:char*major;//新增加的數據成員floatscore[40];public:……voidget_data{……}print_major(){……}//新增加的成員函數voidprint(){……}}實
例:大學生類undergstudent的構造函數方案一undergstudent(intn,stringstu_name,stringstu_major,float*scoreList){ID=n;name=stu_name;major=stu_major;score=scoreList;}派生類無法訪問基類的私有成員數據方案二undergstudent(intn,stringstu_name,stringstu_major,float*scoreList){student(n,stu_name);//調用基類構造函數major=stu_major;score=scoreList;}不能在派生類構造函數體中顯式調用構造函數!雖然能通過編譯,但結果不對派
生
類
構
造
函
數
的
解
決
之
道方案三undergstudent(intn,stringstu_name,stringstu_major,float*scoreList):student(n,stu_name);//調用基類構造函數{/*派生類的函數體中只對派生類新增的數據成員進行初始化*/major=stu_major;score=scoreList;}解決的辦法是通過成員初始化表來完成基類數據成員的初始化,在成員初始化表中可以顯式調用基類構造函數派生類的函數體完成新增數據成員的初始化派
生
類
構
造
函
數
的
基
本
形
式<派生類名>(<總參數表>):<基類構造函數名>(<參數表1>){<派生類數據成員的初始化>};說明:1、如果派生類構造函數在類的外面定義,類體中只需要寫這個函數的聲明:<派生類名>(<總參數表>);2、總參數表中的參數包括了基類構造函數所需的參數和派生類新增數據成員初始化所需的參數
生成一個類對象(10009,bob,CS,……)
undergstudent(n,stu_name,stu_major,scorelist):student(n,stu_name)構造函數的成員初始化表有
子
對
象
的
派
生
類
的
構
造
函
數子對象類的數據成員本身就是一個類對象classundergstudent
:publicstudent{private:char*major;floatscore[40];
Teacher*tutor;//子對象public:……}classteacher
{private:intID;char*name;
char*title;public:……}分析:對象建立時需要對它的數據成員進行初始化派生類構造函數對其數據成員進行初始化的時候也需要對其中的子對象進行初始化在成員初始化表中顯式調用子對象的構造函數undergstudent(intn,stringstu_name,stringstu_major,float*scoreList,intT_n,stringT_name,stringT_title):student(n,stu_name),tutor(T_n,T_name,T_title)派
生
類
構
造
函
數
的
一
般
形
式<派生類名>(<總參數表>):<基類名>(<參數表1>),<對象成員名>(<參數表2>){<派生類其它數據成員的初始化>};派生類構造函數的任務初始化基類數據成員初始化子對象數據成員初始化其它派生類數據成員派生類構造函數執(zhí)行順序調用基類構造函數調用子對象構造函數執(zhí)行派生類構造函數體特
殊
形
式
的
派
生
類
構
造
函
數多層派生時,不需要在成員初始化表中理出其上每一層派生類的構造函數,只需要列出其直接基類的構造函數即可派生類新增成員無需任何初始化時,派生類構造函數的函數體唯恐如果基類沒有定義構造函數,或者定義了沒有參數的構造函數,派生類定義構造函數可以不寫基類構造函數,派生類構造函數調用時,系統(tǒng)會自動首先調用基類的默認構造函數派
生
類
的
析
構
函
數回顧:析構函數的作用在對象撤銷之前,進行必要的清理工作派生類的析構函數對派生類新增加的成員進行清理根據需要定義相應的析構函數如果有子對象,還需要對子對象進行清理調用子對象的析構函數完成需要對接收自基類的成員進行清理調用基類的析構函數完成派生類析構函數的任務清理基類數據成員清理子對象數據成員清理其它派生類數據成員派生類析構函數執(zhí)行順序調用基類析構函數調用子對象析構函數執(zhí)行派生類析構函數部分派
生
類
的
同
名
覆
蓋覆蓋規(guī)則基類的同名成員在派生類中被屏蔽,成為不可見的定義在派生類對象模塊中通過對象名訪問同名的成員,訪問的是派生類的成員實例:studentstudent_A;undergstudentstudent_B;student_A.print();//調用基類中的成員函數printstudent_B.print();//調用派生類中的成員函數printstudent_B.student::print();//指明作用域而調用基類成員數據成員的覆蓋只要命名相同即可成員函數的覆蓋不僅函數名要相同,函數的參數表包括參數個數和類型都要相同多
重
繼
承一個類可以從一個或者多個基類派生而來。根據派生類繼承基類的個數,將繼承分為單繼承和多繼承。當派生類有多個基類時稱為多繼承。單繼承可以看作是多繼承的一個特例,多繼承可以看作是多個單繼承的組合,它們有很多相同特性。實例教師類在職研究生類研究生類多
重
繼
承
派
生
類
的
定
義class<派生類名>:<繼承方式><基類名1>,…,<繼承方式><基類名n>{<派生類新定義成員>};多
重
繼
承
派
生
類
的
構
造
函
數成員初始化表中需要包含多個基類構造函數一般形式<派生類名>(<總參數表>):<基類名1>(<參數表1>),…,<基類名n>(<參數表n>){<派生類數據成員的初始化>};多
重
繼
承
的
二
義
性
問
題情形一:兩個基類有同名成員classgstudent:
{protected:intID;char*name;char*major;public:init();……}classteacher
{protected:intID;char*name;
char*title;public:init();……}classonjobgstudent:publicgstudent,publicteacher
{public:voidprint();{cout<<ID<<endl;cout<<name<<endl;}……}main()
{onjobgstudentTeach_A;
Teach_A.init();//該調用哪個init()?
Teach_A.print();//print函數該輸出哪個ID…?//研究生學號和教師工號并不同}二義性解
決
方
案
一用作用域運算符“::”進行限定,顯式訪問基類成員。main()
{onjobgstudentTeach_A;
Teach_A.Teacher.init();Teach_A.gstudent.init();
Teach_A.print();}classonjobgstudent:publicgstudent,publicteacher
{public:voidprint();{cout<<Teacher.ID<<endl;cout<<T<<endl;cout<<gstudent.ID<<endl;cout<<major<<endl;}……}派生類的成員函數訪問基類成員,不必寫對象名情
形
二:兩個基類和派生類都有同名成員classgstudent:
{protected:intID;char*name;char*major;public:voidinit(…);……}classteacher
{protected:intID;char*name;
char*title;public:voidinit(…);……}classonjobgstudent:publicgstudent,publicteacher
{public:voidinit(…);{
}voidprint();……}main()
{onjobgstudentTeach_A;
Teach_A.init(…);//調用哪個init?
//根據同名覆蓋原則//調用onjobgstudent的init
Teach_A.print();}無二義性內在問題分析Teacher_A.init調用類onjobgstudent的init函數進行初始化,而類onjobgstudent的init函數分別調用了gstudent的init函數和teacher的init函數進行基類數據成員的初始化Teacher_A這個對象中保留了多份同名成員teacher.IDgstudent.IDteacher.ID和gstudent.ID互不相同必須保留
和相同保留多份同名數據占用空間,易出錯解
決
方
案
二:虛
基
類
的
引
入派生類:在職研究生類直接基類:教師類、研究生類直接基類來自于同一個基類(共同基類):Person類教師類和研究生類
具有數據成員ID,不再具有nameperson類具有數據成員name情況是否有改善?教師類繼承person類的name研究生類繼承person類的name在職研究生類中仍有兩個同名name數據備份教師類在職研究生類研究生類Person類publicpublicpublicpublic虛
基
類
的
作
用
和
聲
明將Person類聲明為虛基類使在職研究生類在多重繼承來自共同基類的直接基類時只保留一份同名成員在職研究生類中只有一份name數據,來自于間接基類Person類虛基類的聲明教師類在職研究生類研究生類Person類virtualpublicvirtualpublicpublicpublicclassperson{…}//正常申明classteacher:virtualpublicperso{…}classgstudent:virtualpublicperson{…}classonjobgstudent:publicteacher,publicgstudent注
意
事
項虛基類在聲明派生類時,通過制定繼承方式時聲明class派生類名
:virtual繼承方式
虛基類名虛基類需要在其所有直接派生類中聲明為虛基類person類為虛基類,其直接派生類teacher和gstudent均需聲明person為虛基類,缺一不可課
后
思
考
題1、目前情況下,類teacher和類gstudent仍然有同名數據成員:ID,請問類onjobgstuent如何處理該同名數據成員?2、如果person類中也增加一個數據成員為ID用于存儲每個人的身份證號,又會出現(xiàn)什么情況?虛
基
類
的
初
始
化引入虛基類之前的情況在職研究生類的構造函數進行初始化時,調用教師類和研究生類各自的構造函數進行初始化,而教師類和研究生類各自的構造函數又分別去調用person類的構造函數在職研究生類的構造函數中只需要寫出它的直接基類teacher和gstudent的構造函數即可教師類在職研究生類研究生類Person類publicpublicpublicpublic引
入
虛
基
類
后,……在職研究生類中只保留了一份同名數據name同名數據來自虛基類Person類這一份數據成員的初始化必須由派生類直接完成如果沿用原來的方式,共同基類person的初始化由teacher和gstudent分別調用,而進行了多次初始化不合理教師類在職研究生類研究生類Person類virtualpublicvirtualpublicpublicpublic虛
基
類
初
始
化
的
規(guī)
定最后的派生類不僅要負責對其直接基類進行初始化,還需要負責虛基類的初始化onjobgstudent(intt_n,ints_n,stringfullname,stringT_title,stringS_major):person(fullname),gstudent(s_n,S_major,fullname),teacher(t_n,T_tile,fullname)虛
基
類
初
始
化
的
說
明C++編譯系統(tǒng)只執(zhí)行最后派生類對虛基類的構造函數的調用,而忽略虛基類的其它派生類對虛基類的構造函數的調用,保證虛基類的數據成員不會被多次初始化構造函數的調用順序(1)先調用虛基類的構造函數,再調用非虛基類的構造函數(2)若同一層次中包含多個虛基類,其調用順序為定義時的順序(3)若虛基類由非虛基類派生而來,則仍按先調用基類構造函數,再調用派生類構造函數的順序引
入
繼
承
后
的
解
決
方
案(俄
羅
斯
方
塊)classShape{intcolor;//基類成員voidleftshift(){};……};classL-shape:publicshape//方塊類派生了L型方塊類//L型方塊類繼承了方塊類,自動擁有其成員{charL-box[2];//定義L型方塊類的形狀屬性voidleftshift(){……};……};不
足
之
處基類的leftshift()等成員函數的函數體均為空,在實現(xiàn)部分仍要寫出函數體,顯得冗余,本無必要,但為了統(tǒng)一規(guī)范類簇的基本行為,又不得不如此主控程序每次隨機生成7種方塊中的一種,然后響應鍵盤控制完成相應的操作和顯示,由于7種方塊對象分屬于7種方塊派生類創(chuàng)建,盡管主控程序中方塊對象的行為和操作保持一致,仍然需要將相似的代碼重復7遍,以保證調用和方塊對象一致的派生類中的成員函數while(;;){inti=random()%7;ifi=0{createaL-shapeobject;detectkeyboard;operateL-shapeobject;display;rule-control;…}ifi=1{createa1-shapeobject;detectkeyboard;operate1-shapeobject;display;rule-control;…}….}程序不夠簡潔Tobecontinued——多態(tài)性和虛函數多
態(tài)
性
和
虛
函
數問
題
的
分
析
有一個類簇,一個基類派生了不同的派生類,這個類簇具有統(tǒng)一的基本行為function,但是不同的派生類實現(xiàn)function的方法不統(tǒng)一不同的派生類創(chuàng)建了一組不同的對象,這一組對象,接收到相同的信息,都需要完成function行為,但是由于對象由不同的派生類所創(chuàng)建,不同派生類完成類似功能的方法不同,需要分別調用不同內容的函數來完成目前的解決機制既然function是這個類簇的統(tǒng)一的基本行為,所以基類中定義統(tǒng)一的成員函數funciton不同的派生類定義各自同名的成員函數function,根據同名覆蓋原則屏蔽基類的成員函數,不同派生類創(chuàng)建的對象調用各自對應的成員函數實
例classstudent:
{protected:intID;char*name;public:voidinit(…);voidprint(…);……}classbachelor:publicstudent
{protected:char*major;
floatscore[40];public:voidinit(…);voidprint(…);……}classmaster:publicstudent,{protected:char*major;teacheradvisor;//導師
floatscore[20];public:voidinit(…);voidprint(…);……}classdoctor:publicstudent,{protected:char*major;teacheradvisor[5];//導師組
floatscore[10];public:voidinit(…);voidprint(…);……}需要打印所有學生的學籍情況bachelorstu1;…stu1.print();…masterstu2;stu2.print();…doctorstu3…stu3.print();…主函數中建立了3個不同類的對象,進行了類似的操作,重復寫了3遍類似的語句繁瑣,不夠簡潔理想狀態(tài)student*stu[3]={&stu1,&stu2,&stu3};//聲明基類指針數組for(inti=0;i<3;i++){…stu[i]->print();/*單一指令,希望根據對象的類型調用對應派生類的特定函數*/…}對象stu1,stu2,stu3來自于同一個基類student,而基類與派生類對象間遵循類型兼容規(guī)則事實上,由于stu[0],stu[1]和stu[2]只能訪問基類成員,所以調用的都是student類中的print函數解
決
機
制動態(tài)多態(tài)相對于靜態(tài)多態(tài)而言:函數重載、運算符重載特點:程序編譯時知道調用哪個函數(編譯時的多態(tài)性)編譯時不能確定調用那個函數,只有在程序運行是才能動態(tài)確定操作所針對的對象理想狀態(tài)下,stu[i].print()調用哪個函數取決于運行時i的值運行時的多態(tài)性引入虛函數虛
函
數虛函數是一類特殊的基類成員函數在基類中聲明,類內用virtual聲明在派生類中可重新定義,但函數名、函數類型、函數參數個數和類型必須與基類的虛函數相同,根據派生類需要重新定義函數體派生類中沒有重新定義時,派生類簡單繼承其直接基類的虛函數虛函數是C++語言的多態(tài)性質和動態(tài)綁定的關鍵虛
函
數
的
特
性特點一個成員函數被申明為虛函數后,同一類簇內所有類內不能再定義與該虛函數具有相同參數和函數返回值類型的同名非virtual函數一個函數一旦被聲明為虛函數,則無論聲明它的類被繼承了多少層,在每一層派生類中該函數都保持虛函數特性。因此,在派生類中重新定義該函數時,可以省略關鍵字virtual。但是,為了提高程序的可讀性,往往不省略當有虛函數聲明時,virtual關鍵字只用在虛函數的聲明中,不能用在虛函數定義中虛
函
數
的
作
用作用可以通過基類指針或者引用來訪問基類和派生類中的同名函數基類指針指向某一派生類對象(符合賦值兼容原則),調用指針指向的派生類對象中的函數而非基類或者其它派生類中的函數(虛函數的作用)突破了原來急了指針僅僅只能指向派生類對象中的基類部分的限制實
例classstudent:
{protected:intID;char*name;public:voidinit(…);
virtualvoidprint(…);……}student*stu[3]={&stu1,&stu2,&stu3};//聲明基類指針數組for(inti=0;i<3;i++){…stu[i]->print();/*print已申明為虛函數*///stu[0]將調用bachelor的print//stu[1]將調用master的print//stu[0]將調用doctor的print…}多
態(tài)
的
分
類C++中多態(tài)的分類重載多態(tài):函數重載和運算符重載強制多態(tài):強制類型轉換包含多態(tài):虛函數參數多態(tài):函數模板和類模板多
態(tài)
的
編
譯
實
現(xiàn)編譯系統(tǒng)要根據已有的信息,對于同名函數的調用做出判斷,確定調用搞得是哪個函數關聯(lián)/binding/聯(lián)編/綁定靜態(tài)多態(tài)靜態(tài)關聯(lián)、早期關聯(lián)動態(tài)多態(tài)動態(tài)關聯(lián)、滯后關聯(lián)C++規(guī)定,動態(tài)關聯(lián)通過繼承和虛函數來實現(xiàn)虛函數申明注意事項靜態(tài)成員函數不能聲明為虛函數。因為靜態(tài)成員函數不屬于某一個對象,沒有多態(tài)性的特征構造函數不能是虛函數內聯(lián)成員函數不能聲明為虛函數。因為內聯(lián)函數的執(zhí)行代碼是明確的,在編譯時已被替換,沒有多態(tài)性的特征析構函數可以是虛函數,且往往被定義為虛函數。一般來說,若某類中有虛函數,則其析構函數也應當定義為虛函數虛
析
構
函
數申明格式virtual~<類名>();必要性實例若析構函數并非虛函數stu是一個指向基類的指針變量,指向new開辟的空間new開辟的空間是按照派生類doctor開辟的,所以除了name、ID之外,還為major、advisor、score申請了空間deletestu僅僅調用了基類student的析構函數,僅僅釋放name、ID的空間,而沒有釋放major、advisor、score的空間student*stu=newdoctor;//聲明基類指針指向doctor類……stu->print();/*print已申明為虛函數*///stu將調用doctor的printdeletstu;return0;純
虛
函
數實例分析福州大學中的學生總共就3類(bachelor、master、doctor),它們的共有基類student實際上是不需要實例化為任何對象的,因此student其實無需為虛函數print定義具體的實現(xiàn)盡管基類本身不需要某一個成員函數,但是考慮到派生類的需要,可以在基類中預留一個函數名,將其聲明為純虛函數只給出該虛函數的原型,但不需要給出函數體virtual
函數類型
函數名(參數列表)const=0;具體功能留給派生類根據需要去定義包含純虛函數的類稱為抽象類抽
象
類包含純虛函數的類是無法創(chuàng)建對象的純虛函數是不能被調用的抽象類是不能用來定義對象的專門作為基類派生新類抽象類的主要作用是將有關的派生類組織在一個繼承層次結構中,由抽象類為它們提供一個公共的根,相關的派生類就從這個根派生出來可以定義指向抽象類數據的指針變量(多態(tài)的實現(xiàn))繼
承
和
多
態(tài)
使
用
實
例實
例
問
題
描
述P.393先建立一個Point(點)類,包含數據成員x,y(坐標點)。以它為基類,派生出一個Circle(圓)類,增加數據成員r(半徑),再以Circle類為直接基類,派生出一個Cylinder(圓柱體)類,再增加數據成員h(高)。要求編寫程序,重載運算符“<<”和“>>”,使之能用于輸出以上類對象基于繼承的解決方案基于繼承和多態(tài)的解決方案基
類Point的
設
計
與
實
現(xiàn)#include<iostream>//聲明類PointclassPoint{public:Point(floatx=0,floaty=0);//有默認參數的構造函數
voidsetPoint(float,float);//設置坐標值
floatgetX()const{returnx;}//讀x坐標
floatgetY()const{returny;}//讀y坐標
friendostream&operator<<(ostream&,constPoint&);//重載運算符“<<”protected://受保護成員
floatx,y;};//下面定義Point類的成員函數//Point的構造函數Point::Point(floata,floatb)//對x,y初始化{x=a;y=b;}//設置x和y的坐標值voidPoint::setPoint(floata,floatb)//為x,y賦新值{x=a;y=b;}//重載運算符“<<”,使之能輸出點的坐標ostream&operator<<(ostream&output,constPoint&p){output<<″[″<<p.x<<″,″<<p.y<<″]″<<endl;returnoutput;}派
生
類Circle的
設
計
與
實
現(xiàn)classCircle:publicPoint//circle是Point類的公用派生類{public:Circle(floatx=0,floaty=0,floatr=0);//構造函數
voidsetRadius(float);//設置半徑值
floatgetRadius()const;//讀取半徑值
floatarea()const;//計算圓面積
friendostream&operator<<(ostream&,constCircle&);//重載運算符“<<”private:floatradius;};//定義構造函數,對圓心坐標和半徑初始化Circle::Circle(floata,floatb,floatr):Point(a,b),radius(r){}//設置半徑值voidCircle::setRadius(floatr){radius=r;}//讀取半徑值floatCircle::getRadius()const{returnradius;}//計算圓面積floatCircle::area()const{return3.14159*radius*radius;}//重載運算符“<<”,使之按規(guī)定的形式輸出圓的信息ostream&operator<<(ostream&output,constCircle&c){output<<″Center=[″<<c.x<<″,″<<c.y<<″],r=″<<c.radius<<″,area=″<<c.area()<<endl;returnoutput;}派
生
類Cylinder的
設
計
與
實
現(xiàn)classCylinder:publicCircle//Cylinder是Circle的公用派生類{public:Cylinder(floatx=0,floaty=0,floatr=0,floath=0);//構造函數
voidsetHeight(float);//設置圓柱高
floatgetHeight()const;//讀取圓柱高
floatarea()const;//計算圓表面積
floatvolume()const;//計算圓柱體積
friendostream&operator<<(ostream&,constCylinder&);//重載運算符“<<”protected:floatheight;//圓柱高};//定義構造函數Cylinder::Cylinder(floata,floatb,floatr,floath):Circle(a,b,r),height(h){}voidCylinder::setHeight(floath){height=h;}//設置圓柱高floatCylinder::getHeight()const{returnheight;}//讀取圓柱高floatCylinder::area()const//計算圓表面積{return2*Circle::area()+2*3.14159*radius*height;}floatCylinder::volume()const//計算圓柱體積{returnCircle::area()*height;}//重載運算符“<<”ostream&operator<<(ostream&output,constCylinder&cy){output<<″Center=[″<<cy.x<<″,″<<cy.y<<″],r=″<<cy.radius<<″,h=″<<cy.height<<″\\narea=″<<cy.area()<<″,volume=″<<cy.volume()<<endl;returnoutput;}抽
象
基
類shape的
設
計
與
實
現(xiàn)#include<iostream>usingnamespacestd;//聲明抽象基類ShapeclassShape{public:virtualfloatarea()const{return0.0;}//虛函數
virtualfloatvolume()const{return0.0;}//虛函數
virtualvoidshapeName()const=0;//純虛函數};Point類
的
設
計
與
實
現(xiàn)//聲明Point類classPoint:publicShape//Point是Shape的公用派生類{public:Point(float=0,float=0);voidsetPoint(float,float);floatgetX()const{returnx;}floatgetY()const{returny;}virtualvoidshapeName()const{cout<<″Point:″;}//對虛函數進行再定義
friendostream&operator<<(ostream&,constPoint&);protected:floatx,y;}//定義Point類成員函數Point::Point(floata,floatb){x=a;y=b;}voidPoint::setPoint(floata,floatb){x=a;y=b;}ostream&operator<<(ostream&output,constPoint&p){output<<″[″<<p.x<<″,″<<p.y<<″]″;returnoutput;}Circle類
的
設
計
與
實
現(xiàn)//聲明Circle類classCircle:publicPoint{public:Circle(floatx=0,floaty=0,floatr=0);voidsetRadius(float);floatgetRadius()const;virtualfloatarea()const;virtualvoidshapeName()const{cout<<″Circle:″;}//對虛函數進行再定義
friendostream&operator<<(ostream&,constCircle&);protected:floatradius;};//聲明Circle類成員函數Circle::Circle(floata,floatb,floatr):Point(a,b),radius(r){}voidCircle::setRadius(floatr):radius(r){}floatCircle::getRadius()const{returnradius;}floatCircle::area()const{return3.14159*radius*radius;}ostream&operator<<(ostream&output,constCircle&c){output<<″[″<<c.x<<″,″<<c.y<<″],r=″<<c.radius;returnoutput;}Cylinder類
的
設
計
與
實
現(xiàn)//聲明Cylinder類classCylinder:publicCircle{public:Cylinder(floatx=0,floaty=0,floatr=0,floath=0);voidsetHeight(float);virtualfloatarea()const;virtualfloatvolume()const;
virtualvoidshapeName()const{cout<<″Cylinder:″;}//對虛函數進行再定義fri
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024版三角高炮合同
- 專項公共區(qū)域裝飾裝修工程承包協(xié)議2024一
- 2025年國際合同第六號生皮國際貿易稅務籌劃合同3篇
- 二零二五年度餐飲企業(yè)員工培訓與職業(yè)發(fā)展規(guī)劃合同3篇
- 2024起重機安裝與運輸安全保障服務合同3篇
- 2025年度柴油發(fā)電機組租賃與維修保養(yǎng)合同4篇
- 2024石材荒料電子商務平臺合作協(xié)議6篇
- 個性化商標創(chuàng)作協(xié)議:2024版委托書版A版
- 2024版生鮮供應合同范本
- 2024金融居間服務的終止與解除合同
- 上海紐約大學自主招生面試試題綜合素質答案技巧
- 辦公家具項目實施方案、供貨方案
- 2022年物流服務師職業(yè)技能競賽理論題庫(含答案)
- ?;钒踩僮饕?guī)程
- 連鎖遺傳和遺傳作圖
- DB63∕T 1885-2020 青海省城鎮(zhèn)老舊小區(qū)綜合改造技術規(guī)程
- 高邊坡施工危險源辨識及分析
- 中海地產設計管理程序
- 簡譜視唱15942
- 《城鎮(zhèn)燃氣設施運行、維護和搶修安全技術規(guī)程》(CJJ51-2006)
- 項目付款審核流程(visio流程圖)
評論
0/150
提交評論