✨✨ 欢送大家来到贝蒂大讲堂✨✨
养成好习惯,先赞后看哦~
所属专栏:C语言学习
贝蒂的主页:Betty‘s blog
1. 函数的概念
在数学中咱们就晓得了函数这个概念,而C语言同样引入了函数这个概念,那C语言的函数到底是什么样的呢?
在C语言中,函数也叫子程序,它是一段能够重复使用的代码,用来独立地实现某个性能,它能够接管用户传递的数据,也能够不接管。
2. 函数的分类
C语⾔的程序其实是由⽆数个⼩的函数组合⽽成的,也能够说:⼀个⼤的计算工作能够分解成若⼲个较⼩的函数(对应较⼩的工作)实现。同时⼀个函数如果能实现某项特定工作的话,这个函数也是能够复⽤的,晋升了开发软件的效率。而C语言函数大抵能够分为两类:库函数与自定义函数
2.1 库函数
C语⾔规范中规定了C语⾔的各种语法规定,然而C语⾔并不提供库函数;C语⾔的国际标准ANSIC规定了⼀些常⽤的函数的规范,被称为规范库,那不同的编译器⼚商依据ANSI提供的C语⾔规范就给出了⼀系列函数的实现。这些函数就被称为库函数。
比如说咱们后面学习的printf和scanf都是库函数,它的头文件时<stdio.h>。然而咱们明天的主题不是解说库函数,如果大家想学习其余库函数,能够去C/C++官网学习
2.2 自定义函数
自定义函数是由程序员自主设计的函数,和库函数一样有函数名、返回类型、形式参数等,明天咱们的指标就是学习如何写自定义函数。
3. 自定义函数
3.1 语法
dataType functionName(形式参数)
{
//body
}
- dataType 是返回值类型,它能够是C语言中的任意数据类型,例如 int、float、char 等。
- functionName 是函数名,它是标识符的一种,命名规定和标识符雷同。函数名前面的括号
( )
不能少。 - body 是函数体,它是函数须要执行的代码,是函数的主体局部。即便只有一个语句,函数体也要由
{ }
突围。 - 如果有返回值,在函数体中应用 return 语句返回。return 进去的数据的类型要和 dataType 一样。
3.2 实例
通过函数的定义咱们能够写一个简略的加法函数:
#include<stdio.h>int Add(int x, int y)//函数名Add//两个形式参数x,y,类型都是int//返回类型也是int{ int c = 0; c = x + y; return c;//返回x+y的和}int main(){ int a, b; 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;//间接返回}
- 通过函数咱们就能够节俭大量工夫,每次调用这个性能只需复用该函数即可。
3.3 作用域与生命周期
作用域和生命周期是C语言中一个特地重要的概念,分明了解这个概念能帮忙咱们写出更好的程序,缩小bug的产生。
作⽤域(scope)是程序设计概念,通常来说,⼀段程序代码中所⽤到的名字并不总是无效(可⽤的,⽽限定这个名字的可⽤性的代码范畴就是这个名字的作⽤域。
- 局部变量的作⽤域是变量所在的部分范畴。
- 全局变量的作⽤域是整个⼯程(项⽬)。
- 以下面加法函数以列,x,y的作用域就是加法函数。当加法函数执行完结,它就会被销毁。
⽣命周期指的是变量的创立(申请内存)到变量的销毁(发出内存)之间的⼀个时间段。
- 局部变量的⽣命周期是:进⼊作⽤域变量创立,⽣命周期开始,出作⽤域⽣命周期完结。
- 全局变量的⽣命周期是:整个程序的⽣命周期。
- 以下面加法函数以列,x,y的生命周期就是其进入函数创立到变量销毁的时间段。
4. 函数的参数
在函数使⽤的过程中,把函数的参数分为,理论参数和形式参数。上面我仍将以加法函数举例:
#include<stdio.h>int Add(int x, int y)//函数名Add//两个形式参数x,y,类型都是int//返回类型也是int{ int c = 0; c = x + y; return c;//返回x+y的和}int main(){ int a, b; scanf("%d%d", &a, &b); int ret = Add(a, b);//加法函数 printf("两个数和为%d\n", ret); return 0;}
4.1 理论参数
顾名思义,理论参数就是理论存在的参数,也就是主函数main调用函数时传给它的参数,也就是下面主函数中的a,b。
4.2 形式参数
同理,形式参数其实就是模式上的参数,也就是下面加法函数Add中的x,y。它并不是理论存在的,在未应用时并不会向内存申请空间,形式参数只有在函数被调⽤的过程中为了寄存实参传递过去的值,才向内存申请空间,这个过程就是模式的实例化。
4.3 实参加形参的关系
在介绍实参和形参关系时,咱们先看看上面这段代码
#include<stdio.h>void swap(int x, int y)//返回类型为void示意不返回值{ int temp = 0;//定义一个长期变量 temp = x;//把x的值赋给temp x = y;//把y的值赋给x y = temp;//把temp的值赋给y,实现替换操作}int main(){ int a = 0; int b = 0; scanf("%d %d", &a, &b); printf("替换前:a=%d,b=%d\n", a, b); swap(a, b);//替换函数 printf("替换后:a=%d,b=%d\n", a, b); return 0;}
那么替换后的输入是多少呢~
为什么替换的值还是不变呢,咱们能够通过调试来察看一下
通过调试,咱们发现a与x,b与y只是在数值上雷同,他们地址是齐全不同的,他们在两个独立的内存空间互不影响,对x,y替换基本不会扭转a,b的值。并且函数在调用实现之后,函数所申请的内存空间会偿还零碎~
所以咱们总结实参加形参的关系是:形参只是实参的⼀份长期拷⻉,对形参扭转基本不会影响实参
4.4 数组做参数
咱们后面学过的数组天然是也能作为函数的参数来应用,上面是一个具体实例:
void print_arr(int arr[], int sz)//打印数组{ int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n");}
- 其中arr是整个数组,sz是数组元素的个数
5. 嵌套调用和链式拜访
5.1 函数的嵌套调用
咱们在后面曾经学习了抉择构造和循环构造的嵌套应用,那函数嵌套调用又是怎么的呢。其实,简略来说,就是函数中也能够调用函数
举个例子,让咱们看看上面这段代码
void print2(){ printf("hello world\n");}void print1(){ print2();//调用print2函数}int main(){ print1();//调用print1函数 return 0;}
- C语言函数反对嵌套调用,然而不反对嵌套定义。
5.2 函数的链式拜访
链式拜访简略来说就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式拜访。
举例:
这是strlen的函数申明:size_t strlen(const char *str)
#include <stdio.h>int main(){ printf("%d\n", strlen("abcdef")); //依据strlen的返回值,咱们就能够间接将其作为printf的参数 //这就是一种简略的链式拜访 return 0;}
6.函数的定义与申明
6.1 函数的定义
函数的定义就是指函数具体的实现过程,交代函数具体性能的实现
比方咱们后面写的加法函数具体实现的过程就是函数的定义
int Add(int a, int b){ return a + b;//间接返回}
然而特地留神函数不能嵌套定义
int add(int x, int y)//加法函数{ return x + y; void print() { ....;//嵌套定义error }}
6.2 函数的申明
不晓得大家留神到一个细节没有,就是贝蒂在写函数时候,会把函数放在主函数之前,那如果放在主函数之后会怎么样呢,让咱们试验一下吧
int main(){ int a, b; scanf("%d%d",&a,&b); int ret = Add(a, b); printf("%d", ret); return 0;}int Add(int x, int y){ return x + y;}
一运行就会报这个正告,这个正告的意思就是说Add函数未定义,那为什么说Add未定义呢,原来是程序从上往下进行的,在遇到Add函数之前,会先执行主函数中的Add,此时程序基本不晓得Add的含意,所以会报正告。
那如何打消这个正告呢,其实我只须要加一个申明,让程序提前晓得有这个函数。
int Add(int x, int y);//函数申明int main(){ int a, b; scanf("%d%d",&a,&b); int ret = Add(a, b); printf("%d", ret); return 0;}int Add(int x, int y){ return x + y;}
int Add(int , int );//申明写出这样也是能够的
- 函数的调⽤⼀定要满足,先申明后使⽤是,而函数的定义也是⼀种非凡的申明,所以如果函数定义放在调⽤之前也是能够的。
7. 函数的迭代与递归
7.1 函数的迭代
「迭代 iteration」是一种反复执行某个工作的控制结构。在迭代中,程序会在满足肯定的条件下反复执行某段代码,直到这个条件不再满足。咱们学习过的while,for,do-while循环构造就是一种迭代。
7.2 函数的递归
「递归 recursion」是一种算法策略,通过函数调用本身来解决问题。它次要蕴含两个阶段。
- 递:程序不断深入地调用本身,通常传入更小或更简化的参数,直到达到“终止条件”。
- 归:触发“终止条件”后,程序从最深层的递归函数开始逐层返回,汇聚每一层的后果。
递归的三个因素:
- 终止条件:用于决定什么时候由“递”转“归”。
- 递归调用:对应“递”,函数调用本身,通常输出更小或更简化的参数。
- 返回后果:对应“归”,将以后递归层级的后果返回至上一层。
7.3 递归问题
(1) 问题一
利用递归计算1+2+3+4+....+n
int recur(int n){ // 终止条件 if (n == 1) { return 1; } // 递:递归调用 return n + recur(n - 1);}
(2) 问题二
输⼊⼀个整数m,打印这个依照程序打印整数的每⼀位。
⽐如:
输⼊:1234 输入:1 2 3 4
输⼊:520 输入:5 2 0
#include <stdio.h>void print(int n){ if (n > 9) { print(n / 10); } printf("%d ", n % 10);}int main(){ int n = 0; scanf("%d", &n); print(n); return 0;}
代码剖析:
void print(int n)//输出1234{ if (n > 9)//1234大于9 { print(n / 10);//将123传入下个递归 } printf("%d ", n % 10);//归的第四步,输入4}void print(int n)//传入123{ if (n > 9)//123大于9 { print(n / 10);//将12传入下个递归 } printf("%d ", n % 10);//归的第二步,输入3}void print(int n)//传入12{ if (n > 9)//12大于9 { print(n / 10);//将1传入下个递归 } printf("%d ", n % 10);//归的第一步,输入2}void print(int n)//传入1{ if (n > 9)//1不大于9 { print(n / 10); } printf("%d ", n % 10);//输入1,开始‘归’}