Skip to content

Ch 12: 函数进阶

  • 掌握递归函数的写法,理解尾递归优化
  • 理解默认参数的全部陷阱(重载二义性、副作用)
  • 学会使用 constexpr 进行编译期计算
  • 掌握内联函数的适用场景和注意事项
  • 理解函数指针和 std::function 的区别和使用场景
# Python - 简单递归
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
# 斐波那契
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# 树形递归(效率低)
result = fibonacci(35) # 需要数百万次调用
// C++ 递归 - 语法与 Python 类似
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// 斐波那契 - 注意效率问题
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 调用
int main() {
int f = factorial(5); // 120
int fib = fibonacci(10); // 55
}
#include <iostream>
// 递归深度受限于栈大小
void countdown(int n) {
std::cout << n << std::endl;
if (n > 1) {
countdown(n - 1); // 每次调用消耗栈空间
}
}
int main() {
countdown(5); // 5, 4, 3, 2, 1
// 大递归会导致栈溢出
// countdown(100000); // 很可能崩溃
return 0;
}
# Python - 没有尾递归优化(截至 Python 3.x)
def factorial_tail(n, accumulator=1):
if n == 0:
return accumulator
return factorial_tail(n - 1, n * accumulator) # 仍然是栈累积
// C++ - 尾递归(编译器可能优化,也可能不优化)
int factorial_tail(int n, int accumulator = 1) {
if (n <= 1) return accumulator;
return factorial_tail(n - 1, n * accumulator); // 尾调用
}
// 斐波那契尾递归
int fibonacci_tail(int n, int a = 0, int b = 1) {
if (n == 0) return a;
return fibonacci_tail(n - 1, b, a + b);
}
int main() {
// 注意:C++ 标准不保证尾递归优化
// GCC/Clang 在优化模式下可能优化
std::cout << fibonacci_tail(20) << std::endl; // 6765
}

关键点:C++ 不保证尾递归优化,编译器说了算。Python 也不保证。选择循环还是递归取决于可读性和栈深度需求。

#include <vector>
// 递归版本 - 直观但有栈溢出风险
int fibonacci_recursive(int n) {
if (n <= 1) return n;
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2);
}
// 迭代版本 - 无栈溢出风险
int fibonacci_iterative(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int temp = a + b;
a = b;
b = temp;
}
return b;
}
// 使用记忆化的递归(C++20)
#include <unordered_map>
int fibonacci_memo(int n, std::unordered_map<int, int>& memo) {
if (n <= 1) return n;
if (auto it = memo.find(n); it != memo.end()) {
return it->second;
}
int result = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo);
memo[n] = result;
return result;
}
#include <iostream>
#include <vector>
#include <string>
// 1. 二叉树遍历(递归天然适合)
struct TreeNode {
int value;
TreeNode* left;
TreeNode* right;
TreeNode(int v) : value(v), left(nullptr), right(nullptr) {}
};
void inorder(TreeNode* node) {
if (!node) return;
inorder(node->left);
std::cout << node->value << " ";
inorder(node->right);
}
void preorder(TreeNode* node) {
if (!node) return;
std::cout << node->value << " ";
preorder(node->left);
preorder(node->right);
}
// 2. 文件系统遍历(模拟)
void walk_directory(const std::string& path, int depth = 0) {
// 实际需要文件系统 API,这里简化
std::string indent(depth * 2, ' ');
std::cout << indent << path << std::endl;
// 递归处理子目录
// walk_directory(subpath, depth + 1);
}
// 3. 全排列
void permute(std::vector<int>& nums, int start, std::vector<std::vector<int>>& results) {
if (start == nums.size()) {
results.push_back(nums);
return;
}
for (int i = start; i < nums.size(); ++i) {
std::swap(nums[start], nums[i]);
permute(nums, start + 1, results);
std::swap(nums[start], nums[i]); // 回溯
}
}
std::vector<std::vector<int>> generate_permutations(std::vector<int> nums) {
std::vector<std::vector<int>> results;
permute(nums, 0, results);
return results;
}

Python 可变默认参数(著名陷阱)

