C++_Primer06.function

函数

自动对象(automatic object):只存在于块执行期间的对象;当块执行结束后,块中创建的自动对象的值就变成未定义的了
局部静态对象(local static object):在程序执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁;其生命周期贯穿函数调用及之后的时间

分离式编译(seperate compilation):允许我们把程序分割到几个文件中去,每个文件独立编译

对象代码(object code):编译每个文件生成各自的对象代码

CC -c factMain.cc   # 生成 factMain.o 文件

参数传递

形参的类型决定了形参和实参的交互方式,如果形参是引用类型,则它将被绑定到对应的实参上;否则实参的值拷贝后赋给形参

当形参是引用类型时,我们说它对应的实参被 引用传递(passed by reference)或函数被 传引用调用(called by reference)
引用形参是它绑定的对象的别名,即引用形参是它对应的实参的别名

当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。这样的实参是被 值传递(passed by value)或者函数被 传值调用(called by value)

传值参数

当初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,对变量的改动不会影响初始值。

指针形参

当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。这样可以间接地访问它所指的对象。

引用形参

引用类型的形参,形参是直接绑定到实参上的,修改形参等于修改了实参。

void reset(int &i){
    i = 0;
}

int main(int argc, char** argv){
    int j = 50;
    reset(j);

    cout << j << endl;      // 0

    return 0;
}

使用引用避免拷贝

使用 const 限定符防止实参被修改

void reset1(int& i){
    i = 0;
}

void reset2(const int& i){
    cout << "i in reset2: " << i << endl;
}

int main(int argc, char** argv){
    int i = 50;
    int j = 60;

    reset1(i);
    cout << i << endl;

    reset2(j);
    cout << j << endl;

    return 0;
}

编译运行:

$ g++ --std=c++11 -o ref ref.cpp
$ ./ref
0
i in reset2: 60
60

使用形参返回额外信息

// 返回字符串中某个指定字符第一次出现的位置,并计算该字符出现的总次数
string::size_type find_char(const string& s, char c, string::size_type& occurs){
    auto ret = s.size();
    occurs = 0;

    for (decltype(ret) i = 0; i != s.size(); ++i){
        if (s[i] == c){
            if (ret == s.size()){
                ret = i;
            }
            ++occurs;
        }
    }

    return ret;
}

尽量使用常量引用

使用引用而非常量引用会极大地限制函数所能接受的实参类型:

string::size_type find_char1(string& s, char c, string::size_type& occurs);
string::size_type find_char2(const string& s, char c, string::size_type& occurs);

find_char1("Hello World", '0', ctr);        // 错误,只能接受 string类型,不能接受字面量(const string) 类型
find_char2("Hello World", '0', ctr);        // 正确

另外,如果主调函数中的字符串 s 是个常量,则只能调用 find_char2 函数:

bool is_sentence(const string& s){
    string::size_type ctr = 0;
    return find_char2(s, '.', ctr) == s.size() - 1 && ctr == 1;
}

数组形参

传递数组时,实际上传递的是指向数组首元素的指针

// 这三种函数定义方式等价
void print(const int*);
void print(const int[]);
void print(const int[10]);      // 这里的维度表示我们期待数组含有多少个元素,实际上不一定

因为数组是以指针的形式传递给函数,所以一开始函数并不知道数组的确切大小,调用者应该为此提供一些额外的信息,一般有三种常用技术:

  • 使用标记指定数组长度
void print(const char* cp){
    if (cp){
        while (*cp){
            cout << * cp++ << endl;
        }
    }
}
  • 使用标准库规范
void print(const int* beg, const int* end){
    while (beg != end){
        cout << * beg++ << endl;
    }
}
  • 显式传递一个表示数组大小的形参
void print(const int ia[], size_t size){
    for (size_t i = 0; i < size; ++i){
        cout << ia[i] << endl;
    }
}

当函数不需要对数组元素执行写操作的时候,数组形参应该是指向 const 指针
只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针

数组引用形参

对数组引用时要注意不要写成引用数组

void print(int (& arr)[10]){
    for (auto i : arr){
        cout << i << endl;
    }
}

