一、函数基础知识
1、函数的声明
函数只能定义一次,但可以声明多次。建议在头文件中声明而在源文件定义。函数的声明和函数的定义非常类似,唯一的区别是函数的声明无须函数体,用一个分号替代即可。
2、函数的定义
函数包括以下部分:返回类型、函数名字、由0个或多个形参组成的列表以及函数体。
returntype functionname (type parameter1,type parameter2…) {……}
函数的形参列表:函数的形参列表可以为空,但是不能省略。void f1()、voidf2(void)
3、形参的基础知识
形参和实参:实参是形参的初始值。第一个实参初始化第一个形参,第n个实参初始化第n个形参。
局部变量:形参和函数体内部定义的变量统称为局部变量,仅在函数的作用域内可见。
自动对象:把只存在于块执行期间的对象称为自动对象。形参是一种自动对象,所以函数一旦结束,形参也就被销毁。
局部静态变量:有时有必要令局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成static从而获得这样的对象。局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对像所在的函数结束执行也不会对它有影响。
二、参数传递
1、函数传参的三种方式:传值传递、地址传递、引用传递
(1)传值传递
(1)传值传递即将实参的值拷贝给形参。形参和实参是两个相互独立的对象,各占一个独立的存储空间。
(2)形参的存储空间是函数被调用时才分配的,调用开始,系统为形参开辟一个临时的存储区,然后将各实参传递给形参,这是形参就得到了实参的值。
示例代码:
#include<stdio.h>
void swap1(int x, inty)//定义中的x,y变量是swap函数的两个形参
{
int tmp;
tmp = x;
x = y;
y = tmp;
printf("x=%d,y=%d\n", x, y);
}
int main()
{
int a = 2;
int b = 3;
swap1(a, b);//a,b变量为swap函数的实际参数
printf("a=%d,b=%d", a, b);
return 0;
}
输出结果为:x=3,y=2; a=2,b=3
代码分析:a虽然赋值给x,但是a的值并没有改变,对x的任何修改都不会改变a的值。函数是通过赋值把a,b赋给x,y,这是一个隐含操作,我们不能把它显式的写出来,进行函数中变量的值进行交换时只是形参x,y的交换,并没有对实参进行真正交换,所以a,b值不变。
(2)地址传递
因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值。
地址传递与值传递的不同在于,它把实参的存储地址传送给形参,使得形参指针和实参指针指向同一块地址。因此,被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。
示例代码:
void swap2(int *px, int*py)
{
int tmp;
tmp = *px;
*px = *py;
*py = tmp;
printf("px=%d,py=%d\n", *px, *py);
}
int main()
{
int a = 2;
int b = 3;
swap2(&a, &b);/*调用了swap函数,同样也有隐含动作px=&a;py=&b;*/
printf("a=%d,b=%d", a, b);
return 0;
}
运行结果为px=3,py=2;a=3,b=2;
代码分析:有了两行隐含赋值操作,我们可以清晰的看出指针*px,*py是对变量a,b的值操作。函数里面对a和b的值进行了交换。这就是传址。
(3)引用传递
引用传递是以引用为参数,则既可以使得对形参的任何操作都能改变相应数据,又使函数调用方便。引用传递是在形参调用前加入引用运算符“&”。引用为实参的别名,和实参是同一个变量,则他们的值也相同,该引用改变则它的实参也改变。
代码示例:
#include<stdio.h>
void swap3(int &x,int &y)
{
int tmp = x;
x = y;
y = tmp;
printf("x=%d,y=%d\n", x, y);
}
int main()
{
int a = 2;
int b = 3;
swap3(a, b);//调用方式与传值一样
printf("a=%d,b=%d", a, b);
system("pause");
return 0;
}
输出结果:x=3,y=2; a=3,b=2;
代码分析:我们看到该代码只与传值中swap函数定义不同,swap3中参数都加了取地址符号&,有了这个函数会将a,b分别替代了x,y,这样函数里面操作就是a,b本身了。
2、结论
(1)除非实参是具体数值(5等)或者实参和形参是相同的名字外,避免使用传值传递。
(2)当某种类型不支持拷贝时,函数只能通过引用形参访问该类型的对象。
(3) 一个函数只能返回一个值,然而有时候函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径。
三、数组形参
1、数组的两个特殊性质:
①不允许拷贝数组(arry1=arry2):因为不能拷贝数组,所以无法以值传递的方式使用数组参数。
②使用数组时会将其转换成指针:因为数组会被转换成指针,所以当为函数传递一个数组时,实际传递的是只想数组首元素的指针。
2、数组数据的传送
要确定一个一维数组只需要知道:一个是数组的首地址,另一个是数组的长度,这样就可以唯一地确定一个一维数组。因此,要想通过实参和形参将一个数组从主调函数传到被调函数,那么只需要传递这两个信息即可。
因为数组是连续存放的,只要知道数组的首地址和数组的长度就能找到这个数组中所有的元素。对于一维数组来说,其数组名就表示一维数组的首地址。所以只需要传递数组名和数组长度这两个参数就可以将数组从主调函数传入被调函数中。
当数组名作为函数的实参时,形参列表中也应定义相应的数组(或用指针变量),且定义数组的类型必须与实参数组的类型一致,如果不一致就会出错。但形参中定义的数组无须指定数组的长度,而是再定义一个参数用于传递数组的长度。所以在传递实参的时候,数组名和数组长度也只能用两个参数分开传递,而不能写在一起。因为即使写在一起,系统在编译时也只是检查数组名,并不会检查数组长度。所以数组长度要额外定义一个变量进行传递。
当将数组从一个函数传到另一个函数中时,并不是将数组中所有的元素一个一个传过来(那样效率就太低了)。而是将能够唯一确定一个数组的信息传过来,即数组名(数组首地址)和数组长度。此时主调函数和被调函数操作的就是同一个数组。
数组名就是数组的首地址。因此在数组名作函数参数时所进行的传送只是地址的传送,也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。
当用数组名作函数参数时,情况则不同。由于实际上形参和实参为同一数组,因此当形参数组发生变化时,实参数组也随之变化。当然这种情况不能理解为发生了“双向”的值传递。但从实际情况来看,调用函数之后实参数组的值将由于形参数组值的变化而变化。
一般来数参数的传递是值传递,也就是说实参传给形参,形参发生改变时实参并不会改变,(单向)。但是数组在传递的时候是地址传递,只要形参发生了变化,实参也会发生变化(“双向”)。
3、管理指针形参
因为数组是以指针的形式传递给函数的,所以开始函数并不知道数组的确切大小,调用者应该为此提供一些额外的信息。管理指针形参有三种常用技术:
(1)使用标记指定数组长度
指要求数组本身包含一个结束字符。这种方法适用于有明显结束标记且该标记不会与普通数据混淆的情况,倒是对像int这样所有取值都是合法值的数据就不太有效了。
void printf(char *cp)
{
if(cp) /*若数组不是一个空指针*/
while(*cp) /*只要指针所指的字符不是空字符*/
cout<<*cp++;
}
(2)使用标准库规范
指传递指向数组首元素和尾后元素的指针。为了调用这个函数,需要定义两个指针:一个指向要输出的首元素,另一个指向尾元素的下一位置。
(3)显式传递一个表示数组大小的形参
指专门定义一个表示数组大小的形参。
# include<stdio.h>
int AddArray(intarray[], int n); //函数声明
int main(void)
{
inta[] = {1, 2, 3, 4, 5, 6, 7, 8};
/*数组所占内存总大小除以该数组中一个元素所占内存的大小, 从而得到数组元素的个数*/
int size = sizeof(a) / sizeof(a[0]);
printf("sum = %d\n", AddArray(a,size));
return 0;
}
int AddArray(intarray[], int n) //形参数组中不需要写长度
{
int i, sum = 0;
for (i=0; i<n; ++i)
{
sum += array[ i];
[ i]
}
return sum;
}
输出结果:sum=36
int test1(int *p)
{
for(int i=0;i<5;i++)
{
printf("%d",p[ i]); //我们在这里还可以用)*(p+i)来输出数组中的值
[ i]
}
}
int test2(int a[])
{
for(int i=0;i<5;i++)
{
printf("%d",a[ i]);
[ i]
}
}
int main()
{
int a[5] = {1,2,3,4,5},*p;
p = a;
test1(p);
test2(a);
}
#include<stdio.h>
floataver(float a[5]){
int i;
float av,s=a[0];
for(i=1;i<5;i++)
s=s+a[ i];
[ i]
av=s/5;
return av;
}
intmain(void){
float sco[5],av;
int i;
printf("\ninput 5 scores:\n");
for(i=0;i<5;i++)
scanf("%f",&sco[ i]);
[ i]
av=aver(sco);
printf("average score is%5.2f",av);
return 0;
}
在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元素的个数。例如,可以写为:
void nzp(int a[])
或写为
void nzp( int a[], int n )
其中形参数组a没有给出长度,而由n值动态地表示数组的长度。n的值由主调函数的实参进行传送。
4、传递多维数组
多维数组也可以作为函数的参数。在函数定义时对形参数组可以指定每一维的长度,也可省去第一维的长度。因此,以下写法都是合法的:
int MA(int a[3][10]) 或 int MA(int a[][10])。
因为多维数组是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针。数组第二维(以及后面所有维度)的大小都是数组类型的一部分,不能省略。
Void fun1( int (*matrix)[10], introwsize ) {……}
Void fun1( int matrix[][10], introwsize ) {……}
灵活的去定位到某一行某一列:
void test2(int m,int n,int **p)
{
//m,n是行和列,
for(int i = 0; i < m; i++)
{
for(int j = 0; j < n; j++)
{
printf("%d ",*((int*)p+n*i+j));
}
}
}
int main()
{
int a[5][5],i,j;
for(i = 0; i < 5; i++)
{
for(j = 0; j < 5; j++)
{
a[ i][j] = i*5 + (j +1);
[ i]
}
}
test2(5,5,(int **)a);
return 0;
}
或者:
#include<stdio>
void test2(int m,int n,int *p)
{
//m,n是行和列,
for(int i = 0; i<m; i++)
{
for(int j = 0; j < n; j++)
{
printf("%d ",*(p+n*i+j));
}
}
}
void main()
{
int a[5][5],i,j;
for(i = 0; i <5; i++)
{
for(j = 0; j < 5; j++)
{
a[ i][j] = i*5 + (j +1);
[ i]
}
}
test2(5,5,(int *)a);
}
|