👏welcome to my blog
请留下你宝贵的足迹吧(点赞👍评论📝收藏⭐)
目录
函数的概念
C语言中的函数(function),可理解为子程序。就是⼀个完成某项特定的任务的一小段代码。这段代码是有特殊的写法和调用方法的。
C语⾔的程序其实是由无数个小的函数组合而成的,也可以说:⼀个大的计算任务可以分解成若干个较 小的函数(对应较⼩的任务)完成。同时⼀个函数如果能完成某项特定任务的话,这个函数也是可以 复⽤的,提升了开发软件的效率。
在C语言中我们⼀般会见到两类函数:
• 库函数
• 自定义函数
1.库函数
1.1标准库和头文件
C语⾔标准中规定了C语⾔的各种语法规则,C语⾔并不提供库函数;C语⾔的国际标准ANSIC规定了⼀ 些常用的函数的标准,被称为标准库,那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现。这些函数就被称为库函数。
printf 、 scanf 都是库函数,库函数也是函数,不过这些函数是现成的,可以直接使用。
各种编译器的标准库中提供了⼀系列的库函数,这些库函数根据功能的划分,在不同的头文件中进行声明。
1.2库函数的使用方法
库函数的学习和查看工具如:
C/C++官方的链接: https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
举例:sqrt
double sqrt (double x);
//sqrt 是函数名
//x 是函数的参数,表⽰调⽤sqrt函数需要传递⼀个double类型的值
//double 是返回值类型 - 表⽰函数计算的结果是double类型的值
1.2.1 功能
Computesquareroot计算平⽅根
Returnsthesquarerootofx.(返回平⽅根)
1.2.2 头⽂件包含
库函数是在标准库中对应的头⽂件中声明的,所以库函数的使⽤,务必包含对应的头⽂件,不包含可能会出现⼀些问题。sqrt包含在<math.h>头文件中
1.2.3 使用
2.自定义函数
自定义函数根据需要自己设计和实现函数,自己使用
2.1 函数的语法形式
其实自定义函数和库函数的语法形式是⼀样的,形式如下:
ret_type fun_name(形式参数)
{
}
• ret_type 是函数返回类型是⽤来表⽰函数计算结果的类型,有时候返回类型可以是 void ,表⽰什么都不返回
• fun_name 是函数名 是为了⽅便使⽤函数;就像⼈的名字⼀样,有了名字⽅便称呼,函数有了名字⽅便调 ⽤,所以函数名尽量要根据函数的功能起的有意义。
• 括号中放的是形式参数 函数的参数就相当于⼯⼚中送进去的原材料,函数的参数也可以是 void ,表⽰函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。
• {}括起来的是函数体函数体就是完成计算的过程。
2.2 函数的举例
//写⼀个加法函数,完成2个整型变量的加法操作。
//函数的定义
int Add(int x, int y)//返回的z是int类型的
{
int z=x + y;
return z;//将计算结果返回
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
//函数的调用
int c = Add(a, b);//将a和b的值传给函数,将其计算结果赋给c
printf("c=%d\n", c);
return 0;
}
//写一个函数,只需打印“呵呵”就行
void Print()//函数不需要传参(也可以在括号内加上void),也不需要返回值
{
printf("呵呵\n");
}
int main()
{
Printf();//调用函数
return 0;
}
3.形参和实参
//写一个函数,求两个整数的较大值
int get_max(int a, int b)//函数定义时函数名后括号中的参数是形式参数,简称形参
{
if (a > b)/*直接return(x>y?x:y)更简单*/
return a;
else
return b;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int m = get_max(a, b);//函数调用时传递给函数的参数是实际参数,简称实参
printf("%d\n", m);
return 0;
}
实际上,如果只是定义了函数,而不去调用的话,函数的参数只是形式上存在的,不会向内存申请空间,不是真实存在的,所以叫形式参数。形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化。
结论:
1.实参和形参是不同的内存空间,对形参的修改不会影响实参
2.形参是实参的一份临时拷贝(x,y只是在函数调用时使用了,调用结束就会被回收)
4.return语句
return语句使⽤的注意事项。
• return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执行表达式,再返回表达式 的结果。
• return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
• return返回的值和函数返回类型不⼀致,系统会自动将返回的值隐式转换为函数的返回类型。
• return语句执行后,函数就彻底返回,后边的代码不再执⾏。
• 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
//写一个函数,判断奇偶
int is_odd(int m)
{
return m % 2;//表达式
}
int main()
{
int n = 0;
scanf("%d", &n);
int a=is_odd(n);
if (a = 1)
printf("是奇数");
else
printf("是偶数");
return 0;
}
//写一个函数,依次打印数
void Print(int m)
{
if (m <= 0)
return;//如果满足if条件return会直接跳出函数
else
{
int i = 1;
for (i = 1; i <= m; i++)
printf("%d ", i);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
Print(n);
return 0;
}
int test()
{
if (1)
return 3.5;
else
return 5.5;
}
int main()
{
int r = test();
printf("%d\n", r);//打印出3
return 0;
}
5. 数组做函数参数
数组传参的几个重点知识:
• 函数的形式参数要和函数的实参个数匹配
• 函数的实参是数组,形参也是可以写成数组形式的
• 形参如果是⼀维数组,数组大小可以省略不写
• 形参如果是⼆维数组,行可以省略,但是列不能省略
• 数组传参,形参是不会创建新的数组的
• 形参操作的数组和实参的数组是同⼀个数组
6.嵌套调⽤和链式访问
6.1 嵌套调用
嵌套调用就是函数之间的互相调⽤,每个函数就像⼀个乐⾼零件,正是因为多个乐高零件互相无缝地配合才能搭建出精美的乐高玩具,也正是因为函数之间有效的互相调用,最后写出来了相对大型的 程序。
函数中不可再定义一个其他的函数,但函数可以嵌套调用
//计算某年某月有多少天
//闰年的判断:1.能被4整除,并且不能被100整除;2.能被400整除
int is_leap_year(int y)
{
if (((y %4 == 0) && ((y %100) != 0) )|| (y %400 == 0))
return 1;
else
return 0;
}
int get_days_of_month(int y, int m)
{
int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
//对应数组下标依次为0,1,2,3,4,5,6,7,8,9,10,11,12把天数与月份对应起来了
/*也可以使用switch语句
* int day=0;
* switch(m)
* {
* case 2:
* day=28;
* break;
* case 1:
* case 3:
* case 5:
* case 7:
* case 8:
* case 10:
* case 12:
* day=31;
* break;
* case 4:
* case 6:
* case 9:
* case 11:
* day=30;
* break;
*/
int day = days[m];
if (is_leap_year(y) && m == 2)//嵌套调用
day += 1;
return day;
}
int main()
{
int year = 0;
int month = 0;
scanf("%d %d", &year, &month);
int d=get_days_of_month(year, month);
printf("%d\n", d);
return 0;
}
6.2 链式访问
链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问。
int main()
{
printf("%zd\n", strlen("abc"));//链式访问
return 0;
}
7.函数的声明和定义
7.1 单个⽂件
//写⼀个函数判断⼀年是否是闰年
//程序从前往后执行,若自定义函数是在main函数之后,则应提前声明自定义函数
int is_leap_year(int y);//函数的声明,声明函数名,参数,返回类型
int main()
{
int x = 0;
scanf("%d", &x);
int r = is_leap_year(x);
if (r == 1)
printf("Yes\n");
else
printf("No\n");
return 0;
}
//函数的定义
int is_leap_year(int y)
{
if (((y % 4 == 0) && ((y % 100) != 0)) || (y % 400 == 0))
return 1;
else
return 0;
}
7.2 多个⽂件
c – 源文件
存放函数的定义
.h – 头文件
存放函数的声明,类型的声明
7.3 static和extern
static 和extern 都是C语⾔中的关键字。
static是静态的的意思,可以⽤来:
• 修饰局部变量
• 修饰全局变量
• 修饰函数
extern 是用来声明外部符号的。
补充📚:作用域(scope)是程序设计概念,通常来说,⼀段程序代码中所而到的名字并不总是有效(可用)
的,而限定这个名字的可而性的代码范围就是这个名字的作用域。
- 局部变量的作用域是变量所在的局部范围。
- 全局变量的作用域是整个工程(项目)。
生命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。 - 局部变量的生命周期是:进⼊作用域变量创建,生命周期开始,出作用域生命周期结束。
- 全局变量的生命周期是:整个程序的生命周期。
7.3.1 static修饰局部变量:
结论:static修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,本 来⼀个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变 量和全局变量是⼀样的,生命周期就和程序的生命周期⼀样了,只有程序结束,变量才销毁,内存才回收。但是作用域不变的。
使用建议:未来⼀个变量出了函数后,我们还想保留值,等下次进⼊函数继续使用,就可以使用static修饰。
7.3.2 static修饰全局变量
源文件编译时是单独编译的
teat1.c中的全局变量a是具有外部链接属性的,使用extern(用来声明外部符号)可使其在test.c文件中被打印,也可被重新赋值使用
若teat1.c中的a被static修饰后外部链接属性就变成了内部链接属性,a就只能在test1.c中使用,使用extern后也不可在test.c文件中使用(这里就不再放图了,可自行测试)
使用建议:如果⼀个全局变量,只想在所在的源文件内部使用,不想被其他文件发现,就可以使用static修饰。
7.3.3 static修饰函数
与全局变量一样函数默认也是具有外部链接属性,但是被static修饰后变成了内部链接属性,使得函数只能在自己所在源⽂件内部使用。其他源⽂件即使声明了,也是无法正常使用的。(这里就不再放图了,可自行测试)
使用建议:⼀个函数只想在所在的源文件内部使用,不想被其他源文件使用,就可以使用static修饰。
8.函数递归
思想:
把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较小的子问题来求解;直到子问题不能再 被拆分,递归就结束了。所以递归的思考方式就是把大事化的小过程。
递归中的递就是递推的意思,归就是回归的意思,
限制条件:
递归在书写的时候,有2个必要条件:
• 递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。
• 每次递归调用之后越来越接近这个限制条件。
注意👀
void test(int n)
{
printf("%d ", n);
if (n < 10000)//存在限制,并且每次递归都更接近限制条件
//但由于10000太大,递归层次太深,导致栈溢出
test(n + 1);
}
int main()
{
test(1);
return 0;
}
/*举例1:求n的阶乘
⼀个正整数的阶乘(factorial)是所有⼩于及等于该数的正整数的积,并且0的阶乘为1。
⾃然数n的阶乘写作n!(n!=n*(n-1)!)
题⽬:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。*/
int Fact(int n)
{
if (n == 0)
return 1;
else
return n * Fact(n - 1);//函数的递归
}
int main()
{
int n = 0;
scanf("%d", &n);
int r = Fact(n);
printf("%d\n", r);
return 0;
}
图解
图解
8.1递归和迭代
斐波那契数
/*求第n个斐波那契数*/
int count = 0;
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
int r = Fib(n);
printf("%d\n", r);
printf("count=%d\n", count);
return 0;
}
当我们输⼊n为50的时候,需要很长时间才能算出结果,这说明递归的写法是非常低效的,但如果我们使用迭代的方式(通常就是循环的⽅式),就会高效很多
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n >= 3)
{
c = a + b;
a = b;
b = c;//实现了将两个数相加的和作为下次相加的因子
n--;
}
return c;
}
int main()
{
int n=0;
scanf("%d", &n);
int d = Fib(n);
printf("%d\n", d);
return 0;
}
💓期待你的一键三连,你的鼓励是我创作的动力之源💓