作者 :会敲代码的Steve
墓志铭:博学笃志,切问静思。
前言:本文旨在总结C语言函数章节的知识点、分为以下九个模块、分别是:
1.函数是什么 2.库函数 3.自定义函数 4.函数参数 5.函数调用
6.函数的嵌套调用和链式访问
7.函数的声明和定义
8.函数递归
尾递归
9.函数作用域规则
正文开始:
目录
1.函数是什么
2.库函数
3.自定义函数
4.函数参数
5.函数调用
6.函数的嵌套调用和链式访问
7.函数的声明和定义
8.函数递归
尾递归
9.函数作用域规则
我们在学习数学的时候,经常会用到函数的概念。但是你了解C语言中的函数吗?
为什么会有库函数?
像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员 进行软件开发。
那么如何学习库函数呢?
可以参照:https://cplusplus.com/reference/
简单的总结,C语言常用的库函数都有:
我们参照官方文档来学习几个库函数
strcat
char * strcat ( char * destination, const char * source );
strcmp
int strcmp ( const char * str1, const char * str2 );
需要注意的地方:
在使用某一个库函数时、必须包含#include对应的头文件。
这里对照文档来学习库函数、目的是学会它的用法。
如何学习库函数?3
需要全部记住吗?No
需要学会查询工具的使用:
英文很重要。最起码得看懂文献。
如果库函数能完成所有的任务,那还要程序员干什么?
所以更加重要的是自定义函数。
自定义函数和库函数一样、有函数名、返回值类型、函数参数。
但不同的是,函数的功能都由我们自己来进行总体的设计、这给了程序员一个很大的发挥空间、
函数的组成部分:
int func(int x,int y)
{
return x+y;
}
int //返回值类型
func//函数名
int x,int y//函数参数
我们举一个例子
求两个数的最小公倍数
#include
int gcd(int a, int b) {
if(!b) {
return a; // (1)
}
return gcd(b, a % b); // (2)
}
int main() {
int a, b;
while(scanf("%d %d", &a, &b) != EOF) {
printf("%d\n", gcd(a, b));
}
return 0;
}
再举一个栗子、计算函数的阶乘和
//1+2!+3!+...+N!的和 (结果为整数形式)
#include
long long func(int x);
int main()
{
int a = 0;
long long sum = 0;
scanf("%d",&a);
for(int b = 1;b<=a;b++)
{
sum+=func(b);
}
printf("%lld",sum);
return 0;
}
long long func(int x)
{
if(x==0)
return 1;
else
return func(x-1)*x;
}
4.1 实际参数
真实传给函数的参数叫做实参、参数类型可以是:常量、变量、表达式、函数等。
无论实参是个怎样的值,在进行函数调用的时候、它们必须具有确定的值、以便把这些值传给形参。
4.2形式参数
形式参数是指函数括号内的变量、因为函数只有在调用的时候才会被实例化(分配内存空间)。所以叫做形式参数、而且形参在函数调用完了之后才会销毁,因此形式参数只有在函数中才有效。
上面的gcd和func函数中的参数、a、b、x、都是形式参数、在main函数中传递的参数都是给gcd函数和func函数的实参。给gcd函数的&a、&b和func函数的&a、都是实际参数。
我们对函数的实参和形参做一下分析:
这里可以看到 func 函数在调用的时候, x 拥有自己的空间,同时拥有了和实参一模一样的内容。 所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝。
5.2传值调用
5.2 传址调用
函数与函数之间是可以按实际的需求进行合作的、也就是说可以嵌套调用的
6.1嵌套调用
#include
#pragma warning(disable : 4996
int gcd(int x, int y)
{
if (!y)
{
return x;
}
else return gcd(y,x % y);
}
int my_we(int x, int y)
{
int sum = 0;
sum = (x * y) / gcd(x, y);
return sum;
}
int main()
{
int x, y;
scanf("%d %d", &x, &y);
printf("%d %d", gcd(x, y), my_we(x, y));
return 0;
}
/* 你的代码将被嵌在这里 */
6.2 链式访问
把一个函数的返回值做另一个函数的参数
#include
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//结果是啥?
//注:printf函数的返回值是打印在屏幕上字符的个数
return 0;
}
7.1函数声明
7.2 函数定义
add.h中的声明:
#pragma once
int add(int x, int y);
int func(int a);
add.c中的实现 :
int add(int x, int y)
{
return x + y;
}
int func(int a)//递归函数举例
{
//递推公式
if (a == 1) return 1;
return func(a - 1) + a;
}
main函数调用:
#include
#include"add.h"
int main()
{
int a;
int b;
int i;
int sum = 0;
scanf_s("%d%d%d", &i,&a,&b);
printf("%d\n", func(i));
printf("%d\n", add(a, b));
//在这里调用的哦
}
多文件编程的好处是在实现某一个功能的时候、可以把它封装成一个模块。等再次要使用的使用的时候直接调用这个接口即可,在调试的时候也方便了不少,节省了大量的时间重构一个项目,这便叫做高内聚、低耦合。
递归的核心思想就是:大事化小
递归的两个必要条件:
案例1 使用递归求1-100的和 例如 :输入 100 输出5050
参考代码:
#include
int func(int x);
int main()
{
int a;
int sum = 0;
scanf("%d",&a);
sum = func(a);
printf("%d",sum);
return 0;
}
int func(int x)
{
if(x==1)
return 1;
else
return func(x-1)+x;
}
案例 2 编写函数不允许创建临时变量,求字符串的长度。
#incude
int Strlen(const char*str)
{
if(*str == '\0')
return 0;
else
return 1+Strlen(str+1);
}
int main()
{
char *p = "abcdef";
int len = Strlen(p);
printf("%d\n", len);
return 0;
}
8.1 递归与迭代
求n的阶乘(不考虑溢出的情况)
int func(int x)
{
if(x==1)
return 1;
else
return x*func(x-1);
}
求第m个斐波那契数列(不考虑溢出)
int func(int x)
{
if(x<=2)
return 1;
else
return func(x-1)+func(x-2);
}
递归算法虽然简洁,但是发现了一个问题;
原因在哪里?
func函数在调用时做了很多的重复计算
如果把代码修改一下?
if(n == 3)
count++;
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
最后输出count,是一个很大的数字
那我们如何改进呢?
那么如何解决上面的问题?
留个坑在这(你们自己写哈)
简单来讲,尾递归是指在一个方法内部,递归调用后直接return,没有任何多余的指令了。
比如,一个递归实现的累加函数。
int static func(int x)
{
if(x==1)
return 1;
else
return func(x-1)+func(x-2);
}
请问这个是尾递归吗?答案是错误的!
可能有的人会说,最后一个步骤就是调用func,为什么不是尾递归?
实际上,你看到的最后一个步骤不代表从指令层面来讲的最后一步。这个方法的return先拿到acc(n-1)的值,然后再将n与其相加,所以求func(n-1)并不是最后一步,因为最后还有一个add操作。
如果我们把上面的代码做一个等价替换?
int static func(int x)
{
if(x==1)
return 1;
int k = func(x-1);
return x+k;
}
看,是不是还隐含一个add操作?
累加的尾递归写法是下面这样子的:
int static func(int x,int sum)
{
if(x==1)
{
return x+sum;
}
return func(x-1,sum+x);
}
递归调用后就直接返回了,这是真正的尾递归。
递归调用的缺点是方法嵌套比较多,每个内部的递归都需要对应的生成一个独立的栈帧,然后将栈帧都入栈后再调用返回,这样相当于浪费了很多的栈空间.
9.1 局部变量
定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。例如:
int main()
{
int a,b;//a,b仅在main函数内有效
scanf("%d%d",&a,&b);
}
int func(int x,int y)
{
int m,n;//m,n仅在func函数内有效
return x+y;
}
说明:
9.2 全局变量
在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。例如:
#include
int func(int x,int y);
double m,k;//全局变量
int main()
{
int d,c;
scanf("%d%d",&d,&c);
}
int a,b;//全局变量
int func(int x,int y)
{
int m,n;
return x+y;
}
a,b,m,k.都是在函数外部定义的全局变量。C语言代码都是依次从前往后执行的,由于函数func定义在a,b前面所以在func函数内无效,而m,k、都定义在开头、所以在main(),func()两个函数内都是有效的。
全局变量的综合举例:
#include
int n = 10; //全局变量
void func1(){
int n = 20; //局部变量
printf("func1 n: %d\n", n);
}
void func2(int n){
printf("func2 n: %d\n", n);
}
void func3(){
printf("func3 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;
}
运行结果:
func1 n: 20
func2 n: 30
func3 n: 10
block n: 40
main n: 30
代码中虽然定义了多个同名变量 n,但它们的作用域不同,在内存中的位置(地址)也不同,所以是相互独立的变量,互不影响,不会产生重复定义(Redefinition)
错误。
{ }
包围的代码块也拥有独立的作用域,printf() 使用它自己内部的变量 n,输出 40。案例二
输入一行字符,统计出其中数字字符的个数。
#include
#include
#include
//定义全局变量
int number = 0; //数字
//定义统计函数
void len_txt(char s[])
{
int c;
int i;
c = strlen(s); //获取字符串的长度
//分别判断
for (i = 0;i < c;i++){
if (isdigit(s[i]))
{
number++;
}
}
}
//主函数
int main()
{
char s[100]; //定义字符串最大长度
gets(s); //获取字符串
len_txt(s); //调用函数
printf("%d ",number);
return 0;
}
根据题意,我们希望借助一个函数返回数字字符的个数,为了方便输出结果。我们定义了一个全局变量number来计数,因为函数是void类型没有返回值。全局变量的作用域是整个程序,在函数len_txt中修改变量的值,,能够影响到包括 main() 在内的其它函数。
都看到这了,还不赶紧收藏起来!