
C 语言的函数是面向过程的「子程序」,而 C++ 的函数是面向对象和泛型编程的「一等公民」。本文将通过12 个核心特性,通过与C语言的对比分析,重点讲解C++在函数设计上的改进与创新。
在C和C++中,函数都是实现代码复用的重要手段。函数允许将一段代码封装起来,通过函数名进行调用,从而提高代码的可读性和可维护性。
在C语言中,函数的定义和声明通常如下所示:
// 函数声明
int add(int a, int b);
// 函数定义
int add(int a, int b) {
return a + b;
}在C++中,函数的定义和声明与C语言非常相似,但C++允许函数具有更复杂的类型系统,例如返回类型和参数类型可以是用户自定义的类型。
#include <iostream>
// 用户自定义类型:二维点
struct Point {
int x;
int y;
// 成员函数示例
void print() {
std::cout << "(" << x << ", " << y << ")\n";
}
};
// 函数声明(参数和返回类型均为自定义类型)
Point addPoints(const Point& a, const Point& b);
int main() {
// 创建自定义类型对象
Point p1 = {1, 2};
Point p2 = {3, 4};
// 调用自定义类型参数的函数
Point result = addPoints(p1, p2);
// 使用自定义类型的成员函数
result.print(); // 输出:(4, 6)
return 0;
}
// 函数定义(实现细节)
Point addPoints(const Point& a, const Point& b) {
Point sum;
sum.x = a.x + b.x;
sum.y = a.y + b.y;
return sum;
}
函数调用在C和C++中都是相同的,通过函数名和参数列表来调用函数。
// C语言中的函数调用
int result = add(3, 4);// C++中的函数调用
int result = add(3, 4);C++引入引用类型作为更安全的指针替代方案:
#include <iostream>
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
std::cout << "Before: x=" << x << ", y=" << y << std::endl;
swap(x, y);
std::cout << "After: x=" << x << ", y=" << y << std::endl;
return 0;
}
引用 vs 指针:
特性 | 引用 | 指针 |
|---|---|---|
空值 | 不能为NULL | 可以为NULL |
重定义 | 不可 | 可以 |
地址操作 | 自动解引用 | 显式操作 |
语法简洁性 | 高 | 低 |
函数重载是C++相对于C语言的一个重要特性。它允许在同一作用域内定义多个同名函数,但这些函数的参数类型或参数个数必须不同。编译器会根据函数调用时提供的参数类型和个数来确定调用哪个函数。
#include <iostream>
// C++支持重载,C语言禁止
void print(int x) { std::cout << "int: " << x << '\n'; }
void print(double x) { std::cout << "double: " << x << '\n'; }
void print(const char* s) { std::cout << "string: " << s << '\n'; }
int main() {
print(42); // int: 42
print(3.14); // double: 3.14
print("Hello"); // string: Hello
}

