C++指针变量的定义格式语言中函数的参数分析
1.概述
在高级语言程序设计中,函数是结构化程序的功能模块。参数是调用函数与被调用函数之间交换数据的通道。函数定义首部的参数称为形式参数,调用函数时使用的参数称为实际参数。
实际参数和形式参数按照不同传递机制进行通信。从传递性质上,C++函数有3种参数:传值参数、指针参数和引用参数。
在值传递机制中,作为实际参数的表达式的值被复制到形式参数所标识的对象中作为初始值。函数体的语句对形参访问、修改都是在这个标识对象上操作,与实际参数无关。
指针参数对应的实际参数是地址表达式。被调用函数可以在函数体内通过形式参数间接访问调用函数中的对象。
引用参数对应的实际参数是对象名。函数被调用时,形式参数不开辟局部存储空间,它作为
引用(别名)绑定于实际参数标识的对象。执行函数体时,对形参的操作就是对实参对象操作。直到函数执行结束,撤消引用绑定。
C++函数参数的形式,可以通过函数原型识别。
2.传值参数
传值参数是一种最简单、最易于理解的参数。具有传值参数的函数原型形式为:
Type function( Type1 arg1, Type2 arg2,… );
其中,Type为函数返回值类型;Type1Type2C++的基本类型或结构类型。
函数被调用时,实际参数作为右值表达式对形式参数赋初值。因此,赋值表达式的性质显而易见地应用于传值参数的传递中。
【例程1 有函数:
void add( int a, int b )
{ a ++ ;  b ++ ; }
其中ab均为传值参数。以下对add的调用是合法的。
        int k=100; double x=0.516;
        add( k, k+x );
实际参数kk+x分别作为形参变量ab的初值表达式,并且会对k+x做赋值的强制类型转换。对于Visual C++,编译时会给出丢失数据的警告:
    warning C4244: 'argument' : conversion from 'double' to 'int', possible loss of data
    传值参数的副作用主要源于对实际参数为赋值表达式和计算顺序不确定造成的。例如,有调用:
        add( a++, a+10 );
对实参表达式从左向右或从右向左求值会使a+10得到不同的结果。避免方法是把赋值表达式放在函数调用之前完成,消除实参表达式之间的依赖关系:
        a++;
        add( a, a+10 );
3.指针参数
    函数的形式参数为指针类型时,对应的实际参数是地址表达式,指针参数的本质是传值参数。具有指针参数的函数原型为:
    Type function(Type1 *arg1, Type2 *arg2,…);
这里的Type1Type2可以是已经定义的各种类型。
    【例程2 操作数组的函数。
    void change( int *ary, int num )    //第一个形式参数是指针参数
{ int i;
  for( i = 0; i < num ; i++ )
    {( *ary )++ ;        //间址访问array数组的元素
ary++ ;        //移动形参指针
}           
  return ;
}
    调用上述函数的方式如:
        int array[M];
        //……
        change(array+5,10);                //1个实际参数是地址表达式
    change的函数原型也可以写为:
        void change( int ary[], int num );
函数参数ary是一个局部指针变量,change被调用时,接受实参地址表达式array+5的值,完全是传值参数的形态。而函数体中通过ary间址访问修改了实参array数组元素的值。
以上程序说明了指针参数的传值特征和地址特征。
4.引用参数
函数的形式参数执行函数体时绑定于实际参数标识的对象,以别名方式操作实参对象。具有引用参数的函数原型形式为:
Type function( Type1 &arg1, Type2 &arg2,… );
其中Type1Type2可以是已经定义的各种类型。
【例程3】通过函数引用参数实现两个整型变量的值交换。
void swap( int &x, int &y )        //形式参数是整型引用
{ int temp = x ;
  x = y ;
  y = temp ;
}
若有调用:
int a, b;
//……
swap(a, b);
则在函数swap执行时,xa的别名,yb的别名。xy没有存储空间。
C++中,有时不能从函数的调用形式区分参数的性质。例如,若swap函数的原型为:
void swap( int x, int y ) ;        //形式参数是传值参数
上述函数体中语句不变,调用形式不变,但函数调用之后,ab的值没有交换。这是因为这里的
swap (a, b);
中的ab性质变成了传值参数xy的右值表达式。
    引用参数的一种例外是常引用。C++规定,函数的常引用参数允许对应的实际参数为常数或者表达式。调用函数进行参数传递时将产生一个匿名对象保存实参的值。形参标识名作为这个匿名对象的引用,对匿名对象操作。匿名对象在被调用函数运行结束后撤消。
