跳到主要内容

C++ 函数

函数是组织代码的基本单元,它将一段逻辑封装起来,可以重复调用。本章将详细介绍 C++ 函数的定义、调用、参数传递和高级特性。

函数基础

函数定义

// 函数返回类型 函数名(参数列表)
// {
// 函数体
// }

// 示例:计算两个整数的和
int add(int a, int b) {
return a + b;
}

函数结构说明:

返回值类型   // 函数执行完后返回的数据类型


int add( // 函数名,遵循标识符规则
int a, // 参数列表,多个参数用逗号分隔
int b
) {
// 函数体:实现函数功能的代码
return a + b; // return 语句返回值
}

函数声明与定义

// 函数声明(函数原型)
int add(int a, int b); // 告诉编译器函数存在

// 函数定义(实现)
int add(int a, int b) {
return a + b;
}

int main() {
int result = add(3, 5); // 调用函数
std::cout << result; // 输出 8
return 0;
}

函数调用

#include <iostream>

// 函数定义
int max(int a, int b) {
return (a > b) ? a : b;
}

int main() {
int x = 10, y = 20;

// 调用函数
int result = max(x, y);

std::cout << "最大值: " << result << std::endl;

// 直接调用
std::cout << max(5, 3) << std::endl;

return 0;
}

参数传递

C++ 中有三种参数传递方式:

1. 值传递

void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}

int main() {
int x = 10, y = 20;
swap(x, y); // 传递的是值的副本
// x 和 y 不会改变!
}

2. 指针传递

void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}

int main() {
int x = 10, y = 20;
swap(&x, &y); // 传递地址
// 现在 x = 20, y = 10
}

3. 引用传递(推荐)

void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}

int main() {
int x = 10, y = 20;
swap(x, y); // 直接传递变量
// x = 20, y = 10
}

对比三种传递方式:

┌─────────────┬────────────┬─────────────┬─────────────┐
│ 方式 │ 参数类型 │ 优点 │ 缺点 │
├─────────────┼────────────┼─────────────┼─────────────┤
│ 值传递 │ T │ 安全,简单 │ 拷贝开销大 │
│ 指针传递 │ T* │ 可以修改 │ 语法复杂 │
│ 引用传递 │ T& │ 简单,可修改│ 可能被意外修改│
└─────────────┴────────────┴─────────────┴─────────────┘

const 引用

使用 const 引用可以避免意外修改,同时避免拷贝开销:

void print(const std::string& str) {
// str 是只读的,不能修改
std::cout << str << std::endl;
}

int main() {
std::string msg = "Hello";
print(msg); // 不需要拷贝
return 0;
}

默认参数

C++ 允许为函数参数设置默认值:

void greet(std::string name = "World") {
std::cout << "Hello, " << name << "!" << std::endl;
}

int main() {
greet(); // 输出: Hello, World!
greet("Alice"); // 输出: Hello, Alice!
greet("Bob"); // 输出: Hello, Bob!
}

注意:

  1. 默认参数必须在函数声明中指定
  2. 默认参数从右到左依次赋值
  3. 有默认值的参数后面不能再有无默认值的参数
// 正确
void func(int a, int b = 10, int c = 20);

// 错误
void func(int a = 10, int b, int c = 20);

函数重载

函数重载允许在同一作用域内定义多个同名函数,但参数列表必须不同:

// 参数类型不同
int add(int a, int b) {
return a + b;
}

double add(double a, double b) {
return a + b;
}

std::string add(const std::string& a, const std::string& b) {
return a + b;
}

int main() {
std::cout << add(1, 2) << std::endl; // 3
std::cout << add(1.5, 2.5) << std::endl; // 4.0
std::cout << add("Hello", " World") << std::endl; // Hello World
}

参数个数不同:

void print(int value) {
std::cout << "int: " << value << std::endl;
}

void print(int a, int b) {
std::cout << "two ints: " << a << ", " << b << std::endl;
}

注意: 返回类型不同不能构成重载:

// 错误!这不是重载,而是重复定义
int getValue() { return 0; }
double getValue() { return 0.0; }

函数模板

使用模板可以让函数支持多种类型:

template<typename T>
T add(T a, T b) {
return a + b;
}

int main() {
std::cout << add(1, 2) << std::endl; // 3
std::cout << add(1.5, 2.5) << std::endl; // 4.0
std::cout << add(3.14f, 2.0f) << std::endl; // 5.14
}

多个模板参数:

template<typename T1, typename T2>
void print(T1 a, T2 b) {
std::cout << a << " - " << b << std::endl;
}

Lambda 表达式(C++11)

