C++函数

函数定义、声明

函数:被组织成具有特定名称的独立单元的代码块

  • 将某段操作代码组织起来,编写一次、多次使用,可以显著降低 程序规模,而且使程序更易于维护

  • 将大型程序分解成多个易于管理的小部分

    • 好的、独特的分解分解方法,会使得每个函数都是紧密的 单元,使得问题整体更加易于理解
    • top-down design:过程一般从主程序开始分解问题, 逐步求精
    • 即使函数只在程序中使用一次,定义函数依然值得
1
2
3
type name(parameters){
body
}
  • 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
    5
    int 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
      8
      int rint(){
      return 9.8;
      // 返回整形
      }
      int main(){
      double j = rint();
      // `j`被初始化为`9.8`
      }
  • 删除为函数调用创建的栈帧,其中所有局部变量被系统清理

  • 将函数返回值(若存在)代入到调用函数调用点的位置

Pointer to a Function

函数指针:函数的第一条指令地址

1
2
3
4
double *g(double);
// 返回double类型指针的函数g
double (*fn)(double);
// 返回double类型的函数指针fn
  • 将函数作为数据值使用:使设计有效的接口变得容易,允许用户 像指定数据一样指定操作,即作为回调函数

    1
    2
    void mapAll(double (*fn)(double));
    // 声明使用函数指针做参数
  • C++对函数指针自动解析引用

  • 早期计算中,程序以代码、数据完全分开形式表示
  • 现代计算机,内存同时存储的数据值、硬件执行的机器指令
  • von Neumann Architecture:冯诺伊曼体系结构,将指令存储 在内存地址中作为数据值使用,使得创建函数指针成为可能

Closure

C++需要使用创建必要数据结构实现闭包

  • 需要将数据、代码封装在一个单独实体,即对象/类

  • 为使得闭包函数一般化,最好的方法是使用function class 实现,直接将实例作为“函数” (当然可以随便实现一个函数,不影响)

  • 函数类参见cppc/basics/class
  • 闭包参见program/program_design/function_design

函数类作为参数

  • 使用函数类作为参数时,没有任何明确方法声明类型,因为任何 重载()操作符都可以作为参数

  • C++使用模板函数实现,任何以函数对象作为参数的函数

    1
    2
    template <typename FunctionClass>
    void mapAll(FunctionClass fn);
    • 传给模板函数mapAll值可以是任意类型
    • 但当编译器展开其时,若参数类型不能获得期望参数, 编译器报错

CPP 类