目录
[2.1 基础语法](#2.1 基础语法)
[2.2 Lambda 与 std::function / 函数指针](#2.2 Lambda 与 std::function / 函数指针)
[三、谨慎使用using namespace std](#三、谨慎使用using namespace std)
[1. 命名空间污染](#1. 命名空间污染)
[2. 破坏代码的明确性与可读性](#2. 破坏代码的明确性与可读性)
[3. 头文件中使用是灾难](#3. 头文件中使用是灾难)
[4.1 为什么需要命名空间?](#4.1 为什么需要命名空间?)
[4.2 定义与使用命名空间](#4.2 定义与使用命名空间)
[4.3 using 的三种用法](#4.3 using 的三种用法)
一、函数指针
简单来说,函数指针就是指向函数的指针变量 ,它允许你将函数作为参数传递,从而实现更灵活、动态的程序行为。这种技术在实现回调函数(Callback) 时尤其有用。
| 概念 | 说明 |
|---|---|
| 本质 | 函数存储在内存中,因此有地址。函数指针就是存储这个地址的变量。 |
| 获取地址 | 直接使用函数名 (如 HelloWorld)而不加括号 (),即可获得函数地址。&HelloWorld 也是有效的,但函数名本身会隐式转换。 |
| 主要用途 | 1. 间接调用函数 2. 作为参数传递,实现回调 |
| 现代实践 | 现代 C++ 中常用 std::function 和 Lambda 表达式来替代。 |
cpp
#include <iostream>
#include <vector>
// 一个接收函数指针作为参数的函数
void ForEach(const std::vector<int>& values, void(*func)(int)) {
for (int value : values) {
func(value); // 通过函数指针调用传入的函数
}
}
// 一个具体的操作函数
void PrintValue(int value) {
std::cout << "Value: " << value << std::endl;
}
void HelloWorld() {
std::cout << "Hello World!" << std::endl;
}
int Add(int a, int b) {
return a + b;
}
// 使用 typedef
typedef void(*HelloWorldFunction)();
// 使用 using (现代C++推荐)
using AddFunction = int(*)(int, int);
int main() {
HelloWorldFunction func1 = HelloWorld;
func1();
AddFunction func2 = Add;
int result = func2(3, 5); // result 为 8
std::vector<int> values = {1, 2, 3, 4, 5};
// 将 PrintValue 函数作为参数传递给 ForEach
ForEach(values, PrintValue);
return 0;
}
二、Lambda表达式
2.1 基础语法
一个 Lambda 表达式通常写成如下形式:
cpp
[capture](parameters) -> return_type { body }
| 组成部分 | 说明 |
|---|---|
[capture] |
捕获列表:指定哪些外部变量可以被 Lambda 内部访问。 |
(parameters) |
参数列表:与普通函数类似,可以省略(无参时括号可略)。 |
-> return_type |
返回类型 :通常可省略,编译器会从 return 语句推导。 |
{ body } |
函数体:具体逻辑代码。 |
捕获列表决定了 Lambda 能否使用其定义所在作用域中的变量。这是 Lambda 与普通函数指针最大的不同点。
| 捕获方式 | 写法 | 说明 |
|---|---|---|
| 空捕获 | [] |
不使用任何外部变量。 |
| 值捕获 | [=] |
以复制 方式捕获所有外部变量(Lambda 内部不能修改副本,除非加 mutable)。 |
| 引用捕获 | [&] |
以引用方式捕获所有外部变量(可以修改原变量)。 |
| 显式值捕获 | [x, y] |
只复制 x 和 y。 |
| 显式引用捕获 | [&x, &y] |
只引用 x 和 y。 |
| 混合捕获 | [=, &x] |
除 x 用引用外,其余变量用值捕获。 |
[&, x] |
x 用值捕获,其余用引用捕获。 |
cpp
std::vector<int> values = {1, 2, 3, 4, 5};
// 之前的写法:传递函数指针
void Print(int v) { std::cout << v << " "; }
ForEach(values, Print);
// Lambda 写法:内联定义
ForEach(values, [](int v) { std::cout << v << " "; });
2.2 Lambda 与 std::function / 函数指针
| 特性 | Lambda (无捕获) | Lambda (有捕获) | std::function |
|---|---|---|---|
| 能否转成函数指针 | ✅ 可以(隐式转换) | ❌ 不可以 | ❌ 不可以 |
| 存储开销 | 极小(同函数指针) | 捕获变量大小 | 有一定开销(类型擦除) |
| 灵活性 | 只能使用全局/静态信息 | 可使用任意上下文变量 | 可包装任何可调用对象 |
示例:无捕获的 Lambda 可以赋值给函数指针
cpp
void (*funcPtr)(int) = [](int x) { std::cout << x; }; // 合法
而有捕获的 Lambda 则不行:
cpp
int y = 5;
// void (*badPtr)(int) = [y](int x) { std::cout << x + y; }; // 编译错误
此时需要用 std::function 接收 有捕获的Lambda表达式:
cpp
#include <functional>
std::function<void(int)> func = [y](int x) { std::cout << x + y; };
func(10); // 输出 15
三、谨慎使用using namespace std
1. 命名空间污染
using namespace std; 会将 std 命名空间中的所有名称(如 vector、string、sort、find 等)引入当前作用域。一旦你自定义了同名函数或类(例如自己写的 vector 或 sort),就会发生名称冲突 ,导致编译错误或更隐蔽的意外重载。
cpp
#include <iostream>
using namespace std;
int vector = 42; // 错误:std::vector 也是名称,冲突!
2. 破坏代码的明确性与可读性
使用 std:: 前缀可以让代码的读者一眼知道某个名字来自标准库。省略前缀后,读到 vector、sort、copy 时,无法区分是标准库还是自定义的,增加理解成本。
3. 头文件中使用是灾难
如果在一个头文件中使用 using namespace std;,那么所有包含这个头文件的 .cpp 文件都会被"污染"-- 被迫引入整个 std 命名空间。这会导致不可控的名称冲突,且难以追踪。永远不要在头文件中使用 using namespace std;
四、命名空间
命名空间是 C++ 中用于组织代码、防止名称冲突的核心机制。
4.1 为什么需要命名空间?
其作用包括:
-
避免全局命名污染:大型项目中,多个开发者可能无意中使用相同的全局名称。
-
逻辑分组:将相关功能(如数学计算、文件 I/O)放入同一命名空间,提高可读性。
-
控制符号可见性 :配合
using指令灵活管理哪些名称进入当前作用域。
例如,没有命名空间时,两个库都定义了 print() 函数:
cpp
// 库 A
void print(const std::string& s) { ... }
// 库 B
void print(int x) { ... }
// 你的代码:调用哪个 print?编译器无法区分。
有了命名空间,可以明确区分。
4.2 定义与使用命名空间
基本定义
cpp
namespace MyNamespace {
int value = 10;
void func() { std::cout << "func\n"; }
class MyClass { ... };
}
访问成员:作用域运算符 ::
cpp
MyNamespace::value = 20;
MyNamespace::func();
MyNamespace::MyClass obj;
嵌套命名空间
cpp
namespace Outer {
namespace Inner {
void nested() { ... }
}
}
// 调用
Outer::Inner::nested();
4.3 using 的三种用法
| 形式 | 作用域影响 | 安全性 |
|---|---|---|
using namespace X; |
将 X 中所有名称引入当前作用域 |
高风险(名称污染) |
using X::symbol; |
仅引入单个符号 | 低风险 |
namespace alias = ...; |
为命名空间起别名 | 无风险 |
示例:
cpp
namespace LongNameSpace { int x; }
// 别名
namespace Short = LongNameSpace;
// 引入单个符号
using std::cout;
using std::endl;
// 引入整个命名空间(谨慎!)
using namespace std; // 不推荐
内联命名空间
内联命名空间中的符号会自动被提升到外层命名空间 ,常用于版本管理。
cpp
namespace MyLib {
inline namespace v1 {
void feature() { std::cout << "v1\n"; }
}
namespace v2 {
void feature() { std::cout << "v2\n"; }
}
}
int main() {
MyLib::feature(); // 调用 v1 版本(因为 v1 是内联的)
MyLib::v2::feature(); // 显式调用 v2
}