函数的定义
函数:将常用的代码以固定的格式封装(包装)成一个独立的模块,只要知道这个模块的名字就可以重复使用,这个模块就叫做函数。
库函数和自定义函数
C语言自带的函数称为库函数。
库是编程中的一个基本概念,可以简单地认为它是一系列函数的集合,在磁盘上往往是一个文件夹。
C语言自带的库称为标准库,其它公司或个人开发的库称为第三方库,自己编写的函数称为自定义函数。
参数
函数的一个明显特征就是使用时带括号(),有必要的话,括号中还要包含数据或变量,称为参数。
返回值
既然函数可以处理数据,那就有必要将处理结果告诉我们,所以很多函数都有返回值。所谓返回值,就是函数的执行结果。
函数返回值由固定的数据类型(int、char、float等),用来接收返回值的变量类型要一致。
函数定义
函数是一段可以重复使用的代码,用来独立地完成某个功能,它可以接收用户传递的数据,也可以不接收。接收用户数据的函数在定义时要指明参数,不接收用户数据的不需要指明,根据这一点可以将函数分为有参函数和无参函数。
将代码段封装成函数的过程叫做函数定义。
无参函数的定义
如果函数不接收用户传递的数据,那么定义时可以不带参数。
dataType functionName()
{
//body
}
dataType 是返回值类型,它可以是C语言中的任意数据类型,例如 int、float、char。
functionName 是函数名,它是标识符的一种,命名规则和标识符相同。函数名后面的括号( )
不能少。
body 是函数体,它是函数需要执行的代码,是函数的主体部分。即使只有一个语句,函数体也要由{}包围。
如果有返回值,在函数体中使用 return 语句返回。return 出来的数据的类型要和 dataType 一样。
return是C语言中的一个关键字,只能用在函数中,用来返回处理结果。
无返回值函数
有的函数不需要返回值,或者返回值类型不确定(很少见),那么可以用 void 表示。
void functionName()
{
//body
}
void是C语言中的一个关键字,表示“空类型”或“无类型”,绝大部分情况下也就意味着没有 return 语句。
有参函数的定义
如果函数需要接收用户传递的数据,那么定义时就要带上参数。
dataType functionName(dataType1 param1, dataType2 param2...)
{
//body
}
dataType1 param1, dataType2 param2...是参数列表。函数可以只有一个参数,也可以有多个,多个参数之间由,
分隔。参数本质上也是变量,定义时要指明类型和名称。与无参函数的定义相比,有参函数的定义仅仅是多了一个参数列表。
数据通过参数传递到函数内部进行处理,处理完成以后再通过返回值告知函数外部。
函数定义时给出的参数称为形式参数,简称形参;函数调用时给出的参数(也就是传递的数据)称为实际参数,简称实参。函数调用时,将实参的值传递给形参,相当于一次赋值操作。
函数不能嵌套定义
C语言不允许函数嵌套定义;也就是说,不能在一个函数中定义另外一个函数,必须在所有函数之外定义另外一个函数。main() 也是一个函数定义,也不能在 main() 函数内部定义新函数。
形参和实参的区别
形参(形式参数):在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。
实参(实际参数):函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。
形参和实参的区别和联系
1. 形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
2. 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。
3. 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。
4. 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,所以,在函数调用过程中,形参的值发生改变并不会影响实参。
5. 形参和实参虽然可以同名,但它们之间是相互独立的,互不影响,因为实参在函数外部有效,而形参在函数内部有效。
返回值
1. 没有返回值的函数为空类型,用void表示。
2. return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值。
3. 函数一旦遇到 return 语句就立即返回,后面的所有语句都不会被执行到了。从这个角度看,return 语句还有强制结束函数执行的作用。
函数调用
函数调用:使用已经定义好的函数。
functionName(param1, param2, param3...);
functionName 是函数名称,param1, param2, param3...
是实参列表。实参可以是常数、变量、表达式等,多个实参用逗号,
分隔。
函数的嵌套调用
函数不能嵌套定义,但可以嵌套调用,也就是在一个函数的定义或调用过程中允许出现对另外一个函数的调用。
如果一个函数 A() 在定义或调用过程中出现了对另外一个函数 B() 的调用,那么我们就称 A() 为主调函数或主函数,称 B() 为被调函数。
当主调函数遇到被调函数时,主调函数会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回主调函数,主调函数根据刚才的状态继续往下执行。
函数声明
C语言代码由上到下依次执行,原则上函数定义要出现在函数调用之前,否则就会报错。但在实际开发中,经常会在函数定义之前使用它们,这个时候就需要提前声明。
所谓声明(Declaration),就是告诉编译器我要使用这个函数,你现在没有找到它的定义不要紧,请不要报错,稍后我会把定义补上。
函数声明的格式非常简单,相当于去掉函数定义中的函数体,并在最后加上分号;
dataType functionName( dataType1 param1, dataType2 param2...);
也可以不写形参,只写数据类型:
dataType functionName( dataType1, dataType2...);
函数原型
函数声明给出了函数名、返回值类型、参数列表(重点是参数类型)等与该函数有关的信息,称为函数原型。
函数原型的作用是告诉编译器与该函数有关的信息,让编译器知道函数的存在,以及存在的形式,即使函数暂时没有定义,编译器也知道如何使用它。
有了函数声明,函数定义就可以出现在任何地方了,甚至是其他文件、静态链接库、动态链接库等。
对于多个文件的程序,通常是将函数定义放到源文件(.
c文件)中,将函数的声明放到头文件(.h文件)中,使用函数时引入对应的头文件就可以,编译器会在链接阶段找到函数体。
全局变量和局部变量
所谓作用域,就是变量的有效范围。形参变量要等到函数被调用时才分配内存,调用结束后立即释放内存。这说明形参变量的作用域非常有限,只能在函数内部使用,离开该函数就无效了。
局部变量
定义在函数内部的变量称为局部变量,它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。
1. 在 main 函数中定义的变量也是局部变量,只能在 main 函数中使用;同时,main 函数中也不能使用其它函数中定义的变量。main 函数也是一个函数,与其它函数地位平等。
2. 形参变量、在函数体内定义的变量都是局部变量。实参给形参传值的过程也就是给局部变量赋值的过程。
3. 可以在不同的函数中使用相同的变量名,它们表示不同的数据,分配不同的内存,互不干扰,也不会发生混淆。
4. 在语句块中也可定义变量,它的作用域只限于当前语句块。
全局变量
在所有函数外部定义的变量称为全局变量,它的作用域默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。
1. 当全局变量和局部变量同名时,在局部范围内全局变量被“屏蔽”,不再起作用。或者说,变量的使用遵循就近原则,如果在当前作用域中存在同名变量,就不会向更大的作用域中去寻找变量。
2. 函数中不存在局部变量 时,编译器只能到函数外部,也就是全局作用域中去寻找变量。
3. 由{}包围的代码块也拥有独立的作用域,printf() 使用它自己内部的变量。
4. C语言规定,只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量。
作用域
所谓作用域,就是变量的有效范围,就是变量可以在哪个范围以内使用。有些变量可以在所有代码文件中使用,有些变量只能在当前的文件中使用,有些变量只能在函数内部使用,有些变量只能在for循环内部使用。
变量的作用域由变量的定义位置决定,在不同位置定义的变量,它的作用域是不一样的。
在函数内部定义的变量(局部变量)
在函数内部定义的变量,它的作用域也仅限于函数内部,出了函数就不能使用了,我们将这样的变量称为局部变量。函数的形参也是局部变量,也只能在函数内部使用。
在所有函数外部定义的变量(全局变量)
C语言允许在所有函数的外部定义变量,这样的变量称为全局变量。
全局变量的默认作用域是整个程序,也就是所有的代码文件,包括源文件(.c文件)和头文件(.h文件)。如果给全局变量加上 static 关键字,它的作用域就变成了当前文件,在其它文件中就无效了。
在一个函数内部修改全局变量的值会影响其它函数,全局变量的值在函数内部被修改后并不会自动恢复,它会一直保留该值,直到下次被修改。
关于变量的命名
C语言规定,在同一个作用域中不能出现两个名字相同的变量,否则会产生命名冲突;但是在不同的作用域中,允许出现名字相同的变量,它们的作用范围不同,彼此之间不会产生冲突。这句话有两层含义:
不同函数内部可以出现同名的变量,不同函数是不同的局部作用域;
函数内部和外部可以出现同名的变量,函数内部是局部作用域,函数外部是全局作用域。
块级变量:在代码块内部定义的变量
所谓代码块,就是由{}包围起来的代码。代码块在C语言中随处可见,例如函数体、选择结构、循环结构等。不包含代码块的C语言程序根本不能运行,即使最简单的C语言程序也要包含代码块。
C语言允许在代码块内部定义变量,这样的变量具有块级作用域;换句话说,在代码块内部定义的变量只能在代码块内部使用,出了代码块就无效了。
在for循环条件里面定义变量
编译器允许在 for 循环条件里面定义新变量,这样的变量也是块级变量,它的作用域仅限于 for 循环内部。
定义在循环条件里面的变量就是一个块级变量,它的作用域就是当前 for 循环,出了 for 循环就无效了。
递归函数
一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。
递归的退出
当递归进入到最内层的时候,递归就结束了,就开始逐层退出了,也就是逐层执行 return 语句。
递归的条件
每一个递归函数都应该只进行有限次的递归调用,否则它就会进入死胡同,永远也不能退出了,这样的程序是没有意义的。
要想让递归函数逐层进入再逐层退出,需要解决两个方面的问题:
存在限制条件,当符合这个条件时递归便不再继续。
每次递归调用之后越来越接近这个限制条件。