目录
前言
我们之前了解了函数的基本原理和基本写法,现在就可以适当的写一些简单的小程序,勤加练习才会有好的结果。
4.函数的调用(补充)
我们可以用函数来写一个二分查找的算法。我们需要再一个数组里面寻找一个数,来看这一组数中是否会有这个数,如果有,打印找到了,并且返回其下标。
#include <stdio.h>
#define _CRT_SECURE_NO_WARNINGS 1
int binary_search(int arr[], int k, int sz)
{
int left = 0; //数组左下标
int right = sz - 1; //右下标
while (left<=right)
{
int mid = (left + right) / 2; //取一半
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
return mid; //返回下标
}
}
return -1;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]); //计算数组的长度
int k = 7;
int pos = binary_search(arr, k, sz);
if (-1 == pos)
{
printf("找不到!!\n");
}
else
printf("找到了,下标为:%d\n",pos);
return 0;
}
以上就是一个用C语言来写的一个二分查找算法,通过不断改变数组范围的下标来折半查找数字,这样比遍历循环要效率高一些。我们通过 binary_search函数来传入三个参数,从而得到最后的下标。
函数的返回类型不写的时候,默认是返回的Int类型。函数要写的严谨一些,这样才不会出现错误。
5.函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
5.1嵌套调用
#include <stdio.h>
void new_line( )
{
printf("okok\n");
}
void three_line( )
{
int i=0;
for(i=0;i<3;i++)
{
new_line( );
}
}
int main( )
{
three_line( );
return 0;
}
上述代码是打印三个okok,上面的three_line会调用new_line函数,调用了三次,这就是嵌套调用,然后通过主函数来调用并输出结果。
但在函数里面定义函数是不可以的,可以在函数里面调用。
5.2链式访问
把一个函数的返回值作为另一个函数的参数
#include <string.h>
#include <stdio.h>
int main( )
{
int len = strlen("abcdef");
printf("%d\n",len);
printf("%d\n",strlen("abcdef"));
return 0;
}
把一个函数的返回值作为另一个函数的参数叫做链式访问,类似把一个个函数串起来一样。 上述代码就似乎通过函数的链式访问来打印abcdef的个数。
int main( )
{
printf("%d",printf("%d",printf("%d",43)));
return 0;
}
上述代码可以看看输出为多少。
首先我们要搞清printf函数的返回值是什么,一般函数返回的都是整形(在没有任何特殊情况的时候),我们可以在printf - C++ Reference (cplusplus.com)来查看printf函数官方的定义以及返回值。
我们可以看见如果返回成功,那么是返回的是打印字符的总个数,如果我们打印的是43,那么返回的值就位2,以此类推。
所以代码先打印43,43的函数返回为2,所以打印2,2的返回为1,最后再打印1,所以最后的结果就是4321。
6.函数的声明和定义
6.1函数声明
1、告诉编译器有一个函数叫做什么,参数是什么,返回类型是什么,但是具体是不是存在,函数声明决定不了。
2、函数的声明一般出现在函数的使用之前,要满足先声明后使用。
3、函数的声明一般要放在头文件中的。
函数的声明和变量的声明非常的形似。声明的时候把函数返回类型参数,类型,以及函数名写出来就行。
6.2函数定义
函数的定义是指函数的具体体现,交代函数的功能实现。
6.3例子
int Add(int x,int y); //函数的声明
int main( )
{
int a=0;
int b=0;
scanf("%d %d",&a,&b);
int ret =Add(a,b);
printf("%d\n",ret);
return 0;
}
int Add(int x,int y)
{
return x+y;
}
如果上述代码删去对函数的声明那一行代码,就会出现一个警告,会说你这个函数没有定义或者声明,因为编译器在编译代码的时候是一行一行的进行编译的,首先会编译主函数,但到了主函数里面的Add函数的时候,没有发现之前的声明,所以会发生报错。如果想解决这个问题,就可以事先声明函数,或者把自定义函数实现的这段代码写在主函数前面,这样就不会发生错误了。
还有一种方法,可以添加一个头文件(.h为后缀的文件),头文件里用来声明这个函数;在添加一个源文件,用来写(实现)(定义)函数的功能;在主函数所在的源文件里加上#include "add.h"来包含以下就行,这里面用双引号来写是因为在本地文件夹而且是我们自己写的,用尖括号的包含是因为那是库。
//头文件
//add.h
int Add(int x,int y); //在头文件里进行声明
//源文件
//add.c
#define _CRT_SECURE_NO_WARNINGS 1
int Add(int x,int y)
{
return x+y;
}
这里面add.h和add.c叫做加法模块
//主函数
//main.c
#include <stdio.h>
#include "add.h"
int main( )
{
int a=0;
int b=0;
scanf("%d %d",&a,&b);
int ret =Add(a,b);
printf("%d\n",ret);
return 0;
}
main.c叫做测试模块,用来测试前面的加法模块。编译器会主动的把这三部分进行组合并编译。
拆开(分开写)的好处:
1.模块化开发(分工)
如果我们要开发一个计算器,假设里面有4功能,而且都非常的复杂,一个功能由一个程序员进行开发,这时候就不会在一个文件里写,因为那样就会发生冲突,这时候,每个人就只写一个头文件和一个实现函数的源文件就可以,各自开发各自的模块,最后再在主文件里统一调用一下就可以,效率很高,分工明确。
2.代码的隐藏
如果我们有一些代码不给别人看,可以用函数编译成为静态库,编译后出现一个.lib的文件,这个lib函数是不能看见写了什么的,打开后是一堆乱码,通过头文件可以知道函数的用法,我们只需要来导入静态库,#pragma comment(lib,"静态库名字"),就可以使用静态库里的函数了。
未来我们对函数的声明一般都放在头文件中,函数的定义一般放在源文件中。函数在使用之前一定要声明。
7.函数递归
7.1什么是递归?
递-递推
归-回归
函数调用自身的编程技巧称为递归(recursion)。
递归作为一种算法在程序设计语言中广泛使用,一个过程或者函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需要少量的代码就可以描述出解题过程所需要的多次重复计算,大大的减少了代码量。
递归的主要思考方式在于:把大事化小
7.2递归的两个必要条件
存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件。
7.3例子
可以看下面的代码:
int main( )
{
printf("okok\n");
main( );
return 0;
}
这段代码每一次调用函数,都会为本次函数在内存的栈区上能开辟出一块代码,上述会发生错误,因为它早晚会把栈区内存耗干,所以最后会出现栈溢出的错误。
当递归深度过大的时候,就会出现栈溢出。
再看一个代码:
接收一个无符号整形,一次打印其每一位。
void Print(unsigned int n)
{
if(n>9)
{
Print(n/10);
}
printf("%d ",n);
}
int main( )
{
unsigned int num =0;
scanf("%u",&num);
Print(num);
return 0;
}
这里利用一个Print( )的函数来进行递归。如果输入1234,进入函数后每一次除以10,依次变为123,12,1,最后会到1,因为1小于9,所以会打印1,之后往回走对12取余得2,对123取余得3,对1234取余,得4,在屏幕上也就打印出来了1234。就实现了依次打印各个数字。
递归需要不断的练习和思考才可以。
如果编写一个函数,不允许定义临时变量,来求字符串的长度:
int my_strlen(char *str)
{
if(*str !='\0')
return 1+my_strlen(str+1);
else
return 0;
}
这个函数就可以输出当前字符串的个数,假如我们要求hello,字符串长度。如果不为\0,那么传入的地址每次str+1,会让字符串的地址向后移动一个,因为传入的参数是字符串的首地址。
my_strlen("hello")
1+my_strlen("ello")
2+my_strlen("llo")
3+my_strlen("lo")
4+my_strlen("o")
5+my_strlen("\0")
5+0
递推后之后回归,所以最后输出是5。
由此看出递归的限制条件非常重要。
总结
递归的思想要掌握,慢慢的就熟练了。下一篇还会从递归开始。