c++复习自存--函数

使用函数处理不同类型的数据

一、函数重载

1. 完整定义

同一个作用域 (全局/同一个类)中,存在多个函数名完全相同 ,但形参列表存在差异 的一组函数,编译器会根据调用时传入实参的个数、类型、顺序自动匹配对应函数,这个机制称为函数重载。

2. 构成重载的3种合法差异
  1. 参数个数 不同:void f(int); / void f(int, double);
  2. 参数类型 不同:void f(int); / void f(double);
  3. 参数顺序 不同:void f(int,char); / void f(char,int);
3. ❌ 不能作为重载区分的条件(高频错题)
  1. 仅返回值不同:int f(); / double f(); 不算重载,编译报错;
  2. 仅形参变量名不同:void f(int a); / void f(int b); 完全等价,无重载;
  3. 仅顶层const修饰值参数:void f(int); / void f(const int); 视为同一个函数。
4. 匹配优先级(编译器调用逻辑)

1.精准匹配(类型完全一致)→ 2.平凡隐式转换(int→long)→ 3.提升转换(char→int)→ 4.算术转换(int→double)

若多个函数同时满足转换条件,产生调用二义性,直接编译失败。

5. 代码示例
cpp 复制代码
#include <iostream>
using namespace std;
// 三组重载print函数
void print(int x) { cout << "整数:" << x << endl; }
void print(double x) { cout << "浮点数:" << x << endl; }
void print(int a, string b) { cout << a << " " << b << endl; }

int main() {
    print(10);      // 匹配int版本
    print(3.14);    // 匹配double版本
    print(20, "test"); // 匹配双参数版本
    return 0;
}
6. 适用场景

统一功能逻辑,适配多种数据类型,无需给不同类型函数起不同名字,提升代码可读性。


二、将数组传递给函数

1. 底层本质

数组名作为函数实参时,隐式转换为数组首元素指针,函数内部不会拷贝整个数组,只传递一个地址,因此:

  1. 函数内修改数组元素,会直接修改外部原数组;
  2. 函数内部sizeof(arr)获取的是指针大小,而非数组总字节长度,必须手动传入数组长度参数。
2. 三种等价传参写法(编译器全部解析为指针)
cpp 复制代码
// 写法1:数组形式(可读性最高,竞赛常用)
void func(int arr[], int len);
// 写法2:指定数组长度(数字会被编译器忽略,无实际约束)
void func(int arr[100], int len);
// 写法3:原生指针形式,和上面完全等价
void func(int* arr, int len);
3. 二维数组传参强制规则

多维数组传参时,除第一维外,其余维度大小必须显式指定

cpp 复制代码
void mat(int arr[][5], int row);  // 合法,第二维固定5列
// void mat(int arr[][], int row); // 非法,无法计算元素偏移
4. const数组传参优化

只读数组建议加const,禁止函数内部修改数组,同时兼容常量数组实参:

cpp 复制代码
void show(const int arr[], int len);
5. 易错坑点

定长数组int a[10]、动态数组vector<int>、指针int* p都可以传给数组形参,但长度必须手动传递;std::array不属于原生数组,传参规则不同。


三、按引用传递参数

1. 引用底层原理

引用T&变量别名 ,编译期直接绑定实参内存地址,运行时不产生任何副本,不分配额外栈内存。

语法:void func(int& x),调用时func(num)x等价于num

2. 两大核心作用
  1. 减少拷贝开销 :传递stringvector、结构体、大数组等大型对象,避免完整复制;
  2. 函数内修改外部变量:无需通过返回值带出修改结果,直接操作原变量。
3. 常量引用 const T&(最优只读传参方案)
cpp 复制代码
void printVec(const vector<int>& v);

优势:

  • 不拷贝,性能和原生指针一致;
  • 编译器禁止函数内部修改容器,保证数据安全;
  • 可接收字面量、临时对象作为实参(普通左值引用T&不支持临时量)。
4. 值传递 / 左值引用 / const引用 完整对比
传递方式 语法 是否拷贝 能否修改外部变量 能否接收临时值
值传递 void f(int x) 拷贝副本 不能 可以
普通引用 void f(int& x) 无拷贝 可以 不可以
const引用 void f(const int& x) 无拷贝 不可以 可以
什么是「临时值(临时对象)」

程序运行中没有独立变量名、转瞬即逝、用完立刻销毁 的数值/对象,就叫临时值。

常见例子:

  1. 字面常量:103.14"abc"
  2. 表达式运算结果:a + bx * 2
  3. 函数返回值:add(1,2)vec.back()
  4. 构造临时对象:vector<int>{1,2,3}

临时值存放在寄存器/临时内存,没有左值身份,不能取地址、不能被普通左值引用绑定

  1. 值传递 void f(int x):能接收临时值

调用示例:

cpp 复制代码
void f(int x) {}
f(10);       // 字面临时值 OK
f(2 + 3);    // 表达式临时结果 OK

原理:

临时值会拷贝一份副本 给形参x,函数操作副本,和原临时对象无关,所以允许传入临时。

  1. 普通左值引用 void f(int& x):不能接收临时值
cpp 复制代码
void f(int& x) {}
f(10); // 编译报错!
f(1+2);// 编译报错!

原因:

int& x 要求绑定有名字、可修改的持久变量(左值)

临时值是转瞬即逝的右值,编译器禁止普通引用绑定它------如果允许绑定,你在函数内修改x,但临时对象马上销毁,修改毫无意义,C++直接禁止这种危险行为。

合法调用只能传已有变量:

cpp 复制代码
int a = 5;
f(a); // 只有带名字的变量能传给int&
  1. const引用 void f(const int& x):可以接收临时值
