第一篇:修練8年C++面向對象程序設計之體會
修練8年C++面向對象程序設計之體會
六年前,我剛熱戀“面向對象”(Object-Oriented)時,一口氣記住了近十個定義。六年后,我從幾十萬行程序中滾爬出來準備寫點心得體會時,卻無法解釋什么是“面向對象”,就象說不清楚什么是數學那樣。軟件工程中的時髦術語“面向對象分析”和“面向對象設計”,通常是針對“需求分析”和“系統設計”環節的。“面向對象”有幾大學派,就象如來佛、上帝和真主用各自的方式定義了這個世界,并留下一堆經書來解釋這個世界。
有些學者建議這樣找“對象”:分析一個句子的語法,找出名詞和動詞,名詞就是對象,動詞則是對象的方法(即函數)。
當年國民黨的文人為了對抗毛澤東的《沁園春·雪》,特意請清朝遺老們寫了一些對仗工整的詩,請蔣介石過目。老蔣看了氣得大罵:“娘希匹,全都有一股棺材里腐尸的氣味。”我看了幾千頁的軟件工程資料,終于發現自己有些“弱智”,無法理解“面向對象”的理論,同時醒悟到“編程是硬道理。”
面向對象程序設計語言很多,如Smalltalk、Ada、Eiffel、Object Pascal、Visual Basic、C++等等。C++語言最討人喜歡,因為它兼容C 語言,并且具備C 語言的性能。近幾年,一種叫Java 的純面向對象語言紅極一時,不少人叫喊著要用Java 革C++的命。我認為Java 好比是C++的外甥,雖然不是直接遺傳的,但也幾分象樣。外甥在舅舅身上玩耍時灑了一泡尿,倆人不該為此而爭吵。
關于C++程序設計的書藉非常多,本章不講C++的語法,只講一些小小的編程道理。如果我能早幾年明白這些小道理,就可以大大改善數十萬行程序的質量了。1.C++面向對象程序設計的重要概念
早期革命影片里有這樣一個角色,他說:“我是黨代表,我代表黨,我就是黨。”后來他給同志們帶來了災難。
會用C++的程序員一定懂得面向對象程序設計嗎?
不會用C++的程序員一定不懂得面向對象程序設計嗎?
兩者都未必。就象壞蛋入黨后未必能成為好人,好人不入黨未必變成壞蛋那樣。
我不怕觸犯眾怒地說句大話:“C++沒有高手,C 語言才有高手。”在用C 和C++編程8年之后,我深深地遺憾自己不是C 語言的高手,更遺憾沒有人點撥我如何進行面向對象程序設計。我和很多C++程序員一樣,在享用到C++語法的好處時便以為自己已經明白了面向對象程序設計。就象擠掉牙膏賣牙膏皮那樣,真是暴殄天物呀。
人們不懂拼音也會講普通話,如果懂得拼音則會把普通話講得更好。不懂面向對象程序設計也可以用C++編程,如果懂得面向對象程序設計則會把C++程序編得更好。本節講述三個非常基礎的概念:“類與對象”、“繼承與組合”、“虛函數與多態”。理解這些概念,有助于提高程序的質量,特別是提高“可復用性”與“可擴充性”。1.1 類與對象
對象(Object)是類(Class)的一個實例(Instance)。如果將對象比作房子,那么類就是房子的設計圖紙。所以面向對象程序設計的重點是類的設計,而不是對象的設計。類可以將數據和函數封裝在一起,其中函數表示了類的行為(或稱服務)。類提供關鍵字public、protected 和private 用于聲明哪些數據和函數是公有的、受保護的或者是私有的。
這樣可以達到信息隱藏的目的,即讓類僅僅公開必須要讓外界知道的內容,而隱藏其它一切內容。我們不可以濫用類的封裝功能,不要把它當成火鍋,什么東西都往里扔。
類的設計是以數據為中心,還是以行為為中心?
主張“以數據為中心”的那一派人關注類的內部數據結構,他們習慣上將private 類型的數據寫在前面,而將public 類型的函數寫在后面,如表8.1(a)所示。
主張“以行為為中心”的那一派人關注類應該提供什么樣的服務和接口,他們習慣上將public 類型的函數寫在前面,而將private 類型的數據寫在后面,如表8.1(b)所示。
很多C++教課書主張在設計類時“以數據為中心”。我堅持并且建議讀者在設計類時“以行為為中心”,即首先考慮類應該提供什么樣的函數。Microsoft 公司的COM 規范的核心是接口設計,COM 的接口就相當于類的公有函數[Rogerson 1999]。在程序設計方面,咱們不要懷疑Microsoft 公司的風格。
設計孤立的類是比較容易的,難的是正確設計基類及其派生類。因為有些程序員搞不清楚“繼承”(Inheritance)、“組合”(Composition)、“多態”(Polymorphism)這些概念。1.2 繼承與組合
如果A 是基類,B 是A 的派生類,那么B 將繼承A 的數據和函數。示例程序如下: class A { public: void Func1(void);void Func2(void);};class B : public A { public: void Func3(void);void Func4(void);};// Example main(){ B b;// B的一個對象
b.Func1();// B 從A 繼承了函數Func1 b.Func2();// B 從A 繼承了函數Func2 b.Func3();b.Func4();}
這個簡單的示例程序說明了一個事實:C++的“繼承”特性可以提高程序的可復用性。正因為“繼承”太有用、太容易用,才要防止亂用“繼承”。我們要給“繼承”立一些使用規則:
一、如果類A 和類B 毫不相關,不可以為了使B 的功能更多些而讓B 繼承A 的功能。
不要覺得“不吃白不吃”,讓一個好端端的健壯青年無緣無故地吃人參補身體。
二、如果類B 有必要使用A 的功能,則要分兩種情況考慮:
(1)若在邏輯上B 是A 的“一種”(a kind of),則允許B 繼承A 的功能。如男人(Man)是人(Human)的一種,男孩(Boy)是男人的一種。那么類Man 可以從類Human 派生,類Boy 可以從類Man 派生。示例程序如下: class Human { … };class Man : public Human { … };class Boy : public Man { … };
(2)若在邏輯上A 是B 的“一部分”(a part of),則不允許B 繼承A 的功能,而是要用A和其它東西組合出B。例如眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是頭(Head)的一部分,所以類Head 應該由類Eye、Nose、Mouth、Ear 組合而成,不是派生而成。示例程序如下: class Eye { public: void Look(void);};class Nose { public: void Smell(void);};class Mouth { public: void Eat(void);};class Ear { public: void Listen(void);};// 正確的設計,冗長的程序 class Head { public: void Look(void){ m_eye.Look();} void Smell(void){ m_nose.Smell();} void Eat(void){ m_mouth.Eat();} void Listen(void){ m_ear.Listen();} private: Eye m_eye;Nose m_nose;Mouth m_mouth;Ear m_ear;};
如果允許Head 從Eye、Nose、Mouth、Ear 派生而成,那么Head 將自動具有Look、Smell、Eat、Listen 這些功能: // 錯誤的設計
class Head : public Eye, public Nose, public Mouth, public Ear { };
上述程序十分簡短并且運行正確,但是這種設計卻是錯誤的。很多程序員經不起“繼承”的誘惑而犯下設計錯誤。
一只公雞使勁地追打一只剛下了蛋的母雞,你知道為什么嗎?
因為母雞下了鴨蛋。
本書3.3 節講過“運行正確”的程序不見得就是高質量的程序,此處就是一個例證。1.3 虛函數與多態
除了繼承外,C++的另一個優良特性是支持多態,即允許將派生類的對象當作基類的對象使用。如果A 是基類,B 和C 是A 的派生類,多態函數Test 的參數是A 的 指針。那么Test 函數可以引用A、B、C 的對象。示例程序如下: class A { public: void Func1(void);};void Test(A *a){ a->Func1();} class B : public A { … };class C : public A { … };// Example main(){ A a;B b;C c;Test(&a);Test(&b);Test(&c);};
以上程序看不出“多態”有什么價值,加上虛函數和抽象基類后,“多態”的威力就顯示出來了。
C++用關鍵字virtual 來聲明一個函數為虛函數,派生類的虛函數將(override)基類對應的虛函數的功能。示例程序如下: class A { public: virtual void Func1(void){ cout<< “This is A::Func1 n”} };void Test(A *a){ a->Func1();} class B : public A { public: virtual void Func1(void){ cout<< “This is B::Func1 n”} };class C : public A { public: virtual void Func1(void){ cout<< “This is C::Func1 n”} };// Example main(){ A a;B b;C c;Test(&a);// 輸出This is A::Func1 Test(&b);// 輸出This is B::Func1 Test(&c);// 輸出This is C::Func1 };
如果基類A 定義如下: class A { public: virtual void Func1(void)=0;};
那么函數Func1 叫作純虛函數,含有純虛函數的類叫作抽象基類。抽象基類只管定義純虛函數的形式,具體的功能由派生類實現。
結合“抽象基類”和“多態”有如下突出優點:
(1)應用程序不必為每一個派生類編寫功能調用,只需要對抽象基類進行處理即可。這一招叫“以不變應萬變”,可以大大提高程序的可復用性(這是接口設計的復用,而不是代碼實現的復用)。
(2)派生類的功能可以被基類指針引用,這叫向后兼容,可以提高程序的可擴充性和可維護性。以前寫的程序可以被將來寫的程序調用不足為奇,但是將來寫的程序可以被以前寫的程序調用那可了不起。2 良好的編程風格
內功深厚的武林高手出招往往平淡無奇。同理,編程高手也不會用奇門怪招寫程序。良好的編程風格是產生高質量程序的前提。2.1 命名約定
有不少人編程時用拼音給函數或變量命名,這樣做并不能說明你很愛國,卻會讓用此程序的人迷糊(很多南方人不懂拼音,我就不懂)。程序中的英文一般不會太復雜,用詞要力求準確。匈牙利命名法是Microsoft 公司倡導的[Maguire 1993],雖然很煩瑣,但用習慣了也就成了自然。沒有人強迫你采用何種命名法,但有一點應該做到:自己的程序命名必須一致。
以下是我編程時采用的命名約定:
(1)宏定義用大寫字母加下劃線表示,如MAX_LENGTH;
(2)函數用大寫字母開頭的單詞組合而成,如SetName, GetName ;
(3)指針變量加前綴p,如*pNode ;
(4)BOOL 變量加前綴b,如bFlag ;
(5)int 變量加前綴i,如iWidth ;
(6)float 變量加前綴f,如fWidth ;
(7)double 變量加前綴d,如dWidth ;
(8)字符串變量加前綴str,如strName ;
(9)枚舉變量加前綴e,如eDrawMode ;
(10)類的成員變量加前綴m_,如m_strName, m_iWidth ;
對于int, float, double 型的變量,如果變量名的含義十分明顯,則不加前綴,避免煩瑣。如用于循環的int 型變量i,j,k ;float 型的三維坐標(x,y,z)等。2.2 使用斷言
程序一般分為Debug 版本和Release 版本,Debug 版本用于內部調試,Release 版本發行給用戶使用。斷言assert 是僅在Debug 版本起作用的宏,它用于檢查“不應該”發生的情況。以下是一個內存復制程序,在運行過程中,如果assert 的參數為假,那么程序就會中止(一般地還會出現提示對話,說明在什么地方引發了assert)。//復制不重疊的內存塊
void memcpy(void *pvTo, void *pvFrom, size_t size){ void *pbTo =(byte *)pvTo;void *pbFrom =(byte *)pvFrom;assert(pvTo!= NULL && pvFrom!= NULL);while(size--> 0)*pbTo + + = *pbFrom + +;return(pvTo);}
assert 不是一個倉促拼湊起來的宏,為了不在程序的Debug 版本和Release 版本引起差別,assert 不應該產生任何副作用。所以assert 不是函數,而是宏。程序員可以把assert 看成一個在任何系統狀態下都可以安全使用的無害測試手段。
很少有比跟蹤到程序的斷言,卻不知道該斷言的作用更讓人沮喪的事了。你化了很多時間,不是為了排除錯誤,而只是為了弄清楚這個錯誤到底是什么。有的時候,程序員偶爾還會設計出有錯誤的斷言。所以如果搞不清楚斷言檢查的是什么,就很難判斷錯誤是出現在程序中,還是出現在斷言中。幸運的是這個問題很好解決,只要加上清晰的注釋即可。這本是顯而易見的事情,可是很少有程序員這樣做。這好比一個人在森林里,看到樹上釘著一塊“危險”的大牌子。但危險到底是什么?樹要倒?有廢井?有野獸?除非告訴人們“危險”是什么,否則這個警告牌難以起到積極有效的作用。難以理解的斷言常常被程序員忽略,甚至被刪除。[Maguire 1993]
以下是使用斷言的幾個原則:
(1)使用斷言捕捉不應該發生的非法情況。不要混淆非法情況與錯誤情況之間的區別,后者是必然存在的并且是一定要作出處理的。
(2)使用斷言對函數的參數進行確認。
(3)在編寫函數時,要進行反復的考查,并且自問:“我打算做哪些假定?”一旦確定了的
假定,就要使用斷言對假定進行檢查。
(4)一般教科書都鼓勵程序員們進行防錯性的程序設計,但要記住這種編程風格會隱瞞錯誤。當進行防錯性編程時,如果“不可能發生”的事情的確發生了,則要使用斷言進行報警。2.3 new、delete 與指針
在C++中,操作符new 用于申請內存,操作符delete 用于釋放內存。在C 語言中,函數malloc 用于申請內存,函數free 用于釋放內 存。由于C++兼容C 語言,所以new、delete、malloc、free 都有可能一起使用。new 能比malloc 干更多的事,它可以申請對象的內存,而malloc 不能。C++和C 語言中的指針威猛無比,用錯了會帶來災難。對于一個指針p,如果是用new申請的內存,則必須用delete 而不能用free 來釋放。如果是用malloc 申請的內存,則必須用free 而不能用delete 來釋放。在用delete 或用free 釋放p 所指的內存后,應該馬上顯式地將p 置為NULL,以防下次使用p 時發生錯誤。示例程序如下: void Test(void){ float *p;p = new float[100];if(p==NULL)return;…// do something delete p;p=NULL;// 良好的編程風格 // 可以繼續使用p p = new float[500];if(p==NULL)return;…// do something else delete p;p=NULL;}
我們還要預防“野指針”,“野指針”是指向“垃圾”內存的指針,主要成因有兩種:
(1)指針沒有初始化。
(2)指針指向已經釋放的內存,這種情況最讓人防不勝防,示例程序如下: class A { public: void Func(void){…} };void Test(void){ A *p;{ A a;p = &a;// 注意a 的生命期 } p->Func();// p 是“野指針”,程序出錯 } 2.4 使用const
在定義一個常量時,const 比#define 更加靈活。用const 定義的常量含有數據類型,該常量可以參與邏輯運算。例如:
const int LENGTH = 100;// LENGTH 是int 類型 const float MAX=100;// MAX 是float 類型 #define LENGTH 100 // LENGTH 無類型 #define MAX 100 // MAX 無類型
除了能定義常量外,const 還有兩個“保護”功能:
一、強制保護函數的參數值不發生變化
以下程序中,函數f 不會改變輸入參數name 的值,但是函數g 和h 都有可能改變name的值。
void f(String s);// pass by value void g(String &s);// pass by referance void h(String *s);// pass by pointer main(){ String name=“Dog”;
f(name);// name 的值不會改變 g(name);// name 的值可能改變 h(name);// name 的值可能改變 }
對于一個函數而言,如果其?&?或?*?類型的參數只作輸入用,不作輸出用,那么應當在該參數前加上const,以確保函數的代碼不會改變該參數的值(如果改變了該參數的值,編譯器會出現錯誤警告)。因此上述程序中的函數g 和h 應該定義成: void g(const String &s);void h(const String *s);
二、強制保護類的成員函數不改變任何數據成員的值
以下程序中,類stack 的成員函數Count 僅用于計數,為了確保Count 不改變類中的任何數據成員的值,應將函數Count 定義成const 類型。class Stack { public: void push(int elem);void pop(void);int Count(void)const;// const 類型的函數 private: int num;int data[100];};int Stack::Count(void)const { ++ num;// 編譯錯誤,num 值發生變化 pop();// 編譯錯誤,pop 將改變成員變量的值 return num;} 2.5 其它建議
(1)不要編寫一條過分復雜的語句,緊湊的C++/C 代碼并不見到能得到高效率的機器代碼,卻會降低程序的可理解性,程序出錯誤的幾率也會提高。
(2)不要編寫集多種功能于一身的函數,在函數的返回值中,不要將正常值和錯誤標志混在一起。
(3)不要將BOOL 值TRUE 和FALSE 對應于1 和0 進行編程。大多數編程語言將FALSE定義為0,任何非0 值都是TRUE。Visual C++將TRUE 定義為1,而Visual Basic 則將TRUE定義為-1。示例程序如下: BOOL flag;…
if(flag){ // do something } // 正確的用法 if(flag==TRUE){ // do something } // 危險的用法 if(flag==1){ // do something } // 危險的用法 if(!flag){ // do something } // 正確的用法
if(flag==FALSE){ // do something } // 不合理的用法 if(flag==0){ // do something } // 不合理的用法
(4)小心不要將“= =”寫成“=”,編譯器不會自動發現這種錯誤。
(5)不要將123 寫成0123,后者是八進制的數值。
(6)將自己經常犯的編程錯誤記錄下來,制成表格貼在計算機旁邊。小結
C++/C 程序設計如同少林寺的武功一樣博大精深,我練了8 年,大概只學到二三成。所以無論什么時候,都不要覺得自己的編程水平天下第一,看到別人好的技術和風格,要虛心學習。本章的內容少得可憐,就象口渴時只給你一顆楊梅吃,你一定不過癮。我借花獻佛,推薦一本好書:Marshall P.Cline 著的《C++ FAQs》[Cline 1995]。你看了后一定會贊不絕口。會編寫C++/C 程序,不要因此得意洋洋,這只是程序員基本的技能要求而已。如果把系統分析和系統設計比作“戰略決策”,那么編程充其量只是“戰術”。如果指揮官是個大笨蛋,士兵再勇敢也會吃敗仗。所以我們程序員不要只把眼光盯在程序上,要讓自己博學多才。我們應該向北京胡同里的小孩們學習,他們小小年紀就能指點江山,評論世界大事。
第二篇:《C++面向對象程序設計》教案
《面向對象程序設計》課程教案
課程編號:08051230
課程名稱:面向對象程序設計(Object-oriented Programming)學時:72學時,其中理論學時54,上機學時18 學分:3.5開課部門:數學與計算機科學學院 開課教研室:計算機科學 開課教師:雷小園 開課學期:第7學期
授課班級:04信計
先修課程:C語言程序設計
考核要求:考試,平時10%,實驗20%,考試70% 使用教材:
《C++面向對象程序設計教程(第2版)》,陳維興,清華大學出版社,2004年 《C++面向對象程序設計習題解答與實驗指導》,陳維興,清華大學出版社,2004年
教學目的與要求:
《面向對象程序設計》是一門計算機及相關專業的重要的專業基礎課。本課程講述C++語言面向對象的基本特性,包括類、對象、派生類、繼承、運算符重載、多態性、虛函數、函數模板、類模板、輸入輸出、流類庫、文件等,使學生掌握面向對象程序設計的基本概念和基本方法,能運用C++語言進行基本的面向對象程序設計。
教學方法:
采用板書講解C++程序設計,再加以上機練習C++編程。
3章 類和對象
3.1 類與對象的基本概念
3.2 構造函數與析構函數
例:點類 Point class Point { private: int x,y;public: Point(){};Point(int xx, int yy){ x=xx;y=yy;} Point(Point &p){ x=p.x;y=p.y;} int GetX()const { return x;} int GetY()const { return y;} void SetXY(int xx, int yy){ x=xx;y=yy;} void Show();};void Point::Show(){ cout<<“X: ”< 例:人類 Person class Person { protected: char *name;int age;char sex;public: Person(char *n, int a, char s);Person(){ name = 0;age = 0;sex = ' ';} Person(Person &p);~Person(){ delete[] name;} void SetName(char *n);void SetAge(int a){ age = a;} void SetSex(int s){ sex = s;} char *GetName()const { return name;} int GetAge()const { return age;} char GetSex()const { return sex;} void Show();}; #include “person.h” #include Person::Person(char *n, int a, char s){ name = new char[strlen(n)+1];strcpy(name,n);age = a;sex = s;} Person::Person(Person &p){ name = new char[strlen(p.name)+1];strcpy(name,p.name);age = p.age;sex = p.sex;} void Person::SetName(char *n){ delete[] name;name = new char[strlen(n)+1];strcpy(name,n);} void Person::Show(){ cout<<“Name: ”< 1、對象數組 所謂對象數組是指每一數組元素都是對象的數組。 2、對象指針 聲明對象指針的一般語法形式為:類名* 對象指針名。當用指向對象的指針來訪問對象成員時,要用“->”操作符。 3、this指針 C++為成員函數提供了一個名字為this的指針,這個指針稱為自引用指針。每當通過一個對象調用一個成員函數時,系統就自動把這個this指針指向該對象。因此使用的數據成員就是該對象的數據成員。 3.4 向函數傳遞對象 1、使用對象作為函數參數 2、使用對象指針作為函數參數 3、使用對象引用作為函數參數 3.5 靜態成員 1、靜態數據成員 在一個類中,若將一個數據成員說明為static,這種成員稱為靜態數據成員。與一般的數據成員不同,無論建立多少個類的對象,都只有一個靜態數據的拷貝。從而實現了同一個類的不同對象之間的數據共享。 定義靜態數據成員的格式如下: static 數據類型 數據成員名;靜態數據成員在該類定義之外被初始化。訪問靜態數據成員可以通過對象或指針來訪問,也可以通過類名::來訪問。 2、靜態成員函數 定義靜態成員函數的格式如下: static 返回類型 靜態成員函數名(參數表);與靜態數據成員類似,調用公有靜態成員函數的一般格式有如下幾種: 類名::靜態成員函數名(實參表)對象.靜態成員函數名(實參表)對象指針->靜態成員函數名(實參表) 例:點類 Point(演示靜態成員)class Point { private: int x,y;static int count;public: Point(int xx=0, int yy=0){ x=xx;y=yy;count++;} Point(Point &p){ x=p.x;y=p.y;count++;} int GetX()const { return x;} int GetY()const { return y;} void SetXY(int xx, int yy){ x=xx;y=yy;} static int GetCount(){ return count;} }; int Point::count=0; int main(){ Point a(100,200), b;cout< 1、友元函數 友元函數不是當前類的成員函數,而是獨立于當前類的外部函數,但它可以訪問該類的所有對象的成員,包括私有成員、保護成員和公有成員。 2、友元成員 一個類的成員函數也可以作為另一個類的友元,這種成員函數不僅可以訪問自己所在類對象中的所有成員,還可以訪問friend聲明語句所在類對象中的所有成員。 3、友元類 一個類也可以作為另一個類的友元。 友元關系是單向的,不具有交換性。若類X是類Y的友元,類Y不一定是類X的友元。友元關系也不具有傳遞性。若類X是類Y的友元,Y是類Z的友元,類X不一定是類Z的友元。 例:點類 Point(演示友元)class Point { private: int x,y;static int count;public: Point(int xx=0, int yy=0){ x=xx;y=yy;} int GetX()const { return x;} int GetY()const { return y;} void SetXY(int xx, int yy){ x=xx;y=yy;} friend double Dist(Point p1, Point p2);}; friend double Dist(Point p1, Point p2);{ double x,y;x=p1.x-p2.x;y=p1.y-p2.y;return sqrt(x*x+y*y);} int main(){ Point a(100,200), b(300,400);cout<<“兩點間的距離為:”< 例:圓類 Circle(包含Point類的寫法)class Circle { private: double radius;//半徑 Point center;//圓心 public: Circle(){} Circle(int x, int y, double r): center(x,y){ SetRadius(r);} Circle(Point p, double r): center(p){ SetRadius(r);} double GetRadius()const { return radius;} void SetRadius(double r){ radius =(r>=0 ? r : 0);} void SetValue(int x, int y, double r){ center.SetXY(x,y);SetRadius(r);} double Area();void Show();}; const double PI=3.14159;inline double Circle::Area(){ return PI * radius * radius;} void Circle::Show(){ cout<<“圓心為: ” center.Show();cout<<“半徑為: ”< 1、const引用 const引用的說明形式如下: const 類型說明符& 引用名 2、const對象 const對象的說明形式如下: const 類名 對象名[(參數表)];如:const Data Mybirthday(1980,1,1);const對象的數據成員值不能被改變,const對象必須進行初始化。通過const對象只能調用它的const成員函數,而不能調用普通成員函數。 3、const數據成員 const數據成員只能通過構造函數的初始化列表來獲得初始值。 4、const成員函數 const成員函數的說明格式如下: 類型說明符 函數名(參數表)const;如:int GetYear()const { return year;} const成員函數不能更新對象的數據成員,也不能調用對象的普通成員函數。const是函數類型的一個組成部分,因此在函數的實現部分也要帶關鍵字const。 5、引用類型的數據成員 引用類型的數據成員也只能通過構造函數的初始化列表來進行初始化。 例 class Test { private: int a;const int b;//不能寫成const int b=10,因類的定義還沒分配空間 int &c;//不能寫成const int &c=a,因變量a還沒分配空間 public: Test(int i,int j,int &k):b(j),c(k){ a=i;} Test():b(10),c(a){ a=20;} } 第4章 派生類與繼承 4.1 派生類的概念 4.2 派生類的構造函數與析構函數 例:圓類 Circle(繼承Point類的寫法)class Circle: public Point { private: double radius;//半徑 public: Circle(){} Circle(int x, int y, double r): Point(x,y){ SetRadius(r);} Circle(Point p, double r): Point(p){ SetRadius(r);} double GetRadius()const { return radius;} void SetRadius(double r){ radius =(r>=0 ? r : 0);} void SetValue(int x, int y, double r){ SetXY(x,y);SetRadius(r);} double Area();void Show();}; const double PI=3.14159;inline double Circle::Area(){ return PI * radius * radius;} void Circle::Show(){ cout<<“圓心為: ” Point::Show();cout<<“半徑為: ”< 1、派生類繼承了它的所有基類中除構造函數和析構函數之外的所有成員。 2、在派生類中成員按訪問屬性劃分為四種:不可訪問的成員、私有成員、保護成員、公有成員。 3、對從基類繼承下來的成員初始化工作是通過調用基類的構造函數來完成的,調用方法是在派生類的構造函數中用初始化列表。 4、如果在派生類的構造函數省略了基類的初始化列表,則將調用基類的缺省構造函數。 5、如果基類定義了帶有參數的構造函數時,派生類就應當定義構造函數,以便顯式地調用基類的構造函數。 6、如果派生類定義了與基類同名的新數據成員或成員函數,則此派生類的成員就覆蓋了基類的同名成員,直接使用成員名只能訪問到派生類的成員。 7、在同名覆蓋的情況下,可以使用基類名+作用域分辨符來訪問基類的同名成員。 8、如果派生類和基類的某個成員函數重名,但參數表不同,仍然屬于覆蓋,不屬于重載。 9、對派生類的對象,構造函數的執行過程是:先調用基類的構造函數(按它們被繼承時聲明的順序),再調用內嵌對象成員的構造函數(按內嵌對象聲明的順序),最后執行自己的構造函數體中的內容。 10、析構函數的調用次序正好和構造函數的調用次序相反。 例:學生類 Student //student.h #include “person.h” class Student: public Person { protected: char *Department;int Number;public: Student(){ Department = 0;Number = 0;} Student(char *, int, char, char *, int);Student(Student &stu);~Student(){ delete[] Department;} void SetDep(char*);void SetNum(int num){ Number = num;} char *GetDep()const { return Department;} int GetNum()const { return Number;} void Show();}; //student.cpp #include “student.h” #include Student::Student(char *name,int age,char sex,char *dep,int num): Person(name, age, sex){ Department = new char[strlen(dep)+1];strcpy(Department, dep);Number = num;} Student::Student(Student &stu): Person(stu){ Department = new char[strlen(stu.Department)+1];strcpy(Department, stu.Department);Number = stu.Number;} void Student::SetDep(char *dep){ delete[] Department;Department = new char[strlen(dep)+1];strcpy(Department, dep);} void Student::Show(){ Person::Show();cout<<“Department: ” < 4.4 多重繼承 例1:X和Y是基類,Z從X和Y派生 class X { public: int b;X(int k){ b=k;} };class Y { public: int c;Y(int k){ c=k;} };class Z: public X, public Y { public: int d;Z(int i,int j,int k):X(i),Y(j){ d=k;} } 例2:X和Y都從W派生而來 class W { public: int a;W(int k){ d=k;} };class X: public W { public: int b;X(int i, int k): W(i){ b=k;} };class Y: public W { public: int c;Y(int i, int k): W(i){ c=k;} };class Z: public X, public Y { public: int d;Z(int i, int j, int k, int l): X(i,j),Y(i,k){ d=l;} } int main(){ Z t(10,20,30,40);cout< 例3:將W做為X和Y的虛基類 class W { public: int a;W(int k){ a=k;} };class X: virtual public W { public: int b;X(int i, int k): W(i){ b=k;} };class Y: virtual public W { public: int c;Y(int i, int k): W(i){ c=k;} };class Z: public X, public Y { public: int d;Z(int i, int j, int k, int l): W(i),X(i,j),Y(i,k){ d=l;} } int main(){ Z t(10,20,30,40);cout< (2)建立一個對象時,如果這個對象中含有從虛基類繼承來的成員,則虛基類的成員是由最遠派生類的構造函數通過調用虛基類的構造函數進行初始化的。該派生類的其他基類對虛基類構造函數的調用都自動被忽略。 (3)若同一層次中同時包含虛基類和非虛基類,應先調用虛基類的構造函數,再調用非虛基類的構造函數,最后調用派生類構造函數;(4)對于多個虛基類,構造函數的執行順序仍然是先左后右,自上而下;(5)對于非虛基類,構造函數的執行順序仍是先左后右,自上而下;(6)若虛基類由非虛基類派生而來,則仍然先調用基類構造函數,再調用派生類的構造函數。 4.5 賦值兼容規則 所謂賦值兼容規則是指在需要基類對象的任何地方都可以使用公有派生類的對象來替代。 附:線性表——順序表 class SeqList { private: int *data;int size;int MaxSize;public: SeqList(int sz=100);~SeqList(){ delete []data;} int Length()const { return size;} bool IsEmpty()const { return size==0;} void Insert(const int &x, int k);void Delete(int k);int GetData(int k)const;int Find(const int &x)const;void Show()const;};SeqList::SeqList(int sz){ MaxSize=sz;data=new int[MaxSize];size=0;} void SeqList::Insert(const int &x, int k){ if(k<1 || k>size+1){ cerr<<“越界出錯”;exit(1);} if(size==MaxSize){ cerr<<“順序表已滿”;exit(1);} for(int i=size-1;i>=k-1;i--)data[i+1]=data[i];data[k-1]=x;size++;} void SeqList::Delete(int k){ if(size==0){ cerr<<“順序表空”;exit(1);} if(k<1 || k>size){ cerr<<“越界出錯”;exit(1);} for(int i=k;i int SeqList::GetData(int k)const { if(k<1 || k>size){ cerr<<“越界出錯”;exit(1);} return data[k-1];} int SeqList::Find(const int &x)const { for(int i=0;i void SeqList::Show()const { for(int i=0;i 第5章 多態性 5.1 編譯時的多態性與運行時的多態性 5.2 函數重載 5.3 運算符重載 例:復數類Complex //mycomplex.h #include #include“mycomplex.h” #include Complex &Complex::operator+=(Complex &c){ re += c.re;im += c.im;return *this;} Complex &Complex::operator-=(Complex &c){ re-= c.re;im-= c.im;return *this;} Complex &Complex::operator*=(Complex &c){ double t = re * c.rere * c.im)/ m;re = t;return *this;} Complex operator+(Complex &a, Complex &b){ return Complex(a.re + b.re, a.im + b.im);} Complex operator-(Complex &a, Complex &b){ return Complex(a.reb.im);} Complex operator*(Complex &a, Complex &b){ return Complex(a.re * b.rea.re * b.im)/ m);} bool operator==(Complex &a, Complex &b){ return a.re == b.re && a.im == b.im;} bool operator!=(Complex &a, Complex &b){ return a.re!= b.re || a.im!= b.im;} ostream &operator<<(ostream &os, Complex &c){ os << c.re << '+' << c.im << 'i';return os;} istream &operator>>(istream &is, Complex &c){ is >> c.re >> c.im;return is;} 例:分數類 Fraction #include class Fraction { private: int num, den;void reduce();public: Fraction(int n=0, int d=1);Fraction operator+(){ return *this;} Fraction operator-(){ return Fraction(-num, den);} Fraction &operator+=(Fraction &);Fraction &operator-=(Fraction &);Fraction &operator*=(Fraction &);Fraction &operator/=(Fraction &);Fraction &operator++();Fraction operator++(int);operator double();friend Fraction operator+(Fraction &, Fraction &);friend Fraction operator-(Fraction &, Fraction &);friend Fraction operator*(Fraction &, Fraction &);friend Fraction operator/(Fraction &, Fraction &);friend bool operator==(Fraction &, Fraction &);friend bool operator!=(Fraction &, Fraction &);friend bool operator<(Fraction &, Fraction &);friend bool operator<=(Fraction &, Fraction &);friend bool operator>(Fraction &, Fraction &);friend bool operator>=(Fraction &, Fraction &);friend ostream &operator<<(ostream &, Fraction &);friend istream &operator>>(istream &, Fraction &);};#include “fraction.h” #include 5.4 類型轉換 1、通過構造函數將別的類型轉換為這個類的類型 如復數Complex類的構造函數 Complex(double r){ re=r;} 2、通過轉換函數講這個類的類型轉換為別的類型 如在復數Complex類中的轉換函數 operator double(){ return re;} 在分數Fraction類中的轉換函數 operator double(){ return static_cast 用explicit關鍵字,可以禁止單個參數的構造函數用于自動類型轉換,如 class Stack { explicit Stack(int size);? ? } Explicit也同樣禁止用賦值來進行帶有類型轉換的初始化行為 如,不可以 Stack s=10;5.5 虛函數 1、引入派生類后的對象指針 例: class A { public: void show(){ cout<<“A”;} };class B:public A { public: void show(){ cout<<“B”;} };int main(){ A a,*pc;B b;pc=&a;pc->show();pc=&b;pc->show();} 輸出為AA 2、虛函數的定義及使用 例:引入虛函數后,上面的例子改為如下 class A { public: virtual void show(){ cout<<“A”;} };class B:public A { public: void show(){ cout<<“B”;} };int main(){ A a,*pc;B b;pc=&a;pc->show();pc=&b;pc->show();} 輸出為AB 3、純虛函數和抽象類 例: class A { public: virtual void show()=0;};class B:public A { public: void show(){ cout<<“B”;} };int main(){ A *pc;B b;pc=&b;pc->show();} 關于虛函數,有以下幾點 1、如果成員函數是通過引用或指針,而不是通過對象來調用,那么,如果沒有使用virtual,程序將根據引用類型或指針類型來選擇方法;如果使用了virtual,程序將根據引用或指針指向的對象的類型來選擇方法。 2、如果要在派生類中重新定義基類的方法,則將它設置為虛擬方法,否則是指為非虛擬方法 3、如果使用指向對象的引用或指針來調用虛擬方法,程序將使用為對象類型定義的方法,而不使用為引用類型或指針類型定義的方法,這稱為動態聯編或晚期聯編。 4、在基類方法的聲明中使用virtual可使該方法在基類以及所有的派生類中都是虛擬的。 5、一個未在派生類中定義的純虛函數仍舊還是一個純虛函數,該派生類仍為一個抽象類。 6、通常應給基類提供一個虛擬析構函數,這樣,當派生類對象結束時,將先調用派生的析構函數,再調用基類的析構函數。 7、如果派生類沒有重新定義虛擬函數,則將使用該函數的基類版本。 8、如果重新定義繼承的方法,應確保與原來的原型完全相同。但有一個例外,就是如果返回類型是基類指針或引用,則可改為指向派生類的指針或引用。實驗 基本C++程序設計 2 類和對象程序設計 3 派生與繼承程序設計 4 運算符重載程序設計 5 模板程序設計 6 I/ O 流程序設計 《面向對象程序設計》實驗教學大綱 課程總學時:64 學分:4 實驗學時:16 實驗個數: 6個實驗學分:1分 課程性質:專業必修課適用專業:計算機類專業 教材及參考書:《C++語言程序設計(第四版)》,鄭莉、董淵編著,北京:清華大學出版社,2011 大綱執筆人:楊軍 大綱審定人: 一、實驗課的性質與任務 本課程實驗大綱是面向計算機專業學生開設的《C++程序設計》實驗課計劃指導大綱,是依據《面向對象程序設計》課程教學計劃指導大綱編制。本課程主要講述了利用C++進行程序設計的思想和方法,既有面向過程和面向對象的程序設計的理論知識,又包括極強的實踐應用能力的培養。本實驗大綱力求結合該課程教學計劃大綱的相應內容,由淺入深的指導學生了解和掌握如何利用C++程序設計語言進行程序設計,提高學生的動手能力,做到理論和實踐相結合,培養學生理解,分析程序,編寫,調試程序的能力,使之能把程序設計應用到今后的專業學習中。 二、實驗目的與要求 1.實驗目的 通過本課程的學習,使學生掌握面向過程的程序設計思想和編程思路,初步掌握面向對象的程序設計思想,學會調試程序,能獨立編寫實用的小型程序。2.實驗要求 學生應該自始至終貫徹課程中所介紹的程序設計風格,養成良好的編程習慣; 應獨立完成所布置習題。為保證盡量在統一安排的上機時間內編譯運行通過程序,學生應事先設計好程序。 三、實驗項目及內容提要 面向對象程序設計實驗課程(071016) 序號 實驗編號 實驗名稱 學時 必做 選做 學分數 實驗類型 內容提要 基本操作 驗證 綜合設計 1 類與對象 √ √ 函數重載,類的設計與使用 2 2 C++程序的結構 √ √ 作用域與生存期,靜態成員 3 數組、指針與字符串 √ √ 三種常見編程元素的使用 4 繼承與派生 √ √ 派生類的設計與使用 5 多態性 √ √ 運算符重載、動態多態 6 模板和文件 √ √ 模板,異常處理機制的設計 四、實驗內容安排: 實驗一類與對象 (設計性實驗 4學時)目的要求: 掌握類的定義和使用;掌握類對象的聲明;練習具有不同訪問屬性的成員的訪問方式;觀察構造函數和析構函數的執行過程; 學習類組合使用方法; 使用VC++的debug調試功能觀察程序流程,跟蹤觀察類的構造函數、析構函數、成員函數的執行順序。實驗內容: 編寫重載函數Max1可分別求取兩個整數,三個整數,兩個雙精度數,三個雙精度數的最大值。 寫一個函數,具有一個引用作為形參參數,在函數中改變引用變量的值,觀察實參變量的變化。 定義一個CPU類,包含等級(Rank)、頻率(frequency)、電壓(voltage)等屬性,有兩個公有成員函數run、stop。其中,rank為枚舉類型CPU__Rank,定義為enum CPU_Rank{P1=1,P2,P3,P4,P5,P6,P7},frequency為單位是MHz的整型數,voltage為浮點型的電壓值。觀察構造函數和析構函數的調用順序。定義一個簡單的Computer類,有數據成員芯片(cpu)、內存(ram)、光驅(cdrom)等等,有兩個公有成員函數run、stop。cpu為CPU類的一個對象,ram為RAM類的一個對象,cdrom為CDROM類的一個對象,定義并實現這個類,為以上的類編寫構造和析構函數,觀察組合類和內嵌類的構造函數和析構函數的調用順序。 為題目2)的類編寫復制構造函數,在主函數中利用復制構造的方式創建新的對象,觀察對象的狀態。 思考并回答以下概念:函數重載,引用,類,對象,數據成員,函數成員,訪問屬性,構造函數,析構函數,類的組合,內嵌對象,初始化列表,復制構造函數。主要儀器設備及軟件:PC+Windows 2000+VC 6.0 實驗二 C++程序的結構(設計性實驗 2學時)目的要求: 觀察程序運行中變量的作用域、生存期和可見性; 學習類的靜態成員的使用; 學習多文件結構在C++程序中的使用。實驗內容: 實現客戶機(CLIENT)類。定義字符型靜態數據成員ServerName,保存其服務器名稱;整型靜態數據成員ClientNum,記錄已定義的客戶數量;定義靜態函數ChangeServerName()改變服務器名稱。 利用多文件結構實現題目1),在頭文件client.h中定義類,在文件client.cpp中實現該類,在文件test.cpp 中測試這個類,觀察相應的成員變量取值的變化情況,要求ClientNum能夠實時記錄客戶機對象的數量。 思考并回答以下概念:類的靜態數據成員,類的靜態函數成員,多文件結構,文件包含。主要儀器設備及軟件:Windows 2000+VC 6.0 實驗三數組、指針與字符串實驗(設計性實驗 4學時)目的要求: 學習使用數組;學習字符串數據的組織和處理;學習標準C++庫的使用; 掌握指針的使用方法;練習通過debug觀察指針的內容及其所指的對象的內容;練習通過動態內存分配實現動態數組,并體會指針在其中的作用; 分別使用字符數組和標準C++庫練習處理字符串的方法。實驗內容: 編寫一個類用于處理3×3矩陣轉置,測試轉置的效果,輸出轉置前后的矩陣。 定義一個具有構造函數和析構函數的類,如實驗一的CPU類,定義一個CPU的對象數組,觀察構造函數的析構函數的調用過程。利用動態內存分配的方式重新完成題目2)。 使用系統提供的string類定義字符串對象并初始化,實現從原始字符串中提取一個子串。選做:定義一個Point(二維點類)的對象數組,利用該數組實現直線的線性擬合。選做:定義一個動態數組類。 思考并回答:數組,指針,對象數組,動態內存分配,默認構造函數,標準類庫,字符串類 string,線性擬合。 3.主要儀器設備及軟件:Windows 2000+VC 6.0 實驗四繼承與派生 (設計性實驗 2學時)目的要求: 學習定義和使用類的繼承關系,定義派生類;熟悉不同繼承方式下對基類成員的訪問控制; 學習利用虛基類解決二義性問題。實驗內容: 定義一個基類Animal,有私有整型成員變量age,構造其派生類dog,在其成員函數SetAge(int n)中直接給age賦值,看看會有什么問題,把 age改為公有成員變量,還會有問題嗎?編程試試看。 定義一個基類BaseClass,有整型成員變量Number,構造其派生類DerivedClass,定義該派生類的對象,觀察構造函數和析構函數的執行情況。 定義一個車(vehicle)基類,具有MaxSpeed、Weight等成員變量,Run、Stop等成員函數,由此派生出自行車(bicycle)類,汽車(motorcar)類。自行車(bicycle)類有高度(Height)等屬性,汽車(motorcycle)類有座位數(SeatNum)等屬性。從bicycle和motorcycle派生出摩托車(Motorcar)類,在繼承過程中,注意把vehicle設置為虛基類。如果不把vehicle 設置為虛基類,會有什么問?編程實驗及分析原因。 思考并回答:繼承,派生,子類對基類成員的訪問權限,繼承方式,繼承時的構造函數和析構函數的調用順序,虛基類 主要儀器設備及軟件:PC+Windows 2000+VC 6.0 實驗五多態和運算符重載(設計性實驗 2學時)目的要求: 掌握運算符重載的方法;學習使用虛函數實現動態多態性。實驗內容: 定義Point類,有坐標x,y兩個私有成員變量;對Point類重載“+”(相加)、“-”(相減)和“==”(相等)運算符,實現對坐標的改變,要求用友元函數和成員函數兩種方法實現。對Point類重載<<運算符,以使得代碼 Point p;cout< 定義一個車(vehicle)基類,有虛函數Run、Stop等成員函數,由此派生出自行車(bicycle)類、汽車(motorcar)類,它們都有Run、Stop等成員函數。在主函數中用不同的方法調用Run、Stop成員函數,觀察這些函數的執行結果,思考如何實現動態多態性,如果Run、Stop沒有被定義為虛函數,執行結果會怎樣,把結果和分析寫入實驗報告。選做,利用類完成求函數的積分(參考教材)。 思考并回答:多態,實現多態性的方法,虛函數,運算符重載,前++,后++,實現運算符重載的方式。 主要儀器設備及軟件:PC+Windows 2000+VC 6.0 實驗六模板和文件 (設計性實驗 2學時)目的要求: 理解模板的作用和語法。 學習掌握C++文件處理類的基本用法。實驗內容: 使用函數模板實現一個求3個數最大值的函數,并完成測試。 編寫程序,用二進制方式打開指定的一個文件,在每一行前加行號。選做,練習使用STL中的vector模板類。選做,定義一個異常類CException,有成員函數Reason(),用來顯示異常的類型。在子函數中觸發異常,在主程序中處理異常,觀察程序的執行過程。思考并回答:模板,函數模板,類模板,文件,文件讀寫,文件流類,文件操作方式,文件存儲方式; STL,容器,異常處理。 3.主要儀器設備及軟件:PC+Windows 2000+VC 6.0 五實驗報告的格式(本部分要求各學院設計成表格作為教學大綱附件) 實驗完畢,應用專門的實驗報告本,根據預習和實驗中的現象及數據記錄等,及時而認真地寫出實驗報告。實驗報告一般包括以下內容: 實驗(序號)實驗名稱 (一)實驗目的 (二)儀器工具及材料列出實驗中所使用的主要儀器工具及材料。 (三)內容及程序應簡明扼要地寫出實驗步驟流程。 (四)結果及分析應用文字、表格、圖形等形式將數據表示出來。根據實驗要求對數據進行分析和誤差處理。 (五)問題討論結合有關理論對實驗中的現象、產生的誤差等進行討論和分析,以提高自己的分析問題、解決問題的能力,并提出應注意的事項,也為以后的科學研究打下一定的基礎。 六、考核方式、方法及實驗成績評定方法 1、考核方式、方法: 面向對象程序設計實驗課成績占面向對象程序設計總成績的15%,即共15分。考核方法為采用實驗課隨堂檢查學生完成情況及現場提問讓學生回答,根據學生完成情況及答辯情況給分次給出平時成績,共5分。學生在完成實驗后應將自己的實驗過程,結果,經驗寫入實驗報告并提交實驗報告,實驗報告成績占10分,根據學生實驗報告的書寫質量及實驗出勤情況打出。 2、實驗成績評定方法: 評定各級成績時,可參考以下標準: (一)優秀(很好)14-15 能正確理解實驗的目的要求,能獨立、順利而正確地完成各項實驗操作,會分析和處理實驗中遇到的問題,能掌握所學的各項實驗技能,能較好地完成實驗報告及其它各項實驗作業,有一定創造精神和能力。有良好的實驗工作作風和習慣。 (二)良好(較好)13-14 能理解實驗的目的和要求,能認真而正確地完成各項實驗操作,能分析和處理實驗中遇到的一些問題。能掌握所學實驗技能的絕大部分,對難點較大的操作完成有困難。能一般完成實驗報告和其它實驗作業。有較好的實驗習慣和工作作風。 (三)中等(一般)11-12 能粗淺理解實驗目的要求,能認真努力進行各項實驗操作,但技巧較差。能分析和處理實驗中一些較容易的問題,掌握實驗技能的大部分。有30%掌握得不好。能一般完成各項實驗作業和報告。處理問題缺乏條理。工作作風較好。能認真遵守各項規章制度。學習努力。 (四)及格(較差)8-9 只能機械地了解實驗內容,能一般性地按實驗步驟完成實驗操作,能完成60%所學的實驗技能,有些雖作但不準確。遇到問題常常缺乏解決的辦法,在別人啟發下能作些簡單處理,但效果不理想。能一般完成實驗報告,能認真遵守實驗室各項規章制度,工作中有小的習慣性毛病(如工作無計劃,處理問題缺乏條理)。 (五)不及格(很差)0-7 盲目地操作,只掌握50%的所學實驗技能。有些實驗雖能作,但一般效果不好,操作不正確。工作忙亂無條理。一般能遵守實驗室規章制度,但常有小的錯誤。實驗報告較多的時候有結果,遇到問題時說不明原因,在教師指導下也較難完成各項實驗作業。或有些小聰明但不努力,不求上進。 七、實驗主要應配套儀器設備及臺(套)數(以一個實驗教學班40人為標準)序號 儀器設備名稱 數量 備注 計算機 40臺 C++開發工具軟件 1套 網絡版 八、主要教材及參考書 《C++語言程序設計習題與實驗指導》,鄭莉、傅仕星編著,北京:清華大學出版社,2004 《面向對象程序設計基礎》教學大綱 課程編號: 課程中文名稱:面向對象程序設計 課程英文名稱:Object-Oriented Programming 總學時: 40 實驗學時: 0 上機學時:學分: 2.5 適用專業:軟件工程專業 一、課程性質、目的和任務(300字內) 《面向對象程序設計基礎》是計算機軟件工程專業本科生的一門專業基礎課。面向對象軟件開發方法是吸收了軟件工程領域有益的概念和有效的方法而發展起來的一種軟件開發方法。它集抽象性、封裝性、繼承性和多態性于一體,可以幫助人們開發出模塊化的程序,并體現信息隱蔽、可復用、易修改、易擴充等特性。本課程主要介紹面向對象程序設計的方法和C++語言的基本概念及C++語言中的面向對象機制。通過本課程的學習,應使學生能夠較好地理解和掌握面向對象程序設計技術的基本概念,掌握面向對象程序的設計方法,并能夠在C++環境下(如VC++)開發較大型的應用程序。從而為以后的工作和學習打下基礎。 二、課程教學內容及學時分配 第一章、面向對象程序設計概述(3學時) 1.教學內容 1.1面向對象程序設計方法的產生和發展 1.2面向過程和面向對象程序設計方法概述 1.3 面向對象程序設計的基本術語 1.4 面向對象程序設計的基本特征 1.5面向對象程序設計語言 1.6基于Visual Studio 2010的C++應用程序的開發 2.基本要求 了解面向對象技術的發展歷程;了解面向過程和面向對象程序設計兩種程序設計方法 優缺點;掌握面向對象程序設計的特點;掌握面向對象程序設計的相關術語和基本特征;了解目前常用的面向對象程序設計語言。了解Visual Studio環境下,C++應用程序的開發過程。3.重點、難點 重點:面向對象程序設計的特點(數據的抽象與封裝、繼承性、多態性)及面向對象的基本術語;C++應用程序的開發環境。 難點:面向對象程序設計的特點(數據的抽象與封裝、繼承性、多態性)和面向對象的基本術語。 第二章、C++基礎(6學時) 1.教學內容 2.1C++程序的組成部分 2.2 命名空間 2.3 C++數據的輸入輸出 2.4引用 2.5函數 2.6 變量的的作用域與可見性 2.7對象的生存期 2.8 const常量 2.9動態內存分配和釋放 2.10編譯預處理 2.11文件的輸入和輸出 2.基本要求 了解C++程序的組成部分;掌握命名空間、變量的的作用域與可見性及生存期的概念;掌握引用及函數的引用參數和返回引用的概念和使用;掌握帶有默認參數的函數的使用;掌握內聯函數和重載函數的使用;掌握動態內存分配和釋放的方法;掌握磁盤文件的輸入輸出操作方法。3.重點、難點 重點:引用及函數的引用參數和返回引用的概念和使用;動態內存分配和釋放的方法;默認參數的函數的使用;內聯函數和重載函數的使用。 難點:函數的引用參數和返回引用的使用;掌握磁盤文件的輸入輸出操作方法。 第三章 類和對象(6學時) 1.教學內容 3.1類和對象的概念 3.2類的定義 3.3對象的創建與使用 3.4構造函數 3.5析構函數 3.6構造函數和析構函數的調用順序 3.7 對象數組與對象指針 3.8向函數傳遞對象 3.9對象的賦值和復制 3.10類的組合 2.基本要求 理解類的概念,掌握類的定義方法;理解對象與類的關系,掌握對象的創建和使用方法;掌握構造函數、析構函數的概念和使用方法;掌握拷貝構造函數的使用方法;掌握對象數組和對象指針的特點和使用方法;掌握函數調用中參數的傳遞方式;理解類的組合的特點。3.重點、難點 重點:構造函數、析構函數的使用方法;對象數組和對象指針的特點和使用方法;函數調用中參數的傳遞方式。 難點:拷貝構造函數的使用方法;對象數組和對象指針的特點和使用方法;類的組合使用。 第四章、類與對象的其他特性(4學時) 1.教學內容 4.1類的靜態成員 4.2友元 4.3類的作用域和對象的生存期 4.4常量類型 2.基本要求 掌握類的靜態成員(靜態數據成員和靜態成員函數)的定義和使用方法;掌握友元函數、友元類的作用、定義和使用方法;了解類的作用域,理解對象的類型和生存期;掌握各種常量的特點、定義和使用方法。3.重點、難點 重點:靜態數據成員和靜態成員函數的使用方法;友元函數、友元類的使用方法。難點:靜態數據成員和靜態成員函數的使用方法;類的作用域、對象的作用域及生存周期。 第五章、繼承與派生(6學時) 1.教學內容 5.1類的繼承與派生概念 5.2基類與派生類 5.3派生類的構造函數和析構函數 5.4多重繼承 5.5子類型與賦值兼容規則 5.6程序實例 2.基本要求 理解基類和派生類的概念;掌握派生類的聲明、生成過程、繼承方式和訪問權限;掌握派生類的構造函數和析構函數;掌握多重繼承的構造函數和析構函數、構造順序和析構順序及多重繼承中的二義性;掌握虛基類的概念;理解子類型和賦值兼容規則; 3.重點、難點 重點:派生類的繼承方式和訪問權限;派生類的構造函數和析構函數的定義;多重繼承構造函數和析構函數的構造順序和析構順序;多重繼承中的二義性;虛基類的定義。 難點:多重繼承中的二義性;虛基類的定義;理解子類型和賦值兼容規則。 第六章、多態性(5學時) 1.教學內容 6.1運算符重載 6.2多態性的概念 6.3虛函數 6.4純虛函數與抽象類 6.5面向對象程序設計 2.基本要求 掌握重載運算符的定義方法;了解運算符重載為成員函數與友元函數的區別;掌握不同類型數據間的轉換方法;掌握多態性的概念;掌握虛函數的定義和使用方法;掌握純虛函數和抽象類的定義;了解面向對象程序設計的基本思想。3.重點、難點 重點:成員函數和友元函數重載運算符;虛函數的使用方法。難點:虛函數的使用方法;純虛函數和抽象類的定義和使用。 第七章、模板(2學時) 1.教學內容 7.1模板的概念 7.2 函數模板與模板函數 7.3類模板與模板類 2.基本要求 了解模板的概念;掌握函數模板的定義和使用,理解函數模板與模板函數的關系;掌握模板函數顯式具體化;掌握類模板的定義和使用,理解類模板與模板類的關系;掌握類模板的派生;掌握類模板的顯式具體化。3.重點、難點 重點:函數模板與類模板的使用。難點:類模板的使用。 第八章 文件和流 1.教學內容 8.1 C++的輸入/輸出 8.2 標準輸入流 8.3 標準輸出流 8.4 文件的輸入和輸出 2.基本要求 了解C++的輸入/輸出的概念;掌握使用cin進行輸入;掌握istream類的方法進行輸入。掌握使用cout進行輸出;掌握格式化輸出;掌握ostream類的方法進行輸出;掌握文件的輸入和輸出。3.重點、難點 重點:掌握istream類和ostream類的輸入和輸出方法;掌握文件的輸入和輸出。 難點:掌握文件的輸入和輸出。 面向對象程序設計教程(C++語言描述)題解 第1章 面向對象程序設計概論 一、名詞解釋 抽象 封裝 消息 【問題解答】 面向對象方法中的抽象是指對具體問題(對象)進行概括,抽出一類對象的公共性質并加以描述的過程。 面向對象方法中的封裝就是把抽象出來的對象的屬性和行為結合成一個獨立的單位,并盡可能隱蔽對象的內部細節。 消息是面向對象程序設計用來描述對象之間通信的機制。一個消息就是一個對象要求另一個對象實施某種操作的一個請求。 二、填空題 (1)目前有面向過程的結構化程序設計方法和面向對象的程序設計方法兩種重要的程序設計方法。(2)結構化程序設計方法中的模塊由順序、選擇和循環3種基本結構組成。 (3)在結構化程序設計方法中,程序可表示為程序=數據結構+算法; 而面向對象的程序設計方法,程序可表示為程序=對象+消息。 (4)結構化程序設計方法中的基本模塊是過程; 而面向對象程序設計方法中的基本模塊是類。(5)面向對象程序設計方法具有抽象性、封裝性、繼承性和多態性等特點。 三、選擇題(至少選一個,可以多選) (1)面向對象程序設計著重于(B)的設計。 A.對象B.類C.算法D.數據 (2)面向對象程序設計中,把對象的屬性和行為組織在同一個模塊內的機制叫做(C)。 A.抽象 B.繼承 C.封裝 D.多態(3)在面向對象程序設計中,類通過(D)與外界發生關系。 A.對象 B.類 C.消息 D.接口(4)面向對象程序設計中,對象與對象之間的通信機制是(C)。 A.對象 B.類 C.消息 D.接口(5)關于C++與C語言的關系的描述中,(D)是錯誤的。 A.C語言是C++的一個子集 B.C語言與C++是兼容的 C.C++對C語言進行了一些改進 D.C++和C語言都是面向對象的 【結果分析】 C語言是面向過程的。C++語言是一種經過改進的更為優化的C語言,是一種混合型語言,既面向過程也面向對象。 (6)面向對象的程序設計將數據結構與(A)放在一起,作為一個相互依存、不可分割的整體來處理。 A.算法 B.信息 C.數據隱藏 D.數據抽象(7)下面(A)不是面向對象系統所包含的要素。 A.重載 B.對象 C.類 D.繼承 【結果分析】 面向對象=對象+類+繼承+消息+多態(8)下面說法正確的是(BC)。 A.將數據結構和算法臵于同一個函數內,即為數據封裝 B.一個類通過繼承可以獲得另一個類的特性 C.面向對象要求程序員集中于事物的本質特征,用抽象的觀點看待程序 D.同一消息為不同的對象接受時,產生的行為是一樣的,這稱為一致性 【結果分析】 面向對象程序設計方法具有抽象性、封裝性、繼承性和多態性等特點。將數據結構和算法臵于同一個類內,即為數據封裝。同一消息為不同的對象接受時,產生的行為可能是不一樣的,這稱為多態性。(9)下面說法正確的是(AD)。 A.對象是計算機內存中的一塊區域,它可以存放代碼和數據 B.對象實際是功能相對獨立的一段程序 C.各個對象間的數據可以共享是對象的一大優點 D.在面向對象的程序中,對象之間只能通過消息相互通信 【結果分析】 對象是計算機內存中的一塊區域。在對象中,不但存有數據,而且存有代碼,使得每個對象在功能上相互之間保持相對獨立。對象之間存在各種聯系,但它們之間只能通過消息進行通信。 四、判斷題 (1)在高級程序設計語言中,一般用類來實現對象,類是具有相同屬性和行為的一組對象的集合,它是創建對象的模板。(√)(2)C++語言只支持面向對象技術的抽象性、封裝性、繼承性等特性,而不支持多態性。(×)【結果分析】 C++語言不僅支持面向對象技術的抽象性、封裝性、繼承性等特性,而且支持多態性。 (3)面向對象程序設計中的消息應該包含“如何做”的信息。(×)【結果分析】 消息是面向對象程序設計用來描述對象之間通信的機制。向對象“發送消息”只需告訴對象做什么,對象根據這個消息決定如何做。 (4)一個消息只能產生特定的響應效果。(×)【結果分析】 當一個對象發出消息時,由于接收對象的類型可能不同,所以,它們可能做出不同的反應。這樣,一個消息可以產生不同的響應效果,這種現象叫做多態。 (5)類的設計和類的繼承機制實現了軟件模塊的可重用性。(√) (6)C++語言和Java語言均不是一個純正的面向對象的程序設計的語言。(×)【結果分析】 Java語言是一個純正的面向對象的程序設計語言。(7)學習C++語言是學習面向對象的程序設計方法的唯一途徑。(×)【結果分析】 程序設計方法是獨立于具體程序設計語言的一種技術,學習C++語言是學習面向對象程序設計方法的重要途徑之一。 (8)在C++語言中,類是支持數據封裝的工具。(√) 五、簡答題 (1)什么是結構化程序設計方法?它有哪些優點和缺點? 【問題解答】 結構化程序設計方法著眼于系統要實現的功能,從系統的輸入輸出出發,分析系統要做哪些事情,進而考慮如何做這些事情,自頂向下地對系統的功能進行分解,來建立系統的功能結構和相應的程序模塊結構,有效地將一個較復雜的程序系統設計任務分解成許多易于控制和處理的子任務,便于開發和維護。 隨著程序規模與復雜性的增長,這種面向過程的結構化程序設計方法存在明顯的不足之處。首先是數據安全性問題。由于數據被每個模塊所共用,因此是不安全的,一旦出錯,很難查明原因。其次是可 維護性及可重用性差。它把數據結構和算法分離為相互獨立的實體,一旦數據結構需要改變時,常常要涉及整個程序,修改工作量極大并容易產生新的錯誤。每一種相對于老問題的新方法都要帶來額外的開銷。另外,圖形用戶界面的應用程序,很難用過程來描述和實現,開發和維護也都很困難。(2)什么是面向對象程序設計方法?它有哪些優點? 【問題解答】 面向對象的程序設計方法中,將程序設計為一組相互協作的對象而不是一組相互協作的函數。在程序中,屬性用數據表示,用來描述對象靜態特征; 行為用程序代碼實現,用來描述對象動態特征。可見,在面向對象的程序設計方法中,對象是數據結構和算法的封裝體。對象之間存在各種聯系,它們之間通過消息進行通信。程序可表示為: 程序=對象+消息 在面向對象程序設計中應著重于類的設計。類正是面向對象語言的基本程序模塊,通過類的設計來完成實體的建模任務。類通過一個簡單的外部接口與外界發生關系。一個類中的操作不會處理到另一個類中的數據,這樣程序模塊的獨立性、數據的安全性就有了良好的保障。程序的執行取決于事件發生的順序,由順序產生的消息來驅動程序的執行。不必預先確定消息產生的順序,更符合客觀世界的實際。并且面向對象程序設計方法提供了軟件重用、解決大問題和復雜問題的有效途徑,具有抽象性、封裝性、繼承性和多態性等特點。 (3)結構化程序設計方法與面向對象程序設計方法在對待數據結構和算法關系上有 什么不同? 【問題解答】 結構化程序設計方法中,把數據結構和算法分離為相互獨立的實體; 而在面向對象程序設計中,數據結構和算法封裝在一起,結合成一個獨立的單位,即對象,并盡可能隱蔽對象的內部細節。對象的私有屬性只能由這個對象的行為來讀取和修改,與外部的聯系通過公有行為充當外部接口。 第2章 從C到C++ 一、名詞解釋 引用內聯函數重載函數 【問題解答】 所謂引用就是給對象取一個別名,使用該別名可以存取該對象。換句話說是使新對象和原對象共用一個地址。 內聯函數是使用inline關鍵字聲明的函數。重載函數指在同一個作用域內名字相同而參數不同的函數。重載函數通常用來對具有相似行為而數據類型或數據個數不同的操作提供—個通用的名稱。 二、填空題 (1)一般情況下,用C++語言編寫的程序是由函數加上類組成的。 (2)C++有兩種注釋符號,一種是//,另一種是 /*……*/。 (3)使用C++風格的輸入輸出,在程序中必須包含頭文件“iostream”。 (4)cin是預定義的標準輸入流對象,>>是輸入操作符,也稱提取運算符。 (5)cout是預定義的標準輸出流對象,<<是輸出操作符,也稱插入運算符。 (6)指針的值是它所指向那個對象的地址值。指針的類型是它所指向對象的類型。指針的內容便是它所指向對象的值。 (7)C++使用運算符 & 來定義一個引用,對引用的存取都是對它所引用的對象的存取。(8)當一個函數調用出現在函數定義之前時,必須先用函數原型對函數進行聲明。 (9)C++有值傳遞和引用傳遞兩種參數傳遞機制。 (10)使用關鍵字inline聲明的函數稱為內聯函數。 (11)運算符new用于進行動態內存分配,運算符delete用于釋放動態分配的內存。(12)下面程序的輸出結果為x=10,y=10; x=100,y=100。#include int *p=&y;*p=100; cout<<“x=”< 三、選擇題(至少選一個,可以多選)(1)在整型指針變量p2、p3的定義中,錯誤的是(A)。 A.int p1,*p2,p3;B.int*p2,p1,*p3;C.int p1,*p2=&p1,*p3;D.int*p2,p1,*p3=&p1;【結果分析】 指針定義的具體格式如下所示: <類型> *<指針名1>,*<指針名2>,…;(2)若有定義“double xx=3.14,*pp=&xx; ”,則*pp等價于(C)。A.&xxB.*xxC.3.14D.xx 【結果分析】 pp指向xx所在的內存單元,這樣*pp和xx等價。(3)下面對引用的描述中(C)是錯誤的。A.引用是某個變量或對象的別名 B.建立引用時,要對它初始化 C.對引用初始化可以使用任意類型的變量 D.引用與其代表的對象具有相同的地址 【結果分析】 所謂引用就是給對象取一個別名,使用該別名可以存取該對象,所以對引用初始化必須使用同類型的變量。 (4)函數沒有返回值的時候,應該選擇(A)的函數類型。 A.void B.int C.不確定 D.float(5)在函數的定義格式中,下面各組成部分中,(D)是可以省略的。 A.函數名 B.函數體 C.返回值類型 D.函數參數 【結果分析】 函數的定義可以缺省形式參數,此時稱為無參函數。 (6)對重載的函數來說,下面敘述不正確的是(D)。 A.參數的類型不同 B.參數的順序不同 C.參數的個數不同 D.參數的個數、類型、順序都相同,但函數的返回值類型不同 【結果分析】 對重載的函數來說,編譯系統將根據函數參數的類型和個數來判斷使用哪一個函數,所以重載函數參數的個數、類型、順序不能都相同。 (7)下列有關設臵函數參數默認值的描述中,(D)是正確的。 A.對設臵函數參數默認值的順序沒有任何規定 B.函數具有一個參數時不能設臵默認值 C.默認參數要設臵在函數的原型中,而不能設臵在函數的定義語句中 D.設臵默認參數可使用表達式,但表達式中不可用局部變量 【結果分析】 在C++中,在函數原型中可以為一個或多個參數指定默認值。對函數參數設臵默認值要注意以下幾點。 ◆若沒有聲明函數原型,參數的默認值可在函數定義的頭部進行設臵,否則必須在函數原型中進行設臵。 ◆在一個指定了默認值的參數右邊不能出現沒有指定默認值的參數。 ◆設臵默認參數可使用表達式,但表達式中不可用局部變量。 (8)下面說法正確的是(BC)。A.所有的函數都可以說明為內聯函數 B.具有循環語句、switch語句的函數不能說明為內聯函數 C.使用內聯函數,可以加快程序執行的速度,但會增加程序代碼的大小 D.使用內聯函數,可以減小程序代碼大小,但使程序執行的速度減慢 【結果分析】 內聯函數主要是解決程序的運行效率問題。在程序編譯時,編譯系統將程序中出現內聯函數調用的地方用函數體進行替換,進而減少了程序運行的時間,但會增加程序代碼的大小。它是以空間換取時間,因此內聯函數適用于功能不太復雜,但要求被頻繁調用的函數。 (9)一個函數功能不太復雜,但要求被頻繁調用,應選用(A)。 A.內聯函數 B.重載函數 C.遞歸函數 D.嵌套函數 (10)C++對C語言做了很多改進,下列描述中 使得C語言發生了質變,即從面向過程變成面向對象的是(D)。A.增加了一些新的運算符 B.允許函數重載,并允許設臵默認參數 C.規定函數說明必須用原型 D.引進了類和對象的概念 【結果分析】 面向對象=對象+類+繼承+消息+多態 四、判斷題 (1)C++程序中,不得使用沒有定義或說明的變量。(√) (2)使用const說明常量時,可以不必指出類型。(×)【結果分析】 如果用const 定義的是一個整型常量,則類型說明符int可以省略。 (3)引用被創建時可以用任意變量進行初始化。(×)【結果分析】 對引用初始化必須使用同類型的變量。 (4)一個返回引用的調用函數可以作為左值。(√) (5)函數可以沒有參數,也可以沒有返回值。(√) (6)沒有參數的兩個函數是不能重載的。(√)(7)函數可設臵默認參數,但不允許將一個函數的所有參數都設臵為默認參數。(×)【結果分析】 函數可設臵默認參數,且允許將一個函數的所有參數都設臵為默認參數。 (8)運算符new分配的空間由運算符delete釋放。(√) 五、簡答題 (1)名字空間的用途是什么? 【問題解答】 名字空間用來防止命名的沖突。(2)引用有何用處? 【問題解答】 除了獨立引用外,在C++程序中,引用的主要用途是用作函數參數和函數的返回值。 (3)比較值調用和引用調用的相同點與不同點。【問題解答】 在值調用機制中,作為實參的表達式的值被復制到由對應的形參名所標識的一個對象中,作為形參的初始值。函數體對形參的訪問、修改都是在這個標識對象上操作的,與實參無關,即數據的傳遞是單向的。 使用引用作函數的形參時,調用函數的實參要用變量名。實參傳遞給形參,相當于在被調用函數中使用了實參的別名。于是,在被調用函數中對形參的操作實質是對實參的直接操作,即數據的傳遞是雙向的。 (4)內聯函數有什么作用?它有哪些特點? 【問題解答】 內聯函數是使用inline關鍵字聲明的函數。在程序編譯時,編譯系統將程序中出現內聯函數調用的地方用函數體進行替換,進而減少了程序運行的時間。 使用內聯函數應注意以下幾點。◆遞歸函數不能定義為內聯函數。 ◆內聯函數一般適合于不含有switch和while等復雜的結構且只有1~5條語句的小函數,否則編譯系統將該函數視為普通函數。 ◆內聯函數只能先定義后使用,否則編譯系統也將該函數視為普通函數。 ◆對內聯函數也不能進行異常接口聲明。(5)函數原型中的參數名與函數定義中的參數名以及函數調用中的參數名必須一致嗎? 【問題解答】 不必一致。所有的參數是根據位臵和類型而不是名字來區分的。 (6)重載函數時通過什么來區分? 【問題解答】 編譯系統將根據函數參數的類型和個數來判斷使用哪一個函數。 六、程序分析題(寫出程序的輸出結果,并分析結果) #include 【輸出結果】 num=60 ref=100 【問題分析】 本題主要考查引用的含義。【結果分析】 程序首先定義一個int類型的對象num,并給它賦初始值50。然后又定義了一個int類型的引用ref,并將它和num相聯系。這樣,無論是對num還是對ref進行操作,實際上都是對那個一開始放著50的物理單元的內容進行操作。 七、程序設計題 寫出一個完整的C++程序,使用系統函數pow(x,y)計算xy的值,注意包含頭文件cmath。【問題分析】 本題主要考查簡單的輸入輸出和標準庫函數的調用方法。【解題思路】 ① 由于要用到系統函數pow(x,y),所以要包含頭文件cmath。 ② 要計算xy的值,首先必須知道x和y的值。為了程序的通用性,最好通過交互的方式輸入x和y的值。【參考程序】 // xt2_1.cpp #include cout<<“please input 2 floats to x,y:”;cin>>x>>y; float z=pow(x,y); cout<<“pow(”< please input 2 floats to x,y:3.1 2 pow(3.1,2)=9.61 第3章 類 與 對 象 一、填空題 (1)類定義中關鍵字private、public和protected以后的成員的訪問權限分別是私有、公有和保護。如果沒有使用關鍵字,則所有成員默認定義為private權限。具有public訪問權限的數據成員才能被不屬于該類的函數所直接訪問。(2)定義成員函數時,運算符“∷”是作用域運算符,“MyClass∷”用于表明其后的成員函數是在“MyClass類”中說明的。 (3)在程序運行時,通過為對象分配內存來創建對象。在創建對象時,使用類作為樣板,故稱對象為類的實例。 (4)假定Dc是一個類,則執行“Dc a[10],b(2)”語句時,系統自動調用該類構造函數的次數為11。【結果分析】 創建10個數組元素需調用構造函數10次,創建對象b需調用構造函數1次,所以系統自動調用該類構造函數的總次數為11。 (5)對于任意一個類,析構函數的個數最多為1個。 (6)delete運算符通常用于實現釋放該類對象中指針成員所指向的動態存儲空間的任務。(7)C++程序的內存格局通常分為4個區: 數據區、代碼區、棧區和堆區。 (8)數據定義為全局變量,破壞了數據的 封裝性; 較好的解決辦法是將所要共享的數據定義為類的 靜態成員。 (9)靜態數據成員和靜態成員函數可由 任意訪問權限許可的函數訪問。 (10)友元函數和 友元類統稱為友元。(11)友元的正確使用能提高程序的效率,但破壞了類的封裝性和數據的隱蔽性。 (12)若需要把一個類A定義為一個類B的友元類,則應在類B的定義中加入一條語句: friend class A。 二、選擇題(至少選一個,可以多選)(1)以下不屬于類訪問權限的是(B)。A.public B.staticC.protectedD.private 【結果分析】 類的訪問權限有public、protected 和private。(2)有關類的說法不正確的是(BC)。A.類是一種用戶自定義的數據類型 B.只有類的成員函數才能訪問類的私有數據成員 C.在類中,如不做權限說明,所有的數據成員都是公有的 D.在類中,如不做權限說明,所有的數據成員都是私有的 【結果分析】 類是一種用戶自定義的數據類型,類中成員均具有一種訪問權限。關鍵字public、protected 和private以后的成員的訪問權限分別是公有、保護和私有的,所有成員默認定義為private的。私有成員是被隱藏的數據,只有該類的成員函數或友元函數才可以訪問它。 (3)在類定義的外部,可以被任意函數訪問的成員有(C)。 A.所有類成員 B.private或protected的類成員 C.public的類成員 D.public或private的類成員 【結果分析】 類是一種用戶自定義的數據類型,類中成員均具有一種訪問權限。公有成員定義了類的外部接口。私有成員是被隱藏的數據,只有該類的成員函數或友元函數才可以引用它。保護成員具有公有成員和私有成員的雙重性質,可以被該類或派生類的成員函數或友元函數引用。可見在類定義的外部,可以被任意函數訪問的成員是public的類成員。(4)關于類和對象的說法(C)是錯誤的。A.對象是類的一個實例 B.任何一個對象只能屬于一個具體的類 C.一個類只能有一個對象 D.類與對象的關系和數據類型與變量的關系相似 【結果分析】 C++語言的類就是一種用戶自己定義的數據類型,類和對象的關系就相當于基本數據類型與它的變量的關系,所以任何一個對象只能屬于一個具體的類,但一個類可以有多個對象。 (5)設MClass是一個類,dd是它的一個對象,pp是指向dd的指針,cc是dd的引用,則對成員的訪問,對象dd可以通過(B)進行,指針pp可以通過(D)進行,引用cc可以通過(B)進行。 A.∷ B..C.& D.-> (6)關于成員函數的說法中不正確的是(C)。A.成員函數可以無返回值 B.成員函數可以重載 C.成員函數一定是內聯函數 D.成員函數可以設定參數的默認值 【結果分析】 與普通函數不同的是,成員函數是屬于某個類的。成員函數的實現,可以放在類體內,也可以放在類體外。在類體外實現的成員函數不再是內聯函數。(7)下面對構造函數的不正確描述是(B)。A.系統可以提供默認的構造函數 B.構造函數可以有參數,所以也可以有返回值 C.構造函數可以重載 D.構造函數可以設臵默認參數 【結果分析】 構造函數不能指定返回類型,即使是void類型也不可以,當然不可能有返回值。 (8)假定A是一個類,那么執行語句“A a,b(3),*p; ”調用了(B)次構造函數。A.1 B.2 C.3 D.4 【結果分析】 聲明指針是不會調用構造函數的。 (9)下面對析構函數的正確描述是(AC)。A.系統可以提供默認的析構函數 B.析構函數必須由用戶定義 C.析構函數沒有參數 D.析構函數可以設臵默認參數 【結果分析】 析構函數的作用是在對象消失時執行一項清理任務。如果一個類中沒有定義析構函數,系統將自動生成一個默認析構函數。析構函數沒有參數,當然不可能設臵默認參數。 (10)類的析構函數是(D)時被調用的。A.類創建 B.創建對象 C.引用對象 D.釋放對象 (11)創建一個類的對象時,系統自動調用(B); 撤銷對象時,系統自動調用(C)。 A.成員函數 B.構造函數 C.析構函數 D.復制構造函數 (12)通常拷貝構造函數的參數是(C)。A.某個對象名 B.某個對象的成員名 C.某個對象的引用名 D.某個對象的指針名 (13)關于this指針的說法正確的是(B)。 A.this指針必須顯式說明B.當創建一個對象后,this指針就指向該對象 C.成員函數擁有this指針D.靜態成員函數擁有this指針。【結果分析】 this指針是由C++編譯器自動產生且較常用的一個隱含對象指針,它不能被顯式聲明。當創建一個對象時,this指針就初始化指向該對象。但只有非靜態成員函數才擁有this指針,并通過該指針來處理對象。 (14)下列關于子對象的描述中,(B)是錯誤的。 A.子對象是類的一種數據成員,它是另一個類的對象 B.子對象可以是自身類的對象 C.對子對象的初始化要包含在該類的構造函數中 D.一個類中能含有多個子對象作其成員 【結果分析】 子對象不可以是自身類的對象。 (15)對new運算符的下列描述中,(B)是錯誤的。 A.它可以動態創建對象和對象數組 B.用它創建對象數組時必須指定初始值 C.用它創建對象時要調用構造函數 D.用它創建的對象數組可以使用運算符delete來一次釋放 【結果分析】 使用運算符new創建對象數組的格式如下: new <類型說明符> [<算術表達式>] 其中,<算術表達式>給出數組的大小,后面不能再跟構造函數參數,所以用它創建對象數組時不能指 定初始值。 (16)對delete運算符的下列描述中,(D)是錯誤的。 A.用它可以釋放用new運算符創建的對象和對象數組 B.用它釋放一個對象時,它作用于一個new所返回的指針 C.用它釋放一個對象數組時,它作用的指針名前須加下標運算符[ ] D.用它可一次釋放用new運算符創建的多個對象 【結果分析】 用delete一次只能釋放用new創建的1個對象,但可釋放一個對象數組。 (17)關于靜態數據成員,下面敘述不正確的是(C)。 A.使用靜態數據成員,實際上是為了消除全局變量 B.可以使用“對象名.靜態成員”或者“類名∷靜態成員”來訪問靜態數據成員 C.靜態數據成員只能在靜態成員函數中引用 D.所有對象的靜態數據成員占用同一內存單元 【結果分析】 靜態數據成員可以在靜態成員函數中引用,也可以在非靜態成員函數中引用。 (18)對靜態數據成員的不正確描述是(CD)。A.靜態成員不屬于對象,是類的共享成員 B.靜態數據成員要在類外定義和初始化 C.調用靜態成員函數時要通過類或對象激活,所以靜態成員函數擁有this指針 D.只有靜態成員函數可以操作靜態數據成員 【結果分析】 this指針是一個局部量,局部于某個對象,而靜態成員函數是屬于整個類而不是某個對象,它沒有this指針。靜態成員函數和非靜態成員函數均可操作靜態數據成員。 (19)下面的選項中,靜態成員函數不能直接訪問的是(D)。 A.靜態數據成員 B.靜態成員函數 C.類以外的函數和數據 D.非靜態數據成員 【結果分析】 由于靜態成員函數沒有this指針,它只能直接訪問該類的靜態數據成員、靜態成員函數和類以外的函數和數據,訪問類中的非靜態數據成員必須通過參數傳遞方式得到對象名,然后通過對象名來訪問。 (20)在類的定義中,引入友元的原因是(A)。A.提高效率 B.深化使用類的封裝性 C.提高程序的可讀性 D.提高數據的隱蔽性 【結果分析】 友元的作用主要是為了提高效率和方便編程,但友元破壞了類的封裝性和隱蔽性,使用時要權衡利 弊。 (21)友元類的聲明方法是(A)。 A.friend class<類名>; B.youyuan class<類名>; C.class friend<類名>; D.friends class<類名>; (22)下面對友元的錯誤描述是(D)。A.關鍵字friend用于聲明友元 B.一個類中的成員函數可以是另一個類的友元 C.友元函數訪問對象的成員不受訪問特性影響 D.友元函數通過this指針訪問對象成員 【結果分析】 友元函數是一個放在類中的普通函數,它沒有this指針。 (23)下面選項中,(C)不是類的成員函數。A.構造函數 B.析構函數 C.友元函數 D.拷貝構造函數 三、簡答題 (1)類與對象有什么關系? 【問題解答】 類是一種用戶自己定義的數據類型,和其他數據類型不同的是,組成這種類型的不僅可以有數據,而且可以有對數據進行操作的函數。程序員可以使用這個新類型在程序中聲明新的變量,具有類類型的變量稱為對象。創建對象時,類被用做樣板,對象稱為類的實例。 (2)類定義的一般形式是什么?其成員有哪幾種訪問權限? 【問題解答】 定義類一般形式為: class類名{ public: <公有數據和函數> protected: <保護數據和函數> private: <私有數據和函數> }; 訪問權限共有3種: 分別是公有(public)、保護(protected)和私有(private)。 (3)類的實例化是指創建類的對象還是定義類? 【問題解答】 指創建類的對象。 (4)什么是this指針?它的主要作用是什么? 【問題解答】 this指針是C++語言為成員函數提供的一個隱含對象指針,它不能被顯式聲明。this指針是一個局部量,局部于某個對象。不同的對象調用同一個成員函數時,編譯器根據this指針來確定應該引用哪一個對象的數據成員。 (5)什么叫做拷貝構造函數?拷貝構造函數何時被調用? 【問題解答】 拷貝構造函數是一種特殊的構造函數,它的作用是用一個已經存在的對象去初始化另一個對象。為了保證所引用的對象不被修改,通常把引用參數聲明為const參數。 在以下3種情況下,拷貝構造函數都會被自動調用: ◆當用類的一個對象去初始化該類的另一個對象時; ◆當函數的形參是類的對象,進行形參和實參結合時; ◆當函數的返回值是類的對象,函數執行完成返回調用者時。 四、程序分析題(寫出程序的輸出結果,并分析結果)(1) #include using namespace std; class Test { private: int num; public: Test();// 默認構造函數 Test(int n);// 帶一個參數構造函數 }; Test∷Test() { cout<<“Init defa”< Test∷Test(int n) { cout<<“Init”<<“ ”< int main() { Test x[2];// 語句1 Test y(15);// 語句2 return 0;} 【輸出結果】 Init defa Init defa Init 15 【問題分析】 本題主要考查構造函數的調用時機和構造函數的匹配問題。【要點提示】 構造函數在創建對象時被自動調用,具體調用哪個構造函數將由編譯系統根據重載函數的匹配原則來確定。【結果分析】 ① 程序聲明了2個對象x和y,類中有2個構造函數。 ② 程序首先執行語句1,創建對象x,調用默認構造函數。由于對象x是對象數組,每個數組元素被創建時都要調用構造函數,所以默認構造函數被調用了2次,輸出第1、2行結果。程序接著執行語句2,創建對象y,調用帶一個參數的構造函數,輸出第3行結果。(2) #include using namespace std;class Xx { private: int num;public: Xx(int x){num=x;} // 構造函數 ~Xx(){cout<<“dst ”< }; int main() { Xx w(5);// 語句1 cout<<“Exit main”< return 0;} 【輸出結果】 Exit main dst 5 【問題分析】 本題主要考查析構函數的調用時機。【要點提示】 析構函數在釋放對象時被自動調用。【結果分析】 ① 程序聲明了一個對象w。 ② 程序首先執行語句1,創建對象w,調用構造函數,num得到初值5。程序接著執行語句2,輸出第1行結果。當程序結束時,釋放對象w,析構函數被調用,輸出第2行結果。 (3)將例3.10中的Whole類如下修改,其他部分不變,寫出輸出結果。 class Whole { public: Whole(int i);// Whole的有參構造函數 Whole(){};// Whole的無參構造函數 ~Whole();// Whole的析構函數 private: Part p1;// 子對象1 Part p2;// 子對象2 Part p3;// 子對象3 }; Whole∷Whole(int i):p2(i),p1(){ cout<<“Constructor of Whole”< Whole∷~Whole() { cout<<“Destructor of Whole”< 【輸出結果】 Default constructor of Part Constructor of Part,3 Default constructor of Part Constructor of Whole Destructor of Whole Destructor of Part,0 Destructor of Part,3 Destructor of Part,0 【問題分析】 本題主要考查子對象初始化的方法和含有子對象時構造函數和析構函數的調用順序。【要點提示】 ◆當建立X類的對象時,先調用子對象的構造函數,初始化子對象,然后才執行X類的構造函數,初始化X類中的其他成員。 ◆對子對象構造函數的調用順序取決于這些子對象在類中的說明順序,與它們在成員初始化列表中給出的順序無關。 ◆如果X類的構造函數沒有給出成員初始化列表,表明子對象將使用默認構造函數進行初始化。◆析構函數的調用順序與構造函數的調用順序正好相反。【結果分析】 程序的Whole類中出現了類Part的3個對象p1、p2和p3,作為該類的數據成員,則p1、p2和p3被稱為子對象。當建立Whole類的對象w時,子對象p1、p2和p3被建立,相應的構造函數被執行。由于p1在Whole類中先說明,所以先執行它所使用的構造函數,即類Part的默認構造函數,接著p2執行它所使用的有參構造函數,緊接著初始化p3,由于Whole類構造函數的成員初始化列表中沒有子對象p3進行初始化的選項,所以執行類Part的默認構造函數,當所有子對象被構造完之后,對象w的構造函數才被執行,從而得到前4行輸出結果,而后4行是執行相應析構函數的輸出結果。(4) #include using namespace std; class Book { public: Book(int w); static int sumnum; private: int num;}; Book∷Book(int w) { num=w; sumnum-=w;} int Book∷sumnum=120;// 語句1 int main() { Book b1(20);// 語句2 Book b2(70);// 語句3 cout< return 0;} 【輸出結果】 【問題分析】 本題主要考查“在類的范圍內所有對象共享靜態成員的數據”的含義。【結果分析】 程序中語句1對靜態成員sumnum進行初始化,sumnum得到初值120。執行語句2時,調用構造函數,sumnum變為100。接著語句3,再調用構造函數,sumnum變為30。 五、程序設計題 (1)聲明一個Circle類,有數據成員radius(半 徑)、成員函數area(),計算圓的面積,構造一個Circle的對象進行測試。【問題分析】 本題主要考查類定義的形式、對象成員訪問和對象初始化的方法。要求理解類和構造函數的真正含義,特別注意如何將客觀事物的屬性和行為抽象為類的成員。【解題思路】 ① 題目中已給出了類的基本部分,需要增加一個構造函數來初始化數據成員radius。 ② 為了程序的通用性,圓的半徑由鍵盤輸入。【參考程序】 // xt3_1.cpp #include第三篇:《面向對象程序設計》(c++)實驗教學大綱
第四篇:C++面向對象程序設計教學大綱
第五篇:面向對象程序設計教程(C++語言描述)題解