C语言函数初学——基础巩固与习题练习

一.自定义函数

如果函数不需要任何返回,就用void

void就是无返回,不需要返回类型8b2ff1d1003149abbcd6644810de0a1d.png

题目:写一个函数可以交换整形变量的内容4f5c1482c59341419dc3b8a4c87bce4d.png

此例题总结 :

错误案例,在函数调用的过程中,没有采取指针接受地址,便会导致形参对实参进行了一份拷贝,修改形参并不会影响实参。

a和b是实参;px,py为形参。

二.函数的参数

 实际参数(实参):

真实传给函数的参数,叫实参。
实参可以是:常量、变量、表达式、函数等。
无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。


形式参数(形参):

形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。

补充:

形参实例化之后就相当于对实参进行了一份拷贝。

三.函数的调用

传值调用

函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。

传址调用:

传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

四.函数的练习

题目:打印100-200的素数

//判断i是否为素数

//是素数就打印

//用2~i-1的数去被i除以

//int flag =1; flag是1,表示素数

b7fde8a01c984f648e3021618aeb6fc1.png

优化方法:

sqrt是数学库函数,是开平方的意思 ,头文件需要加入#include <math.h>

Ps:设一个数m,m不是素数,m = a * b,

a和b 中一定有一个数字<=sqrt(m),

//只要能在开平方i之前找到一个因子,若i整除这个因子为0,说明i不是素数。c97549110d164a738b7afcf9b469b42b.png

此程序还可以再优化:

最外部for循环语句改为for(i = 101;i<= 200,i+=2)          

 //偶数除了2都不是素数

题目:写一个函数可以判断一个数是不是素数?fa37b1cb128e46078fc53e166c66aa96.png

 //Is_prinme函数 i传给n,只是数值就够啦,没必要进行传址;

题目:写一个函数判断是不是闰年

bd25465c08524212b283281adc76b919.png

补充: 

函数的功能要足够单一,即高内聚低耦合,这样才会有人去使用,譬如此闰年函数,有人只需判断,不需要打印,为什么不在函数里面打印也就显而易见了。

题目:写一个函数,实现一个整形有序数组的二分查找

//二分查找的函数:binary_search();

//写函数的话,尽量先写函数怎么用,再去写函数的功能。

//找到了,返回下标;找不到,返回-1dcc1f93d0a594725bf7f176a540bd341.png

补充:

形式参数和实际参数的名字可以相同,也可以不同。

数组传参直接传的是数组名字,不带【】

Int binary_search (int arr[]) 

 此处arr是一个指针变量

int sz = sizeof(arr)/sizeof(arr[0]) 

 sizeof(arr)是4或者8 ,32位的平台是4,64位的平台是8,除以一个数组元素的大小,不是1就是2,并非求的是数组元素的个数。

Ps:数组传参,传递的是数组首元素的地址,而不是整个数组,所以在函数内部计算一个函数参数部分的数组的元素个数是错误的。

形参arr看似是一个数组,实则是一个指针。

题目:写一个函数,每调用一次这个函数,就会将 num 的值增加1。

92494e264c784f7899364adc613ed011.png

五.函数的嵌套调用和链式访问

1. 函数可以嵌套调用,但不能嵌套定义,以下为嵌套定义错误示例;

eb0f928b98fd4dedbd41b8112ce63ce8.png

2.链式访问:把一个函数的返回值,作为另一个函数的参数。

#include <stdio.h>
#include <string.h>
int main() 
{
char arr[20] = "hello";
int ret = strlen(strcat(arr,"bit"));//这里介绍一下strlen函数 
printf("%d\n", ret);
return 0;
}
#include <stdio.h>
int main() 
{
printf("%d", printf("%d", printf("%d", 43)));    
//结果是啥?
//注:printf函数的返回值是打印在屏幕上字符的个数 
return 0;
}

 跨越补充:

//main函数有三个参数;

int main(int argc,char* argv[],char *envp[])

六.函数的声明和定义

1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。

2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用

3.函数的声明一般要放在头文件中的。(.h文件)           头文件和源文件分开

4.函数的定义是指函数的具体实现,交待函数的功能实现。

跨越思维补充:

导入一个静态库:#pragma comment(lib."     ")

静态库在一定程度上把一些代码隐藏起来了。

七.函数的递归

1 什么是递归?

        程序调用自身的编程技巧称为递归( recursion)。 递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略,只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

2.递归的主要思考方式在于:

把大事化小


3.递归的两个必要条件:

存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件。


练习1:接收一个整型值(无符号),按照顺序打印它的每一位。

例如:输入:1234,输出 1 2 3 4

//%d打印有符号的整数(会有正负数)
//%u打印无符号的整数
33bafa350faa472cac4d6c389839e9aa.png

练习2:编写函数时不创建临时变量来求字符长度

以下方法是创建临时变量:448bb49ffbb24966b118a6123b51982b.png

利用递归:

24676905139743c7a3fcdf3fe0c96310.pngfb06b758b0844f1abf14675ab8d330cf.png

 4.递归与迭代

循环是一种迭代,但迭代不一定是一种循环。

练习3:求n的阶乘(不考虑溢出)

//举例子 5的阶乘就是5*4的阶乘 公式:n*fac(n-1) 

//下面是用递归的方式实现

两处错误  scanf  去掉\0

                 printf    应是\n

aa9ab119c0334930897ce083ac69fbf6.png

 //下面用迭代的方式实现  (非递归)

两处错误  scanf  去掉\0

                 printf    应是\n

a905540e0dfb422aa2f706fef09f46b8.png

练习4:求第n个斐波那契数 (不考虑溢出)

//斐波那契数列 : 前两个数相加得出第三个数来

//1 1 2 3 5 8 13 21 ......

参考代码:      

int fib(int n)
{
    if (n <= 2)
        return 1;
    else
        return fib(n - 1) + fib(n - 2);
}

但是我们发现有问题;
在使用 fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。 使用factorial函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。

为什么呢?
我们发现 fib 函数在调用的过程中很多计算其实在一直重复。 如果我们把代码修改一下:

int count = 0;//全局变量 int fib(int n)
{
    if(n == 3)
        count++;
    if (n <= 2)
        return 1;
    else
        return fib(n - 1) + fib(n - 2);
}

那我们如何改进呢?
在调试factorial 函数的时候,如果你的参数比较大,那就会报错:stack overflow(栈溢出)这样的信息。 系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一 直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。
那如何解决上述的问题:
1. 将递归改写成非递归。
2. 使用static对象替代nonstatic局部对象。在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
比如,下面代码就采用了,非递归的方式来实现:

错误  scanf  去掉\n77ac9314f27b4667a1fc2af787d99ae6.png

 提示:
1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值