设计模式13-单件模式
设计模式13-单件模式
- 写在前面
- 对象性能模式
- 典型模式
- 1. 单例模式(Singleton Pattern)
- 2. 享元模式(Flyweight Pattern)
- 3. 原型模式(Prototype Pattern)
- 4. 对象池模式(Object Pool Pattern)
- 5. 延迟初始化(Lazy Initialization)
- 6. 虚拟代理(Virtual Proxy)
- 7. 缓存(Caching)
- 8. 数据传输对象(DTO,Data Transfer Object)
- 动机
- 单例模式的定义与结构
- 单例模式的代码推导
- 2. 线程安全版本,但锁的代价过高
- 3. 双检查锁,但由于内存读写reorder不安全
- 4. C++11版本之后的跨平台实现(volatile)
- volatile关键字
- 平台限制和使用场景
- 多线程编程中的问题
- 推荐的跨平台多线程同步机制
- 总结
- 单例模式的应用
- 单例模式的特点总结
- 补充一个单例模式的实现
- 实现方式
- 1. 第一种单例模式(使用 `std::atomic` 和 `std::mutex`)
- 2. 第二种单例模式(使用 `std::call_once`)
- 比较
- 总结
写在前面
对象性能模式
- 面向对象很好的解决了抽象的问题,但是必不可免的要付出一定的代价(类空间的重复分配等)。对于通常情况来讲,面向对象的成本大多都可以忽略不计。但某些情况面向对象所在的成本必须谨慎处理。
- 设计模式中的对象性能模式是指那些专注于提高系统性能、优化资源使用和管理的设计模式。这些模式通过有效的对象创建、共享、复用和管理策略,减少内存占用、提高运行效率,从而提升整体系统性能。
典型模式
- 单例模式(Singleton Pattern)
- 享元模式(Flyweight Pattern)
当然也有一些其他的模式。
设计模式中涉及对象性能优化的模式主要包括以下几种
1. 单例模式(Singleton Pattern)
单例模式确保一个类只有一个实例,并提供全局访问点。这种模式避免了重复创建对象的开销,特别是在需要频繁访问的场景中,如配置类、日志类等。
2. 享元模式(Flyweight Pattern)
享元模式通过共享对象来减少内存使用。它特别适用于有大量相似对象的场景,例如字符处理、图形绘制等。
3. 原型模式(Prototype Pattern)
原型模式通过复制现有对象来创建新对象,从而减少了创建新对象的开销。这种模式适用于创建代价高昂的对象,如大型数据结构、复杂对象等。
4. 对象池模式(Object Pool Pattern)
对象池模式维护一个对象池,复用池中的对象而不是每次都创建和销毁对象。它适用于对象创建和销毁成本较高的情况,如数据库连接、线程等。
5. 延迟初始化(Lazy Initialization)
延迟初始化是指对象的创建或初始化在其实际被使用时才进行。这可以减少程序启动时的资源占用,并将资源分配推迟到实际需要时。
6. 虚拟代理(Virtual Proxy)
虚拟代理是一种代理模式,通过代理对象来控制对实际对象的访问。虚拟代理可以延迟实际对象的创建或初始化,从而优化性能。例如,大型图片的加载可以通过虚拟代理延迟到实际需要时再进行。
7. 缓存(Caching)
缓存模式通过存储之前计算或创建的结果来减少重复计算或创建的开销。这种模式广泛应用于各种性能优化场景,如数据库查询结果缓存、计算结果缓存等。
8. 数据传输对象(DTO,Data Transfer Object)
DTO模式通过一次性传输批量数据来减少多次远程调用的开销。它适用于分布式系统中,通过减少网络通信次数来提高性能。
动机
在软件系统中经常有这样一些特殊的类必须保证他们在系统中只存在一个实例。才能保证他们的逻辑的正确性以及良好的效率。如何绕过常规的构造器提供一种机制来保证一个类只有一个实例呢?让使用者只需使用一个类?这显然不合理。因为这应该是类设计者的责任,而不是使用者的责任。模式就是为了解决这一类问题的模式。
more动机:
- 在许多应用中,有一些对象需要全局唯一,比如配置文件管理器、日志管理器等。为了确保这些对象在整个应用中只存在一个实例,并且能被全局访问,我们需要一种机制来限制实例化次数并提供统一的访问方式。
- 单例模式的主要动机是控制对象的实例化过程,确保一个类只有一个实例,并提供一个访问它的全局访问点
单例模式的定义与结构
定义
保证一个类仅有一个实力,并提供一个该实例的全局访问点。
结构:
- Singleton类:
- 定义一个静态变量来保存类的唯一实例。
- 提供一个静态方法用于创建或获取该实例。
- 将构造函数设为私有,防止外部通过new操作符实例化该类。
以下是单例模式的UML图:
单例模式的代码推导
class Singleton{
private:Singleton();Singleton(const Singleton& other);
public:static Singleton* getInstance();static Singleton* m_instance;
};Singleton* Singleton::m_instance=nullptr;//线程非安全版本
Singleton* Singleton::getInstance() {if (m_instance == nullptr) {m_instance = new Singleton();}return m_instance;
}//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {Lock lock;if (m_instance == nullptr) {m_instance = new Singleton();}return m_instance;
}//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {if(m_instance==nullptr){Lock lock;if (m_instance == nullptr) {m_instance = new Singleton();}}return m_instance;
}//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;Singleton* Singleton::getInstance() {Singleton* tmp = m_instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acquire);//获取内存fenceif (tmp == nullptr) {std::lock_guard<std::mutex> lock(m_mutex);tmp = m_instance.load(std::memory_order_relaxed);if (tmp == nullptr) {tmp = new Singleton;std::atomic_thread_fence(std::memory_order_release);//释放内存fencem_instance.store(tmp, std::memory_order_relaxed);}}return tmp;
}
这段代码展示了不同版本的单例模式的实现,包括线程不安全版本、线程安全但性能不佳的版本、双检查锁版本以及C++11之后的跨平台实现。以下是对每个版本的详细说明:
##1. 线程不安全版本
class Singleton{
private:Singleton();Singleton(const Singleton& other);
public:static Singleton* getInstance();static Singleton* m_instance;
};Singleton* Singleton::m_instance = nullptr;Singleton* Singleton::getInstance() {if (m_instance == nullptr) {m_instance = new Singleton();}return m_instance;
}
说明:
- 这个版本中,
getInstance
方法检查m_instance
是否为空,如果为空则创建一个新的Singleton
实例。 - 这种实现方式在单线程环境中是安全的,但在多线程环境中可能导致多个线程同时创建多个实例,违背单例模式的初衷。
2. 线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {Lock lock;if (m_instance == nullptr) {m_instance = new Singleton();}return m_instance;
}
说明:
- 这个版本使用了锁(例如
std::mutex
)来确保线程安全。 - 虽然保证了线程安全,但每次调用
getInstance
方法都需要获取锁,代价较高,可能影响性能。
3. 双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {if (m_instance == nullptr) {Lock lock;if (m_instance == nullptr) {m_instance = new Singleton();}}return m_instance;
}
说明:
- 双检查锁的目的是在进入锁之前先检查
m_instance
是否为空,以减少获取锁的次数,提高性能。 - 但是,由于编译器和CPU可能对内存读写操作进行重新排序,这种实现方式可能不安全。因为在所有编译器中会对代码进行优化处理,对于new Singleton操作在某些情况下可能会先返回分配空间的指针,后调用构造函数,那么此时第二个线程可能在进行第一个实例判空判断时直接返回指针,但此时这个指针是不可用的,也就是说线程可能看到未完全构造的对象。
4. C++11版本之后的跨平台实现(volatile)
#include <atomic>
#include <mutex>class Singleton{
private:Singleton() {}Singleton(const Singleton& other) = delete;Singleton& operator=(const Singleton& other) = delete;
public:static Singleton* getInstance();static std::atomic<Singleton*> m_instance;static std::mutex m_mutex;
};std::atomic<Singleton*> Singleton::m_instance{nullptr};
std::mutex Singleton::m_mutex;Singleton* Singleton::getInstance() {Singleton* tmp = m_instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acquire); // 获取内存fenceif (tmp == nullptr) {std::lock_guard<std::mutex> lock(m_mutex);tmp = m_instance.load(std::memory_order_relaxed);if (tmp == nullptr) {tmp = new Singleton;std::atomic_thread_fence(std::memory_order_release); // 释放内存fencem_instance.store(tmp, std::memory_order_relaxed);}}return tmp;
}
说明:
- 使用了
std::atomic
和std::mutex
来实现线程安全的单例模式。 - 通过
std::atomic_thread_fence
确保内存读写操作的顺序性,防止重排序问题。 - 在获取锁之前,先尝试加载
m_instance
,并使用内存屏障(fence)确保正确的内存顺序。 - 获取锁后再次检查
m_instance
,如果仍为空则创建实例并存储到m_instance
中。
volatile关键字
volatile
关键字在 C++ 中的使用是为了告知编译器该变量可能会被异步地修改(例如,由硬件或其他线程),因此每次访问该变量时都需要重新读取,而不是使用缓存值。
然而,volatile
并不是用于实现线程安全的同步机制。在多线程编程中,volatile
不能保证变量访问的原子性或防止数据竞争。因此,对于跨平台的多线程编程,volatile
并不是一个可靠的选择。
平台限制和使用场景
-
硬件访问:
volatile
常用于访问硬件寄存器或与中断服务例程(ISR)共享的数据。在这些场景中,使用volatile
是合适的,因为它能够防止编译器优化掉对这些变量的访问。 -
信号处理:在处理异步信号时,可以使用
volatile
变量标记信号状态。
多线程编程中的问题
在多线程编程中,volatile
并不能保证以下内容:
- 原子性:访问
volatile
变量的操作可能不是原子的。多个线程同时读取和写入volatile
变量时,可能会出现竞态条件。 - 顺序性:
volatile
不能防止编译器、CPU 或内存系统对读写操作进行重新排序。因此,多个线程之间的操作顺序可能会混乱。 - 可见性:虽然
volatile
保证变量的最新值总是被读取,但它不能保证一个线程对变量的修改立即对其他线程可见。
推荐的跨平台多线程同步机制
为了实现跨平台的线程安全,应使用 C++11 及以后的标准库提供的原子操作和同步机制:
std::atomic
:提供原子操作,确保变量访问的原子性和线程之间的可见性。- 内存顺序(Memory Order):C++11 标准引入了内存模型,通过
std::memory_order
枚举类型定义内存顺序,以控制内存操作的排序。 - 锁(Locks):使用
std::mutex
和std::lock_guard
实现互斥锁,以保护共享数据。 - 条件变量(Condition Variables):使用
std::condition_variable
实现线程间的同步和通信。
代码同上
总结
- 线程不安全版本:简单但在多线程环境下不安全。
- 线程安全版本(锁代价高):线程安全但性能较差,因为每次获取实例都需要获取锁。
- 双检查锁版本:优化了性能,但存在内存重排序的问题。
- C++11版本:使用
std::atomic
和内存屏障,解决了内存重排序的问题,是跨平台的线程安全实现。
通过这些不同版本的实现,我们可以看到单例模式在实际应用中的演变和优化,特别是针对线程安全和性能的考虑。
单例模式的应用
应用:
- 配置管理: 需要在应用程序中全局共享配置实例。
- 日志记录: 保证日志记录器实例在整个应用程序中唯一,统一管理日志记录。
- 数据库连接池: 控制数据库连接池实例的唯一性,方便管理连接。
- 线程池: 确保线程池实例的唯一性和复用。
单例模式的特点总结
特点总结:
- 唯一实例: 确保一个类只有一个实例,并提供全局访问点。
- 控制实例化: 通过私有构造函数和静态方法,控制对象的创建和获取。
- 延迟实例化: 在需要时才创建实例(懒加载),避免不必要的资源浪费。
- 线程安全: 需要特别注意多线程环境下的安全问题,可以通过双重检查锁(Double-Checked Locking)或静态内部类等方式实现线程安全。
- 全局访问: 提供一种全局访问点,使得可以方便地访问唯一实例。
单例模式是一种非常常用的设计模式,它在需要全局唯一实例的场景下非常有用。然而,使用单例模式时要注意可能带来的全局状态和并发问题,合理设计和实现才能确保系统的高效和稳定。
补充一个单例模式的实现
有两种单例模式实现的主要区别在于线程安全机制的实现方式和具体细节。以下是对这两种实现的详细比较:
实现方式
1. 第一种单例模式(使用 std::atomic
和 std::mutex
)
class Singleton {
private:Singleton() {}Singleton(const Singleton& other) = delete;Singleton& operator=(const Singleton& other) = delete;public:static Singleton* getInstance();static std::atomic<Singleton*> m_instance;static std::mutex m_mutex;
};std::atomic<Singleton*> Singleton::m_instance{nullptr};
std::mutex Singleton::m_mutex;Singleton* Singleton::getInstance() {Singleton* tmp = m_instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acquire);if (tmp == nullptr) {std::lock_guard<std::mutex> lock(m_mutex);tmp = m_instance.load(std::memory_order_relaxed);if (tmp == nullptr) {tmp = new Singleton;std::atomic_thread_fence(std::memory_order_release);m_instance.store(tmp, std::memory_order_relaxed);}}return tmp;
}
特点:
- 使用
std::atomic
来存储单例实例,确保对实例的访问是原子操作。 - 使用内存屏障(memory fences)来确保内存操作的顺序。
- 双重检查锁定(Double-checked locking)机制,通过
std::mutex
确保在实例创建过程中只有一个线程进入临界区。
2. 第二种单例模式(使用 std::call_once
)
class CCalibrationJsonCoinfig {
private:static CCalibrationJsonCoinfig* instance;CCalibrationJsonCoinfig() {}~CCalibrationJsonCoinfig() {}public:static CCalibrationJsonCoinfig* getInstance() {std::call_once(flag, &CCalibrationJsonCoinfig::initInstance);return instance;}private:static void initInstance() {instance = new CCalibrationJsonCoinfig();}static std::once_flag flag;
};CCalibrationJsonCoinfig* CCalibrationJsonCoinfig::instance = nullptr;
std::once_flag CCalibrationJsonCoinfig::flag;
特点:
- 使用
std::call_once
和std::once_flag
确保实例初始化函数只被调用一次,避免多线程环境下的重复初始化。 - 初始化逻辑更加简洁,利用
std::call_once
实现线程安全的单例模式。
比较
-
线程安全机制:
- 第一种方法:使用
std::atomic
和std::mutex
实现双重检查锁定(Double-checked locking),需要手动管理锁和内存屏障,代码复杂度较高。 - 第二种方法:使用
std::call_once
和std::once_flag
实现,标准库提供的简洁接口,避免了手动管理锁和内存屏障,代码简洁。
- 第一种方法:使用
-
性能:
- 第一种方法:在高并发环境下,由于使用了双重检查锁定,只有在实例尚未初始化时会加锁,之后获取实例的性能较高。
- 第二种方法:
std::call_once
只在初始化时会有性能开销,之后获取实例的性能也较高。
-
代码简洁性:
- 第一种方法:代码较为复杂,需要理解和正确使用内存屏障和双重检查锁定。
- 第二种方法:代码相对简洁,利用
std::call_once
提供的标准库特性,减少了代码复杂度。
总结
- 推荐使用
std::call_once
和std::once_flag
的方式(第二种方法):这种方式更为简洁和安全,避免了手动管理锁和内存屏障的复杂性。标准库提供的接口已经优化了线程安全初始化的性能。 - 在特殊情况下使用第一种方法:如果你对性能有极高的要求,或者在特定的硬件环境下需要手动优化,可以考虑使用第一种方法,但需要非常小心地处理内存屏障和线程同步问题。
相关文章:

设计模式13-单件模式
设计模式13-单件模式 写在前面对象性能模式典型模式1. 单例模式(Singleton Pattern)2. 享元模式(Flyweight Pattern)3. 原型模式(Prototype Pattern)4. 对象池模式(Object Pool Pattern…...

怎么给PDF文件加密码?关于PDF文件加密的四种方法推荐
怎么给PDF文件加密码?给PDF文件加上密码是保护文件安全的一种重要方法,特别是当需要在不受授权的访问下保护敏感信息时。这个过程不仅仅是简单地设置密码,而是涉及到对文档内容和访问控制的深思熟虑。加密PDF文件可以有效防止未经授权的用户查…...

GoFly快速开发框架基于Go语言和Vue3开发后台管理附件管理插件包
说明 为了给客户提供更好的交互体验,框架把附件管理独立打包成插件包,这样附件管理接可以做个不通需求的附件管理插件包来满足不同甲方客户需求。 目前附件插件包有2个:一个基础包、一个高级包 附件插件包功能 1.基础包 统一管理业务系统…...

matlab实验:实验六MATLAB 数值计算与符号运算
题目1:(线性方程组数值求解) 1. 用不同的方法求解下面方程:(方程原式参考 P369 实验 10,第 1 题) 第 1 种,左除和求逆函数(inv) 第 2 种 , 用 符 号 运 算 的…...

基于STM32设计的老人摔倒检测系统(4G+华为云IOT)(193)
文章目录 一、前言1.1 项目介绍【1】项目功能介绍【2】项目硬件模块组成1.2 设计思路【1】整体设计思路【2】整体构架【3】上位机开发思路【4】供电方式1.3 项目开发背景【1】选题的意义【2】可行性分析【3】参考文献【4】课题研究的意义【5】国内外技术发展现状【6】课题研究思…...

PyTorch和TensorFlow概念及对比
PyTorch和TensorFlow是两个流行的深度学习框架,用于构建和训练机器学习和深度学习模型。它们各自有一些独特的特点和优点: 一 、PyTorch 动态计算图: PyTorch使用动态计算图(Dynamic Computation Graph),…...

github的Codespaces是什么
目录 github的Codespaces是什么 一、定义与功能 二、特点与优势 三、工作原理 四、使用场景与限制 github的Codespaces是什么 GitHub的Codespaces是一个基于云的即时开发环境,它利用容器技术为开发者提供一个完全配置好的开发环境,以便他们能够直接在浏览器或通过Visua…...

Unity UGUI 之 图集
本文仅作学习笔记与交流,不作任何商业用途 本文包括但不限于unity官方手册,唐老狮,麦扣教程知识,引用会标记,如有不足还请斧正 本文在发布时间选用unity 2022.3.8稳定版本,请注意分别 1.什么是图集 精灵图…...

rust日常提问
rust 如何为类 添加一个函数 举例说明 在 Rust 中,我们通常使用 struct(结构体)来创建类似其他语言中的类(class)。Rust 中的结构体可以拥有关联函数(associated functions),这些函数…...

Vue3与Element-plus配合 直接修改表格中的一项数据——控制输入框的显示与隐藏
利用控制与隐藏输入框,直接修改表格中的每一项数据。 <!-- 表格模块 --> <div><el-table :data"tablelist" style"width: 100%"><el-table-column align"center" prop"deposit" label"接单押金">&l…...

设计模式--创建型
实现 #include <iostream> #include <memory>// 抽象产品类 class Product {public:virtual ~Product() {}virtual void Operation() const 0; };// 具体产品 类A class ConcreteProductA : public Product {public:virtual void Operation() const override {st…...

Vue3时间选择器datetimerange在数据库存开始时间和结束时间
♥️作者:小宋1021 🤵♂️个人主页:小宋1021主页 ♥️坚持分析平时学习到的项目以及学习到的软件开发知识,和大家一起努力呀!!! 🎈🎈加油! 加油!…...

鼠标移入事件 mouseover
<template><div><div mouseover"handleMouseOver">区域1</div></div> </template><script> export default {methods: {handleMouseOver() {console.log(鼠标悬停在区域1);}} } </script>...

UE4 自动换行——按排序关键字1.2.3.
要自动换行的字符串举例:“有效节点为:1.demo-worker-02 2.demo-worker-01 3.demo-master-01” 1.获取相邻两位字符串,组合后与关键字比较 2.当两位字符串与关键字相等,附加一次换行 3.其他例如 1)2)3)、(1)(2)(3)、<1><2><…...

