内容提要
- 函数
- 函数的概述
- 函数的分类
- 函数的定义
- 形参和实参
- 函数的返回值
函数
函数的概述
-
**函数:**实现一定功能的,独立的代码模块,对于函数的使用,一定是先定义,后使用。
-
使用函数的优势:
①我们可以通过函数提供功能给别人使用。当然我们也可以是使用别人提供的函数,减少代码量。
②借助函数可以减少重复性代码。
③实现结构化(模块化:C语言中的模块其实就是多文件+函数)程序设计思想。
关于结构化设计思想将大型的任务功能划分为相互独立的小型的任务模块来设计(多文件+函数)
-
函数是C语言程序的基本组成单元:
C语言程序必须包含一个main函数,可以包含零个或多个其他函数。
函数的分类
-
按来源分:
- **库函数:**C语言标准实现的并提供使用的函数,如:scanf()、printf()、fgets()、fputs()、strlen()…
- **自定义函数:**需要程序员自行实现,开发中大部分函数都是自定义函数。
-
按参数分:
- **无参函数:**函数调用时,无需传递参数,可有可无返回值,如:show_all();
- **有参函数:**函数调用时,需要参数传递数据,经常需要配套返回值来使用,如:printf(“%d\n”,12);
-
按返回值分:
- **有返回值函数:**函数执行后返回一个值,如:if(scanf(“%d”, &num) != 1)
- **无返回值函数(void):**函数字仅执行操作,不返回值
-
从函数调用的角度:
-
**主调函数:**主动去调用其他函数。(main函数只能作为主调函数)
-
**被调函数:**被其他函数调用的函数。
-
举例:
//主调函数 int main() { //被调函数 printf("hello world!\n"); }
很多时候,尤其是对于自定义函数,一个函数可能既是主调函数,又是被调函数。
int fun_b() { printf("函数B\n"); } // 主调函数 int fun_a() { printf("函数A\n"); // 被调函数 fun_b(); } int main() { //被调函数 fun_a(); }
以上案例中,fun_a()对于fun_b()来说是主调函数,同时对于main()函数来说,它又是被调函数。
-
函数的定义
定义
语法:
[返回类型] 函数名([形参列表]) --函数头 | 函数首部
{
函数体语句; --函数题: 整个{}包裹的内容都属于函数体,{}不能省略
}
函数头:
- **返回类型:**函数返回值的类型
- **函数名:**函数的名称,遵循标识符命名(不能以数字开头,只能包含字母、数字、下划线。建议:小写+下划线,举例:
show_al1()
,或者小驼峰命名法,第一个单词首字母小写,其他单词首字母大写,举例:showAll()
) - **形参列表:**用于接收主调函数传递的数据,如果有多个参数,使用
","
分隔,且每个形参都需要指明类型。
小贴士:
①形参列表(被调函数):主调函数给被调函数传递数据:主调函数→被调函数
②返回类型(被调函数):被调函数给主调函数返回数据:被调函数→主调函数
通过生活中的案例理解函数调用:
假设:饮料店的工作人员通过榨汁机榨取新鲜果汁
理解:
工作人员:主调函数
榨汁机:被调函数
水果:传递的参数
果汁:函数的返回值
工作人员向榨汁机放入一个水果:主调函数调用被调函数,并传递数据
工作人员用杯子接收榨汁机榨出的果汁:主调函数接受被调函数返回的数据
说明:
-
函数的返回类型:就是返回值的类型,两个类型可以不同,但是必须能够进行转换,举例:
double fun_a() //函数的返回类型是 double { return 12; //函数的返回值是12,其对应的类型是int(return后面跟的是返回值) }
以上代码可以理解为:将int类型的12赋值给一个double类型的匿名变量(int→double)此时属于隐式转换 正确
int fun_a() //函数的返回类型是int { return 12.5; //函数的返回值是int }
以上代码可以理解为:将double类型的12.5赋值给一个int类型的匿名变量(double→int)舍弃小数点后的数据。
案例:
#include <stdio.h> double fun_a() { return 12;// 就是将int类型的12赋值给double类型的匿名变量 int --> double } int fun_b() { return 12.5;// 就是将double类型的12.5赋值给int类型的匿名变量 double --> int 此时会舍弃掉小数部分 } double fun_c() { return 12.5; // 就是将double类型的12.5赋值给double类型的匿名变量 double --> double } int main(int argc,char *argv[]) { // 接收函数返回值,函数返回什么类型,就用什么类型接 double result1 = fun_a();// 主调函数使用double来接收被调函数返回的double,double --> double printf("%lf\n",result1); int result2 = fun_b(); // 主调函数使用int来接收被调函数返回的int,int --> int printf("%d\n",result2); int result3 = (int)fun_c(); // 主调函数使用int来接收被调函数返回的double,int --> (int)double printf("%d\n",result3) return 0; }
-
在C语言中无返回值时应明确使用void类型,举例:
void test() //此时这个函数,没有返回值,也就是它的返回只是retun; { ptintf("hello\n"); } //下面写法等价于上面写法 void test() { return; //一般,这个return是省略不写的 }
-
在C语言中,C89标准允许函数的返回类型标识符可以省略,如果省略,默认返回int。C99/C11标准要求必须明确指定返回类型,不再支持默认int类型,举例:
//写法1:main返回类型是int类型,默认的返回值是0,等价于写法2 main() { .... } //写法2:main的返回类型是int类型,返回值是0 推荐写法 int main() { return 0; }
-
函数中返回语句的形式为
return(表达式)
或者return 表达式
//写法1 int main() { return (0); } //写法2 推荐 int main() { return 0; }
-
如果
参数列表
中有多个形式参数,则它们之间要用,
分隔;即使它们的类型相同,在形式参数中只能逐个将进行说明,举例:// 正确示例 int avg(int x, int y, int z) { ... } // 错误示例 int avg(int x, y, z) //不能省略每一个形参的类型 { ... }
-
如果
形参列表
中没有参数,我们可以不写,也可以用void标识,举例:// 写法1 推荐 int main() { ... } // 写法2 int main(void) { ... }
案例
案例1
-
需求:计算1~n之间自然数的阶乘值
-
代码:
#include <stido.h> /** *求1~n的阶乘值 *建议:设计的函数,尽量让被调函数改动角小区,能够重复用,有主调函数去影响 */ size_t fun_1(int n) { int i; //循环变量 int s = 1; //阶乘值,初始值是1 for(i = 1; i <= n; i++) s*=i; return s; // 返回结果给主调函数 } int main() { printf("1~12的阶乘结果是%d\n",fun_1(12)); printf("1~20的阶乘结果是%d\n",fun_1(20)); printf("1~30的阶乘结果是%d\n",fun_1(30)); printf("1~40的阶乘结果是%d\n",fun_1(40)); return 0; }
运行结果:
注意:这里计算结果为0,是因为数据太大,超过int存储范围,高位数据丢失,低位数据转出来为0,建议使用
unsigned long
类型。
案例2
-
需求:计算一个圆台两个面的面积之和
-
代码:
//定义PI #define PI 3.141599266 /** * 定义一个函数,根据半径计算圆面积 * @param r:半径 * @return 面积 */ double cicle_area(double r) { // return PI * r * r; return PI * pow(r,2.0); //pow(底数,指数):求r的2次方,我们有些库函数不是c标准函数,需要我们做特殊处理,比如pow需要在编译时加 -lm } int main() { //定义两个半径,两个面积 double r1,r2,area1,area2; printf("请输入两个圆的半径:\n"); scanf("%lf,%lf",&r1,&r2); //调用函数计算圆的面积 area1 = cicle_area(r1); area2 = cicle_area(r2); printf("一个圆台两个面面积之和是%.2lf\n",area1 + area2); return 0; }
-
编译命令:
gcc demo03.c -lm //pow() 不是C语言标准函数,无法自动链接,血药手动链接 -lm linked math 链接数学库
形参和实参
形参(形式参数)
定义
函数定义时指定的参数,形参是用来接收数据的,函数定义时,系统不会为形参申请内存,只有当函数调用时,系统才会为形参申请内存。主要用于存储实际参数,并且当函数返回时(执行return),系统会自动回收为形参申请的内存资源。
- C语言中所有参数传递都是值传递
- 若要修改实参,需传递指针
案例
-
需求:判断一个数是偶数还是奇数
-
代码:
/** * 方式1 */ void fun0(int n) // 这里的n就是形式参数 { if(n %2 == 0) printf("%d是偶数!\n",n); else printf("%d是奇数!n",n); } /** * 方式2 */ void fun1(int n) // 这里的n就是形式参数 { if(n %2 == 0) { printf("%d是偶数!\n",n); return; //提前结束函数,return后的代码不再执行 } printf("%d是奇数!n",n); } /** * 方式3 */ void fun2(int n) // 这里的n就是形式参数 { if(n %2 == 0) { printf("%d是偶数!\n",n); return -1; } printf("%d是奇数!n",n); return 0; } int main() { fune(5); fun1(5); fun2(5); return 0; }
实参(实际参数)
定义
实参是函数调用时由主调函数传递给被调函数的具体的数据。实参可以是常量、变量或者表达式
关键特性
1.类型多样性:
-
实参可以是常量、变量或者表达式。
-
例如:
fun(12); //常量作为实参 fun(a); //变量作为实参 fun(a + 12); //表达式作为实参
2.类型转换
-
当实参和形参类型不同时,会按照赋值规则进行隐式类型转换
-
类型转换可能导致精度损失。
-
例如:
/** * 求一个数的最大值 */ double fabs(double a) { return a < 0 ? -a : a; } int main() { int x = 12, y = -12; //函数调用时,自动将int类型隐式转换为double类型; //函数转换时,需要将duble类型转为int类型,此时需要显示转换 int x1 = (int)fabs(x); int y1 = (int)fabs(y); //同上 }
注意:函数调用的时候,通过实参给形参赋值。
3.单向值传递
-
C语言采用单向值传递机制(赋值的方向:实参→形参)
-
实参仅将其值赋给形参,不传递参本身
-
形参值的改变不会影响实参。
-
案例:
int modify(int n)//n的变量地址:0x11 { n=20; // 修改形参n = 20 } int main() { int n = 10;//实参变量 n=10 n的变量地址:0x22 modify(n); printf("%d\n",n); // 10 }
4.内存独立性
-
实参和形参在内存中占据不同的空间
-
形参拥有独立的内存地址
-
演示:
#include <stdio.h> int fun(int n) // n 是形参 { printf("形参n的值: %d\n", n); n += 5; // 修改形参 return n; } int main() { int a = 10; printf("调用前实参a的值: %d\n", a); // 变量作为实参 int result = fun(a); // a 是实参 printf("调用后实参a的值: %d\n", a); // a保持不变 printf("函数返回值: %d\n", result); // 常量作为实参 fun(12); // 字面量12是实参 // 表达式作为实参 fun(a + 12); // 表达式a+12是实参 return 0; }
上述示例程序输出:
案例
-
需求:输入4个整数,要求一个函数求出最大数
-
分析:
- 设计一个函数,这个函数只是西安2个数求最大值
- 多次复用这个函数实现最终求值
-
代码:
/** * 定义一个函数,求两个数中的最大值 *@param x,y *@return 最大值 */ int max(int x, int y) { return x > y ? x : y; } int main() { //定义4个变量,用来接收控制台输入 int a, b, c, d, m; printf("请输入四个整数:\n"); scanf("%d%d%d%d",&a,&b,&c,&d); // 求a,b中的最大值 m = max(a,b); // 求a,b,c中的最大值 m = max(m,c); // 求a,b,c,d中的最大值 m = max(m,d); printf("%d,%d,%d,%d中的最大数是%d", a, b, c, d, m); return 0: }
-
运行结果:
函数的返回值
定义
-
若不需要返回值,函数可以没有return语句。
//如果返回类型是void,return 关键字可以省略 void fun1() { ... } // 这种写法,return关键字也可以省略,但是此时默认返回是 return 0 int fun2() { } // 这种写法,return关键字也可以省略,但是此时默认返回是 return 0 fun3() // 如果不写返回类型,默认返回int { }
-
一个函数中可以有多个return语句,但是任一时刻只有一个return语句被执行。
int eq(int num) { if(num % 2 ==0) { printf("%d是偶数!\n", num); return 0; } printf("%d是奇数!\n", num); return 0; }
-
返回值类型一般情况下要和函数中return语句的数据类型一致,如果不一致,以函数定义指定的返回值类型为标准。
案例
-
需求:输入两个整数,要求用一个函数求出最大数
-
案例1:不涉及类型转换
#include <stdio.h> /** * 定义一个函数,求两个数中的最大数 * @param x,y 外部传入的整数 * @return 最大数 */ int get_max(int x, int y) { if (x > y) return x; return y; } int main(int argc,char *argv[]) { // 定义三个变量,用来存储控制台输入的两个整数以及最大值 int a,b,max; // 通过控制台录入数据 printf("请输入两个整数:\n"); scanf("%d%d",&a,&b); // 调用函数进行比较 max = get_max(a,b); printf("%d,%d中的最大值是%d\n",a,b,max); return 0; }
-
案例2:设计类型转换
#include <stdio.h> /** * 定义一个函数,求两个数中的最大数 * @param x,y 外部传入的整数 * @return 最大数 */ double get_max(int x, int y) { if (x > y) return x; return y; } int main(int argc,char *argv[]) { // 定义三个变量,用来存储控制台输入的两个整数以及最大值 int a,b,max; // 通过控制台录入数据 printf("请输入两个整数:\n"); scanf("%d%d",&a,&b); // 调用函数进行比较 max = (int)get_max(a,b); // double → int 需要显示转换 printf("%d,%d中的最大值是%d\n",a,b,max); return 0; }
-
案例3:涉及类型转换-显示转换
#include <stdio.h> /** * 定义一个函数,求两个数中的最大数 * @param x,y 外部传入的整数 * @return 最大数 */ int get_max(int x, int y) { double z; z = x > y ? x : y; return (int)z; // double → int } int main(int argc,char *argv[]) { // 定义三个变量,用来存储控制台输入的两个整数以及最大值 int a,b,max; // 通过控制台录入数据 printf("请输入两个整数:\n"); scanf("%d%d",&a,&b); // 调用函数进行比较 max = get_max(a,b); printf("%d,%d中的最大值是%d\n",a,b,max); return 0; }