从用户使用的角度看,函数有两种:
① 系统函数,即库函数。这是由编译系统提供的,用户
② 用户自己定义的函数。用以解决用户的专门需要。
从函数的形式看,函数分两类:
① 无参函数。调用函数时不必给出参数。
② 有参函数。在调用函数时,要给出参数。在主调函数
{声明部分
语句
定义有参函数的一般形式为:
注意:
- 在定义函数时,函数名后面括号中的变量称为形式参数(formal parameter,简称形参);在主调函数中调用一个函数时,函数名后面括号中的参数(可以是一个表达式)称为实际参数(actual parameter,简称实参)。
- 在调用函数时,主调函数和被调用函数之间有数据传递关系。
有关形参与实参的说明:
① 定义函数时所指定的形参,在未出现函数调用时,它们并不占内存中的存储单元,也就不存在数据。只有在发生函数调用时,函数中的形参才被分配内存单元,以便接收从实参传来的数据。在调用结束后,形参所占的内存单元被释放。
② 实参可以是常量、变量或表达式。但实参必须有确定的值,以便在调用函数时将实参的值赋给形参。
③ 在定义函数时,必须在函数首部指定形参的类型。
④ 实参与形参的类型应相同或赋值兼容。如果实参与形参的类型不同,则按不同类型数值的赋值规则进行转换。字符型与整型可以互相通用。
⑤ 实参变量对形参变量的数据传递是单向的“值传递”,即只由实参传给形参,而不能由形参传回来给实参。注意:
实参单元与形参单元是不同的单元。在调用函数时,编译系统临时给形参分配存储单元,用来接收从实参传来的数据。调用结束后,形参单元被释放,但实参单元仍保留并维持原值。因此,在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数中实参的值。2 函数的返回值
有关函数返回值的说明:
① 函数的返回值是通过函数中的return语句获得的。return语句将被调用函数中的一个确定值带回到主调函数中。return语句后面的括号可以要,也可以不要。return后面的值可以是一个表达式。
② 函数返回值的类型是在定义函数时指定的,应为某一个确定的类型。
③ 如果函数返回值的类型和return语句中表达式值的类型不一致,则以函数类型为准,即函数类型决定返回值的类型。对数值型数据,可以自动进行类型转换。
函数名([实参列表])
在一个函数中调用另一个函数需要具备的条件:
① 首先被调用的函数必须是已经存在的函数。
函数的定义是指对函数功能的确立,包括指定函数名、函数类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。
4 函数原型
函数原型的一般形式为
函数类型 函数名(参数类型1,参数类型2…);
【例】
float add(float,float);
关于调用函数的说明:
① 如果被调用函数的定义出现在主调函数之前,可以不必加以声明。因为编译系统已经事先知道了已定义的函数类型,会根据函数首部提供的信息对函数的调用作正确性检查。
② 函数声明的位置可以在调用函数所在的函数中,也可以在函数之外。如果函数声明放在函数的外部,并在所有函数定义之前,则在各个主调函数中不必对所调用的函数再作声明。
- 调用函数需要一定的时间和空间开销,其过程如下图所示。
- 在程序运行到该句的时候会直接将函数中的语句写入其中,
-
注意:
① 内置函数的声明可以是在声明函数和定义函数时都加inline,也可以只在其中一处声明inline。
② 使用内置函数可以节省运行时间,但却增加了目标程序的长度。因此一般只将规模很小(一般为5个语句以下)而调用频繁的函数声明为内置函数。
③ 内置函数中不能包括复杂的控制语句,如循环语句和switch语句。说明:声明内置函数,只是编程者对编译系统提出的一个建议,而不是指令。并非指定函数为inline,编译系统就必须把它作为inline处理,而是编译系统会根据具体情况决定是否这样做。因此,只有那些规模较小而又被频繁调用的简单函数,才适合于声明为inline函数。 - 6 函数的重载
C++允许用同一函数名定义多个函数,即对一个函数名重新赋予它新的含义,使一个函数名可以多用,这就是函数的重载(function overloading)。
注意:
重载函数的参数个数、参数类型或参数顺序3者中必须至少有一种不同,函数返回值类型可以相同也可以不同。
建议:在使用重载函数时,同名函数的功能应当相同或相近,不要用同一函数名去实现完全不相干的功能,虽然程序也能运行,但可读性不好,使人莫名其妙。
- 7 函数模板
所谓函数模板(function template),实际上是一个通用函数,该函数在建立时其函数类型和形参类型不具体指定,而是用一个虚拟的类型来代表。在调用函数时系统会根据实参的类型来取代函数模板中的虚拟类型, 从而实现不同的函数功能。
定义函数模板的一般形式为:
template < typename T1,typename T2,……>
或
template <class T1,class T2,……>
凡是函数体相同的函数都可以用一个函数模板来代替,不必定义多个函数,只需在模板中定义一次即可。注意:
虽然用函数模板比函数重载更方便,程序更简洁。但它只适用于函数的参数个数相同而类型不同,且函数体相同的情况。 -
8 有默认参数的函数在函数调用时,形参一般是从实参那里取得值,因此实参的个数应与形参相同。有时多次调用同一函数时使用同样的实参,C++提供简单的处理办法,即给形参一个默认值,这样形参就不必一定要从实参取值。如果函数有多个形参,可以使每个形参有一个默认值,也可以只对一部分形参指定默认值,另一部分形参不指定默认值。
- 默认值的位置问题
- 注意:实参与形参的传值是从左至右顺序进行的。因此指定默认值的参数必须放在形参列表中的最右端,否则函数调用时会出错。
- 使用带有默认参数的函数可简化编程,使程序比较灵活,并能提高运行效率。但使用中要注意两点:
- ① 如果函数的定义在函数调用之前,则应在函数定义中给出默认值。如果函数的定义在函数调用之后,则在函数调用之前需要有函数声明,此时必须在函数声明中给出默认值,在函数定义时可以不给出默认值。
- ② 一个函数不能既作为重载函数,又作为有默认参数的函数。因为当调用函数时如果少写一个参数,系统无法判定是利用重载函数还是利用默认参数的函数,出现二义性,系统无法执行。
- 注意:如果被调用函数的定义在主调函数之后,那么应在主调函数之前对被调用函数作声明。
注意:
- 在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,即该变量只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的。同样,在复合语句中定义的变量只在本复合语句范围内有效。这类变量称为局部变量(local variable)。
说明:
① 主函数main中定义的变量只在主函数中有效,不会因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。
② 不同函数中可以使用同名的变量,它们代表不同的对象,互不干扰。
④ 形式参数也是局部变量。其他函数不能调用。
说明:
(1)全局变量的作用是增加函数间数据联系的渠道。
(2)建议在不必要时不使用全局变量,因为:
① 全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
② 它使函数的通用性降低了,因为在执行函数时要受到外部变量的影响。
(3)如果在同一个源文件中,全局变量与局部变量同名,则在局部变量的作用范围内,全局变量被屏蔽,即它不起作用。
变量除了具有作用域(从空间的角度来分析)这一属性外,还有另一种属性——存储期(从变量值存在的时间角度来分析),也称生命期。
存储期是指变量在内存中的存在期间。存储期可以分为静态存储期和动态存储期,它是由变量的静态存储方式和动态存储方式决定的。
内存中供用户使用的存储空间可以分为三部分,即:
② 静态存储区
2 自动变量
C++中的变量除了具有数据类型属性外,还有存储类别属性。所谓存储类别指的是数据在内存中存储的方法,即静态存储和动态存储,具体包含4种:自动的(auto)、静态的(static)、寄存器的(register)和外部的(extern)。根据变量的存储类别,可以知道变量的作用域和存储期。
函数的形参和函数中的局部变量(不加关键字static声明)属于自动变量。这类变量在函数调用时,系统给它们动态分配存储空间,数据存储在动态存储区中。在函数调用结束时自动释放这些空间。
说明:
① 在定义自动变量时,存储类别和数据类型的顺序任意。
对静态局部变量的说明:
① 静态局部变量在静态存储区内分配存储单元,在程序整个运行期间都不释放。
② 静态局部变量赋初值是在编译时进行,即只赋初值一次。在程序运行时每次调用函数都不再重新赋初值而只是保留上次函数调用结束时的值。而自动变量赋初值,不在编译时进行,而是在函数调用时进行,每调用一次函数重新给一次初值。
③ 如果在定义局部变量时不赋初值,那么对静态局部变量来说,编译时自动赋初值0(数值型变量)或空字符(字符型变量)。而对自动变量来说,它的值是一个不确定的值。
使用静态局部变量的两种情况:
5 用extern声明外部变量
外部变量 (即全局变量)是在函数的外部定义,其作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以被本文件中各个函数所引用。系统编译时将全局变量分配在静态存储区。
用extern来声明全局变量,可以扩展全局变量的作用域。
⑴ 在一个文件内声明全局变量
⑵在多个文件的程序中声明外部变量
6 用static声明静态外部变量
① 存储类别 C++允许使用auto、static、register和extern 4种存储类别。
② 作用域 指程序中可以引用该变量的区域。
③ 存储期 指变量在内存的存储期限。
从作用域角度,变量可分为局部变量和全局变量。它们采用的存储类别如下:
从变量存储期,变量有动态存储和静态存储两种类型。静态存储是程序整个运行时间都存在,而动态存储则是在调用函数时临时分配单元。
(4)
关于作用域和存储期的概念。对一个变量的性质可以从两个方面分析,一是从变量的作用域,一是从变量的存储期。前者是从空间的角度,后者是从时间的角度。二者有联系但不是同一回事。作用域和存储期的示意图如下图所示。
如果一个变量在某个文件或函数范围内有效,则称该文件或函数为该变量的作用域,在此作用域内可以引用该变量,即变量在此作用域内“可见”,这种性质称为变量的可见性。
如果一个变量值在某一时刻是存在的,则认为这一时刻属于该变量的存储期,即该变量在此时刻“存在”。
(5) static声明使变量采用静态存储方式,但它对局部变量和全局变量所起的作用不同。
一个函数一般由声明部分和执行语句组成。声明部分的作用是对有关的标识符(如变量、函数、结构体、共用体等)进行属性说明。对于函数,声明和定义有明显的区别:函数的声明是函数的原型,而函数的定义是函数功能的确立。对函数的声明是可以放在声明部分中的,而函数的定义是一个文件中的独立模块。
变量出现在声明部分有两种情况:一种是需要建立存储空间的,即定义性声明,简称为定义(如inta;
);另一种是不需要建立存储空间的,即引用性声明(如extern int a;)。
- 如果一个函数只能被本源程序文件中的函数所调用,就称为内部函数。定义内部函数的一般格式为:
- 函数本质上是全局的,但也可以指定函数只能被本源程序文件调用,而不能被其他文件调用。根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数。
- (1) 在定义函数时,如果在函数首部的最左端加上关键字extern,则表示该函数是外部函数。
C++提供的“预处理命令”,可以改进程序设计环境,提高编程效率。
预处理命令是C++统一规定的,但它们不是C++语言本身的组成部分,不能直接对它们进行编译(因为编译程序不能识别它们)。因此程序在编译之前先进行“预处理”,即根据预处理命令对程序作出相应的处理,经预处理后程序中不再包含预处理命令,然后再进行编译连接。现在的C++编译系统都包括了预处理、编译和连接等部分。
① 宏定义
② 文件包含
- 宏定义的作用是将一个指定的标识符(即宏名)来代表一个字符串,一般是用一个短的名字代表一个长的字符串。C++提供了#define命令来实现“宏定义”的操作。
#define 标识符 字符串
- 所谓“文件包含”处理是指一个源程序文件可以将另外一个源程序文件的全部内容包含进来。C++提供了#include命令来实现“文件包含”的操作。
“文件包含”命令很有用,绝大多数C++程序中都包括“文件包含”命令,它可以节省程序设计人员的重复劳动。
现在,库函数的开发者把信息写在一个称为 “头文件”的文件中,用户如需使用这些信息,只需将该“头文件” 包含进程序即可,这就大大简化了程序。
头文件一般包含以下几类内容:
① 对类型的声明。
② 函数声明。
③ 内置(inline)函数的定义。
④ 宏定义。用#define定义的符号常量和用const声明的常变量。
⑤ 全局变量定义。
⑥ 外部变量声明。如enterninta;
⑦ 还可以根据需要包含其他头文件。(2) include命令的两种形式
在include命令中,文件名除了可以用尖括号括起来以外,还可以用双撇号括起来。include命令的一般形式为:
#include <文件名>
或
#include ″文件名″(3) 关于C++标准库
C++的库除了保留C的大部分系统函数和宏定义外,还增加了预定义的模板和类。但是不同C++库的内容不完全相同,由各C++编译系统自行决定。
新的C ++标准库中的头文件一般不再包括后缀.h,但为了使大批已有的C程序能继续使用,许多C++编译系统保留了C的头文件,即提供两种不同的头文件,由程序设计者选用。3 条件编译
一般情况下,编译时会对源程序中的每一行进行编译。但有时希望程序中某一部分内容只在满足一定条件时才进行编译,如果不满足这个条件,就不编译这部分内容。这就是“条件编译”。
条件编译命令常用的有以下3种形式:
(1) #ifdef 标识符
程序段1
#else //此部分可以没有程序段2
#endif //限定#ifdef命令的范围其作用是当所指定的标识符已经被#define命令定义过,则在程序编译阶段只编译程序段1,否则编译程序段2。(2) #ifndef 标识符
程序段1
#else //此部分可以没有程序段2
#endif其作用是当所指定的标识符未被#define命令定义过,则在程序编译阶段只编译程序段1,否则编译程序段2。
(3) #if 表达式
程序段1
#else //此部分可以没有程序段2
#endif其作用是当指定的表达式值为真时就编译程序段1,否则编译程序段2。