函数
一.函数的定义
1.函数概念
1)从函数定义的角度来分类: 可分为标准函数(即库函数)和用户自定义函数两种.
(1)库函数. 由C语言系统提供,可直接使用,如printf,scanf等
(2)用户自定义函数. 是由用户按需编写的函数.
2)从函数的形式来分类: 可分为无参函数和有参函数两种.
(1)无参函数. 无参函数在函数定义、函数说明及函数调用中均不带参数.主调函数和被调函数之间不进行参数传送,此类函数通常用于完成一组指定的功能,可以返回或不返回函数值.
(2)有参函数. 也称带参函数,在函数定义及函数说明时都有参数,称为形式参数(形参).在函数调用时也必须给出参数,称为实际参数(实参).进行函数调用时,主调函数将把实参的值传送给形参.
2.函数定义
函数定义就是对函数所要完成的操作进行描述,即编写一段程序,使该段程序完成所指定的操作.
函数的一般形式:
类型标识符 函数名(类型 形式参数,类型 形式参数,…){ 声明部分 执行部分}
函数值通过return语句返回.执行函数时,一旦遇到return语句就立即结束当前函数的执行,返回到主调函数的调用点,如果没有返回值,则函数类型标识符用void
二.函数参数和返回值
1.形式参数和实际参数
当有参函数调用时,需要由实参向形参传递参数.当函数未被调用时,函数的形参并不占据实际的存储单元,也没有实际值.C语言中函数的值传递有两种方式: 一种是传递数值(即传递基本类型的数据、结构体数据等),另一种是传递地址(即传递储存单元的地址).
值传递,即将实参的值传递给形参变量.当函数调用时,为形参分配存储单元,并将实参的值传给形参变量;调用结束后,形参单元被释放,实参单元仍保留并维持原样,形参变量的改变不会影响实参的值.
所谓地址传递方式,是指当函数调用时,将实参数据的存储地址作为参数传递给形参.其特点是:形参和实参占据同样的内存单元,函数中对形参值的改变会改变实参的值.
注意:(1)实参和形参必须是地址常量或变量.比较典型的地址传递方式就是用数组名作为函数的参数.当用数组名作为函数参数时,不是进行值的传送,只是地址的传送,把实参数组的首地址赋予形参数组名,实际上是形参数组与实参数组同为一个数组.
(2)形参数组可以不用指定大小,在定义数组时只在数组名后加一个空的方括号即可.
(3)形参数组和实参数组的类型必须一致,否则会引起错误.
(4)形参数组和实参数组的长度可以不同.
3.函数的调用
1)函数调用
调用有参函数的一般形式:
函数名(实参表列)
若是无参函数:
函数名()
如果实参表列包含多个实参,则实参之间以逗号相隔,调用时实参与形参的个数必须相等,类型应匹配。
函数调用可以有三种方式:
(1)表达式方式。函数调用出现在一个表达式中。这类函数必须要有一个明确的返回值以参加表达式运算。
例如:c=2*max(a,b);
函数max是表达式的一部分,将其值乘以2赋给c。
(2)参数方式。函数调用作为另一个函数调用的实参。同样这类函数也必须有返回值。
例如:d=max(a,max(b,c))
其中函数调用max(b,c)的值又作为max函数调用的一个实参。d的值是a、b、c中的最大者。
(3)语句方式。函数调用作为一个独立的语句,一般用在仅仅要求函数完成一定的操作,不要求函数带回返回值的情况下,如scanf函数、printf函数等库函数的调用。
2)函数声明
一般形式:
类型标识符 函数名(类型 参数名,类型 参数名,…);或
类型标识符 函数名(类型 , 类型,…);
函数声明完了就可以任意调用.
3)嵌套调用
C语言中函数是不允许嵌套定义的,但允许嵌套调用,所谓嵌套调用就是函数在被调用过程中又去调用了其他函数。嵌套调用其他函数的个数又被称为嵌套的深度或层数。
4)递归调用
递归是一种特殊的解决问题的方法。其基本思想是:将要解决的问题分解成比原问题规模小的子问题,当解决这个子问题时,又可以用到原问题的解决方法,并按照这一原则逐步递推,最终将原问题转化成较小且有已知解的子问题。这就是递归求解问题的方法。
递归方法适用于一类特殊的问题,即分解后的子问题必须与原问题类似,能用原来的方法解决问题,且最终的子问题是已知解或易于解的。
用递归求解问题的过程分为递推和回归两个阶段。
递推阶段是将原问题不断地转化成子问题,逐渐从未知向已知推进,最终到达已知解的问题,即递推阶段结束。
回归阶段是从已知解的问题出发,按照递推的逆过程,逐一求值回归,最后到达递归的开始处,即结束回归阶段,获得问题的解。
注意:当递归调用时,虽然函数代码一样,变量名相同,但系统多位函数的形参和函数体内的变量分配了相应的存储空间,因此每次调用函数时使用的都是本次调用所新分配的存储单元及其值,当递归调用结束返回时,释放掉本次调用所分配的形参变量和函数体内的变量,并带本次计算值,返回到上次调用点。
5)变量的作用域
C语言中的变量分为全局变量和局部变量
在函数内部定义的变量被称为局部变量,其作用域是所定义的函数即只能在本函数中,可对该变量赋值或使用该变量值,一旦离开这个函数就不能引用该变量了。形式参数也是局部变量。在复合语句内定义的变量,也是局部变量,只在复合语句内有效。
在函数外面定义的变量是外部变量,也称为全局变量,其作用域从变量定义的位置开始到文件结束,可被本文件的所有函数所共用。
指针
一.指针和地址
在C语言中,如果变量p中的内容是另一个变量a的地址,则称变量p指向变量a,或称p是指向变量a的指针变量.显然可以认为变量的指针即为变量的地址,而存放其他变量地址的变量是指针变量.
二.指针变量
1.指针变量的定义
指针变量不同于之前介绍的整型变量、字符型变量等,它专门用于存放地址,这个地址不仅可以是变量的地址,也可以是其他数据结构的地址.对指针变量的定义必须包含以下三方面的内容:
(1)指针类型说明,即定义变量为一个指针变量;
(2)指针变量名;
(3)指针值所指向的变量的数据类型.
一般形式为: 类型说明符 *变量名;
其中,*表示这是一个指针变量;变量名即为定义的指针变量名;类型说明符表示本指针变量所指向的变量的数据类型.
2.指针变量赋值
假设有指向整型变量的指针变量p,如要把整型变量a的地址赋予p,有两种方式:
(1)指针变量初始化
int a;
int *p=&a;
(2)赋值语句
int a;
int *p;
p=&a;
不允许直接把一个数赋予指针变量,被赋值的指针变量前不能再加“*”说明符。
3.指针运算符与指针表达式
C语言中有两个关于指针的运算符:
(1)取地址运算符&。取地址运算符&是单目运算符,结合性为自右至左,其功能是取变量的地址,如&a是变量a的地址。在前面介绍指针变量赋值中,我们已经了解并使用了&运算符。
(2)取值运算符*。取值运算符是单目运算符,其结合性为自右至左,用于表示指针变量所指的变量。在运算符之后的变量必须是指针变量,如p是取指针变量p所指向的存储单元的内容,即所指向的变量的值。
需要注意的是,指针运算符和指针变量说明中的指针说明符意义不同。在指针变量说明中,“”是类型说明符,表示其后的变量是指针类型;而表达式中出现的“”则是一个运算符,用以表示指针变量所指的变量。
(3)“”与“&”运算符的进一步说明。
①如果已执行赋值语句“p=&a;”,则“&p”的值是&a。因为“”与“&”的运算符优先级相同,根据自右至左结合的特性,可以将“&p”看作“&(p)”,所以先进行p的运算得到变量a,再进行&运算得到的值为变量a的地址。
②如果已执行赋值语句“a=20;”,则“&a”的值是a即20。因为先进行&a运算得到a的地址,再进行*运算,得到a地址的内容a。
③指针加一,不是单纯加一,而是加一个所指变量的字节个数。