《C语言精讲攻略第七课》——3000字叙说函数的进阶超详细
文章目录
前言
上一节,我为大家简单带大家认识了一下函数,了解了一下库函数,函数的参数分为:形式参数、实际参数,函数的调用分为:传值调用、传址调用,没有看三国的小伙伴可以去看一下哟!
接下来这一节我将给大家再拓展一些函数的使用,请大家可以给我一些意见和不足,谢谢!
🌈1.函数的嵌套调用和链式访问
💧1.1嵌套调用
对于函数的嵌套调用,我们先举个生活中的小例子,就是说,对于一辆汽车,他是由多种机构组成,而机构又由成千上万个零件组成,这样才能造出一辆完整的汽车。
而对于我们的程序来说,一个大的程序,如果把它拆分开来的话,它其实是由多个函数组合在一起进行使用,所以就有了我们函数的嵌套调用——函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
void NewLine()
{
printf("你真的好帅!\n");
}
void ThreeLine()
{
int i = 0;
for (i = 0; i < 3; i++)
{
NewLine();
}
}
int main()
{
ThreeLine();
return 0;
}
但是,函数不可以嵌套定义如:
💧1.2链式访问
把一个函数的返回值作为另一个函数的参数!
然后,我们对以上的代码进行以下修改
接下来请看以下的链式访问练习:
//我们用代码在进行一次解读
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//首先打印出43返回一个2
//将上面代码进行拆分
printf("%d", printf("%d", 2));
//然后再打印出来2,返回一个1,再拆分
printf("%d",1);//最后打印出来1
return 0;
}
🌈2.函数的声明和定义
⚡2.1函数声明
-
告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数
声明决定不了。 -
函数的声明一般出现在函数的使用之前。要满足先声明后使用。
-
函数的声明一般要放在头文件中的。
例如:
但是请看以下情况:
对于这种情路况,我们先声明,再使用,最后定义,这种是没办法通过的,必须是先声明,在定义,最后使用;但是,对于局部变量来说可以!
情况二:
对于这种情况来说,int a 就叫做定义而不是声明,当我们后面对a进行了定义后,这个int a 才叫做声明。
⚡2.2函数定义
关于函数的定义,结合具体代码为大家讲解:
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", ret);
return 0;
}
int Add(int x, int y)
{
return x + y;
}
但是,我们一般不这样写代码,而是把自定义的函数放到main()函数的前面:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
int ret = Add(a, b);
printf("%d", ret);
return 0;
}
🌈3.函数递归
⚡3.1什么是递归
程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接
调用自身的
一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,
递归策略
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
以下先进行一个最简单的递归:
什么是栈溢出?这里先为大家进行一个简单的介绍,具体的讲解可以看我主页里《函数栈帧的创建与销毁》里面非常详细的介绍了函数、形式参数、实际参数的在内存中是如何存储的。
这里每当调用一次main()函数,就会在栈区内创建一块内存,如果一直调用,就会一直开辟内存直到把栈区内的用完,最后溢出,就造成了程序崩溃。
⚡3.2递归的两个必要条件
1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。
2.每次递归调用之后越来越接近这个限制条件。
🌊3.2.1递归练习(画图讲解)
1、接受一个整形值,按照顺序打印它的每一位!
void Print(int n)
{
if (n > 9)
{
Print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
int num = 0;
scanf("%u", &num);
Print(num);
return 0;
}
代码讲解
递归的意思就是:递归 、 回推
2、编写函数,不允许创建临时变量,求字符串长度。
我们知道,求字符串长度可以用strlen()函数,但是此题不允许创建临时变量,所以,我们采用递归方法模拟实现strlen()函数。
int count_ch(char* str)
{
if (*str != '\0')
{
return 1 + count_ch(str + 1);//这里的递归思路与上一题一摸一样
}
else
return 0;
}
int main()
{
char arr[] = "abcdef";
int ret = count_ch(arr);
printf("%d", ret);
return 0;
}
⚡3.3递归与迭代(循环)
🌊3.3.1练习1
采用递归和迭代求n的竭诚
//递归
int Num(int n)
{
if (n == 1)
{
return 1;
}
else if (n > 1)
{
return Num(n - 1) * n;
}
else
return 0;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Num(n);
printf("%d", ret);
return 0;
}
//迭代
int Num(int n)
{
int i = 1;
int ret = 1;
for (i = 1; i <= n; i++)
{
ret *= i;
}
return ret;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Num(n);
printf("%d", ret);
return 0;
}
🌊3.3.2练习2
计算斐波那契数列
//迭代
//斐波那契数列:1 1 2 3 5 8
int Fib(int n)
{
if (n <= 2)
{
return 1;
}
int a = 1;
int b = 1;
int c = 0;
while(n>=3)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
while((scanf("%d", &n))!=EOF)
{
int ret = Fib(n);
printf("%d", ret);
}
return 0;
}
//递归
int Fib(int n)
{
if (n > 2)
{
return Fib(n - 1) + Fib(n - 2);
}
else
return 1;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d", ret);
return 0;
}
在这里,递归和迭代都可以解决问题,但是以后在使用的时候,看哪一个更能高效的解决问题,在以上这个问题中,当计算的位数较大时,比如计算第50位斐波那契数列,那么使用递归就会花费很长时间,但是,使用迭代的话,效率就会高很多,所以应该合理的应用。
🌈4.数组作为 函数参数
往往我们在写代码的时候,会将数组作为参数传个函数,比如:我要实现一个冒泡排序(这里要讲算法思想)函数,将一个整形数组排序。
假如这里有一列数:5,9,4,2,7,1,0,8,6,3, 对于这一列数我们先要按照升序的方式将它排列的话,这里就用到了一种排序方式:冒泡排序
以下是冒泡排序的思路:
请看下面代码分析
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)//因为有10个元素,每趟排序解决一个数字,
//最后一个自动归位,不用再排,所以是sz-1个
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)//10个数字需要进行9对比较,排完一个数字,
//就不需要再动,剩8个数字进行7对比较所以是sz-1-i
{
if (arr[j] > arr[j + 1])//进行比较互换啊
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 5,9,4,2,7,1,0,8,6,3 };
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组里面有几个元素
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}