C++学习笔记——函数指针、Lambda表达式、谨慎使用using namespace std、命名空间

目录

一、函数指针

二、Lambda表达式

[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] 只复制 xy
显式引用捕获 [&x, &y] 只引用 xy
混合捕获 [=, &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 命名空间中的所有名称(如 vectorstringsortfind 等)引入当前作用域。一旦你自定义了同名函数或类(例如自己写的 vectorsort),就会发生名称冲突 ,导致编译错误或更隐蔽的意外重载

cpp 复制代码
#include <iostream>
using namespace std;

int vector = 42;   // 错误:std::vector 也是名称,冲突!

2. 破坏代码的明确性与可读性

使用 std:: 前缀可以让代码的读者一眼知道某个名字来自标准库。省略前缀后,读到 vectorsortcopy 时,无法区分是标准库还是自定义的,增加理解成本。

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
}
相关推荐
red_redemption1 小时前
自由学习记录(214)
学习
疯狂打码的少年1 小时前
【数据结构】树的基本概念与二叉树定义
java·数据结构·笔记·算法
马上去吃汉堡王2 小时前
Windows11安装Geant4
笔记
气泡水。2 小时前
RHCSA笔记实记
linux·笔记
祈禾3 小时前
Redis三大特殊数据类型
运维·服务器·数据库·redis·笔记·缓存
祈禾3 小时前
计算机组成原理之编译、汇编、链接、解释
开发语言·汇编·笔记·学习
知识分享小能手3 小时前
线性代数学习教程,从入门到精通,矩阵的初等变换与线性方程组(6)
学习·线性代数·矩阵
小龙报3 小时前
【优选算法】1.搜索插入位置 2.x的平方根
java·c语言·数据结构·c++·python·算法·蓝桥杯
A_humble_scholar3 小时前
Linux(二) C++ TCP/UDP网络编程
linux·网络·c++
我命由我123454 小时前
四流一致(合同流、业务流、资金流、发票流)
经验分享·学习·职场和发展·求职招聘·职场发展·产品经理·学习方法