核心要点速览

  • 声明与定义:可多次声明,仅能一次定义;原型需匹配参数类型 / 个数(返回类型不影响)
  • 参数传递:值传递(拷贝,不影响实参)、引用传递(别名,无拷贝)、指针传递(地址,支持 nullptr)
  • 函数重载:同名 + 参数列表不同(类型 / 个数 / 顺序),返回类型不参与重载
  • 内联函数:inline 修饰,建议编译器嵌入调用处(适合短函数,无循环 / 递归)
  • 默认参数:遵循 “靠右原则”,不可跳过右侧参数省略左侧
  • 返回值:禁止返回局部变量引用;大型对象返回有 RVO/NRVO 优化

一、函数的声明与定义

概念

  • 声明:告知编译器函数的 “存在及原型”(返回类型、函数名、参数类型 / 个数),不包含实现;参数名可选(仅声明时)。
    • 用途:解决 “调用在前、定义在后” 的编译错误,如int add(int, int);
  • 定义:包含声明 + 函数体(具体实现),编译器会分配内存生成指令。

规则

  • 单定义原则(ODR):一个函数可在多个文件中声明,但仅能在一个文件中定义(否则链接报错 “重复定义”)。
  • 原型匹配:声明与定义的参数类型、个数必须一致;返回类型可省略(默认 int,不推荐),但建议显式声明。

二、参数传递

参数传递决定函数与实参的交互方式,核心分三类,重点考察适用场景与区别。

1. 值传递

  • 规则:形参是实参的拷贝,函数内修改形参不影响实参。

  • 优点:安全(隔离实参)、逻辑简单。

  • 缺点:大对象拷贝开销大(如大型结构体、类对象)。

  • 示例:

    1
    2
    void swap(int a, int b) { int t=a; a=b; b=t; } // 仅交换拷贝,实参不变
    int x=1, y=2; swap(x,y); // x=1, y=2(实参未变)

2. 引用传递(int&

  • 规则:形参是实参的别名,无拷贝开销;函数内修改形参直接影响实参。

  • 优点:效率高(无拷贝)、语法简洁、可修改实参。

  • 约束:引用必须绑定实参(不能传字面量,如func(5)报错),绑定后不可更改指向。

  • 示例:

    1
    2
    void swap(int& a, int& b) { int t=a; a=b; b=t; }
    int x=1, y=2; swap(x,y); // x=2, y=1(实参已交换)

3. 指针传递(int*

  • 规则:形参是实参的地址拷贝,通过解引用(*p)间接修改实参。

  • 优点:可修改实参,支持传递nullptr(空值语义)。

  • 缺点:需显式解引用(易漏写*),可能出现野指针(未初始化指针)。

  • 示例:

    1
    2
    void swap(int* a, int* b) { int t=*a; *a=*b; *b=t; }
    int x=1, y=2; swap(&x,&y); // x=2, y=1(通过地址修改)

三类传递方式对比表

对比维度 值传递 引用传递 指针传递
内存开销 拷贝实参(大对象开销大) 无拷贝(仅别名) 拷贝地址(4/8 字节,固定)
实参修改 不可修改 可直接修改 可修改(需解引用)
空值传递 不支持 不支持(必须绑定对象) 支持(传nullptr
适用场景 小对象、无需修改实参 大对象、需修改实参 需 “空值” 语义的场景
语法复杂度 简单(直接传值) 简洁(传变量名) 复杂(传地址 + 解引用)

三、返回值

1. 返回值类型与规则

  • 基本类型(int/double等):直接返回值拷贝,无额外风险。
  • 自定义类型(类 / 结构体):返回对象副本,编译器可能触发返回值优化(RVO/NRVO) ,避免临时对象拷贝。
  • 引用返回(&):
    • 优点:无拷贝,效率高(适合大对象)。

    • 禁忌:禁止返回局部变量的引用(局部变量生命周期随函数结束,引用变为 “悬垂引用”,访问即未定义行为)。

    • 示例(错误):

      1
      2
      int& func() { int x=10; return x; } // x是局部变量,函数结束后销毁
      int& res = func(); // 悬垂引用,访问res可能崩溃

2. 特殊返回值:void

  • 表示 “无返回值”,函数体中可省略return,或用return;提前结束。
  • 限制:不能定义void类型变量(void x;报错);可定义void*指针(指向任意类型,需强转后使用)。

3. 返回值优化(RVO/NRVO)

  • 概念:编译器对 “返回大型对象” 的优化,直接在调用处构造对象,避免函数返回时的临时对象拷贝。
  • 特点:无需手动修改代码,编译器自动触发(主流编译器均支持),显著提升大型对象返回效率。

四、函数重载

核心定义

同一作用域中,函数名相同但参数列表不同,用于实现 “同一功能的不同参数版本”。

重载的条件(缺一不可)

  1. 同一作用域(如同一命名空间、同一类)。
  2. 函数名完全相同。
  3. 参数列表不同(满足任一即可):
    • 参数类型不同(如add(int, int) vs add(double, double));
    • 参数个数不同(如add(int) vs add(int, int));
    • 参数顺序不同(如func(int, double) vs func(double, int))。
  4. 补充场景(易忽略):
    • 参数为引用 / 指针时,const修饰可形成重载(如func(int&) vs func(const int&));
    • 类的成员函数中,const修饰this指针可形成重载(如void show() vs void show() const)。

重载的排除条件

  • 返回类型不同不能形成重载(如int add(int) vs double add(int),编译报错);
  • 参数名不同不能形成重载(如add(int a) vs add(int b),视为同一函数)。

重载决议(编译器匹配逻辑)

编译器按 “实参与形参的匹配度” 选择最佳函数,匹配度从高到低:

  1. 精确匹配(类型完全一致,含const匹配);
  2. 提升转换(如char→intfloat→double);
  3. 标准转换(如int→doubleint*→void*);
  4. 用户定义转换(如类的构造函数、运算符重载)。
  • 若匹配度相同(歧义),编译报错(需显式类型转换解决)。

五、内联函数(inline

特性

  • 作用:建议编译器将函数体 “嵌入” 调用处(而非生成函数调用指令),减少栈帧创建 / 销毁的开销。
  • 建议性:inline是编译器 “建议”,非强制 —— 若函数含循环、递归、复杂分支,编译器会忽略inline,按普通函数处理。
  • 定义即声明:内联函数需在调用前定义(不能仅声明),通常放在头文件中(方便多个编译单元包含)。
  • 避免重复定义:允许在多个编译单元中定义,但所有定义必须完全一致(否则链接报错)。

与宏(#define)的区别

对比维度 内联函数(inline 宏(#define
类型检查 有(编译期校验参数类型) 无(文本替换,无类型校验)
调试支持 支持(可打断点、查看变量) 不支持(替换后无宏信息)
复杂逻辑支持 支持(循环、分支等) 不适合(易因优先级出错)
作用域 受作用域限制(如局部内联) 全局替换(无作用域)
副作用风险 低(参数仅计算一次) 高(参数可能多次计算)

六、默认参数

规则

  • 靠右原则:默认参数必须从右往左连续指定,不能跳过右侧参数省略左侧参数

  • 示例(正确 vs 错误):

    1
    2
    3
    4
    void func(int a, int b=2, int c=3); // 正确(从右往左指定)
    func(1); // 等价于func(1,2,3)
    func(1,4); // 等价于func(1,4,3)
    // func(,4,5); // 错误(跳过左侧a,未指定默认值)
  • 声明与定义一致性:默认参数仅需在声明或定义中指定一次(建议在声明中指定,方便外部调用者查看)。