【例程4】常引用参数的匿名对象测试。
#include<iostream.h>
void anonym ( const int & ref )        //ref是常引用参数
{ cout << "The address of ref is : " << &ref << endl;
  return ;
}
void main()
{ int val = 10 ;
  cout << "The address of val is : " << &val << endl;
  anonym( val ) ;
  anonym( val + 5 ) ;
}
    上述程序的执行结果可以看到ref为接受表达式建立的匿名空间。这种常引用参数的使用效果与传值参数的情况类似。
5.指针引用参数
    当要求函数参数操作数据对象时,使用指针参数和引用参数,程序都易于理解。但要求
函数参数操作指针变量时,情况就变得比较复杂。以下用堆栈的入栈操作做分析。
动态栈是程序设计中常用的数据结构,数据入栈操作时必须修改栈顶指针。在没有引用参数机制的C语言中,一般情况有3种方法:使用全局量;使用函数返回地址值;使用二级指针定义形式参数。
1)使用全局变量。
node * Top=NULL;        //栈顶指针是全局变量
//……
void push(int x)
{ node *p = new (node);
  p->data=x;
  p->link = Top;   
  Top=p;                //修改栈顶指针
}
    2)通过函数返回值传递地址。
node * push(node * T, int x)    //T是传地址值参数
{ node *p = new (node);
  p->data=x;
  p->link = T;
  T=p;
  return T;
}
    调用push函数的形式是:
node * Top=NULL;    //栈顶指针是函数中定义的局部变量
//……
Top=push(Top,n);    //Top是地址表达式
    函数的形式参数T是传地址值参数,入栈语句中,调用函数的实际参数Top是地址表达式,而函数返回后的新地址值写入变量Top,从而完成修改栈顶指针操作。
    3)定义形式参数为二级指针。
void push(node ** T, int x)  //形式参数T是二级指针
{ node *p = new (node);
  p->data=x;
  p->link = *T;            //通过间址操作T所指的Top指针
  *T=p;
}
    调用push函数的形式是:
node * Top=NULL;    //栈顶指针是函数中定义的局部变量
//……
push(&Top,i);        //向形式参数传递指针变量的地址值
通过二级指针形式参数T,对一级指针的变量Top做间址操作。
上述的第(1)种方法,因为使用全局量push函数不能独立编译,增加了函数的偶合度,降低了结构性。第(2)种方法和第(3)方法程序使得不易理解,并增加了运行时参数传递和间址访问的开销。
C语言中函数只有传值参数,以上是无奈的选择。C++的引用参数有效地改变了这种状态。
【例程5】数据进栈的简单操作。
struct node
{ int data; node * link;};
//……
void push(node * & T, int x)    //T是指针引用参数
{ node *p = new(node);
  p->data=x;
  p->link = T;                //T以别名方式操作Top
  T=p;
}
调用push函数的形式是:
node * Top=NULL;    //栈顶指针是函数中定义的局部变量
//……
push(Top,n);        //Top是传名参数
    例程5中,函数push中的形式参数T是指针引用,调用函数时与实际参数Top绑定,TTop的别名,不需要开辟形参存储空间,也不需要用间址方式访问实参,高效地完成数据进栈操作。程序的结构和可读性都相当好。
再看一个申请动态数组的函数。由于函数要求得到堆内存地址,也适用于指针引用参数。
【例程6申请指定长度的动态数组,并完成初始化操作。
void apply( int * & pa, int len, int t=0 )
{ pa = new int[len];            //动态分配数组
  if( pa == NULL )
  { cout << "allocation faiure\n";
return ;
}
  for( int i = 0; i<len; i++ )    //赋初始值
pa[i] = t ;       
}
    pa是指针引用参数,若有调用
        int *array=NULL:
apply(array,n,0);
执行函数时pa由名绑定而在array上,从而实现了完美的操作。
   
6.结束语
    函数参数传递是高级语言程序设计教学难点,也是容易令程序员困惑之处。使用C++函数的参数主要掌握2个关键概念:
1C++函数的传值参数有两种:传数据值和传地址值。指针参数是传地址值的参数,形式参数通过间址方式访问外部对象。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。