第 7 章 函数
# 第 7 章 函数
# 7.1 函数的定义
书本内容
# return 语句
书本内容
# 7.2 函数声明
书本内容
# 7.2.1 原型
书本内容
# 7.2.2 函数的缺省认定
书本内容
提示
- 示例中的例子在当前编译器
gcc 9.3.0
很难得出类似的结果
# 7.3 函数的参数
书本内容
# 7.4 ADT 和黑盒
书本内容
# 7.5 递归
书本内容
# 7.5.1 追踪递归函数
书本内容
# 7.5.2 递归与迭代
书本内容
# 7.6 可变参数列表
书本内容
# 7.6.1 stdarg 宏
书本内容
# 7.6.2 可变参数的限制
书本内容
# 7.7 总结
书本内容
# 7.10 问题
#
问题 1
具有空函数体的函数可以作为存根使用。你如何对这类函数进行修改,使其更加有用?
答案
- 当存根被调用时,打印一条消息,显示该函数被调用
- 也可以打印作为参数传递给它的值
#
问题 2
在ANSI C中,函数的原型并非必需。请问这个规定是优点还是缺点?
答案
- 唯一的优点只是可以少写一些代码
- 当函数和函数的调用者位于一个文件内,且函数出现在函数调用之前时,省略原型没有任何问题
- 缺点就是编译器可能无法得到函数的返回值的参数信息,当函数没有出现在函数调用之前时,其会默认函数的返回值是
int
类型,而且编译器无法确定函数调用时传入的参数- 由此会导致各种难以预测的问题
#
问题 3
如果在一个函数的声明中,它的返回值类型为 A
,但它的函数体内有一条 return
语句,返回了一个类型为 B
的表达式。请问,这将导致什么后果?
答案
- 表达式的值会被强制转换为函数返回值的类型
A
#
问题 4
如果一个函数声明的返回类型为 void
,但它的函数体内包含了一条 return
语句,返回了一个表达式。请问,这将导致什么后果?
答案
- 这种操作是不允许的,编译器应该给出一条错误信息
#
问题 5
如果一个函数被调用之前,编译器无法看到它的原型,那么当这个函数返回一个不是整型的值时,会发生什么情况?
答案
- 返回值会被当做整形进行解释
#
问题 6
如果一个函数被调用之前,编译器无法看到它的原型,如果当这个函数被调用时,实际传递给它的参数与它的形式参数不匹配,会发生什么情况?
答案
- 参数值会被解释为形参的类型,而不是它们实际的类型
#
问题 7
下面的函数有没有错误?如果有,错在哪里?
int
find_max( int array[10] )
{
int i;
int max = array[0];
for( i = 1; i < 10; i += 1 )
if( array[i] > max )
max = array[i];
return max;
}
2
3
4
5
6
7
8
9
10
答案
- 形参中的
int array[10]
并不能限定数组的长度为10
,其传入的只是一个指针 - 对数组的访问实际由第 6 行的
for
语句指定的 - 这样一来,如果数组
array
的长度小于 10,则会访问越界,其结果是难以预计的
#
问题 8
递归和 while
循环之间是如何相似的?
答案
- 递归和迭代都必须设置一些目标,当达到这些目标时便终止执行程序
- 每个递归调用和循环的每次迭代都必须取得一些进展,以逐步的达到设定的目标
#
问题 9
请解释把函数原型单独放在#include文件中的优点?
答案
优点如下:
- 如果在文件中用
#include
包含了原型文件,则函数原型具有文件作用域,其可作用于整个源文件,较之在函数每次调用前单独书写一份该函数的原型要容易得多 - 函数原型只用书写一次,避免了多份原型拷贝之间不匹配的现象
- 如果函数的定义进行了修改,只需要修改原型文件,并重新编译所有包含了该源型的源文件即可
- 如果函数的原型同时也被
#include
指令包含到定义函数的文件中,编译器就可以确认函数原型与函数定义的匹配
#
问题 10
在你的系统中,进入递归形式的菲波那契函数,并在函数的起始处增加一条语句,它增加一个全局整型变量的值。
现在编写一个main函数,把这个全局变量设置为0并计算Fibonacci(1)。
重复这个过程,计算Fibonacci(2)至Fibonacci(10)。
在每个计算过程中分别调用了几次Fibonacci函数(用这个变量值表示)?这个全局变量值的增加和菲波那契数列本身有没有任何关联?基于上面这些信息,你能不能计算出Fibonacci(11)、Fibonacci(25)和Fibonacci(50)分别调用了多少次Fibonacci函数?
答案
- 代码
#include <stdio.h>
#include <stdlib.h>
int counter;
/*
** Compute the value of the n'th Fibonacci number, recursively.
*/
long
fibonacci( int n )
{
counter++;
if( n <= 2 )
return 1;
return fibonacci( n - 1 ) + fibonacci( n - 2 );
}
int main(void)
{
int i;
long j;
for(i=1; i <= 10; i++)
{
counter = 0;
j = fibonacci(i);
printf(
"Fibonacci(%02d) = %2ld, Fibonacci(%02d)_counter = %d\n",
i, j, i, counter);
}
return EXIT_SUCCESS;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- 输出
Fibonacci(01) = 1, Fibonacci(01)_counter = 1
Fibonacci(02) = 1, Fibonacci(02)_counter = 1
Fibonacci(03) = 2, Fibonacci(03)_counter = 3
Fibonacci(04) = 3, Fibonacci(04)_counter = 5
Fibonacci(05) = 5, Fibonacci(05)_counter = 9
Fibonacci(06) = 8, Fibonacci(06)_counter = 15
Fibonacci(07) = 13, Fibonacci(07)_counter = 25
Fibonacci(08) = 21, Fibonacci(08)_counter = 41
Fibonacci(09) = 34, Fibonacci(09)_counter = 67
Fibonacci(10) = 55, Fibonacci(10)_counter = 109
2
3
4
5
6
7
8
9
10
设
为计算第 个菲波那契数时 Fibonacci
函数调用的次数,则由函数的调用过程可得设第
个菲波那契数为 ,则 ,具体可对 进行归纳证明 可由下列程序求出 - 代码
#include <stdio.h> #include <stdlib.h> /* ** Compute the value of the n'th Fibonacci number, iteratively. */ long fibonacci( int n ) { long result; long previous_result; long next_older_result; result = previous_result = 1; while( n > 2 ){ n -= 1; next_older_result = previous_result; previous_result = result; result = previous_result + next_older_result; } return result; } int main(void) { int numbers[3] = {11, 25, 50}; int i; for(i = 0; i < 3; i++) printf("T(%d) = %ld\n", numbers[i], 2 * fibonacci(numbers[i]) - 1); return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34- 输出
T(11) = 177 T(25) = 150049 T(50) = 25172538049
1
2
3
# 7.11 编程练习
#
编程练习 1
Hermite Polynomials(厄密多项式)是这样定义的:
int hermite( int n, int x)
答案
- 代码
/*
** Calculate Hermite Polynomials
*/
int hermite(int n, int x)
{
if(n <= 0)
return 1;
if (n==1)
return 2 * x;
return 2 * x * hermite(n-1, x) - 2 * (n - 1) * hermite(n-2, x);
}
2
3
4
5
6
7
8
9
10
11
12
#
编程练习 2
两个整型值
请编写一个名叫 gcd
的函数,它接受两个整型参数,并返回这两个数的最大公约数。如果这两个参数中的任何一个不大于零,函数应该返回零。
答案
代码
- 用循环来消除尾递归
int gcd(int m, int n) { int r; if (m <= 0 || n <= 0) return 0; while ((r = m % n) != 0) { m = n; n = r; } return n; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#
编程练习 3
为下面这个函数原型编写函数定义:
int ascii_to_integer( char *string );
这个字符串参数必须包含一个或多个数字,函数应该把这些数字字符转换为整数并返回这个整数。如果字符串参数包含了任何非数字字符,函数就返回零。请不必担心算术溢出。
提示:这个技巧很简单——你每发现一个数字,把当前值乘以10,并把这个值和新数字所代表的值相加
答案
代码
int ascii_to_integer(char *string) { int number; while (*string >= '0' && *string <= '9') { number *= 10; number += *string - '0'; string++; } if (*string != '\0') number = 0; return number; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
最后判断 string
是否为 \0
,可以减少判断的次数
#
编程练习 4
编写一个名叫 max_list
的函数,它用于检查任意数目的整型参数并返回它们中的最大值。参数列表必须以一个负值结尾,提示列表的结束
答案
代码
#include <stdarg.h> /* ** Retrun the largest value form the argument list. The list is ** terminated by a negative value. */ int max_list(int first_arg, ...) { int current_arg; va_list var_arg; if (first_arg < 0) return 0; va_start(var_arg, first_arg); while ((current_arg = va_arg(var_arg, int)) > 0) if (first_arg < current_arg) /* first arg is the largest value */ first_arg = current_arg; va_end(var_arg); return first_arg; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
提示
- 可变参数的实现需要用到 stdarg 宏
- 函数至少应有一个命名参数,用于宏
va_start
的初始化
#
编程练习 5
实现一个简化的 printf
函数,它能够处理 %d
、 %f
、 %s
和 %c
格式码。根据 ANSI 标准的原则,其他格式码的行为是未定义的。你可以假定已经存在函数 print_integer
和 print_float
,用于打印这些类型的值。对于另外两种类型的值,使用 putchar
来打印。
答案
代码
#include <stdarg.h> void printf_integer(int); void printf_float(float); void printf(char *format, ...) { va_list var_arg; char ch; char *string; va_start(var_arg, format); while ((ch = *format++) != '\0') { if (ch != '%') putchar(ch); else { switch (*format != '\0' ? *format++ : '\0') { case 'd': printf_integer(va_arg(var_arg, int)); break; case 'f': printf_float(va_arg(var_arg, double)); break; case 'c': putchar(va_arg(var_arg, int)); break; case 's': string = va_arg(var_arg, char *); while (*string != '\0') putchar(*string++); break; default: /*other format codes, do nothing*/ break; } } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
提示
- 对于其它的格式码,直接忽略
- 注意,
stdarg
对于...
中的参数会进行参数提升,char, short
变为int
,float
变为double
- 因此
va_arg
宏中的参数不能出现char, short, float
- 因此
#
编程练习 6
编写函数
void written_amount( unsigned int amount, char *buffer );
它把 amount
表示的值转换为单词形式,并存储于 buffer
中。这个函数可以在一个打印支票的程序中使用。例如,如果 amount
的值是 16 312
,那么 buffer
中存储的字符串应该是
SIXTEEN THOUSAND THREE HUNDRED TWELVE
调用程序应该保证 buffer
缓冲区的空间足够大。
有些值可以用两种不同的方法进行打印。例如, 1 200
可以是 ONE THOUSAND TWO HUNDRED
或 TWELVE HUNDRED
。你可以选择一种你喜欢的形式。
答案
代码
#include <string.h> /* ** Convert a numberic value to words; */ static char *digits[] = { "", "ONE ", "TWO ", "THREE ", "FOUR ", "FIVE ", "SIX ", "SEVEN ", "EIGHT ", "NINE ", "TEN ", "ELEVEN ", "TWELVE ", "THIRTEEN ", "FOURTEEN ", "FIFTEEN ", "SIXTEEN ", "SEVENTEEN ", "EIGHTEEN ", "NINETEEN "}; static char *tens[] = { "", "", "TWENTY ", "THIRTY ", "FORTY ", "FIFTY ", "SIXTY ", "SEVENTY ", "EIGHTY ", "NINETY "}; static char *magnitudes[] = { "", "THOUSAND ", "MILLION ", "BILLION "}; static handle_on_group(unsigned int amount, char *buffer, char **magnitude) { unsigned int value; value = amount / 1000; if (value > 0) handle_on_group(value, buffer, magnitude + 1); amount %= 1000; value = amount / 100; if (value > 0) { strcat(buffer, digits[value]); strcat(buffer, "HUNDRED "); } amount %= 100; if (amount >= 20) { strcat(buffer, tens[amount / 10]); amount %= 10; } strcat(buffer, digits[amount]); strcat(buffer, *magnitude); } void written_amount(unsigned int amount, char *buffer) { if (amount == 0) strcat(buffer, "ZERO "); else handle_on_group(amount, buffer, magnitudes); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
提示
unsigned int
32 位下的极大值为2^32 - 1 = 4 294 967 295
,最大用billion
即可表示- 每 3 个分为 1 组,各组的处理逻辑相近,可以用自定义一个函数进行处理
- 为了简化代码,提升代码可读性,可以采用递归调用的形式
- 借助指针数组完成数字到单词的映射