欢迎光临
我们一直在努力

C语言、函数+函数递归

        什么是函数?

        在数学中,其实函数这个概念在小学中我们就能够了解到函数这个概念了。比如:y = kx+b,这个一次函数,有自变量和因变量。

        在C语言中也引入了函数(Function)这个概念。也被有些人称为子程序。C语言中函数就是完成一些特定功能或者任务的一小段代码。

在一般情况下我们会见到两种类型的函数。  库函数和自定义函数

库函数:

        C语言中本身是不提供库函数的。C语⾔的国际标准ANSI C规定了⼀ 些常⽤的函数的标准,被称为标准库;不同的编译器⼚商根据ANSI提供的C语⾔标准给出函数的实现。这些函数就被称为库函数。

        有了库函数,一些常见的功能就不需要程序员自己去实现了,一定程度提升了程序员的开发效率。

        比如我们在数学中使用的平方根:sqrt

#include<stdio.h>
#include<math.h>
int main()
{
double x = 4;
double y = sqrt(x);
printf("%lf\\n",y);

return 0;
}

        结果如下:

自定义函数:


类型+函数名字+(形式参数)
​{
里面是函数体
}

        我们可以将函数想象成一个工厂,(形式参数)可以想象成原材料,将原材料里面的东西交给函数体,原料经过函数体转化成成品,再经过return返回回去自己定义好类型的成品。

        ()里面的形式参数可以没有,也可以设置多个。而函数名就可以想象成一个工厂的名字,通过这个名字去找到这个工厂,让他帮忙加工原材料。

        当你不想要返回的值的时候你可以设置为void类型。

        当我们想要去写一个加法函数的时候,我们就能这么写:

#include<stdio.h>

int Add(int x,int y)
{
return x+y;
}

int main()
{
int r = Add(3,5);
printf("%d\\n",r);
return 0;
}

        结果显然易见:8

        我们的函数名是Add

        形式参数是我们自己设置的 int类型的x和y

        函数体 return x+y

        函数是灵活变换的  ;函数类型根据自己未来的需求来设置。取决于自己想要一个怎么样的函数,怎么设置这个函数的功能,未来都需要自己来设置。

形参与实参:

        就比如我们上面写的Add加法函数。当我们调用Add函数的时候传递3和5给Add函数,这个3和5就是实际参数,简称实参,而在函数()里面设置的x、y就是形式参数,简称形参。

       什么是形参?

        实际上,如果我们只定义了Add这个函数的时候,而不去调用这个函数,这个x和y只是在形式上存在,也不会向内存去申请空间,不是真实存在的。

形参与实参的关系:

#include<stdio.h>

int Add(int x,int y)
{
return x+y;
}

int main()
{
int a =3;
int b =5;
int r = Add(a,b);

return 0;
}

          在我们的VS2022中调试窗口里面的监视可以看见我们的内存分布情况。

        我们在监视窗口可以看见我们的内存都不一样,所以我们可以理解为形参是实参的一个临时的拷贝。

      注意:  形参的改变不会影响实参。

函数与return语句的关系:

        在我们自己写函数的过程中经常可以见到return语句。

  那么return语句到底有什么用?

1.return后面可以接一个表达式也能接一个值,若是表达式先计算表达式里面得出的结果然后再返回回去。

2.当我们的返回类型是void类型的时候return后面可以什么都不加。

3.return语句执行完之后,后面的代码将不会继续执行。

4.return返回的值和自己设置的返回类型不同时,系统会⾃动将返回的值隐式转换为函数的返回类型。

5.若函数中有分支语句,应确保每种情况都有返回值,否则会出现编译错误。

6.如果函数的返回类型不写,编译器会默认返回类型为int

7.如果函数写了返回值,但是没有return语句,那么返回的值是未知的。

数组做为函数的参数:

        在我们解决问题的时候难免会将数组传过去来解决问题,在我们的函数内部对数组进行操作。

        例如:写一个整型的数组,再写一个函数将数组内的内容全部设置为1,然后打印出来。

