7.2.2 引用形參、const類形參(重要)

簡介

(記住了,很重要,形參是const引用和非const引用的區別)

  • 可以使用指針來修改實參的值,也可以使用非const引用來修改實參的值。

  • 使用const 類型的引用的好處:

    • 不需要複製實參時,可以用引用類型,所有實參過大時用引用類形參,提高效率;

    • 又因爲是const類型的引用,所以不能通過引用來修改實參的值

    • 非const引用只能綁定到與該引用同類型的對象;

一般情況下,將不需要修改的引用形參定義爲const引用,普通的非const引用形參在使用時不太靈活,因爲這樣的形參不能用字面值或產生右值的表達式實參初始化。

少用非const引用類形參,一般定義引用時最好加上const(記住了!!!!)

============================================================================================

導讀:

  1. 當我們調用一個函數時,第一件事就是創建形參的那兩個變量,並將這兩個變量初始化爲調用函數時傳遞的實參值。
  2. 當我們使用普通的形參(非引用類形參)時,實參對形參的初始化就是直接執行復制操作,將實參的值複製給形參。在本節的形參時會討論到這個問題,就是直接執行復制有時候會效率很低—(7.1節)

  3. 複製實參並不是在所有的情況下都適合,不適宜複製實參的情況如下,在如下的情況中,有效的解決辦法是將形參定義爲引用或指針類型:

    1. 當需要在函數中修改實參的值時;
    2. 當需要以大型對象作爲實參傳遞時,對實際應用而言,複製對象所付出的時間和存儲空間代價往往過大;
    3. 當沒有辦法實現對象的複製時(13章)。—-(7.2節)

一、引用形參

1、使用指針來修改實參的值,也可以使用非const引用來修改實參的值。**

void swap(int v1,int v2)
{int temp;
temp=v2;
v2=v1;
v1=temp;
//形參v1和v2並不會長期存在
//這個函數是想改變實參的值,但是沒有辦法改變,執行swap時,只交換了其實參的局部副本,而傳遞給swap的實參並沒有修改。
}
int main(){
int i=0;
int j=20;
cout<<"Before swap():"<<i<<" "<<j<<endl;
swap(i,j);//調用上面定義的swap函數,i和j的值並沒有修改
cout<<"after swap():"<<i<<" "<<j<<endl;
return 0;
}
//如果想要修改i和j 的值,可以將形參設置爲引用類型
void swap(int &v1,int &v2)
{int temp;
temp=v2;
v2=v1;
v1=temp;}
//與所有引用一樣,引用形參直接關聯到其所綁定的對象,而並非這個對象的副本。
定義引用時,必須用與該引用綁定的對象初始化該引用。
//當調用swap(i,j) 函數時,形參v1只是對象i的另一個名字,修改v1的值就是修改i的值。

//將引用當實參與將指針當實參具有一樣的效果
//將引用當實參與將指針當實參具有一樣的效果
void swap(int *v1,int *v2)
{int temp;
temp=*v2;
*v2=*v1;
*v1=temp;}

//main函數裏面的函數調用寫成swap(&i,&j)

2、使用引用形參返回額外的信息

沒有看懂!!!!!!!

  • 函數只能返回單個值,但有些時候,函數有不止一個的內容需要返回,例如定義一個find_val函數,在一個整型vector對象的元素中搜索某個特定值,如果找到滿足要求的元素,則返回指向該元素的迭代器;
  • 否則返回一個迭代器,指向該vector對象的end操作返回的元素。

  • 此外,如果該值出現了不止一次,我們還希望函數可以返回其出現的次數,在這種情況下,返回的迭代器應該指向具有要尋找的值得第一個元素。

如何定義既返回一個迭代器又返回出現次數的函數呢?
我們可以定義一種包含一個迭代器和一個計數器的新類型。或者給find_val傳遞一個額外的引用實參,用於返回出現次數的統計結果:

//函數返回類型是vector<int>::const_iterator,返回一個迭代器
//函數類型是vector<int>::const_iterator
//函數名是find_val
//有四個形參
vector<int>::const_iterator find_val(
vector<int>::const_iterator beg,//第一個元素
vector<int>::const_iterator end,//
int value,                     //我們要的值
vector<int>::size_type &occurs//出現的次數
)  
{
//函數體開始
vector<int>::const_iterator res_iter=end;
occurs=0;
for(;beg!=end;++beg)
  if(*beg==value) { 
   if(res_iter==end)
   ret_iter=beg;
   ++occurs;
}
return res_iter;//返回的是
}
it=find_val(ivec.begin(),ivec.end(),42,ctr);
//調用後,ctr的值將是42出現的次數,如果42在ivec中出現了,則it將指向其第一次出現的位置;
//否則,it的值爲ivec.end(),而ctr則爲0、

3、利用const引用避免複製

  • 在向函數傳遞大型對象時,需要使用引用形參,這是引用形參適用的另一種情況,但是對於大部分類類型或者大型數組,它的效率太低了。(13章學到某些類類型是無法複製的,使用引用形參,函數可以直接訪問實參對象,而無須複製它)

  • const 引用

    • const引用是指向const對象的引用;
    • 非const引用只能綁定到與該引用同類型的對象;
    • const引用則可以綁定到不同但相關的類型的對象或綁定到右值