含有可变形参的函数

C++11 提供了两种方法:

  • 如果所有的实参类型相同,可以传递一个名为 initializer_list 的标准库类型
  • 如果实参类型不同,我们可以编写一种特殊的函数,也就是所谓的可变参数模板

另外,C++ 还有一种特殊的形参类型,即省略符,可以用它传递可变数量的实参,不过这种功能一般只用于与 C 函数交互的接口程序

initializer_list

类似 vector,initializer_list 也是一种模板类型,定义 initializer_list 对象时,必须说明列表中所含元素的类型:

initializer_list<string> ls;
initializer_list<int> li;

initiliazer_list 提供的操作:

操作 说明
initializer_list lst; 默认初始化;T类型元素的空列表
initializer_list lst{a,b,c...}; 列表初始化
lst2(lst) 拷贝或赋值,但不会拷贝列表中的元素;拷贝后原始列表和副本共享元素
lst2 = lst; 同上
lst.size() 元素数量
lst.begin() 指向首元素
lst.end() 指向尾元素下一个位置的指针

定义不定数量形参的函数:

void error_msg(initializer_list<string> il){
    for (auto beg = il.begin; beg != il.end(); ++beg){
        cout << * beg << " ";
    }
    cout << endl;
}

使用该函数:

if (expected != actual){
    error_msg({"functionX", expected, actual});
} else {
    error_msg({"functionX", "okay"});
}

initializer_list 形参也可以和其他参数同时使用。比如调试系统可能有个名为 ErrCode 的类用来表示不同类型的错误:

void error_msg(ErrCode e, initializer_list<string> il){
    cout << e.msg() << ": ";
    for (auto& ele : il){
        cout << ele << " ";
    }
    cout << endl;
}

调用:

if (expected != actual){
    error_msg(ErrCode(42), {"functionX", expected, actual});
} else {
    error_msg(ErrCode(42), {"functionX", "okay"});
}

省略符形参

省略符形参是为了便于 C++ 调用 C 代码而设置的,这些代码使用了名为 varargs 的 C 标准库功能。

省略符形参应当仅仅用于 C++ 和 C 代码的通用类型
大多数类型的对象在传递给省略符形参时都无法正确拷贝

省略符形参只能出现在形参列表的最后一个位置,它有如下两种形式:

// 省略符形参对应的实参无须类型检查
void foo(param_list, ...);      // 逗号可选
void foo(...);

返回类型和 return 语句

值是如何被返回的

返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量

// 给定计数值,单词和结束符,根据计数值大于1则返回复数形式,否则返回单词圆形
string make_plural(size_t ctr, string& word, string& ending){
    return (ctr > 1) ? word + ending : word;
}

该函数的返回类型是 string,意味着返回值将被拷贝到调用点。因此,该函数将返回 word 的副本或者 word 与 ending 和组成的临时 string 对象。

如果函数返回引用,则该引用仅是它所引对象的一个别名:

const string& shorterString(const string& s1, const string& s2){
    return s1.size() > s2.size() ? s2 : s1;
}

形参和返回类型都是 const string 的引用,所以不管是调用函数还是返回结构都不会真正拷贝 string 对象

不要返回局部对象的引用或指针

函数完成后,它所占用的存储空间也会被释放掉。因此函数终止意味着局部变量的引用将指向不再有效的内存区域:

// 错误示范
const string& manip(){
    string ret;
    if (!ret.empty()){
        return ret;         // 错误:ret是局部对象
    } else {
        return "Empty";     // 错误:"Empty" 是一个局部临时量
    }
}

类似地,返回局部对象的指针也是错误的,一旦函数完成,局部对象被释放,指针将指向一个不存在的对象

引用返回左值

函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值,其他返回类型得到右值。我们可以像使用其他左值那样来使用返回引用的函数的调用。特别的,我们能为返回类型是非常量引用的函数的结果赋值

char& get_val(string& str, string::size_type idx){
    return str[idx]
}

int main(int argc, char** argv){
    string s("a value");
    cout << s << endl;      // a value

    get_val(s, 0) = 'A';
    cout << s << endl;      // A value

    return 0;
}

