數據結構實驗〇 C、C++語言中值傳遞、地址傳遞和引用傳遞


  本教材雖然是C語言版(教材內的程序思想設計上是面向過程的),但是調試程序時需寫用C++文件。Why?因為本教材程序在參數傳遞中使用了引用傳遞方式&,而C文件調試不支持引用,所以程序需寫C++文件,即使用&,cout等C++語句,但程序思想仍是C面向過程(主函數+子函數+子函數+……)而不是面向對象。本文是說明*和&的含義,是解釋參數傳遞,尤其是本教材多用值傳遞和引用傳遞,希望同學們畫圖畫圖畫圖來理解,來學習數據結構這門課。

一、C/C++中*和&的用法

  C++語言是C語言的超集。幾乎所有可以運行的C程序都是可以運行的C++程序。因此,寫一個不包含C++特性的C++程序是可能的,盡管cout和引用的使用(見下文)更好地構成了一個C++程序。

C中*的用法:1. 乘法運算:x=y*z;x*=y;相當於x=x*y

                     2. 指針的聲明:int *p; 讀法:p是指向一個整數類型的指針(int***p等等)。

                     3. 解引用(取值):x=*p 把指針p指向的值賦值給x。

     &的用法:1. 邏輯運算:if((a>1)&&(b<0));x&=y;與 x=x&y含義相同;

                     2. 位運算與:x=a&b; 

                     3. 取址運算:p=&x;讀法:把x的地址賦給p(指針)。

