C++ 函数:从基础到进阶及二级结论总结大全
函数是 C++ 中组织代码、封装逻辑的基本单元。从简单的自由函数到泛型算法、函数对象与 Lambda,理解函数相关规则是掌握 C++ 的必经之路。本文从基础语法出发,逐步深入高阶用法,并提炼出可直接用于编程实践与面试回答的二级结论。
一、函数基础
1.1 声明与定义
// 声明(函数原型)
int add(int a, int b);
// 定义
int add(int a, int b) {
return a + b;
}
关键点:
-
声明可多次,定义只能一次(ODR 原则)。
-
声明放在头文件中,定义放在源文件中,可分离编译。
-
返回值类型不可省略(除
main外,C++ 不允许隐式int)。
二级结论 1:
函数声明是接口契约,定义是实现细节。在设计库时,应尽量在头文件中只放声明,将定义隐藏于 .cpp 文件,以降低编译依赖。
1.2 参数传递基础
| 传递方式 | 语法 | 特点 |
|---|---|---|
| 值传递 | void f(int x) |
拷贝实参,函数内修改不影响原值;效率取决于拷贝开销 |
| 指针传递 | void f(int *p) |
传递地址,可修改所指对象;可为空;需检查 |
| 引用传递 | void f(int &r) |
别名,无拷贝,可修改实参;不可为空 |
| 常引用传递 | void f(const int &r) |
无拷贝且只读;可绑定右值 |
二级结论 2:
对于只读大对象参数,应优先使用 const T&:既避免拷贝,又保护实参不被修改,且可接受右值。对于内置类型,值传递通常更优。
1.3 数组参数
void f(int arr[]); // 等价于 void f(int* arr); void f(int arr[10]); // 10 被忽略,仍是指针 void f(int (&arr)[10]); // 引用,数组大小是类型的一部分
二级结论 3:
-
传递数组时,一维数组会退化为指针,丢失长度信息;可改用
std::array、std::vector或模板+引用数组保持长度。 -
多维数组只有第一维可省略:
void f(int arr[][5])等价于void f(int (*arr)[5])。
二、默认参数
void func(int a, int b = 10, int c = 20); func(1); // 1,10,20 func(1, 2); // 1,2,20 func(1, 2, 3); // 1,2,3
规则:
-
有默认值的参数必须从右向左连续指定。
-
声明与定义只能在一处指定默认值(通常在声明处)。
-
默认参数在编译时替换,不参与重载决议。
二级结论 4:
默认参数不是函数重载的替代品。如果不同参数组合需要不同逻辑,应使用重载而非默认参数,避免语义混淆。
三、函数重载
void print(int x); void print(double x); void print(const std::string &s);
重载规则:
-
参数数量、类型或顺序不同(不包含返回值)。
-
顶层
const不区分重载(值参数):void f(int)与void f(const int)视为相同。 -
底层
const可区分:void f(int*)与void f(const int*)不同。 -
引用限定可区分:
void f(int&)与void f(const int&)不同。
二级结论 5:
重载时避免模糊调用(歧义)。当实参可匹配多个重载版本时,编译器按最佳匹配规则选择;若无法区分,则产生编译错误。设计 API 时,应保证每个调用上下文有唯一最佳候选。
四、内联函数
inline int max(int a, int b) { return a > b ? a : b; }
-
inline是对编译器的建议,现代编译器会根据自身启发式决策。 -
内联函数通常定义在头文件中(为可见),否则无法在调用点展开。
-
适合小型、频繁调用的函数;递归函数不易内联。
二级结论 6:
-
内联不改变函数语义,是纯优化手段。不应为性能随意加
inline,应依赖编译器优化和链接时代码生成(LTO)。 -
类内定义的成员函数隐式为内联。
五、函数返回
5.1 返回类型
| 返回类型 | 语义 | 注意事项 |
|---|---|---|
值 T |
返回副本 | 可能触发拷贝/移动;常用于小对象或需安全独立副本 |
引用 T& |
返回别名 | 不能返回局部变量引用;常用于链式调用、访问容器元素 |
常引用 const T& |
只读别名 | 最安全,避免拷贝,常用于返回成员变量 |
指针 T* |
返回地址 | 可为 nullptr,注意生命周期 |
5.2 返回值优化(RVO/NRVO)
std::string make_string() {
return std::string("hello"); // RVO
}
std::string make_string() {
std::string s("hello");
return s; // NRVO
}
二级结论 7:
-
C++17 起返回值优化为强制要求(纯右值传递不发生拷贝/移动)。
-
编写返回局部对象的函数时,直接
return该对象,不要尝试std::move,否则会抑制 RVO。 -
函数按值返回且类型匹配的局部对象,在现代 C++ 中几乎零开销。
六、函数指针
using FuncPtr = int (*)(int, int); // 等价于 typedef int (*FuncPtr)(int, int);
int add(int a, int b) { return a + b; }
FuncPtr fp = add;
int result = fp(3, 4); // 调用
-
函数指针可以指向静态函数、全局函数或类的静态成员函数。
-
不能直接指向非静态成员函数(因含有隐含
this参数)。
二级结论 8:
-
为增强可读性,推荐用
using定义函数指针类型。 -
函数指针是 C 风格的“回调”机制,C++ 中更推荐使用
std::function或模板。
七、函数对象(仿函数)
struct Compare {
bool operator()(int a, int b) const {
return a < b;
}
};
Compare cmp;
bool less = cmp(3, 5); // 像函数一样调用
-
函数对象可以拥有状态(成员变量)。
-
相比函数指针,编译器更容易内联。
-
常与泛型算法搭配。
二级结论 9:
-
在需要自定义比较或操作的状态保持时,优先用函数对象而非函数指针。
-
std::sort等算法接受可调用对象,函数对象性能常优于函数指针。
八、Lambda 表达式(C++11+)
auto f = [](int x) { return x * 2; };
int y = f(10); // 20
捕获方式:
| 捕获 | 语义 |
|---|---|
[] |
不捕获外部变量 |
[=] |
以值捕获所有外部变量 |
[&] |
以引用捕获所有外部变量 |
[a, &b] |
混合捕获:a 按值,b 按引用 |
[this] |
捕获当前对象指针(可访问成员) |
[*this] |
C++17 捕获当前对象副本 |
二级结论 10:
-
Lambda 本质上生成了一个匿名函数对象的类,捕获变量成为其成员。
-
按值捕获的变量默认不可修改(相当于函数对象的
const成员)。若需修改,加mutable关键字。 -
泛型 Lambda:
[](auto x) { ... }生成模板调用运算符。
二级结论 11:
-
避免以引用捕获的方式让 Lambda 超出被引用变量的生命周期(悬垂引用)。
-
在异步或回调场景,推荐按值捕获或使用
shared_ptr延长生命周期。
九、std::function:通用函数包装器
#include <functional>
std::function<int(int, int)> func;
func = add; // 函数指针
func = [](int a, int b) { return a * b; }; // Lambda
func = std::multiplies<int>(); // 函数对象
-
std::function能存储任何可调用实体,只要签名兼容。 -
有轻微运行时开销(类型擦除),不适合极端性能场景。
二级结论 12:
-
API 需要接受用户自定义可调用对象时,首选模板;次选
std::function(当需要运行时绑定或类型统一时)。 -
在高性能循环中,避免直接传递
std::function,改用模板或硬编码调用。
十、成员函数与 const
class MyClass {
int data;
public:
int get() const { return data; } // const 成员函数
void set(int x) { data = x; }
};
要点:
-
const成员函数的this是const MyClass*,不能修改非mutable成员。 -
const对象只能调用const成员函数。 -
可重载:
const与 non-const版本可共存,根据对象是否是常量自动选择。
二级结论 13:
-
如果成员函数不修改对象状态,应声明为
const。这不仅是语义承诺,更影响接口在const上下文中的可用性。 -
减少代码重复的惯用法:在 non-
const版本中调用const版本,结合const_cast。
const T& get() const { ... }
T& get() {
return const_cast<T&>(static_cast<const MyClass&>(*this).get());
}
十一、虚函数与多态
class Base {
public:
virtual void show() const { ... }
virtual ~Base() = default;
};
class Derived : public Base {
public:
void show() const override { ... }
};
-
virtual启用动态绑定,通过基类指针/引用调用时根据实际类型派发。 -
override关键字确保确实重写了基类虚函数。 -
final可禁止进一步重写。 -
虚函数表(vtable)是常见实现方式,每个多态类有 vtable,对象包含 vptr。
二级结论 14:
-
若类可能被继承,基类析构函数应声明为
virtual或protected(非virtual析构会阻止正确释放派生资源)。 -
构造函数不能是虚函数(对象尚未构造),析构函数可以。
-
默认参数在虚函数中使用需谨慎:默认参数是静态绑定,会取基类定义的默认值,而不是动态类型中的默认值。
十二、模板函数(泛型函数)
template<typename T>
T max(T a, T b) { return a > b ? a : b; }
特性:
-
编译时实例化,生成多个具体版本。
-
支持显式指定模板参数:
max<int>(3, 5)。 -
模板参数推导:可从函数实参自动推导。
-
可重载:非模板函数优先匹配,模板版本仅在没有更佳匹配时使用。
二级结论 15:
-
模板函数定义通常放在头文件中,因为编译器需要看到完整定义才能实例化。
-
模板 + 完美转发是实现泛型库的基础(见下文)。
十三、完美转发与可变参数模板
template<typename F, typename... Args>
auto call(F&& f, Args&&... args) -> decltype(f(std::forward<Args>(args)...)) {
return f(std::forward<Args>(args)...);
}
要点:
-
std::forward<Args>(args)保留参数的左值/右值性质。 -
引用折叠规则使万能引用
T&&能绑定左值或右值。 -
可变参数模板
Args...允许任意数量参数。
二级结论 16:
-
完美转发是编写工厂函数、包装函数、委托构造函数等的核心技术。
-
结合
decltype和尾置返回类型,可精确保留原始函数调用的返回类型。
十四、移动语义与函数
void sink(std::string&& s) {
std::string local = std::move(s);
}
-
右值引用
T&&绑定即将销毁的对象,允许移动构造/赋值,避免深拷贝。 -
对于函数参数,
T&&可作为“万能引用”,配合完美转发传递。 -
对于局部变量,如果确定不再使用,可使用
std::move转为右值,启用移动。
二级结论 17:
-
不要返回局部对象的右值引用(引用悬垂)。
-
设计按值返回的函数时,直接返回局部对象即可,不要
std::move,以免抑制 RVO。 -
接受按值参数的函数,若需保留副本,可用
std::move将其移入存储,减少一次拷贝。
十五、constexpr 函数(编译期函数)
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int f5 = factorial(5); // 编译期计算
-
constexpr函数可能在编译期执行,也可在运行期执行,取决于调用上下文。 -
C++14 起允许
constexpr函数中有局部变量和循环,C++17 进一步放宽限制。 -
与
const不同:const只读,constexpr强调编译期求值可能性。
二级结论 18:
-
尽量将可在编译期确定的函数标记为
constexpr,提高性能并支持常量表达式上下文使用。 -
注意:
constexpr函数在运行期调用时,其行为与普通函数一致,无额外约束。
十六、二级结论速查总表
| 编号 | 结论 |
|---|---|
| 1 | 头文件放声明,源文件放定义,降低编译依赖 |
| 2 | 大对象只读参数使用 const T&:避免拷贝、保护实参、接受右值 |
| 3 | 数组参数会退化为指针;保持长度信息用 std::array 或引用数组 |
| 4 | 默认参数替代重载可能导致语义模糊,不同逻辑应用重载 |
| 5 | 设计重载时确保唯一最佳匹配,避免歧义 |
| 6 | 内联由编译器决策,类内定义成员函数隐式内联 |
| 7 | 返回局部对象不要 std::move;RVO 在 C++17 强制 |
| 8 | 回调优先用模板或 std::function 而非函数指针 |
| 9 | 需要状态的函数对象优于函数指针 |
| 10 | Lambda 按值捕获变量不可修改,加 mutable 可改 |
| 11 | 避免引用捕获在异步中产生悬垂引用 |
| 12 | 运行时绑定用 std::function,性能敏感用模板 |
| 13 | 不修改对象的成员函数声明为 const,可减少重复用 const_cast |
| 14 | 基类析构应为虚函数;虚函数默认参数静态绑定 |
| 15 | 模板定义放头文件;非模板比模板优先匹配 |
| 16 | 完美转发保留值类别,是泛型库的基础 |
| 17 | 不要返回局部右值引用,按值返回勿用 std::move |
| 18 | 编译期可行函数标记为 constexpr,同时支持编译期和运行期调用 |
以上为 C++ 函数从基础到高阶的全景式总结。扎实掌握这些规则与二级结论,既是编写高质量 C++ 代码的保证,也是技术面试中脱颖而出的关键
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)