C Plus 總複習筆記
2014.6.25.Chitry
第一章 面向對象介紹
2014年6月25日 09:28:36
1. 結構化程序設計:程序 = 算法 + 數據結構
基本思想:自頂向下,逐步細化的設計方法和單入單出的控制結構
重點:函數
缺點:a.程序難以管理
b.數據修改存在問題
c.程序可重用性差
d.用戶難以在系統分析階段準確定義,致使系統在交付使用時產生許多問題
e.用系統開發的每個階段的成果來進行控制,不能適應事物變化的要求
缺陷根源: 數據與數據處理相分離
2.面向對象程序設計: 程序 = 對象 + 對象 + 對象 + … + 消息傳遞
“以對象爲中心的思維方式”
將系統看成通過交互作用來完成特定功能的對象的集合,每個對象用自己的方法來管理數據,亦即:只有對象內部的代碼能夠操作對象內部的數據
模擬自然界認知和處理事物的方法,將數據和對數據的操作方法放在一起,形成一個相對獨立的整體——抽象(object)
對象 = [ 算法 + 數據結構 ] “結合,適應變化,封裝的,呈現的行爲時穩定的,僅藉口暴露 ”
對象(內容) = 屬性 + 行爲
同類對象可抽象出共性,形成類(class)
一個類中的數據通常只能通過本類提供的方法進行處理,這些方法成爲該類與外部的接口
對象之間通過消息(message)進行通訊
3.面向對象程序設計的基本特徵、特性、及其優缺點:
A.面向對象程序設計的基本特徵: 抽象、封裝、繼承、多態
B.面向對象編程的特性: a.程序設計的重點在數據而不是函數
b.程序由對象對象組成,建立對象不是爲了完成一個步驟,而是爲了描述某個事物在整個解決問題的步驟中行爲
c.對象之間通過相互協作來完成功能
e.函數與相關數據緊密結合
e.數據可以被隱藏
f.很容易擴充新的數據和函數
C.面向對象編程的優缺點:
優點:a.易維護
b.質量高
c.效率高
d.易拓展
缺點:相對面向過程,運行效率會下降10%左右
4.面向結構化和麪向對象的不同之處:
根本在於其抽象的級別不同
A.結構化程序設計方法應用的是過程抽象,是將問題域中具有明確功能定義的操作抽取出來,並將其作爲一個實體看待
B.面向對象對象設計方法應用的是數據抽象,是較過程抽象更高級別的抽象方式,將抽象客體的屬性和行爲綁定在一起,實現統一的抽象,從而實現對現實世界客體的真正模擬。
5.編程語言四大需求:
1>.效率:速度
2>.靈活
3>.抽象:大型的系統開發的適應
4>.生產力:開發效率
* C :效率、靈活
C Plus:效率、靈活、抽象
Java & .Net :抽象、生產力
6.世界上第一種面向對象編程語言: SmallTalk
第二章 從C到C++(C99,C++11)
2014年6月25日11:20:26
1.輸入輸出流: cin >> 變量 輸入對象:鍵盤
cout << 數據 輸出對象:顯示屏
“>>” ”<<”仍然保留“按位移”功能
I/O流輸入輸出的是一串字節
2.命令空間:using namespace std;
若預處理命令爲 .h 頭文件形式,則無需申請命令空間
兩種頭文件不能混用!!
(尖括號爲根目錄下尋找頭文件,圓括號爲當前目錄下查找頭文件)
3.輸出操縱符:“endl” 插入換行符,並刷新流(!!!endl不能用在輸入流中,否則會報錯!!!)
4.格式控制符:
cout << hex << x << ‘ ’ << dec << x << ‘ ’<< oct << x << endl;
hex:十六進制輸出
dec:十進制輸出
oct:八進制輸出
*作用域一直到程序結束
# cout << setw(6) << 456 << endl; 以六位域寬輸出“456”
但是必須加入iomanip.h頭文件
5.續行符:“\” 續接過長的語句
6.C Plus中可以隨時聲明定義變量(C中要求要統一列與程序前幾句)
7.結構、聯合和枚舉名可以直接作爲類型名
8.const限定符:
與宏(#define)的區別:define是完全替換,不作整合總結
const 是先整合,後替換
默認爲 const (int)i = 2100;
*這個常量i是有類型的 ,佔存儲單元的,有地址的,可以用指針指向它,但是不能修改它(常量的不可修改性)<內存空間被固化>
#*#
A. const char * name = “lin”; 指向常量的指針 固化指針所指向的空間
B. char * const name = “lin”; 常指針 固化指針的指向
C. const char * const name = “lin”; 指向常量的常指針 固化指針的指向和指針所指向的空間
&*&%:void型指針;void * p; 不確定類型 “通用型指針”
獲值後,必須進行顯示轉換處理
9.內聯函數:空間換時間!!
inline int show (int i, int j)“聲明和函數體都必須加上inline關鍵字”
A. 內聯函數體內不允許出現循環和switch語句(if可以出現)
B.內斂函數具有宏定義的相似機理,但更安全
宏:先替換語句,後傳遞參數
內斂函數:先傳遞參數,後替換語句
10.帶缺省參數的函數:
int abc (itn x = 5,float y = 5.3);
調用:abc(100,79.8);//x = 100, y = 79.8
abc(25);//x = 25,y = 5.3
abc( );//x = 5,y = 5.3
*聲明函數時,所有指定默認值的參數都必須出現在不指定默認值的參數的右邊(實參和形參的結合時從左到右順序進行的),因此指定參數的形參必須放在參數列表中的最右端
int fun(int i,int j = 5,int k); //Error!!
*調用時,不允許出現某個參數省略,再給其後參數指定參數值的情況
abc( , 21.5); //Error!!
*函數聲明和定義中只要求出現一次缺省值的定義就可以了,必須是在調用以前定義好,之後不必要再定義一次,否則會出現“重複指定默認值”的錯誤信息
11.函數重載: 靜態的多態
前提:同一作用域中,形參個數和形參類型不同(與返回值無關)
*重載函數中儘量不要使用缺省值,可能引起二義性
void abc(int i = 0,int j = 0,int k = 0);
void abc(int i);
調用:abc(20);
編譯系統無法確定調用哪一個函數
*重載函數調用時,若給出的實參與形參類型不相符,C++編譯器就會自動做類型轉換工作,有可能的錯誤:
void abc(int x);
void anc(long x);
調用:abc(5.56);
編譯無法確定應該將5.56轉換爲何種類型
12.(作用)域運算符: “::”
“擴大/改變局部變量的作用範圍”
::aver 調用已被局部同名變量覆蓋的全局變量
13.無名聯合: 類比匿名對象
C++一種特殊聯合(union),它在關鍵字union後面沒有給出聯合名,它可是一組數據成員共享同一內存地址
14.new/delete運算符:
A.new運算符用於創建堆空間(類似C中的malloc函數“動態分配”)
**分配成功返回空間首地址,失敗提示異常(VS中)(而不是NULL空指針(VC中))**
語法:a.指針變量 = new 數據類型; 創建一個數據類型的元素
Int *p;p = new int;
b.指針變量 = new 數據類型[長度n]; 創建n個數據類型的元素
Int *p;p = new char[50];//數組大小必須寫,new不能爲之初始化
# new動態分配多維數組空間:
Int *p = new int[2][3][4]; 必須提供所有維度的大小
*其中,第一維可以使任意整數表達式
Int i =3;
Int *p = new int[i][3][4];
c. new 可以在爲簡單變量分配內存的同時,進行初始化
指針變量 = new 數據類型(初值)
Int *p;p = new int(99); *p = 99
B.delete運算符用於釋放堆空間(類似C中的free函數)
語法:delete 指針變量;
delete[] 指針變量
C. new一個新對象: 1>.內存分配(operator new)
2>.調用構造函數
Delete釋放一個對象: 1>.調用析構函數
2>.釋放內存(operator delete)
15.引用: 給變量起別名 本質是指針
類型 & 引用名 = 已定義的變量名
Int i= 5;
Int & j = i; //j是i的引用,j與i共用一段存儲空間,改變i、j任何一個都會改變對應內存空間上的值
*a.除用作函數參數或返回值類型外,聲明引用時,必須立即對它進行初始化
*b.可以爲一個變量起多個別名
Int i= 5;
Int & j = i;
Int & k = j; //i、j、k共用一段空間,引用j、k本身是不分配內存的
*c.引用在初始化後不能再被重新聲明爲另一個變量的引用(別名),亦即一個別名不能被多個變量同時使用
*d.不允許建立void類型的引用
*e.不能建立引用數組
*f.不能建立引用的引用,不能建立指向引用的指針
Int n = 3;
Int && r = n;//Error!
Int &* p = n;//Error!
*g.可以將引用的地址賦給一個指針,此時指針指向原來的變量
Int num = 50;
Int & ref = num;
Int * p = & ref;//*p = num &在此爲取地址運算符,而不是引用運算符
#引用的最大用處體現在函數返回值和形參上
函數聲明的時候變量名可以省略,但是在函數體第一行中不能省略
第三章 類和對象
2014年6月25日19:37:29
類是一種數據類型,是抽象概念,而對象是自然實體
1.訪問控制符:信息隱藏
public:類與外部接口,任何外部函數都可以訪問共有類型數據和函數
private:只允許本類中的函數訪問,二類外部的任何函數都不能訪問
protected:與private相似,其差別表現在繼承與派生時對派生類的影響不同
*訪問控制符對當前類內部無效
2.成員函數的定義:
A.類內部,訪問控制符下直接定義
B.類外部,在函數名前加上類名和域限定符(如:int Point :: get())
<::get()和get()都是普通函數>
此種情況下,在類中的聲明可以不帶形參變量名,但有要類型
在類外定義成員函數又稱作 隱式內聯聲明
*以內聯方式定義成員函數體(顯式內聯)
類中聲明時,函數返回值前的inline關鍵字可以略去
但類外定義的函數體第一行前面的inline關鍵字不能省去
“較短的函數才適合內聯函數,必須滿足內聯函數的使用前提”
3.類與結構體的區別:未指定訪問權限時,class默認是私有的,而struct是公有的
結構體是一種特殊的類!
4.對象的定義和使用:
1》.定義方式:
A.全局對象定義:類似全局結構體
在類的定義之後,立刻定義對象,隨後用分號封裝類
如:
class Point{
……
}op1,op2;
op1 和op2就是全局定義的對象
B.聲明類以後,在使用時定義對象,若在main函數以內,則爲局部對象
Point op1,op2;
在沒有重寫構造函數的情況下,此種對象定義已經分配好堆空間,這是與Java中不同的地方
2》.訪問方式:
A.對象名.數據成員名
對象名.成員函數名[(實參表)]
*其中“.”稱作“簡點運算符”
op1 .get(); 完全等價於 op1 .Point :: get();
B.指向標識符:類似結構體指針
如:
Data d,*p;定義了指向類Data的指針變量p
p = &d;//使指針p指向對象d
Cout << p ->year; //輸出p指向對象中的成員year
5.構造函數和析構函數:
1》.構造函數是特殊的成員函數
創建類類型的新對象,系統會自動調用構造函數
其存在意義是:保證對象的每個數據成員都能被正確的初始化
定義: 1、函數名與類名完全相同
2、不能定義構造函數的類型(返回值類型),即使是void
3、通常情況下構造函數爲共公有函數(私有構造函數有特殊用途)
4、可以有任意類型和任意個數的參數,一個類可以有多個構造函數(重載),一個類至少有一個構造函數
**在建立對象的同時,採用構造函數給數據成員賦初值,通常有兩種形式:
形式1:
類名 對象名[(實參表)] 如:Point A(1.1,2.2);
形式2:
類名 * 指針變量名 = new 類名[(實參表)] 使用new運算符建立的是動態對象 如:Point * pa = new Point(1.1,2.2);該對象是無名對象,但是該對象有地址,這個地址存放在指針變量pa中,訪問用new動態建立的對象一般不用對象名,而是通過指針訪問
cout << pa->get() << endl;
#注意動態分配的堆空間一定要使用delete運算符釋放空間!!!
構造函數的調用:1、構造函數由系統自動調用(不需要用“對象名.”形式調用,初始化對象同時調用構造函數)
2、一個對象僅能調用一次(一個)
3、構造函數可以不帶參數
2》.成員初始化列表初始化數據成員:
帶有成員初始化列表的構造函數的一般形式:
類名 ::構造函數名[(實參表]) [ : (成員初始化列表)]
{
//構造函數體
}
成員初始化列表的一般形式:
數據成員名1 (初始值1),數據成員名2(初始值2),……
舉例類外成員函數:
Point ::Point(double r,double i) : real(r),imag(i)
{
}
*應用:const修飾的數據成員 或是 引用類型的數據成員(以及其他不允許使用賦值語句賦值的情況)
*#*類是一個抽象,系統不會爲之分配空間,是以,在類的定義中,不能進行賦值初始化操作!!!!
3》.析構函數:
定義; 1、函數名和類名類似,前面加一個字符“~”
2、沒有返回類型
3、沒有參數
4、若沒有定義析構函數,則系統會自動生成一個默認析構函數,撤銷對象時,自動調用,無需對象名調用
注意:1、析構函數與delete的區別:
如:
Test *t = new Test[2];//創建兩個對象,未賦值
Delete [] t;
運行的結果是:Initializing……
……
Destory……
……
亦即:析構函數是真正的釋放內存空間,而delete是銷燬內存空間
*在棧區中創建的對象,在生存期結束時會自動調用析構函數
*在堆上創建的對象,要有程序員顯式調用delete釋放該內存空間
2、析構函數可以顯式調用:
如:
Test t;//在對象生命期結束以後,系統會自動調用析構一次
t. ~Test();//亦即,此處析構第二次
6.對象數組和對象指針:
1》.對象數組;
類名 數組名[下標表達式]
如:
Point p[10];
賦值:Point p[4] = {11,22,33,44};
多個值的情況:
Point p[3] = {
Point(11,22);
Point(33,44);
Point(55,66);
};
訪問:數組名[下標].成員名 如:ob[2].get();
*每個數組元素消亡時,都會自動調用一次析構函數
2》.對象指針:
類名 * 對象指針名
如:
Point op;//定義了一個對象
Point *p;//定義了一個對象指針變量p
p = &op;//使對象指針指向對象op(由於類是一種數據類型,指針的類型與其所要指向的變量的數據類型必須一致,所以定義時候用相同的類進行定義)
p ->get();//調用成員函數
*用對象指針訪問對象數組元素時,必須要對指針指向進行移位
3》.this指針:自引用指針
This指針是一個const指針,不能進行修改和賦值
This指針式一個局部數據,其作用域僅在一個對象內部
7.向函數傳遞對象:
1、使用對象作爲函數參數:
void abc(Point opp);//定義對象作爲形參以接收傳參
2、使用對象指針作爲函數參數:
定義:void abc(Point *opp);//定義對象指針作爲形參以接收傳參
調用:abc(&op);//傳入對象的地址
3、使用對象引用作爲函數參數:
定義:void abc(Point &opp);//定義對象引用作爲形參以接收傳參
調用:abc(op);
8.對象的賦值和拷貝:
1、對象的賦值:淺拷貝
Point p1,p2;
P1.set(20,5);
P2 = p1;
*賦值運算符已經重載
注意: a.兩個對象的類型(數據類型/類類型)必須相同
B.賦值以後兩個對象時相互分離的,互補影響
C.此種賦值方式在類中含有指針的時候可能會產生錯誤
2、對象的拷貝;
拷貝構造函數:深拷貝
使用一個已經存在的對象來初始化一個新的統一類型的對象
聲明:只有一個參數,並且參數爲該類對象 的引用
*倘若類中沒有說明拷貝構造函數,則系統自定生成一個缺省拷貝構造函數
定義:
類名 ::類名(const 類名 & 對象名)
{
//拷貝構造函數的函數體
}
如:(在Point類中)
Point(const Point &p)
{
X = 2*p.x;
Y = 2*p.y;
}
調用:
Test t(10);
Test tt(t);//若未定義拷貝構造函數,則系統自動調用默認拷貝構造函數,完全複製
*參數爲引用,減少內存的複製,對象的拷貝,可加速
拷貝構造函數的調用情況: 1、當用一個對象去初始化另一個對象時,拷貝構造函數會被自動調用 Point p2(p1)代入法 或者 p2 = p1;
2、當函數的形參是類的對象,在調用函數進行形參和實參結合時,拷貝構造函數將會被調用
3、當函數的返回值是類的對象,在函數調用完畢將返回值(對象)帶回函數調用處時,此時就會調用拷貝構造函數,將此對象複製給一個臨時對象並傳到該函數的調用處。
9.靜態成員:
非static數據成員存在於類類型的每個對象中,static數據成員獨立該類的任意對象存在,它是與類關聯的,不與類對象關聯
Static成員優點:1、成員的名字在類作用域中,因此可以避免與其他類成員或全局對象名字衝突
2、可以封裝,static成員是私有的,而全局對象不可以
定義:
Static 數據類型 數據成員名
static成員需要在類定義體外進行初始化與定義
特殊的整型static const成員:其可以在類定義體中初始化該成員,可以不在類體外定義
Static成員函數: 1、無this指針
2、非靜態成員函數可以訪問靜態成員,反之,不可
10.友元: 破壞信息隱藏的一種手段
友元是一種允許非類成員函數訪問類的非公有成員的一種機制
可以把一個函數指定爲類的友元,也可以把整個類指定爲另一個類的友元
友元的作用在於:提高程序的運行效率
1〉.友元函數:
友元函數在類作用域外定義,但它需要在類體中進行說明
爲了與該類成員函數想區別,定義的方式是在類中用關鍵字friend說明該函數,格式如下:
friend 類型 友元函數名 (參數表)
注意事項:
1/友元函數不是類的成員函數,在函數體中訪問對象的成員,必須用 對象名 + 運算符“.” +對象成員名
但友元函數可以訪問類中的所有成員,而一般的函數只能訪 問類中的公有成員
2/友元函數不受類中訪問權限關鍵字的限制,可以把類的公有,私有,保護部分,但是結果一樣
3/某類的友元函數的作用域並非該類作用域,若該類的友元函數是另一類的成員函數,則其作用域爲另一類的作用域,否則與一般函數相同
4/友元函數破壞了類的封裝性,是以,友元函數鄙視必須是以哦那個,則儘可能少使用。或用其他手段保證封裝性
2〉.友元類:
若某類B的成員函數會頻繁的存取另一個類A的數據成員,而A的數據成員的 Private/Protected 限制會造成 B 的存取麻煩, B只能通過 A 的Public 成員函數 進行間接存取
1/把 B 做成 A類的友元類,即 A類 向 B類 開發 其 private/protected 內容 ,讓B直接存取
2/友元類:一個類可以作爲另一個類的友元
3/友元類的所有成員函數都是另一個類的友元函數
4/友元類的聲明:
friend class 類名
5/注意事項:
1.友元關係是單向的(A是B的友元類,並不代表B是A的友元類)
2.友元關係不能被傳遞(A是B的友元類,B是C的友元類,並不代表A是C的友元類)
3.友元關係不能被繼承(A是B的友元類,C繼承自A,並不代表C是B的友元類)
11.類的組合(對象的嵌入):
在一個類中內嵌另一個類的對象作爲數據成員,是爲 類的組合,該內嵌對象稱爲對象成員
如果一個類具有內嵌的對象成員,那麼內嵌對象成員也將被自動創建,因此,在創建對象時既要對本類的基本數據成員初始化,又要對內嵌對象成員進行初始化
如:
class {
類名1 對象成員1;
類名2 對象成員2;
……
類名n 對象成員n;
};
一般來說,類X的構造函數的類外定義形式爲:
X :: X(形參表0) : 對象成員1(形參表 1),對象成員2(形參表2),…… //後半部分稱作初始化表
{
//類X的構造函數體
}
當調用這個構造函數的時候,首先按各內嵌對象成員在類聲明中的順序依次調用它們的構造函數,對這些對象進行初始化,然後再執行類X的構造函數體,初始化類X中的其他成員,析構函數的調用順序與之相反,這是一種壓棧思想。
12.常類型: 再談const
1〉.常引用:const 類型 & 引用名
如:
Int a = 5;
const int & b = a;
則b是一個常引用,它所指向的對象不允許更改(固化引用指向的內存空間),即出現 b = 12是非法的
實際中常引用被用作形參,此爲常形參,便不會產生對實參的不希望的更改(形參不允許更改)
2〉.常對象:
類名 const 對象名[(參數表)];
或者
Const 類名 對象名[(參數表)];//意味着該對象是一個常量
常對象的數據成員值在對象的整個生存期內不能被改變
在定義對象時必須被初始化,而且不能被更新
*注意: 通過const對象只能調用它的const常成員函數,而不能調用它的普通成員函數,常成員函數是常對象唯一的對外接口,此爲C++從語法機制上對常對象的保護(構造和析構除外)
*常對象的數據成員不允許被普通成員函數訪問和改變值
*常成員函數可以訪問普通數據成員,常數據成員和常對象的數據成員,但是,均不允許改變其值
*用mutable修飾的數據成員即使是在const對象或在const成員函數中都可以被修改!!!
3〉.常對象成員:
A.常數據成員: 只能在初始化時,改變值
如果在一個類中說明了常數據成員,(不允許任何賦值),那麼構造函數就只能通過成員初始化列表對該數據成員進行初始化,而其他任何函數都不能對該成員賦值
B.常成員函數:
類型說明符 函數名 (參數表) const;
Const是函數類型的一個組成部分,因此在聲明函數和定義函數時都要有關鍵字const,而在調用時不必加const
常成員函數不會修改對象的狀態
常成員函數只能訪問數據成員的值,而不能修改它
常成員函數可以與非常成員函數構成重載
在不要求修改數據數據成員的值時,儘量使用const成員函數
第四章 派生類和繼承
2014年6月28日13:18:49
1.關於繼承和派生: 繼承——提高軟件/代碼的重用性
繼承:新類具有基類中已有屬性和方法
派生:在基類基礎上加上滿足新類特定要求所需的新成員
基類稱爲父類,子類稱爲派生類;
2.派生類的定義:
格式:
class <派生類名>:[<繼承方式>] <基類名>
{
<派生類新定義>
};
其中<繼承方式>有:
public
private
protected
繼承方式的不同,確定了派生類成員以及類外對象對於從基類繼承來的成員的訪問權限
若爲public繼承方式,則派生類成員和類外對象都可以訪問父類中的非私有成員
若爲protected繼承方式,則僅派生類成員可以訪問父類成員,而類外對象並不能訪問父類中的非私有成員
若爲private繼承方式……(具體看錶格)
基類中成員在派生類中的訪問權限
繼承方式 |
基類特性 |
派生類特性 |
派生類中的成員函數 |
派生類的對象 |
公有繼承 |
public protected private |
public protected 不可訪問 |
可訪問基類中的公有成員 和保護成員 |
可訪問基類和派生 類中的公有成員 |
私有繼承 |
public protected private |
private private 不可訪問 |
可訪問基類中的公有成員 和保護成員 |
不能訪問基類中的 所有成員 |
保護繼承 |
public protected private |
protected protected 不可訪問 |
可訪問基類中的公有成員 和保護成員 |
不能訪問基類中的 所有成員 |
*默認爲private私有繼承
例:如果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);
};
注意:
基類中的成員函數可以訪問基類的所有成員,但不能訪問派生類的成員
基類的對象只能訪問基類的公有數據成員和調用基類的公有成員函數,基類對象不能訪問派生類的成員
派生類中的成員函數可以訪問派生類的所有成員,也能訪問其基類的public成員和protect成員
派生類的對象能訪問派生類的公有數據成員和調用派生類的公有成員函數,公有繼承的派生類對象還能夠訪問其基類的public成員和protect成員
****C++的“繼承”特性可以提高程序的可重用性。
要防止亂用“繼承”:
1、如果類A 和類B 毫不相關,不可以爲了使B 的功能更多些而讓B 繼承A 的功能。
2、如果類B 有必要使用A 的功能,則要分兩種情況考慮:
(1)若在邏輯上B 是A 的“一種”(a kind of ),則允許B 繼承A 的功能。
(2)若在邏輯上A是B的“一部分”(a part of),則不允許B 繼承A 的功能,而是要用A和其它東西組合出B 。
*繼承的好處:
複用編好的代碼和設計好的數據結構,而不用重複編寫(軟件重用和接口重用);
使程序易於維護,因爲相關的數據和代碼集中放在某處;
有效模擬了實際生活中的許多關係,如人與男人,交通工具和汽車等。
3.派生類對象的初始化:
定義了一個派生類後,它將繼承其基類中的全部成員。則派生類對象中不僅包含派生類的數據成員,還包含從父類繼承的全部數據成員。
*派生類對繼承而來的成員的調整:
1/改變基類成員在派生類中的訪問屬性(通過繼承方式實現)
2/重定義基類中的非私有成員(覆蓋定義)
<注意:倘若是重定義成員函數,不僅應使函數名相同,還應使函數的參數表也相同,否則視作重載>
3/訪問聲明:私有繼承後者保護繼承時,在派生類的公有關鍵字下將基類的非私有成員通過作用域運算符調整爲公有繼承
如:class B :private A{
Public:
……
A ::print;//void print()在A類中爲公有成員函數,此處調整爲私有派生類B的公有成員函數
*派生類對象的初始化:與類的組合相對照理解
派生類對象初始化時,既要對派生類本身的數據成員初始化,又要對基類繼承來的數據成員初始化。
C++語言定義派生類構造函數的格式爲:
派生類名(參數總表(包括類型)):父類名(參數表) ;
//冒號前爲定義當前類的構造函數,冒號後爲調用基類的構造函數,是以,形參名前不用綴上數據類型
在冒號“:”後面是父類構造函數表(原則上有多少個父類就有多少個父類構造函數),以及對象成員初始化列表。
*特殊:含有對象成員(子對象)的派生類的構造函數:(即類的繼承與類的組合相結合)
派生類名(參數總表(包括類型)):父類名(參數表0) ,對象成員名1(參數表1),……對象成員名n(參數表n);
冒號前爲定義當前類的構造函數,需要綴上數據類型,而冒號後面爲單純的調用,所以不需要綴上數據類型
調用順序由它們在類中的聲明順序決定
注意:
(1) 當使用基類參數的構造函數來完成基類成員的初始化時,即使派生類構造函數本身無需完成任何工作(函數體爲空),也必須定義派生類的構造函數。
(2) 如果基類定義有缺省構造函數,則在派生類構造函數可省略基類初始列表。
(3) 派生類構造函數的執行順序是:先父母(執行基類構造函數),再客人(初始化對象成員),最後自己(初始化派生類本身的普通數據成員)。
(4) 派生類析構函數的執行順序與派生類構造函數的執行順序剛好相反
4.多重繼承:
定義:
多繼承下派生類的定義格式如下:
class <派生類名>:<繼承方式1> <基類名1>,<繼承方式2> <基類名2>,......
{
//<派生類新定義的成員>
};
多個基類名間用逗號隔開,繼承方式同單繼承。
多重繼承的初始化:
多繼承下派生類的構造函數成員初始化列表中應包含所有基類的構造函數,其定義格式:
<派生類構造函數名>(參數總表):<基類名1>(參數表1),<基類名2>(參數表2),…,<對象成員名>(參數表n)
多重繼承派生類構造函數的執行順序(析構函數相反):
所有基類的構造函數(基類間以定義派生類時順序爲準)
對象成員的構造函數
派生類的構造函數
*類比單繼承理解
5.虛基類:
*C++繼承機制:
不允許繼承自身
不允許同時直接繼承和間接繼承同一個基類
*1/多繼承的二義性:
二義性:無法確定性、不唯一性
A.多重繼承中可能出現的兩種二義性:
同名引起的二義性:當多重繼承的派生類的不同父類中含有同名成員時,這些成員都會被派生類所繼承,派生類在使用這些成員,就會產生二義性。
B.公共基類帶來的二義性(重複繼承,直接+間接)
同一數據被繼承兩次,導致的二義性
解決二義性方法:作用域運算符:: ,虛基類
*作用域運算符解決二義性的方法:
調用疑似同名的成員時,在成員名前綴上所屬的類,具體哪個類的成員
**虛基類: 注:虛基類的特性只有在重複繼承下才能體現。
虛基類使派生類從公共基類只繼承一個數據成員,即從不同路徑繼承過來的同名數據成員和成員函數在內存中只有一個拷貝。
虛基類是對派生類進行聲明,格式爲:
Class 派生類名 : virtual 繼承方式 基類名
{
……
};
虛基類的初始化:
與一般的多繼承語法一樣
注意:
(1)必須在最新派生出來的派生類的初始串列中,調用虛基類的構造函數,以初始化在虛基類中定義的數據成員;
(2)虛基類的構造函數由最新派生出來的派生類負責調用,其構造函數僅調用一次;
(3)初始串列中各個基類構造函數的調用順序是:先調用虛基類構造函數,然後調用非虛基類構造函數;
(4) 如果在最新派生出來的派生類的初始串列中,沒有顯式調用虛基類構造函數,則編譯程序將調用虛基類的缺省構造函數。
(5) 關鍵字virtual和派生方式關鍵字的先後順序無關緊要,它只說明是“虛擬派生”
(6) 一個基類在作爲某些派生類虛基類的同時,又作爲另一些派生類的非虛基類,這種情況是允許存在的。
6.賦值兼容規則:
在一定條件下,不同類型的數據之間可以進行類型轉換,例如可以將整形數據賦給雙精度型變量,在賦值之前,先把整形數據轉換爲雙精度數據,然後再把它賦給雙精度變量。這種不同類型數據之間的自動轉換和賦值,稱爲賦值兼容。
定義:用公有派生類對象替換基類對象;
理由:公有繼承的派生類有基類中除構造函數和析構函數外的所有成員和相應的訪問權限;
規則中的替換包括:
派生類的對象可以賦值給基類對象
派生類的對象可以初始化基類的引用
派生類對象的地址可以賦給指向基類的指針
例:class B{};
class D:public B{};
B b1,*ptr;
D d1;
下列操作是正確的: b1=d1;
B &b2=d1;
ptr=&d1;
注意:
1/聲明爲指向基類對象的指針可以指向它的公有派生的對象,但不允許指向它的私有派生對象
2/允許將一個聲明爲指向基類的指針指向其公有派生類的對象,但是不能將一個生命爲指向派生類對象的指針指向其基類的一個對象。
第五章 多態性
2014年6月28日17:55:59
1.話說多態:
多態:
不同對象收到相同的消息時,產生不同的響應。
通俗地說:
同一個函數名具有不同的實現(如函數重載)
同一個運算符具有不同的功能(如運算符重載)
C++支持兩種多態性:
編譯時的多態性:靜態聯編--重載
運行時的多態性:動態聯編--繼承和虛函數
“同一接口,多種方法”
多態性的好處:實現了更高級、更自然的抽象
進一步減少了信息冗餘
提高了程序的可重用性、可擴充性和可維護性
2.重載:
“重載”是面向對象程序設計的基本特點之一;
“重載”類似於自然語言中的“一詞多義”;
在進行面向對象的程序設計時使用兩種形式的重載:
函數重載
運算符重載
重載(Overloading):
函數重載是指在同一作用域內的若干個參數特徵不同的函數可以使用相同的函數名字
運算符重載:
運算符重載是指同一個運算符可以施加於不同類型的操作數上面。
例如:11/4=2 和 11.0/4.0=2.75
這裏運算符“/”的意義不同。
對於重載的運算符,也是在編譯時,根據被操作數的類型,決定使用該運算符的哪種語義
3.運算符重載:
目的:使得運算符的功能,能夠針對新的數據類型而進行改進
C++語言中運算符重載實際上是通過定義重載的運算符成員函數或友元函數來完成的。
通常把重載運算符的成員函數或友元函數,統稱爲運算符函數
運算符重載成員函數定義:
〈函數返回值類型〉 operator <運算符> ( 形參表) { … }
運算符重載友元函數定義:
friend <函數返回值類型> operator <運算符 >(形參表){ …}
說明:
<返回類型>爲運算符函數運算結果的返回值類型;
operator爲運算符重載時必須使用的關鍵字,它和被重載的運算符連在一起,作爲運算符函數的專用函數名;
*運算符重載成員函數的形參:
1/單目運算符重載(++、--、!、~、-(負號))
前置單目(++i),參數表爲空
void operator ++();
後置單目(++、--),參數表中有一個整型形參
void operator ++(int);
在這種情況下,當前對象(即調用該運算符函數的對象)作爲該運算符唯一的操作數。如下調用(myX爲X的對象):
++myX myX.operator++()
myX++ myXoperator++(0)
2/二元(二目)運算符重載
有一個參數。此時當前對象作爲該運算符的左操作數,參數作爲右操作數。
如前例:
複數對象c1,c2,c3有操作 c3=c1+c2 , 等價於
c3=c1.operator+(c2)
*運算符重載爲類的友元函數:
class X{
friend <函數返回值類型> operator <運算符 >(形參表){ …}
}
1/在函數原型加關鍵字friend。
2/由於是友元函數,因此在類的公有段和私有段說明此函數的效果相同。
3/用友元函數重載運算符時參數表中參數個數,比用成員函數重載運算符時參數表中的參數個數多一個。
*運算符重載友元函數形參:
1、 單目運算符
1/ 前置單目:一個形參
形參爲類類型,是此運算符的操作數
2/後置單目:兩個形參
一形參爲類類型,是此運算符的操作數;另一形參爲int
2、 二元運算符:
兩個參數,分別爲左右操作數
例:複數自加運算c1++,相應的運算符重載友元函數可爲
void operator++(Complex& o,int t)
{ o.real=o.real+1;
o.imag=o.imag+1;
}
*賦值運算符重載:
賦值運算符是雙目運算符
可以使用缺省的賦值運算符實現類的賦值
如:Complex c1(20,10),c2;
c2=c1
表示將c1的數據成員逐個賦給c2的對應數據成員,即
c2.real=20;
c2.imag=10;
C++中 “=”, “&”, “,” 三種運算符,不必重載可直接使用
*對運算符重載的限制:
C++語言對運算符重載規定了下述一些限制:
(1) 只能重載C++語言中已有運算符;
(2) · ,* ,∷ ,?:四個運算符不能重載;
(3) 不能改變原運算符的操作數個數;
(4) 不能改變原運算符原有的優先級和結合特性;
(5) 不能改變原運算符對預定義類型數據的操作方式。
(6) =,→,(),[] 四個運算符只能爲成員函數重載,不能爲友元函數重載。
(7) >>,<<兩個運算符只能爲友元函數重載,不能爲成員函數重載
(8)不可使用缺省參數
(9) 賦值運算符重載不能繼承
*成員重載函數必須定義爲公有,而友元重載函數訪問控制權限可隨意,因爲它不屬於所在的類
4.再談指針懸掛的問題:
-*解決指針懸掛的方法:
1/運算符重載
通過重載賦值運算符可以解決指針懸掛問題;
C++規定:賦值運算符必須使用成員函數重載,且重載賦值運算符的成員函數operator=不能被繼承。
解決上述問題的指導思想是:
(1)在賦值之前,先釋放s1.p原先指向的內存空間;
(2)s1.p重新申請內存空間;
(3)內容傳遞:對目的對象s1的數據成員指針p(即s1.p)的賦值,是把源對象s2的數據成員指針p (即s2.p)所指向的內容傳遞給它,而不是簡單地傳遞指針值;
2/使用拷貝函數
5.類型轉換: 就是將一種類型轉換爲另一種類型值
(1)隱式類型轉換: 當兩個操作數類型不一致時:
int a;
double d = 13.5;
a = d;
cout << a << endl;
在編譯第三句時出現警告:
warning : conversion from ‘ double’ to ‘ int’, possible loss of data
(2) 顯式類型轉換
顯式類型轉換有兩種方式:
強制轉換法 (類型名)表達式
函數法 類型名(表達式)
*類類型與系統預定義類型間的轉換:
1/轉換構造函數:
作用:將一個其他類型的數據轉換成它所在類的對象
單參數的構造函數,具有將參數類型轉換爲該類類型的功能;
例:
class D
{ public:
D( ) { d=0; cout<<“缺省構造函數\n"; }
D( double i) { d=i; cout<<“單參數構造函數\n";}
void Print( ) { cout<<d<<endl; }
private:
double d;
};
void main()
{ D myD;
myD = 12; // 類型轉換
myD.Print( );
}
特徵: 1.單個參數的構造函數
2.將其他類型轉換爲類類型
3.類的構造函數只有一個參數是非常危險的,因爲編譯器可以使用這種構造函數把參數的類型隱式轉換爲類類型
4.
*帶一個參數的構造函數: 1.普通構造函數(初始化)
2.轉換構造函數(初始化 + 類型轉換)
*explicit:
只提供給類的構造函數使用的關鍵字
作用;阻止隱式轉換
編譯器不會將聲明爲explicit的構造函數用於隱式轉換,它只能在程序代碼中顯式創建對象,亦即作用僅限初始化
*話說初始化:
1.構造函數的初始化列表:〈推薦在該列表中進行初始化〉
構造函數的執行分爲兩個階段; 1>.初始化階段
2>.普通計算階段
2.對象成員及其初始化:
若一個對象沒有默認構造函數,則必須在初始化列表中進行初始化
3.const成員/引用成員的初始化:
1〉.常量必須在初始化列表中進行初始化(const成員)
2〉.引用成員的初始化也只能在初始化列表中進行初始化
3〉.對象成員(對象所對應的類沒有默認構造函數,也只能在初始化列表中進行)
2/類型轉換函數:
定義類類型轉換函數的格式如下:
operator 目的類型( );
目的類型(即要轉換成的類型)既可以是自定義類型也可以是預定義類型。
class Try
{ int a;
public:
Try(int a1){a=a1;}
operator double(){ return double(a); }
};
void main()
{ Try t(10);
double s1=4.5,d;
d=s1+t;
cout<<d<<endl;
}
注意: 1/類型轉換函數只能定義爲一個類的成員函數而不能定義爲類的友元函數。類型轉換函數也可以在類題中聲明函數原型,而將函數體定義在類的外部
2/類型轉換函數既沒有參數,也沒有在函數名前面指定函數類型
3/類型函數中必須有return語句,即必須送回目標類型的數據作爲函數的返回值
4/一個類可以定義多個類型轉換函數。C++編譯器將根據類型轉換函數名自動地選擇一個合適的類型轉換函數
6.虛函數:
虛函數是重載的另一種表現形式,這是一種動態的重載方式
虛函數允許函數調用與函數體之間的聯繫在運行時才建立,也就是在運行時才決定如何動作,即所謂的動態聯編
1/引入:
在靜態聯編時,編譯程序根據指針的聲明(例如base *ptr),認定ptr只能指向對象中基類的空間,因此ptr只能找到並調用Base::who();
問題:ptr指向obj2對象時,如何通過ptr調用派生類中的who()函數?
2/定義:
虛函數的聲明:virtual 函數原型
class Base
{ public:
virtual void who()
{ cout<<“Base"<<endl; }
};
class Derive : public Base
{ public:
void who()
{ cout<<“Derive”<<endl; }
};
void main(){
Base obj1, *ptr;
Derive obj2;
ptr = &obj1;
ptr->who();
ptr = &obj2;
ptr->who();
}
結果:
Base
Derive
3/虛函數改變了聯編方式;
將成員函數聲明爲虛函數相當於告訴編譯程序:由指針實際指向的對象類型決定調用哪個類中定義的函數
4/使用虛函數時的注意事項:
(1)在基類(不一定是最高)中聲明虛函數,用虛函數實現多態性時,派生類應從基類公有派生;
(2)派生類中同型(與基類虛函數原型完全相同)的成員函數自動成爲虛函數;
(3)只有非靜態成員函數可以聲明爲虛函數;
(4)虛函數聲明只出現在類聲明中的函數原型中,而不能在成員的函數體實現的時候;
(5)虛函數由成員函數調用或通過指針、引用來訪問;
5/虛析構函數:
* 構造函數可以重載
析構函數不能重載
構造函數不能虛化
析構函數可以虛化
class A{
public:
A( ){ }
~A( ) { cout<<"~A() is called!"<<endl; }
};
class B : public A
{ int *p;
public:
B( ) { p=new int[10]; }
~B( )
{ delete []p;
cout<<"~B() is called!"<<endl;
}
};
void main( )
{ A *a=new B; delete a; }
運行結果:~A() is called!
原因:指針a是基類指針,delete a只調用基類A的析構函數,不調用派生類B的析構函數,從而導致指針p指向的空間沒有釋放。
改進:
class A{
public:
A( ){ }
virtual ~A( ) { cout<<"~A() is called!"<<endl; }
};
class B : public A
{ int *p;
public:
B( ) { p=new int[10]; }
~B( )
{ delete []p;
cout<<"~B() is called!"<<endl;
}
};
void main( )
{ A *a=new B; delete a; }
程序執行動態聯編的方式,在用delete運算符撤銷派生類的無名對象時,先調用派生類的析構函數,再調用基類的析構函數,可以將基類的析構函數聲明爲虛析構函數。
6/純虛函數和抽象類:(相當於java 中德抽象方法)
如果在基類中不能爲虛函數給出一個有意義的定義,則可以將其說明爲純虛函數;
純虛函數的定義格式如下:
virtual <返回類型> <函數名>(形參)=0;
例:virtual void GetArea()=0;
純虛函數沒有具體實現,即在定義它的基類中不給出它的具體實現,而在其派生類中必須提供它的實現代碼;
純虛函數是一種特殊的虛函數,它僅起到爲派生類提供一個一致接口的作用;
注意與空的虛函數區別:
virtual void GetArea(){};
至少包含一個純虛函數的類稱爲抽象類;
抽象類只能作爲基類來派生新類,不能說明抽象類的對象,但可以說明指向抽象類對象的指針;
例:class Shape{
virtual void GetArea()=0;
};
Shape s1; Error!!
Shape *s2; Right!!
*注意:如果一個類至少有一個純虛函數,那麼就稱這個類爲抽象類
1〉.由於抽象類中至少包含有一個沒有功能的純虛函數,因此抽象類只能用作其他類的基類,不能建立抽象類對象
2〉.抽象類不能用作參數類型,函數返回類型或顯式轉換的類型,但可以聲明指向抽象類的指針變量,此指針可以指向它的派生類,進而實現多態性
3〉.如果在抽象類的派生類中沒有重新說明純虛函數,則該函數在派生類中仍然爲純虛函數,這個派生類仍然還是一個抽象類。
第六章 模板與異常處理
2014年6月28日 20:15:37
1.模板:
模板是一種對類型進行參數化的工具。
通常有兩種形式:函數模板和類模板
函數模板針對僅參數類型不同的函數;類模板針對僅數據成員和成員函數類型不同的類。
1/函數模板:
函數模板是對一組函數的抽象,定義函數模板的格式如下:
template <class T1,class T2,…>/〈typename 類型參數〉
<返回類型> 函數名(參數表)
{
<函數體>
}
注:class不是類,它是一個標誌,表示其後爲參數化的類型名。
與函數模板的參數表相匹配的函數調用稱爲一個模板函數。
在使用函數模板時,要先對模板參數實例化。
*注意:
1/在模板函數中允許使用多個類型參數,但是應當注意template定義部分的每個類型參數前必須有關鍵字typename(或class)
2/在template語句與函數模板定義語句之間不允許有別的語句。
3/模板函數類似於重載函數,只是更加嚴格,函數被重載的時候,在每個函數體內可以執行不同的操作,但同一函數模板實例化後的所有模板函數都必須執行相同的操作
4/同一般函數一樣,函數模板也可以重載
5/函數模板與同名的非模板函數可以重載
*函數模板與重載函數一起使用:
調用的約定:
(1)先尋找函數模板。
(2)如找不到相應的函數模板則找重載函數。
(3)如再找不到重載函數則進行強制類型轉換,此時可能丟失精度。
2/類模板:
類模板的定義格式:
template <class T1,class T2,…>/〈typename 類型參數〉
class 類名 {
…
};
類模板定義了一組類,這組類中數據成員的類型或某個成員函數的類型不侷限於某一個具體類型。
類模板的使用:用下列方式代替類名:
類名 <具體類型名>
在類體外定義的成員函數,應定義成函數模板
注意:
倘若在模板類中的成員函數需要在類外定義,C++油哦一下規定: 1.需要在成員函數定義之前加上模板聲明(就是類前的模板聲明)
2.在成員函數名前綴上“類名〈類型參數〉 ::”
在每個類模板定義前都需要在前面加上模板聲明
模板類可以有多個類型參數
2.異常處理:
程序運行中有些錯誤是可以預料但不可避免的,如內存空間不足、硬件上文件已被移動、打印機未連接好等系統運行環境造成的錯誤——運行環境錯誤;
有些函數本身無法排除錯誤,引發異常,由調用函數處理;
允許用戶排除錯誤,繼續運行程序;至少給出適當的提示信息,不能輕易出現死機,更不能出現災難性後果——容錯能力;
程序在運行過程中出現的錯誤統稱爲異常。
編程時充分考慮各種意外情況,並給予恰當的處理——異常處理。
傳統的異常處理方法:採用判斷、分支語句實現。
C++異常處理的方法是:
異常的判斷和處理不在同一個函數中進行
在被調用函數中出現異常,就發出信息(異常的引發),傳給它的上一級(調用函數) ,處理由調用函數來解決。如果調用函數處理不了,則異常可以一直向上傳播,直到被解決或由C++運行系統處理(自動調用運行函數terminate,由它調用函數abort終止程序)
採用異常引發和處理相分離的機制
三部分組成:檢查、拋出和捕獲
三個子句:try、throw、catch
異常的拋出:(被調用函數)
throw 表達式; 某段程序出現了異常,拋給調用者
異常處理:(主釣函數)
Try 檢查(引發)異常
複合語句 可能出現異常的語句放在try後
catch(異常類型聲明) 捕獲throw拋出的異常
複合語句 異常處理程序
catch(異常類型聲明)
複合語句
……
注意: 1:拋擲異常與異常處理程序間是按數據類型的嚴格匹配來捕獲的,如果程序中有多處要拋擲異常,則應用不同的操作數類型來相互區別,操作數的值不能用來區別不同的異常;
2:try後緊跟一個或多個catch塊,目的是對發生的異常進行處理;
3:異常處理的目的是儘可能減少因錯誤而造成的破壞,並妥善處理它們,而不去影響其它部分程序的執行。
第七章 C++流類庫與輸入輸出:
2014年6月28日 21:04:17
目的:把對象保存到磁盤文件中並從磁盤文件重建對象。
數據從一個對象到另一個對象的傳送被抽象爲“流”。數據的輸入/輸出就是通過輸入/輸出流來實現的。
1.C++的基本流類體系 :
流類體系:以抽象類模板basic_ios爲基類,流類模板派生體系見圖9.1。整個流類模板體系的標準I/O在頭文件<iostream>中說明,包含頭文件<ios>、<streambuf>、<istream>和<ostream>。而輸入輸出文件流部分在頭文件<fstream>中說明。
流類體系說明:basic_streambuf不是basic_ios的派生類,而是一個獨立的類,只是basic_ios有一個保護訪問限制的指針指向它。 類basic_streambuf的作用是管理一個流的緩衝區。 標準輸入/輸出流對象:在C++的流類庫中定義了四個全局流對象:cin,cout,cerr和clog。可以完成人機交互的功能。
cin標準輸入流對象,鍵盤爲其對應的標準設備。 cout標準輸出流對象,顯示器爲標準設備。
cerr和clog標準錯誤輸出流,輸出設備是顯示器。
其中cin、cout和clog是帶緩衝區的,緩衝區由streambuf類對象來管理。而cerr爲非緩衝區流,一旦錯誤發生立即顯示。要使用這四個功能,必須包含<iostream>文件。 重載的提取運算符“>>”和插入運算符“<<”,執行輸入/輸出操作。 “提取”的含義是指輸入操作,可看作從流中提取一個字符序列。 “插入”的含義是指輸出操作,可看作向流中插入一個字符序列。 cin使用提取運算符。cout、cerr和clog使用插入運算符。
2. 輸入/輸出的格式控制 :
格式提供了許多功能:設置顯示域寬、設置顯示精度、設置域填充字符、刷新流、設置和清除格式化標誌、插入換行符等。
C++在類ios中提供格式化輸入輸出。這些格式是對所有文本方式的輸入輸出流均適用。 輸入輸出流格式控制標誌:
protected:
int x_precision; //標誌浮點數精度,默認爲6位
int x_width; //輸出域寬,默認域寬爲0,
//重設域寬只對其後第一輸出項有效,如域寬不足,則不受限制 char x_fill; //標誌域寬有富餘時填入的字符
*************************************
常用流操作子表(*表示默認的流狀態):
操作符 含義
boolapha 把true和false表示爲字符串
*noboolalpha 把true和false表示爲0、1
Showbase 產生前綴,指示數值的進制基數
*noshowbase 不產生進制基數前綴
showpoint 總是顯示小數點
*noshowpoint 只有當小數部分存在時才顯示小數點
showpos 在非負數值中顯示“+”
*noshowpos 在非負數中不顯示“+”
*skipws 輸入操作符跳過空白字符
noshipws 輸入操作符不跳過空白字符
uppercase 在十六進制下顯示OX,科學計數法中顯示E
*nouppercase 在十六進制下顯示Ox,科學計數法中顯示e
*dec 一十進制顯示 hex 以十六進制顯示 oct 以八進制顯示
left 將填充字符加到數值的左邊
Right 將填充字符加在數值的右邊
Internal 將填充字符加到符號和數值的中間
*fixed 以小數形式顯示浮點數
scientific 以科學計數法形式顯示浮點數
flush 刷新ostream緩衝區
ends 插入字符串結束符,然後刷新ostream緩衝區
endl 插入換行符,然後刷新ostream緩衝區
以下這些參數化的流操作子要求#include<iomanip >
setfill(ch) 用ch填充空白字符
setprecision(n) 將浮點精度設置爲n
setw(n) 按照n個字符來讀或者寫
setbase(b) 以b爲進制基數輸入整數值
*************************************
注意:絕大多數流操作子僅適用於新的C++標準流類庫(頭文件不帶.h)。
3.標準設備的輸入/輸出 :
標準設備輸入使用要點:
1/. cin爲緩衝流。鍵盤輸入的數據保存在緩衝區中,當要提取時,是從緩衝區中拿。如果一次輸入過多,會留在那兒慢慢用,如果輸入錯了,必須在回車之前修改,如果回車鍵按下就無法挽回了。只有把輸入緩衝區中的數據取完後,纔要求輸入新的數據。不可能用刷新來清除緩衝區,所以不能輸錯,也不能多輸!
2/. 輸入的數據類型必須與要提取的數據類型一致,否則出錯。出錯只是在流的狀態字state(枚舉類型io_state)中對應位置位(置1),程序繼續。所以要提高穩健性,就必須在編程中加入對狀態字state的判斷。
3/. 空格和回車都可以作爲數據之間的分格符,所以多個數據可以在一行輸入,也可以分行輸入。但如果是字符型和字符串,則空格(ASCII碼爲32)無法用cin輸入,字符串中也不能有空格。回車符也無法讀入。
4/ 輸入數以後再輸入字符或字符串:如果數後直接加回車,應該用cin.get()提取回車。如果還有空格,則要清空緩衝區。 輸入流成員函數聲明。
字符輸入:int istream::get();//提取一個字符,包括空格,製表,backspace和回車等,
//與cin有所不同.注意返回爲整型 istream&istream::get(char &);
istream&istream::get(unsigned char &); 提取一個字符,放在字符型變量中
get系列函數要求單獨提取結束字符。getline提取字符串時如遇到指定結束符則提取該結束符,但不保存在串中。
其他函數:
函數gcount()返回最後一次提取的字符數量,包括回車:int istream::gcount();
函數ignore()讀空(指定一個大的數量)緩衝區:istream&istream::ignore(int=1,int=EOF); 第一個參數爲要提取的字符數量,默認爲1;第二個參數爲結束字符,提取該結束字符,但對所提取的字符不保存不處理,作用是空讀。第二個參數的默認值EOF爲文件結束標誌。 在iostream中EOF定義爲-1,在int get()函數中,讀入輸入流結束標誌Ctrl+Z(^Z)時,函數返回EOF,爲了能表示EOF的“-1”值,返回類型爲int。採用cin.eof()函數,當前所讀爲EOF則返回非零,注意函數自身未從流中讀取。 輸出流成員函數聲明:
ostream&ostream::put(char); //輸出參數字符
ostream&ostream::put(unsigned char); ostream&ostream::put(signed char);
ostream&ostream::flush(); //刷新一個輸出流,用於cout和clog
重載插入和提取運算符:重載必須保留原來的使用特性。重載只能在用戶定義類中,將重載的運算符的函數說明爲該類的友元函數:
friend istream&operator>>(istream&,className&); friend ostream&operator<<(ostream&,className&);
函數的返回值是對輸入或輸出流的引用,這是爲了保證在cin和cout中可以連續使用“>>”或“<<”運算符,與所有“>>”和“<<”重載函數一致。第一個參數是輸入或輸出流的引用,作爲“>>”或“<<”的左操作數;第二個參數爲用戶定義類的引用,作爲右操作數,流用作函數參數,必須是引用調用,不能是傳值調用。
5.文件的輸入與輸出 :
C++根據文件內容的數據格式,可分爲兩類:二進制文件和文本文件。文本文件由字符序列組成,也稱ASCII碼文件,在文本文件中存取的最小信息單位爲字符,而二進制文件中存取的最小信息單位爲字節。
C++把每一個文件都看成一個有序的字節流,每一個文件或者以文件結束符結束,或者在特定的字節號處結束。
當打開一個文件時,該文件就和某個流關聯起來了。對文件進行讀寫實際上受到一個文件定位指針的控制。
輸入流的指針也稱爲讀指針,每一次提取操作將從讀指針當前所指位置開始,每次提取操作自動將讀指針向文件尾移動。 輸出流指針也稱寫指針,每一次插入操作將從寫指針當前位置開始,每次插入操作自動將寫指針向文件尾移動。
文件使用步驟:
1/.說明一個文件流對象,這又被稱爲內部文件:
ifstream ifile;//只輸入用
ofstream ofile;//只輸出用
fstream iofile;//既輸入又輸出用
2/.使用文件流對象的成員函數打開一個磁盤文件。這樣在文件流對象和磁盤文件名之間建立聯繫。文件流中說明了三個打開文件的成員函數。
void ifstream::open(const char*,int =ios::in,int=filebuf::openprot);
void ofstream::open(const char *,int=ios::out,int=filebuf::opernprot);
void fstream::open(const char*,int,int=filebuf::openprot);
第一個參數爲要打開的磁盤文件名。第二個參數爲打開方式,有輸入(in),輸出(out)等,打開方式在ios基類中定義爲枚舉類型。第三個參數爲指定打開文件的保護方式,一般取默認。所以第二步可如下進行:iofile.open(“myfile.txt”,ios::in|ios::out);
文件打開方式:
是由在ios類中定義的公有枚舉成員決定:
enum open_mode{ in=0x01, out=0x02, ate=0x04, app=0x08, trunc=0x10, binary=0x80 };
打開方式解釋:
in標識打開文件用於輸入操作(從文件讀取)。打開方式只要含in,如文件不存在則返回失敗。在打開爲輸入輸出方式時(同時用out),編程應注意判斷是否失敗,失敗時千萬不可再寫入文件。
out標識打開文件用於輸出操作(寫入文件)。如文件不存在,則建立新文件,如文件存在,未同時設app, in則文件清空。
trunc標識打開文件,並清空它(文件長度截爲0)。文件不存在則建立新文件,與out默認操作相同。但與in配合,文件不存在則返回失敗。
app標識打開文件用於輸出,原文件內容保留,新數據接在尾部
ate意思是at end,標識打開文件,文件指針在文件尾,但文件指針可以移動,即新數據可寫到任何位置。文件是否清空由其它標識決定。
以上三個標識最好配合out、in等一起用,因爲不同的C++平臺,要求不同,一起用不會出錯。不一起用,至少VC++不認這種格式。
binary標識以二進制方式打開文件。同時用out時,如文件不存在,則建立新文件,並且新文件能用,不必清狀態字。
打開文件也應該判斷是否成功,若成功,文件流對象值爲非零值,不成功爲0(NULL),文件流對象值物理上就是指它的地址
3/.使用提取和插入運算符對文件進行讀寫操作,或使用成員函數進行讀寫,這在下一節中討論。
4/.關閉文件。
三個文件流類各有一個關閉文件的成員函數 :
void ifstream::close();
void ofstream::close(); void fstream::close();
使用很方便,如:iofile.close();
關閉文件時,系統把該文件相關聯的文件緩衝區中的數據寫到文件中,保證文件的完整,收回與該文件相關的內存空間,可供再分配,把磁盤文件名與文件流對象之間的關聯斷開,可防止誤操作修改了磁盤文件。如又要對文件操作必須重新打開。
關閉文件並沒有取消文件流對象,該文件流對象又可與其他磁盤文件建立聯繫。文件流對象在程序結束時,或它的生命期結束時,由析構函數撤消。它同時釋放內部分配的預留緩衝區。 文本文件的順序讀寫:順序讀寫可用C++的提取運算符(>>)和插入運算符(<<)進行。 資源獲取是由構造函數實現,而資源釋放是由析構函數完成。
文件結束判斷:
讀函數並不能知道文件是否結束,可用狀態函數int ios::eof()來判斷文件是否結束。必須指出系統是根據當前操作的實際情況設置狀態位
*#*二進制文件優點:
可以控制字節長度,讀寫數據時不會出現二義性,可靠性高。同時不知格式是無法讀取的,保密性好。文件結束後,系統不會再讀(見eofbit的說明),但程序不會自動停下來,所以要判斷文件中是否已沒有數據。如寫完數據後沒有關閉文件,直接開始讀,則必須把文件定位指針移到文件頭。如關閉文件後重新打開,文件定位指針就在文件頭。
文件的隨機訪問:在C++中可以由程序控制文件指針的移動,從而實現文件的隨機訪問,即可讀寫流中任意一段內容。一般文本文件很難準確定位,所以隨機訪問多用於二進制文件。 流的指針位置類型streampos和流的指針偏移類型streamoff定義爲長整型,也就是可訪問文件的最大長度爲4G。
爲了便於記憶,函數名中g是get的縮寫,而p是put的縮寫。對輸入輸出文件定位指針只有一個但函數有兩組,這兩組函數功能完全一樣。
6.字符串流 :
字符串也可以看作字符流。可以用輸入輸出操作來完成串流的操作。串流與內存相關,所以也稱內存流。串流類包括ostrstream、istrstream、strstream,它們在<strstrea.h>中說明。串流類對象可以保存字符,也可以保存整數、浮點數。串流類對象採用文本方式。 其構造函數常用下面幾個: istrstream::istrstream(const char * str); istrstream::istrstream(const char * str,int); ostrstream::ostrstream(char *,int,int=ios::out);
strstream::strstream(char *,int,int);
其中第二個參數說明數組大小,第三參數爲文件打開方式。
7.文件與對象 規範化操作:
在面向對象的程序設計中,信息總是放在對象的數據成員裏。這些信息最終應該保存到文件中。當程序開始運行時要由打開的文件重新創建對象。在運行過程中,放在對象的數據成員裏的信息得到利用和修改。運行結束時必須把這些信息重新保存到文件中,然後關閉文件。 在面向對象的C++程序設計中,文件應該在構造函數中打開,並創建對象;而在析構函數中保存和關閉文件,並撤銷對象。
第八章 面向對象程序設計方法與實例
2014年6月28日 20:59:41
1.面向對象的開發過程包括:
面向對象的分析(OOA)
尋求問題域中的需求,建立對象模型
面向對象的設計(OOD)
對象的分解過程,設計邏輯模型與物理模型、靜態模型與動態模型
面向對象的實現(OOP)
用計算機語言描述類及其關係,形成由相互聯繫的對象構成的程序
2.面向對象建模:
爲什麼要建立模型?
完全、徹底地理解問題
什麼是模型?
對事物的一種抽象,用一組圖示符號和相應的組織規則形式化的描述具體的事物。
目的:理解事物。
用面向對象方法開發軟件,通常要建立三種形式的模型:
對象模型:描述系統數據結構
動態模型:描述系統控制結構
功能模型:描述系統功能
3.類間關係的表示:
類間、對象間的關係可以概括爲繼承關係、組合關係及關聯關係三種關係
泛化關係(繼承關係):“是一種”(IS_A)
“一般─特殊”關係,反映了一個類與若干個互不相容的子類之間的分類關係。
組合關係
組合關係就是“整體─部分”關係,它反映了對象之間的構成關係。組合關係也稱爲聚集關係。
在C++語言中,通常是在一個類中包含另一個類的對象成員來實現這種關係。
4.面向對象分析:
什麼是面向對象分析?
抽取和整理用戶需求並建立問題域精確模型的過程。
面向對象分析工作大體上按照下列順序進行:
尋找類—對象
識別類間關係
確定屬性
確定方法
5.面向對象設計:
把分析階段得到的需求轉變成符合成本和質量要求的、抽象的程序實現方案的過程。
是一個逐漸擴充模型的過程。
在實際的軟件開發過程中分析和設計的界限是模糊的,許多分析結果可以直接映射成設計結果,而在設計過程中又往往會加深和補充對系統需求的理解,從而進一步完善分析結果。
OOD主要完成下述工作:
1.建立類等級
面向對象程序的一個突出優點來源於繼承性。應該儘量抽取出相似類的公共屬性和公共服務,以建立這些相似類的父類,並在類等級的適當層次中正確地定義各個屬性和服務。
2.定義屬性
所謂定義屬性就是要確定每個屬性的數據類型和數據結構,同時還要確定每個屬性的訪問權限(通常被定義在保護部分或私有部分)。
3.定義服務
6.面向對象設計原則:
1/模塊化:對象就是模塊。
2/抽象:類是一種抽象數據類型
3/信息隱藏:信息隱藏通過對象的封裝性實現
4/弱耦合
耦合:一個軟件結構內不同模塊之間互連的緊密程度。
弱耦合是優秀設計的一個重要標準。
在面向對象方法中,對象是最基本的模塊,因此,耦合主要指不同對象之間相互關聯的緊密程度。
對象之間的耦合可分爲兩大類 :交互耦合、繼承耦合。
交互耦合
即對象之間的耦合通過消息連接來實現,交互耦合應儘量鬆散。遵循的原則:
(1)儘量降低消息連接的複雜程度(參數個數和參數的複雜度)
(2)減少對象發送或接收的消息數
繼承耦合
是一般化類與特殊類之間耦合的一種形式。越緊密越好。
5/強內聚
內聚衡量一個模塊內各個元素彼此結合的緊密程度。
在設計時應該力求做到高內聚。
6/可重用
軟件重用是提高軟件開發生產率和目標系統質量的重要途徑。
重用有兩方面的含義:
(1)一是儘量使用已有的類(包括開發環境提供的類庫,及以往開發類似系統時創建的類)
(2)二是如果確實需要創建新類,則在設計這些新類的協議時,應該考慮將來的可重複使用性。
7.面向對象的實現:
面向對象實現主要包括下述兩項工作:
1. 把面向對象設計的結果翻譯成用某種面 向對象程序語言書寫的面向對象程序。
2. 測試和調試編寫出的面向對象程序。
面向對象的語言:C++、Java等
注:程序的質量主要由設計的質量決定,但是所選用的程序語言的特點對程序質量也有重要影響。(應該將大量的時間花在系統分析與設計上)