Object.entries()解析出来的数组顺序乱了,健是string类型
现象: 从后端哪里拿到了一长串数据 const obj {"2023-07-01":10,"2023-09-18":2,"2023-10-10":3,"2024-01-10":1,"2024-01-12":1,"2024-02-20":4,"2024-07-01":4,... }; 比如上面的数据有一年的 并…...

SSM(Spring + Spring MVC + MyBatis)框架面试三道题
以下是三道关于SSM(Spring Spring MVC MyBatis)框架的面试题,由简单到困难进行排列: 1. 简答题:请简述Spring框架的核心特性。 答案: Spring框架的核心特性主要包括以下几个方面: 控制反转…...

ctfshow-web入门-php特性(web132-web136)
目录 1、web132 2、web133 3、web134 4、web135 5、web136 1、web132 存在 robots.txt 访问 /admin 需要传三个参数,并且需要满足: if($code mt_rand(1,0x36D) && $password $flag || $username "admin"){if($code admin){ech…...

通信原理-实验六:实验测验
实验六 实验测验 一:测验内容和要求 测试需要完成以下几个步骤: 配置好以下网络图;占总分10%(缺少一个扣一分)根据下面图配置好对应的IP和网关以及路由等相关配置,保证设备之间连通正常;占总…...

意得辑润色新用户注册直减15%
ABSJU202 优惠了很多...

重拾CSS,前端样式精读-函数(颜色,计算,图像和图形)
前言 本文收录于CSS系列文章中,欢迎阅读指正 在计算机编程中,函数有着重要的作用和意义,它可以实现封装,复用,模块化,参数等功能效果,在如何在CSS中写变量?一文带你了解前端样式利…...

经纬恒润与奇瑞汽车签订新能源项目重点供应商合作协议,共同开启合作新篇章
近日,2024年国家级芜湖经开区汽车零部件生态大会成功举行,经纬恒润受邀出席,与行业各伙伴齐聚经开区,同绘发展蓝图,助力经开区汽车产业高质量发展。会上,经纬恒润与奇瑞汽车签署合作协议,成为奇…...

@RestController和@Controller
RestController和Controller 在 Spring MVC 中,RestController 和 Controller 是用于定义控制器的注解,但它们有一些重要的区别。下面是对它们的详细解释和示例: Controller Controller 注解用于标记一个类是一个 Spring MVC 控制器&#…...

STM32-寄存器DMA配置指南
配置步骤 在STM32F0xx中文参考手册中的DMA部分在开头给出了配置步骤 每个通道都可以在外设寄存器固定地址和存储器地址之间执行 DMA 传输。DMA 传输的数据 量是可编程的,最大达到 65535。每次传输之后相应的计数寄存器都做一次递减操作,直到 计数为&am…...

【Django】anaconda环境变量配置及配置python虚拟环境
文章目录 配置环境变量配置python虚拟环境查看conda源并配置国内源在虚拟环境中安装django 配置环境变量 control sysdm.cpl,,3笔者anaconda安装目录为C:\ProgramData\anaconda3 那么需要加入path中的有如下三个 C:\ProgramData\anaconda3 C:\ProgramData\anaconda3\Scripts C:…...

保障企业数据主权:安全可控的爬虫工具与管理平台
摘要 在数据驱动的时代,企业对数据的需求日益增长,但如何在保障数据主权的前提下高效采集数据?本文深入探讨了选择安全可控爬虫工具与管理平台的重要性,分析了关键特性,并提出实用建议,助力企业维护数据安…...

NC重建二叉树
系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 描述 给定节点数为…...

2025第十九届中国欧亚国际军民两用技术及西安国防电子航空航天暨无人机展
2025第十九届中国欧亚国际军民两用技术及西安国防电子航空航天暨无人机展 时间:2025年3月14-16日 地点:西安国际会展中心 详询主办方陆先生 I38(前三位) I82I(中间四位) 9I72(后面四位&am…...

电子邮件协议详解
电子邮件作为互联网通信的重要组成部分,已经成为日常交流不可或缺的一部分。为了确保电子邮件的有效传输和管理,计算机网络使用了多种协议。本文将深入探讨电子邮件协议中的三大核心协议:SMTP、POP3 和 IMAP。我们将详细介绍这些协议的工作原…...

C++客户端Qt开发——Qt窗口(工具栏)
2.工具栏 使用QToolBar表示工具栏对象,一个窗口可以有多个工具栏,也可以没有,工具栏往往也可以手动移动位置 ①设置工具栏 #include "mainwindow.h" #include "ui_mainwindow.h" #include<QToolBar> #include<…...

Python酷库之旅-第三方库Pandas(046)
目录 一、用法精讲 161、pandas.Series.cumsum方法 161-1、语法 161-2、参数 161-3、功能 161-4、返回值 161-5、说明 161-6、用法 161-6-1、数据准备 161-6-2、代码示例 161-6-3、结果输出 162、pandas.Series.describe方法 162-1、语法 162-2、参数 162-3、功…...