引入
关于函数,第一次听到这个名字应该是在数学学习时吧。而C语言中也有函数这一概念,不过此函数非彼函数。
函数的概念
函数的英语表达为function,这样我们就可以把函数理解成一个完成某项特定任务的一小段代码,这段代码在C语言中有着特定的写法和调用方法。
C语言的程序事实上是由无数个小的函数组合而成的,也就是说把一个大的任务分解成若干个较小的函数来完成。
函数的分类
(1)库函数
(2)自定义函数
库函数
库函数的概念
C语言标准中规定了C语言中的语法规则,但C语言并不提供库函数;C语言的国际标准ANSI C规定了一些常用的函数标准,称为标准库,不同的厂商根据C语言标准就给出了一系列函数的实现,这些函数称为库函数。
之前所学的printf、scanf都是库函数,这些函数都是现成的,学会后就可以直接使用。这些库函数在一定程度上提升了效率。
库函数的使用
库函数是在标准库中对应的头文件中声明的,所以库函数的使用,一定要包含对应的头文件,否则会出现一些问题。
自定义函数
函数的语法形式
函数的返回类型 函数名(形式参数){
}
{}括起来的是函数体
我们可以把函数联想成一个加工厂,工厂获得原材料通过加工得到产品。函数也是这样,函数获得参数,经过在函数内计算得出相关结果。
但是与工厂不同的是,工厂必须要有原材料才能有产品,而函数获得的参数可以是0个也可以是无数个。函数也可能没有计算结果,即没有返回值的情况。
由此我们可以对函数的语法有更深的理解
(1)函数的返回类型:用来表示函数计算结果的类型,但有时函数的返回类型可以是void,表示什么都不返回。
(2)函数名:函数名就和每个人的名字一样,有了名字方便称呼和区别,函数有了名字就方便调用,所以在给函数起名字的时候要是有意义的名字。
(3)形式参数:函数的参数可以是void。如果有参数,都要交代清楚参数的名字,类型和个数。
(4)函数体:函数体就是完成计算的过程
上面提到了参数和返回类型为0的情况,以下是一个例子
形参和实参
函数中的参数分为实参和形参
实参:
实际参数就是真实传递给函数的参数
形参:
如果我们定义了一个函数,不去调用的话 ,函数中的参数只是形式上存在的,不会向内存申请空间。形参只有再被调用 的过程中为了存放实参传递过来的值。才会向内存中申请空间,这个过程就是形参的实例化。
实参与形参的关系:
实参和形参各自是独立的内存空间,实参和形参的传递通常是单向的值传递,即从实参传递到形参。但是也存在通过指针等方式实现“双向传递”的情况。
单向值传递
当简单数据类型(如 int 、 float 等)作为实参传递给函数时,传递的是实参的值的副本。在函数内部对形参的修改不会影响到实参。例如:
#include <stdio.h>
void modifyValue(int num) {
num = 100;
}
int main() {
int a = 10;
modifyValue(a);
printf("a的值为:%d\n", a);
return 0;
}
上述代码中, main 函数中的 a 作为实参传递给 modifyValue 函数的形参 num ,在 modifyValue 函数中修改 num 的值为100,但不会影响 main 函数中 a 的值,输出仍为10。
指针实现“双向传递”的情况
当实参是指针类型时,传递的是指针变量的值(即地址) ,函数内部可以通过地址的访问和修改指针所指向的变量的值,从而达到修改实参的目的。例如:
#include <stdio.h>
void modifyValue(int *ptr) {
*ptr = 100;
}
int main() {
int a = 10;
int *p = &a;
modifyValue(p);
printf("a的值为:%d\n", a);
return 0;
}
在main函数中定义了一个变量a和指向a的指针p,将p作为实参传递给所定义的函数,在函数内部通过指针ptr修改了a的值
关于return的那些事
在函数中,我们会经常看到return语句,那你对return的了解有多少呢?
return语句的一般形式是
return 表达式 或者是 return (表达式)
(1)return后面可以是一个数也可以是一个表达式,如果是表达式的话则会先执行表达式,再返回表达式的值
(2)return后面也可以啥也没有,直接写return,这样的一般是返回类型是void的情况
(3)return语句执行后,函数彻底返回,后面的代码不会执行,可以认为它有强制结束的作用。这里需要和break语句区分开
从以上对比可以看出,break 能直接跳出循环语句,而return则是直接结束整个主函数的运行,后面不会打印c和i的值。
(4)当return返回的值和函数返回类型不一致时,系统自动将返回的值隐式转换为函数的返回类型
(5)函数的返回类型如果不写,将自动默认为int类型
如: Add (int a,int b)
我们在函数定义时没有写它的返回类型,那么它的返回类型将默认为int 类型
(6)如果有返回类型,但是没有写返回类型的值,那么函数的返回值是未知的
(7)return语句可以有多个,可以在函数体的任何位置,但是每次调用函数时只能有一个return语句被执行,只能有一个返回值。
(8)return 0表示函数正常退出,即当return语句提供了一个返回值时,这个值就成为函数的返回值。return 1表示异常退出,返回主调函数处理,继续向下执行出现异常或需要提前停止时用return-1表示。
好了,以上就是关于return的知识点,可能不太全面,欢迎各位进行补充学习
数组做函数的参数
我们在用函数解决有关问题时,难免会碰到含有数组的情况,这就不可避免的会将数组作为参数传递给函数。那么我们要如何处理呢?
假设我们现在定义一个函数对数组进行处理,打印出数组中的所有元素,这样的函数应该怎样定义和使用呢?
这里出现了数组作为参数时一个常见的错误。我们在进行传递时,使用的是a[0],a[0]表示的是数组a中的第一个元素,它是一个int类型的值。而我们使用所定义的函数时,实际上是将一个int类型的值作为第一个参数传递给Print函数。
但是Print函数所期望的第一个参数是一个int类型的数组,这样的传递会导致类型不匹配,无法按照我们所预期的那样处理数组中的元素。
所以在数组传参时只传数组名即可(在C语言中,数组名在大多数情况下会被自动转换为指向数组首元素的指针)
这样就可以完成预期功能了!
关于一维数组的形参,我们可以省去数组的大小,这样也不会出错,例如:
这两种写法都是对的。
下面来总结一下有关数组传参的几个重要知识:
(1)函数的形参个数要与实参个数相匹配
(2)函数的实参是数组,形参也可以写成数组形式的
(3)如果形参是一维数组,数组的大小可以省略不写
(4)如果形参是二维数组,行可以省略,但是列不可以省略
(5)数组传参时,形参是不会创建新的数组的,形参操作的数组和实参是同一个数组
函数的嵌套调用和链式访问
嵌套调用
函数的嵌套调用就是函数之间的相互调用。就好比我们玩乐高一样,每一个函数就像是一块小小的零件,只有相互配合才能搭建出精美的乐高玩具。函数的嵌套调用也是如此,正是因为函数之间有效的相互调用最后才写出了大型的程序。不过需要注意的是,函数的定义不可以嵌套。
一个简单的例子,我们现在想知道某一年的某一月有多少天,每一月的天数几乎是固定的,但是2月是个例外,那么我们首先必须确定是闰年还是非闰年,然后再进行判断。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int is_leap_year(int year) {
if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)) {
return 1;
}
else {
return 0;
}
}
int days_of_month(int year,int month) {
int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int day = days[month];
if (is_leap_year(year) && month == 2) {
day += 1;
}
return day;
}
int main() {
int month = 0, year = 0;
int days = 0;
scanf("%d%d", &year, &month);
days = days_of_month(year, month);
printf("%d", days);
return 0;
}
第一个函数,先判断是否是闰年
第二个函数就嵌套调用了第一个函数,得出想要的结果
链式访问
所谓的链式访问就是将一个函数的返回值作为另一个函数的参数,像链条一样将函数串起来 ,这就叫做函数的链式访问。
下面一printf函数为例,首先我门要知道printf函数返回的是什么。printf函数返回的是打印在屏幕上字符的个数
这里我们从后往前看,首先打印出来的是43,这是毋庸置疑的,因为这个时候打印在屏幕上的字符个数是2,所以第二个printf函数的返回值是2。到这里 屏幕上打印的字符个数就是1了,所以第一个printf函数的所接收到返回值为1,最终结果就是4321。