C++11:unique_ptr的基本用法、使用场景和最佳使用指南

news2025/6/5 19:27:15

文章目录

  • 1. 简介
  • 2. 基本语法和用法
    • 2.1. 创建unique_ptr
    • 2.2. 访问指向的对象
    • 2.3. 所有权管理
  • 3. 自定义删除器
  • 4. 数组支持
  • 5. 常见使用场景
    • 5.1. RAII资源管理
    • 5.2. 工厂模式
    • 5.3. 容器中存储多态对象
    • 5.4. Pimpl(指针到实现)习惯用法
  • 6. 与其他智能指针的比较
    • 6.1. unique_ptr vs shared_ptr
    • 6.2. unique_ptr vs 原始指针
  • 7. 最佳实践指南
    • 7.1. 创建对象
    • 7.2. 函数参数传递
    • 7.3. 函数返回值
    • 7.4. 需要避免的反模式
  • 8. 总结

1. 简介

unique_ptr是C++11引入的智能指针,它具有对动态分配内存对象的独占所有权。是自动内存管理的核心工具,提供了异常安全的RAII(资源获取即初始化)语义,自动管理对象的生命周期,防止内存泄漏。

unique_ptr 遵循移动语义,只能被移动,不能被复制。这样就确保了在任何时候只有一个unique_ptr 拥有特定对象的所有权。

使用 unique_ptr 有以下几个优点

  • 内存安全:自动防止内存泄漏,是现代C++的核心特性
  • 零开销:提供智能指针的便利性而不牺牲性能
  • 异常安全:即使在异常情况下也能正确管理资源

2. 基本语法和用法

掌握基本语法是使用unique_ptr的基础,不同的创建和访问方式适用于不同的场景,了解它们有助于写出安全高效的代码。

2.1. 创建unique_ptr

可以通过原生指针、make_uniquenew 指针和默认初始化的方式创建 unique_ptr

#include <memory>

// 方法1:使用new(不推荐)
std::unique_ptr<int> ptr1(new int(42));

// 方法2:使用make_unique(推荐,C++14)
std::unique_ptr<int> ptr2 = std::make_unique<int>(42);

// 方法3:默认构造(空指针)
std::unique_ptr<int> ptr3;

// 方法4:从原始指针构造
int* raw_ptr = new int(100);
std::unique_ptr<int> ptr4(raw_ptr);

应该使用哪种创建方式比较好?

  • make_unique最佳:提供异常安全,避免内存泄漏,代码更简洁
  • 避免直接new:直接使用new容易在异常时造成内存泄漏
  • 空指针的用途:用于延迟初始化或条件性对象创建
  • 原始指针转换:用于接管已有的原始指针,但要确保不会重复删除

2.2. 访问指向的对象

创建一个 unique_ptr 之后,需要通过这个指针访问所指向的对象。
访问对象的内容有两种常见的方式:

  • 解引用操作符:直接访问对象的值,适用于简单类型
  • 箭头操作符:访问对象的成员,特别是对于类对象
std::unique_ptr<int> ptr = std::make_unique<int>(42);

// 解引用操作符
int value = *ptr;                // 获取值: 42
std::cout << *ptr << std::endl;  // 输出: 42

// 箭头操作符(对于对象指针)
class Person {
public:
    std::string name;
    void speak() { std::cout << name << " is speaking" << std::endl; }
};

std::unique_ptr<Person> person = std::make_unique<Person>();
person->name = "Alice";
person->speak();  // Alice is speaking

// get()方法获取原始指针
int* raw = ptr.get();  // 获取原始指针,但不转移所有权

2.3. 所有权管理

由于 unique_ptr 是独占所有权,所以所有权只能转移或者消亡。那么,有哪些引起所有权变化的操作呢?

  • std::move:转移一个指针的所有权
  • release():释放 unique_ptr 的所有权到原生指针,此时必须手动释放原生指针所指向的内存。
  • reset() 或者 reset(make_unique<int>(10)) :前者删除当前对象,并将指针设置为 nullptr;后者删除当前对象,指向新对象。
  • swap() :交换两者指针的所有权。
