C++函数
函数定义、声明
函数:被组织成具有特定名称的独立单元的代码块
将某段操作代码组织起来,编写一次、多次使用,可以显著降低 程序规模,而且使程序更易于维护
将大型程序分解成多个易于管理的小部分
- 好的、独特的分解分解方法,会使得每个函数都是紧密的 单元,使得问题整体更加易于理解
- top-down design:过程一般从主程序开始分解问题, 逐步求精
- 即使函数只在程序中使用一次,定义函数依然值得
1 | type name(parameters){ |
name
:函数名parameters
:逗号分隔的函数形参列表
Parameters
形参:调用函数时用以传递实参的占位符
- 类似局部变量,但是在调用时使用实参进行初始化
- 如果需要使用形参值,可以忽略其形参名,即使是在
函数实现中也可以忽略,如:
++
后缀重载
Default Parameter
默认形参:具有默认值的形参,调用时可以不给其传递实参值
- 默认形参只能出现在函数声明中,不能出现在函数定义中
- 默认形参只能出现在形参列表末尾
- 默认形参在C++中有滥用的问题,更倾向使用函数重载完成默认 形参的功能
- 默认形参、函数重载同时使用可能导致编译器无法识别应该调用 何者而报错
Value Parameter
值参数:函数调用时,被调函数中值形参将获得主调函数的实参的 值拷贝
- 被调函数中传入的实参变量值仅改变被调用函数局部形参 的值,对主调函数中实参变量的值没有影响
Reference Parameter
引用参数&
:函数调用时,被调函数中引用形参获得主调函数中实参
引用
主调函数、被调函数共享实参变量的统一存储空间,不需要复制 实参变量中的值,有时更高效
对应引用形参的实参必须是可赋值的量,如:变量名
实参可以是指针,即参数可以同时有
* &
1
int insertAVL(BSTNode * & t, const string & key);
常用于
- 需要保存函数对参数值的修改
- 函数需要返回多个值:并通过实参列表向函数传递、获取值
Constant Reference Parameter
常量引用调用const &
:常量引用作为函数参数调用
传递对象时,常量引用调用通常优于传统引用调用、值调用, 提供了引用传递的高效性、值传递的安全性
需要注意参数中
const
关键字的位置1
2
3
4
5int strlen(const char * cptr);
// `const`后是类型名,表明`cptr`是指向`const char`指针
// 不能改变`cptr`指向的支付串内容
int strlen(char * const cptr);
// `const`后是形参名,表明`cptr`常量,其值不能改变使用常量引用需要参数类是constant correct,能够提供更多 的关于类中定义的方法的信息
Prototype
函数原型/函数声明:函数定义的首行加上分号结尾组成
提供编译器大部分情况下仅仅需要的形参、返回值类型
函数原型中形参名可选,但是好的形参名有助于可读性
如果函数先定义后调用,可以不需要编写函数原型,但这种代码 风格和自顶向下的程序设计风格相悖
Signature
函数签名:函数的形参模型
- 和函数原型相比,不包括返回值类型
Overloading
函数名重载:使用相同名字的不同版本函数
函数名相同、函数参数列表不同是合法的,即函数签名不同即 合法(函数原型不同不一定合法,返回值类型不同)
- 形参数量
- 形参类型
编译器遇到调用函数的函数名指代不唯一时,编译器会检查所传 实参,选择最合适的函数版本
Calling
使用函数名调用函数代码(块)的行为
函数被调用后将会获得函数argument提供的值,执行函数功能
返回函数调用点:记忆主调程序工作情况,以便程序 返回函数调用的确切位置是函数调用机制的主要特性之一
- argument:实参,调用函数时的表达式,用于向函数传递信息
- 调用函数前必须对函数提供声明或定义,以使编译器可以判断 函数调用是否与其定义兼容
函数调用步骤
主调函数将实参与自己上下文中的若干局部变量绑定来 计算每个参数值
- 实参通常为表达式,计算其值时可能涉及操作符、其他函数 调用
- 新的函数开始执行前,主调函数会对传如的实参合法性进行 验证
系统为新函数所需的所有局部变量(包括形参)创建新的存储 空间
- 这些变量将被分配在内存中stack frame区域中
每个实参值将被传入到函数相应的形参变量中
- 对于包含多个形参的函数,实参对形参的值拷贝将按照 对应函数形参顺序执行
- 如有必要,编译器将像变量赋值一样,执行从实参到 形参的类型转换
- 对引用参数,栈帧会存储一个指针指向该值内存单元
执被调函数体中语句,直到遇到
return
语句或没有多余语句如果函数有返回值,函数体内
return
语句表达式的值将被计算 ,作为返回值返回给主调函数- 如有必要,编译器将执行数值的类型转换,确保返回值
符合被调函数值的类型要求(被调函数返回之前转换)
1
2
3
4
5
6
7
8int rint(){
return 9.8;
// 返回整形
}
int main(){
double j = rint();
// `j`被初始化为`9.8`
}
- 如有必要,编译器将执行数值的类型转换,确保返回值
符合被调函数值的类型要求(被调函数返回之前转换)
删除为函数调用创建的栈帧,其中所有局部变量被系统清理
将函数返回值(若存在)代入到调用函数调用点的位置
Pointer to a Function
函数指针:函数的第一条指令地址
1 | double *g(double); |
将函数作为数据值使用:使设计有效的接口变得容易,允许用户 像指定数据一样指定操作,即作为回调函数
1
2void mapAll(double (*fn)(double));
// 声明使用函数指针做参数C++对函数指针自动解析引用
- 早期计算中,程序以代码、数据完全分开形式表示
- 现代计算机,内存同时存储的数据值、硬件执行的机器指令
- von Neumann Architecture:冯诺伊曼体系结构,将指令存储 在内存地址中作为数据值使用,使得创建函数指针成为可能
Closure
C++需要使用创建必要数据结构实现闭包
需要将数据、代码封装在一个单独实体,即对象/类
为使得闭包函数一般化,最好的方法是使用function class 实现,直接将实例作为“函数” (当然可以随便实现一个函数,不影响)
- 函数类参见cppc/basics/class
- 闭包参见program/program_design/function_design
函数类作为参数
使用函数类作为参数时,没有任何明确方法声明类型,因为任何 重载
()
操作符都可以作为参数C++使用模板函数实现,任何以函数对象作为参数的函数
1
2template <typename FunctionClass>
void mapAll(FunctionClass fn);- 传给模板函数
mapAll
值可以是任意类型 - 但当编译器展开其时,若参数类型不能获得期望参数, 编译器报错
- 传给模板函数