const int ival=1024;
const int &refval=ival;//可以,引用和被引用對象都是const類型
int &ref2=ival;//不可以
//可以讀取但是不能修改refval,因此對refval的賦值都是不合法的,不能直接對ival賦值,因此不能通過使用refval來修改ival。const引用只能綁定到與該引用同類型的對象;
const引用則可以綁定到不同但相關的類型的對象或綁定到右值

注意:

  1. 避免複製的意思是不是就是說,實參在給形參初始化的時候將值複製給形參;

  2. 因爲如果是一般的形參(非引用),那麼在調用函數的時候,形參的初始化時是將實參的值複製給了形參;

  3. 可是在實參很長的情況下,複製操作的效率太低了,這時候我們就可以使用引用類的形參;

  4. 而且有時候(13章)是沒有辦法複製的

//這個函數需要訪問每個string對象的size,但不必修改這些對象所以用const類型形參。
//由於string對象相當長,所有我們希望避免複製操作,使用const引用就可以避免將實參值複製給形參;
//每一個形參都是const string類型的引用,因爲形參是引用,所有不復制實參;
//又因爲形參時const引用,所有函數不能使用該引用來修改實參
bool isShorter(const string &s1,const string &s2){
return s1.size()<s2.size();
}

使用const引用形參的好處:

  1. 因爲是引用類型,所有實參過大時,不需要複製實參,提高效率;

  2. 又因爲是const類型的引用,所有不能通過引用來修改實參的值(在一般情況下非const類的引用,因爲引用是別名,是能夠通過引用來修改實參的值。7.2.1節)

4、更靈活的指向const的引用

前面提到的:
- 非const引用只能綁定到與該引用同類型的對象;

  • const引用則可以綁定到不同但相關的類型的對象或綁定到右值

    • 如果函數具有普通的非const引用形參,則顯然不能通過const對象進行調用,因爲此時函數可以修改傳遞進來的對象,這樣就違背了實參的const特性;

    • 調用這樣的函數時,傳遞一個右值或具有需要轉換的類型的對象同樣是不允許的。

    • 應該將不需要修改的引用形參定義爲const引用,普通的非const引用形參在使用時不太靈活,這樣的形參既不能用字面值或產生右值的表達式實參初始化。

int incr(int &val){

return ++val;
}
int main()
{
short v1=0;
const int v2=42;
int v3=incr(v1);//錯誤!!!v1不是int類型
v3=incr(v2);//錯誤!!!v2是const類型
v3=incr(0);//錯誤!!!0不是一個對象名
v3=incr(v1+v2);//錯誤!!!只有一個形參
int v4=incr(v3);//可以,類型相同!!!!都不是const

}
//不是很懂
string::size_type find_char(string &s,char c){
 string::size_type i=0;
 while(i!=s.size()&&s[i]!=c)
 ++i;
 return i;
}

//這個函數的問題是將其string類型的實參當做普通(非const)的引用,儘管函數並沒有修改這個形參的值,這樣的定義帶來的問題是不能通過字符串字面值來調用這個函數(因爲非const引用形參只能與完全同類型的非const對象關聯)。可更正爲const string&s

if(find_char("hello world",'o'))//會導致編譯問題
//雖然字符串字面值可以轉換爲string對象,但上述調用仍然會導致編譯失敗
//即使程序本身沒有const對象,而且只使用string對象(而並非字符串字面值或產生string對象的表達式)調用find_char函數,編譯階段仍然會出現問題。
//如下,可能有另外一個函數is_sentence 調用find_char來判斷一個string對象是否是句子:
bool is_sentence (const string &s){
return (find_char(s,',')==s.size() -1);
}
//函數is_sentence中的find_char的調用是一個編譯錯誤,傳遞進is_sentence的形參時指向const string對象的引用,不能將這種類型的參數傳遞給find_char

5、形參—–指向指針的引用

通過定義變量的引用可以實現實參值的變化,通過定義一個指針的引用(指針別名)可以實現實參指針的變化。

void ptrswap(int *&v1,int *&v2){
int *tmp=v2;
v2=v1;
v1=tmp;
}

//形參 int *&v1定義從右到左,理解是首先v1是一個引用,與指向int型對象的指針的別名。
也就是說v1只是傳遞進ptrswap函數的任意指針的別名。
int main()
{
   int  i=10;
   int j=20;
   int *pi=&i;
   int *pj=&j;
   cout<<*pi<<" "<<*pj<<endl;
   ptrswap(pi,pj);
   cout<<*pi<<" "<<*pj<<endl;
   return 0;
}

//輸出:
//10  20
// 20  10
即指針的值交換了,換句話說現在pi指向j,pj指向i。 

銜接下一節

習題7.12 什麼時候用指針形參??

當函數需要處理數組且函數體不依賴於數組的長度時應使用指針形參,其他情況下應使用引用形參。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章