列表初始化返回值

C++11 规定,函数可以返回列表。如果列表为空,临时量执行值初始化,否则返回的值由返回类型决定:

// 根据不同的错误,返回不同数量的字符串
vector<string> process(){
    // some code
    if (expected.empty()){
        return {};                                  // 返回一个空的 vector 对象
    } else if (expected == actual){
        return {"functionX", "okay"};               // 返回列表初始化的 vector 对象
    } else {
        return {"functionX", expected, actual};
    }
}

如果函数返回的是内置类型,则花括号包围的列表最多包含一个值,而且该值所占空间不应该大于目标类型的空间;
如果函数返回的是类类型,由类本身定义初始值如何使用

主函数 main 的返回值

main 函数的返回值是个例外,它可以没有 return 语句而直接结束,此时编译器将隐式地插入一条返回0的 return 语句。
main 函数的返回值可以看做是状态指示器,返回0表示成功,返回其他值表示执行失败。

返回数组指针

数组不能被拷贝,所以函数不能返回数组。但是函数可以返回数组的指针或引用

类型别名

最直接的方法是使用类型别名:

typedef int arrT[10];
// 等价于
using arrT = int[10];

// 使用:
arrT* func(int i);      // func 返回一个指向含有10个整数的数组的指针

声明一个返回数组指针的函数

想要在声明 func 时不使用类型别名,必须要牢记数组的维度,并且数组的维度必须跟在函数名之后:

Type (*function(parameter_list))[dimension];

如果没有外层的括号把 * 和函数名括起来,则函数的返回值将是指针的数组

int (* func(int i))[10];

使用尾置返回类型

trailing return type. C++11 新标准,简化上述声明的方法,任何函数的定义都能使用尾置返回,但这种形式对于返回类型比较复杂的函数最有效:

auto func(int i) -> int(* )[10];

func 接受一个int类型的参数,返回一个指针,指向一个含有10个整数的数组

使用 decltype

int odd[] = {1,2,3};
int even[] = {4,5,6};

// 返回一个指针,指向含有3个整数的数组
decltype(odd)* arrPtr(int i){
    return (i%2) ? &odd : &even
}

函数重载

overload. 同一个作用域内,函数名相同,而形参列表不同

main 函数不能重载

const 形参的重载

顶层 const 不影响传入函数的对象,所以带顶层 const 的形参和不带的形参不属于重载:

// 以下两个等价:
Record lookup(Phone);
Record lookup(const Phone);

// 以下两个等价:
Record lookup(Phone*);
Record lookup(Phone* const);

对于指针或引用形参,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,因为它们的 const 是底层的:

// 以下两个属于重载:
Record lookup(Phone&);
Record lookup(const Phone&);    // 常量引用

// 以下两个属于重载:
Record lookup(Phone*);
Record lookup(const Phone*);    // 指向常量的指针

const_cast 和重载

const string& shorterString(const string& s1, const string& s2){
    return s1.size() <= s2.size() ? s1 : s2;
}

上述函数的参数和返回类型都是 const string 的引用,我们想要对两个非常量的 string 实参调用这个函数,但返回的结果仍然是 const string 的引用,此时我们需要重载,当它的实参不是常量时,得到的结果是一个普通引用:

string& shorterString(string& s1, string& s2){
    auto& r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
    return const_cast<string&>(r);
}

完整代码:

#include <iostream>
#include <string>

using namespace std;

const string& shorterString(const string& s1, const string& s2);
string& shorterString(string& s1, string& s2);

int main(int argc, char** argv){
    string s1 = "abc";
    string s2 = "abcd";

    cout << shorterString(s1, s2) << endl;

    return 0;
}

const string& shorterString(const string& s1, const string& s2){
    return s1.size() <= s2.size() ? s1 : s2;
}

string& shorterString(string& s1, string& s2){
    auto& r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
    return const_cast<string&>(r);
}

调用重载的函数

函数匹配(function matching),或重载确定(overload resolution):编译器首先将调用的实参与重载集合中每一个函数的形参进行比较,然后根据比较的结果决定到底调用哪个函数。

