函数入门
在C语言中,函数意味着功能模块。一个典型的C语言程序,就是由一个个的功能模块拼接而成的整体。也因为如此,C语言被称为模块化语言。
什么是函数? function 功能 函数是完成某个功能的指令序列的封装。 C语言的指令(C语句)必须在函数内部。 注意: 除声明语句,其他语句都必须在函数内部。
对于函数的使用者,可以简单的将函数理解为黑箱子,使用者只管按照规定给黑箱一些输入,就会得到一些输出,而不必要理会黑箱子里面的运行细节。黑箱的输入和输出日常使用的电视机可以被理解为一个典型的黑箱子,它有一些公共的接口提供给使用者操作,比如开关、音量、频道等,使用者不需要理会其内部电路,更不需要管电视机的工作原理,只需要按照规定的接口操作得到结果即可。
对于函数的设计者,最重要的工作是封装,封装意味着对外提供服务并隐藏细节。对于一个封装良好的函数而言,其对外提供服务的接口应当是简洁的,内部功能应当是明确的。
为什么要使用函数?
函数可以把相对独立的某个功能抽象出来, 使之成为程序中的一个独立实体。 可以在同一个程序或其他程序中多次重复使用
函数可以实现代码复用,以及实现模块化的设计。 结构化程序设计者主张把一个大任务分成多个功能函数来完成。 函数机制的特点: 使程序变得更简短而清晰 有利于程序维护 可以提高程序开发的效率 提高了代码的重用性 函数分为两大类: 内置函数: 由C语言系统提供; 用户无须定义,也不必在程序中作类型说明; 只需在程序前包含有该函数定义的头文件;
自定义函数: 用户在程序中根据需要而编写的函数;
函数的定义
-
函数头:函数对外的公共接口
-
函数名称:命名规则与变量一致,一般取与函数实际功能相符合的、顾名思义的名称。
-
参数列表:即黑箱的输入数据列表,一个函数可有一个或多个参数,也可以不需要参数。
-
返回类型:即黑箱的输入数据类型,一个函数可不返回数据,但最多只能返回一个数据
-
-
函数体:函数功能的内部实现
-
语法说明
返回值类型 函数名称(输入参数列表) { C语句,函数功能的具体的实现(函数体) ... } 函数返回值类型:C语言任意合法的类型都可以 函数名: C标识符的规定 输入参数列表: 类型1 参数名1, 类型2 参数名2, ……。 void: 表示该函数不带参数。
关于函数的返回值
return: 返回的意思。 return只能用在函数内部, //表示函数结束的意思。 return ; //函数结束,不带返回值 return 表达式; //函数结束,带一个返回值,“表达式的值” //函数的返回值类型,就是return 语句后面的表达式的类型。 如果没有返回值,返回值类型填void
-
四个步骤
1. 确定函数的功能 函数名 2. (确定需求)确定函数的参数 需要 几个? 参数类型? 不需要 输入参数列表:void ,或不填 3. (是否需要反馈结果)有无返回值 有 返回值类型 无 返回值类型为 void 注意: 如果不知道有没有返回值 先填void 4. 算法实现 算法:完成一个任务,所要实现的步骤 eg: /* add:获取两个整数的和 @a:加数 @b:加数 返回值: 返回两个数的和 */ int add(int a, int b) { }
-
函数示例:求两个给定整数的和
int add(int a, int b) { int c = a+b; return c; }
练习:
写一个函数:求两个给定整数的最大值
int Get_max(int a, int b) { return a > b ? a : b; }
-
语法汇总:
-
当函数的参数列表为void时,表示该函数不需要任何参数。
-
当函数的返回类型为void时,表示该函数不返回任何数据。
-
关键字return表示退出函数。①若函数头中规定有返回数据类型,则 return 需携带一个类型与之匹配的数据;②若函数头中规定返回类型为 void,则 return 不需携带参数
-
-
注意
-
1.函数名前面为返回值的类型,哪怕此函数没有返回值,也要写上void
-
2.大括号表示函数的工作范围,如果离开此范围,数据就不属于此函数管辖 函数不能在main(){}里面实现,虽然不会报错,如果不调用,这个子函数是不会被执行的 所有函数的实现都在主函数外面实现
-
-
如果该函数有返回值,函数内部如果没有return 值,系统默认返回一个随机值
-
实参与形参
-
概念:
-
函数调用中的参数,被称为实参,即arguments
-
函数定义中的参数,被称为形参,即parameters
-
-
实参与形参的关系:
-
实参与形参的类型和参数个数必须一一对应。
-
形参的值由实参初始化。
-
形参与实参位于不同的内存区域,彼此独立。
-
-
示例:
int Get_max(int a, int b)// a,b 就是形参 { return a > b ? a : b; } Get_max(1,2);// 1,2 就是实参
函数调用的流程
函数调用:调用一个已经写好 的函数去执行。 “执行一个任务/执行一个功能代码”
函数调用时,进程的上下文回切换到被调函数,当被调函数执行完毕之后再切换回去。
函数调用时的执行流程
(1) 如何调用函数呢? 函数名(实参列表); 实参列表 : 传给函数的值 eg: int add(int a, int b) { return a+b; } add(1+2,3+4); add(a,b); add(a+b, 1>2); (2) 函数调用表达式 函数名(实参列表) 函数调用表达式的值 ===> 函数的返回值 (3) 函数的调用过程 !!!!!! 首先把实参的值一一传递给相应的形参(拷贝给相应的形参),然后再跳转到函数里面去执行,直到遇到return或函数结束,再返回到函数调用处。 形参 空间开辟 先于 函数内部 所有变量 形参的初始值 来源于 实参
练习:
写一个函数,获取一个数最近相邻最小质数,将该质数返回 质数: 除了1 和 本身 以外没有别的因子 //int main(int a) { for(int =a;a<100;a++) { if(a%a=0) printf("%d",a); } }
函数声明
1.“声明”: C语言中的声明是一个已经存在的标识符。 声明:告诉编译器有这个东西并且有名字 声明这个名字对象是什么
2.为什么要声明呢? 因为不声明编译识别不出他的属性
3.函数声明: 函数返回值类型 函数名 ( 输入参数列表);
外部的函数的声明 extern 外部函数的头部 ;// 告诉编译器,该函数是别的文件中的,不是该文件中的 本文件中的函数的声明 本文件中的函数的头部分; 约定: 函数声明中,形参的名字可以要,也可以不要。
数组作为函数传参
当一个函数的参数是一个数组时, 函数声明时,数组当形式参数时,该如何描述呢?
(1)一维数组 int a[5];// typeof(a) ==> int[5] int b[4];//typeof(b) ==> int[4] ========================= void Func(int x[5], int num)// 形参: 类型 名字 ==》 int [5] x ==> int x[5] { } int main() { int a[5]; Func(a, 5);// typeof(a) ==>int[5] } (2)二维数组 void Func(int x[][4], int num)/ { } int main() { int a[3][4];// typeof(a) ==> int[4][3]; Func(a, 3);// } /*在函数声明 `void Func(int x[][4], int num)` 中,数组参数 `x` 前的 `int` 是必需的,而 `[][4]` 中的 `4` 是指定数组中的第二维大小。但是,对于第一维的大小,此处留空表示第一维大小是未指定的,需要在函数定义时指定。 这种写法是因为,对于多维数组,C 语言要求指定至少一维的大小,以便在编译时知道如何计算数组元素的地址。如果在函数参数中指定多维数组的第一维大小,其实际大小可能与函数调用时传入的数组的大小不同,导致类型不匹配问题。因此,通常在函数声明中会留空第一维,然后在函数定义中根据实际情况填写具体的大小。 因此,正确的用法是在函数定义时指定具体的数组大小,例如: ```c void Func(int x[][4], int num) { // 函数实现 } ``` 在函数定义中,`x[][4]` 表示`x` 是一个二维数组,第二维大小为 4,而第一维的大小将在调用函数时确认。 */
作业:
1.写一个函数,求一个二维整型数组的最大值
#include <stdio.h> int findMax(int arr[][3], int rows, int cols) { int max = arr[0][0];//初始化并赋初值,确保是数组中的值 for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (arr[i][j] > max) { max = arr[i][j]; } } } return max; } int main() { int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; int max = findMax(arr, 3, 3); printf("The maximum value in the 2D array is: %d\n", max); return 0; }
2.写一个函数,求一个整数的二进制形式中有多少个1?
#include <stdio.h> int countOnes(int num) { int count = 0;//附初值 while (num != 0) { if (num & 1) {//num & 1的作用是检查num的二进制表示的最低位是否为1 count++; } num = num >> 1; } return count; } int main() { int num = 23; // 23的二进制表示为 10111 int onesCount = countOnes(num); printf("The number of 1s in the binary representation of %d is: %d\n", num, onesCount); return 0; }
3.写一个函数,实现把数据x 插入到一维整型数组a 中第 y 个位置(y从0开始) n :代表数组已有元素个数
#include <stdio.h> void insertElement(int a[], int n, int y, int x) { if (y < 0 || y > n) { printf("Invalid position to insert.\n"); return; } // 将第y个位置之后的元素向后移动一位 for (int i = n; i > y; i--) { a[i] = a[i - 1]; } // 在第y个位置插入新元素x a[y] = x; printf("Element %d inserted at position %d.\n", x, y); } int main() { int a[10] = {1, 2, 3, 4, 5}; int n = 5; // 已有元素个数 int y = 2; // 要插入的位置 int x = 10; // 要插入的元素 insertElement(a, n, y, x); // 打印插入后的数组 printf("Array after insertion:\n"); for (int i = 0; i < n + 1; i++) { printf("%d ", a[i]); } printf("\n"); return 0; }
4.
5.自动发牌 一副扑克有52张牌,打桥牌时应将牌分给四个人。请设计一个程序完成自动发牌的工作。要求:黑桃用S(Spaces)表示;红桃用H(Hearts)表示;方块用D(Diamonds)表示;梅花用C(Clubs)表示。 *问题分析与算法设计 按照打桥牌的规定,每人应当有13张牌。在人工发牌时,先进行洗牌,然后将洗好的牌按一定的顺序发给每一个人。为了便于计算机模拟,可将人工方式的发牌过程加以修改:先确定好发牌顺序:1、2、3、4;将52张牌顺序编号:黑桃2对应数字0,红桃2对应数字1,方块2对应数字2,梅花2对应数字3,黑桃3对应数字4,红桃3对应数字5,...然后从52 张牌中随机的为每个人抽牌。 这里采用C语言库函数的随机函数,生成0到51之间的共52个随机数,以产生洗牌后发牌的效果。
递归函数
-
递归概念:如果一个函数内部,包含了对自身的调用,则该函数称为递归函数。
自己调用自己,注意需要有结束条件,否则会出现内存溢出(段错误) 什么时候用到递归 : 1.在解决一个问题的同时,遇到了该问题本身 2. 不能无限递归,一定要显而易见的结果 void func() { if() return ; if() func() }
-
递归问题:
-
阶乘
int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } }
-
幂运算
-
字符串翻转
练习:
求 汉诺塔的移动次数 (递归)
汉诺塔问题 : 有三个桌子A,B,C 有n 个盘子,把所有盘子原封不动从A移动到C 中途借助B。 规则:小的盘子一定要放在大的盘子上面 一次只能移动一个盘子 求移动的最少次数
-
-
要点:
-
只有能被表达为递归的问题,才能用递归函数解决
-
递归函数必须有一个可直接退出的条件,否则会无限递归
-
递归函数包含两个过程,一个逐渐递进的过程,和一个逐渐回归的过程。
-
-
递归调用时,函数的栈内存的变化如下图所示。可见,随着递归函数的层层深入,栈空间逐渐往下增长,如果递归的层次太深,很容易把栈内存耗光。
-
层层递进时,问题的规模会随之减小,减小到可直接退出的条件时,函数开始层层回归。
练习:
打印汉诺塔的移动路径
void Hanoi_path(int n,char a,char b,char c) { if(n == 1) { printf("%c --> %c\n", a, c); return ; } Hanoi_path(n-1,a, c, b); printf("%c --> %c\n", a, c); Hanoi_path(n-1,b, a, c); }
作业:
-
判断一维数组是否为递增数组(递归)
#include <stdio.h> //参数的设置 int isIncreasing(int arr[], int n) { // 递归的结束条件:数组为空或只有一个元素时,认为是递增的 if (n <= 1) { return 1; } // 检查当前元素与下一个元素的大小关系 if (arr[n-1] > arr[n-2]) { // 递归调用,检查剩余元素 return isIncreasing(arr, n-1); } else { return 0; } } int main() { int arr1[] = {1, 2, 3, 4, 5}; int arr2[] = {1, 3, 2, 4, 5}; if (isIncreasing(arr1, 5)) { printf("arr1 is an increasing array.\n"); } else { printf("arr1 is not an increasing array.\n"); } if (isIncreasing(arr2, 5)) { printf("arr2 is an increasing array.\n"); } else { printf("arr2 is not an increasing array.\n"); } return 0; }