C++中&的補充用法:

  C++中有一種C不存在的變量類型引用變量(簡單說來為引用,&),盡管在C語言中用指針也可以實現類似的功能。

  引用,指針,地址是聯系密切的概念。地址是在電腦內存中的地址(一般是一些變量的值在內存中的儲存位置),指針是存地址的變量,所以指針可以“指向”內存地址。概念上講,引用變量本質上是指針的另一個名字(但是並不能被編譯器實例化,即不分配地址空間

  在函數內像其他變量一樣定義一個引用是可能的,舉例

  void main(void)

  {

    int i;

    int& r = i;

    …

  }

  但是這是沒有意義的,因為引用的使用(r的使用)引用的變量(i)的使用是一樣的。故而,通常是在參數傳遞中使用&

  void main(void)

  {

    int i=3;

    f(i);

    cout << i;

  }

  void f(int& r)

  {

    r = 2*r;

  }

  這段程序輸出“6”。2*r使被r引用的變量加倍,也就是指i,r即是i,雖說f()返回類型為void,但實參 的值經過調用子函數而發生改變,也即隱式把值返回主函數。類似地,張三起個別名叫帥哥,別人喊帥哥其實就是喊張三;李四也可以叫帥哥,這時帥哥就是李四;大家想下,這不就是C中為什么寫子函數嗎?代碼重用,過程清晰,如add(a.b) add(c,d),不能每兩個數求和就寫一個子函數,只需寫一個子函數add(&x,&y)調用它即可。

  在C語言中,實現同樣的功能,我們可以通過聲明f()為void f(int *r),其中r是指向整數類型的指針,然后調用參數&i(i的地址)調用函數f(),在函數f()內使用r的解引用,但是顯然,C++提供了一種更簡明的通過引用的方式向子函數傳值,隱式把值返回主函數,void子函數也可將值返回給主函數。 

  綜上,本教材為什么用引用&傳參?一是內涵實參值(通常是結構體變量)要變(隱式把子函數處理后形參的值還給實參);二是形式簡明(不用指針);三是可以將多個形參值返給實參(即隱式保留多個形參值,而子函數顯式的只能renturn一個值)

二、C/C++語言中值傳遞、地址傳遞和引用傳遞

通過一個例子:swap(交換兩個整型變量的值)來表現!(要求:上機練習此題,提前熟悉下面的程序,筆記本畫下內存變化,以此理解這幾種參數傳遞) 

#include <stdio.h>
 void swap(int a,int b); 
 void swap1(int* a,int* b);
 void swap2(int& a,int& b);
 void swap3(int* a,int* b);
 void Calculate(float &sum,float &dif,float &pro,float &quo,int m,int n);
 int main(){ 
    printf(" Hello World!\n");
    int a = 3;
    int b = 4;
    printf(" Before swap: add of a = %d, add of b = %d\n",&a,&b);
    printf(" Before swap: val of a = %d, val of b = %d\n",a,b);
    swap(a,b);//每次調試一個swap,觀察子函數運行后,實參ab是否變化
    //swap1(&a,&b);
    //swap2(a,b);
    //swap3(&a,&b);       
    printf(" After  swap: val of a = %d, val of b = %d \n",a,b);
    //子函數return的話只能返回1個值,怎么樣讓一個子函數一下返回4個值呢?分析歸納總結為我所用,這就是語文。類比過來就是。
    //計算兩個數的和、差、積、商。
    int m=6, n=2;
    float sum, dif, pro, quo;
    Calculate(sum,dif,pro,quo,m,n);//調用子函數,沒有return,卻能將形參的值保留到實參中。
    printf("sum=%f,dif=%f,pro=%f,quo=%f",sum,dif,pro,quo);//這些實參值是變了,因為引用傳參,形參和實參都是同一個內存空間數據。       	
 return 0; 
 }
 // pass by value
 void swap(int a,int b){
     int temp = a;
     a = b;
     b = temp;
 }
 // pass by address
 void swap1(int* a,int *b){
     int temp = *a;
     *a = *b;
     *b = temp;
 }
 // pass by reference,注意swap1 2區別,功能一樣,只不過引用傳參,內存不在實例化開辟空間放值。
 void swap2(int& a,int& b){
     int temp = a;
     a = b;
     b = temp;
 }
 // pass by value ?
 void swap3(int* a,int *b){
     int* temp = a;//都是指針變量,值全為地址 
     a = b;
     b = temp;
 }
 void Calculate(int m,int n,float &sum,float &dif,float &pro,float &quo){
//注意函數功能是返回兩個數的和商積差,參數傳遞是雙向的,故加&, 
//m n是實參的拷貝,主函數扔給子函數就不要了,故不加&. 
 sum=m+n; 
 dif=m-n; 
 pro=m*n; 
 quo=m/n;//n不能為0 
}

上面的函數,四個swap函數,輸出結果:

swap(a,b):

swap1(a,b):

 

swap2(a,b):

 

swap3(a,b):

 

我們看到,真正起作用的是swap1和swap2.這兩個分別是地址傳遞和引用傳遞。swap是典型的值傳遞,swap3也是值傳遞,只不過值是地址。

  0. 值傳遞

  這個比較簡單,實參a 原本指向地址 1638212,代表1638212這個地址的值是3。在swap函數中,實參a將值拷貝給形參a,形參a此時也在內存中擁有地址,地址= xxxx,值為3,在所有的函數體內的操作,都是對 xxxx這個地址的操作,所以並不會影響實際參數的值。

  1. 地址傳遞

  這個對於理不清指針是什么的同學來說比較難。在這里我們習慣把指針寫成int* a,int* b而不是int *a,int *b。我們可以這樣理解:指針是一種特殊的數據類型,若 int c = 5;int* a = &c;則a是一個指針變量,它的值是c的地址!星號“*”是一個取值操作,和號“&”是一個取址操作。所以此時單純看a和b都是一個整數,它們表示地址,進行取值操作之后就可以得到相應地址的值。函數接受兩個類型為指針的變量,實際接受的是a和b,即兩個地址。所以現在分析函數體:

1 int temp = *a;//取出地址a的值,並賦值給整型變量temp
2 *a = *b;      //取出地址b的值,並將這個值賦給地址a指向的值
3 *b = temp;    //將temp的值賦給地址b所指向的值

  因此,我們看到,由於函數傳入的是地址,而函數體內又對地址進行取值和賦值操作,所以相對應的地址的值發生了改變。但是地址並沒有實際改變,從函數的輸出來看,a的地址並不會改變。在C語言中,函數在運行的時候會對每個變量分配內存地址,分配之后只要變量不被銷毀,這個地址不能改變。&a = &b;是無法編譯通過的。

  2. 引用傳遞

  這個理解起來更簡單,我們這樣理解引用,引用是變量的一個別名,調用這個別名和調用這個變量是完全一樣的。所以swap2的結果可以解釋。值得注意的是,由於引用時別名,所以引用並不是一種數據類型,內存並不會給它單獨分配內存,而是直接調用它所引用的變量。這個與地址傳遞也就是指針是不一樣的(也就是說一個指針雖然指向一個變量,但是這個指針變量在內存中是有地址分配的),下面代碼進行驗證。

復制代碼
 1 void main(){
 2     printf("Hello World!\n");
 3     int a = 3;
 4     int b = 4;
 5     int* c = &a;//c是指向a的指針
 6     int& d = b;//d是b的引用,alias of b = d
 7     printf("val of a = %d\n",a);
 8     printf("add of a = %d\n",&a);
 9     printf("val of c = %d\n",c);
10     printf("add of c = %d\n",&c);
11     printf("val of b = %d\n",b);
12     printf("add of b = %d\n",&b);
13     printf("val of d = %d\n",d);
14     printf("add of d = %d\n",&d);
15 }
復制代碼

輸出結果:

我們看到c的值是a的地址,c的地址是單獨分配的;而d的值是b的值,d的地址是b的地址!

  3. 關於swap3怎么解釋。

  我認為swap3是一種傳遞,如果我們把int*完全當做跟int一個級別的數據類型,那么swap3和swap兩個函數是一摸一樣的。只不過前者傳入的是變量a,b的拷貝值,而后者傳入的是變量a,b的地址的拷貝值;前者不能反應在外部,后者也不能,即把形參的值返回主函數。

  最后,我們注意,對於應用,如果我們有代碼:int a = 3; int& b = a;(b is an alias of a)b = 10;那么我們會發現a的值此時也變成了10。

  但是在java中,如果我們把java的引用簡單想象成這里的引用,是有問題的。因為如果一個函數出入一個對象Person person = new Person("ZHANG San"),而在函數體內進行這個操作:person = new Person("LI Si");那么person的值並不能被改變,所以我們說java的函數傳遞都是值傳遞。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM