C++ 函数

函数(function)是组织代码的最小复用单元。把一段逻辑封装成函数,起个名字,就能反复调用。这一章我们看 C++ 函数的方方面面:参数传递方式、默认参数、函数重载、内联、递归,以及 C++11 引入的革命性特性——lambda 表达式

一、函数的基本结构

函数由返回类型、名字、参数列表、函数体组成:

#include <iostream>
using namespace std;

// 函数声明(原型):告诉编译器有这个函数
int add(int a, int b);

int main() {
    int sum = add(3, 5);   // 函数调用
    cout << sum << endl;   // 8
    return 0;
}

// 函数定义:具体实现
int add(int a, int b) {
    return a + b;          // 函数体
}
// 函数签名:返回类型 + 名字 + 参数列表
// int add(int, int) 和 double add(int, int) 是不同签名

二、参数传递:值 vs 引用(C++ 重点)

C++ 函数参数有三种传递方式,这是 C++ 区别于 Java/Python 的核心:

#include <iostream>
using namespace std;

// 1. 值传递:形参是实参的副本,改了不影响外面
void byValue(int x) {
    x = 100;
}

// 2. 引用传递:形参是实参的别名,改了直接作用原变量
void byReference(int& x) {
    x = 100;
}

// 3. const 引用:不修改,但避免拷贝(大对象必备)
void print(const string& s) {
    cout << s << endl;     // 只读,不能改 s
}

int main() {
    int a = 1;
    byValue(a);
    cout << a << endl;     // 1(没变)

    int b = 1;
    byReference(b);
    cout << b << endl;     // 100(被修改了)

    string big = "very long string ...";
    print(big);            // 用 const 引用,不拷贝
    return 0;
}

记住这条规则:只读小类型用值,只读大类型用 const 引用,要修改用引用。这是 C++ 性能优化的第一原则。

三、默认参数

给参数设默认值,调用时可以省略:

#include <iostream>
using namespace std;

// 默认参数:从右往左给,调用时可省略
void greet(string name, string greeting = "Hello", string punct = "!") {
    cout << greeting << ", " << name << punct << endl;
}

int main() {
    greet("Alice");                     // Hello, Alice!
    greet("Bob", "Hi");                 // Hi, Bob!
    greet("Carol", "Hey", ".");         // Hey, Carol.
    // greet("Dave", , ".");            // 编译错误:不能跳过中间参数

    // 默认参数必须在声明里写,不能在定义里再写
    return 0;
}

规则:

四、函数重载(overload)

C++ 允许同名函数只要参数列表不同就能共存,这叫重载:

#include <iostream>
using namespace std;

// 函数重载:同名函数,但参数列表不同
// 编译器根据调用时参数类型,自动选对那个

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

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

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

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

int main() {
    cout << add(1, 2) << endl;             // 3  (调 int 版)
    cout << add(1.5, 2.5) << endl;         // 4  (调 double 版)
    cout << add(1, 2, 3) << endl;          // 6  (调三参数版)
    cout << add("Hello, "s, "C++") << endl;// Hello, C++(调 string 版)
    return 0;
}

编译器根据参数个数/类型自动选对那个,这叫重载决议(overload resolution)。注意:

五、内联函数 inline

inline 是给编译器的建议:把函数体直接展开到调用处,省去函数调用开销:

#include <iostream>
using namespace std;

// inline:建议编译器"内联展开"(把函数体直接贴到调用处)
// 适合短小、频繁调用的函数,省去函数调用开销
inline int square(int x) {
    return x * x;
}

// 现代 C++:在类内定义的成员函数自动 inline
// 编译器比人聪明,可能不展开你标了 inline 的函数,
// 也可能展开你没标 inline 的函数——inline 只是建议

int main() {
    cout << square(5) << endl;   // 25
    // 编译后可能变成:cout << (5*5) << endl;
    return 0;
}

六、递归

函数调用自己就是递归。经典例子:

#include <iostream>
using namespace std;