当调用重载函数时,有三种可能的结果:

  • 编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码
  • 找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配(no match)的错误
  • 有多于一个函数可以匹配,但每一个都不是明显的最佳选择,此时也将发生错误,称为二义性调用(ambiguous call)

特殊用途语言特性

  • 默认实参
  • 内联函数
  • constexpr 函数

默认实参

typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, char background=' ');

一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值

当设计含有默认实参的函数时,应当尽量让不怎么使用默认值的形参出现在前面,让经常使用默认值的形参出现在后面

默认实参声明

函数的声明通常是将其放在头文件中,并且一个函数只声明一次,但多次声明同一个函数也是合法的。
函数的后续声明只能为那些没有默认值的形参添加默认实参,而且该形参的右侧所有形参都必须有默认值:

// 已有函数声明
string screen(sz, sz, char = ' ');

string screen(sz, sz, char = '*');      // 错误,重复声明
string screen(sz = 24, sz = 80, char);  // 正确,添加默认实参

默认实参初始值

局部变量不能作为默认实参。除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参:

// wd, def, ht 的声明必须出现在函数之外
sz wd = 80;
char def = ' ';
sz ht();
string screen(sz = ht(), sz = wd, char = def);

用作默认实参的名字在函数声明所在的作用域内解析,而这些名字的求值过程发生在函数调用时:

void f2(){
    def = '*';              // 改变了实参的默认值
    sz wd = 100;            // 隐藏了外层定义的 wd,但没有改变默认值
    window = screen();      // screen(ht(), 80, '*')
}

内联函数和 constexpr 函数

在大多数机器上,一次函数调用其实包含着一系列工作:

  • 调用前要先保存寄存器,并在返回时恢复;
  • 可能需要拷贝实参;
  • 程序转向一个新的位置继续执行

内联函数可以避免函数调用的开销。如果把 shorterString 函数定义成内联函数,则以下调用在编译时会展开:

cout << shorterString(s1, s2) << endl;
// 在编译时展开成:
cout << (s1.size() < s2.size() ? s1 : s2) << endl;

在 shorterString 函数的返回类型前面加上关键字 inline,就可以将它声明成内联函数了:

inline const string&
shorterString(const string& s1, const string& s2){
    return s1.size() <= s2.size() ? s1 : s2;
}

内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求

一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数。很多编译器都不支持内联递归函数。

constexpr 函数

constexpr 函数是指能用于常量表达式的函数。

定义的方法与其他函数类似,不过要遵循几个约定:

  • 函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条 return 语句
constexpr int new_sz(){return 42;}
constexpr int foo = new_sz();

编译器在编译阶段把对 constexpr 函数的调用替换成其结果值。为了能在编译过程中随时展开,constexpr 函数被隐式地指定为内联函数

constexpr 函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。比如空语句、类型别名以及 using 声明

我们允许 constexpr 函数的返回值并非一个常量:

constexpr size_t scale(size_t cnt){return new_sz() * cnt;}
// 如果实参是常量表达式,则它的返回值也是常量表达式;反之则不然:
int arr[scale(2)];      // 正确,scale(2) 是常量表达式
int i = 2;
int a2[scale(i)];       // 错误,scale(i) 不是常量表达式

当把 scale 函数用在需要常量表达式的上下文中时,由编译器负责检查函数的结果是否符合要求。constexpr 函数不一定返回常量表达式。

const 与 constexpr:
const并未区分出编译期常量和运行期常量
constexpr限定在了编译期常量
constexpr修饰的函数,返回值不一定是编译期常量
constexpr修饰的函数,简单的来说,如果其传入的参数可以在编译时期计算出来,那么这个函数就会产生编译时期的值。但是,传入的参数如果不能在编译时期计算出来,那么constexpr修饰的函数就和普通函数一样了。不过,我们不必因此而写两个版本,所以如果函数体适用于constexpr函数的条件,可以尽量加上constexpr。