Section titled “Python 可变默认参数(著名陷阱)”
# Python - 可变默认参数是共享的!
def append_to(item, target=[]):
target.append(item)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] ← BUG!应该是 [2]
print(append_to(3)) # [1, 2, 3] ← 累积 Bug
# 正确写法
def append_to(item, target=None):
if target is None:
target = []
target.append(item)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [2]
print(append_to(3)) # [3]
// C++ - 默认参数是固定值,没有 Python 的问题
#include <vector>
#include <string>
// 每次调用都会创建新的空 vector
void append_to(int item, std::vector<int> target = {}) {
target.push_back(item);
// 注意:这是拷贝!不影响调用者
}
// 如果想修改原对象,用引用
void append_to_ref(int item, std::vector<int>& target = ???) {
// 错误!不能引用默认参数
}
// C++20 解决方案:用 std::optional
#include <optional>
void append_to_cpp20(int item, std::optional<std::vector<int>> target = std::nullopt) {
std::vector<int> vec = target.value_or({}); // 创建新 vector
vec.push_back(item);
std::cout << "Result: " << vec.size() << std::endl;
}
int main() {
append_to_cpp20(1); // Result: 1
append_to_cpp20(2); // Result: 1(每次都是新 vector)
}
// ✅ 正确:默认参数放在参数列表末尾
void connect(const std::string& host, int port = 80, bool use_ssl = false);
// 允许的情况
void f(int a, int b = 0, int c = 0); // OK
void f(int a = 0); // OK
// ❌ 错误:默认参数不能在非默认参数前面
void f(int a = 0, int b); // 错误
void f(int a, int b = 0, int c); // 错误:c 在有默认值的 b 后没有默认值
// 正确示例
void create_user(const std::string& name,
const std::string& email = "",
int age = 0,
bool verified = false);
// 编译器如何处理默认参数
// 源代码:
void greet(const std::string& name, const std::string& greeting = "Hello") {
std::cout << greeting << ", " << name << std::endl;
}
greet("Alice"); // 编译器转换为 greet("Alice", "Hello")
greet("Bob", "Hi"); // 保持原样
// 本质:默认参数是编译时展开,不是运行时处理
#include <iostream>
// 陷阱:重载导致二义性
void process(int x);
void process(int x, int y = 10); // 添加了默认参数
// 调用
int main() {
process(5); // ❌ 编译错误!两个函数都匹配
// process(int) 直接匹配
// process(int, int) 也能匹配,因为 y 有默认值
process(5, 3); // ✅ OK,调用第二个
return 0;
}
// 解决方案 1:不要这样重载
void process_single(int x) { /* ... */ }
void process_double(int x, int y) { /* ... */ }
// 解决方案 2:用 nullptr 区分重载
#include <cstddef>
void process(int x);
void process(int x, int* y); // 第二个参数是指针
int main() {
process(5); // 调用第一个
process(5, nullptr); // 调用第二个
}
#include <iostream>
#include <vector>
// 陷阱:默认参数是 static 或全局变量
std::vector<int> results;
void add_item(int value, std::vector<int>& container = results) {
container.push_back(value);
}
int main() {
add_item(1); // 添加到 results
add_item(2); // 添加到 results(累积)
std::cout << results.size() << std::endl; // 2
// 清空
results.clear();
add_item(3); // results 又累积
std::cout << results.size() << std::endl; // 1
}
#include <string>
#include <vector>
class Database {
private:
std::string connection_string_;
int timeout_;
public:
// 成员函数的默认参数
void connect(const std::string& uri = "localhost:5432",
int timeout = 30) {
connection_string_ = uri;
timeout_ = timeout;
}
void query(const std::string& sql, bool async = false) {
// ...
}
};
// 使用
int main() {
Database db;
db.connect(); // 使用默认 uri 和 timeout
db.connect("postgres://example.com"); // 使用自定义 uri
db.connect("postgres://example.com", 60); // 自定义 uri 和 timeout
db.query("SELECT * FROM users"); // async = false
db.query("SELECT * FROM users", true); // async = true
}
# Python - 无编译期计算概念
def factorial(n):
if n <= 1: return 1
return n * factorial(n)
# 全部在运行时计算
result = factorial(10) # 运行时才计算
// C++ constexpr - 编译期计算
constexpr int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// 编译期验证!
static_assert(factorial(5) == 120, "factorial(5) must be 120");
int main() {
// 两种用法
int runtime_val = factorial(10); // 可能在编译期计算,也可能在运行时
constexpr int compile_val = factorial(10); // 强制编译期计算
}
// C++11 - constexpr 限制很多
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// C++14 - 解锁更多特性(允许更复杂的函数体)
constexpr int fibonacci(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int temp = a + b;
a = b;
b = temp;
}
return b;
}
// C++17 - 允许 if constexpr
template<typename T>
constexpr T abs(T x) {
if constexpr (std::is_signed_v<T>) {
return x < 0 ? -x : x;
} else {
return x;
}
}
// C++20 - constexpr 支持更多 STL 容器
#include <vector>
constexpr auto create_vector() {
std::vector<int> v = {1, 2, 3};
return v; // C++20 开始支持
}
#include <array>
// 1. 数组大小必须是编译期常量
constexpr int SIZE = 10;
int arr[SIZE]; // OK
// 2. 模板参数必须是编译期常量
template<int N>
struct ArrayHolder {
int data[N];
};
ArrayHolder<factorial(5)> holder; // 数组大小 120
// 3. static_assert 编译期断言
static_assert(sizeof(int) >= 4, "int must be at least 32 bits");
static_assert(factorial(6) == 720);
// 4. 查找表
constexpr int triangle_number(int n) {
return n * (n + 1) / 2;
}
constexpr std::array<int, 10> triangles = {
triangle_number(0), triangle_number(1), triangle_number(2),
triangle_number(3), triangle_number(4), triangle_number(5),
triangle_number(6), triangle_number(7), triangle_number(8),
triangle_number(9)
};
特性constexprinline
目的编译期求值消除调用开销
适用常量表达式短小函数
保证编译期计算建议优化
要求无副作用短小函数体
// constexpr 隐含 inline
constexpr int square(int x) { return x * x; } // 同时是 constexpr 和 inline
// 但两者目的不同
constexpr int consteval_fn(int x) { return x + 1; } // 必须编译期求值
inline int inline_fn(int x) { return x + 1; } // 建议内联
// consteval - 强制编译期求值
consteval int factorial_consteval(int n) {
if (n <= 1) return 1;
return n * factorial_consteval(n - 1);
}
int main() {
constexpr int a = factorial_consteval(5); // OK,编译期
int b = factorial_consteval(5); // OK,编译期(强制)
int n = 5;
// int c = factorial_consteval(n); // ❌ 错误!n 不是常量
// factorial_consteval 要求参数也是编译期可确定
int d = square(n); // OK!square 不是 consteval
return 0;
}
// constexpr 可以有时在编译期,有时在运行时
constexpr int square(int x) { return x * x; }
int main() {
constexpr int a = square(5); // 编译期
int n = 5;
int b = square(n); // 运行时(n 不是常量)
}
# Python - 每个函数调用有开销
import time
def max(a, b):
return a if a > b else b
start = time.time()
for i in range(10000000):
m = max(i, 100)
end = time.time()
print(f"Time: {end - start:.3f}s") # 约 1-2 秒
// inline 建议编译器内联(消除函数调用开销)
inline int max(int a, int b) {
return a > b ? a : b;
}
int main() {
for (int i = 0; i < 10000000; ++i) {
int m = max(i, 100);
// 编译器可能展开为: int m = i > 100 ? i : 100;
}
}
// 源代码
inline int add(int a, int b) {
return a + b;
}
int main() {
int result = add(1, 2);
}
// 编译器可能转换为
int main() {
int result = 1 + 2; // 直接内联展开
}
// ✅ 适合内联
inline int square(int x) { return x * x; }
inline int max(int a, int b) { return a > b ? a : b; }
inline bool is_even(int x) { return x % 2 == 0; }
// ❌ 不适合内联
inline int fibonacci(int n) { // 递归不适合
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
inline void complex_operation(...) { // 复杂函数不适合
// 大量代码
// 循环
// 分支
}
// 1. inline 必须在头文件中定义(因为需要编译时展开)
// .h 文件
inline int add(int a, int b) { return a + b; }
// 2. 模板函数隐式 inline
template<typename T>
T max(T a, T b) { // 不需要写 inline,编译器自动处理
return a > b ? a : b;
}
// 3. 类内定义的成员函数隐式 inline
struct Point {
int x, y;
// 隐式 inline(在类内定义)
int getX() const { return x; }
int getY() const { return y; }
};
// 4. 过大的内联会导致代码膨胀(bloat)
inline void big_operation() { // 1000 行代码
// ...
}
// C 宏(有问题)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int x = 5, y = 10;
int m = MAX(x++, y++); // 陷阱!x 和 y 各被递增两次
// 展开为: ((x++) > (y++) ? (x++) : (y++))
// 结果不可预测
}
// C++ 内联函数(安全)
template<typename T>
inline T max(T a, T b) {
return a > b ? a : b;
}
int main() {
int x = 5, y = 10;
int m = max(x++, y++); // OK,每个表达式只求值一次
}
#include <iostream>
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int subtract(int a, int b) { return a - b; }
int main() {
// 声明函数指针
int (*operation)(int, int);
// 指向函数
operation = add;
int result = operation(3, 4); // 7
// 重新指向
operation = multiply;
result = operation(3, 4); // 12
// 也可以用 & 取地址(但不是必需的)
operation = &subtract;
result = operation(3, 4); // -1
}
#include <iostream>
// 使用 typedef
typedef int (*BinaryOp)(int, int);
using BinaryOp = int(*)(int, int); // C++11 using 方式更清晰
BinaryOp get_operation(char op) {
switch (op) {
case '+': return add;
case '*': return multiply;
case '-': return subtract;
default: return nullptr;
}
}
int main() {
if (auto op = get_operation('+')) {
std::cout << op(5, 3) << std::endl; // 8
}
}
#include <iostream>
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int subtract(int a, int b) { return a - b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }
int main() {
// 函数指针数组
int (*operations[])(int, int) = {add, multiply, subtract, divide};
for (int i = 0; i < 4; ++i) {
std::cout << operations[i](10, 3) << " ";
// 13, 30, 7, 3
}
std::cout << std::endl;
}
#include <iostream>
#include <vector>
// Python: def apply(data, callback): return [callback(x) for x in data]
// C++ 回调函数
std::vector<int> apply(const std::vector<int>& data,
int (*callback)(int)) {
std::vector<int> result;
result.reserve(data.size());
for (int x : data) {
result.push_back(callback(x));
}
return result;
}
int double_it(int x) { return x * 2; }
int square_it(int x) { return x * x; }
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
auto doubled = apply(nums, double_it);
// doubled = {2, 4, 6, 8, 10}
auto squared = apply(nums, square_it);
// squared = {1, 4, 9, 16, 25}
for (int n : doubled) std::cout << n << " ";
std::cout << std::endl;
}
#include <iostream>
#include <vector>
#include <algorithm>
struct Person {
std::string name;
int age;
};
bool compare_by_age(const Person& a, const Person& b) {
return a.age < b.age;
}
bool compare_by_name(const Person& a, const Person& b) {
return a.name < b.name;
}
int main() {
std::vector<Person> people = {
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35}
};
// 使用函数指针排序
std::sort(people.begin(), people.end(), compare_by_age);
// 按 age 排序
std::sort(people.begin(), people.end(), compare_by_name);
// 按 name 排序
}

