绪论
书接上回,上回我们将C语言中的《控制语句》进行了详细的书写,这次我将在第二章写道《函数》,他不仅仅是名字和数学上的函数一样,其内涵其实是一样的!希望你可以通过我的这篇文章深刻的认识到函数的具体内容 (建议电脑观看) 。
所以安全带系好,发车啦(建议电脑观看)。
思维导图:
要XMind思维导图的话可以私信哈
目录
1.函数是什么?
2.库函数
3.自定义函数
4.函数的参数
4.1 实际参数(实参)
4.2:形式参数(形参)
5.函数的调用
5.1传值调用:
5.2传址调用:
5.3基本练习:
6.函数的嵌套调用和链接访问
6.1:嵌套调用
6.2 链接访问
7.函数的声明和定义
7.1函数的声明:
7.2函数的定义:
8.函数的递归
9.迭代
1.函数是什么?
在维基百科中函数又被称为“子程序”:
在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method, subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组 成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
而在C语言中函数分成了两类:库函数,自定义函数
附:主函数就是以 int main() ,大括号,return 0 组成的他在一个程序中是必须的,也是唯一的(只能有一个)
int main()
{
语句;
return 0;
}
2.库函数
知识点:
库函数就是我们在前面文章代码中直接有用到的,打印函数(printf),输入函数(scanf),字符串比较函数(strcmp),这些都是在日常编写程序时常常会用到的函数,也就是我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。
细节点:
注:既然不是自己写的,故在使用时就需要去'引'头文件#include....
如当你要打印(省略主函数)
#include<stdio.h>
printf("yyds");
像常用库函数还有如:
strlen(求字符串长度),字符串比较函数(strcmp) :
memset()将数组前几位变成某一值 : #include<string>
system()调用VS自带的指令: #include<windows.h>
time()时间戳:#include<time.h>
srand() ,rand() 生成随机数: #include<stdlib.h>
pow()求次方,sqrt()开平方:#include<math.h>
.....
C语言中自己带的库函数还有很多很多若需要系统的了解其内涵则:
这推荐给你一个软件MSDN,和网站cplusplus.com(进入到旧版本),个人还是较喜欢用MSDN若有需要可以私信我。
使用方法:
MSDN:
1.如何搜索
2.该看的关键部分
参数形式,头文件,返回值
附:
在开始会有对其功能的介绍
3.可以根据情况康康
下面还有:
Parameters(参数),Remarks(备注),example(举例)
cplusplus差不多,你可以根据我给的链接直达,在最上面搜索框中搜索
3.自定义函数
自己定义的函数,通过和库函数相同的方法,创建一个函数,传入参数,并返回一个值。程序员这个职业作用也体现于此,否则库函数如果可以作任何我们想完成的事,那肯定是不可能的(那还要程序员干嘛?),所以当遇到一些复杂独特的逻辑时,库函数就无法完成这时就一定需要程序员自己写一个函数来完成特定的任务。
他的大概语法是:
return_type function_name(para1,...) //return_type返回类型,function_name函数名称 //para1函数参数 { 语句; }
附:
void test (void)//不要参数,当有参数传进则会提示警告
{
...;
}
int main()
{
test(10);
}
下面以一些题目来展示自定义函数
基本练习:
1.找最大值
#include<stdio.h> int max(int a,int b) { return (a>b?a:b);//条件操作符先对1表达式进行判断若成立则进入2表达式(a)否则进入3表达式 } int main() { int x = 0; int y = 0; int max = max(x,y); printf("%d",max); return 0; }
4.函数的参数
4.1 实际参数(实参)
简单来说就是函数传进去的参数,
如:max()中传进去的x,y 即max(x,y)
附:实参可以是常量,变量,表达式,函数,地址....,但一定得是一个具体的值
4.2:形式参数(形参)
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(具体的实例化:分配内存单元这我会再写一篇博客...),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了(类似局部变量),因此形式参数只在函数中有效。
即max()中的max(int a, int b);
附:传值调用时的形参实例化之后其实相当于实参的一份临时拷贝。
5.函数的调用
知识点:
5.1传值调用:
传值调用时函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
5.2传址调用:
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操 作函数外部的变量(实参)(通过指针找到内存空间)
以下例子为例:
#include <stdio.h> void Swap1(int x, int y)//因为这里是传值调用则无法改变实参 { int tmp = 0; tmp = x; x = y; y = tmp; } void Swap2(int *px, int *py)//而此处因建立了联系则可以完成交换 { int tmp = 0; tmp = *px; *px = *py; *py = tmp; } int main() { int num1 = 1; int num2 = 2; Swap1(num1, num2); printf("%d %d\n", num1, num2); Swap2(&num1, &num2); printf("%d %d\n", num1, num2); return 0; }
5.3基本练习:
用函数写一个二分查找(折半查找),递增的数组
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int Cheak(int* ar, int lef , int ri , int ke )//在形参部分起的名称可以和实参一样也可以不一样但尽量有意义 { while (lef <= ri) { int mid = (ri + lef) / 2; if (ke < *(ar+mid)) { ri = mid - 1; } else if (ke > *(ar + mid)) { lef = mid + 1; } else { return *(ar + mid); } } return 0; } int main() { int arr[] = { 1,3,5,7,9,11,13,15,17,19}; int k = 0; int l = 0; printf("输入一个数,我来帮你找到它"); scanf("%d", &k); printf("\n"); int r = sizeof (arr) /sizeof(arr[1]) -1; int num = Cheak(arr, l, r, k); // arr为首元素的地址,虽然如此但在形参接收时也可以直接用int arr[] 代替int * ar ,此处就直接用第二种了 //但在初学时尽量用第一种,int arr [1] = *(arr + 1) 注意点+1一定要加括号 if (num == 0) printf("不在范围内\n"); else printf("找到了 k = %d", num); return 0; }
6.函数的嵌套调用和链接访问
知识点:
6.1:嵌套调用
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。也就是在函数内在写一个函数,并调用。
如下代码
#include <stdio.h> void new_line() { printf("hehe\n"); } void three_line() { int i = 0; for(i=0; i<3; i++) { new_line();//在函数内再次调用函数 } } int main() { three_line();//主函数内调用的第一个函数 return 0; }
附:函数可以嵌套调用,但是不能嵌套定义(不能同时在一处创建两个函数块)。
6.2 链式访问
把一个函数的返回值作为另外一个函数的参数。
#include <stdio.h> int main() { printf("%d", printf("%d", printf("%d", 43))); return 0; }
printf:
上面大概意思是:这个函数返回打印的字符个数,如果发生错误则返回负值。
故上面的结果应该是----评论区
7.函数的声明和定义
7.1函数的声明:
当check函数放在了主函数后边时,就会出现一个警告Check未定义,因为程序是从上往下一步步看的。故此时若不想有警告则可以声明(或者将函数定义放在调用之前面(此处主函数之前)
声明的写法:告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。即 可以大概写成 int Add(int , int ) ,参数内有无名字都可以
附:一般可以把函数的声明放在头文件中,当你要调用的时候在包含一个自己的头文件即可#include<game.h>(此处可以等我后面更到用代码写小游戏时看出),即实现分模块开发,这样还可以利于假如想保留你的知识产权就可以进行代码的隐藏
7.2函数的定义:
自己写的函数,里面有具体的实现,交代函数功能
int Add(int a ,....)
{
code;
}
8.函数的递归
知识点:
递归:一种程序调用自身的编程技巧
这种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解, 递归策略,即把大事化小,进行着重复的过程
细节点:
对于递归:因为如果用了递归他就会持续的调用自己,这个方法在每次的调用中都会开辟一个空间给这个函数(有就是堆栈)这种方法可以虽然可以大事化小,但同时也存在隐患(无限的开辟空间就可能会导致栈没占满而导致的栈溢出),所以我们在使用递归方法时要限制一个量然他不会无限递归下去(也就是一个限制条件),同时在每次递归后都要不断的接近这个限制条件
附:递归虽好,但也未必一定每次写代码都要用到它,只要自己写的代码简洁高效就是最好的代码,即(要注意递归的使用并不如何地方都适用)
递归的优点和缺点:
1.许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
下面我们用一些题目来对递归这个用一个更深刻的认识
习题练习:
输入一个整形,按照顺序打印他的每一位
如输入1234,打印1 2 3 4
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> 1. void leave(int a) { if (a / 10 != 0) //当a / 10 不等于0说明他是一个大于10的数 { leave(a / 10); } printf("%d ", a % 10);//打印 //当递归回来时就会下来打印 } #include<stdio.h> int main() { int a = 0; scanf("%d", &a); leave(a); return 0; } 2. int leave(int a) { if (a / 10 == 0)//先设置限制条件,到个位 { printf("%d ", a % 10); return 0;//当进到限制内就会开始返回 } leave(a / 10); printf("%d ", a % 10);//返回后就会下来打印 当第一次返回时会变12余(莫)2并打印 return 0;//此处可以不写但会有一个警告而已 } #include<stdio.h> int main() { int a = 0; scanf("%d", &a); leave(a); return 0; }
用递归实现strlen
int Strlen(char a[]) { if (*a == '\0') return 0; else return 1 + Strlen(a+1);//a+1就会时指针向后跳一个字符,a是首元素地址,故 } int main() { char a[] = "abcd"; int n = Strlen(a); printf("%d", n); return 0; }
递归实现裴波那契数(生兔子)
1 1 2 3 5 8 13 ....
int Fac(int x) { if (x <= 2)//因为前俩个无法相加 { return 1;//当减到小于等于2时就直接为1并返回 } else { return Fac(x - 2) + Fac(x - 1);//加上前两个数 } } int main() { int i = 0; scanf("%d", &i); int n = Fac(i); printf("%d", n); return 0; }
如:当输入第3为时,Fac(x-2 == 1) == 1 | Fac(x-1 == 2)== 1 因为x = 1, 2,故会退出递归
而当输入为5时,第一次 x-2 == 3 ,x-1 == 4 ,再进行x-2,与x-1他就会继续递归下去直到x-1,x-2 他们小于等于2时才会跳出
那当为6时他们第一次x-2 == 4 x-1 == 5 , 然后4 和 5 要继续分, 4变成 2 3,5 变成4,3 ,然后能分的还要继续递归。
从上面这些较小的数中就已经可以发现,这样的递归是不够效率的他的步骤不断的重复进行。
这样的话,当输入一个较大的数值时,他就一定会因为空间的不足,而栈溢出出错。
故就需要考虑改变把递归,变成迭代了
9.迭代
大概意思也就是一种非递归的形式。
接上裴波那契数递归会导致栈溢出,那么就改成迭代形式即:
int Fac(int x) { int a = 1; int b = 1; int sum = 0; for (; x > 2; x--) { sum = a + b; a = b; b = sum; } return sum; } int main() { int i = 0; scanf("%d", &i); int n = Fac(i); printf("%d", n); return 0; }
本章完。预知后事如何,暂听下回分说。