一个函数直接或间接地调用该函数本身,称为函数的递归调用。例如
int f(int x)
{
int y, z;
z = f(y); //函数f调用了自己本身
return (2 * z);
}
递归条件:
(1)递归出口:要存在一个递归结束的条件,满足该条件时不再继续;
(2)每次递归调用都是在逐渐接近递归出口;
(1)有5个人坐在一起,问第5个人多少岁?他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第3个人,又说比第2个人大2岁。问第2个人,说比第1个人大2岁。最后问第1个人,他说是10岁。请问第5个人多大?
分析:
age(5) = age(4) + 2
age(4) = age(3) + 2
age(3) = age(2) + 2
age(2) = age(1) + 2
age(1) = 10
可以用数学公式表述如下:
a g e ( n ) = { 10 , n = 1 a g e ( n − 1 ) , n > 1 age(n)=\begin{cases} 10,&n=1\\ age(n-1),&n>1 \end{cases} age(n)={10,age(n−1),n=1n>1
#include
int age(int n);
int main()
{
printf("第5个人的年龄是: %d\n", age(5));
return 0;
}
int age(int n)
{
int a;
if (n == 1) a = 10;
else a = age(n - 1) + 2;
return a;
}
#include
int factorial(int n);
int main()
{
int N;
scanf_s("%d", &N);
printf("%d", factorial(N));
return 0;
}
int factorial(int n)
{
if (n == 0) return 1;
else
return n * factorial(n - 1);
}
例如求5!
(3)汉诺塔(Tower of Hanoi)问题
传说印度的主神梵天做了一个梵塔,即汉诺塔,它是在黄铜板上插3根宝石针,其中一根针上从上到下按从小到大的顺序串上64个金片。梵天要求僧侣们轮流把金片在3根宝石针之间移来移去,规定每次只能移动一个金片,且不许将大金片压在小金片之上,并预言若这64个金片全部移到另一根宝石针上时,世界将在一生霹雳之中毁灭,而梵塔、庙宇和众生也都将同归于尽。要求编程序打印出移动的步骤。
将n个金片从1号针移动到3号针可以分为3步:
(1)将1号针上n-1个金片借助3号针先移到2号针上;
(2)把1号针上剩下的一个金片移到3号针上;
(3)将n-1个金片从2号针借助于1号针移到3号针上。
#include
void move(int n, int star, int goal, int temp);
int main()
{
int n = 64, star = 1, goal = 3, temp = 2;
move(n, star, goal, temp);
return 0;
}
void move(int n, int star, int goal, int temp)
{
if (n >= 1)
{
move(n - 1, star, temp, goal);
printf("move disk %d from %d to %d.\n", n, star, goal);
move(n - 1, temp, goal, star);
}
运行结果
……
P.S.这个结果我等了10分钟也没等到,梵天预言这个结果出来世界将毁灭hhhhh
用clock()函数获得程序的运行时间:
#include
#include
void move(int n, int star, int goal, int temp);
clock_t star, stop; //clock_t是clock()函数返回的变量类型
double duration; //记录被测函数运行时间,以秒为单位
int main()
{/*不在测试范围内的语句写在clock()调用之前*/
int n = 15, star = 1, goal = 3, temp = 2;
star = clock(); //开始计时
move(n, star, goal, temp); //把被测函数加在这里
stop = clock(); //结束计时
duration = ((double)(stop - star)) / CLK_TCK; //计算运行时间
printf("运行时间: %8.4f", duration);
/*注意CLK_TCK是机器时钟每秒钟所走的时钟打点数*/
/*其他不在测试范围内的语句写在后面,如duration的值*/
return 0;
}
当n=15时,部分运行结果及运行时间:
当n=20的时候运行时间就要好久好久……
数组元素作为函数实参,其用法与变量相同
例,输入10个数,要求输出其中值最大的元素和该数是第几个数
#include
int max(int x, int y);
int main()
{
int a[10], i, m, n;
printf("Please enter 10 integers: \n");
for (i = 0; i < 10; i++)
scanf_s("%d", &a[i]);
printf("\n");
for (i = 1, m = a[0], n = 0; i < 10; i++)
{
if (max(m, a[i]) > m)
{
m = a[i];
n = i;
}
}
printf("The largest number is %d.\nit is %dth number in array.", m, n);
return 0;
}
int max(int x, int y)
{
return x > y ? x : y;
}
可以用数组名作函数实参,此时形参应当用数组名或用指针变量
例,有一个一维数组score,内放10个学生成绩,求平均成绩
#include
float average(float s[10]);
int main()
{
float score[10], aver;
printf("Please enter 10 scores(0-150):\n");
for (int i = 0; i < 10; i++)
scanf_s("%f", &score[i]);
printf("\n");
printf("The average score is %5.2f.\n", average(score));
return 0;
}
float average(float s[10])
{
float sum = 0.0, aver;
for (int i = 0; i < 10; i++)
sum += s[i];
aver = sum / 10;
return aver;
}
运行结果
注意:
(1)用数组名作函数实参,应该在主调函数和被调函数中分别定义数组。
(2)实参数组和形参数组类型应该一致。
(3)形参数组可以不指定大小,因为C语言编译系统对形参数组大小不作检查。
(4)可以另设一个形参,传递数组的大小
#include
int main()
{
float average(float array[], int n); //形参数组可以不指定数组长度
float score1[5] = { 98.5, 97, 91.5, 60, 55 };
float score2[10] = { 67.5, 89.5, 99, 69.5, 77, 89.5,76.5, 54, 60, 99.5 };
printf("the average of class A is % 6.2f\n",average(score1, 5)); //实参为数组名
printf("the average of class B is % 6.2f\n",average(score2, 10));
return 0;
}
float average(float array[], int n)
{
int i;
float aver, sum = array[0];
for (i = 1; i < n; i++)
sum += array[i];
aver = sum / n;
return aver;
}
(5)数组名作函数实参时,不是把数组元素的值传递给形参数组,而是把实参数组的首地址传递给形参数组,这样两个数组共占同一段内存单元。形参数组中各元素的值如发生变化,会使实参数组的值同时发生变化。这与变量作函数参数的情况不同,务请注意
例,用选择法对数组中10个整数按由小到大排序
选择法是先将10个数中最小的数与a[0] 对换;再将a[1]到a[9]中最小的数与a[1]对换……。每比较一轮,找出一个未经排序的数中最小的一个,共比较9轮。
#include
void sort(int a[], int n);
int main()
{
int a[10], i;
printf("Please enter array:\n");
for (i = 0; i < 10; i++)
scanf_s("%d", &a[i]);
printf("\nThe sorted array: \n");
sort(a, 10);
for (i = 0; i < 10; i++)
printf("%5d", a[i]);
printf("\n");
return 0;
}
void sort(int a[], int n)
{
int i, j, k, t;
for (i = 0; i < n - 1; i++)
{
k = i;
for (j = i + 1; j < n; j++)
if (a[j] < a[k]) k = j;
t = a[k];
a[k] = a[i];
a[i] = t;
}
}
可以用多维数组名作为函数的实参和形参,定义形参数组时可以省略第一维的大小说明,但不能省略第二维以及其它高维的大小说明。
正确:int array[3][10]; int array[][10];
错误:int array[3][]; int array[][];
例,有一个3行4列的矩阵,求所有元素中的最大值
#include
int max(int a[][4]);
int main()
{
int a[3][4] = { {3,5,2,6},{10,2,65,44},{21,1,34,11} };
printf("The maximun value is: %d.\n", max(a));
return 0;
}
int max(int a[][4])
{
int m = a[0][0], i, j;
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++)
if (a[i][j] > m)m = a[i][j];
return m;
}
局部变量:在一个函数内部定义的变量,只在本函数范围内有效,即只有在本函数内才能使用,在此函数以外是不能使用
全局变量:在函数之外定义的变量,从定义变量的位置开始到本源文件结束都有效,可以为本文件中的函数共用
例如,在汉诺塔问题的程序中,
#include
#include
void move(int n, int star, int goal, int temp);
clock_t star, stop; //star,stop是全局变量
double duration; //duration是全局变量
int main()
{
int n = 15, star = 1, goal = 3, temp = 2;
//n,star,goal,temp都是局部变量,只在main函数的范围内有效
star = clock();
move(n, star, goal, temp);
stop = clock();
duration = ((double)(stop - star)) / CLK_TCK;
printf("运行时间: %8.4f", duration);
return 0;
}
void move(int n, int star, int goal, int temp)
{
if (n >= 1)
{
move(n - 1, star, temp, goal);
printf("move disk %d from %d to %d.\n", n, star, goal);
move(n - 1, temp, goal, star);
}
}
例,一维数组中存放了10个学生成绩,求出平均分、最高分和最低分
#include
float average(float array[], int n);
float Max = 0, Min = 0; //全局变量
int main()
{
float ave, score[10]; //局部变量,在main函数内有效
for (int i = 0; i < 10; i++)
scanf_s("%f", &score[i]);
ave = average(score, 10);
printf("max = %6.2f\nmin = %6.2f\naverage = %6.2f\n", Max, Min, ave);
return 0;
}
float average(float array[], int n)
{
float aver, sum = array[0]; //局部变量,在average函数内有效
Max = Min = array[0];
for (int i = 1; i < n; i++)
{
if (array[i] > Max) Max = array[i];
else if (array[i] < Min) Min = array[i];
sum = sum + array[i];
}
aver = sum / n;
return aver;
}
#include
int n = 10; //全局变量
void func1()
{
int n = 20; //局部变量
printf("input1 n: %d\n", n);
}
void func2(int n)
{
printf("input2 n: %d\n", n);
}
void func3()
{
printf("input3 n: %d\n", n);
}
int main()
{
int n = 30; //局部变量
func1();
func2(n);
func3();
//代码块由{}包围
{
int n = 40; //局部变量,作用域是距离它最近的{}
printf("block n: %d\n", n);
}
printf("main n: %d\n", n);
return 0;
}
运行结果
代码中虽然定义了多个同名变量 n,但它们的作用域不同,在内存中的位置(地址)也不同,所以是相互独立的变量,互不影响,不会产生重复定义错误。
(1)全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不动态地进行分配和释放;
(2)局部变量,如不专门声明为static存储类型,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间;
(3)当全局变量和局部变量同名时,在局部范围内全局变量被“屏蔽”,不再起作用。或者说,变量的使用遵循就近原则,如果在当前作用域中存在同名变量,就不会向更大的作用域中去寻找变量。对于 func1(),输出结果为 20,显然使用的是函数内部的 n,而不是外部的 n;func2() 也是相同的情况;
(4)func3() 输出 10,使用的是全局变量,因为在 func3() 函数中不存在局部变量 n,所以编译器只能到函数外部,也就是全局作用域中去寻找变量 n;
(5)由{}包围的代码块也拥有独立的作用域,printf() 使用它自己内部的变量 n,输加粗样式出 40;
(6)C语言规定,只能从小的作用域向大的作用域中去寻找变量,而不能反过来使用更小的作用域中的变量。对于 main() 函数,即使代码块中的 n 离输出语句更近,但它仍然会使用 main() 函数开头定义的 n,所以输出结果是 30。
根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数
内部函数:只能被本文件中其他函数所调用。定义内部函数的一般形式:
static 类型标识符 函数名(形参表列)
例如:
static int fun(int a, int b)
外部函数:可供本文件和其它文件的函数调用定义外部函数的一般形式:
extern 类型标识符 函数名(形参表列)
extern可以缺省
例如:
extern int fun(int a, int b)
例,有一个字符串,输入一个字符,要求在字符串中删去该字符。用外部函数实现
/*file1.c*/
#include
int main()
{
extern void enter_string(char str[]); //外部函数
extern void delete_string(char str[], char ch); //外部函数
extern void print_string(char str[]); //外部函数
char c, str[6];
enter_string(str);
scanf_s("%c", &c);
delete_string(str, c);
print_string(str);
return 0;
}
/*file2.c*/
#include
void enter_string(char str[5]) //定义外部函数
{
gets_s(str,6); // 向字符数组输入字符串
}
/*file3.c*/
void delete_string(char str[], char ch) //定义外部函数
{
int i, j;
for (i = j = 0; str[i] != '\0'; i++)
if (str[i] != ch)
str[j++] = str[i];
str[j] = '\0a';
}
/*file4.c*/
#include
void print_string(char str[]) //定义外部函数
{
printf("%s\n", str); //输出字符串
}
运行结果
说明:
在文件2中定义了函数enter_string()用来输入一个字符串;
在文件3定义了函数delete_string()作用是把一个字符串中的某个字符删除;
文件4定义了函数print_string()输出一个字符串;
文件1调用了文件2,3, 4中定义的外部函数.