C++ 常见语法

智能指针

shared_ptr

std::shared_ptr 是 C++11 引入的智能指针(定义在 <memory> 头文件中),用于共享对象的所有权并自动管理内存,避免手动 new/delete 导致的内存泄漏。

核心原理:引用计数

std::shared_ptr 通过引用计数(reference count) 机制工作:

  • 当创建一个 shared_ptr 指向对象时,引用计数初始化为 1
  • 每当一个新的 shared_ptr 拷贝或赋值指向同一对象时,引用计数加 1
  • shared_ptr 被销毁(如超出作用域)或指向其他对象时,引用计数减 1
  • 当引用计数变为 0 时,自动调用对象的析构函数释放内存。

基本用法

1. 创建 shared_ptr
#include <memory>
#include <iostream>

class MyClass {
public:
    MyClass() { std::cout << "构造函数" << std::endl; }
    ~MyClass() { std::cout << "析构函数" << std::endl; }
    void do_something() { std::cout << "执行操作" << std::endl; }
};

int main() {
    // 方法 1:通过 std::make_shared 创建(推荐,更高效)
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();

    // 方法 2:通过 new 手动创建(不推荐,需手动传入指针)
    std::shared_ptr<MyClass> ptr2(new MyClass());

    return 0; // 离开作用域时,引用计数归 0,自动释放内存
}
2. 共享所有权
int main() {
    auto ptr1 = std::make_shared<MyClass>(); // 引用计数 = 1
    std::shared_ptr<MyClass> ptr2 = ptr1;    // 引用计数 = 2(拷贝赋值)
    
    ptr1->do_something();  // 通过指针调用成员函数
    (*ptr2).do_something(); // 解引用后调用

    // ptr1 销毁(超出作用域),引用计数 = 1
    // ptr2 销毁,引用计数 = 0 → 调用析构函数
    return 0;
}
3. 获取原始指针

通过 get() 方法获取原始指针(谨慎使用,避免手动管理):

auto ptr = std::make_shared<MyClass>();
MyClass* raw_ptr = ptr.get(); // 获取原始指针
// 注意:不要用 delete raw_ptr,否则会导致 double free 错误

常见场景

  1. 多所有者共享对象
    当一个对象需要被多个组件共享(如多线程访问同一资源),shared_ptr 确保只有当所有使用者都不再需要时才释放内存。

  2. 容器存储动态对象
    在容器中存储 shared_ptr 可以安全管理动态对象的生命周期:

    #include <vector>
    std::vector<std::shared_ptr<MyClass>> vec;
    vec.push_back(std::make_shared<MyClass>());
    vec.push_back(std::make_shared<MyClass>());
    // 容器销毁时,所有 shared_ptr 自动释放对象
    
  3. 避免悬垂指针
    相比普通指针,shared_ptr 不会出现“对象已释放但指针仍指向该地址”的悬垂指针问题。

注意事项

  1. 避免循环引用
    两个 shared_ptr 互相引用会导致引用计数无法归 0,造成内存泄漏。解决方法是使用 std::weak_ptr 打破循环:

    struct A { std::shared_ptr<B> b; };
    struct B { std::shared_ptr<A> a; };
    
    // 循环引用示例(内存泄漏)
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->b = b;
    b->a = a; // 互相引用,引用计数永远为 1
    
  2. 不要用同一个原始指针创建多个 shared_ptr
    会导致重复释放内存(double free):

    MyClass* raw = new MyClass();
    std::shared_ptr<MyClass> p1(raw);
    std::shared_ptr<MyClass> p2(raw); // 错误!p1 和 p2 会各自释放 raw
    
  3. 优先使用 std::make_shared
    相比 shared_ptr(new T())std::make_shared 可以一次性分配对象和引用计数的内存,更高效且能避免某些异常安全问题。

总结

std::shared_ptr 是 C++ 中管理共享对象生命周期的核心工具,通过引用计数自动释放内存,大幅降低内存泄漏风险。适用于多个所有者需要共享同一对象的场景,但需注意避免循环引用等问题。

lambda表达式

你可能是指“lambda 表达式”(Lambda Expression),这是 C++11 及以上版本引入的匿名函数特性,允许在代码中直接定义短小的函数对象,无需单独声明函数。

核心作用

lambda 表达式的主要价值是简化代码,尤其适合作为“临时使用的小函数”(如回调函数、算法参数等),避免单独定义函数的繁琐。

基本语法

lambda 表达式的基本格式如下:

[capture](parameters) -> return_type {
    // 函数体
}

各部分含义:

  • [capture]:捕获列表,用于捕获外部变量(lambda 表达式所在作用域的变量)到函数体内使用。
  • (parameters):参数列表,与普通函数的参数列表用法相同(可省略,若没有参数)。
  • -> return_type:返回类型(可选,若函数体只有一条 return 语句,编译器可自动推导)。
  • { ... }:函数体,包含具体逻辑。

