第5章 函数
5.1 函数定义
在标准C+ +中,函数的定义形式为:
<返回类型><函数名>(<形参列表>)
{
<函数体>
}
<函数名>一般是标识符,一个程序只有一个main函数,其他函数名可随意取(当然,必须避免使用C+ +的关键字),好的程序设计风格要求函数名最好是取有助于记忆的名字,如getchar函数,通过函数的名字可以知道函数的功能,这无疑会增加程序的可读性。
<形参列表>是由逗号分隔的,分别说明函数的各个参数。形参将在函数被调用时从调用函数那里获得数据。在C+ +中,函数形参列表可以为空,即一个函数可以没有参数。但即使函数形参列表为空,括起函数参数的一对圆括号也不允许省略。
<返回类型>又称函数类型,表示一个函数所计算(或运行)的结果值的类型。如果一个函数没有结果值,如函数仅用来更新(或设置)变量值、显示信息等,则该函数返回类型为void类型。一个没有返回值的函数类似于一些程序语言(如pascal语言)中的过程(procedure)。
由一对花括号括起来的<函数体>是语句的序列,它定义了函数应执行的具体操作。
需要注意的是,C+ +不允许函数定义嵌套,即在一个函数体内不能包含有其他函数的定义。
5.2 函数调用
C+ +中函数调用的一般形式为:
<函数名>(<实参表>)
当调用一个函数时,其实参的个数、类型及排列次序必须与函数定义时的形参相一致,也就是说实参与形参应该一对一地匹配。当函数定义时没有形参,则函数调用时,<实参表>亦为空。
依据对函数返回值的使用方式,函数的调用方法可分为以下几种:
(1)语句调用,这通常用于不带返回值的函数。这种情况下,被调用函数作为一个独立的语句出现在程序中。
(2)表达式调用。将被调用函数作为表达式的一部分来进行调用。它适用于被调用函数带有返回值的情况。
(3)参数调用。被调用函数作为另一个函数的一个参数进行调用。
5.3 函数原型
在C+ +中,函数在使用之前要预先声明。这种声明在标准C+ +中称为函数原型(function prototype),函数原型给出了函数名、返回类型以及在调用函数时必须提供的参数的个数和类型。函数原型的语法为:
<返回类型><函数名>(<形参列表>);
(注意在函数原型后要有分号)
实际上函数原型说明有两种形式:
(1)直接使用函数定义的头部,并在后面加上一个分号。
(2)在函数原型说明中省略参数列表中的形参变量名,仅给出函数名、函数类型、参数个数及次序。
注意:在C+ +中,在调用任何函数之前,必须确保它已有原型说明。函数原型说明通常放在程序文件的头部,以使得该文件中所有函数都能调用它们。实际上,标准函数的原型说明放在了相应的头文件中,这也是为什么在调用标准函数时必须要包含相应的头文件的原因之一。
在了解了函数定义、函数调用和函数原型之后,就可以写出一个完整的C+ +程序,并可将其编译和运行。
5.4 函数返回类型
根据函数是否带有参数以及函数是否有返回值,可以将函数分为如下四类。
1带参数的有返回值函数
定义形式为:
<返回类型><函数名>(<参数列表>)
{
<语句序列>
}
2不带参数的有返回值函数
定义形式为:
<返回类型><函数名>()
{
<语句序列>
}
3带参数的无返回值函数
定义形式为:
void<函数名>(<参数列表>)
{
<语句序列>
}
4不带参数的无返回值函数
定义形式为:
void<函数名>()
{
<语句序列>
}
5.5 函数参数
C+ +中,函数之间传递参数有传值和传地址两种传递方式。此外,C+ +还提供了默认参数机制,可以简化复杂函数的调用。
1参数的传递方式
(1)传值
传值是将实参值的副本传递(拷贝)给被调用函数的形参。它是C+ +的默认参数传递方式,在此之前的多数函数参数传递都是传值。
由于传值方式是将实参的值复制到形参中,因此实参和形参是两个不同的变量,有各自的存储空间,可以把函数形参看作是函数的局部变量。传值的最大好处是函数调用不会改变调用函数实参变量的内容,可避免不必要的副作用。
(2)传地址
有时我们确实需要通过函数调用来改变实参变量的值,或通过函数调用返回多个值(return语句只能返回一个值),这时仅靠传值方式是不能达到目的。
2默认参数
在C+ +中,可以为参数指定默认值,在函数调用时没有指定与形参相对应的实参时就自动使用默认值。默认参数可以简化复杂函数的调用。
默认参数通常在函数名第一次出现在程序中的时候,如在函数原型中,指定默认参数值。指定默认参数的方式从语法上看与变量初始化相似。
5.6 函数重载
如果能用同一个函数名字在不同类型上做相类似的操作就会方便很多,这种情况即为函数重载。其实这一技术早已用于C+ +的基本运算符。例如加法操作只有一个运算符+,但它却可以用来做整型数、浮点数和指针的加法运算。将这一思想推广到函数,即为函数重载。
5.7 内联函数
C+ +引入内联(inline)函数的原因是用它来取代C中的预处理宏函数。内联函数和宏函数的区别在于,宏函数是由预处理器对宏进行替换,而内联函数是通过编译器来实现的,因此内联函数是真正的函数,只是在调用的时候,内联函数像宏函数一样的展开,所以它没有一般函数的参数压栈和退栈操作,减少了调用开销,因此,内联函数比普通函数有更高的执行效率。
在C+ +中使用inline关键字来定义内联函数。inline关键字放在函数定义中函数类型之前。不过,编译器会将在类的说明部分定义的任何函数都认定为内联函数,即使它们没有用inline说明。
5.8 递归函数
如果一个函数在其函数体内直接或间接地调用了自己,该函数就称为递归函数。递归是解决某些复杂问题的十分有效的方法。递归适用以下的一般场合。
(1)数据的定义形式按递归定义。
(2)数据之间的关系(即数据结构)按递归定义,如树的遍历,图的搜索等。
(3)问题解法按递归算法实现,例如回溯法等。
使用递归需要注意以下几点:
(1)用递归编写代码往往较为简洁,但要牺牲一定的效率。因为系统处理递归函数时都是通过压栈/退栈的方式实现的。
(2)无论哪种递归调用,都必须有递归出口,即结束递归调用的条件。
(3)编写递归函数时需要进行递归分析,既要保证正确使用了递归语句,还要保证完成了相应的操作。
5.9 变量作用域与生存周期
1C+ +中变量的存储类型分为如下几种类型:
auto——函数内部的局部变量(auto可省略不写)。
static——静态存储分配,又分为内部和外部静态。
extern——全局变量(用于外部变量说明)。
register——变量存储在硬件寄存器中。
(1)自动变量
①在函数内部定义的局部变量即为自动变量,用于说明自动变量的关键字auto可以省略。
②在函数头部定义的自动变量作用域为定义它的函数;而在块语句中定义的自动变量作用域为所在块。与C不同,C+ +还允许在变量使用之前才定义变量。
③编译程序不给自动变量赋予隐含的初值,故其初值不确定。因此,每次使用自动变量前,必须明确地赋初值。
④形参可以看成是函数的自动变量,作用域仅限于相应函数内。
⑤自动变量所使用的存储空间由程序自动地创建和释放。当函数调用时为自动变量创建存储空间,函数调用结束时将自动释放为其创建的存储空间。因此,自动变量随函数的调用而存在并随函数调用结束而消失,由一次调用到下一次调用之间不保存值。
(2)外部变量
①在函数外部定义的变量即为外部变量。
②外部变量的作用域是整个程序(全局变量)。
③在C+ +中,程序可以分别放在几个源文件上,每个文件可作为一个编译单位分别编译。外部变量只需在某个文件上定义一次,其他文件若要引用此变量时,应用extern加以说明。(外部变量定义时不必加extern关键字)。
④在同一文件中,若前面的函数要引用在其后面定义的外部(在函数之外)变量时,也应用extern加以说明。
⑤外部变量是由编译程序在编译时给其分配空间,属于静态分配变量,对于数值型(整型、浮点型和字符型)外部变量来说,其有隐含初值0。
引进外部变量的原因:其一是只要程序运行外部变量的值是始终存在的;其二是外部变量可以在所有函数间共享。
在C+ +中,可以使用外部变量,但是,必须要清楚使用外部变量的副作用。使用外部变量的函数独立性差,通常不能被移植到其他程序中,而且,如果多个函数都使用到某个外部变量,一旦出现问题,就很难发现问题是由哪个函数引起的。在C+ +中,尽量不用或少用外部变量,可使用参数在函数间进行数据的传递。
(3)静态变量
内部静态变量:
①在局部变量前加上“static”关键字就成为内部静态变量。
②内部静态变量仍是局部变量,其作用域仍在定义它的函数内。但该类型变量采用静态存储分配,当函数执行完,返回调用点时,该变量并不撤消,其值将继续保留,若下次再进入该函数时,其值仍然存在。内部静态变量有隐含初值0,并且只在编译时初始化一次。
外部静态变量:
①在函数外部定义的变量前加上“static”关键字便成了外部静态变量。
②外部静态变量的作用域为定义它的文件,即成为该文件的“私有”(private)变量,只有其所在文件上的函数可以访问该外部静态变量,而其他文件上的函数一律不得直接访问该变量,除非通过外部静态变量所在文件上的各种函数来对它进行操作,这也是一种实现数据隐藏的方式。
③与内部静态变量一样,外部静态变量也采用静态存储分配,有隐含初值0。
在C+ +中,除了支持C风格的内部和外部静态变量的使用之外,还可将类成员声明成static,它有着不同的含义。
(4)寄存器变量
①只有自动(局部)变量和函数参数可指定为寄存器存储类,它的作用域与生存期与自动变量完全相同。
②当指定的寄存器变量个数超过系统所能提供的寄存器数量时,多出的寄存器变量将视同自动变量。
③只限于int,char,short,unsigned和指针类型可使用register存储类。
④不能对寄存器变量取地址(即&操作)。
⑤使用寄存器变量可以提高存取速度,可将使用频率最高的变量说明成为寄存器变量。一般常用于说明循环变量。
由于硬件的快速发展,存储器(如内存)的性能有了很大的改进,因此,目前在实际应用中,使用register来说明变量的情况并不多。
2生存周期
(1)变量由编译程序在编译时给其分配存储空间(称为静态存储分配),并在程序执行过程中始终存在,这类变量包括全局变量、外部静态变量和内部静态变量。这类变量的生存周期与程序的运行周期相同,当程序运行时,该变量的生存周期随即存在,程序运行结束,变量的生存周期随即终止。
(2)变量由程序在运行时自动给其分配存储空间(称为自动存储分配),这类变量为函数(或块)中定义的自动变量。它们在程序执行到该函数(或块)时被创建,在函数(或块)执行结束时释放所用的空间。
北京 | 天津 | 上海 | 江苏 | 山东 |
安徽 | 浙江 | 江西 | 福建 | 深圳 |
广东 | 河北 | 湖南 | 广西 | 河南 |
海南 | 湖北 | 四川 | 重庆 | 云南 |
贵州 | 西藏 | 新疆 | 陕西 | 山西 |
宁夏 | 甘肃 | 青海 | 辽宁 | 吉林 |
黑龙江 | 内蒙古 |