Cpp学习23 函数进阶

铁名_IronName Lv5

函数指针

函数存在于内存中指定的地址(因此它们是左值)。
当函数名被引用(不带括号)时,C++ 会将函数转换为函数指针(保存函数的地址)。

要创建一个常量函数指针,const 放在星号后面:

1
2
int (*fcnPtr)();
int (*const fcnPtr)();

将函数赋值给函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int foo(int x)
{
return x;
}

int goo(int x)
{
return 6;
}

int main()
{
int (*fcnPtr)(int){ &foo }; // fcnPtr points to function foo
fcnPtr = &goo; // fcnPtr now points to function goo

if (fcnPtr) // make sure fcnPtr isn't a null pointer
fcnPtr(5); // otherwise this will lead to undefined behavior
// or (*fcnPtr)(5)
return 0;
}

函数指针的类型(参数类型和返回类型)必须与函数的类型匹配。
&不加也行,但是加上了能明显知道这是个地址。

默认参数对通过函数指针调用的函数不起作用

函数指针最有用的用途之一是将一个函数作为参数传递给另一个函数。用作参数的函数有时被称为回调函数 。callback functions.
(std::sort 中使用的 cmp 排序函数就是这个吧)

1
2
void selectionSort(int* array, int size, bool (*comparisonFcn)(int, int))
void selectionSort(int* array, int size, bool comparisonFcn(int, int))

回调函数也能设默认值。

不得不承认,函数指针的语法确实很丑陋。不过,我们可以使用类型别名,让函数指针看起来更像普通变量:

1
using ValidateFunction = bool(*)(int, int);

使用 std::function

定义和存储函数指针的另一种方法是使用 std::function,它是标准库的一部分。<functional> 头部。要使用此方法定义函数指针,请像这样声明一个 std::function 对象:

1
2
#include <functional>
bool validate(int x, int y, std::function<bool(int, int)> fcn); // std::function method that returns a bool and takes two int parameters

同样地,可以使用类型别名提高可读性。
 
 auto 关键字也可以推断函数指针的类型。
 
由于声明函数指针的原生语法不够简洁且容易出错,我们建议使用 std::function

Quiz程序片段

my:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <functional>
//略
using ArithmeticFunction = std::function<int(int, int)>;
ArithmeticFunction getArithmeticFunction(char& op)
{
switch (op)
{
case '+':return &add;
case '-':return &subtract;
case '*':return &multiply;
case '/':return &divide;
}

return nullptr;
}

int main()
{
int x{ getInteger() };
char op{ getOperation() };
int y{ getInteger() };

std::cout << "The result is " << getArithmeticFunction(op)(x, y);

return 0;
}

参考答案,确实更安全:

1
2
3
ArithmeticFunction fcn{ getArithmeticFunction(op) };
if (fcn)
std::cout << x << ' ' << op << ' ' << y << " = " << fcn(x, y) << '\n';

栈和堆

程序使用的内存通常被划分为几个不同的区域,称为段 segments:

  • 代码段(也称文本段)是编译后的程序在内存中存放的位置。代码段通常是只读的。
  • bss 段(也称为未初始化数据段),其中存储零初始化的全局变量和静态变量。
  • 数据段(也称为已初始化数据段),用于存储已初始化的全局变量和静态变量。
  • 堆,动态分配的变量就是从这里分配的。
  • 调用栈,其中存储着函数参数、局部变量和其他与函数相关的信息。

堆段(也称为“空闲存储区”)用于跟踪动态内存分配所占用的内存。
调用栈跟踪从程序开始到当前执行点所有已调用但尚未终止的函数,并负责所有函数参数和局部变量的分配。

递归

递归函数是指调用自身的函数。

命令行参数

命令行参数是可选的字符串参数,由操作系统在程序启动时传递给程序。程序可以将它们用作输入(也可以忽略它们)。就像函数参数允许一个函数向另一个函数提供输入一样,命令行参数也允许用户或程序向 程序 提供输入。

在 Microsoft Visual Studio 中,右键单击解决方案资源管理器中的项目,然后选择“属性”。打开“配置属性”树状元素,然后选择“调试”。在右侧窗格中,有一行名为“命令参数”。您可以在此处输入用于测试的命令行参数,这些参数会在程序运行时自动传递给程序。

1
int main(int argc, char* argv[])

argc 是一个整数参数,用于计数传递给程序的参数数量(可以理解为:argc = 参数数量 )。argc 的值始终至少为 1,因为第一个参数始终是程序本身的名称。用户提供的每个命令行参数都会使 argc 的值加 1。
argv 用于存储实际的参数值(可以理解为:argv = 参数值 ,尽管它的正式名称是“参数向量”)。虽然 argv 的声明看起来有些复杂,但它实际上只是一个 C 风格的字符指针数组(每个指针指向一个 C 风格的字符串)。该数组的长度为 argc。