// 移动所有权
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
std::unique_ptr<int> ptr2 = std::move(ptr1);  // ptr1变为nullptr,ptr2拥有所有权

// 释放所有权
std::unique_ptr<int> ptr = std::make_unique<int>(42);
int* raw_ptr = ptr.release();  // ptr变为nullptr返回原始指针
// 注意:必须手动delete raw_ptr

// 重置指针
ptr.reset();                    // 删除当前对象,设为nullptr
ptr.reset(new int(100));        // 删除当前对象,指向新对象

// 交换两个unique_ptr
std::unique_ptr<int> ptr1 = std::make_unique<int>(1);
std::unique_ptr<int> ptr2 = std::make_unique<int>(2);
ptr1.swap(ptr2);  // 或 std::swap(ptr1, ptr2);

API 使用场景

  • 移动语义:高效转移所有权,避免不必要的复制,体现独占所有权语义
  • 谨慎使用release():在需要与C风格API交互时使用,但容易引入内存泄漏
  • reset()灵活管理:动态改变指向的对象,提供运行时灵活性
  • swap()高效交换:避免临时对象,性能优化的需要

3. 自定义删除器

默认删除器只能处理用new分配的对象,但实际开发中经常需要管理各种资源(文件、网络连接、系统句柄等),自定义删除器提供了统一的RAII管理方式。

unique_ptr允许自定义删除器,用于特殊的清理需求。例如下面的例子中,FILE 文件指针需要使用 fclose 函数关闭,这个时候就可以自定义文件删除器删除,避免手动调用 fclose 函数。当超出 file_ptr 的作用域时,会自动调用 FileDeleter

// 自定义删除器 - 函数对象
struct FileDeleter {
    void operator()(FILE* f) {
        if (f) {
            std::fclose(f);
            std::cout << "文件已关闭" << std::endl;
        }
    }
};

std::unique_ptr<FILE, FileDeleter> file_ptr(std::fopen("test.txt", "w"));

4. 数组支持

动态数组在C++中很常见,unique_ptr 提供的数组支持解决了数组内存管理的痛点,自动使用正确的 delete[] 操作符,避免未定义行为。

unique_ptr专门支持动态数组:

// 动态数组
std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);

// 使用下标访问
for (int i = 0; i < 10; ++i) {
    arr[i] = i * i;
    std::cout << arr[i] << " ";
}

// 注意:数组版本不支持解引用和箭头操作符
// *arr;     // 编译错误
// arr->x;   // 编译错误

使用原因

  • 正确删除:自动使用delete[]而不是delete,避免未定义行为
  • 类型安全:编译期防止在数组指针上使用解引用操作
  • 内存安全:自动管理数组生命周期,防止内存泄漏
  • 性能优化:避免使用vector的开销,适合简单的数组需求

5. 常见使用场景

了解典型使用场景有助于在实际开发中正确选择 unique_ptr,下面这些模式是工业级代码的常见实践,掌握它们能显著提高代码质量。

5.1. RAII资源管理

结合 unique_ptr 和 RAII 管理文件资源、数据库连接、网络连接等。下面是一个管理文件资源的例子。

struct FileDeleter {
    void operator()(FILE* f) {
        if (f) {
            std::fclose(f);
            std::cout << "文件已关闭" << std::endl;
        }
    }
};

std::unique_ptr<FILE, FileDeleter> file_ptr(std::fopen("test.txt", "w"));

RAII核心优势

  • 自动资源管理:构造时获取资源,析构时自动释放,无需手动管理;
  • 异常安全保证:即使在异常情况下,unique_ptr 也确保资源正确清理;
  • 组合资源管理:可以在同一类中管理多种不同类型的资源;
  • 移动语义支持:支持高效的资源所有权转移,避免不必要的复制。

5.2. 工厂模式

class Shape {
public:
    virtual ~Shape() = default;
    virtual void draw() = 0;
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "绘制圆形" << std::endl; }
};

class Rectangle : public Shape {
public:
    void draw() override { std::cout << "绘制矩形" << std::endl; }
};

// 工厂函数返回unique_ptr
std::unique_ptr<Shape> create_shape(const std::string& type) {
    if (type == "circle") {
        return std::make_unique<Circle>();
    } else if (type == "rectangle") {
        return std::make_unique<Rectangle>();
    }
    return nullptr;
}

// 使用
auto shape = create_shape("circle");
if (shape) {
    shape->draw();  // 绘制圆形
}

使用原因

  • 明确所有权:工厂返回unique_ptr明确表示调用者拥有对象
  • 多态支持:基类指针可以指向派生类对象,支持多态
  • 内存安全:对象自动管理,无需手动delete
  • 空值语义:返回nullptr表示创建失败,语义清晰

5.3. 容器中存储多态对象

std::vector<std::unique_ptr<Shape>> shapes;

shapes.push_back(std::make_unique<Circle>());
shapes.push_back(std::make_unique<Rectangle>());

for (const auto& shape : shapes) {
    shape->draw();
}
// 容器销毁时,所有对象自动释放

使用原因

  • 多态容器:可以在同一容器中存储不同类型的对象
  • 自动清理:容器销毁时所有对象自动释放,无内存泄漏
  • 移动语义:对象在容器中移动而不是复制,性能更好
  • 异常安全:即使在容器操作中发生异常,已创建的对象也会被正确清理

5.4. Pimpl(指针到实现)习惯用法

Pimpl的核心思想是在类的公有接口(通常在.h头文件中声明)中,不直接包含私有成员变量和私有成员函数的具体声明,而是只包含一个指向不完整类型(通常是一个内部类或结构体,称为Impl类)的指针。这个Impl类则包含了所有原先的私有成员和实现细节。

实现逻辑:

  1. 头文件:
    • 前向声明一个内部实现类,例如 class Impl
    • 持有一个指向该实现类的只能指针,通常是 std::unique_ptr<Impl>,因为Pimpl通常意味着独占所有权;
  2. 实现文件:
    • 定义完整的内部Impl类,包含所有私有数据成员和辅助函数;
    • 实现外部类的构造函数、析构函数、拷贝构造函数等;
    • 实现外部类的公有函数,函数体通过Impl指针调用内部类的函数。

有什么好处?

  1. 减少编译依赖。这是Pimpl最主要的优点。当类的私有成员(尤其是那些依赖于其他复杂头文件的成员)发生改变时,只需要重新编译该类的.cpp实现文件,而不需要重新编译所有包含该类头文件的客户端代码。
  2. 隐藏实现细节。类的用户只能看到公有接口,完全不知道其内部实现细节(如私有成员变量的类型和数量),这增强了封装性。例如,我们为甲方开发了一套库,但是不希望甲方知道算法的内部逻辑。

有什么缺点?

  1. 增加了构造和析构函数的开销。需要额外在堆上为Impl对象分配内存(通过std::make_unique),并进行构造。虽然 std::unique_ptr 能很好地管理生命周期,但堆分配本身有开销。
  2. 增加了调试的复杂度。

代码例子:

// Widget.h - 头文件
class Widget {
public:
    Widget();
    Widget(int value, const std::string& name);
    ~Widget();
    
    // 拷贝和移动操作需要特别处理
    Widget(const Widget& other);
    Widget& operator=(const Widget& other);
    Widget(Widget&& other) noexcept;
    Widget& operator=(Widget&& other) noexcept;
    
    // 公共接口
    void do_something();
    void set_value(int value);
    int get_value() const;
    std::string get_name() const;
    
private:
    class Impl;  // 前向声明,不暴露实现细节
    std::unique_ptr<Impl> pImpl;  // 指向实现的智能指针
};