和其他函数不一样的是,内联函数和 constexpr 函数可以在程序中多次定义。不过对于某个给定的内联函数或 constexpr 函数来说,它的多个定义必须完全一致。基于这个原因,内联函数和 constexpr 函数通常定义在头文件中。

调试帮助

assert 预处理宏

所谓预处理宏其实是一个预处理变量,其行为类似内联函数。它的用法:

assert(expr);

首先对 expr 求值,如果表达式为假,assert 输出信息并终止程序的执行;如果为真则什么也不做。

assert 宏定义在 cassert 头文件中,预处理名字由预处理器而非编译器管理,所以我们可以直接使用而不需要提供 using 声明。宏名字在程序内必须唯一,含有 cassert 头文件的程序不能再定义名为 assert 的变量、函数或其他实体。

NDEBUG 预处理变量

assert 的行为依赖于一个名为 NDEBUG 的预处理变量的状态。如果定义了 NDEBUG,则 assert 什么也不做。默认状态下没有定义 NDEBUG,此时 assert 将执行运行时检查。

关闭调试状态的方法有两种:

  • 使用 #define 语句定义 NDEBUG
  • 使用编译器提供的命令行选项:
$ CC -D NDEBUG main.C       # 对于 windows,使用 /D

这等价于在 main.C 文件的一开始写 #define NDEBUG

assert 应该仅用于验证那些确实不可能发生的事情。我们可以把它当做调试程序的一种辅助手段,但是不能用它替代真正的运行时逻辑检查,也不能替代程序本身应该包含的错误检查。

也可以利用 NDEBUG 编写自己的条件测试代码:

void print(const int ia[], size_t size){
#ifndef NDEBUG
    // __func__ 是编译器定义的一个局部静态变量,用于存放函数的名字
    cerr << __func__ << ": array size is " << size << endl;
#endif
    // ...
}

除了 __func__ 外,预处理器还定义了另外4个用于程序调试的很有用的变量:

变量名 说明
__FILE__ 存放文件名的字符串字面量
__LINE__ 存放当前行号的整型字面值
__TIME__ 存放文件编译时间的字符串字面值
__DATE__ 存放文件编译日期的字符串字面值

函数匹配

函数匹配顺序

以如下函数组为例:

void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6);     // 调用 void f(double, doube)

确定候选函数和可行函数

函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数称为 候选函数(candidate function)。候选函数具备两个特征:函数名相同;其声明点在调用点可见。此时有4个名为 f 的候选函数。

第二步是考察实参,然后从候选函数集中选出这组实参调用的函数,这些新选出的函数称为 可行函数(viable function)。可行函数也有两个特征个:形参数量相同;每个实参与形参类型相同或能转换成形参的类型。此时根据参数数量排除掉两个,剩下第2个和第4个。

如果没找到可行函数,编译器将报告无匹配函数的错误

寻找最佳匹配

第三步是从可行函数中选择与本次调用最匹配的函数。逐一检查函数调用提供的实参,寻找形参类型与实参类型最匹配的那个可行函数。如果调用 f(int),实参不得不从 double 转换成 int。而另一个 f(double, double) 则与实参精确匹配。因此编译器把 f(5.6) 解析成对含有两个 double 形参的函数的调用。

精确匹配需要的条件是有且只有一个函数满足下列条件,则匹配成功:

  • 该函数每个实参的匹配都不劣于其他可行函数需要的匹配
  • 至少有一个实参的匹配优于其他可行函数提供的匹配

如果在检查了所有实参之后没有任何一个函数脱颖而出,则该调用是错误的。编译器将报二义性调用的信息。

实参类型转换

编译器将实参到形参的转换划分为几个等级,具体顺序:

  • 精确匹配
    • 类型相同
    • 实参从数组类型或函数类型转换成对应的指针类型
    • 向实参添加顶层 const 或从实参中删除顶层 const
  • 通过 const 转换实现的匹配
  • 通过类型提升实现的匹配
  • 通过算数类型转换或指针转换实现的匹配
  • 通过类类型转换实现的匹配

需要类型提升和算数类型转换的匹配

内置类型的提升和转换可能在函数匹配时产生意想不到的结果
在设计良好的系统中,函数很少出现类似下面例子的形参