命令行参数始终以字符串形式传递,即使提供的值本质上是数值。要将命令行参数用作数字,必须将其从字符串转换为数字。

省略号(以及为什么要避免使用它们)

由于省略号很少使用,而且可能存在危险,我们建议避免使用省略号。

“此函数接受可变数量的参数!”太自由了,出的bug也自由。
类型检查已暂停“。

那还看啥了,拜拜了。

lambda 表达式(匿名函数)

lambda 表达式 (也称为 lambda 或闭包 )允许我们在一个匿名函数内部定义另一个函数。嵌套非常重要,因为它既可以避免命名空间污染,又能使函数尽可能靠近其使用位置进行定义(从而提供更多上下文)。

lambda 表达式的形式如下:

1
2
3
4
[ captureClause ] ( parameters ) -> returnType
{
statements;
}

用 lambda 表达式定义初始化 lambda 变量,然后在后面使用它。

1
2
3
4
5
6
auto isEven{
[](int i)
{
return (i % 2) == 0;
}
};

lambda 表达式没有我们可以显式使用的类型。所以,对lambda函数用 auto 吧。

传给函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Case 2: use a function template with a type template parameter
template <typename T>
void repeat2(int repetitions, const T& fn)
{
for (int i{ 0 }; i < repetitions; ++i)
fn(i);
}

// Case 3: use the abbreviated function template syntax (C++20)
void repeat3(int repetitions, const auto& fn)
{
for (int i{ 0 }; i < repetitions; ++i)
fn(i);
}

这是个通用的(用了auto推导参数类型) lambda 表达式的例子:

1
2
3
4
const auto sameLetter{ std::adjacent_find(months.begin(), months.end(),
[](const auto& a, const auto& b) {
return a[0] == b[0];
}) };

返回类型:

1
2
3
4
5
6
7
// note: explicitly specifying this returns a double
auto divide{ [](int x, int y, bool intDivision) -> double {
if (intDivision)
return x / y; // will do an implicit conversion of result to double
else
return static_cast<double>(x) / y;
} };

对于常见的运算(例如加法、取反或比较),您无需编写自己的 lambda 表达式,因为标准库提供了许多基本的可调用对象,可以直接使用。这些对象定义在 <functional> 头文件中。

1
2
3
4
5
const auto best { // returns an iterator
std::max_element(arr.begin(), arr.end(), [](const auto& a, const auto& b) {
return a.points < b.points;
})
};

Lambda 捕获

Lambda 函数只能访问在 Lambda 函数外部定义的某些类型的对象,包括具有静态存储持续时间的对象(例如全局变量和静态局部变量)以及 constexpr 对象。

The capture clause

捕获子句用于(间接地)让 Lambda 函数访问其通常无法访问的外部作用域中的变量。
也就是放到方括号里:

1
2
3
4
// Capture @search                                vvvvvv
auto found{ std::find_if(arr.begin(), arr.end(), [search](std::string_view str) {
return str.find(search) != std::string_view::npos;
}) };

lambda 表达式不允许修改这些捕获对象。

为了允许修改已捕获的变量,我们可以将 lambda 标记为 mutable 。but这里的修改是变量的副本,除非捕获的是引用(但也不需要mutable了)。
使用引用捕获变量时要格外小心,尤其是在使用默认引用捕获时。捕获的变量必须比 lambda 表达式的生命周期更长。

1
2
3
4
5
6
7
8
9
auto shoot{
// We don't need mutable anymore
[&ammo]() { // &ammo means ammo is captured by reference
// Changes to ammo will affect main's ammo
--ammo;

std::cout << "Pew! " << ammo << " shot(s) left.\n";
}
};

可以用逗号分隔多个变量。这可以包括按值或按引用混合使用的变量。

默认捕获 (也称为默认捕获 )会捕获 lambda 表达式中提到的所有变量。如果使用默认捕获,则不会捕获 lambda 表达式中未提到的变量。
要按值捕获所有已使用的变量,请使用捕获值 = 。
要按引用捕获所有已使用的变量,请使用捕获值 & 。

1
2
3
4
5
6
7
8
// Capture health and armor by value, and enemies by reference.
[health, armor, &enemies](){};

// Capture enemies by reference and everything else by value.
[=, &enemies](){};

// Capture armor by value and everything else by reference.
[&, armor](){};

在捕获中初始化变量。(没必要这么节省吧

最佳实践

尽量避免使用可变 lambda 表达式。不可变 lambda 表达式更容易理解,不会出现上述问题(意外副本),也不会出现并行执行时出现的更危险的问题。

  • 标题: Cpp学习23 函数进阶
  • 作者: 铁名_IronName
  • 创建于 : 2026-08-14 09:53:58
  • 更新于 : 2026-08-14 11:13:58
  • 链接: https://blog.ironname.top/2026/Cpp/Cpp学习23/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论