C语言零基础入门(五 函数(续))

接下来我们继续学习函数吧!

5.2 函数参数及其传递方式

5.2.1 函数的参数

形参和实参的使用要注意以下几点:

(1)形参只有在函数调用的过程中才占用内存的存储单元。在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。

(2)实参可以是常量、变量、表达式、函数返回值等。无论实参是何种形式的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值、输入等办法使实参获得确定值。

(3)实参和形参在数量、类型、顺序上应相同或赋值兼容,否则会发生“类型不匹配”的错误。

(4)函数调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向传送给实参。因此在函数调用过程中,形参的值发生改变,而实参的值不会改变。

5.2.2 函数参数的传递方式

使用函数参数可以传递两种不同的数据,即传递值和传递变量的地址。前一种方式称为值传递,后一种方式称为地址传递。

1. 值传递

形参变量是普通变量(非指针变量)时,参数的传递即为值传递。

【例】分析下面程序中参数传递过程相关变量的变化情况。

#include<stdio.h>
void main()
{
    void swap(int a, int b);    /* swap()函数的声明 */
    int x = 2, y = 3;
    printf("调用swap前,变量x,y的值:%d, %d\n", x, y);
    swap(x, y);    /* swap()函数的调用,x,y为实参 */
    printf("调用swaphou,变量x,y的值:%d, %d\n", x, y);
}

void swap(int a, int b)    /* swap()函数的定义,a,b为形参 */
{
    int t;
    t = a; a = b; b = t;    /* 交换形参变量a,b的值 */
    printf("调用swap中,变量a,b的值:%d, %d\n", x, y);
}

 程序执行结果如下图5-1所示。从结果可以看出,函数调用中形参的值进行了交换,而实参的值没有改变,仍保持原值。

图 5-1 程序运行结果

2. 地址传递

当形参变量是指针或数组类型时,实参和形参之间是地址传递。调用函数时,要求为形参传递变量的地址或数组的首地址,或者说,实参应是主调函数中变量的地址或数组的首地址。

使用地址传递时,形参有两种具体形式:一种是指针形参;另一种是数组形参。数组形参时,编译程序把数组名也作为指针变量处理。也就是说,这两种形式的形参没有本质区别,仅仅是书写形式上的不同。

(1)指针作函数的参数

指针作为函数参数时,实参和形参都为指针类型。进行函数调用时,将实参地址传递给形参指针变量。在被调用函数的内部,通过形参指针间接访问主调函数中的变量。

【例】指针作函数参数。

#include<stdio.h>
void main()
{
    void swap(int *a, int *b);
    int x = 2, y = 3;
    int *p1, *p2;
    p1 = &x;
    p2 = &y;
    printf("调用swap前,变量x,y的值:%d, %d\n", x, y);
    swap(p1, p2);    /* 调用swap()函数,p1,p2为实参 */
    printf("调用swap后,变量x,y的值:%d, %d\n", x, y);
}

void swap(int *a, *b)    /* 定义swap()函数,a,b为指针形参 */
{
    int t;
    t = *a; *a = *b; *b = t;    /* 交换形参变量a,b所指向变量的值 */
}

程序执行结果如下图5-2所示。 

图 5-2 程序运行结果

(2)数组作函数的参数

数组作为函数的参数,其本质与指针作为参数是一样的。当函数的形参为数组时,实参应是数组名,将实参数组的首地址传给形参,形参数组与实参数组共用一块内存空间,即主调函数中数组的空间。

【例】设计一个函数,对整型数组的前n个元素从小到大排序,在主函数中调用该函数对数组中的数据排序。

#include<stdio.h>
void selsort(int x[], int n)    /* 定义函数selsort(),选择法排序 */
{
    int i, j, k, temp;
    for(i = 0; i < n; i++)
    {
        k = i;
        for(j = i + 1; j < n; j++)
        {
            if(x[k] > x[j])    k = j;
        }
        if(k != i)
        {
            temp = x[k]; x[k] = x[i]; x[i] = temp;
        }
    }
}

void printa(int *x, int n)    /* 定义函数printa(),输出数组数据 */
{
    int i;
    for(i = 0; i < n; i++)
    {
        printf("%4d", x[i]);
    }
    printf("\n");
}

void main()
{
    int a[10] = {66, 77, 45, 76, 89, 54, 32, 67, 78, 88};
    printa(a, 10);    /* 调用函数printa() */
    selsort(a, 10);    /* 调用函数selsort() */
    printa(a, 10);    /* 调用函数printa() */
}

