鏈接:https://www.cnblogs.com/whale90830/p/10488595.html
由C到C++
OOP第一課
C語言的程由局限
C++的特點
C++的程序特征
C++程序的結構特性
C++程序的編輯、編譯和運行
?C++對C的向對象編補充
C語言的局限
類型檢查機制相對較弱,使得程序中的程由一些錯誤不能在編譯時由編譯器檢查出來。
C語言本身沒有支持代碼重用的向對象編語言結構
不適合開發大型程序,當程序的程由規模達到一定的程度時,程序員很難控制程序的向對象編復雜性。
C++的程由特點
C++繼承了C的優點,并有自己的向對象編特點,主要有:
1、程由全面兼容C,向對象編C的程由許多代碼不經修改就可以為Cpp所用,用C編寫的向對象編庫函數和實用軟件可以用于Cpp。
2、程由用C++編寫的向對象編程序可讀性更好,代碼結構更為合理,可直接在程序中映射問題空間結構。
3、生成代碼的質量高,運行效率高。
4、從開發時間、費用到形成軟件的可重用性、可擴充性、可維護性和可靠性等方面有了很大提高,使得大中型的程序開發項目變得容易得多。
5、支持面向對象的機制,可方便的構造出模擬現實問題的實體和操作。
C++的程序特征
例1.1 輸出一行字符:“This is a C++ program.”。
程序如下:
#include??//包含頭文件iostream
using?namespace?std;?//使用命名空間std
int?main(?)
{
????cout<<″This?is?a?C++?program.″;
????return?0;
}
在運行時會在屏幕上輸出以下一行信息:
This is a C++ program.
用main代表“主函數”的名字。每一個C++程序都必須有一個 main 函數。main前面的int的作用是聲明函數的類型為整型。程序第6行的作用是向操作系統返回一個零值。如果程序不能正常執行,則會自動向操作系統返回一個非零值,一般為-1。
函數體是由大括號{ }括起來的。本例中主函數內只有一個以cout開頭的語句。注意C++所有語句最后都應當有一個分號。
再看程序的第1行“#include
”,這不是Cpp的語句,而是Cpp的一個預處理命令,它以“#”開頭以與Cpp語句相區別,行的末尾沒有分號。
#include
是一個“包含命令”,它的作用是將文件iostream的內容包含到該命令所在的程序文件中,代替該命令行。文件iostream的作用是向程序提供輸入或輸出時所需要的一些信息。 iostream是i-o-stream3個詞的組合,從它的形式就可以知道它代表“輸入輸出流”的意思,由于這類文件都放在程序單元的開頭,所以稱為“頭文件” (head file)。在程序進行編譯時,先對所有的預處理命令進行處理,將頭文件的具體內容代替#include命令行,然后再對該程序單元進行整體編譯。
程序的第2行“using namespace std;”的意思是“使用命名空間std”。Cpp標準庫中的類和函數是在命名空間std中聲明的,因此程序中如果需要用到Cpp標準庫(此時就需要用#include命令行),就需要用“using namespace std;”作聲明,表示要用到命名空間std中的內容。
在初學C++時,對本程序中的第1,2行可以不必深究,只需知道:如果程序有輸入或輸出時,必須使用“#include
”命令以提供必要的信息,同時要用“using namespace std;”,使程序能夠使用這些信息,否則程序編譯時將出錯。
例1.2 求a和b兩個數之和
//?求兩數之和?(本行是注釋行)
#include??//預處理命令
using?namespace?std;?//使用命名空間std
int?main(?)?//主函數首部
{ ?//函數體開始
????int?a,b,sum;?//定義變量
????cin>>a>>b;?//輸入語句
????sum=a+b;?//賦值語句
????cout<<″a+b=″<????return?0;?//如程序正常結束,向操作系統返回一個零值
}?//函數結束
本程序的作用是求兩個整數a和b之和sum。
第1行“//求兩數之和”是一個注釋行,Cpp規定在一行中如果出現“//” ,則從它開始到本行末尾之間的全部內容都作為注釋。
例1.3 給兩個數x和y, 求兩數中的大者
#include??//預處理命令
using?namespace?std;
int?max(int?x,int?y)?//定義max函數,函數值為整型,形式參數x,?y為整型
{ ?//max函數體開始
????int?z;?//變量聲明,定義本函數中用到的變量z為整型
????if(x>y)?z=x;?//if語句,如果x>y,?則將x的值賦給z
????else?z=y;?//否則,將y的值賦給z
????return(z);?//將z的值返回,通過max帶回調用處
}?//max函數結束
int?main(?)?//主函數
{ ?//主函數體開始
????int?a,b,m;?//變量聲明
????cin>>a>>b;?//輸入變量a和b的值
????m=max(a,b);?//調用max函數,將得到的值賦給m
????cout<<″max=″<????return?0;?//如程序正常結束,向操作系統返回一個零值
}?//主函數結束
本程序包括兩個函數:主函數main和被調用的函數max。
程序運行情況如下:
18 25 ↙ (輸入18和25給a和b)
max=25 (輸出m的值)
注意輸入的兩個數據間用一個或多個空格間隔,不能以逗號或其他符號間隔。
在上面的程序中,max函數出現在main函數之前,因此在main函數中調用max函數時,編譯系統能識別max是已定義的函數名。如果把兩個函數的位置對換一下,即先寫main函數,后寫max函數,這時在編譯main函數遇到max時,編譯系統無法知道max代表什么含義,因而無法編譯,按出錯處理。
為了解決這個問題,在主函數中需要對被調用函數作聲明。上面的程序可以改寫如下:
#include?
using?namespace?std;
int?max(int?x,int?y);?//對max函數作聲明
int?main(?)
{
????int?a,b,c;
????cin>>a>>b;
????c=max(a,b);?//調用max函數例1.3 給兩個數x和y,?求兩數中的大者。
????cout<<″max=″<????return?0;
}
int?max(int?x,int?y)?//定義max函數
{
????int?z;
????if(x>y)?z=x;
????else?z=y;
????return(z);
}
只要在被調用函數的首部的末尾加一個分號,就成為對該函數的函數聲明。函數聲明的位置應當在函數調用之前。
C++程序的結構特性
一個面向對象的C++程序一般由類的聲明和類的使用兩大部分組成。
類的使用部分一般由主函數及有關子函數組成。
典型的C++程序結構
#include?
//類的聲明部分
class?A{
????int?x,y,z;
????……
????fun(?){ ……}
????……
};
//類的使用部分
int?main()
{
????A?a;
????……
????a.fun();
????return?0;
}
在C++程序中,程序設計始終圍繞“類”展開。通過聲明類,構建了程序所要完成的功能,體現了面向對象程序設計的思想。
C++程序的編輯、編譯和運行
C++源程序文件的擴展名為.CPP
可以用多種編譯器編輯、編譯和運行
C++對C的補充
1、注釋與續行
注釋符:“/*”和“*/” 或“//” 。
Cpp新增了注釋語句,它由“//”開始,到行尾結束。
例如:
X?=?y?+?z;?/*This?is?a?comment?*/
X?=?y?+?z;?//This?is?a?comment
續行符:“\”(反斜杠)。作用是當一個語句太長時可以用該符號把它分段寫在幾行中。
例:
cout?<‘\n’?<“x=”?<?<“u=”?<=”?<
2、輸入輸出流
C中I/O操作出現的問題:
int?i;
float?f;
scanf(“%f”,i);
printf(?“%d”,d);
Cpp中使用更安全更方便的方法:
int?i;
float?f;
cin?>>?i;
cout?<
cout和cin分別是C++的標準輸出流和輸入流。Cpp支持重定向,但一般cout指的是屏幕, cin指的是鍵盤。
操作符“<<”和“>>”除了具有C語言中定義的左移和右移的功能外,在這里符號“<<”是把右方的參數寫到標準輸出流cout中;相反,符號“>>”則是將標準輸入流的數據賦給右方的變量。
例1.4 一個完整的C++程序
#include?
int?main()
{
????char?name[20];
????cout?<"Hello, your name:?";
????cin?>>?name;
????cout?<????return?0;
}
注意:
程序中必須包含頭文件iostream.h
cin和>>,cout和<<配套使用
cin可以輸入多個數據,但要用空白符隔開(tab,空格,回車)
如:cin >>a >>b >>c;
換行符:‘\n’或endl
如:cout << “x=” << x << endl; cout << “x=” << x << ‘\n’;
使用cout和cin時,也可以對輸入和輸出的格式進行控制,比如可用不同的進制方式顯示數據,只要設置轉換基數的操作符dec、hex和oct即可。
例1.5 操作符dec、 hex和oct的使用
#include
void?main()
{
????int?x=25;
????cout?<}
輸出結果為:19 25 31
3、靈活的變量說明
定義變量的位置
在程序中的不同位置采用不同的變量定義方式,決定了該變量具有不同的特點。變量的定義一般可有以下三種位置:
(1) 在函數體內部
在函數體內部定義的變量稱為局部變量,這種局部變量只在進入定義它的函數體時起作用,離開該函數體后該變量就消失(被釋放),即不再起作用。因此,不同函數體內部可以定義相同名稱的變量,而互不干擾。
(2)?形式參數
當定義一個有參函數時,函數名后面括號內的變量,統稱為形式參數。
(3)?全局變量
在所有函數體外部定義的變量,其作用范圍是整個程序,并在整個程序運行期間有效。
在C語言中,全局變量聲明必須在任何函數之前,局部變量必須集中在可執行語句之前。
Cpp中的變量聲明非常靈活,它允許變量聲明與可執行語句在程序中交替出現。
例如
f(?)
{
????int?i;
????i=10;
????int?j;
????j=25;
????//?…
}
float?fun(int?x,int?y)
{
????for(int?i=0;i<10;i++)
????{
????????int?sum=0;
????????sum=sum+i;
????????cout<<“sum=”<????}
????int?z=0;
????z=x+y;
}
4、結構、聯合和枚舉名
在C++中,結構名、聯合名、枚舉名都是類型名。在定義變量時,不必在結構名、聯合名或枚舉名前冠以struct、union或enum。
例如:
enum?boole{ FALSE,TRUE};
struct?string{
????char?*string;
????int?length;
};
union?number{
????int?i;
????float?f;
};
在傳統的C中,定義變量時,必須寫成:
enum?boole?done;
struct?string?str;
union?number?x;
但是,在C++中,可以說明為:
boole?done;
string?str;
number?x;
5、函數原型
C語言建議編程者為程序中的每一個函數建立原型,而Cpp要求為每一個函數建立原型,以說明函數的名稱、參數類型與個數,以及函數返回值的類型。
其主要目的是讓C++編譯程序進行類型檢查,即形參與實參的類型匹配檢查,以及返回值是否與原型相符,以維護程序的正確性。
例如
int?sum(int?a,int?b);???//是函數sum的原型
函數原型語法的一般形式為:返回類型 函數名(參數表);
函數原型是一條語句,它必須以分號結束。
例1.6 函數原型的說明
#include
void?write(char?*s);
void?main()
{ write("Hello,world!");}
void?write(char?*s)
{ cout<
在程序中,要求一個函數的原型出現在該函數的調用語句之前。
說明:
函數原型的參數表中可不包含參數的名字,而只包含它們的類型。例如:long Area(int ,int);
函數定義由函數首部和函數體構成。函數首部和函數原型基本一樣,但函數首部中的參數必須給出名字而且不包含結尾的分號。
Cpp的參數說明必須放在函數說明后的括號內,不可將函數參數說明放在函數首部和函數體之間。這種方法只在C中成立。
主函數不必進行原型說明,因為它被看成自動說明原型的函數。
原型說明中沒有指定返回類型的函數(包括主函數main),Cpp默認該函數的返回類型是int
如果一個函數沒有返回值,則必須在函數原型中注明返回類型為void,主函數類似處理。
如果函數原型中未注明參數,Cpp假定該函數的參數表為空(void)。
6、const修飾符
在C中,習慣使用#define定義常量。
一般格式:?#define 宏名 常數
如
#define?PI?3.14
…………
s?=?2?*?PI?*?r;
…………
C++利用const定義正規常數
一般格式:const 數據類型標識符 常數名=常量值;
采用這種方式定義的常量是類型化的,它有地址,可以用指針指向這個值,但不能修改它。
說明:
1、const必須放在被修飾類型符和類型名前面
2、數據類型是一個可選項,用來指定常數值的數據類型,如果省略了該數據類型,那么編譯程序認為它是 int 類型。
如:const int a=10;?表示定義了一個初始值為10的整型常量,它在程序中不可改變,但可用于表達式的計算中。
例2.6?#define的不安全性
#include?"iostream.h"
main()
{
????int?a=1;
????#define?T1?a+a
????#define?T2?T1-T1
????cout<<"T2?is?"<????return?0;
}
但實際的輸出是:T2 is 2
const作用與#define相似,但消除了#define的不安全性。
如果用const取代了兩個#define,就不會引起這個錯誤。
#include
int?main()
{
????int?a=1;
????const?T1=a+a;
????const?T2=T1-T1;
????cout?<<"T2?is"<????return?0;
}
const可以與指針一起使用
(1)指向常量的指針:一個指向常量的指針變量。
例如:
const?char*?pc=“abcd”;??//聲明指向常量的指針
pc[3]=‘x’;??//錯誤
pc=“efgh”;??//允許
(2)常指針:把指針本身,而不是它指向的對象聲明為常量。
例如:
char*?const?pc=“abcd”;??//常指針
pc[3]=‘x’;??//合法
pc=“efgh”;??//出錯
創建一個常指針,就是創建一個不能移動的固定指針,但是它所指的數據可以改變。例如:
(3)指向常量的常指針:這個指針本身不能改變,它所指向的值也不能改變。
要聲明一個指向常量的常指針,二者都要聲明為const。
例如:
const?char*?const?pc=“abcd”;????//指向常量的常指針
pc[3]=‘x’;??//出錯
pc=“efgh”;??//出錯
這個語句的含義是:聲明了一個名為pc的指針變量,它是一個指向字符型常量的常指針,用“abcd”的地址初始化該指針。
說明
(1). 如果用const定義的是一個整型常量,關鍵詞int可以省略。所以下面的兩語句是等價的
const int bufsize=200;
const bufsize=200;
(2). 常量一旦被建立,在程序的任何地方都不能再更改。
(3). 與#define定義的常量有所不同,const定義的常量可以有自己的數據類型,這樣C++的編譯程序可以進行更加嚴格的類型檢查,具有良好的編譯時的檢測性。
(4). 函數參數也可以用const說明,用于保證實參在該函數內部不被改動,大多數C++編譯器能對具有const參數的函數進行更好的代碼優化。
例如:通過函數i_Max求出整型數組a[200]中的最大值,函數原型應該是:int i_Max(const int* ptr);
這樣做的目的是確保原數組的數據不被破壞,即在函數中對數組元素的操作只許讀,而不許寫。調用時的格式可以是:i_Max(a);
7、void型指針
void 通常表示無值,但將void作為指針的類型時,它卻表示不確定的類型。
這種void型指針是一種通用型指針,也就是說任何類型的指針值都可以賦給void類型的指針變量。
例如下面的程序段
void?pa;????//錯誤,不能聲明void類型的指針變量
void*?pc;???//正確,可以聲明void類型的指針
int?i=456;
char?c=‘a’;
pc=&i;
pc=&c;
void型指針可以接受任何類型的指針的賦值,但對已獲值的void型指針,對它在進行處理,如輸出或傳遞指針值時,則必須進行強制類型轉換,否則會出錯。
#include?
main()
{
????void?*pc;
????int?i=456;
????char?c='a';
????pc=&i;
????cout<<*(int?*)pc<????pc=&c;
????cout<<*(char?*)pc<????return?0;
8、內聯函數
調用函數時系統要付出一定的開銷,用于信息入棧出棧和參數傳遞等。特別是對于那些函數體較小但調用又較頻繁的函數,計算機的開銷相對就比較可觀。
在C語言中,用宏替換,可解決這個問題。例如,有如下的函數:
add(int?x,int?y)
{
????return?x+y;
}
用宏替換時,上面的函數功能可寫為:
#define?add(x,y)?x+y
C++引進了內聯函數(inline function)的概念。
宏替換實質上是文字替換。內聯函數與一般函數不同的是,在進行程序的編譯時,編譯器將內聯函數的目標代碼作拷貝并將其插入到調用內聯函數的地方。
例1.7 內聯函數的使用
#include?"iostream.h"
inline?double?circle(double?r)
{ return?3.1416*r*r;}
int?main()
{
????for(int?i=1;i<=3;i++)
????????cout<<"r="<????return?0;
}
說明:
(1). 內聯函數在第一次被調用前必須進行聲明或定義,否則編譯器將無法知道應該插入什么代碼。
(2). C++的內聯函數具有與C中的宏定義#define相同的作用和類似機理,但消除了#define的不安全性。
(3). 內聯函數體內一般不能有循環語句和開關語句。
(4). 后面類結構中所有在類說明體內定義的函數都是內聯函數。
(5). 通常較短的函數才定義為內聯函數。
9、帶有缺省參數值的函數
在C++中,函數的參數可以有缺省值。
當調用有缺省參數的函數時,如果相應的參數沒有給出實參,則自動用相應的缺省參數值作為其實參。
函數的缺省參數,是在函數原型中給定的。
例如:
int?init(int?x=5,?int?y=10);
init(100,80);???//允許
init(25);???//允許
init();???//允許
說明:
(1)在函數原型中,所有取缺省值的參數必須出現在不取缺省值的參數的右邊。
int fun(int I,int j=5,int k);?錯誤
int fun(int I,int k,int j=5);?正確
(2)在函數調用時,若某個參數省略,則其后的參數皆應省略而采用缺省值。
init (,20)?錯誤
例.編寫一個帶有默認參數的函數,使得在默認情況下顯示兩個整數的較大者,否則顯示兩個整數的較小者。
int?main()
{
????void?showValue(int?x,?int?y,?bool?Max?=?true);?//?聲明函數
????int?a?=?5,?b?=?10;
????showValue(a,b);
????showValue(a,b,false);
????return?0;
}
void?showValue(int?x,?int?y,?bool?Max?=?true)?//?定義函數
{
????if(Max)
????????cout?<“the?bigger?value?is:?"?<(x>y)?x:y?<????else
????????cout?<"the?smaller?value?is:?"?<(x}
10、函數重載
(1) 什么是函數重載
函數重載是指一個函數可以和同一作用域中的其他函數具有相同的名字,但這些同名函數的參數類型、參數個數不同。如:
#include?
void?whatitis(int?i)
{ ?cout<<"this?is?integer"<void?whatitis(char?c[])
{ ?cout<<“this?is?string”<main()
{
????int?i=1;
????char?c[]="abcdef";
????whatitis(i);
????whatitis(c);
}
在本例中定義了兩個名稱都叫whatitis的函數,但它們的形參類型不同。因此,這兩個函數就是重載函數。
(2) 為什么要使用函數重載
在原有C語言中,每個函數必須有其唯一的名稱,這樣的缺點是所有具有相同功能、而只是函數參數不一樣的函數,就必須用一個不同的名稱.
而C++中采用了函數重載后,對于具有同一功能的函數,如果只是由于函數參數類型不一樣,則可以定義相同名稱的函數。
(3) 匹配重載函數的順序
由于重載函數具有相同的函數名,在進行函數調用時,系統一般按照調用函數時的參數個數、類型和順序來確定被調用的函數。
具體來說,按以下三個步驟的先后次序找到并調用那個函數:
(1)尋找一個嚴格的匹配,即:調用與實參的數據類型、個數完全相同的那個函數。
(2)通過內部轉換尋求一個匹配,即:通過(1)的方法沒有找到相匹配的函數時,則由C++系統對實參的數據類型進行內部轉換,轉換完畢后,如果有匹配的函數存在,則執行該函數。
(3)通過用戶定義的轉換尋求一個匹配,若能查出有唯一的一組轉換,就調用那個函數。即:在函數調用處由程序員對實參進行強制類型轉換,以此作為查找相匹配的函數的依據。
例1.8 重載例子
#include?
void?print(double?d)
{ ?cout<<"this?is?a?double?"<void?print(int?i)
{ ?cout<<"this?is?an?integer?"<void?main()
{
????int?x=1,z=10;
????float?y=1.0;
????char?c='a';
????print(x);???//按規則(1)自動匹配函數void?print(int?i)
????print(y);???//按規則(2)通過內部轉換匹配函數?void?print(double?i)
????//因為系統能自動將float型轉換成double型
????print(c);???//按規則(2)通過內部轉換匹配函數?void?print(int?i)
????//因為系統能自動將char型轉換成int型
????print(double(z));???//按規則(3)匹配void?print(double?i)
????//因為程序中將實參z強制轉換為double型。
}
例 重載例子
編寫一個程序,用來求兩個整數或3個整數中的最大數。如果輸入兩個整數,程序就輸出這兩個整數中的最大數,如果輸入3個整數,程序就輸出這3個整數中的最大數。
#include?
using?namespace?std;
int?main(?)
{
????int?max(int?a,int?b,int?c);?//函數聲明
????int?max(int?a,int?b);?//函數聲明
????int?a=8,?b=-12,?c=27;
????cout<<"max(a,b,c)="<????cout<<"max(a,b)="<}
int?max(int?a,int?b,int?c)
{
????if(b>a)?a=b;
????if(c>a)?a=c;
????return?a;
}
int?max(int?a,int?b)
{
????if(a>b)?return?a;
????else?return?b;
}
(4) 定義重載函數時的注意事項
重載函數間不能只是函數的返回值不同,應至少在形參的個數、參數類型或參數順序上有所不同。
如:
void?myfun(int?i)
{ ………………}
int?myfun(int?i)
{ ………………}
//?這種重載是錯誤的
應使所有的重載函數的功能相同。如果讓重載函數完成不同的功能,會破壞程序的可讀性。
(5) 函數模板
1)?函數模板 (function template):
建立一個通用函數,其函數類型和形參類型不具體指定,而是一個虛擬類型。
2)?應用情況:
凡是函數體相同的函數都可以用這個模板來代替,不必定義多個函數,只需在模板中定義一次即可。在調用函數時系統會根據實參的類型來取代模板中的虛擬類型,從而實現了不同函數的功能。
3) 一般形式:
-?template < typename T>// 模板頭?通用函數定義
-?template
-?template
說明:class與typename可以通用
#include?
using?namespace?std;
template?//?模板聲明,其中T為類型參數
T?max(T?a,?T?b)?//?定義一個通用函數,?T作為虛擬的類型名
{
????if(b>a)?return?b;
????else?return?a;
}
//template??T?max(T?a,?T?b)
//{
//…
//}
int?main(?)
{
????int?i1=111,?i2=222,?i;
????double?d1=12.34,?d2=56.78,d;
????i=max(i1,i2);?//?調用模板函數,此時T被?int?取代
????d=max(d1,d2,d3);?//?調用模板函數,此時T被?double?取代
????cout<<"i_max="?<????cout<<"f_max="?<????return?0;
}
函數模板說明:
1) 在對程序進行編譯時,遇到第13行調用函數max(i1,i2), 編譯系統會將函數名max與模板max相匹配,將實參的類型取代了函數模板中的虛擬類型T。此時相當于已定義了一個函數,然后調用它。
int?max(int?a,int?b)
{
if(b>a)?a=b;
if(c>a)?a=c;
return?a;
}
2)?與重載函數比較:用函數模板比函數重載更方便,程序更簡潔。但應注意它只適用于:函數的參數個數相同而類型不同,且函數體相同的情況。如果參數的個數不同,則不能用函數模板;
3) main函數不能定義為模板函數。
11. 作用域標示符::
通常情況下,如果有兩個同名變量,一個是全局的,另一個是局部的,那么局部變量在其作用域內具有較高的優先權。
下面的例子說明了這個問題。
#include?"iostream.h"
int?avar=10;
main(?)
{
????int?avar;
????avar=25;
????cout<<"avar?is"???return?0;
}
如果希望在局部變量的作用域內使用同名的全局變量,可以在全局變量加上“::”,此時::avar代表全局變量avar
#include?
int?avar=10;
main()
{
int?avar;
avar=25;
cout<<"local?avar?="return?0;
}
12、無名聯合
無名聯合是C++中的一種特殊聯合,可以聲明一組無標記名共享同一段內存地址的數據項。如:
union{
????int?i;
????float?f;
}
在此無名聯合中,聲明了變量i和f具有相同的存儲地址。無名聯合可通過使用其中數據項名字直接存取,例如可以直接使用上面的變量i或f,如:i=20;
13、強制類型轉換
在C中數據類型轉換的一般形式:(數據類型標識符)表達式
int?i=10;
float?x=(float)?i;
C++支持這樣的格式,還提供了一種更為方便的函數調用方法,即將類型名作為函數名使用,使得類型轉換的執行看起來好像調用了一個函數。形式為:數據類型標識符 (表達式)
int?i=10;
float?x=float(i);
以上兩種方法C++都接受,但推薦使用后一種方式。
14、動態內存分配
作為對C語言中malloc和free的替換,C++引進了new和delete操作符。它們的功能是實現內存的動態分配和釋放。
動態分配new的一般形式是:
指針變量=new 數據類型;
指針變量=new 數據類型(初始值);
int?*a,?*b;
a=new?int;
b=new?int(10);
釋放由new操作動態分配的內存時,用delete操作。
它的一般形式是:delete 指針變量;
delete?a;
delete?b;
例1.9 操作符new和delete的使用
#include?
main()
{
????int?*p;?//?聲明一個整型指針變量p
????p=new?int;?//?動態分配一個存放int型數據的內存空間,并將首地址賦給p
????*p=10;
????cout<<*p;
????delete?p;?//?釋放指針變量p指向的內存空間
????return?0;
}
例1.10 將new和delete用于結構類型
#include
#include
struct?person?{
????char?name[20];
????int?age;
};
main()
{
????person?*p;
????p=new?person;
????strcpy(p->name,?"Wang?Fun");
????p->age=23;
????cout<<"\n"<name<<"?"<age;
????delete?p;
????return?0;
}
與C的內存動態分配和釋放操作(malloc和free)相比,C++提供的動態分配有以下優點
(1) new和delete 操作自動計算需要分配和釋放類型的長度。這不但省去了用sizeof計算長度的步驟,更主要的是避免
了內存分配和釋放時因長度出錯帶來的嚴重后果;(2) new操作自動返回需分配類型的指針,?無需使用強制類型轉換;
(3) new操作能初始化所分配的類型變量。
(4) new和delete都能可以被重載,允許建立自定義的內存管理法。
對使用new和delete的幾點說明:
(1)用new分配的空間,使用結束后應該用delete顯示的釋放,否則這部分空間將不能回收而變成死空間。
(2)使用new動態分配內存時,如果沒有足夠的內存滿足分配要求, new將返回空指針(NULL)。因此通常要對內存的動態分配是否成功進行檢查。
例1.11 對內存的動態分配是否成功進行檢查
#include?
main()
{
????int?*?p;
????p=new?int;
????if(!p){
????????cout<<"allocation?failure\n";
????????return?1;
????}
????*p=20;
????cout<<*p;
????delete?p;
????return?0;
}
(3) 使用new可以為數組動態分配內存空間這是需要在類型后面綴上數組大小。其語法形式為:
指針變量=new 類型名 [下標表達式];如:int *pi=new int[2][3][4];
其中第一維的界值可以是任何合法的表達式,如:int i=3; int *pi=new int[ i ][2][3];
例如:int *pi=new int[10];
這時new為具有10個元素的整型數組分配了內存空間,并將首地址賦給了指針pi。
使用new為多維數組分配空間時,必須提供所有維的大小,
(4) 釋放動態分配的數組存儲區時,可使用delete運算符,其語法形式為:delete [ ]指針變量;
無須指出空間的大小,但老版本的Cpp要求在delete的方括號中標出數字,以告訴Cpp要釋放多少個元素所占的空間。例如:delete []pi; delete [10]pi;
(5) new可在為簡單變量分配內存空間的同時,進行初始化。這時的語法形式為:指針變量=new 類型名(初始值列表)
例 1.12 new為簡單變量分配內存空間的同時,進行初始化
#include?
int?main()
{
????int?*p;
????p=new?int(99);?//?動態分配內存,并將99作為初始值賦給它
????if?(!p)
????{
????????cout<<"allocation?failure\n";
????????return?1;
????}
????cout<<*p;
????delete?p;
????return?0;
}
例 1.13 給數組分配內存空間的例子。
#include?
main()
{
????double?*s;
????s=new?double[10];
????if(!s){
????????cout<<"alocation?failure\n";
????????return?1;
????}
????for(int?i=0;i<10;i++)
????????s[i]=100.00+2*i;
????for(int?i=0;i<10;i++)
????????cout<????delete?[]s;
????return?0;
}
15. 引用
(1) 引用的概念
引用就是某一變量(目標)的一個別名,這樣對引用的操作就是對目標的操作。
引用的聲明方法:?類型標識符 &引用名=目標變量名;
int?a;
int?&ra=a;?//定義引用ra,它是變量a的引用,即別名
說明:
(1) &在此不是求地址運算,而是起標識作用。
(2)?類型標識符是指目標變量的類型。
(3)聲明引用時,必須同時對其進行初始化。
(4)引用聲明完畢后,相當于目標變量名有兩個名稱。
(5)聲明一個引用,不是新定義了一個變量,系統并不給引用分配存儲單元。
例1.15 引用的使用
#include?
void?main()
{
????int?i;
????int?&j=i;
????i=30;
????cout<<"i="<????j=80;
????cout<<"i="<????cout<<"Address?of?i"<<&i<<"\n";
????cout?<<"Address?of?j"<<&j<<"\n";
}
結果:
i=30?j=30
i=80?j=80
Address?of?oxfff4
Address?of?oxfff4
例1.16 使用引用可以簡化程序
#include
main()
{
????int?i=15;
????int*?iptr=&i;
????int?&?rptr=i;
????cout<<"?i?is?"<????cout<<"?*iptr?is?"<<*iptr<????cout<<"?rptr?is?"<????i=29;
????cout<<" After changing i to 29:?"<????cout<<"?i?is?"<????cout<<"?*iptr?is?"<<*iptr<????cout<<"?rptr?is?"<????return?0;
}
運行結果:
i?is?15
*iptr?is?15
rptr?is?15
After?changing?i?to?29:
i?is?29
*iptr?is?29
rptr?is?29
(2) 引用的使用
(1)引用名可以是任何合法的變量名。除了用作函數的參數或返回類型外,在聲明時,必須立即對它進行初始化,不能聲明完后再賦值。
int?i;
int?&j;
j=i;
(2)引用不能重新賦值,不能再把該引用名作為其他變量名的別名,任何對該引用的賦值就是該引用對應的目標變量名的賦值。對引用求地址,就是對目標變量求地址。
int?i=5;
int?&j1=i;?
int?&j2=j1;?
int?num=50;
int?&?ref=num;
int?*p=&ref;
(3)由于指針變量也是變量,所以,可以聲明一個指針變量的引用。方法是:?類型標識符 *&引用名=指針變量名;
#include?
void?main()
{
????int?*a;?//定義指針變量a
????int?*&p=a;?//定義引用p,初始化為指針變量a,所以p是a的引用(別名)
????int?b=10;
????p=&b;?//等價于a=&b,即將變量b的地址賦給a。
????cout<<*a<????cout<<*p<????cout???cout<}
(4)引用是對某一變量或目標對象的引用,它本身不是一種數據類型,因此引用本身不占存儲單元,這樣,就不能聲明引用的引用,也不能定義引用的指針。
int?a;
int?&?&?ra=a;?//錯誤
int?&*p=&ra;?//錯誤
(5)不能建立數組的引用,因為數組是一個由若干個元素所組成的集合,所以就無法建立一個數組的別名。
(6)不能建立空指針的引用
int?&rp=NULL;?//錯誤
(7)也不能建立空類型void的引用,因為盡管在C++語言中有void數據類型,但沒有任何一個變量或常量屬于void類型。
void?&ra=3;?//錯誤
(8) 盡管引用運算符與地址操作符使用相同的的符號,但時不一樣的。引用僅在聲明時帶有引用運算符&,以后就像普通變量一樣使用,不能再帶&。其他場合使用的&都是地址操作符。
int?j=5;
int&?i=j;?//?聲明引用i,?"&"為引用運算符
i=123;?//?使用引用i,不帶引用運算符
int?*pi=&i;?//?在此,?"&"為地址操作符
cout<<π?//?在此,?"&"為地址操作符
(3) 用引用作為函數的參數
一個函數的參數也可定義成引用的形式
void?swap(int?&p1,?int?&p2)?//形參p1,?p2都是引用
{
????int?p;
????p=p1;
????p1=p2;
????p2=p;
}
在主調函數的調用點處,直接以變量作為實參進行調用即可,不需要實參變量有任何的特殊要求。
swap(a,b);?//直接以a和b作為實參調用swap函數
例1.17 采用指針參數的例子
#include
void?swap(int?*m,?int?*n)
{
????int?temp;
????temp=*m;
????*m=?*n;
????*n=temp;
}
main()
{
????int?a=5,?b=10;
????cout<<"a="???swap(&a,?&b);
????cout<<"a="???return?0;
}
運行結果:
a=5?b=10
a=10?b=5
例1.18 采用“引用參數”傳遞函數參數
#include?
void?swap(int&?m,?int&?n)
{
????int?temp;
????temp=m;
????m=n;
????n=temp;
}
main()
{
????int?a=5,?b=10;
????cout<<"a="???swap(a,?b);
????cout<<"a="???return?0;
}
運行結果:
a=5?b=10
a=10?b=5
(4) 用引用返回函數值
函數可以返回一個引用,將函數說明為返回一個引用的主要目的是:為了將函數用在賦值運算符的左邊。
要以引用返回函數值,則函數定義時要按以下格式:
類型標識符 &函數名(形參列表及類型說明)
{ 函數體}
說明
以引用返回函數值,定義函數時需要在函數名前加&
用引用返回一個函數值的最大好處是,在內存中不產生被返回值的副本。
例1.19 返回引用的函數
#include?
int?a[]={ 1,?3,?5,?7,?9};
int&?index(int);?//?聲明返回引用的函數
void?main()
{
????cout<????index(2)=25;?//?將a[2]重新賦值為25
????cout<}
int&?index(int?i)
{
????return?a[i];
}
例1.20 用引用返回函數的值
#include
int?A[10];
int&?array(int?i);
void?main()
{
????int?i,?number;
????A[0]=0;
????A[1]=1;
????cin>>number;
????for?(i=2;i????{
????????array(i)=array(i-2)+array(i-1);
????????cout<<"array("<}
int&?array(int?i)
{
????return?A[i];
}
運行結果:
array(2)=1
array(3)=2
array(4)=3
array(5)=5
array(6)=8
array(7)=13
array(8)=21
array(9)=34
在定義返回引用的函數時,注意不要返回該函數內的自動變量 (局部變量)的引用,由于自動變量的生存期僅限于函數內部,當函數返回時,自動變量就消失了。
int&?fun()
{
????int?a;
????//...
????return?a;
}
傳遞引用給函數與傳遞指針的效果是一樣的,但使用更簡練。
使用引用傳遞函數的參數,在內存中并沒有產生實參的副本,它是直接對實參操作;
void?swap(int?*p1,?int?*p2)
{
????int?p;
????p=*p1;?//必須用“*指針變量名”的形式操作目標變量
????p1=*p2;
????*p2=p;
}
main()
{
????int?a,b;
????cin>>a>>b;
????swap(&a,&b);?//必須以變量a和b的地址作為實參
????cout<}
如何使一個被調函數同時返回多個值
由于函數的返回值是通過函數體中的return語句完成的,但一個return語句只能返回一個值,為此,我們可以采用以下方法:
(1)利用全局變量的方法:在函數中把所需數據保存在全局變量中。當被調函數執行完畢后在主調函數中直接讀取全局變量的值即可。
(2)使用指針或數組的方法:指針作為函數參數的情況下,可將主調函數的某些變量的地址傳遞給被調函數。
(3)利用引用的方法:使用引用傳遞參數,可以在被調函數中改變主調函數中目標變量的值,這種方法實際上就是可以使被調函數返回多個值。
例 使用引用使函數返回多個值
以下定義了可以同時返回10個數中的最大值和最小值的函數max_min。
#include?
void?max_min(int?*p,int?n,int?&max,int?&min);
//聲明函數max_min
void?main()
{
????int?a[10];
????int?ma,mi;
????int?i;
????for(i=0;i<10;i++)
????????cin>>a[i];
????max_min(a,10,ma,mi);?//調用函數max_min
????cout<}
void?max_min(int?*p,int?n,int?&max,int?&min)
//形參max?和min定義成引用
{
????int?i=0;
????max=*(p+i);
????min=*(p+i);
????for(i=1;i????????if(max<*(p+i))
????????????max=*(p+i);?//實質上就是對實參變量ma賦值
????????if(min>*(p+i))
????????????min=*(p+i);?//實質上就是對實參變量mi賦值
????}
}
例 以下程序中定義了一個普通的函數fn1(它用返回值的方法返回函數值),另外一個函數fn2,它以引用的方法返回函數值。
#include?
float?temp;?//定義全局變量temp
float?fn1(float?r);?//聲明函數fn1
float?&fn2(float?r);?//聲明函數fn2
float?fn1(float?r)?//定義函數fn1,它以返回值的方法返回函數值
{
????temp=(float)(r*r*3.14);
????return?temp;
}
float?&fn2(float?r)?//定義函數fn2,它以引用方式返回函數值
{
????temp=(float)(r*r*3.14);
????return?temp;
}
void?main()?//主函數
{
????float?a=fn1(10.0);//第1種情況,系統生成要返回值的副本(即臨時變量)
????float?&b=fn1(10.0);//第2種情況,可能會出錯(不同C++系統有不同規定)
????//不能從被調函數中返回一個臨時變量或局部變量的引用
????float?c=fn2(10.0);?//第3種情況,系統不生成返回值的副本
????//可以從被調函數中返回一個全局變量的引用
????float?&d=fn2(10.0);?//第4種情況,系統不生成返回值的副本
????//可以從被調函數中返回一個全局變量的引用
????cout<}
一個返回引用的函數值作為賦值表達式的左值
一般情況下,賦值表達式的左邊只能是變量名,即被賦
值的對象必須是變量,只有變量才能被賦值,常量或表達式不能被賦值,但如果一個函數的返回值是引用時,賦值號的左邊可以是該函數的調用。
例2-26 測試用返回引用的函數值作為賦值表達式的左值。
#include?
int?&put(int?n);
int?vals[10];
int?error=-1;
void?main()
{
????put(0)=10;?//以put(0)函數值作為左值,?等價于vals[0]=10;
????put(9)=20;?//以put(9)函數值作為左值,?等價于?vals[9]=10;
????cout<????cout<}
int?&put(int?n)
{
????if?(n>=0?&&?n<=9?)
????????return?vals[n];
????else{
????????cout<<”subscript?error”;
????????return?error;
????}
}
用const限定引用
聲明方式:const 類型標識符 &引用名=目標變量名;
用這種方式聲明的引用,不能通過引用對目標變量的值進行修改,從而使引用的目標成為const,達到了引用的安全性。
例2-27
#include?“iostream.h”
double?&fn(const?double?&pd)
{
????static?double?ad=32;
????ad+=pd;
????cout<????return?ad;
}
void?main()
{
????double?a=100.0;
????double?&pa=fn(a);
????cout<????a=200.0;
????pa=fn(a);
????cout<}
程序運行的結果
100
132
200
332
引用總結
(1)在引用的使用中,單純給某個變量取個別名是毫無意義的,引用的目的主要用于在函數參數傳遞中,解決大對象的傳遞效率和空間不如意的問題。
(2)用引用傳遞函數的參數,能保證參數傳遞中不產生副本,提高傳遞的效率,且通過const的使用,保證了引用傳遞的安全性。
(3)引用與指針的區別是,指針通過某個指針變量指向一個對象后,對它所指向的變量間接操作,程序中使用指針,程序的可讀性差;而引用本身就是目標變量的別名,對引用的操作就是對目標變量的操作。
課后練習題目
#include
int?&max(int?&num1,int?&num2);?//?返回一個較大值
int?&min(int?&num1,int?&num2);?//?返回一個較小值
main()
{
????int?num1,?num2;
????cout<<"Enter?the?first?number:?";
????cin>>num1;
????cout<<"Enter?the?second?number:?";
????cin>>num2;
????max(num1,num2)=0;
????cout<<"\nAfter?putting?zero?in?largest,?the?numbers?are";
????cout<<"\n"<????cout<<"\nNow,?please?enter?two?more?numbers.\n";
????cout<<"Enter?the?first?number?:";
????cin>>num1;
????cout<<"Enter?the?second?number:";
????cin>>num2;
????min(num1,?num2)=0;
????cout<<"\nAfter?putting?zero?in?smallest?the?numbers?are";
????cout<<"\n"<????return?0;
}
int?&max(int?&num1,int?&num2)
{
????return?(num1>num2)?num1:num2;
}
int?&min(int?&num1,int?&num2)
{
????return?(num1}
免責聲明:本文內容由21ic獲得授權后發布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯系我們,謝謝!