面向對象編程中引用和const的結合增強程序的效率和健壯性
摘要:在使用c++做面向對象編程的時候,合理的使用引用和const關鍵字可以達到提高程序的效率和健壯性的目的
一、引用的介紹
1.1 什麼是引用
引用(Reference)通過&來標記,用來爲存儲器取別名。例如:
Int X;
Int &ref = X;
分配了一個int單元,它擁有兩個名字:X和ref。執行:X = 3;或則 ref = 3;都將3存儲到該int 單元。
1.2 引用的用法
由定義可以知道:引用就是給存儲器取別名。因此可以通過引用直接訪問某個存儲單元。通常在函數調用過程中,傳遞參數有兩種方式:傳值(Call by value)和傳地址(Call by Referenc)。而在函數返也有兩種方式:傳值返回(Return by Value)和引用返回(Return by Reference)。因此本節用將分別從函數調用和函數返回兩個方面介紹引用的用法。
1.2.1 引用調用
在傳值調用時,實參傳遞給形參的只是一個實參的臨時拷貝,雖然在函數執行時改變了形參,但是當函數結束後,形參的生命週期就結束了。此時,形參的改變不能反映到實參上去。例如:
Void swap( int a ,int b)
{
Int Temp;
Temp = a;
a = b;
b = temp;
}
執行如下語句:
int x = 5;
int y = 10;
swap(x,y);
cout<<x<<”,”<<y<<endl;
後輸出的仍然是:5,10.
而引用調用時,引用參數將實際的實參地址傳遞給函數,而不是將實參的一個臨時拷貝傳遞給函數。如果將上述代碼中的函數定義部分改成:swap(&a,&b)其他部分不做修改,那麼運行結果將是:10,5.這是因爲在執行swap(x,y)時,形參a直接指向了實參x的存儲空間,形參b直接指向了實參y的存儲空間。此時,swap函數不是對x,y的臨時拷貝進行操作,而是直接對x,y本身進行操作。因此函數結束時就完成了對變量交換的目的。
由此,我們如果需要利用一個函數實現對實參的修改,那麼我們可以在定義形參的時候使用引用類型。
1.2.2 引用返回
在c++中,默認情況下,當函數執行:return expression ; 時,expression被求值,然後把這個值拷貝到臨時存儲空間,以便函數調用者訪問。這種返回方式叫做:傳值返回。調用如下函數:
Int F( )
{
// the body ………
Return I ;
}時,I的值將拷貝到臨時存儲空間,然後函數結束,I的生命週期也結束,調用者可以獲得I的一個副本,但不能再訪問I的值,因爲它的生命週期已經結束。如:J = F ( );
則I的值拷貝到臨時空間,然後再拷貝到J.
函數返回的另外一種形式:引用返回(Return by Reference)。定義引用返回的形式如下:
Int & F()
{
//the body ….
Return I;
}
在這種情況下,返回值I不再被拷貝到臨時存儲單元,甚至對return 語句所用的存儲單元對調用者來說也是可訪問的。因此:j = F( )的執行不再是將I的副本拷貝到j 中,而是直接將I的值拷貝給j 。
使用引用返回的一個好處是:如果一個函數以引用凡是返回,則這個函數調用可以出現在賦值號的左邊。如:F( ) = 8;此時如果要訪問F()函數中的return語句之後的變量I值,那麼需要I的生命週期沒有結束纔可以,也就是說,I不能定義成一個作用域在F()內的局部變量。如:
Int F( )
{
Int I;
//………..
Return I;
}
包含一個錯誤,當F返回I時,I已經不存在了(因爲I是局部變量)。因此,如果函數調用者想使用F( )= 8;來訪問I的值就是不可能的。此時可以將I定義成全局變量,或者定義成一個引用型變量:
Int F(Int & I)
{
//……….
Return I;
}
或者將被返回的變量定義成一個static 類型的變量:
Int F ( )
{
Static int I;
Return I;
}
1.3 引用的特點
通過前面兩節可以知道:使用引用傳參形式可以直接修改實際參數的內容,解決了形參的局部性限制問題;使用引用返回,可以達到直接訪問返回變量的目的,而且可以爲一個函數調用賦值!
二、const 介紹
2.1 什麼是const
Const的中文意思是:常數,不變的。其英文解釋可以是:An attribute of a data object that declares that the value of the object cannot be changed. 也就是說用const修飾的變量、函數等是隻讀的,不可以被修改。
2.2 const 的用法
在c++中,const大致可以用來實現如下功能:定義const參數,定義const函數,用const限定函數的返回值,定義常量指針,定義指針常量。在這裏只介紹前面三種用法,而對常量指針和指針常量不做介紹。
2.2.1 定義const參數
在如下的類聲明中:
Class person{
Public:
Void SetName(const string &n){ name = n;}
// ….other public members.
Private:
String name;
};
函數SetName的string類型參數n標記爲const,表明SetName不會改變n,只會將n的值賦給數據成員name。同時注意在這裏n被定義成引用類型。 使用const限定符和string &n形式組合作爲參數傳遞給函數是一種良好的習慣.可以提效率.string& n將用引作爲參數時,系統不用再爲參數建立臨時對象,提高了效率.再加上限定符 const就可以保證用引變量不會被修改.從而實現了值傳遞,而且還減少了系統開銷。
2.2.2 定義const函數
在如下的類聲明中:
Class person{
Public:
Void SetName(const string &n){ name = n;}
String GetName()const { return name;}
// ….other public members.
Private:
String name;
};
成員函數:GetName不需要改變類person的任何數據成員,我們將這種用const標記的函數稱爲:只讀函數。將成員函數標記爲只讀函數可以防止成員函數對類的數據成員的修改,這樣提高了程序的健壯性。同時注意,只讀函數不能在其內部調用非只讀函數,因爲非只讀函數有可能修改類的數據成員。如果一個成員函數不需要直接或則間接的修改類的數據成員,那麼最好將該函數聲明爲只讀函數。
2.2.3 使用const限定函數的返回值
在如下的類聲明中:
Class person{
Public:
Void SetName(const string &n){ name = n;}
Const String& GetName()const { return name;}
// ….other public members.
Private:
String name;
};
成員函數GetName的返回數據成員name的一個const型引用,此處的const表明誰也不能通過這個引用來修改數據成員name的值。同時注意這個類展示了const的三種不同用法。
三、引用和const對程序的效率和健壯性的貢獻
3.1通過引用來傳遞和返回對象提高程序的效率
和其他類型一樣,對象作爲參數傳遞時有兩種方式:傳值和引用。當對象作爲一個函數的返回值的時候也有兩種方式:傳值返回和引用返回。
不管是參數的傳值傳遞還是函數的傳值返回,他們都有一個共同的問題:將對象拷貝到一個臨時變量(對象)中,然後在傳遞給函數或則返回給調用者。當一個類的對象很複雜的時候,這個拷貝臨時對象的過程將會是數據增大,浪費內存,從而明顯影響程序的效率。
因此,除非迫不得已,一般來說應該採用引用方式進行對象的傳遞和返回,而不採用傳值方式。
3.2 利用const關鍵字增強程序的健壯性
如果一個成員函數的形參定義成引用類型,那麼我們就可以在該函數中改變傳遞給這個函數的實參的值,而有時候出於安全考慮,我們希望成員函數不要修改傳遞給其的實參的值,這時,我們可以使用const關鍵字加以限定(定義const參數)。
如果我們希望一個類的成員函數不要對這個類的成員變量進行修改,那麼我們也可以使用const關鍵字加以限定(定義const函數。
同理,如果我們不希望通過引用返回的方式修改類的數據成員,那麼可以利用const關鍵字限定一個成員函數的返回類型。
3.3 引用與const聯合提高程的序效率和健壯性
通過前面兩節的介紹,我們知道,合理的使用引用可以提高程序的效率,但它卻有影響程序健壯性的可能,因爲引用的使用可以修改實參,也可以對類的成員函數進行修改。而const的作用正好是強制限定了這些修改功能。因此在利用c++做面向對象編程的時候將引用和const結合起來,可以達到提高效率的同時也增強程序的健壯性的目的。
四、示例測試
#include "stdafx.h"
#include "iostream"
#include "string"
using namespace std;
class person1{
public:
void SetName(string n){name = n;}
string GetName(){return name;}
private:
string name;
};
class person2{
public:
void SetName(string &n){n= "ppp2";name = n;}
string& GetName(){return name;}
private:
string name;
};
class person3{
public:
void SetName(const string& n)
{
//n = "p3"; Error !!
name = n;
}
string GetName()const {return name;}
// string &GetName()const {return name;} Error 函數返回是const就不能指定函數返回爲引用
private:
string name;
};
class person4{
public:
void SetName(const string& n)
{
//n = "p4"; Error!!!
name = n;
}
const string& GetName()const {return name;}
private:
string name;
};
int main(int argc, char* argv[])
{
person1 p1;
person2 p2;
person3 p3;
person4 p4;
//test of class person1:
p1.SetName("p1");
cout<<"test of class person1: "<<p1.GetName()<<endl<<endl;
//test of class person2:
string p2name = "p2";
p2.SetName(p2name);
cout<<"value of p2name:"<<p2name<<endl<<endl;
cout<<"test of class person2:"<<p2.GetName()<<endl<<endl;
p2.GetName()="p2";
cout<<"test of class person2:"<<p2.GetName()<<endl<<endl;
//test of class person3:
p3.SetName("p3");
cout<<"test of class person3:"<<p3.GetName()<<endl<<endl;
p3.GetName() = "ppp3";
cout<<"test of class person3:"<<p3.GetName()<<endl<<endl;
//test of class person4:
p4.SetName("p4");
cout<<"test of class person4:"<<p4.GetName()<<endl<<endl;
//p4.GetName() = "pppp4"; 不能對const的引用返回賦值!!
return 0;
}
五、示例分析
在上面的例子中我們可以看到第一個類person1是最無效率的,它的參數,函數返回類型都是傳值的。但是它比較安全。第二個類person2的參數類型和函數返回都定義成了引用類型,效率提高了,但是它是最不安全的,可以在函數體裏面修改參數的值,也可以通過函數返回修改類成員的值。第三個類person3將SetName函數的參數類型修改爲const限制的引用,提高了效率和安全性,同時將GetName函數函數指定爲了const類型函數,保證了安全性,但此時不能將函數指定爲引用返回,這影響了函數返回的效率。第四個類中修正了第三個類的缺點,將GetName函數的返回類型指定爲const限定的引用類型,這樣達到了效率與安全性的兼顧。