​#include<stdio.h>

void test(int arr[],int n)
{
//将数组内容全部改为1
int i = 0;
for(i=0;i<n;i++)
{
arr[i] = 1;
}

int j=0;
//打印数组的内容
for(j=0;j<n;j++)
{
printf("%d ",arr[j]);
}

}

int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};

test(arr,10);//当数组在传参的时候,传给函数的是函数的首元素的地址

return 0;
}

        在我们使用数组当参数的时候,需要注意以下几点:

1.函数的形式参数要和实参匹配

2.函数的实参是数组形式,形参也是可以写成数组的形式。

3.形参如果是一维数组大小可以不写,但是形参要是二维数组,二维数组的行能省略,列不能够省略。

4.数组传参的时候,形参是不会重新创建数组的。

5.数组的形参的名字可以和实参的相同。

嵌套调用和链式访问:

        嵌套调用就是函数之间互相调用,就像乐高玩具零件,在多个零件的作用下,才能搭配出这个精美的乐高玩具。函数与函数之间的嵌套调用也是这样。

        例如:假设我们想要知道某年某月有多少天,我们可以设计两个函数来实现。

​​​#include<stdio.h>

int is_leap_year(int year)//判断是不是闰年的函数
{
if( (year%4==0) &&(year %100 != 0) || (year %400 ==0) )
{
return 1;
}
else
return 0;
}

