目录
函数声明和定义:
1、函数的声明:
(1) 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数 声明决定不了。
(2) 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
一般写法:
#include<stdio.h>
int ADD(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int x = 1;
int y = 2;
int ret = ADD(x, y);
return 0;
}
如果我们把ADD函数放到main函数之后,就会出现ADD函数没有定义的报错,这种报错就是我们的函数没有满足先声明后使用。
如果我们把ADD函数放到main函数之后,就要写成以下的形式。
#include<stdio.h>
int ADD(int x, int y) //函数的声明,告诉有这个函数
int main()
{
int x = 1;
int y = 2;
int ret = ADD(x, y);
return 0;
}
int ADD(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
(3) 函数的声明一般要放在头文件中的。
2、函数的定义
函数的定义是指函数的具体实现,交待函数的功能实现。
在实际工程应用中我们会用分文件的形式来写整个工程,这样做是为了:(1)实现多人协作,(2)对函数进行分装和隐藏。
举例:上面的加法函数
add.h的内容——放置函数的声明
#pragma once //无论函数声明多少次,函数只声明一次
//函数的声明
int Add(int x, int y);
test.c的内容——放置整体的大致思路——main函数
int main()
{
int x = 1;
int y = 2;
int ret = ADD(x, y);
return 0;
}
add.c的内容——放置函数的实现
int ADD(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
函数递归
1、什么是递归
程序调用自身的编程技巧称为递归( recursion)—— 自己调用自己
递归做为一种算法在程序设计语言中广泛应用。
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
递归策略——只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
2、递归的两个必要条件
(1)存在限制条件,当满足这个限制条件的时候,递归便不再继续。
(2)每次递归调用之后越来越接近这个限制条件。
举例1:
接受一个整型值(无符号),按照顺序打印它的每一位。
例如: 输入:1234
输出 1 2 3 4
分析:
递归解决:大事化小 —— 函数自己调用自己
函数的形式:
Print(1234)—— Pirnt(123)+ 4 —— Print(12)+ 3 + 4 —— Print(1) + 2 + 3 + 4
#include<stdio.h>
void Print(int num)
{
if (num > 9)
Print(num/10); //自己调用自己
printf("%d ", num % 10);
}
int main()
{
int num = 1234;
Print(num); //自己定义的打印函数
return 0;
}
画图讲解:
一种常见的递归错误分析:
#include<stdio.h>
void Print(int num)
{
//if (num > 9)
Print(num / 10);
printf("%d ", num % 10);
}
int main()
{
int num = 1234;
Print(num); //自己定义的打印函数
return 0;
}
这时就会无限的递归下去——形成死递归——出现栈溢出的现象。
每一次函数的创建都要为函数申请空间。
举例2
题目:不利用库函数,求字符串的长度
一般方法:
//用函数求字符串长度
#include<stdio.h>
int my_strlen(char* arr) //传的是字符地址要用字符指针变量来接收
{
int count = 0;
while (*arr != '\0')
{
count++;
arr++;
}
return count;
}
int main()
{
char arr[10] = "abcd";
//其中的arr是数组名,也是首元素的地址
int ret = my_strlen(arr); //自己定义的一个求字符长度的函数
printf("%d\n", ret);
return 0;
}
补充:
如果是:字符指针变量 —— char* p —— p+1 —— 跳过一个字符
如果是:整型指针变量 —— int* p —— p+1 —— 跳过四个字符
利用递归的方法:
分析:
函数形式:my_strlen(abcd) —— 1 + my_strlen(bcd) —— 1 + 1 + my_strlen(cd) ——
1 + 1 + 1 + my_strlen(d) —— 1 + 1 + 1 + 1 + 0
代码展示:
//用函数求字符串长度
#include<stdio.h>
int my_strlen(char* arr)
{
if (*arr == '\0')
return 0;
else
{
return 1 + my_strlen(arr + 1);
}
}
int main()
{
char arr[10] = "abcd";
int ret = my_strlen(arr);
printf("%d\n", ret);
return 0;
}
举例3:
求n的阶乘。(不考虑溢出)
一般求解:
#include<stdio.h>
int Fac(int n)
{
int tem = 1;
while (n > 0)
{
tem *= n;
n--;
}
return tem;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fac(n);
printf("%d\n", ret);
return 0;
}
递归求解:
#include<stdio.h>
int Fac(int n)
{
if (n <= 1)
{
return 1;
}
else
{
return n * Fac(n - 1);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fac(n);
printf("%d\n", ret);
return 0;
}
举例4:
求第n个斐波那契数。(不考虑溢出)
什么是斐波那契数:1 1 2 3 5 8 13 21 34 55 ...........
递归求解:
//求第n个斐波那契数。(不考虑溢出)
#include<stdio.h>
int Fan(int n)
{
if (n <= 2)
return 1;
else
return Fan(n - 1) + Fan(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fan(n);
printf("%d\n", ret);
return 0;
}
这个递归函数只能求比较小的数字,如果求比较大的数字就会计算的很慢
int count = 0; //全局变量
int Fan(int n)
{
if(n == 3)
count++;
if (n <= 2)
return 1;
else
return Fan(n - 1) + Fan(n - 2);
}
如果上面这个函数求第40位的斐波那契数:count 就会是一个非常大的数字。
分析:
那如何解决上述的问题:
将递归改写成非递归。
//求第n个斐波那契数。(不考虑溢出)
#include<stdio.h>
int Fan(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fan(n);
printf("%d\n", ret);
return 0;
}
提示:
1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开 销。