下面是实现文件:

// Widget.cpp - 实现文件
#include "Widget.h"
#include <iostream>
#include <vector>
#include <map>
#include <complex_third_party_library.h>  // 只在.cpp中包含

// 实现类定义(完全隐藏)
class Widget::Impl {
public:
    Impl(int val, const std::string& n) : value(val), name(n) {}
    
    void do_something() {
        std::cout << "处理 " << name << " 的值: " << value << std::endl;
        // 复杂的实现逻辑...
        process_data();
        use_third_party_library();
    }
    
    void set_value(int val) { value = val; }
    int get_value() const { return value; }
    std::string get_name() const { return name; }
    
private:
    int value;
    std::string name;
    std::vector<double> data;  // 复杂的数据结构
    std::map<std::string, int> cache;
    ThirdPartyObject complex_obj;  // 第三方库对象
    
    void process_data() {
        // 复杂的内部逻辑
    }
    
    void use_third_party_library() {
        // 使用第三方库的代码
    }
};

// 公共接口的实现
Widget::Widget() : pImpl(std::make_unique<Impl>(0, "default")) {}

Widget::Widget(int value, const std::string& name) 
    : pImpl(std::make_unique<Impl>(value, name)) {}

Widget::~Widget() = default;  // unique_ptr自动清理

// 拷贝构造函数
Widget::Widget(const Widget& other) 
    : pImpl(std::make_unique<Impl>(*other.pImpl)) {}

// 拷贝赋值操作符
Widget& Widget::operator=(const Widget& other) {
    if (this != &other) {
        *pImpl = *other.pImpl;
    }
    return *this;
}

// 移动构造函数
Widget::Widget(Widget&& other) noexcept = default;

// 移动赋值操作符
Widget& Widget::operator=(Widget&& other) noexcept = default;

// 委托给实现类的方法
void Widget::do_something() {
    pImpl->do_something();
}

void Widget::set_value(int value) {
    pImpl->set_value(value);
}

int Widget::get_value() const {
    return pImpl->get_value();
}

std::string Widget::get_name() const {
    return pImpl->get_name();
}

6. 与其他智能指针的比较

了解不同智能指针的特点有助于在合适的场景选择合适的工具,避免过度工程或性能损失,这是高级C++程序员必备的知识。

6.1. unique_ptr vs shared_ptr

特性unique_ptrshared_ptr
所有权独占共享
内存开销低(通常只有一个指针大小)高(需要引用计数)
性能高(无引用计数开销)较低(原子操作开销)
线程安全移动操作需要同步引用计数是线程安全的
使用场景明确单一所有者需要共享所有权

选择原因

  • unique_ptr优先:大多数情况下对象只需要一个所有者;
  • 性能考虑:unique_ptr零开销,shared_ptr有引用计数开销;
  • 设计清晰:unique_ptr强制明确所有权关系,设计更清晰;
  • 特定需求:只有真正需要共享所有权时才使用shared_ptr。

6.2. unique_ptr vs 原始指针

// 原始指针的问题
void problematic_function() {
    int* ptr = new int(42);
    
    if (some_condition) {
        return;  // 内存泄漏!
    }
    
    risky_operation();  // 如果抛出异常,内存泄漏!
    
    delete ptr;  // 可能永远执行不到
}

// unique_ptr解决方案
void safe_function() {
    auto ptr = std::make_unique<int>(42);
    
    if (some_condition) {
        return;  // 自动清理,无泄漏
    }
    
    risky_operation();  // 异常安全,自动清理
    
    // 函数结束时自动清理
}

选择原因

  • 内存安全:unique_ptr防止内存泄漏,原始指针容易泄漏
  • 异常安全:unique_ptr提供强异常安全保证
  • 代码简洁:无需手动管理内存,减少样板代码
  • 性能相等:unique_ptr零开销,性能与原始指针相同

7. 最佳实践指南