int get_year_month(int year,int month)
{
int days[]={0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int day=days[month];//得到不是闰年的天数
if( is_leap_year(year) && month == 2 )//判断是不是闰年
{
day+=1;
}
return day;
}

int main()
{
int y = 0;
int m = 0;
scanf("%d %d",&y,&m);
if(m >12 || m<1)
{
printf("输入错误,请选择1-12月的数字\\n");
return 1;
}
int r = get_year_month(y,m);//用r来接收
printf("%d\\n",r);
return 0;
}

        这段代码完成了一个独立的功能,在这个函数中我们也用了很多的函数如 :printf,scanf,return还有我们自己写的一个函数get_year_month ;is_leap_year。

        以后在我们写更大的代码的时候都是函数嵌套的,但是函数是不能够嵌套定义的。

函数的链式访问:

        所谓的链式访问就是将函数的返回值作为另一个函数的参数,就像链条一样将函数与函数之间串联起来。

        

#include<stdio.h>
#include<string.h>
int main()
{
printf("%d\\n",strlen("abcdef") );//链式访问

return 0;
}

        这就是简单的链式访问。

        现在我们来看一个有趣的代码:

#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}

        输出的结果会是什么呢?

        答案是:4 3 2 1

        想要知道这个答案的来源我们首先得知道printf的返回值

        printf函数的返回值是打印在屏幕上的字符个数。

        上面例子中第一个printf的打印的是第二个函数的返回值,第二个printf打印的是第三个printf的返回值,第三个printf打印的就是43。

        第三个printf打印的是43,返回值是2

        第二个printf打印的是2,返回值是1

        第一个printf打印的是1

        最终打印的就是4321

函数的声明和定义:

单个文件中:

        注意:当我们写的函数在主函数之前,我们写的函数没有声明的话,编译器也不会报错,

当我们写的函数在主函数之后,如果没有函数的声明的话编译器就会报错。

        因为在主函数之前写的函数,其实他的定义就是一种特殊的声明。


​​​#include<stdio.h>

int main()
{
//函数的声明
int is_leap_year(int year);
int get_year_month(int year,int month);

int y = 0;
int m = 0;
scanf("%d %d",&y,&m);
if(m >12 || m<1)
{
printf("输入错误,请选择1-12月的数字\\n");
return 1;
}
int r = get_year_month(y,m);//用r来接收
printf("%d\\n",r);
return 0;
}

int is_leap_year(int year)//判断是不是闰年的函数
{
if( (year%4==0) &&(year %100 != 0) || (year %400 ==0) )
{
return 1;
}
else
return 0;
}

int get_year_month(int year,int month)
{
int days[]={0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int day=days[month];//得到不是闰年的天数
if( is_leap_year(year) && month == 2 )//判断是不是闰年
{
day+=1;
}
return day;
}

多个文件:

        一般我们在企业中写代码,不可能用一个文件,代码比较多,也不可能将所以的代码都放在一个文件中;我们往往会根据功能拆分为多个文件的形式。

        ⼀般情况下,函数的声明、类型的声明放在头⽂件(.h)中,函数的实现是放在源⽂件(.c)⽂件中。

static与extern:

        sstaticc和extern都是C语言中的关键字。

        static是静态的意思,static可以用来:

        1.修饰局部变量或者修饰全局变量

        2.修饰函数

        extern是用来声明外部符号的。

生命周期:变量的创建(申请内存)到变量的销毁(收回内存)这个时间段

作用周期:是程序设计概念,通常来说,⼀段程序代码中所⽤到的名字并不总是有效(可⽤) 的,⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。

static修饰局部变量:

        如图所示,这个局部变量加了static与没加的区别,static延长了局部变量的生命周期

        代码1的test1函数中的局部变量i是每次进⼊test函数先创建变量(⽣命周期开始)并赋值为0,然后 ++,再打印,出函数的时候变量⽣命周期将要结束(释放内存)。

        代码2,i的值有累加的效果,其实test2函数中的i创建好后,出函数的时候是不会销毁的,重新进⼊函数也就不会重新创建变量,直接上次累积的数值继续计算。

        static修饰局部变量改变了变量的生命周期,本质上是因为static将局部变量的存储类型改变了。本来局部变量是放在栈区的,static将局部变量存储到了静态区。存储在静态区的变量和全局变量一样。

        ⽣命周期就和程序的⽣命周期⼀样了,只有程序结束,变量才销毁,内存才 回收。但是作⽤域不变的。

        使⽤建议:未来⼀个变量出了函数后,我们还想保留值,等下次进⼊函数继续使⽤,就可以使⽤ static 修饰。

static修饰全局变量:

        当我们用static修饰age的时候会发生什么呢?

        编译器会报错!

        当我们想要用static修饰一个全局变量的时候,static修饰的全局变量只能在修饰static的文件中使用。否则在编译过程中会出现链接性错误。即使用extern声明外部符号也无济于事。

        使⽤建议:如果⼀个全局变量,只想在所在的源⽂件内部使⽤,不想被其他⽂件发现,就可以使⽤ static 修饰。

函数递归:

        什么是函数的递归?

        在c语言中递归是一种解决问题的方法。递归就是函数自己调用自己,这一个过程。

#include<stdio.h>
int main()
{
main();
return 0;
}

        在我看来这个代码是世界上最简单的一个递归函数。

        上面只是⼀个简单的递归程序,只不过上⾯的递归只是为了演⽰递归的基本形式,不是为了解决问 题,代码最终也会陷⼊死递归,导致栈溢出。

递归的思想:大事化小!

        递归中递是递推,而归就是回归。

        总不可能让我们自己的函数自己调用自己无线下去,总有一个结束的条件。否则就会栈溢出。

        递归的限制条件:当递归满足这个条件的时候就不再继续递归下去。

例如:当我们想要求一个数字的阶乘的时候,我们就能够用递归的思想。

        

#include<stdio.h>

int test(int n)
{
if(n==0)
{
return 1;
}
else
return n*test(n-1);
}
int main()
{
int m=0;
scanf("%d",&m);
int r = test(m);
printf("%d\\n",r);
return 0;
}

        结果(这⾥不考虑n太⼤的情况,n太大存在溢出):

        有时候,递归虽好,但是也会引入一些问题,所以我们⼀定不要迷恋递归,适可而止就好。

                                                                                                                                        完——

赞(0)
未经允许不得转载:171主机测评 » C语言、函数+函数递归
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址