数据结构实验〇 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