使用函数处理不同类型的数据
一、函数重载
1. 完整定义
在同一个作用域 (全局/同一个类)中,存在多个函数名完全相同 ,但形参列表存在差异 的一组函数,编译器会根据调用时传入实参的个数、类型、顺序自动匹配对应函数,这个机制称为函数重载。
2. 构成重载的3种合法差异
- 参数个数 不同:
void f(int);/void f(int, double); - 参数类型 不同:
void f(int);/void f(double); - 参数顺序 不同:
void f(int,char);/void f(char,int);
3. ❌ 不能作为重载区分的条件(高频错题)
- 仅返回值不同:
int f();/double f();不算重载,编译报错; - 仅形参变量名不同:
void f(int a);/void f(int b);完全等价,无重载; - 仅顶层
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. 底层本质
数组名作为函数实参时,隐式转换为数组首元素指针,函数内部不会拷贝整个数组,只传递一个地址,因此:
- 函数内修改数组元素,会直接修改外部原数组;
- 函数内部
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. 两大核心作用
- 减少拷贝开销 :传递
string、vector、结构体、大数组等大型对象,避免完整复制; - 函数内修改外部变量:无需通过返回值带出修改结果,直接操作原变量。
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) |
无拷贝 | 不可以 | 可以 |
什么是「临时值(临时对象)」
程序运行中没有独立变量名、转瞬即逝、用完立刻销毁 的数值/对象,就叫临时值。
常见例子:
- 字面常量:
10、3.14、"abc" - 表达式运算结果:
a + b、x * 2 - 函数返回值:
add(1,2)、vec.back() - 构造临时对象:
vector<int>{1,2,3}
临时值存放在寄存器/临时内存,没有左值身份,不能取地址、不能被普通左值引用绑定。
- 值传递
void f(int x):能接收临时值
调用示例:
cpp
void f(int x) {}
f(10); // 字面临时值 OK
f(2 + 3); // 表达式临时结果 OK
原理:
临时值会拷贝一份副本 给形参x,函数操作副本,和原临时对象无关,所以允许传入临时。
- 普通左值引用
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&
- 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. 特殊规则
inline只是建议,不是强制命令,编译器拥有最终决定权;- 在类内部直接定义的成员函数,会被编译器隐式标记为inline;
- 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;
}
- 形参
int& x:x是传入实参的左值引用 ,类型为int& decltype(x):提取x完整类型,结果就是int&-> decltype(x):强制指定函数返回值类型为int&,保留引用属性
补充:decltype(auto)简化版(C++14)
C++14新增decltype(auto),不需要尾置语法,等价简化:
cpp
// C++14 一行替代尾置写法,自动保留引用/const
decltype(auto) getVal(int& x) {
return x;
}
auto:丢弃引用、constdecltype(auto):完整保留返回表达式的所有类型修饰(引用、const、volatile)
三、lambda匿名函数
Lambda = 匿名函数 :没有函数名字、可以就地临时写出来、用完就丢的小型函数。
普通函数需要单独定义在全局 / 类里,而 Lambda 可以直接写在调用它的地方(比如 sort、循环、回调里),专门处理一次性的简短逻辑。
1. 完整标准语法
cpp
[捕获列表](形参列表) mutable -> 返回类型 { 函数体; }
各分段作用:
[]捕获列表:控制lambda访问外部局部变量的方式;()参数列表:和普通函数参数规则一致;mutable可选:允许修改值捕获的外部变量;->类型尾置返回:可省略,编译器自动推断返回类型。
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. 优势
无需单独定义全局/局部函数,临时逻辑就地编写,大幅简化算法回调、异步回调代码。