C语言程序设计笔记---010
- C语言函数 --- 续
- 1、函数声明和函数的定义
- 1.1、函数声明例程
- 2、函数递归
- 2.1、错误递归,栈溢出例程
- 2.2、函数递归练习例程1
- 2.3、递归的必要条件
- 函数递归例程2
- 3、递归与迭代
- 递归与迭代例程1
- 递归与迭代例程2
- 4、结语
C语言函数 — 续
1、函数声明和函数的定义
函数在使用之前,先声明,再使用。
通常放在头文件之后,主函数之前。
函数的定义:是指函数的具体体现,交代函数的功能实现。
通常放在.h头文件中声明和定义。
1.1、函数声明例程
说明:
写一个函数,完成两个整数的相加
#include <stdio.h>
#include "test019.h"
//函数定义放main之前时,编译器判定执行顺序。相当于即定义也声明
//int Add(int a ,int b)
//{
// return a + b;
//}
//函数声明
//int Add(int a, int b);
int main()
{int num1 = 0;int num2 = 0;scanf("%d %d",&num1,&num2);//计算两个整数的和//函数的调用(传值调用)int ret = Add(num1,num2);printf("%d\n",ret);return 0;
}
//函数定义放主函数之后,就需声明了
//int Add(int a, int b)
//{
// return a + b;
//}
test019.h
// .h 头文件存放函数的声明//求和函数的声明
int Add(int a, int b);
test019.c
// .c 头文件存放函数的定义(执行函数功能)、
//调用 .c 文件更加方便//求和函数的定义
int Add(int a, int b)
{return a + b;
}
小结:
在未来的多数工程中,代码较多,函数一般都是分别在.h文件中声明,在.c文件中定义功能实现的。
模块化编程,有利于对工程程序的分工、保护、移植等优势
(1)、多人协作
(2)、代码保护
(3)、方便理解性和移植性
2、函数递归
什么是递归?
程序调用自身的编程技巧称为递归
概念:递归作为一种算法在程序设计语言中广泛应用。
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题来求解,递归策略只需要少量的程序就可以描述解题过程所需要的多次重复计算,大大减少了程序的代码量
主要编程思想:”大事化小“
注意:首先,程序调用自身的编程技巧称为递归
但是得考虑栈溢出:Stack overflow
还得考虑数据类型得最大范围等问题
2.1、错误递归,栈溢出例程
#include <stdio.h>
int main()
{printf("溢出\n");main();return 0;
}
2.2、函数递归练习例程1
说明:
按顺序打印数的每一位 ---- 递归实现
如:1234 输出:1 2 3 4
思路:
1234
1234%10 = 4
1234/10 = 123
/23 %10 = 3
123/10 = 12
.....
循环写法:
#include <stdio.h>
int main()
{int num = 0;scanf("%d",&num);//1234while (num){printf("%d ",num%10);//4 3 2 1num = num / 10;}return 0;
}
递归写法 – 思想:大事化小
#include <stdio.h>
void Print(int num)
{if (num > 9)Print(num/10);printf("%d ",num%10);
}
int main()
{int num = 0;scanf("%d",&num);//1234//实现将参数Num每一位按照顺序输出Print(num);//Print(1234)//Print(123) + 4 //Print(12) + 3 //Print(1) + 2return 0;
}
小结:
递归:递推 + 回归
(1)、函数之所以能实现调用,都是因为,函数在调用时,会维护一个函数
(2)、函数的递归调用是在栈区里开辟的内存空间,层层递推,层层回归
(3)、递归的跟循环语句类似,需要一个重要的、必要的跳出/限制条件
2.3、递归的必要条件
(1)、存在限制条件,当满足这个限制条件的时候,递归便不再继续
(2)、每次递归调用之后越来越接近这个限制条件
函数递归例程2
说明:
不创建临时变量,求字符串长度 ---- 模拟strlen函数功能 + 递归
正常写法:
#include <stdio.h>
//实现计算字符串长度
size_t my_strlen(char* str)
{size_t count = 0;while (*str != '\0'){count++;str++;}return count;
}int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);//数组名作为参数,传递的是首元素地址printf("%zd\n",len);//6return 0;
}
递归写法:
#include <stdio.h>
int my_strlen(char* str)
{if (*str == '\0')return 0;elsereturn 1 + my_strlen(str+1);//str+1下一个字符作为首元素。传递其地址
}int main()
{char arr[20] = "abc";int ret = my_strlen(arr);printf("%d\n",ret);return 0;
}
3、递归与迭代
迭代等价于循环
递归可以与迭代相转换,根据实际需求应用递归或迭代
递归与迭代例程1
说明:
求阶乘 — 递归
思路:公式法
5! = 12345
4! = 1234
等价于:5! = 54!
即:
n <= 1 —>return 1
n >= 2 —>return n*Fac(n-1)
正常写法:
#include <stdio.h>
//实现阶乘计算
int Fac(int n)
{int i = 0;int k = 1;for (i = 1; i <= n; i++){//k = k * i;k *= i;}return k;
}
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;elsereturn n * Fac(n - 1);
}int main()
{int n = 0;scanf("%d",&n);int ret = Fac(n);printf("%d\n",ret);return 0;
}
小结:
(1)、递归层次太深入可能导致栈溢出,还有类型数据范围超出
(2)、迭代/循环写法一般不会有栈溢出的情况
递归与迭代例程2
说明:
求第n个斐波那契数列,如:1 1 2 3 5 8 13 21 34 55 …
#include <stdio.h>
//递归写法:
//int Fib(int n)
//{
// if (n <= 2)
// return 1;
// else
// return Fib(n - 1) + Fib(n - 2);
//}
//迭代写法:
int Fib(int n)
{int a = 1;int b = 1;int c = 1;while (n >= 3){c = a + b;a = b;b = c;n--;}return c;
}
int main()
{int n = 0;scanf("%d", &n);int ret = Fib(n);printf("%d\n",ret);return 0;
}
小结:
权衡何时使用递归,何时使用迭代。
一般情况下,递归较容易想到,写出的代码没有明显的额缺陷,就可以使用递归写法 ,
如:套公式这种较多,但如果写出的递归,有明显的问题,
如:栈溢出。效率缓慢等问题就不建议使用递归的写法。
解决上述递归易错的问题,一般可优化的方法:
(1)、使用静态区,分担栈区的空间,防止栈溢出
(2)、递归转化为迭代
4、结语
利用好递归函数,对于思维能力是非常有用的,能够帮助程序员在编写程序时,使用恰当能够大大提高程序的简洁性,事半功倍,以少量代码完成指定功能。
因此,编写程序时应该充分发挥函数的作用,并合理地搭配其他语句和特性使用,以便更好地保证程序的正确性、可靠性以及严谨性。