为什么重要:最佳实践是多年经验的总结,遵循这些指导原则可以避免常见陷阱,写出高质量、可维护的代码,这对团队协作和项目维护至关重要。

7.1. 创建对象

// 推荐:使用make_unique
auto ptr = std::make_unique<MyClass>(args);

// 不推荐:使用new
std::unique_ptr<MyClass> ptr(new MyClass(args));

避免直接使用 new 创建智能指针,因为它无法提供异常安全,而make_unique提供异常安全。

7.2. 函数参数传递

// 传递所有权:按值传递
void take_ownership(std::unique_ptr<Widget> widget) {
    // 函数拥有widget的所有权
}

// 借用使用:传递原始指针或引用
void use_widget(Widget* widget) {
    // 临时使用,不改变所有权
}

void use_widget_ref(const Widget& widget) {
    // 只读使用
}

// 调用示例
auto widget = std::make_unique<Widget>();
use_widget(widget.get());         // 借用
use_widget_ref(*widget);          // 借用(引用)
take_ownership(std::move(widget)); // 转移所有权
// widget现在是nullptr

使用原因

  • 意图明确:参数类型清楚表达函数是否需要所有权
  • 性能优化:借用时避免不必要的所有权转移
  • 接口设计:清晰的接口设计减少误用
  • 兼容性:原始指针参数与现有代码兼容

7.3. 函数返回值

// 推荐:返回unique_ptr表明所有权转移
std::unique_ptr<Widget> create_widget() {
    return std::make_unique<Widget>();
}

// 工厂函数的典型模式
std::unique_ptr<Shape> shape_factory(ShapeType type) {
    switch (type) {
        case ShapeType::Circle:
            return std::make_unique<Circle>();
        case ShapeType::Rectangle:
            return std::make_unique<Rectangle>();
        default:
            return nullptr;  // 表示创建失败
    }
}

使用原因

  • 所有权转移:明确表示调用者获得对象所有权
  • 异常安全:返回过程中的异常不会导致内存泄漏
  • 错误处理:nullptr表示创建失败,语义清晰
  • 移动语义:高效的对象传递,避免复制

7.4. 需要避免的反模式

// 反模式1:不要从unique_ptr创建shared_ptr
std::unique_ptr<Widget> unique_widget = std::make_unique<Widget>();
// 不推荐
std::shared_ptr<Widget> shared_widget(unique_widget.release());

// 反模式2:不要将同一个原始指针给多个unique_ptr
Widget* raw = new Widget();
std::unique_ptr<Widget> ptr1(raw);  // 危险!
std::unique_ptr<Widget> ptr2(raw);  // 双重删除!

// 反模式3:不要保存get()返回的指针
auto ptr = std::make_unique<Widget>();
Widget* raw = ptr.get();
ptr.reset();  // 现在raw是悬空指针!
// raw->do_something();  // 未定义行为!

避免原因

  • 双重删除:多个智能指针管理同一对象会导致双重删除
  • 悬空指针:保存get()返回的指针容易产生悬空指针
  • 设计混乱:混用不同的智能指针类型破坏设计清晰性
  • 难以调试:这些反模式产生的bug往往难以定位和修复

8. 总结

unique_ptr是现代C++中内存管理的基石,它提供了:

  1. 自动内存管理:无需手动调用 delete;
  2. 异常安全:即使在异常情况下也能正确清理资源;
  3. 移动语义:高效的所有权转移;
  4. 零开销:运行时性能与原始指针相当。

使用unique_ptr的关键原则:

  • 优先使用make_unique创建对象;
  • 通过移动语义转移所有权;
  • 使用原始指针或引用进行临时访问;
  • 在容器中存储unique_ptr实现多态;
  • 避免混合使用智能指针和原始指针。

掌握unique_ptr是编写现代C++代码的必备技能,它能有效防止内存泄漏,提高代码的安全性和可维护性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2398509.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

测量3D翼片的距离与角度