常见用法示例

1. 最简单的 lambda(无参数、无返回值)
#include <iostream>

int main() {
    // 定义并立即调用 lambda
    []() { 
        std::cout << "Hello, Lambda!" << std::endl; 
    }();  // 末尾的 () 表示立即执行

    return 0;
}
2. 带参数和返回值的 lambda
#include <iostream>

int main() {
    // 带参数和返回值的 lambda(返回类型自动推导)
    auto add = [](int a, int b) { 
        return a + b; 
    };

    std::cout << add(3, 5) << std::endl;  // 输出 8

    return 0;
}
3. 捕获外部变量(重点)

lambda 可以通过 [capture] 捕获外部变量,常见捕获方式:

  • []:不捕获任何变量。
  • [=]:按值捕获所有外部变量(函数体内为副本,修改不影响外部)。
  • [&]:按引用捕获所有外部变量(函数体内修改会影响外部)。
  • [x, &y]:按值捕获 x,按引用捕获 y

示例:

#include <iostream>

int main() {
    int x = 10, y = 20;

    // 按值捕获 x,按引用捕获 y
    auto func = [x, &y]() {
        // x = 100;  // 错误:按值捕获的变量不能修改
        y = 200;     // 正确:按引用捕获的变量可以修改
        std::cout << "x = " << x << ", y = " << y << std::endl;
    };

    func();               // 输出 x = 10, y = 200
    std::cout << "外部 y = " << y << std::endl;  // 输出 200(被修改)

    return 0;
}
4. 作为算法参数(典型场景)

在 STL 算法中,lambda 常用于简化回调逻辑(如排序、遍历):

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> nums = {3, 1, 4, 1, 5};

    // 用 lambda 作为排序规则(降序)
    std::sort(nums.begin(), nums.end(), [](int a, int b) {
        return a > b;  // 从大到小排序
    });

    // 用 lambda 遍历输出
    for_each(nums.begin(), nums.end(), [](int num) {
        std::cout << num << " ";  // 输出 5 4 3 1 1
    });

    return 0;
}

与普通函数的区别

  • 匿名性:lambda 表达式没有函数名,通常通过 auto 赋值给变量使用。
  • 捕获变量:可以直接捕获外部作用域的变量(普通函数需要通过参数传递)。
  • 生命周期:通常在定义的作用域内使用,适合短期临时逻辑。

总结

lambda 表达式是 C++ 中简化代码的强大工具,尤其适合编写短小的回调函数或算法参数,避免单独定义函数的冗余。其核心是通过简洁的语法定义匿名函数,并支持灵活捕获外部变量,在现代 C++ 编程中应用广泛。

std::function

什么是std::function?

std::function 是 C++11 引入的标准库模板类(定义在 <functional> 头文件中),用于封装任何可调用对象(函数、lambda 表达式、函数对象、绑定表达式、成员函数指针等),形成一个类型安全的通用函数包装器。

它的核心价值是消除不同可调用对象之间的类型差异,让各种调用形式统一成相同的接口,便于存储、传递和使用。

基本用法

std::function 需要指定一个函数签名作为模板参数,格式为:

std::function<返回值类型(参数类型1, 参数类型2, ...)>

例如:

  • std::function<int(int, int)>:可封装“接收两个 int 参数、返回 int”的可调用对象。
  • std::function<void()>:可封装“无参数、无返回值”的可调用对象。

支持的可调用对象类型

std::function 几乎可以包装所有“可被调用”的实体:

  1. 普通函数

    int add(int a, int b) { return a + b; }
    std::function<int(int, int)> func = add;
    
  2. lambda 表达式

    std::function<int(int, int)> func = [](int a, int b) { return a * b; };
    
  3. 函数对象(仿函数)

    struct Multiply {
        int operator()(int a, int b) { return a * b; }
    };
    std::function<int(int, int)> func = Multiply();
    
  4. 类成员函数

    class Calculator {
    public:
        int subtract(int a, int b) { return a - b; }
    };
    Calculator calc;
    // 绑定成员函数时需指定对象
    std::function<int(int, int)> func = std::bind(&Calculator::subtract, &calc, std::placeholders::_1, std::placeholders::_2);
    
  5. std::bind 绑定的表达式

    using namespace std::placeholders;
    auto bound = std::bind(add, _2, _1); // 交换参数顺序
    std::function<int(int, int)> func = bound;
    

