C_Primer_Plus09.function

函数

  • 要点

return
*(一元), &(一元)
函数及其定义方式
使用参数和返回值
把指针变量用作函数参数
函数类型
ANSI C 原型
递归

函数的作用

函数的作用:

  • 减少重复代码
  • 模块化编程
  • 完成特定功能

主调函数: calling function
被调函数: called function
驱动程序: driver (用于测试函数的程序)

函数原型

不固定参数类型和数量的函数原型,比如 printf() 函数:

int printf(const char *, ...);

第一个参数是字符串,后面还有其他未指定的参数。这中原型叫做部分原型。C 库通过 stdarg.h 头文件提供了一个定义这类函数的标准方法。

可以在第一次调用该函数之前进行定义,对于较小的函数,这种方法很普遍:

int imax(int a, int b) {return a > b ? a : b;}
int main(void){
    int x, z;
    // process x
    z = imax(x, 50);

    return 0;
}

递归

函数调用自己的过程叫做递归。递归有时难以琢磨,有时方便实用。结束递归是使用递归的难点,因为如果递归代码中没有终止递归的条件测试,它会无限递归下去。

可以使用循环的地方通常都可以使用递归。有时用循环解决问题比较好,有时用递归更好。递归简洁,但效率却没有循环高。

尾递归(tail recursion)

最简单的形式是把递归调用放在函数末尾,刚好在 return 语句之前。

计算阶乘例:

#include <stdio.h>

unsigned long long factorial(unsigned long long n);

int main(void){
    unsigned long long n;

    printf("Enter a number to calculate factorial: ");
    while (scanf("%llu", &n) != 1){
        while (getchar() != '\n'){
            continue;
        }
        printf("Please input an integer: ");
    }
    printf("Factorial of %llu is %llu.\n", n, factorial(n));

    return 0;
}

unsigned long long factorial(unsigned long long n){
    if (1 >= n){
        return 1;
    }
    return factorial(n-1) * n;
}

output:

Enter a number to calculate factorial: 10
Factorial of 10 is 3628800.

递归?循环?
一般选择循环比较好。
每次递归都会创建一组变量,所以递归使用的内存更多。每次递归调用都会把创建的一组新变量放在栈中。调用递归的数量受限于内存空间。
由于每次调用函数要花费一些时间,所以递归执行的速度较慢。

编译多源代码文件的程序

gcc file1.c file2.c

最终会生成一个名为a.out的可执行,另外还生成两个名为 file1.o 和 file2.o 的目标文件。

如果后来改动了 file1.c,而 file2.c 不变,则可运行一下命令编译第一个文件,并与第二个文件的目标代码合并:

gcc file1.c file2.o

unix 为 cc,windows 为cc

多文件程序中使用头文件

把函数原型和已定义好的字符常量放在头文件中是一个良好的编程习惯。
在函数的定义文件中,引用头文件,然后对函数进行定义。在使用该函数的文件中,也只引用头文件,然后使用函数。
C 标准库就是这样做的。把函数原型放在头文件中,就不用在每次使用函数文件时都写出函数的原型。

TODO: example

指针简介

pointer 是一个值为内存地址的变量,它的类型根据所指向的变量类型而定。
指针有许多用法,这里介绍如何把指针作为函数参数使用。

获取变量的地址:

ptr = &a;

ptr “指向” a. ptr 和 &a 的区别是 ptr 是变量,而 &a 是常量。或者,ptr 是可修改的左值,而 &a 是右值,还可以把 ptr 指向别处:

prt = &b;

通常地址是用一个无符号整数表示。但不要把指针认为是整数类型。它实际上是一个新类型。ANSI C 专门为指针提供了 %p 格式的转换符。

简介运算符 *

indirection operator,用于找出存储在某地址中的值。

ptr = &a;
val = *ptr;
// equals to:
val = a;

使用地址和间接运算符可以间接完成上面最后一条语句的功能,这也是“间接”的由来