特性 | C++ | C 语言 |
|---|---|---|
同名函数 | 支持(参数列表不同) | 禁止(链接错误) |
编译机制 | 名称改编(Name Mangling) | 直接使用函数名 |
错误检查 | 编译期类型安全检查 | 仅检查参数数量(弱类型) |
void f(int x, int y = 0); // 声明带默认参数
void f(int x, int y); // 定义不带默认参数(C++允许,但调用时按声明处理)C++允许在函数定义或声明时为参数指定默认值。当调用函数时,如果未提供具有默认值的参数,则使用默认值。
#include <iostream>
using namespace std;
// 定义函数,为参数b和c指定默认值
int add(int a, int b = 5, int c = 10) {
return a + b + c;
}
int main() {
cout << "add(3) = " << add(3) << endl; // 使用默认值:b=5, c=10
cout << "add(3, 7) = " << add(3, 7) << endl; // 使用默认值:c=10
cout << "add(3, 7, 2) = " << add(3, 7, 2) << endl; // 不使用默认值
return 0;
}
注意事项:
// C语言通过宏模拟默认参数
#define SET_CONFIG(port, host) set_config((port) ? (port) : 8080, (host) ? (host) : "localhost")
void set_config(int port, const char* host); // 无默认参数内联函数是C++中用于提高函数调用效率的一种机制。通过将函数体在调用点展开(内联展开),可以减少函数调用的开销(如栈帧的创建和销毁、参数传递等)。
特性 | C++ | C 语言(C99+) |
|---|---|---|
关键字 | inline | static inline(文件作用域) |
链接属性 | 可跨文件(需同名定义) | 静态(文件内) |
编译器控制 | 建议性(可能被忽略) | 强制展开(函数体必须简单) |
// 强制内联(GCC/Clang扩展)
[[gnu::always_inline]]
void fast_math(float& x) { x *= 1.618f; } // 高频调用的数学函数#include <iostream>
using namespace std;
// 使用inline关键字声明内联函数
inline int max(int a, int b) {
return (a > b) ? a : b;
}
int main() {
cout << "max(3, 4) = " << max(3, 4) << endl;
return 0;
}
inline关键字只是向编译器提出一个请求,编译器可以选择忽略这个请求。因此,即使使用了inline关键字,也不能保证函数一定会被内联展开。
inline关键字。
constexpr int square(int x) { return x * x; } // 编译期计算
constexpr auto arr = {square(2), square(3)}; // 编译期初始化数组特性 | constexpr 函数 | C 宏 |
|---|---|---|
类型安全 | 严格类型检查 | 无类型(可能导致副作用) |
调试信息 | 保留函数名和行号 | 宏展开后难以追踪 |
递归支持 | 支持编译期递归 | 不支持 |
constexpr int fib(int n) {
return n <= 1 ? n : fib(n-1) + fib(n-2); // 编译期计算
}
constexpr int fib_42 = fib(42); // 编译期完成计算C++11引入了Lambda表达式,允许在代码中定义匿名函数对象。Lambda表达式提供了一种简洁而强大的方式来定义和使用短小的函数对象。
// [捕获列表](参数列表) mutable? exception? -> 返回类型 { 函数体 }
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 5); // 8捕获方式 | 说明 | 示例 |
|---|---|---|
空捕获 | 不捕获任何变量 | []{} |
值捕获 | 拷贝变量值(默认 const) | [x]{ return x*2; } |
引用捕获 | 引用变量(需确保生命周期) | [&y]{ y++; } |
混合捕获 | 部分值 / 部分引用 | [x, &y]{ return x + y; } |
初始化捕获 | C++14,任意表达式初始化 | [a=1, b=std::move(vec)]{} |
// C语言:通过函数指针实现回调
void (*callback)(int);
callback = &handle_event;
// C++:lambda直接捕获上下文
std::vector<int> data = {1,2,3};
std::for_each(data.begin(), data.end(), [&](int x) {
std::cout << x * data.size(); // 直接捕获data
});#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> vec = {1, 2, 3, 4, 5};
// 使用Lambda表达式对vector中的元素进行排序(降序)
sort(vec.begin(), vec.end(), [](int a, int b) {
return a > b;
});
// 输出排序后的vector
for (int n : vec) {
cout << n << " ";
}
cout << endl;
return 0;
}
template <typename T>
T max(T a, T b) { return a > b ? a : b; }
int main() {
std::cout << max(5, 3); // int
std::cout << max(3.14, 2.71); // double
}// 特化指针版本
template <typename T>
T* max(T* a, T* b) { return *a > *b ? a : b; }
// 特化字符串版本
template <>
const char* max(const char* a, const char* b) {
return std::strcmp(a, b) > 0 ? a : b;
}
class Shape {
public:
virtual double area() const = 0; // 纯虚函数
};
class Circle : public Shape {
public:
double area() const override { return M_PI * r * r; }
};class Data {
private:
int value;
public:
int get() const { return value; } // 保证不修改成员
void set(int v) { value = v; }
};// 声明不抛异常(C++11)
void critical_operation() noexcept {
// 若抛出异常,调用std::terminate()
}
// 有条件不抛异常
void safe_operation() noexcept(std::is_integral_v<Param>) {
// 仅当Param为整数类型时不抛异常
}特性 | C++ | C 语言 |
|---|---|---|
错误处理 | 异常机制(try/catch/throw) | 返回错误码或全局错误变量(errno) |
错误传播 | 栈展开(Stack Unwinding) | 依赖函数调用链检查返回值 |
性能 | 无异常时零开销 | 始终检查返回值(潜在性能损失) |
// 传统写法(需前置声明)
template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
return t + u;
}
// C++14简化(自动推导)
template <typename T, typename U>
auto add(T t, U u) {
return t + u;
}// 返回指向成员函数的指针
auto get_fun() -> int (Data::*)() const {
return &Data::get;
}int base = 10;
auto adder = [a = base + 10](int x) { return a + x; };
std::cout << adder(5); // 25(a=20)// C语言需手动封装状态
typedef struct { int base; } Adder;
int add(Adder* self, int x) { return self->base + x; }
Adder adder = { .base = 20 };
add(&adder, 5); // 25(需显式传递状态)class Data {
friend void print(const Data& d); // 友元声明
private:
int value;
};
void print(const Data& d) { // 访问私有成员
std::cout << "Value: " << d.value << '\n';
}template <typename T>
class Container {
friend T; // 授予整个类友元权限
friend void debug(Container<T>& c); // 授予特定函数权限
};class Data {
public:
void print() const { std::cout << value << '\n'; }
int value;
};
int main() {
Data d{42};
void (Data::*mem_fn)() const = &Data::print;
(d.*mem_fn)(); // 42(调用成员函数)
}特性 | C++ 成员函数指针 | C 语言函数指针 |
|---|---|---|
绑定对象 | 必须关联类对象 | 独立于数据(面向过程) |
语法 | &Class::member | &function |
调用方式 | obj.*mem_fn() 或 ptr->*mem_fn() | fn() |
#include <coroutine>
struct Generator {
struct promise_type {
int current_value = 0;
Generator get_return_object() { return Generator{this}; }
std::suspend_always yield_value(int value) {
current_value = value;
return {};
}
std::suspend_void return_void() { return {}; }
};
// ... 其他实现 ...
};
Generator countdown(int n) {
for (; n >= 0; --n) co_yield n;
}
// 使用协同函数
for (int x : countdown(5)) {
std::cout << x << ' '; // 5 4 3 2 1 0
}// 优先匹配非模板函数
void print(int x) { /* 特化实现 */ }
template <typename T> void print(T x) { /* 通用实现 */ }// 避免不必要的捕获
auto processor = [=](int x) mutable noexcept { /* 无堆分配的轻量级lambda */ };class Resource {
public:
void use() const noexcept { /* 无修改的常量成员 */ }
~Resource() = default; // 遵循Rule of Zero
};特性 | C 语言 | C++ | 价值定位 |
|---|---|---|---|
函数重载 | 不支持 | 支持(编译期多态) | 接口统一化 |
默认参数 | 不支持 | 支持(接口弹性) | 减少重复代码 |
lambda 表达式 | 不支持 | 支持(匿名函数 + 状态捕获) | 函数式编程支持 |
成员函数 | 结构体 + 函数指针模拟 | 原生支持(封装 / 继承 / 多态) | 面向对象基础 |
函数模板 | 不支持 | 支持(泛型编程) | 类型安全的代码复用 |
constexpr | 不支持 | 支持(编译期计算) | 性能与安全性的双重提升 |
编程哲学:C++ 函数不仅是代码块,更是类型系统的延伸、抽象机制的载体、运行时与编译时的桥梁。掌握这些特性,方能驾驭现代 C++ 的三大范式(面向对象、泛型、函数式)。
using声明在模板编程中有着重要应用,如定义模板类型别名等。