【时时三省】(C语言基础)用数组名作函数参数
山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省
可以用数组名作函数的参数。
例如:
array是实参数组名,arr为形参数组名。当用数组名作参数时,如果形参数组中各元素的值发生变化,实参数组元素的值随之变化。这究竟是什么原因呢?在学习指针以后,对此问题就容易理解了。
先看数组元素作实参时的情况。如果已定义一个函数,其原型为
void swap ( int x , int y );
假设函数的作用是将两个形参( x,y )的值交换,今有以下的函数调用:
swap ( a [ 1 ],a [ 2 ] );
用数组元素a [ 1 ]和a [ 2 ]作实参的情况,与用变量作实参时一样,是“值传递”方式,将a [ 1 ] a [ 2 ]的值单向传递给x和y,当x和y的值改变时a [ 1 ]和a [ 2 ]的值并不改变。
再看用数组名作函数参数的情况。实参数组名代表该数组首元素的地址,形参是用来接收从实参传递过来的数组首元素地址的。因此,形参应该是一个指针变量(只有指针变量才能存放地址)。实际上,C编译都是将形参数组名作为指针变量来处理的。函数fun的形参是写成数组形式的:
fun ( int arr [ ] , int n )
但在程序编译时是将arr按指针变量处理的,相当于将函数fun的首部写成
fun ( int * arr , int n )
以上两种写法是等价的。在该函数被调用时,系统会在fun函数中建立一个指针变量arr,用来存放从主调函数传递过来的实参数组首元素的地址。如果在fun函数中用运算符sizeof测定arr所占的字节数,可以发现sizeof ( arr )的值为4 (用VisualC++时)。这就证明了系统是把arr作为指针变量来处理的(指针变量在Visual C++中占4个字节)。
当arr接收了实参数组的首元素地址后,arr就指向实参数array组首元素,也就是指向array [ 0 ]。因此,* arr就是array [ 0 ]。arr +1指向array [ 1 ] ,arr + 2指向array [ 2 ],arr + 3指向array [ 3 ]。也就是说,* ( arr +1 ),* ( arr + 2 ),* ( arr + 3 )分别是array [ 1 ],array [ 2 ],array [ 3 ]。( arr + i )和arr[i]是无条件等价的。因此,在调用函数期间,arr [ 0 ]和* arr以及array [ 0 ]都代表数组array序号为0的元素,依此类推,arr[3],* ( arr + 3 ),array [ 3 ]都代表array数组序号为3的元素,
常用这种方法通过调用一个函数来改变实参数组的值。
说明:C语言调用函数时虚实结合的方法都是采用“值传递”方式,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组首元素地址,因此传递的值是地址,所以要求形参为指针变量。
在用数组名作为函数实参时,既然实际上相应的形参是指针变量,为什么还允许使用形参数组的形式呢?这是因为在C语言中用下标法和指针法都可以访问一个数组(如果有一个数组a,则a [ i ]和* ( a + i )无条件等价),用下标法表示比较直观,便于理解。因此许多人愿意用数组名作形参,以便与实参数组对应。从应用的角度看,用户可以认为有一个形参数组,它从实参数组那里得到起始地址,因此形参数组与实参数组共占同一段内存单元,在调用函数期间,如果改变了形参数组的值,也就是改变了实参数组的值。在主调函数中就可以利用这些已改变的值。
注意:实参数组名代表一个固定的地址,或者说是指针常量,但形参数组名并不是一个固定的地址,而是按指针变量处理。在函数调用进行虚实结合后,形参的值就是实参数组首元素的地址。在函数执行期间,它可以再被赋值。
例如:
void fun ( arr [ ] , int n )
{
printf ( " % d\n " , * arr );
arr = arr + 3 ;
printf ( " % d\n " , * arr );
}
例题:
将数组a中n个整数按相反顺序存放。
解题思路:
将a [ 0 ]与a [ n-1 ]对换,再将a [ 1 ]与a [ n-2 ]对换……直到将a [ int ( n-1 ) / 2 ]与a [ n—int ( ( n-1 ) / 2 ) -1对换。今用循环处理此问题,设两个“位置指示变量”i和j,i的初值为0,j的初值为n—1。将a [i]与a[j]交换,然后使i的值加1,j的值减1,再将a [ i ]与a [ j ]对换,直到i = ( n-1 ) / 2为止。用一个函数inv来实现交换。实参用数组名a,形参可用数组名,也可用指针变量名。
编写程序:
运行结果:
程序分析:
在main函数中定义整型数组a,并赋予初值。函数inv的形参数组名为x。在定义inv函数时,可以不指定形参数组x的大小(元素的个数)。因为形参数组名实际上是一个指针变量,并不是真正地开辟一个数组空间(定义实参数组时必须指定数组大小,因为要开辟相应的存储空间)。inv函数的形参n用来接收需要处理的元素的个数。在main函数中有函数调用语句“inv ( a,10 );”,表示要求对a数组的10个元素实行题目要求的颠倒排列。如果改为“inv ( a,5 );”,则表示要求将a数组的前5个元素实行颠倒排列,此时,函数inv只处理5个数组元素。函数inv中的m是i值的上限,当i<=m时,循环继续执行;当i > m时,则结束循环过程。
对这个程序可以作一些改动。将函数inv中的形参×改成指针变量。相应的实参仍为数组名a,即数组a首元素的地址,将它传给形参指针变量x,这时x就指向a [ 0 ]。x + m是a[m]元素的地址。设i和j以及p都是指针变量,用它们指向有关元素。i的初值为x,j的初值为x + n-1,使*i与* j交换就是使a [ i ]与a [ j ]交换。
修改程序:
运行结果与上面程序一样
归纳分析:
如果有一个实参数组,要想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下4种情况。
( 1 )形参和实参都用数组名,例如:
由于形参数组名x接收了实参数组首元素a [ 0 ]的地址,因此可以认为在函数调用期间,形参数组与实参数组共用一段内存单元,这种形式比较好理解。
( 2 )实参用数组名,形参用指针变量。例如:
实参a为数组名,形参x为int*型的指针变量,调用函数开始后,形参x指向a [ 0 ],即x =&a[0],通过x值的改变,可以指向a数组的任一元素。
( 3 )实参形参都用指针变量。例如:
实参p和形参x都是int*型的指针变量。先使实参指针变量p指向数组a [ 0 ],p的值是& a [ 0 ]。然后将p的值传给形参指针变量x,x的初始值也是& a [ 0 ],通过x值的改变可以使x指向数组a的任一元素。
( 4 )实参为指针变量,形参为数组名。例如:
实参p为指针变量,它指向a [ 0 ],形参为数组名x,编译系统把×作为指针变量处理。将a [ 0 ]的地址传给形参x,使×也指向a [ 0 ],也可以理解为形参数组x和a数组共用同一段内存单元,在函数执行过程中可以使x[i]的值发生变化,而x [i]就是a [ i ]。这样,main函数可以使用变化了的数组元素值。
例题:
用指针方法对10个整数按由大到小顺序排序。解题思路:在主函数中定义数组a存放10个整数,定义int*型指针变量p并指向a [ 0 ]。定义函数sort使数组a中的元素按由大到小的顺序排列。在主函数中调用sort函数,用指针变量p作实参。sort函数的形参用数组名。用选择法进行排序。
编写程序:
程序分析:
为了便于理解,函数sort中用数组名作为形参,用下标法引用形参数组的元素,这样的程序很容易看懂。当然也可以改用指针变量,这时sort函数的首部可以改为
sort ( int * x , int n )
形其他不改,程序运行结果不变。
可以看到,即使在函数sort中将x定义为指针变量,在函数中仍可用x [ i ]和x [ j ]这样的形式表示数组元素,它就是x + i和x + j所指的数组元素。