1&#xff0c;目的。 测量3D翼片的距离与角度。说明&#xff1a; 标注A 红色框选的区域即为翼片&#xff0c;本示例的3D 对象共有3个翼片待测。L1与L2的距离、L1与L2的角度即为所求的翼片距离与角度。 2&#xff0c;原理。 使用线结构光模型&#xff08;标定模式&#xff0…

零基础学习计算机网络编程----socket实现UDP协议

本章将会详细的介绍如何使用 socket 实现 UDP 协议的传送数据。有了前面基础知识的铺垫。对于本章的理解将会变得简单。将会从基础的 Serve 的初始化&#xff0c;进阶到 Client 的初始化&#xff0c;以及 run。最后实现一个简陋的小型的网络聊天室。 目录 1.UdpSever.h 1.1 构造…

谷歌地图2022高清卫星地图手机版v10.38.2 安卓版 - 前端工具导航

谷歌地图2022高清卫星地图手机版是由谷歌公司推出的一款非常好用的手机地图服务软件&#xff0c;用户能够通过精准的导航和定位来查看地图&#xff0c;周边的商店等生活服务都会在地图上显示&#xff0c;用起来超级方便。 谷歌卫星高清地图 下载链接&#xff1a;夸克网盘分享 …

RAG的ETL Pipeline源码解读

原文链接&#xff1a;SpringAI(GA)&#xff1a;RAG下的ETL源码解读 教程说明 说明&#xff1a;本教程将采用2025年5月20日正式的GA版&#xff0c;给出如下内容 核心功能模块的快速上手教程核心功能模块的源码级解读Spring ai alibaba增强的快速上手教程 源码级解读 版本&a…

杭州白塔岭画室怎么样?和燕壹画室哪个好?

杭州作为全国美术艺考集训的核心区域&#xff0c;汇聚了众多实力强劲的画室&#xff0c;其中白塔岭画室和燕壹画室备受美术生关注。对于怀揣艺术梦想的考生而言&#xff0c;选择一所契合自身需求的画室&#xff0c;对未来的艺术之路影响深远。接下来&#xff0c;我们将从多个维…

晶台光耦在手机PD快充上的应用

光耦&#xff08;光电隔离器&#xff09;作为关键电子元件&#xff0c;在手机PD快充中扮演信号隔离与传输的“安全卫士”。其通过光信号实现电气隔离&#xff0c;保护手机电路免受高电压损害&#xff0c;同时支持实时信号反馈&#xff0c;优化充电效率。 晶台品牌推出KL817、KL…

【亲测有效 | Cursor Pro每月500次快速请求扩5倍】(Windows版)Cursor中集成interactive-feedback-mcp

前言&#xff1a;使用这个interactive-feedback-mcp组件可以根据用户反馈来决定是否结束这一次的请求。如果本次请求并没有解决我们的问题&#xff0c;那我们便可以选择继续这次请求流程&#xff0c;直到问题解决。这样的话&#xff0c;就可以避免为了修复bug而白白多出的请求。…

CRM管理软件的数据可视化功能使用技巧:让数据驱动决策

在当今数据驱动的商业环境中&#xff0c;CRM管理系统的数据可视化功能已成为企业优化客户管理、提升销售效率的核心工具。据企销客研究显示&#xff0c;具备优秀可视化能力的CRM系统&#xff0c;用户决策效率可提升47%。本文将深入解析如何通过数据可视化功能最大化CRM管理软件…

linux批量创建文件

文章目录 批量创建空文件touch命令批量创建空文件循环结构创建 创建含内容文件echo重定向多行内容写入 按日期创建日志文件根据文件中的列内容&#xff0c;创建文件一行只有一列内容一行有多列内容 批量创建空文件 touch命令批量创建空文件 # 创建文件file1.txt到file10.txt …

颠覆传统!单样本熵最小化如何重塑大语言模型训练范式?

