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
}
相关推荐
夜猫子ing2 小时前
如何编写一个CMakelists文件
开发语言·c++
独小乐2 小时前
013.定时器之系统Tick实现|千篇笔记实现嵌入式全栈/裸机篇
linux·笔记·单片机·嵌入式硬件·arm
是上好佳佳佳呀2 小时前
【前端(六)】HTML5 新特性笔记总结
前端·笔记·html5
踮起脚看烟花2 小时前
chapter10_泛型算法
c++·算法
@小博的博客2 小时前
【Linux探索学习】第六弹:操作系统的概念及冯诺依曼体系结构
linux·学习
山栀shanzhi2 小时前
C++四大常见排序对比
c++·算法·排序算法
云栖梦泽2 小时前
Linux内核与驱动:8.ioctl驱动基础
linux·c++
talen_hx2962 小时前
《零基础入门Spark》学习笔记 Day 14
大数据·笔记·学习·spark
Clarence Liu2 小时前
langchain源码研究 - deepagents设计思想学习
人工智能·驱动开发·学习·langchain