导图社区 第07课 函数
函数是一个完成特定功能的代码模块,其程序代码独立,通常要求有返回值,也可以是空值。
编辑于2022-05-26 19:25:43第07课 函数
函数的基本用法
函数是一个完成特定功能的代码模块,其程序代码独立。通常要求有返回值,也可以是空值。
函数的一般形式如下: <数据类型> <函数名称>( <形式参数说明> ) { 语句序列; return[(<表达式>)]; }
<数据类型> 是整个函数的返回值类型; return[(<表达式>)] 语句中 <表达式> 的值,要和函数的 <数据类型> 保持一致; 如无返回值应该写为 void 型。
<形式参数说明> 是逗号 “,” 分隔的多个变量的说明形式
大括弧对 {<语句序列> },称为函数体;<语句序列> 是大于等于零个语句构成的
函数得先声明(实现)再调用; 或者先在 main 函数前写一句声明(说明)语句,后面再写实现语句。 “函数的说明” 可以单写一条语句,此时变量名可以省略,但类型不能省略; “函数的实现” 变量名和类型都不能省略。 “函数的说明” 就是指 函数原型。 其中,写“函数的说明”时 <形式参数说明> 可以缺省说明的变量名称,但类型不能缺省。 例如: double power(double x,int n);//“函数的说明”时可以不写变量名称,但类型必须要写 double power(double,int);
Tips:man 3 printf //库函数中对于 printf 的介绍
函数的使用也叫函数的调用,一般形式如下: 函数名称 (<实际参数>)
实参就是在使用(调用)函数时,调用函数传递给被调用函数的数据。需要确切的数据。
函数调用可以作为一个运算量出现在表达式中,也可以单独形成一个语句。对于无返回值的函数来讲,只能形成一个函数调用语句。
C语言中的形参和实参
如果把函数比喻成一台机器,那么参数就是原材料,返回值就是最终产品; 从一定程度上讲,函数的作用就是根据不同的参数产生不同的返回值。
C语言函数的参数会出现在两个地方,分别是函数定义处和函数调用处 在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。 函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。 形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。
形参和实参的区别和联系
形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。
实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。 当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。
函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参; 换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了; 所以,在函数调用过程中,形参的值发生改变并不会影响实参。
形参和实参虽然可以同名,但它们之间是相互独立的,互不影响,因为实参在函数外部有效,而形参在函数内部有效。
C语言中 return 的用法
函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 return 语句返回。
return 语句的一般形式为: return 表达式; 或者 return (表达式);
有没有( )都是正确的,为了简明,一般也不写( )。
对C语言返回值的说明
没有返回值的函数为空类型,用void表示。 一旦函数的返回值类型被定义为 void,就不能再接收它的值了。
return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值。
函数一旦遇到 return 语句就立即返回,后面的所有语句都不会被执行到了。从这个角度看,return 语句还有强制结束函数执行的作用。 return 语句是提前结束函数的唯一办法 return 后面可以跟一份数据,表示将这份数据返回到函数外面; return 后面也可以不跟任何数据(此时函数得是 void型),表示什么也不返回,仅仅用来结束函数。
思考题:使用库函数时,为什么引入头文件,有没有别的替代写法? 答:因为头文件里有库函数的函数说明,这样在调用库函数的时候编译器才不会报错。 也可以把库函数的函数说明拿出来单独写,这样就可以不用引入头文件了。
函数的参数传递
函数之间的参数传递方式
全局变量
复制(赋值)传递方式(值传递)
地址传递方式(指针传递)
全局变量
在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。 全局变量是在函数体外说明的变量,它们在程序中的每个函数里都是可见的。 全局变量一经定义后就会在程序的任何地方可见。函数调用的位置不同,程序的执行结果可能会受到影响。不建议使用。
Tips:和“局部变量”一同理解
定义在函数内部的变量称为局部变量(Local Variable), 它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。
几点说明: (1)在 main 函数中定义的变量也是局部变量,只能在 main 函数中使用; 同时,main 函数中也不能使用其它函数中定义的变量; main 函数也是一个函数,与其它函数地位平等。 (2)形参变量、在函数体内定义的变量都是局部变量。 实参给形参传值的过程也就是给局部变量赋值的过程。 (3)可以在不同的函数中使用相同的变量名,它们表示不同的数据,分配不同的内存,互不干扰,也不会发生混淆。 (4)在语句块中也可定义变量,它的作用域只限于当前语句块。
复制(赋值)传递方式(值传递)
调用函数将实参传递给被调用函数,被调用函数将创建同类型的形参并用实参初始化。 形参是新开辟的存储空间,因此,在函数中改变形参的值,不会影响到实参。
地址传递方式(指针传递)
按地址传递,实参为变量的地址,而形参为同类型的指针。 被调用函数中对形参的操作,将直接改变实参的值 (被调用函数对指针的目标操作,相当于对实参本身的操作)。
函数中传递数组参数
数组在函数间传参的用法
全局数组传递方式
复制传递方式:实参为数组的指针,形参为数组名(本质是一个指针变量,即同类型的指针变量)
地址传递方式:实参为数组的指针,形参为同类型的指针变量
函数中传递一维数组参数 对于一维整型数组,传参时需要传递数组名和元素个数; 对于一维字符串数组,只需传数组名即可,因为字符数组可以通过‘\0’来找到截止位置。
函数中传递二维数组参数 对于二维整型数组,传参时需要传递数组名和行号、列号; 对于二维字符串数组,见具体需求。 多维数组作为参数在函数间的传递也有全局数组、复制传递和地址传递三种方式。
指针函数&函数指针
指针函数
指针函数(核心是函数)是指一个函数的返回值为地址量的函数。
指针函数的定义的一般形式如下: <数据类型> * <函数名称>(<参数说明>) { 语句序列; }
当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中。 指针函数就是返回一个地址给调用者。 普通的返回值其实返回的并不是变量,而是一个具体的值; 而指针函数的返回值是地址,即指针。
指针函数是一个函数,函数都有返回类型,如果不返回值,则为 void 类型,只不过指针函数返回类型是某一类型的指针; 指针函数的返回值是一个地址值,函数返回值必须用同类型的指针变量来接收,也就是说,指针函数一定有“函数返回值”,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。(见后面代码验证:并不是必须的)
返回值:全局变量的地址、static变量的地址、字符串常量的地址、“堆”上的地址(动态内存),此时可以直接使用return。 对于指针函数中的局部变量,不能直接使用return来返回,此时运行结果不确定。 局部变量是放到“栈”上的,超过使用范围就会自动回收。 Tips:在C语言中,全局变量、static修饰的局部变量、字符串常量,关于变量的处理只有这三种情况下的数据是放在静态区的,程序结束才释放内存(VIP待遇,一旦分配,则一直占据内存,直到程序结束才释放)。
个人理解: 当指针函数无形参时,即指针函数没有同类型指针来接收参数,以下情况才可以直接返回地址: (1)全局变量的地址; (2)static 变量的地址; (3)字符串常量的地址。 注意:局部变量的地址不能直接返回,运行结果会出错。
返回值可以是局部变量, 前提是把局部变量的地址在函数中赋值成全局变量的地址, 或者说是把局部变量赋值成在另一个函数中的变量地址, 也就是说这个“局部变量”要和主调函数中的变量是有联系的, 即针对指针函数有形参的情况!!!
思考题: 1、编写一个指针函数, 删除一个字符串中的空格 2、编写一个指针函数, 实现字符串连接 3、编写一个指针函数,把整数123转化成字符串”123”
函数指针
函数指针(核心是指针,是一个变量!!!)用来存放函数的地址,这个地址是一个函数的入口地址。 函数名代表了函数的入口地址。
函数指针变量说明的一般形式如下: <数据类型> (* <函数指针名称>)( <参数说明列表> );
<数据类型> 是函数指针所指向的函数的返回值类型 <参数说明列表> 应该与函数指针所指向的函数的形参说明保持一致 即:<数据类型> 和 <参数说明列表> 与要指向的函数一样即可。 (* <函数指针名称>)中,* 说明为指针,( )不可缺省,表明为函数的指针 函数指针声明为指针,它与变量指针不同之处是,它不是指向变量,而是指向函数。所以一定要注意!!! 函数指针有两个用途:调用函数和做函数的参数.
函数指针数组
函数指针数组是一个保存若干个函数名的数组。 函数名代表了函数的入口地址。
一般形式如下: <数据类型> (* <函数指针数组名称> [<大小>] ) ( <参数说明列表> ); 其中,<大小> 是指函数指针数组中元素的个数, 其它同普通的函数指针一样。
思考题:调用C库中的 qsort 函数来实现整形或字符数组的排序 qsort()函数:快速排序的函数 -引用 #include<stdlib.h> 头文件 函数原型:通过指令 man qsort 查看 void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void * elem1, const void * elem2)); 参数说明: void *base:要排序的目标数组; size_t nmemb:待排序的元素个数; size_t size:一个元素的大小,单位是字节; int (*compar)(const void * elem1, const void * elem2):compar是函数指针,compar指向的是:排序时,用来比较两个元素的函数。需要自己编写。 返回值: <0:elem1 小于 elem2 =0:elem1 等于 elem2 >0:elem1 大于 elem2 qsort 内部调用 compar 函数,根据 compar 函数返回值的正负来判断大小。 qsort 是库函数,使用时看不到源代码。 qsort 里面调用的 compar 是一个函数指针,compar 函数的形式是 qsort 里面规定好了的。 使用const void *是因为: 一是可以防止输入被意外改动; 二是void *类型的指针可以转换为任意类型的其它指针,这样 qsort 就变得在不同类型数据间通用了; 缺点:不能进行运算,不能+ -整数,不能解引用,需进行强制类型转换。 对于 *,翻译为“解引用”,解引用是返回内存地址中对应的对象。
递归函数
递归函数是指一个函数的函数体中直接或间接调用了该函数自身。 递归函数调用的执行过程分为两个阶段: (1)递推阶段: 从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件; (2)回归阶段: 按递归终止条件求出结果,逆向逐步代入递归公式,回归到原问题求解。