C Programming: A Modern Approach

第9章 函数

2020-02-28  本文已影响0人  橡树人

英文原版:P183

在C语言中,什么是函数?

在C语言中,函数有什么作用?

本章的主要内容有:

9.1节 函数定义和函数调用

函数定义

返回值类型 函数名(形式参数)
{
  声明
  语句
}

针对返回值类型有如下几条规则:

针对形式参数有几点说明:

针对函数体有几点说明:

例1 错误的函数定义

double average(double a, b){
}

例2 有返回值且有形式参数的函数定义

double average(double a, double b){
  return (a+b)/2;
}

例3 无返回值有形式参数的函数定义

void print_count(int n) {
  printf("T minus %d and counting\n", n);
}

例4 无返回值无形式参数的函数定义

void print_pun(void) {
  printf("To C, or not to C: that is the question.\n");
}

函数调用

格式:

函数名(参数列表)

例1 函数调用示例

average(x, y)
print_count(i)
print_pun()

例2 返回值是void的函数调用语句

print_count(i);
print_pun();

例3 保存一个非void函数的调用语句

avg = average(x, y);
if (average(x, y) > 0) {
  printf("Average is positive\n");
}
printf("The average is %g\n", average(x, y));

例4 丢弃非void函数返回值的调用语句

average(x,y);

程序示例1:计算平均值

源文件:average.c

#include <stdio.h>

double average(double a, double b){
  return (a+b)/2;
}

int main(void) {
    double x, y, z;

    printf("Enter three numbers: ");
    scanf("%lf%lf%lf", &x, &y, &z);
    printf("Average of %g and %g: %g\n", x, y, average(x, y));
    printf("Average of %g and %g: %g\n", y, z, average(y, z));
    printf("Average of %g and %g: %g\n", z, x, average(z, x));

    return 0;
}

程序示例2:输出递减计数器

源文件:countdown.c

#include <stdio.h>

void print_count(int n){
  printf("T minus %d and counting\n", n);
}

int main(void) {
    int i;

    for(i=10; i>0; --i){
        print_count(i);
    }

    return 0;
}

程序示例3:打印双关语

源文件pun2.c

#include <stdio.h>

void print_pun(void)
{
  printf("To C, or not to C: that is the question\n");
}

int main(void)
{
    print_pun();

    return 0;
}

程序示例4:检测一个数是不是素数

源文件:prime.c

#include <stdio.h>
#include <stdbool.h>

bool is_prime(int n)
{
  int divisor;

  if (n < 1) {
    return false;
  }

  for (divisor = 2; divisor * divisor <= n; divisor++) {
    if (n % divisor == 0) {
        return false;
    }
  }

  return true;
}

int main(void)
{
    int n;

    printf("Enter a number: ");
    scanf("%d", &n);

    if (is_prime(n)) {
        printf("Prime\n");
    }else {
        printf("Not prime\n");
    }

    return 0;
}

9.2节 函数声明

函数声明格式:

返回值类型 函数名(形式参数列表);

规则:

注:
这种函数声明就是函数原型。

函数原型(函数声明)有什么作用?

例1 计算平均数
average2.c

#include <stdio.h>

double average(double a, double b);

int main(void) {
    double x, y, z;

    printf("Enter three numbers: ");
    scanf("%lf%lf%lf", &x, &y, &z);
    printf("Average of %g and %g: %g\n", x, y, average(x, y));
    printf("Average of %g and %g: %g\n", y, z, average(y, z));
    printf("Average of %g and %g: %g\n", z, x, average(z, x));

    return 0;
}

double average(double a, double b){
  return (a+b)/2;
}

9.3节 实际参数

区分形参和实参

在C语言中,形参有什么性质?

形参跟变量类似,形参的初始值是相匹配的实参的值。

在C语言中,实参有什么性质?

按值传递:当函数被调用时,会对每个参数求值,并把每个参数的值拷贝给相应的形参。
由于形参包含的是实参值的拷贝值,所以在程序执行过程中对形参的修改,不会影响实际参数。

按值传递有什么优势?

例1 利用按值传递的性质来减少真正需要的变量数量
源程序:

int power(int x, int n)
{
  int i, result = 1;
  
  for(i=1; i<=n; i++){
    result = result * x;
  }

  return result;
}

减少变量后:

int power(int x, int n)
{
  int result = 1;
  while(n--){
    result = result * x;
  }

  return result;
}

按值传递有什么缺点?

void decompose(double x, long int_part, double frac_part)
{
  int_part = (long)x;
  frac_part = x - int_part;
}

但是由于按值传递的性质,不起作用。此时,需要利用指针的性质来实现,修改函数的参数类型为:

void decompose(double x, long *int_part, double *frac_part)
{
  *int_part = (long)x;
  *frac_part = x - *int_part;
}

实参的类型转换规则

C语言允许函数调用时的实参的类型跟形参的类型不匹配。

转换规则:

数组作为函数参数

一维数组作为函数参数的约定:

例1 1维数组作为函数参数
函数声明:

int sum_array(int a[], int n);

函数定义:

int sum_array(int a[], n)
{
  int i, sum = 0;
  
  for(i=0; i<n; i++){
    sum += a[i];
  }
}

函数调用

#define LEN 100

int main(void)
{
  int b[LEN], total
  ...
  total = sum_array(b, LEN);//注意,这里不能写成sum_array(b[], LEN)
  ...
}

例2 修改数组参数的元素值,会修改对应实参数组的元素值
函数定义:

void store_zeros(int a[], int n)
{
  int i;

  for (i=0; i< n; i++) {
    a[i] = 0;
  }
}

根据上述函数定义,可知:函数调用store_zeros(b, 100)的效果就是在数组b的前100个元素里存储0值。

多维数组作为函数参数相关规则:

例3 多维数组作为函数参数

#define LEN 10

int sum_dimensional_array(int a[][LEN], int n)
{
  int i, j, sum = 0;
  
  for (i=0; i<n;i++){
    for(j=0;j<LEN;j++){
      sum += a[i][j];
    }
  }
}

可变长度数组作为函数参数

例1 可变长度数组作为函数参数
函数原型

int sum_array(int n, int a[n]);
int sum_array(int n, int a[*]);

函数定义

//注意:对于可变数组来说,这里的n跟a[n]的顺序是不能交换的
int sum_array(int n, int a[n])
{
  ...
}

例2 使用多维可变数组形参来可传递任意列数
函数声明:

int sum_dimensional_array(int n, int m, int a[n][m]);

函数定义

int sum_dimensional_array(int n, int m, int a[n][m])
{
  int i, j, sum = 0;
  
  for (i=0; i<n;i++){
    for(j=0;j<m;j++){
      sum += a[i][j];
    }
  }
}

在实参中使用复合字面量

什么是复合字面量?
一个没有名字的数组,它的元素需要简单地列出。

格式:

(类型 []){实参列表}

例1 使用复合字面量来进行函数调用

//(int [5]){3, 0, 3, 4, 1}
total = sum_array((int [5]){3, 0, 3, 4, 1}, 5);
//在复合字面量里,可不用指出数组长度:(int []){3, 0, 3, 4, 1}
total = sum_array((int []){3, 0, 3, 4, 1}, 5);
//复合字面量可包含指定初始化式:(int [10]){8, 6}
total = sum_array((int [10]){8, 6}, 10);
//复合字面量可包含任意表达式:(int []){2*i, i + j, j * k}
total = sum_array((int []){2*i, i + j, j * k}, 3);
//只读复合字面量:(const int []){5, 4}
total = sum_array((const int []){5, 4}, 2);

在数组参数的声明中使用static

例1 使用static来声明一维数组

int sum_array(int a[static 3], int n)
{
    ...
}

解释:

注:
如果一个数组参数有多维,则static只能使用在第一个维。

9.4节 return语句

格式:

return 表达式;

解释:

例1 常见的返回语句

return 0;
return status;
return n>=0 ? n:0;

例2 在void函数里的返回语句

return ;

9.5节 程序终止

有两种方式来终止一个C程序:

return语句退出程序

通过main函数里的return语句来终止程序。

main函数必须有返回值,且返回值类型为int。
没有返回值的main函数是非法的。

main函数的返回值是一个状态码,可用该状态码来测试程序何时终止。

注:
main函数有参数吗?

例1 正常退出程序

//如果main函数没有参数,也要显式地表明
int main(void)
{
    ...
    
    return 0;
}

exit函数终止程序

例1 正常退出程序

exit(0);//或者exit(EXIT_SUCCESS);

例2 非正常退出程序

exit(EXIT_FAILURE);

return语句退出程序跟exit语句之间的关系

相同点:

return 表达式;

等价于

exit(表达式);

不同之处:

9.6节 递归

C语言允许递归,但不经常使用递归。

为了避免无限递归,所有的递归都必须有中止条件。

如果一个函数自己调用自己,则称这个函数是递归的。

例1 计算一个数的阶乘

int fact(int n)
{
  if (n <= 1)
  {
    return 1;
  }
  else 
  {
    return n * frac(n-1);
  }
}

例2 计算一个数的幂次方

int power(int x, int n)
{
  if (n == 0) 
  {
    return 1;
  }
  else
  {
    return x * power(x, n-1);
  }
}

程序示例:快速排序

qsort.c

#include <stdio.h>

#define N 10

void quicksort(int a[], int low, int high);
int split(int a[], int low, int high);

int main(void)
{
    int a[N], i;

    printf("Enter %d numbers to be sorted: ", N);
    for(i=0;i<N;i++)
    {
        scanf("%d", &a[i]);
    }
    quicksort(a, 0, N-1);

    printf("In sorted order: ");
    for (i = 0; i < N; ++i)
    {
        printf("%d ", a[i]);
    }
    printf("\n");

    return 0;

}


void quicksort(int a[], int low, int high)
{
    int middle;

    if (low >= high) 
    {
        return ;
    }
    middle = split(a, low, high);
    quicksort(a, low, middle -1);
    quicksort(a, middle+1, high);
}

int split(int a[], int low, int high)
{
    int part_element = a[low];

    for(;;) 
    {
        // 从右边开始向左扫描,找第一个被part_ment小的元素
        // 将其放置到part_ment的左边
        while(low < high && part_element <= a[high])
        {
            high--;
        }
        if(low >= high)
        {
            break;
        }
        a[low++] = a[high];
        // 从左边开始向右边扫描,找第一个被part_ment大的元素
        // 将其放置到part_ment的右边
        while(low < high && a[low]<=part_element)
        {
            low++;
        }
        if (low >= high)
        {
            break;
        }
        a[high--]=a[low];
    }

    //将part_ment放置到适当的位置
    a[high] = part_element;
    return high;
}
上一篇 下一篇

猜你喜欢

热点阅读