Lambda 是匿名函数,可以在需要的地方定义函数体:

// 基本语法
[capture](parameters) -> return_type {
// 函数体
}

// 示例
auto add = [](int a, int b) -> int {
return a + b;
};

std::cout << add(3, 5) << std::endl; // 8

Lambda 捕获列表

int x = 10;
int y = 20;

// [x] - 按值捕获 x
// [&x] - 按引用捕获 x
// [=] - 按值捕获所有外部变量
// [&] - 按引用捕获所有外部变量
// [=, &x] - 按值捕获其他,按引用捕获 x
// [&, x] - 按引用捕获其他,按值捕获 x

// 示例:按值捕获
auto func1 = [x](int a) { return a + x; };
// x 是副本,修改 x 不会影响 lambda 内的值

// 示例:按引用捕获
auto func2 = [&x](int a) { x = a; return x; };
func2(100); // x 变为 100

完整示例

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
std::vector<int> nums = {5, 2, 8, 1, 9};

// 使用 lambda 进行排序(降序)
std::sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b;
});

// 使用 lambda 查找第一个偶数
auto it = std::find_if(nums.begin(), nums.end(), [](int n) {
return n % 2 == 0;
});

// 打印结果
for (int n : nums) {
std::cout << n << " ";
}
std::cout << std::endl;

if (it != nums.end()) {
std::cout << "第一个偶数: " << *it << std::endl;
}

return 0;
}

递归函数

函数可以调用自身,这就是递归:

// 阶乘计算
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}

int main() {
std::cout << factorial(5); // 120
return 0;
}

递归要素:

  1. 基准条件:递归终止条件
  2. 递归步骤:将大问题分解为小问题

递归示例:斐波那契数列

// 斐波那契:1, 1, 2, 3, 5, 8, 13, 21, ...
int fibonacci(int n) {
if (n <= 1) return 1; // 基准条件
return fibonacci(n - 1) + fibonacci(n - 2); // 递归调用
}

// 优化:使用记忆化
#include <unordered_map>
std::unordered_map<int, long long> memo;

long long fibonacci_memo(int n) {
if (n <= 1) return 1;
if (memo.count(n)) return memo[n]; // 缓存命中

memo[n] = fibonacci_memo(n - 1) + fibonacci_memo(n - 2);
return memo[n];
}

尾递归优化

尾递归是指递归调用是函数的最后一条语句:

// 普通递归
int factorial(int n, int result = 1) {
if (n <= 1) return result;
return factorial(n - 1, n * result); // 不是尾递归
}

// 尾递归(编译器可能会优化)
int factorial_tail(int n, int result = 1) {
if (n <= 1) return result;
return factorial_tail(n - 1, n * result); // 尾递归
}

内联函数

使用 inline 关键字可以建议编译器将函数内联:

inline int max(int a, int b) {
return (a > b) ? a : b;
}

使用场景:

  • 函数体较小(通常几行)
  • 函数被频繁调用
  • 不适合复杂逻辑和递归函数

注意: inline 只是建议,编译器可能忽略。

函数指针

函数指针指向函数,可以用来动态调用函数:

int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }

int main() {
// 函数指针
int (*operation)(int, int) = add;

std::cout << operation(5, 3) << std::endl; // 8

// 指向另一个函数
operation = subtract;
std::cout << operation(5, 3) << std::endl; // 2

return 0;
}

使用 typedef/using 简化:

using Operation = int(*)(int, int);
// 或
typedef int (*Operation)(int, int);

Operation op = add;

std::function(C++11)

std::function 是更灵活的函数封装:

#include <functional>

int add(int a, int b) { return a + b; }

int main() {
std::function<int(int, int)> func = add;
std::cout << func(3, 5) << std::endl; // 8

// 可以存储 lambda
func = [](int a, int b) { return a * b; };
std::cout << func(3, 5) << std::endl; // 15

return 0;
}

练习

  1. 编写函数,判断一个数是否为素数
  2. 编写函数,计算阶乘(递归和非递归版本)
  3. 编写函数,使用二分查找在有序数组中查找元素
  4. 编写函数,将字符串反转

小结

本章学习了:

  1. 函数基础:定义、声明、调用
  2. 参数传递:值传递、指针传递、引用传递
  3. 默认参数:简化函数调用
  4. 函数重载:同名函数,不同参数
  5. 函数模板:泛型函数
  6. Lambda 表达式:匿名函数
  7. 递归:函数自调用
  8. 函数指针:指向函数的指针

下一步

接下来让我们学习 C++ 面向对象编程,了解类和对象的概念。