颠覆传统&#xff01;单样本熵最小化如何重塑大语言模型训练范式&#xff1f; 大语言模型&#xff08;LLM&#xff09;的训练往往依赖大量标注数据与复杂奖励设计&#xff0c;但最新研究发现&#xff0c;仅用1条无标注数据和10步优化的熵最小化&#xff08;EM&#xff09;方法…

ssm学习笔记day04

RequestMapping 首先添加依赖 Maven的配置 测试 在controller创建HelloController&#xff0c;如果只加RequestMapping&#xff0c;默认跳转到新页面 如果要是加上ResponseBody就把数据封装在包(JSON)&#xff0c;标签RestController是前后分离的注解&#xff08;因为默认用…

Read View在MVCC里如何工作

Read View的结构 Read View中有四个重要的字段&#xff1a; m_ids&#xff1a;创建 Read View 时&#xff0c;数据库中启动但未提交的「活跃事务」的事务 id 列表 。min_trx_id&#xff1a;创建 Read View 时&#xff0c;「活跃事务」中事务 id 最小的值&#xff0c;即 m_ids …

建筑工程施工进度智能编排系统 (SCS-BIM)

建筑工程施工进度智能编排 (SCS-BIM) 源码可见于&#xff1a;https://github.com/Asionm/SCS-BIM 项目简介 本项目是一个面向建筑工程的施工进度智能编制平台&#xff0c;用户只需上传一份标准 IFC 建筑信息模型文件&#xff0c;系统将自动完成以下任务&#xff1a; 解析模…

pikachu通关教程-XSS

XSS XSS漏洞原理 XSS被称为跨站脚本攻击&#xff08;Cross Site Scripting&#xff09;&#xff0c;由于和层叠样式表&#xff08;Cascading Style Sheets&#xff0c;CSS&#xff09;重名&#xff0c;改为XSS。主要基于JavaScript语言进行恶意攻击&#xff0c;因为js非常灵活…

AIGC学习笔记(9)——AI大模型开发工程师

文章目录 AI大模型开发工程师008 LangChain之Chains模块1 Chain模块核心知识2 Chain模块代码实战LLMSequentialTransformationRouter AI大模型开发工程师 008 LangChain之Chains模块 1 Chain模块核心知识 组合常用的模块 LLM&#xff1a;最常见的链式操作类型SequentialChain…

Keil MDK5.37或更高版本不再预装ARM Compiler Version5导致编译错误的解决方法

Keil MDK5.37预装的是最新的ARM Compiler Version6 我们可以先右击查看工程属性 在Target标签下&#xff0c;我们可以看到Compiler Version5就是丢失的 在Target标签下&#xff0c;我们可以看到Compiler Version5就是丢失的 图1 以固件库方式编程&#xff0c;编译之后全是错…

Unity-UI组件详解

今天我们来学习Unity的UI的详解&#xff0c;这部分的内容相对较少&#xff0c;对于程序员来说主要的工作是负责将各种格式的图片呈现在显示器上并允许操作这些图片。 本篇帖子的理论依据依然是官方开源的UGUI代码&#xff0c;网址为&#xff1a;GitHub - Unity-Technologies/u…

黑马点评完整代码(RabbitMQ优化)+简历编写+面试重点 ⭐

简历上展示黑马点评 完整代码地址 项目描述 黑马点评项目是一个springboot开发的前后端分离项目&#xff0c;使用了redis集群、tomcat集群、MySQL集群提高服务性能。类似于大众点评&#xff0c;实现了短信登录、商户查询缓存、优惠卷秒杀、附近的商户、UV统计、用户签到、好…

Java 大视界 -- Java 大数据在智能安防视频监控中的异常事件快速响应与处理机制(273)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

【数据库】安全性

数据库安全性控制的常用方法&#xff1a;用户标识和鉴定、存取控制、视图、审计、数据加密。 1.用户标识与鉴别 用户标识与鉴别(Identification & Authentication)是系统提供的最外层安全保护措施。 2.存取控制 2.1自主存取控制(简称DAC) (1)同一用户对于不同的数据对…