函数定义的语法形式 | |||||||||||||||
语法 | 说明 | ||||||||||||||
类型标识符 函数名 (含类型说明符的形式参数列表){ 语句序列; ………… return; } | 形参表语法的定义形式: 例如:<typel>name1,<type2>name2,<typen>namen 尖括号里面的type是表示形参的类型,也就是说形参表里面的每个参数可 以拥有相同类型或者不同类型,所以要为每一个参数去说明类型,其中 name1,name2这些是参数名,形参是被初始化的内部变量,寿命和可见 性仅限于函数体内部(形参实际上相当于一个局部变量)。 函数还需要定义返回值: 函数的返回值就是计算的结果,也就是说谁需要使用这个函数做计算,哪一段 程序要调用这个函数做计算,那么计算结果就要返回给调用者程序中去,怎么 返回呢?我们看在语句序列的最后一句通常应该是一个return语句,用来返回 计算结果,那么返回值类型是什么呢?返回值类型就在函数名前面这个类型标识 符定义(表示返回值类型、由return语句给出返回值,若无返回值、则写void 、不必写return语句。)所以这个类型表示符可以用来定义这个函数的返回值类 型。 | ||||||||||||||
函数原型声明 | |||||||||||||||
类型标识符 被调用函数名(含类型说明的形式参数列表) | 调用函数前需要先声明函数原型, | ||||||||||||||
函数的调用 | |||||||||||||||
函数名(实际参数列表) | 调用的时候使用函数名、和实际参数列表。 在调用表达式执行的时候,使用调用表达式中的实际参数列表来初始化函数定义中的形式参数列表。 | ||||||||||||||
函数的嵌套调用 | |||||||||||||||
函数名(实际参数列表){ 调用函数1; } 类型标识符 函数名1 (形式参数列表){ 调用函数2; ………… return 调用者; }//结果返回给调用者 类型标识符 函数名2 (形式参数列表){ 调用函数n; ………… return 函数1; }//结果返回给函数1 …………………… 类型标识符 函数名n (形式参数列表){ 语句序列; ………… return 函数2; }//结果返回给函数2 | 在一个函数的函数体中,调用另一个函数 也就是说在函数1被调用的时候,函数1又去调用了其它函数。 | ||||||||||||||
函数的递归调用 | |||||||||||||||
函数直接或间接调用自身 注意: 函数的递归调用是先递推在回归执行顺序; 例如: 计算n的阶乘代码如下: 函数类型标识符 函数名 (形式参数){ 类型说明符 变量名; if (递归终结条件){//递归终结点 变量名 = 表达式; }else{ 变量名 = 函数名(n - 1) * n; } return 变量名; } int main (){ 类型说明符 变量名 = 函数名(n); }
这个例子就是自己调用自己的递归调用,执行顺序是,先从函数调用者程序中调用函数名的执行程序,函数名开始执行函数体直到满足递归终结条件,然后开始回推。
第一阶段: 将原有问题不断分解为新的子问题,逐渐从未知向已知推进,到已知的条件,既递归结束的条件,这是递推阶段结束。 第二阶段: 回归。从已知的条件出发,按照递推的逆过程,逐一求值回归,最后达到递推的开始处,结束回归阶段,完成递归调用。 可以理解为:先执行调用1进入被调用函数体,接下来被调用函数体又调用了自身(第二次被调用)一直到n(递归终结点),然后开始从n(递归终结点)向回调用一直到(第二次被调用)这时候返回到调用1的程序。 递归调用必需要使用控制语句来在合适的时候终止递归过程,否则会无限递归下去陷入死循环, 控制语句可以是选择语句、也可以是循环语句或者其他语句。
注意: 递归调用是会不停调用的,使用不当会死循环。 所以调用时要求至少满足两个条件:
| |||||||||||||||
函数的参数传递 | |||||||||||||||
在函数被调用的时候才给形参分配存储单元的 实参可以是常量、变量或者表达式 实参类型必需与形参相符(如果不相符编译器会试图进行类型转换,将实参类型转换成形参类型,看看 是否是安全合法的,如果可以转换就做隐含转换,否则报错) 值传递是传递参数值,既单向传递(在函数体重如果对这个参数值有任何修改都只修改了形参,它触及 不到主调函数中的实参,因为它是单向传递把值赋给形参他们之间 就切断联系了) 引用传递可以实现双向传递 常引用作参数可以保障实参数据的安全 | |||||||||||||||
类型说明符 &引用名称 = 变量名; | 引用(&)是标识符的别名 例如: int i, j; int &ri = i;//定义int引用ri,并初始化为变量i的引用 定义一个引用时,必需同时对它进行初始化,使它指向一个已存在的对象 如果我们做以下操作: int j = 10; ri = j;//那么就相当于表达式(i = j) 引用可以作为形参 | ||||||||||||||
含有可变参数的函数 | |||||||||||||||
initializer_list<类型说明符> 变量名 | 如果所有参数类型相同,可以传递一个名为initializer_list的标准库类型 initializer_list是一种标准库类型,用于表示某种特定类型的值的数组,该类型定义在同名的头文件中。
使用initializer_list模版时,我们需要在模版名字后面跟一对尖括号,尖括号内给出类型参数。 例如: initializer_list<string> ls;//initializer_list的元素类型是string iniaitlizer_list<int> li;//initializer_list的元素类型是int
iniaitlizer_list比较特殊的一点是,其对象中的元素永远是常量值,我们无法改变iniaitlizer_list对象中的元素值
含有iniaitlizer_list形参的函数也可以同时拥有其他形参 | ||||||||||||||
内联函数 | |||||||||||||||
inline | 内联函数体内不能有循环语句和switch语句 内联函数的定义必需出现在内联函数第一次被调用之前 对内联函数不能进行异常接口声明 | ||||||||||||||
constexpr函数 | |||||||||||||||
constexpr 类型说明符 函数名(){ 函数体 } | constexpr修饰的函数,在其所有参数都是constexpr时一定返回constexpr,这样的函数函数体中只允许有一条return语句 我们可以使用constexpr类型的函数来初始化使用constexpr修饰的常量表达式 | ||||||||||||||
带默认参数值的函数 | |||||||||||||||
类型说明符 函数名(类型说明符 形参名 = 默认值,类型说明符 形参名 = 默认值){ 函数体 } | 如果预先设置默认形参值,那么调用时如果给出实参值,就采用实参值来计算,如果不给出实参值那么就采用形参默认值来进行计算(注意:如果定义了多个带默认值的形参而仅仅传递一个或多个,并且少于形参列表的形参数时,那么传进来的实参会从左至右一次初始化形参,后面没有传递实参进来的形参使用形参自带的默认值)
有默认参数的形参必需列在形参列表的最右边,既带默认参数值的形参的右面不能有无默认值的形参
调用时实参与形参的结合次序是从左至右
如果一个函数有原型声明,且原型声明在定义之前,则默认参数值应在函数原型声明中给出; 如果只有函数的定义,或函数定义在前则默认参数值可以在函数定义中给出。 | ||||||||||||||
函数重载 | |||||||||||||||
形参类型不同、形参个数不同 函数重载的形参必须不同:个数不同或类型不同。 编译程序将根据实参和形参的类型及个数的最佳匹配来选择调用哪一个函数。
注意: 参数名是不能用区分重载函数的 编译器不以返回值类型来区分重载函数 不要将不同功能的函数声明为重载函数,以免出现调用结果的误解、混淆、这样不好。 | |||||||||||||||
C++系统函数 | |||||||||||||||
使用系统函数时要包含相应的头文件 例如: cmath(数学函数)
使用系统函数应该注意以下两点: (1)编译环境提供的系统函数分为两类,一类是标准C++的函数,另一类是非标准C++的函数,它是当前操作系统或编译环境中所特有的系统函数。例如,cmath中所声明的sin,cos,tan等函数都是标准C++的函数。编程时应优先使用标准C++的函数,因为标准C++函数是各种编译环境所普遍支持的,只使用标准C++函数的程序具有很好的可移植性。 提示 标准C++函数,很多是从标准C继承而来的。上例中使用的cmath头文件中的前缀c,就用来表示它是一个继承自标准C的头文件,类似的头文件还有cstdlib,cstdio,ctime等。标准C中,这些头文件的名字分别是math.h,stdlib.h,stdio.h,time.h等,为了保持对C程序的兼容性,C++中也允许继续使用这些以.h为后缀的头文件。保留这些头文件仅仅是出于兼容性考虑,在编写C++程序时,应尽量使用不带.h后缀的头文件。
(2)有时也需要使用一些非标准C++的系统函数,例如,在处理和操作系统相关的事务时,常常需要调用当前操作系统特有的一些函数。不同的编译系统提供的函数有所不同。即使是同一系列的编译系统,如果版本不同系统函数也会略有差别。
|