void ff(int);
void ff(short);
ff('a');            // char 提升为 int,调用 ff(int)
void manip(long);
void manip(float);
manip(3.14);        // 错误:二义性调用

函数指针

声明一个可以指向该函数的指针,只需要用指针替换函数名即可:

// 函数原型
bool lengthCompare(const string&, const string&);

// 定义 pf
bool (* pf)(const string&, const string&);
// 给 pf 赋值,使 pf 是指向 lengthCompare 的指针;以下两种写法等价:
pf = lengthCompare;
pf = &lengthCompare;

pf = 0;     // 使 pf 不指向任何函数

调用函数指针,以下三种写法等价:

bool b1 = pf("hello", "goodbye");
bool b2 = (* pf)("hello", "goodbye");
bool b3 = lengthCompare("hello", "goodbye");

函数指针形参

不能定义函数类型的形参,但形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用:

// 两种定义方式等价
void useBigger(const string&, const string&, bool pf(const string&, const string&));
void useBigger(const string&, const string&, bool (*pf)(const string&, const string&));

我们可以直接把函数作为实参使用,此时它会自动转换成指针:

useBigger(s1, s2, lengthCompare);

直接使用函数名显得冗长,可以使用类型别名和 decltype 来简化:

// 函数类型别名
typedef bool Func(const string&, const string&);
typedef decltype(lengthCompare) Func2;              // 与上一行等价

// 函数指针的类型别名
typedef bool (* FuncP)(const string&, const string&);
typedef decltype(lengthCompare) *FuncP2;            // 与上一行等价

// 用函数指针当做形参
void useBigger(const string&, const string&, Func);
void useBigger(const string&, const string&, FuncP2);   // 与上等价

返回指向函数的指针

要想声明一个返回函数指针的函数,最简单的方法是使用类型别名:

using F = int(int*, int);       // F 是函数类型,不是指针
using PF = int(* )(int*, int);  // PF 是指针类型

与函数类型的形参不一样的是,返回类型不会自动地转换成指针,必须显式地将返回类型指定为指针:

PF f1(int);         // 正确
F f1(int);          // 错误,F不是指针
F* f1(int);         // 正确

或者,直接把函数指针写在返回类型上:

int (* f1(int))(int*, int);

从内向外,f1 是个函数,需要一个 int 参数
返回的是一个指针,指向一个函数,这个函数有两个参数
第一个参数是 int 指针,第二个参数是 int 值,返回值是 int

使用 auto 和 decltype 用于函数指针

如果我们明确知道返回的函数是哪一个,就能使用 decltype 简化书写

string::size_type sumLength(const string&, const string&);
string::size_type largerLength(const string&, const string&);

// 根据形参的取值,getFunc 函数返回指向 sumLength 或 largerLength 的指针
decltype(sumLength)* getFunc(const string&);

练习 6.54

  • 编写函数的声明,令其接受两个int,返回int,然后声明一个vector对象,令其元素指向该函数的指针。
  • 编写四个函数,分别是加减乘除,将它们保存在上述 vector 中
  • 调用这四个函数
#include <iostream>
#include <vector>

using namespace std;

int add(int, int);
int subtract(int, int);
int multiply(int, int);
int divide(int, int);

typedef int(* pf)(int, int);
using pf1 = int(* )(int, int);

int main(int argc, char** argv){
    vector<int(* )(int, int)> v1;

    v1.push_back(&add);
    v1.push_back(&subtract);
    v1.push_back(&multiply);
    v1.push_back(&divide);

    cout << v1[0](5, 2) << endl;
    cout << v1[1](5, 2) << endl;
    cout << v1[2](5, 2) << endl;
    cout << v1[3](5, 2) << endl;

    return 0;
}

int add(int a, int b){
    return a + b;
}
int subtract(int a, int b){
    return a - b;
}
int multiply(int a, int b){
    return a * b;
}
int divide(int a, int b){
    return a / b;
}

output:

7
3
10
2
posted @ 2021-11-09 21:41  keep-minding  阅读(83)  评论(0)    收藏  举报