// 递归:函数调用自己
// 必须有"终止条件",否则无限递归栈溢出

// 经典:阶乘 n! = n * (n-1)!
int factorial(int n) {
    if (n <= 1) return 1;        // 终止条件
    return n * factorial(n - 1); // 递归调用
}

// 经典:斐波那契数列 F(n) = F(n-1) + F(n-2)
int fib(int n) {
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}

int main() {
    cout << factorial(5) << endl;  // 120 (5*4*3*2*1)
    cout << fib(10) << endl;       // 55

    // 注意:递归版 fib 是指数复杂度,大 n 时慢得离谱
    // 实际用循环或记忆化
    return 0;
}

递归的两个关键:

递归的代价:每次调用都消耗栈空间,深度太大会栈溢出。如 fib 的朴素递归是指数复杂度,实际工程中往往改用循环或"记忆化递归"。

七、lambda 表达式(C++11)

lambda 是匿名函数,可以就地定义、就地使用。这是现代 C++ 最重要的特性之一:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    // lambda:匿名函数,C++11 引入
    // 语法: [捕获](参数) -> 返回类型 { 函数体 }

    // 1. 基本 lambda
    auto greet = []() {
        cout << "Hello!" << endl;
    };
    greet();   // Hello!

    // 2. 带参数和返回值
    auto add = [](int a, int b) -> int {
        return a + b;
    };
    cout << add(3, 4) << endl;   // 7

    // 3. 捕获外部变量
    int factor = 10;
    auto multiply = [factor](int x) {   // 用值捕获 factor
        return x * factor;
    };
    cout << multiply(5) << endl;   // 50

    // 4. STL 算法的回调(最常见用法)
    vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6};
    sort(nums.begin(), nums.end(), [](int a, int b) {
        return a > b;     // 降序排序
    });
    for (int x : nums) cout << x << " ";
    cout << endl;
    // 输出: 9 6 5 4 3 2 1 1
    return 0;
}

lambda 语法:方括号捕获 + 小括号参数 + 箭头返回类型 + 花括号函数体。

八、lambda 捕获详解

#include <iostream>
using namespace std;

int main() {
    int a = 1, b = 2, c = 3;

    // []        不捕获
    // [=]       按值捕获所有外部变量
    // [&]       按引用捕获所有外部变量
    // [x]       只按值捕获 x
    // [&x]      只按引用捕获 x
    // [=, &x]   默认按值,但 x 按引用
    // [&, x]    默认按引用,但 x 按值

    auto f1 = [=]() { return a + b + c; };            // 值捕获,只读
    cout << f1() << endl;   // 6

    auto f2 = [&]() { a = 100; b = 200; };            // 引用捕获,可改
    f2();
    cout << a << " " << b << endl;   // 100 200

    // C++14+:可以给捕获起别名、用初始化捕获
    int x = 5;
    auto f3 = [y = x + 10]() { return y; };
    cout << f3() << endl;   // 15
    return 0;
}

几个捕获陷阱:

九、声明与定义的分离

实际项目里,函数声明和定义通常分到两个文件:

// 头文件 utils.h
#pragma once
int add(int a, int b);     // 只有声明

// 源文件 utils.cpp
#include "utils.h"
int add(int a, int b) {    // 定义
    return a + b;
}

// 主文件 main.cpp
#include <iostream>
#include "utils.h"           // 引入声明
using namespace std;

int main() {
    cout << add(1, 2) << endl;   // 3
    return 0;
}

// 编译:g++ main.cpp utils.cpp -o app
// 为什么分离?让声明在头文件,其他文件 include 后就能用;
// 实现细节藏在 .cpp 里,加快编译速度、隐藏实现。

小结

这一章你掌握了 C++ 函数的全部要点:三种参数传递方式(值/引用/const 引用)、默认参数、函数重载、inline、递归、lambda。下一篇我们专门讲引用——C++ 独有的概念,以及它和指针的对比。

← 上一篇 C++ 控制流

下一篇 C++ 引用

✈️💬