函数基础

建议先阅读:09_数组基础

原理

函数调用栈帧

每次函数调用在调用栈上压入一个栈帧(stack frame),包含:

  • 返回地址(调用方下一条指令的地址)
  • 保存的基指针(调用方的 ebp/rbp)
  • 局部变量
  • 参数(部分可能通过寄存器传递)

函数返回时弹出栈帧,程序计数器跳回返回地址继续执行。这就是为什么递归过深会导致栈溢出——每个递归调用都分配新栈帧,超出操作系统限制(通常 8MB)。

值传递 vs 引用传递

void byValue(int x) { x++; }   // 栈帧上复制一份 x,修改不影响实参
void byRef(int& x) { x++; }    // x 是实参的别名,修改直接作用于实参

引用在底层实现为指针——编译器自动解引用,语法层无需 *。对大型对象(如 string),const 引用避免昂贵的内存拷贝。

名称修饰 (Name Mangling)

C++ 的函数重载依赖名称修饰——编译器将函数名、参数类型信息编码为唯一的链接符号。例如 add(int, double) 在不同编译器下可能编为 _Z3addid(GCC)或 ?add@@YAHHN@Z(MSVC)。这就是为什么 C++ 函数不能被 C 代码直接调用(需要 extern "C" 禁止修饰)。

内联与调用开销

函数调用有固定开销:压栈参数、跳转、分配栈帧、返回。inline 将函数体在调用处展开,消除调用开销。但内联增加代码体积(code bloat),影响指令缓存。编译器会根据启发式规则自主决定是否内联——inline 关键字只是建议。


语法

声明与定义

int add(int a, int b);          // 声明(原型)
int add(int a, int b) {         // 定义
    return a + b;
}

声明可多次,定义只能一次(单一定义规则 ODR)。函数必须先声明后调用。

值传递与引用传递

void swap(int& a, int& b) {     // 引用传递,修改生效
    int temp = a;
    a = b;
    b = temp;
}
 
void print(const std::string& s) { // const 引用:不拷贝,不修改
    std::cout << s << '\n';
}

基本类型 (int, char, double) 值传递即可;大对象使用 const 引用。

函数重载

同名函数,参数列表不同(类型/数量/顺序):

int add(int a, int b);
double add(double a, double b);
int add(int a, int b, int c);

仅返回类型不同不构成重载——编译器无法仅凭调用上下文区分。

默认参数

void print(std::string msg, int times = 1, std::string end = "\n") {
    for (int i = 0; i < times; i++)
        std::cout << msg << end;
}
// 调用: print("Hi"); print("Hi", 3); print("Hi", 2, " ");

默认参数必须从右向左连续设置,不能跳跃。

递归

int factorial(int n) {
    if (n <= 1) return 1;        // 基准条件 (终止)
    return n * factorial(n - 1);
}

每个递归必须有无条件到达的基准条件。尾递归(最后一步是自身调用)可被编译器优化为循环。

头文件组织

// utils.h
#ifndef UTILS_H
#define UTILS_H
int add(int a, int b);
#endif
 
// utils.cpp
#include "utils.h"
int add(int a, int b) { return a + b; }
 
// main.cpp
#include "utils.h"

实践

计算整数幂(快速幂):

double power(double base, int exp) {
    if (exp == 0) return 1;
    double half = power(base, exp / 2);
    if (exp % 2 == 0)
        return half * half;
    else if (exp > 0)
        return half * half * base;
    else
        return half * half / base;  // 负指数
}

力扣:
力扣: 距离计算题 (封装计算函数)
力扣: 质数筛选题 (函数封装 + 数组)
力扣: 闰年判断展示题 (函数封装 + 循环)

AI 自检提示:让 AI 绘制一个带有 int max3(int, int, int) 调用的栈帧示意图,解释参数入栈顺序和返回地址的位置。