程序执行结果如下图5-3所示。 

图 5-3 程序运行结果

就参数传递而言,地址传递与值传递没有区别,都是将实参的值(普通值或地址值)赋值给形参。但从数据访问来看,由于地址传递的是主调函数中变量的地址,通过该地址可以间接访问主调函数中的变量;而值传递是将常量、变量或表达式的值传递给形参,与主调函数之间没有访问变量的通道,不能访问主调函数中的变量。因此,有人认为值传递是单向传递,而地址传递是双向传递,这是从被调函数是否能访问主调函数中的变量而言的。

5.3 函数的嵌套调用和递归调用

5.3.1 函数的嵌套调用

C语言中函数的定义都是相互平行、相互独立的,即定义一个函数时,该函数体内不能包含另一个函数的定义,即函数不能嵌套定义。

在C语言中,函数不可以嵌套定义,但可以嵌套调用。

【例】用牛顿迭代法求解一元三次方程x^{3}-2x^2-4x-7=0x=4.0附近的近似解,精确到10^{-5}

(注:牛顿迭代法详细讲解见“ 马同学高等数学 ”的博客:https://blog.csdn.net/ccnt_2012/article/details/81837154

分析:

用牛顿迭代法求解方程f(x)=0在α附近近似解方法如下:

(1)在α附近取一点x_0作为根的第0次近似值。

(2)过点(x_{0},f(x_{0}))\left ( x_{0} \right,f\left ( x_{0} \right ) )作曲线y=f(x)的切线与x轴的交点的横坐标x_1作为方程的第1次近似解。

切线方程为:y-f(x_{0})=f'(x_{0})(x-x_{0})

y=0,得切线与x轴交点的横坐标:x_{1}=x_{0}-f(x_{0})/f'(x_{0})

(3)过点\left ( x_{1} \right,f\left ( x_{1} \right ) )作曲线y=f(x)的切线与x轴的交点的横坐标x_{2}作为方程的第2次近似解:x_{2}=x_{1}-f(x_{1})/f'(x_{1})

(4)推广到一般:x_{n+1}=x_{n}-f(x_{n})/f'(x_{n})

x_{0},x_{1},x_{2},...,x_{n}逐次逼近方程的解,直到|f(x)|<\varepsilon为止。

程序如下:

#include<stdio.h>
#include<math.h>
float f(float x);                /* f()函数声明 */
float tang(float x);             /* tang()函数声明 */
float root(float x);             /* root()函数声明 */
void main()
{
    float x0 = 4.0, x1, y;        /* 取4为根的第0次近似值 */
    do{                           /* 牛顿法求方程根 */
        x1 = root(x0);            /* root()函数调用 */
        y = f(x1);
        x0 = x1;
    }while(fabs(y) >= le-5);
    printf("a root of equation is %8.4f\n", x1);
}

float f(float x)                  /* f()函数定义 */
{
    float y;
    y = x * x * x - 2 * x * x - 4 * x - 7;
    return(y);
}

float tang(float x)               /* tang()函数定义 */
{
    float y;
    y = 3 * x * x - 4 * x - 4;
    return(y);
}

float root(float x)                /* root()函数定义 */
{
    float y;
    y = x - f(x) / tang(x);
    return(y);
}

程序运行结果如下图5-4所示。

图 5-4 程序运行结果

5.3.2 函数的递归调用

C语言允许函数直接或间接地调用它自身,这种调用形式称为递归调用。

合理的递归调用应该是有限的,是在一定条件下能终止的递归调用,即要有递归终止条件。

【例】用递归函数计算n!

分析:求n!可以表示为递归公式:      n!=\left\{\begin{matrix} 1&(n=0,n=1) \\ n*(n-1)! &(n>0) \end{matrix}\right.

程序如下:

#include<stdio.h>
long fac(int n);                /* fac()函数声明 */
void main()
{ 
    int n;
    long y;
    printf("input a integer number:");
    scanf("%d", &n);
    y = fac(n);                /* fac()函数调用 */
    printf("\n%d! = ld\n", n, y);
}

long fac(int c)                /* fac()函数定义,采用递归方法实现 */
{
    long f;
    if(n == 0 || n == 1)
    {
        f = 1;
    }
    else
    {
        f = fac(n - 1) * n;    /* fac()函数定义中又调用fac()函数 */ 
    }
    return(f);
}

程序运行结果如下图5-5所示。

图5-5 程序运行结果

 


函数部分未完待续……欢迎大家提出意见或建议,一起进步!

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值