小结:

  • 地址运算符 &
    • 后面跟一个变量名时,& 给出该变量的地址
  • 间接运算符 *
    • 后面跟一个指针名或地址时,* 给出存储在指针指向地址上的值

声明指针

不同类型的变量占用不同的存储空间,因此不能使用统一的类型来声明指针变量,在声明指针时,必须指定指针所指向变量的类型:

int * pi;  // pi 是一个指向int变量的指针; *pi 是int类型的变量
char * pc;
float * pf, * pg;

* 和指针名之间的空格可有可无。通常,在声明时使用空格,在解引用时省略空格。

使用指针在函数间通信

#include <stdio.h>

// void interchange(int * u, int * v);
// 函数原型中的形参名是可以省略的,因此上一句可以写为:
void interchange(int *, int *);

int main(void){
    int x = 6;
    int y = 9;

    printf("Original: x = %d, y = %d\n", x, y);
    interchange(&x, &y);
    printf("After change: x = %d, y = %d\n", x, y);

    return 0;
}

void interchange(int * u, int * v){
    int temp;

    temp = *u;
    *u = *v;
    *v = temp;
}

output:

Original: x = 6, y = 9
After change: x = 9, y = 6

关于指针,C 和 C++ 有个不同的地方:C 没有引用变量,而 C++ 有引用变量。

普通变量把值作为基本量,把地址作为通过 & 运算符获得的派生量。而指针把地址作为基本量,把值作为通过 * 运算获取的派生量。


ex06:
编写并测试一个函数,该函数以3个double变量的地址作为参数,把最小值放入第1个函数,中间值放入第2个变量,最大值放入第3个变量。

#include <stdio.h>

void exchange(double *, double *, double *);

int main(void){
    double a = 3.14;
    double b = -4.;
    double c = .6;

    printf("Original: a = %.2f, b = %.2f, c = %.2f\n", a, b, c);
    exchange(&a, &b, &c);
    printf("After exchange: a = %.2f, b = %.2f, c = %.2f\n", a, b, c);

    return 0;
}

void exchange(double * u, double * v, double *w){
    double min = *u;
    double max = *u;
    double mid = *u;

    if (min > *v){
        min = *v;
    }else{
        max = *v;
    }

    if (max < *w){
        mid = max;
        max = *w;
    }else if(min < *w){
        mid = *w;
    }else{
        mid = min;
        min = *w;
    }

    *u = min;
    *v = mid;
    *w = max;
}

output:

Original: a = -4.00, b = 0.60, c = -3.14
After exchange: a = -4.00, b = 0.60, c = -3.14

ex11:
编写并测试Fibonacci()函数,该函数用循环代替递归计算斐波那契数。

#include <stdio.h>

unsigned long fibonacci(unsigned long);

int main(void){
    unsigned long n;

    printf("Input an integer: ");
    while (scanf("%lu", &n) == 1){
        printf("fibonacci of %lu is %lu\n", n, fibonacci(n));
        while (getchar() != '\n'){
            continue;
        }
        printf("Input another integer: ");
    }
    printf("Bye.\n");

    return 0;
}

unsigned long fibonacci(unsigned long n){
    unsigned long n2 = 1;
    unsigned long result = 1;

    for (unsigned long i = 0; i <= n; ++i){
        if (1 >= i){
            n2 = 1;
            result = 1;
        }else{
            result += n2;
            n2 = result - n2;
        }
    }

    return result;
}

output:

Input an integer: 0
fibonacci of 0 is 1
Input another integer: 1
fibonacci of 1 is 1
Input another integer: 2
fibonacci of 2 is 2
Input another integer: 3
fibonacci of 3 is 3
Input another integer: 4
fibonacci of 4 is 5
Input another integer: 5
fibonacci of 5 is 8
Input another integer: 6
fibonacci of 6 is 13
Input another integer: 7
fibonacci of 7 is 21
Input another integer: 20
fibonacci of 20 is 10946
Input another integer: 20
fibonacci of 20 is 10946
Input another integer: q
Bye.
posted @ 2020-06-14 22:50  keep-minding  阅读(73)  评论(0)    收藏  举报