




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1第九章類與對象
C
語言程序設計案例教程12/23/20242隨著計算機技術應用的不斷深入,面向過程的程序設計的開發方法已不太適應越來越復雜且高速發展的信息處理的要求。20世紀80年代以來,面向對象方法克服了傳統的結構化方法在建立問題系統模型和求解問題時存在的缺陷,提供了更合理、更有效、更自然的方法,正被廣大的系統分析和設計人員認識、接受、應用和推廣,實際上已成為現今軟件系統開發的主流技術。C++?是最具代表性的面向對象程序設計語言。C++?是從C發展而來,它繼承了C語言的優點,并引入了面向對象的概念,是C語言的超集,完全兼容標準C;同時也增加了一些新特性,這些新特性使C++?程序比C程序更簡潔、更安全。12/23/202439.1C++?對C的改進9.1.1常規的改進1.新增的關鍵字C++?在C語言關鍵字的基礎上增加了許多關鍵字,下面列出幾種常用的關鍵字:asmcatchclassdeletefriendinlinenamespacenewoperatorprivateprotectedpublictemplatetryusingvirtual在將原來用C語言寫的程序用C++編譯之前,應把與上述關鍵字同名的標識符改名。2.注釋在C語言中,用“/*”及“*/”作為注釋分界符號,C++?除了保留這種注釋方式外,還提供了一種更有效的注釋方式,即用“//”導引出單行注釋。例如:inta;/*定義一個整型變量*/intA;//定義一個整型變量這兩條語句是等價的。C++?的“//…”注釋方式特別適合于注釋內容不超過一行的注釋。“/*…*/”被稱為塊注釋,“//…”被稱為行注釋。12/23/202443.類型轉換C++?支持兩種不同的類型轉換形式:inti=0;longl=(long)i; //C的類型轉換longm=long(i); //C++?的新風格4.靈活的變量聲明在C語言中,局部變量說明必須置于可執行代碼段之前,不允許將局部變量說明和可執行代碼混合起來。但在C++?中,允許在代碼塊的任何地方說明局部變量,也就是說,變量可以放在任何語句位置,不必非放在程序段的開始處。例如:voidf(){inti;i=1;intj;j=2;//…}這樣,可以隨用隨定義,這是C++?封裝的要求,易讀性好,而且避免了變量在遠離使用處的地方聲明,易引起的混淆或導致錯誤的問題。12/23/202455.?const在C語言中,使用#define來定義常量,例如:#defineSIZE100C++?提供了一種更靈活、更安全的方式來定義變量,即使用類型限定符const來表示常量。所以,C++?中的常量可以是有類型的,程序員不必再用?#define創建無類型常量。例如:constintsize=100;聲明成const的變量,實際是常量,它有地址,可以用指針指向這個值,但在程序中是不可修改的。使用?#define有時是不安全的,如下例所示。12/23/20246例9.1#define的不安全性。#include<iostream.h>voidmain(){intx=1;#defineWx+x#defineYW-Wcout<<"Yis"<<Y<<endl;}初看程序,似乎應打印出:Yis0但是實際的輸出結果是:Yis2其原因是C++?把語句“cout<<"Yis"<<Y<<endl;”解釋成“cout<<"Yis"<<x+x-x+x<<endl;”,如果程序中用const取代了兩個#define,將不會引起這個問題。12/23/20247例9.2使用const消除#define的不安全性。#include<iostream.h>voidmain(){intx=1;constW=x+xconstY=W-Wcout<<"Yis"<<Y<<endl;}輸出:Yis0另外,在ANSIC中,用const定義的常量是全局常量,而C++?中const定義的常量根據其定義位置來決定其是局部的還是全局的。12/23/202486.structC++?的struct后的標識符可看作是類型名,所以定義某個變量比C中更加直觀。例如,在C語言中:structpoint{intx;inty;};structpointp;而在C++?中:structpoint{intx;inty;};pointp;這里不必再寫struct。對于union,也可以照此使用。為了保持兼容性,C++?仍然接受老用法。在后面會看到,C++?的類就是對C中的struct的擴充。12/23/202497.作用域分辨運算符“::”“::”是作用域分辨運算符,它用于訪問在當前作用域中被隱藏的數據項。如果有兩個同名變量,一個是全局的,另一個是局部的,那么局部變量在其作用域內具有較高的優先權。12/23/202410例9.3局部變量在其作用域內具有較高的優先權,會屏蔽同名的全局變量。#include<iostream.h>intX=1;//全局變量Xintmain(){intX;X=2;//局部變量Xcout<<"Xis"<<X<<endl;}程序運行結果如下:Xis2如果希望在局部變量的作用域內使用同名的全局變量,可以在該變量前加上“::”,此時“::X”代表全局變量。12/23/202411例9.4使用域作用運算符。#include<iostream.h>intX;//全局變量Xintmain(){intX;X=2;//局部變量X::X=1;//全局變量Xcout<<"localXis"<<X<<endl;cout<<"globalXis"<<::X<<endl;}程序運行結果如下:localXis2globalXis112/23/202412注意:作用域分辨運算符“::”只能用來訪問全局變量,不能用于訪問一個在語句塊外聲明的同名局部變量。例如,下面的代碼是錯誤的:voidmain(){intX=10;//語句塊外的局部變量
{intX=25;//語句塊內的局部變量
::X=30;//編譯報錯:X不是全局變量
…}}12/23/2024139.1.2C++的動態內存分配C程序中,動態內存分配是通過調用malloc()和free()等庫函數來實現的,而C++給出了使用new和delete運算符進行動態內存分配的新方法。運算符new用于內存分配的使用形式為:p=newtype;其中,type是一個數據類型名,p是指向該數據類型的指針。new從內存中為程序分配一塊sizeof(type)字節大小的內存,該塊內存的首地址存于指針p中。運算符delete用于釋放new分配的存儲空間,它的使用形式為:deletep;其中,p必須是一個指針,保存著new分配的內存的首地址,使用delete來釋放指針內存。以下是C++程序中用新方法實現動態內存分配的例子。12/23/202414例9.5用new實現動態內存分配。#include<iostream.h>voidmain(){int*X=newint;//為指針X分配存儲空間*X=10;cout<<*X;deleteX;//釋放X指向的存儲空間}使用傳統的C程序實現是這樣的:12/23/202415例9.6用malloc實現內存分配。#include<stdio.h>#include<malloc.h>voidmain(){int*X;X=(int*)malloc(sizeof(int));*X=10;printf("%d",*X);free(X);}12/23/202416下面我們再對new和delete的使用作幾點說明:(1)使用new可以為數組動態分配存儲空間,這時需要在類型名后綴上數組大小。例如:int*p=newint[10];這時new為具有10個元素的整型數組分配了內存空間,并將首地址賦給了指針p。需要注意的是,使用new給多維數組分配空間時,必須提供所有維的大小。例如:int*p=newint[2][3][4];其中,第一維的界值可以是任何合法的整形表達式。例如:intX=3;int*p=newint[X][3][4];(2)?new可以在為簡單變量分配內存的同時,進行初始化。例如:int*p=newint(100);new分配了一個整型內存空間,并賦初始值100。但是,new不能對動態分配的數組存儲區進行初始化。(3)釋放動態分配的數組存儲區時,可用如下的delete格式:delete[]p;(4)使用new動態分配內存時,如果分配失敗,即沒有足夠的內存空間滿足分配要求,new將返回空指針(NULL)。因此通常要對內存的動態分配是否成功進行檢查。12/23/202417例如:intmain(){int*p=newint; //為指針p分配存儲空間
if(!p){cout<<"分配失敗"<<endl;return1;}*p=10;cout<<*p;deletep; //釋放p指向的存儲空間}若動態分配失敗,則程序將顯示“分配失敗”。為了避免程序出錯,建議在動態分配內存時對是否分配成功進行檢查。12/23/2024189.1.3引用前面學習了指針的概念,指針就是內存單元的地址,它可能是變量的地址,也可能是函數的入口地址。C++?引入了另外一個同指針相關的概念:引用。先看一個例子。例9.7值傳遞應用。#include<iostream.h>voidswap(intx,inty){
inttemp;
temp=x;x=y;y=temp;}intmain(){
inta=2,b=1;
cout<<"a="<<a<<",b="<<b<<endl;
swap(a,b);
cout<<"a="<<a<<",b="<<b<<endl;}12/23/202419讓我們來分析一下這個例子。首先在內存空間為a、b開辟兩個存儲單元并賦初值,然后調用swap函數,swap函數為x、y開辟了存儲空間,并將a、b的值傳遞給x、y。x、y的值互換了,在swap結束時,x、y的生存周期結束,存儲空間被收回。所以a、b的值并沒有互換。此時輸出:a=2,b=1a=2,b=1可以使用指針傳遞的方式解決這個問題,我們改寫上面的程序。例9.8使用指針(地址)進行值傳遞。#include<iostream.h>voidswap(int*x,int*y){
inttemp;
temp=*x; *x=*y;*y=temp;}intmain(){
inta=2,b=1;
cout<<"a="<<a<<",b="<<b<<endl;
swap(&a,&b);
cout<<"a="<<a<<",b="<<b<<endl;}12/23/202420程序首先在內存空間為a、b開辟兩個存儲單元并賦初值,然后調用swap函數,swap函數為指針x、y開辟了存儲空間,并將a、b的地址傳遞給x、y。在swap函數中,對x、y的間接引用的訪問就是對a、b的訪問,從而交換了a、b的值。那么C++?中還有沒有更簡單的方式呢?有,那就是引用。引用是能自動進行間接引用的一種指針。自動間接引用就是不必使用間接引用運算符*,就可以得到一個引用值。我們可以這樣理解,引用就是某一變量(目標)的一個別名,兩者占據同樣的內存單元,對引用的操作與對變量直接操作完全一樣。12/23/2024211.引用的定義定義引用的關鍵字是“type&”,它的含義是“type類型的引用”。例如:inta=5;int&b=a;它創建了一個整型引用,b是a的別名,a和b占用內存同一位置。當a變化時,b也隨之變化,反之亦然。引用的初始值可以是一個變量或另一個引用,以下的定義也正確。inta=5;int&b=a;int&b1=b;12/23/2024222.使用規則(1)定義引用時,必須立即初始化。inta;int&b; //錯誤,沒有初始化b=a;(2)引用不可重新賦值。inta,k;int&b=a;b=&k; //錯誤,重新賦值(3)引用不同于普通變量,下面的聲明是非法的:int&b[3]; //不能建立引用數組int&*P; //不能建立指向引用的指針int&&r; //不能建立指向引用的引用12/23/202423(4)當使用&運算符取一個引用的地址時,其值為所引用的變量的地址。intnum=50;int&ref=num;int*p=&ref;則p中保存的是變量num的地址。我們使用引用改寫例9.8。12/23/202424例9.9引用傳遞。#include<iostream.h>voidswap(int&x,int&y){
inttemp;
temp=x;
x=y;
y=temp;}main(){
inta=2,b=1;
cout<<"a="<<a<<",b="<<b<<endl;
swap(a,b);
cout<<"a="<<a<<",b="<<b<<endl;return0;}當程序中調用函數swap()時,實參a、b分別初始化引用x和y,所在函數swap()中,x和y分別引用a和b,對x和y的訪問就是對a和b的訪問,所以函數swap()改變了main()函數中變量a和b的值。盡管通過引用參數產生的效果同按地址傳遞是一樣的,但其語法更清楚簡單。C++?主張用引用傳遞取代地址傳遞的方式,因為前者語法容易且不易出錯。12/23/2024259.1.4C++?中的函數C++?對傳統的C函數說明作了一些改進。這些改進主要是為了滿足面向對象機制的要求,以及可靠性、易讀性的要求。1.主函數mainC并無特殊規定main()函數的格式,因為通常不關心返回何種狀態給操作系統。然而,C++?卻要求main()函數匹配下面兩種原型之一:voidmain() //無參數,無返回類型intmain(intargc,char*argv[]) //帶參數,有返回類型,參數也可以省略如果前面不寫返回類型,那么main()等價于intmain()。函數要求具有int返回類型,如例9.9。12/23/2024262.函數原型函數原型的概念在前面的章節已提及,函數原型實際上就是對函數的頭格式進行說明,包含函數名、參數及返回值類型。C語言建議編程者為程序中的每一個函數建立原型,而C++?要求必須為每一個函數建立原型。有了函數原型編譯程序方能進行強類型檢查,從而確保函數調用的實參類型與要求的類型相符。早期的C正是缺乏這種強類型檢查,很容易造成非法參數值傳遞給函數,因此造成程序運行時不可預料的錯誤。函數原型的語法形式一般為返回類型函數名(參數表);函數原型是一條語句,它必須以分號結束。它由函數的返回類型、函數名和參數表構成。參數表包含所有參數及它們的類型,參數之間用逗號分開。請看下面的例子。12/23/202427例9.10C++?中函數原型的聲明。#include<iostream.h>voidswap(int&m,int&n);//函數原型的聲明voidmain(){
inta=5,b=10;
cout<<"a="<<a<<"b="<<b<<endl;
swap(a,b);
cout<<"a="<<a<<"b="<<b<<endl;}voidswap(int&m,int&n){
inttemp;
temp=m; m=n;n=temp;}12/23/202428在程序中,要求一個函數的原型出現在該函數的調用語句之前。這樣,當一個函數的定義在后,而對它的調用在前時,必須將該函數的原型放在調用語句之前;但當一個函數的定義在前,對它的調用在后,一般就不必再單獨給出它的原型了,如例9.9所示。說明:(1)函數原型的參數表中可以不包含參數的名字,而是包含它們的類型,但函數定義的函數說明部分中的參數必須給出名字,而且不包含結尾的分號。例如:voidmax(int,int); //函數原型的聲明voidmax(intm,intn) //函數定義的函數說明部分{
//…}(2)原型說明中沒有指出返回類型的函數(包括主函數main),C++?默認該函數的返回類型是int,因此以下的原型說明在C++?中是等價的:fun(inta);intfun(inta);12/23/2024293.內置函數函數調用導致了一定數量的額外開銷,如參數壓棧、出棧等。有時正是這種額外開銷迫使C程序員放棄使用函數調用,進行代碼復制以提高效率。C++?的內置函數正好解決這一問題。當函數定義是由inline開頭時,表明此函數為內置函數。編譯時,使用函數體中的代碼替代函數調用表達式,從而完成與函數調用相同的功能,這樣能加快代碼的執行,減少調用開銷。例如:inlineintsum(inta,intb) //內置函數{returna+b;}值得注意的是:內置函數必須在它被調用之前定義,否則編譯不會得到預想的結果。若內置函數較長,且調用太頻繁時,程序將加長很多。因此,通常只有較短的函數才定義為內置函數,對于較長的函數,最好作為一般函數處理。12/23/2024304.缺省參數值C++?對C函數的重要的改進之一就是可以為函數定義缺省的參數值。例如:intfunction(intx=2,inty=6);//函數給出缺省的參數值x與y的值分別是2和6。當進行函數調用時,編譯器按從左向右順序將實參與形參結合,若未指定足夠的實參,則編譯器按順序用函數原型中的缺省值來補足所缺少的實參。例如:function(1,2); //x=1,y=2function(1); //x=1,y=6function(); //x=2,y=6一個C++?函數可以有多個缺省參數,并且C++?要求缺省參數必須連續的放在函數參數表的尾部,也就是說,所有取缺省值的參數都必須出現在不取缺省值的參數的右邊。當調用具有多個缺省參數時,若某個參數省略,則其后的參數皆應省略而采用缺省值。當不允許出現某個參數省略時,再對其后的參數指定參數值。例如:function(,3) //這種調用方式是錯誤的12/23/2024315.函數重載在C語言中,函數名必須是唯一的,也就是說不允許出現同名的函數。當要求編寫求整數、浮點數和雙精度浮點數的立方數的函數時,若用C語言來處理,必須編寫三個函數,這三個函數的函數名不允許同名。例如:Icube(inti); //求整數的三次方Fcube(floatf); //求浮點數的三次方Dcube(doublei); //求雙精度浮點數的三次方當使用這些函數求某個數的立方數時,必須調用合適的函數,也就是說,用戶必須記住這三個函數,雖然這三個函數的功能是相同的。在C++?中,用戶可以重載函數,只要函數參數的類型不同,或者參數的個數不同,兩個或兩個以上的函數可以使用相同的函數名。一般而言,重載函數應執行相同的功能。我們可用函數重載來重寫上面的三個函數。12/23/202432例9.11重載cube函數。#include<iostream.h>intcube(inti){returni*i*i;}floatcube(floatf){returnf*f*f;}doublecube(doubled){returnd*d*d;}intmain(){inti=123;floatf=4.5;doubled=6.78;cout<<i<<'*'<<i<<'*'<<i<<'='<<cube(i)<<endl;cout<<f<<'*'<<f<<'*'<<f<<'='<<cube(f)<<endl;
cout<<d<<'*'<<d<<'*'<<d<<'='<<cube(d)<<endl;}12/23/202433在main()中三次調用了cube()函數,實際上調用了三個不同的版本。由系統根據傳送的參數類型的不同來決定調用哪個重載版本。值得注意的是重載函數應在參數個數或參數類型上有所不同,否則編譯程序將無法確定調用哪一個重載版本,即使返回類型不同,也不能區分。例如:intfun(intx,inty);floatfun(intx,inty);上面的重載就是錯誤的。12/23/2024349.2C++?的輸入與輸出C語言提供了強有力的I/O函數,其功能強,靈活性好,是很多語言無法比擬的。但C++?為何還要定義自己的I/O系統,而不建議使用C語言原有的函數呢?在C語言中進行I/O操作時,常會出現以下錯誤:inti;floatf;scanf("%f",i);printf("%d",f);這些錯誤C語言編譯器是不能檢查出來的,而在C++?中,可以將上面的操作寫成:inti;floatf;cin>>i;cout<<f;12/23/202435cin是標準的輸入流,在程序中用于代表標準輸入設備,即鍵盤。運算符“>>”是輸入運算符,表示從標準輸入流(即鍵盤)讀取的數值傳送給右方指定的變量。運算符“>>”允許用戶連續讀入一連串數據,兩個數據間用空格、回車或Tab鍵進行分割。例如:cin>>x>>y;cout是標準的輸出流,在程序中用于代表標準輸出設備,通常指屏幕。運算符“<<”是輸出運算符,表示將右方變量的值顯示到屏幕上。運算符“<<”允許用戶連續輸出數據。例如:cout<<x<<y;這里的變量應該是基本數據類型,不能是void型。其實,可以在同一程序中混用C語言和C++?語言的I/O操作,繼續保持C語言的靈活性。因而,在把C語言程序改為C++?語言程序時,并不一定要修改每一個I/O操作。12/23/2024369.2.1C++?的流類結構C++?語言和C語言的I/O系統都是對流(tream)進行操作。流實際上就是一個字節序列。輸入操作中,字節從輸入設備(如鍵盤、磁盤、網絡連接等)流向內存;輸出操作中,字節從內存流向輸出設備。使用C++?式的I/O的程序必須包含頭文件iostream.h,對某些流函數可能還需要其他頭文件,例如進行文件I/O時需要頭文件fstream.h。1.?iostream庫iostream庫中具有streambuf和ios兩個平行的類,這都是基本的類,分別完成不同的工作。streambuf類提供基本流操作,但不提供格式支持。類ios為格式化I/O提供基本操作。2.標準流iostream.h說明了標準流對象cin、cout、cerr與clog。cin是標準輸入流,對應于C語言的stdin;cout是標準輸出流,對應于C語言的stdout;cerr是標準出錯信息輸出,clog是帶緩沖的標準出錯信息輸出。cerr和clog流被連到標準輸出上對應于C語言的stderr。cerr和clog之間的區別是cerr沒有緩沖,發送給它的任何輸出立即被執行,而clog只有當緩沖區滿時才有輸出。缺省時,C++?語言標準流被連到控制臺上。12/23/2024379.2.2格式化I/O
習慣C語言的程序員,對printf()等函數的格式化輸入也一定很熟悉。用C++?語言的方法進行格式化I/O有兩種方法:其一是用ios類的成員函數進行格式控制;其二是使用操作子。1.狀態標志字C++?語言可以對每個流對象的輸入輸出進行格式控制,以滿足用戶對輸入輸出格式的需求。輸入輸出格式由一個longint類型的狀態標志字確定。在ios類中定義了一個枚舉,它的每個成員可以分別定義狀態標志字的一個位,每一位都稱為一個狀態標志位。這個枚舉定義如下:12/23/202438enum{skipws=0x0001, //跳過輸入中的空白字符,可以用于輸入
left=0x0002, //輸出數據左對齊,可以用于輸出
right=0x0004, //輸出數據右對齊,可以用于輸出
internal=0x0008, //數據符號左對齊,數據本身右對齊,可以用于輸出
dec=0x0010, //轉換基數為十進制形式,可以用于輸入或輸出
oct=0x0020, //轉換基數為八進制形式,可以用于輸入或輸出
hex=0x0040, //轉換基數為十六進制形式,可以用于輸入或輸出
showbase=0x0080, //輸出的數值數據前面帶基數符號(0或0x),可以用于輸入或輸出
showpoint=0x0100, //浮點數輸出帶小數點,可以用于輸出
uppercase=0x0200, //用大寫字母輸出十六進制數值,可以用于輸出
showpos=0x0400, //正數前面帶“+”號,可以用于輸出
scientific=0x0800, //浮點數輸出采用科學表示法,可以用于輸出
fixed=0x1000, //浮點數輸出采用定點數形式,可以用于輸出
unitbuf=0x2000, //完成操作后立即刷新緩沖區,可以用于輸出
stdio=0x4000, //完成操作后刷新stdout和stderr,可以用于輸出};12/23/2024392.?ios類中用于控制輸入輸出格式的成員函數在ios類中,定義了幾個用于控制輸入輸出格式的成員函數,下面分別介紹。(1)設置狀態標志。將某一狀態標志位置為“1”,可以使用setf()函數,其一般格式為longios::setf(longflags);該函數設置參數flags所指定的標志位為1,其他標志位保持不變,并返回格式更新前的標志。例如,要設置showbase標志,可使用如下語句:stream.setf(ios::showbase); //其中stream是所涉及的流實際上,還可以一次調用setf()來同時設置多個標志。例如:cout.setf(ios::showpos│ios::scientific); //使用按位或運算12/23/202440例9.12設置狀態標志。#include<iostream.h>intmain(){cout.setf(ios::showpos|ios::scientific);cout<<521<<""<<131.4521<<endl;}輸出結果為+521+1.314521e+002(2)清除狀態標志。清除標志可用unsetf()函數,其原型與setf()類似,使用時調用格式與setf相同,將參數flags所指定的標志位為0。(3)取狀態標志。用flags()函數可得到當前標志值和設置新標志,分別具有以下兩種格式:longios::flags(void);longios::flags(longflags);前者用于返回當前的狀態標志字,后者將狀態標志字設置為flag,并返回設置前的狀態標志字。flags()函數與setf()函數的差別在于:setf()函數是在原有的基礎上追加設定的,而flags()函數是用新設定替換以前的狀態標志字。12/23/202441(4)設置域寬。域寬主要用來控制輸出,設置域寬可以使用width()函數,其一般格式為intios::width();intios::width(intlen);前者用來返回當前的域寬值,后者用來設置域寬,并返回原來的域寬。注意每次輸出都需要重新設定輸出寬度。(5)設置顯示的精度。設置顯示精度的函數一般格式為intios::precision(intnum);此函數用來重新設置浮點數所需的精度,并返回設置前的精度。默認的顯示精度是6位。如果顯示格式是scientific或fixed,精度指小數點后的位數;如果不是,精度指整個數字的有效位數。(6)填充字符。填充字符函數的格式為charios::fill();charios::fill(charch);前者用來返回當前的填充字符,后者用ch重新設置填充字符,并返回設置前的填充字符。12/23/202442下面舉例說明以上這些函數的作用。例9.13使用ios類中用于控制輸入輸出格式的成員函數。#include<iostream.h>main(){cout<<"x_width="<<cout.width()<<endl;cout<<"x_fill="<<cout.fill()<<endl;cout<<"x_precision="<<cout.precision()<<endl;cout<<520<<""<<520.45678<<endl;cout<<"-------------------------"<<endl;cout<<"****x_width=10,x_fill=&,x_precision=4****"<<endl;cout.fill('&');cout.width(10);cout.setf(ios::scientific);cout.precision(4);cout<<520<<""<<520.45678<<endl;cout.setf(ios::left);cout.width(10);cout<<520<<""<<520.45678<<endl;cout<<"x_width="<<cout.width()<<endl;cout<<"x_fill="<<cout.fill()<<endl;cout<<"x_precision="<<cout.precision()<<endl;}程序運行結果如下:x_width=0x_fill=x_precision=6520520.457-------------------------****x_width=10,x_fill=&,x_precision=4****&&&&&&&5205.2046e+002520&&&&&&&5.2046e+002x_width=0x_fill=&x_precision=412/23/202443分析以上的程序運行結果可看出:在缺省情況下,x_width取值為“0”,這個“0”意味著無域寬,既按數據自身的寬度打印;x_fill默認值為空格;x_precision默認值為6,這是因為沒有設置輸出格式是scientific或fixed,所以520.45678輸出為520.457,整個數的有效位數為6,后面省略的位數四舍五入;接下來設置x_width為10,x_fill為“&”x_precision為4,輸出格式為scientific。每次輸出都需要重新設定輸出寬度。由于輸出格式為scientific,精度設置為4,指的是小數點后位數,所以輸出為5.2046e+002。12/23/2024443.用操作子進行格式化上面介紹的格式控制每個函數的調用需要寫一條語句,而且不能將它們直接嵌入到輸入輸出語句中去,使用起來不方便,因此可以用操作子來改善上述情況。操作子是一個對象,可以直接被插入符或提取符操作??刂坪瘮悼勺鳛閰担苯訁⑴cI/O操作。C++?流類庫所定義操作子如下:dec,hex,oct:數值數據采用十進制或十六進制、八進制表示,可用于輸入或輸出。ws:提取空白符,僅用于輸入。endl:插入一個換行符并刷新輸出流,僅用于輸出。ends:插入空字符,僅用于輸出。flush:刷新與流相關聯的緩沖區,僅用于輸出。setbase(intn):設置數值轉換基數為n(n的取值為0、8、10、16),0表示使用缺省基數,即以十進制形式輸出。resetiosflags(longf):清除參數所指定的標志位,可用于輸入或輸出。setiosflags(longf):設置參數所指定的標志位,可用于輸入或輸出。setfill(intn):設置填充字符,缺省為空格,可用于輸入或輸出。setsprecision(intn):設置浮點數輸出的有效數字個數,可用于輸入或輸出。setw(intn):設置輸出數據項的域寬,可用于輸入或輸出。特別注意:使用操作子必須包含頭文件iomanip.h。12/23/202445例9.14使用操作子進行格式化。#include<iostream.h>#include<iomanip.h>main(){cout<<setfill('^')<<setw(10)<<123<<setw(5)<<456<<endl;cout<<123<<setiosflags(ios::scientific)<<setw(15)<<123.456789<<endl;cout<<123<<setw(8)<<hex<<123<<endl;cout<<123<<setw(8)<<oct<<123<<endl;cout<<123<<setw(8)<<dec<<123<<endl;cout<<resetiosflags(ios::scientific)<<setprecision(4)<<123.456789<<endl;cout<<setiosflags(ios::left)<<setfill('*')<<setw(8)<<dec<<123<<endl;cout<<resetiosflags(ios::left)<<setfill('^')<<setw(8)<<dec<<456<<endl;}程序運行結果如下:^^^^^^^123^^456123^^1.234568e+002123^^^^^^7b7b^^^^^173173^^^^^123123.5123*****^^^^^45612/23/202446案例一學生類設計1.問題描述設計一個學生類,具有姓名、年齡、目前學習的時間等信息,根據學制確定學生上學年限,畢業時打印學生姓名、年齡、修業年限等信息。2.問題分析注意類和對象的定義方法、類成員的訪問權限設置方法以及不同訪問權限成員的訪問方法、構造函數和析構函數的設計、內聯函數使用方法、靜態成員的使用和初始化、函數默認參數值的設定、C++?注釋的使用方法、輸入輸出以及相應格式的設置方法等相關知識的掌握。12/23/2024473.?C++?代碼#include<iostream.h>#include<iomanip.h>#include<string.h>classstu{ //class是定義類的關鍵字,stu是類的名字
private: //訪問權限設置,后面的成員為私有,直到下一個權限設置進行更改
charname[20]; //學生姓名
intage; //學生年齡
staticintstudyyear;//static修飾studyyear后成為靜態的類成員,為所有對象共享的數據, 表示學習年數
staticintstunumber;//靜態的數據成員放在類中,不占用對象空間,stunumber表示學生總數
public: //訪問權限設置,后面的成員為公有訪問屬性
inlinestu(char*xingming="",intnianling=6,intxuexinianshu=0)//構造函數在類內部定義,是內聯函數,有默認參數,inline是定義內聯函數關鍵字
{stunumber++;strcpy(name,xingming);age=nianling;studyyear=xuexinianshu;}stu(conststu&x) //復制構造函數在類內部定義
{*this=x;stunumber++;}inline~stu(); //析構函數聲明
voidset(char*xingming,intnianling,intxuexinianshu);//重新設置對象成員的函數
voiddisplay(); //顯示對象成員的函數
intgetstudyyear(); //獲取目前的學習年數12/23/202448staticvoidaddstudyyear() //靜態成員函數,只能訪問靜態數據成員,實現學習時間增加
{studyyear++;}voidaddage() //成員函數,實現年齡增加
{age++;}}stuA("張三",18,0); //類stu的封裝,定義stuA是全局對象inlinestu::~stu() //析構函數在類外定義{stunumber--;cout<<this->name<<"經過"<<studyyear<<"年學習已經畢業!"<<endl;//cout是輸入流對象,使用需要頭文件iostream.h,this是函數隱含的對象指針參數,指向調用該函數的對象}voidstu::set(char*xingming,intnianling,intxuexinianshu){strcpy(name,xingming);age=nianling;studyyear=xuexinianshu;}voidstu::display(){cout.setf(ios::left);//setf是cout的成員函數,ios::left是操作子,使用操作子需要iomanip.h文件
cout<<setw(10)<<"學生總數"<<setw(10)<<"姓名"\<<setw(10)<<"年齡"<<setw(10)<<"學習年數"<<endl;//\為續行符
cout<<setw(10)<<stunumber<<setw(10)<<name<<setw(10)<<age<<setw(10)<<studyyear<<endl;}intstu::getstudyyear(){returnstudyyear;}intstu::studyyear=0; //靜態數據成員的初始化必須放在類外進行intstu::stunumber=0;12/23/202449voidmain(){intx; //動態變量x表示畢業需要的學制
stuA.display(); //全局對象stuA的成員顯示
stustuB("李四",19,0); //stuB是局部動態對象
stuA.display(); //再次顯示stuA的成員,注意分析成員值變化原因
stuB.display(); stustuC=stu(stuB); //stuC是復制stuBstuA.display();stuB.display();stuC.display();stuC.set("王五",20,0); //stuC重新設置
stuC.display();cout<<"對象占有的空間字節數:"<<sizeof(stuA)<<endl; //函數及靜態數據成員在類中存放,不占用對象所用的空間
cout<<"請輸入畢業需要學制年數:";cin>>x; //cin是輸入流對象
while(stuA.getstudyyear()<x) //學習年數小于學制
{stuA.addage();stuB.addage();stuC.addage();stu::addstudyyear(); //增加學習時間
//靜態成員函數調用可以不通過對象來進行,也可以通過對象來調用
}cout<<"學習"<<x<<"年后……"<<endl;stuA.display();stuB.display();stuC.display();}12/23/2024504.程序運行結果學生總數姓名年齡學習年數1張三180學生總數姓名年齡學習年數2張三180學生總數姓名年齡學習年數2李四190學生總數姓名年齡學習年數3張三180學生總數姓名年齡學習年數3李四190學生總數姓名年齡學習年數3李四190學生總數姓名年齡學習年數3王五200對象占有的空間字節數:24請輸入畢業需要學制年數:4學習4年后……學生總數姓名年齡學習年數3張三224學生總數姓名年齡學習年數3李四234學生總數姓名年齡學習年數3王五244王五經過4年學習已經畢業!李四經過4年學習已經畢業!張三經過4年學習已經畢業!12/23/2024519.3類與對象的概念C++?是面向過程和面向對象的語言,既支持面向過程也支持面向對象編程。C++?又稱為帶類的C語言。類是一組具有相同屬性和行為的對象的統稱,它為屬于該類的全部對象提供了統一的抽象描述。對象是類的實例。類相當于C語言中的數據類型,對象相當于變量。類需要程序員進行定義,類中除可以定義數據成員外,還可以定義對這些數據成員進行操作的函數——成員函數;類的成員也有不同的訪問權限,這樣就保證了數據的私有性。下面,我們將要介紹怎樣定義類及類的成員。12/23/2024529.3.1類的定義類的定義一般形式如下:class類名{[private:]私有的數據成員和成員函數public://外部接口公有的數據成員和成員函數protected:保護性的數據成員和成員函數};類的定義由頭和體兩個部分組成。類頭由關鍵字class開頭,然后是類名,其命名規則與一般標識符的命名規則一致,類體包括所有的細節,并放在一對花括號中。類的定義也是一個語句,所以要有分號結尾。類體定義類的成員,它支持兩種類型的成員:(1)數據成員:指定了該類對象的內部表示。(2)成員函數:指定該類的操作。12/23/2024539.3.2數據成員和成員函數
(1)類的成員分私有成員、保護性成員和公有成員。私有成員用private說明,私有成員是默認的訪問屬性,private下面的每一行,不論是數據成員還是成員函數,都是私有成員。私有成員只能被該類的成員函數或本類的友元函數(關于友元函數的概念后面介紹)訪問,這是C++?實現封裝的一種方法,即把特定的成員定義為私有成員,就能嚴格的控制對它的訪問,如果緊跟在類名稱的后面聲明私有成員,則關鍵字private可以省略,因為成員默認的訪問權限是私有的,以保護數據的安全性。公有成員用public說明,public下面每一行都是公有成員,公有成員可被類外部的其他函數訪問,它們是類的對外接口。保護性成員用protected說明,protected下面每一行都是保護性成員,保護成員不可被類外部的其他函數訪問,只能被本類的成員函數和本類的派生類(關于派生類的概念后面介紹)的成員函數、本類的友元函數訪問。類聲明中的private、public、protected關鍵字可以按任意順序出現任意次。定義類的成員及訪問屬性稱為類的封裝,封裝、繼承、多態(繼承和多態的概念后續章節介紹)是面向對象的三個基本特征。(2)成員函數。成員函數的定義通常采用兩種方式。第一種方式是在類聲明中只給出成員函數的原型,而成員函數體在類的外部定義,如案例一中的display();成員函數的第二種定義方式是:將成員函數定義在類的內部,即定義為內聯函數。這種情況又分成兩種:一種直接在類中定義函數,如案例一中的inlinestu(char*xingming="",intnianling=6,intxuexinianshu=0)構造函數;第二種情況是定義內置函數時,將它放在類定義體外,但在該成員函數定義前插入inline關鍵字,使它仍然起內置函數的作用。如案例一中的inlinestu::~stu()的析構函數。12/23/2024549.3.3對象
1.對象的定義對象的定義可以采用以下的兩種方式:(1)在聲明類的同時,直接定義對象,就是在聲明類的右花括號“}”后,直接寫出屬于該類的對象名表。如案例一中的stuA("張三",18,0)。stuA是使用全局類定義的全局對象。(2)先聲明類,在使用時再定義對象。如案例一中stustuB("李四",19,0);stuB是使用全局類定義的局部對象。對象是類的實例,是類型為類的變量,也有對象數組、對象指針、對象形參、返回對象數據的函數等情況,這里不再一一敘述。2.對象的引用對象的引用是指對對象成員的引用。不論是數據成員還是成員函數,只要是公有的,就可以被外部函數直接引用。引用的格式是:對象名.數據成員名或對象名.成員函數名(實參表)由于成員函數中隱含了指向當前對象(是指調用成員函數的對象)的指針,成員函數可以直接引用對象的數據成員名。類在函數外定義的稱為全局類,在函數內部定義的稱為局部類。同樣,對象也有全局對象和局部對象的分類。全局對象只能由全局類來定義。12/23/2024553.關于this指針在C++?中,定義了一個this指針,它是成員函數所屬對象的指針,它指向類對象的地址,成員函數通過這個指針可以知道自己屬于哪一個對象,也就是由哪一個對象來調用的成員函數。this指針是一種隱含指針,它隱含于每個類的成員函數中,僅能在類的成員函數中訪問,不需要定義就可以使用。因此,成員函數訪問類中數據成員的格式也可以寫成:this->成員變量下面定義一個類Date。classDate{private:intyear,month,day;public:voidsetYear(int);voidsetMonth(int);voidsetDay(int);};該類的成員函數setMonth可用以下兩種方法實現:方法1:voidDate::setMonth(intmn) //使用隱含的this指針{
month=mn;}方法2:voidDate::setMonth(intmn) //顯式使用this指針{this->month=mn;}12/23/202456雖然顯式使用this指針的情況并不是很多,但是this指針有時必須顯式使用。例如,下面的賦值語句是不允許的:voidDate::setMonth(intmonth){month=month;}//形參month和成員month同名時,默認指的是形參,相當于形參自己給自己賦值為了給同名的數據成員賦值,可以用this指針來解決:voidDate::setMonth(intmonth){this->month=month;//this->month是類Date的數據成員,month是函數setMonth()的形參}12/23/202457例9.15this指針示例。#include<iostream.h>classPoint{intx,y;public:Point(inta,intb){x=a;y=b;}voidMovePoint(inta,intb){this->x+=a;this->y+=b;}voidprint(){cout<<"x="<<x<<"y="<<y<<endl;}};voidmain(){Pointpoint1(10,10);point1.MovePoint(2,2);point1.print();}12/23/202458當一個對象調用成員函數時,該成員函數的this指針便指向這個對象。如果不同的對象調用同一個成員函數,則C++?編譯器將根據該成員函數的this指針指向的對象來確定應該引用哪一個對象的數據成員。當在類的非靜態成員函數中訪問類的非靜態成員的時候,編譯器會自動將對象本身的地址作為一個隱含參數傳遞給函數。也就是說,即使你沒有寫上this指針,編譯器在編譯的時候也是加上this的,它作為非靜態成員函數的隱含形參,對各成員的訪問均通過this進行。當對象point1調用MovePoint(2,2)函數時,即將point1對象的地址傳遞給了this指針。MovePoint函數的原型應該是voidMovePoint(Point*this,inta,intb);第一個參數是指向該類對象的一個指針,我們在定義成員函數時沒看見是因為這個參數在類中是隱含的。這樣point1的地址傳遞給了this,所以在MovePoint函數中便顯式的寫成:voidMovePoint(inta,intb){this->x+=a;this->y+=b;}。在實際編程中,由于不標明this指針的形式使用起來更加方便,因此大部分程序員都使用簡寫形式。12/23/2024599.4構造函數和析構函數
在C++?中,有兩種特殊的成員函數,即構造函數和析構函數。12/23/2024609.4.1構造函數
C++?中定義了一種特殊的初始化函數,稱之為構造函數。創建對象時,自動調用構造函數。構造函數具有一些特殊的性質:構造函數的名字必須與類名相同;構造函數可以有任意類型的參數,但不能具有返回類型;定義對象時,編譯系統會自動地調用構造函數。(1)構造函數不能像其他成員函數那樣被顯式地調用,它是在定義對象的同時調用的,其一般格式為類名對象名(實參表);如案例一中:stustuB("李四",19,0);(2)在實際應用中,通常需要給每個類定義構造函數。如果沒有給類定義構造函數,則編譯系統自動地生成一個缺省的構造函數。例如,如果沒有給stu類定義構造函數,編譯系統則為stu生成下述形式的構造函數:stu::stu(){}這個缺省的構造函數不帶任何參數,它只為對象開辟一個存儲空間,而不能給對象中的數據成員賦初值,這時的初始值是隨機數,程序運行時可能會造成錯誤。因此給對象賦初值是非常重要的。給對象賦初值并不是只能采用構造函數這一途徑,如案例一中stuC.set("王五",20,0);就是給對象stuC賦值的;這種通過顯式調用成員函數來進行對象賦初值是完全允許的。但是這種方法存在一些缺陷,比如,對每一個對象賦初值都需要一一給出相應的語句,因此容易遺漏而產生錯誤。而構造函數的調用不需要寫到程序中,是系統自動調用的,所以不存在遺忘的問題。兩者相比,選擇構造函數的方法為對象進行初始化比較合適。12/23/202461(3)構造函數可以是不帶參數的。例如:classabc{private:inta;public:abc(){cout<<"initialized"<<endl;a=5;}};
此時,類abc的構造函數就沒有帶參數。在main()函數中可以采用如下方法定義對象:abcs;在定義對象s的同時,構造函數s.abc::abc()被系統自動調用執行,執行結果是:在屏幕上顯示字符串“initialized”,并給私有數據成員a賦值5。12/23/202462(4)構造函數也可以采用構造初始化表對數據成員進行初始化,例如:classA{inti;charj;floatf;public:A(intx,chary,floatz){i=x;j=y;f=z;}};這個含有三個數據成員的類,利用構造初始化表的方式可以寫成:classA{inti;charj;floatf;public:A(intx,chary,floatz):i(x),j(y),f(z){}};(5)缺省參數的構造函數。在實際使用中,有些構造函數的參數值通常是不變的,只有在特殊情況下才需要改變它的參數值。這時可以將其定義成帶缺省參數的構造函數,例如:#include<iostream.h>classPoint{
intxVal,yVal;public:
Point(intx=0,inty=0)
{xVal=x;yVal=y;}
voiddisplay()
{cout<<xVal<<""<<yVal<<endl;}};在類Point中,構造函數的兩個參數均含有缺省參數值,因此,在定義對象時可根據需要使用其缺省值。下面我們用main()函數來使用它。voidmain(){Pointp1; //不傳遞參數,全部使用缺省值
Pointp2(2); //只傳遞一個參數
Pointp3(2,3); //傳遞兩個參數}12/23/202463在上面定義了三個對象p1、p2、p3,它們都是合法的對象。由于傳遞參數的個數不同,使它們的私有數據成員取得不同的值。由于定義對象p1時,沒有傳遞參數,所以xVal和yVal全取構造函數的缺省值為其賦值,因此均為0。在定義對象p2時,只傳遞了一個參數,這個參數傳遞給構造函數的第一個參量,而第二個參量取缺省值,所以對象p2的xVal取值2,yVal取值0。在定義對象p3時,傳遞了兩個參數,這兩個參數分別傳給了xVal和yVal,因此xVal取值2,yVal取值3。同一個函數名,由于使用的參數類型和個數不同,從而執行不同的函數體的,這種行為稱之為函數的重載。12/23/2024649.4.2復制構造函數
復制構造函數是一種特殊的構造函數。它用于依據已存在的對象建立一個新對象。典型的情況是,將參數代表的對象逐域復制到新創建的對象中。用戶可以根據自己的需要定義復制構造函數,系統也可以為類產生一個缺省的復制構造函數。1.自定義復制構造函數自定義復制構造函數的一般形式如下:classname(constclassname&ob){//復制構造函數的函數體}其中,ob是用來初始化的另一個對象的對象的引用。12/23/202465下面是一個用戶自定義的復制構造函數:classPoint{intxVal,yVal;public:Point(intx,inty) //構造函數
{xVal=x;yVal=y;}Point(constPoint&p) //復制構造函數
{xVal=2*p.xVal;yVal=2*p.yVal;}//…};例如p1、p2為類Point的兩個對象,且p1已經存在,則下述語句可以調用復制構造函數初始化p2:Pointp2(p1);12/23/202466下面給出使用自定義復制構造函數的完整程序。例9.16自定義Point類復制構造函數。#include<iostream.h>classPoint{intxVal,yVal;public:Point(intx,inty) //構造函數
{xVal=x;yVal=y;}Point(constPoint&p) //復制構造函數
{xVal=2*p.xVal;yVal=2*p.yVal;}
voidprint(){cout<<xVal<<""<<yVal<<endl;}};12/23/202467voidmain(){Pointp1(30,40); //定義類Point的對象p1Pointp2(p1); //顯示調用復制構造函數,創建對象p2p1.print();p2.print();}本例在定義對象p2時,調用了自定義復制構造函數。程序運行結果如下:30406080本例除了顯式調用復制構造函數外,還可以采用賦值形式調用復制構造函數。例如將主函數main()改寫成如下形式:voidmain(){Pointp1(30,40);Pointp2=p1; //使用賦值形式調用復制構造函數,創建對象p2p1.print();p2.print();}在定義對象p2時,雖然從形式上看是將對象p1賦值給了對象p2,但實際上調用的是復制構造函數,在對象p2被創建時,將對象p1的值逐域復制給對象p2,運行結果同上。12/23/2024682.缺省的復制構造函數如果沒有編寫自定義的復制構造函數,C++?會自動地將一個已存在的對象賦值給新對象,這種按成員逐一復制的過程是由缺省復制構造函數自動完成的。例9.17將例9.16中的
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 快速查漏農業植保員資格試題及答案
- 提升解題能力2024年體育經紀人試題及答案
- 2024年足球裁判員考試技巧與試題
- 明確方向的2024籃球裁判員考試的試題與答案
- 2025年中國分膠咀市場調查研究報告
- 2025年中國六表框架市場調查研究報告
- 2025年中國光數綜合架市場調查研究報告
- 2025年中國伸出軸離合器市場調查研究報告
- 2025年中國乙酰乙酰-5-氨基苯駢咪唑酮市場調查研究報告
- 2025年中國不銹鋼中孔曝氣管市場調查研究報告
- 2024年9月28日福建省事業單位統考《行政職業能力測試》真題及答案
- 2024國家電投集團中國電力招聘(22人)筆試參考題庫附帶答案詳解
- 2025-2030中國醫藥冷鏈物流行業市場發展分析及競爭格局與投資前景研究報告
- 心血管-腎臟-代謝綜合征患者的綜合管理中國專家共識(2025版)解讀
- 樹立正確的婚戀觀講座課件
- 安徽省示范高中皖北協作區高三下學期第27屆聯考(一模)數學試題
- 急性闌尾炎中醫護理查房
- 【羅蘭貝格】2025全球醫療器械報告-創新與效率平衡之道
- 居間費用分配協議
- 《礦山安全生產治本攻堅三年行動(2025-2027年)實施方案》培訓
- 2023-2024學年福建省福州市四年級(下)期中數學試卷
評論
0/150
提交評論