12.6 std::function(更现代的方式)

Section titled “12.6 std::function(更现代的方式)”
#include <functional>
#include <iostream>
#include <vector>
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int main() {
// std::function 可以存储任何可调用对象
std::function<int(int, int)> operation;
operation = add; // 函数指针
std::cout << operation(3, 4) << std::endl; // 7
operation = multiply; // 重新赋值
std::cout << operation(3, 4) << std::endl; // 12
// 存储 lambda
operation = [](int a, int b) { return a + b; };
std::cout << operation(5, 6) << std::endl; // 11
}
特性函数指针std::function
类型安全编译期检查编译期检查
内存开销仅指针有虚调用开销
可存储仅函数任何可调用对象
空状态指向 nullptr可为 std::function<...>{}
#include <functional>
// 函数指针版本(更轻量)
template<typename T>
T apply(T value, int (*fn)(T)) { // 只能接受函数指针
return fn(value);
}
// std::function 版本(更灵活)
template<typename T>
T apply Flexible(T value, std::function<T(T)> fn) { // 可以接受任何可调用对象
return fn ? fn(value) : value;
}
int double(int x) { return x * 2; }
int main() {
apply(5, double); // OK
apply(5, [](int x) { return x * 2; }); // ❌ 函数指针不能存储 lambda
// std::function 版本
apply_flexible(5, double); // OK
apply_flexible(5, [](int x) { return x * 2; }); // OK
}
#include <functional>
#include <iostream>
#include <vector>
#include <map>
int main() {
// 命令模式
std::map<std::string, std::function<void()>> commands;
commands["help"] = []() { std::cout << "Available commands\n"; };
commands["quit"] = []() { std::cout << "Goodbye!\n"; };
commands["version"] = []() { std::cout << "Version 1.0\n"; };
// 执行命令
std::string cmd = "help";
if (auto it = commands.find(cmd); it != commands.end()) {
it->second(); // 调用函数
}
// 数字计算器
std::map<char, std::function<int(int, int)>> ops;
ops['+'] = [](int a, int b) { return a + b; };
ops['-'] = [](int a, int b) { return a - b; };
ops['*'] = [](int a, int b) { return a * b; };
ops['/'] = [](int a, int b) { return b ? a / b : 0; };
std::cout << ops['+'](10, 3) << std::endl; // 13
std::cout << ops['*'](10, 3) << std::endl; // 30
}
#include <functional>
#include <iostream>
#include <vector>
struct Calculator {
int value = 0;
void add(int x) { value += x; }
void multiply(int x) { value *= x; }
void print() const { std::cout << value << std::endl; }
};
int main() {
Calculator calc;
// 使用 lambda 包装成员函数
auto add_fn = [&calc](int x) { calc.add(x); };
add_fn(10);
// 或者用 std::bind
auto add_bound = std::bind(&Calculator::add, &calc, std::placeholders::_1);
add_bound(5);
// 存储成员函数指针
std::vector<std::function<void(int)>> operations;
operations.push_back([&calc](int x) { calc.add(x); });
operations.push_back([&calc](int x) { calc.multiply(x); });
for (auto& op : operations) {
op(2); // 先加2,再乘2
}
calc.print(); // 24 ((0 + 10) + 5) * 2 * 2 = 60... wait
// ((0 + 10) * 2) * 2 = 40
}
#include <functional>
#include <chrono>
#include <iostream>
void simple_fn() { /* empty */ }
int main() {
// 函数指针调用几乎零开销
void (*fn_ptr)() = simple_fn;
// std::function 有虚调用开销
std::function<void()> fn = simple_fn;
// 性能测试
const int iterations = 10000000;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
fn_ptr();
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Function pointer: "
<< std::chrono::duration<double>(end - start).count() << "s\n";
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
fn();
}
end = std::chrono::high_resolution_clock::now();
std::cout << "std::function: "
<< std::chrono::duration<double>(end - start).count() << "s\n";
}
#include <iostream>
#include <functional>
#include <map>
#include <string>
#include <vector>
#include <sstream>
#include <cctype>
class Calculator {
private:
double memory_ = 0;
std::map<char, std::function<double(double, double)>> operations_;
std::map<char, std::function<void()>> commands_;
public:
Calculator() {
// 算术运算
operations_['+'] = [](double a, double b) { return a + b; };
operations_['-'] = [](double a, double b) { return a - b; };
operations_['*'] = [](double a, double b) { return a * b; };
operations_['/'] = [](double a, double b) { return b != 0 ? a / b : 0; };
operations_['%'] = [](double a, double b) { return static_cast<int>(a) % static_cast<int>(b); };
// 命令(使用 lambda)
commands_['c'] = [this]() { memory_ = 0; };
commands_['q'] = [this]() { std::cout << "Bye!\n"; };
commands_['s'] = [this]() { std::cout << "Current: " << memory_ << "\n"; };
}
bool calculate(const std::string& expression) {
std::istringstream iss(expression);
double a, b;
char op;
if (!(iss >> a >> op >> b)) {
std::cerr << "Invalid format. Use: <num> <op> <num>\n";
return false;
}
if (auto it = operations_.find(op); it != operations_.end()) {
memory_ = it->second(a, b);
std::cout << "= " << memory_ << "\n";
return true;
}
std::cerr << "Unknown operator: " << op << "\n";
return false;
}
void run_repl() {
std::cout << "Calculator REPL (type 'q' to quit, 'c' to clear)\n";
while (true) {
std::cout << "> ";
std::string line;
if (!std::getline(std::cin, line)) break;
if (line.empty()) continue;
// 检查命令
if (line == "q" || line == "quit") break;
if (line == "c" || line == "clear") {
memory_ = 0;
continue;
}
if (line == "s" || line == "status") {
std::cout << "Memory: " << memory_ << "\n";
continue;
}
calculate(line);
}
}
};
int main() {
Calculator calc;
calc.run_repl();
return 0;
}
概念关键点
递归与 Python 类似;C++ 不保证尾递归优化;注意栈溢出
默认参数必须是常量表达式;在参数列表末尾;避免与重载混用
constexpr编译期求值;C++14 支持复杂函数体;C++20 支持容器
consteval强制编译期求值;参数必须也是常量
inline消除调用开销;适合短小函数;必须在头文件定义
函数指针低开销;typedef/using 简化;用于回调
std::function统一接口;可存储任何可调用对象;有虚调用开销

下章预告:ch13 学习 Lambda 表达式和闭包,掌握 C++ 的匿名函数。