cpp 复制代码
void f(const int& x) {}
f(10);     // OK
f(3*4);    // OK

原理:

const T&常量左值引用 ,编译器做了特殊规则放宽:

允许绑定临时对象,并且会延长临时对象的生命周期 ,直到函数执行完毕再销毁。

同时加了const限制:函数内不能修改这个临时值,不存在"修改无效"的隐患,所以语法放行。


微处理器如何处理函数调用

一、 内联函数 inline

1. CPU普通函数调用底层开销

调用普通函数时,CPU需要:保存现场寄存器→开辟栈帧→参数压栈→跳转执行函数→返回恢复寄存器,频繁小函数调用会产生大量性能损耗。

2. inline核心作用

inline修饰函数,向编译器提交建议:将函数体代码直接嵌入每一处调用位置,消除函数跳转、栈帧开辟的开销。

3. 适用场景

函数体短小(1~5行)、逻辑简单、高频循环调用:数值计算、取值判断、简单存取函数。

4. ❌ 禁止使用inline的场景

包含循环、递归、大量分支判断、复杂IO操作的函数;编译器会自动忽略inline建议,降级为普通函数。

5. 特殊规则
  1. inline只是建议,不是强制命令,编译器拥有最终决定权;
  2. 类内部直接定义的成员函数,会被编译器隐式标记为inline;
  3. inline函数定义必须和声明放在同一头文件(多文件共用)。
6. 示例
cpp 复制代码
inline int square(int x) { return x * x; }
int main() {
    int a = square(5); // 编译后直接替换为 int a = 5*5;
    return 0;
}

二、自动推断返回类型

分为两套语法,适配C++11/C++14标准:

1. C++14 简化auto返回推断(最常用)
cpp 复制代码
auto add(int a, int b) {
    return a + b; // 编译器根据return表达式自动推导返回值类型
}

约束:函数所有return语句必须返回完全相同类型,否则编译报错;无法用于递归函数(编译器无法提前推导类型)。

2. C++11 尾置返回类型 decltype(auto)

解决参数为引用、返回值需要保留引用属性的场景:

cpp 复制代码
// 保留变量引用属性
auto getVal(int& x) -> decltype(x) {
    return x;
}

适用场景:模板函数、返回类型依赖参数类型的泛型代码。

(1)普通auto返回的缺陷
cpp 复制代码
auto getVal(int& x) {
    return x;
}
int a = 10;
auto res = getVal(a);
res = 99;

这里res普通int副本 ,不是引用:

auto推导时会剥离引用属性 ,就算返回的是引用变量x,返回值依然是值拷贝,无法修改外部a

(2)decltype(表达式)作用

decltype(变量/表达式)原样获取表达式的完整类型,不会剥离引用、const修饰。

cpp 复制代码
int num = 5;
int& r = num;
decltype(r) x = num; // x的类型就是 int&,保留引用
(3)尾置返回语法 auto 函数(参数) -> 类型

C++11 标准写法:

auto只是占位符,真正返回类型写在->后面;

好处:返回类型里可以直接使用函数的参数名(普通前置返回做不到)。

cpp 复制代码
// auto:占位符,真正返回类型在 -> 后指定
// -> decltype(x):返回类型 = x 的完整原始类型(int&)
auto getVal(int& x) -> decltype(x) {
    return x;
}
  1. 形参int& xx是传入实参的左值引用 ,类型为int&
  2. decltype(x):提取x完整类型,结果就是int&
  3. -> decltype(x):强制指定函数返回值类型为int&,保留引用属性

补充:decltype(auto)简化版(C++14)

C++14新增decltype(auto),不需要尾置语法,等价简化:

cpp 复制代码
// C++14 一行替代尾置写法,自动保留引用/const
decltype(auto) getVal(int& x) {
    return x;
}
  • auto:丢弃引用、const
  • decltype(auto):完整保留返回表达式的所有类型修饰(引用、const、volatile)

三、lambda匿名函数

Lambda = 匿名函数 :没有函数名字、可以就地临时写出来、用完就丢的小型函数。

普通函数需要单独定义在全局 / 类里,而 Lambda 可以直接写在调用它的地方(比如 sort、循环、回调里),专门处理一次性的简短逻辑。

1. 完整标准语法
cpp 复制代码
[捕获列表](形参列表) mutable -> 返回类型 { 函数体; }

各分段作用:

  1. []捕获列表:控制lambda访问外部局部变量的方式;
  2. ()参数列表:和普通函数参数规则一致;
  3. mutable可选:允许修改值捕获的外部变量;
  4. ->类型尾置返回:可省略,编译器自动推断返回类型。
2. 捕获规则(考试核心)
捕获写法 含义
[=] 值捕获:复制所有外部局部变量,只读
[&] 引用捕获:绑定所有外部变量别名,可修改原变量
[x] 仅值捕获变量x
[&x] 仅引用捕获变量x
[=, &y] 默认全部值捕获,唯独y使用引用捕获
[this] 捕获当前类对象指针(类内lambda专用)
3. 经典实战场景(STL排序)
cpp 复制代码
#include <algorithm>
#include <vector>
using namespace std;
int main() {
    vector<int> v = {3,1,4,2};
    // lambda作为排序比较函数,降序排列
    sort(v.begin(), v.end(), [](int a, int b){
        return a > b;
    });
    return 0;
}
4. mutable关键字说明

值捕获的变量默认const只读,添加mutable后可在lambda内修改副本,但不会影响外部原变量:

cpp 复制代码
int x = 10;
auto f = [=]() mutable { x++; };
f(); // 仅修改lambda内部副本,外部x仍为10
5. 优势

无需单独定义全局/局部函数,临时逻辑就地编写,大幅简化算法回调、异步回调代码。