典型应用场景

  1. 回调函数
    作为函数参数传递,在特定事件(如按钮点击、异步操作完成)发生时执行。

    // 接收一个回调函数,在完成任务后调用
    void do_task(std::function<void(int)> callback) {
        int result = 42; // 模拟任务结果
        callback(result); // 执行回调
    }
    
    // 使用时传入lambda作为回调
    do_task([](int res) { 
        std::cout << "任务完成,结果:" << res << std::endl; 
    });
    
  2. 存储函数列表
    把多个函数放入容器,实现批量执行或动态选择执行逻辑。

    std::vector<std::function<void()>> actions;
    actions.push_back([]() { std::cout << "步骤1" << std::endl; });
    actions.push_back([]() { std::cout << "步骤2" << std::endl; });
    
    // 依次执行所有函数
    for (auto& action : actions) {
        action();
    }
    
  3. 实现策略模式
    动态切换算法或行为,无需修改调用代码。

    // 定义策略接口
    using SortStrategy = std::function<void(std::vector<int>&)>;
    
    // 具体策略
    void bubble_sort(std::vector<int>& v) { /* 冒泡排序实现 */ }
    void quick_sort(std::vector<int>& v) { /* 快速排序实现 */ }
    
    // 排序器(依赖策略)
    class Sorter {
    private:
        SortStrategy strategy;
    public:
        Sorter(SortStrategy s) : strategy(s) {}
        void sort(std::vector<int>& v) { strategy(v); }
    };
    
    // 使用时动态选择策略
    Sorter s(quick_sort); // 用快速排序
    std::vector<int> data = {3,1,4};
    s.sort(data);
    

注意事项

  1. 空状态检查
    未绑定任何可调用对象的 std::function 处于“空状态”,调用时会抛出 std::bad_function_call 异常。可通过 operator bool() 检查:

    std::function<void()> func;
    if (func) { // 检查是否绑定了函数
        func();
    }
    
  2. 性能开销
    std::function 内部通过类型擦除实现,调用时会有轻微的性能损耗(通常可忽略,但不适合极致性能场景)。

  3. 与函数指针的区别
    函数指针只能指向普通函数或静态成员函数,而 std::function 支持更广泛的可调用对象,且类型更灵活。

总结

std::function 是 C++ 中处理“函数作为数据”的强大工具,它统一了各种可调用对象的接口,使得函数可以像普通变量一样被存储、传递和使用,极大提升了代码的灵活性和可扩展性,尤其在回调机制、事件驱动编程等场景中不可或缺。

为什么需要用std::function包装无参无返回值的函数,而不是直接调用?

这本质上是关于“直接调用”与“间接调用”的区别,std::function的价值体现在需要“灵活传递、存储、延迟执行”函数的场景,即使是无参void函数也不例外。

核心原因:函数作为“数据”的需求

直接调用函数适用于“明确知道要调用哪个函数,且立即执行”的场景。但在很多情况下,我们需要把函数像普通变量一样:

  • 存储起来(比如放进容器,后续再执行)
  • 传递给其他函数(比如作为回调函数)
  • 动态切换要执行的函数(比如根据条件选择不同实现)

这时std::function就成了理想的“函数容器”,即使是无参void函数也能从中受益。

具体场景举例

假设我们有几个无参void函数:

#include <functional>
#include <vector>
#include <iostream>

void print_hello() { std::cout << "Hello "; }
void print_world() { std::cout << "World "; }
void print_endl()  { std::cout << std::endl; }
1. 批量存储并批量执行

如果想把这些函数放进一个列表,然后依次执行,直接调用做不到,但std::function可以:

// 用func_t存储多个无参void函数
using func_t = std::function<void()>;
std::vector<func_t> actions;

// 像存储数据一样存储函数
actions.push_back(print_hello);
actions.push_back(print_world);
actions.push_back(print_endl);

// 批量执行(遍历容器调用)
for (auto& f : actions) {
    f(); // 依次执行三个函数,输出 "Hello World "
}
2. 作为回调函数传递

如果需要在某个事件发生后(比如按钮点击、定时结束)执行函数,std::function可以作为“回调参数”传递:

// 一个模拟“延迟执行”的函数,接收一个无参void函数作为回调
void delay_execute(func_t callback, int ms) {
    // 模拟延迟(实际中可能是异步操作)
    // ...
    callback(); // 延迟后执行传入的函数
}

// 使用时:传递不同的无参函数作为回调
delay_execute(print_hello, 1000); // 1秒后执行print_hello
delay_execute(print_endl, 2000);  // 2秒后执行print_endl
3. 封装匿名函数(lambda)

对于临时定义的lambda表达式(无参void类型),std::function是存储和传递它们的便捷方式:

func_t f = [](){ 
    std::cout << "这是一个临时定义的无参函数" << std::endl; 
};

f(); // 执行lambda

总结

直接调用函数是“即时执行已知函数”,而std::function的价值是让函数具备“可存储、可传递、可动态绑定”的特性,即使是最简单的无参void函数,在需要这些特性的场景下(如回调、批量执行、动态逻辑),std::function能极大提升代码的灵活性。

简单说:直接调用是“用函数做事”,std::function是“把函数当数据用”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值