【单例模式】—— C++设计模式【附百度Apollo单例模式详细解读】

参考资料:
(1)单例模式—— 代码随想录
(2)我给面试官讲解了单例模式后,他对我竖起了大拇指!
(3)C++ 单例模式详解
(4)单例模式之C++实现,智能指针,线程安全版本
(5)深入探索单例设计模式:以百度 Apollo 为例

1 单例模式

单例模式:创建型设计模式
核心思想:保证一个类只有一个实例,并提供一个全局访问点来访问这个实例

  • 一个实例:在整个应用程序中,只存在该类的一个实例对象,而不是创建多个相同类型的对象
  • 全局访问点:为了让其他类能够获取到这个唯一实例,该类提供了一个全局访问点(通常是一个静态方法),通过这个方法就能获得实例

单例模式的类型:

  • 懒汉式:在真正需要使用对象时才去创建该单例类对象
  • 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用

2 使用单例模式的需求

  1. 全局控制:保证只有一个实例,严格的控制客户怎样访问它以及何时访问它,简单的说就是对唯一实例的受控访问
  2. 节省资源:避免多次创建了相同的对象,从而节省了系统资源,而且多个模块还可以通过单例实例共享数据
  3. 懒加载:只有在需要时才进行实例化

3 实现单例模式的步骤

  • 私有的构造函数:防止外部代码直接创建类的实例
  • 提供一个公有的静态方法:通过公有的静态方法来获取类的实例
  • 在类中定义一个私有静态指针,指向本类的变量的静态变量指针:保存该类的唯一实例

4 懒汉式创建单例对象

懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象。,否则则先执行实例化操作。
在这里插入图片描述
适用于单线程场景的懒汉式单例

class Singleton {
public:
	static Singleton* getInstance() {
		if (instance_ = NULL) {
			instance_ = new Singleton();	
		}
		return instance_;
	}
private:
	Singleton() = default;
	static Singleton* instance_;
};
// 初始化静态成员变量
Singleton* Singleton::instance_ = NULL;

懒汉式单例模式存在内存泄漏的问题:

  • 使用智能指针
class Singleton {
public:
	static shared_ptr<Singleton> getInstance() {
		if (instance_ == NULL) {
			lock_guard<mutex> lock(mutex_);
			if (instance_ == NULL) {
				instance_ = shared_ptr<Singleton>(new Singleton());	
			}
		}	
		return instance_;
	}
private:
	Singleton() = default;
	static shared_ptr<Singleton> instance_;
	static mutex mutex_;
};
shared_ptr<Singleton> Singleton::instance_ = NULL;
mutex Singleton::mutex_;
  • 使用静态的嵌套类对象
class Singleton {
public:
	static Singleton* getInstance() {
		if (instance_ = NULL) {
			instance_ = new Singleton();	
		}
		return instance_;
	}
private:
	Singleton() = default;
	static Singleton* instance_;
	class Deletor {
	public:
		~Deletor() {
			if (Singleton::instance_ != NULL)
				delete Singleton::instance_;
		}
	};
	static Deletor deletor;
};
// 初始化静态成员变量
Singleton* Singleton::instance_ = NULL;

Meyers 单例:静态局部变量的懒汉单例(C++11线程安全)

C++11规定了local static在多线程条件下的初始化行为,要求编译器保证了内部静态变量的线程安全性。《Effective C++》使用函数内的 local static 对象,这样,只有当第一次访问getInstance()方法时才创建实例。
如果多个线程同时尝试初始化相同的静态局部变量,初始化动作只会发生一次,这个内部特性通常也是通过双检锁模式实现的。

class Singleton {
	public:
		static Singleton& getInstance() {
			static Singleton instance_;
			return instance_;	
		}
	private:
		Singleton() {};
		~Singleton() {};
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);	
};

多线程安全的懒汉式单例:双检锁 + 原子变量实现
(1) 使用双检锁确保性能:针对单检锁方法中存在的性能问题,有一种所谓的双检锁模式(Double-Checked Locking Pattern,DCLP)优化方案,即在 GetInstance 中执行锁操作前,在最外层额外地进行一次实例指针的检查操作(“双检”的体现),这样可以保证实例指针完成内存分配后,单纯的实例访问操作不会再附带锁操作带来的性能开销

class LazySingleton
{
private:
    static LazySingleton *pinstance_;
    static std::mutex mutex_;

private:
    LazySingleton() {}
    LazySingleton(const LazySingleton &) = delete;
    LazySingleton &operator=(const LazySingleton &) = delete;

public:
    ~LazySingleton() {}

public:
    static LazySingleton *GetInstance();
};

LazySingleton *LazySingleton::pinstance_{nullptr};
std::mutex LazySingleton::mutex_;

LazySingleton *LazySingleton::GetInstance()
{
    if (nullptr == pinstance_)
    {
        std::lock_guard<std::mutex> lock(mutex_);
        if (nullptr == pinstance_)
        {
            pinstance_ = new LazySingleton;
        }
    }
    return pinstance_;
}

双检锁方法初衷虽好,但却破坏了多线程场景下的安全性,这是由动态内存分配时 new 底层操作的非原子性导致的,执行 pinstance_ = new LazySingleton; 语句时,底层其实对应了三个步骤:

  • 向系统申请分配内存,大小为 sizeof(LazySingleton)
  • 调用 LazySingleton 的默认构造函数在申请的内存上构造出实例
  • 返回申请内存的指针给 pinstance_

根本问题在于上面的这三个步骤无法确保执行顺序。例如,出于优化的原因,处理器很可能调整步骤 3 和步骤 2 的执行顺序(按照 1、3、2 的顺序执行)。

假设,现在某个线程执行到了 pinstance_ = new LazySingleton; 语句,底层操作完成了内存申请(步骤 1)和实例指针赋值(步骤 3),但尚未完成申请内存的构造(步骤 2),意即,现在 pinstance_ 指向的是一片脏内存。此时,另一个线程恰好执行到双检锁的最外层检查,该线程发现 pinstance_ 非空(发生了脏读),检查为 false,因而直接取走了尚未完成构造的实例指针(return pinstance_;),从而可能诱发程序未定义行为(undefined behavior)。

(2) 使用原子变量确保多线程安全性
可以通过封装一个单例指针类型的 std::atomic 原子对象,将单例指针的读写操作转化为对原子对象的操作,以此来确保双检锁实现的懒汉式单例的多线程安全性。
std::atomic 是 C++11 定义于 <atomic> 中的新特性,每个 std::atomic 模板的实例化和全特化定义一个原子类型,若一个线程写入原子对象,同时另一线程从它读取,则行为良好定义。另外,对原子对象的访问可以建立线程间同步,并按 std::memory_order 枚举类型中的枚举常量对非原子内存访问定序:

typedef enum memory_order {
    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst
} memory_order;

下面给出经典的基于双检锁 + 原子变量的懒汉式单例实现:

class LazySingleton
{
private:
    static std::atomic<LazySingleton *> ainstance_;
    static std::mutex mutex_;

private:
    LazySingleton() {}
    LazySingleton(const LazySingleton &) = delete;
    LazySingleton &operator=(const LazySingleton &) = delete;

public:
    ~LazySingleton() {}

public:
    static LazySingleton *GetInstance();
};

std::atomic<LazySingleton *> LazySingleton::ainstance_;
std::mutex LazySingleton::mutex_;

LazySingleton *LazySingleton::GetInstance()
{
    LazySingleton *tmp = ainstance_.load(std::memory_order_acquire);
    if (nullptr == tmp)
    {
        std::lock_guard<std::mutex> lock(mutex_);
        tmp = ainstance_.load(std::memory_order_relaxed);
        if (nullptr == tmp)
        {
            tmp = new LazySingleton;
            ainstance_.store(tmp, std::memory_order_release);
        }
    }
    return tmp;
}
  • load:原子性地加载并返回原子变量的当前值,类似读操作。唯一形参类型为std::memory_order,默认值为 memory_order_seq_cst
  • store:根据第一实参原子性地替换原子变量的当前值,类似写操作。第二形参类型为 std::memory_order,默认值为 memory_order_seq_cst

上面这种原子变量的使用方式称为 Acquire-Release Semantic 内存模型,如果保持 load 和 store 的 std::memory_order 参数缺省,则成为 Sequential Consistency 内存模型,性能会稍有损失。

5 饿汉式创建单例对象

在这里插入图片描述
在main函数之前初始化,所以没有线程安全的问题。但是潜在问题在于no-local static对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。就是说说,static Singleton instance_;static Singleton& getInstance()二者的初始化顺序不确定,如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例。

#include <iostream>

using namespace std;

class Singleton {
public:
    static Singleton* getInstance() {
        return instance_;
    }
private:
    Singleton() {cout << "a" << endl;};
    ~Singleton() {};
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
private:
    static Singleton* instance_;
};

// initialize defaultly
Singleton* Singleton::instance_ = new Singleton();
int main()
{
    cout << "we get the instance" << endl;
    Singleton* a1 = Singleton::getInstance();
    cout << "we destroy the instance" << endl;
    system("pause");
    return 0;
}

运行结果:

a
we get the instance
we destroy the instance

6 百度 Apollo 中的懒汉式单例:once_flag & call_once 实现

一个普通的 SensorManager 类经宏定义 DECLARE_SINGLETON(SensorManager) 修饰成为单例类:

class SensorManager {

  // ...
  //
  // other code
  //
  // ...

  DECLARE_SINGLETON(SensorManager)
};

DECLARE_SINGLETON(classname) 定义在 apollo/cyber/common/macros.h中:

#ifndef CYBER_COMMON_MACROS_H_
#define CYBER_COMMON_MACROS_H_

#include <iostream>
#include <memory>
#include <mutex>
#include <type_traits>
#include <utility>

#include "cyber/base/macros.h"

DEFINE_TYPE_TRAIT(HasShutdown, Shutdown)

template <typename T>
typename std::enable_if<HasShutdown<T>::value>::type CallShutdown(T *instance) {
  instance->Shutdown();
}

template <typename T>
typename std::enable_if<!HasShutdown<T>::value>::type CallShutdown(
    T *instance) {
  (void)instance;
}

// There must be many copy-paste versions of these macros which are same
// things, undefine them to avoid conflict.
#undef UNUSED
#undef DISALLOW_COPY_AND_ASSIGN

#define UNUSED(param) (void)param

#define DISALLOW_COPY_AND_ASSIGN(classname) 
  classname(const classname &) = delete;    
  classname &operator=(const classname &) = delete;

#define DECLARE_SINGLETON(classname)                                      
 public:                                                                  
  static classname *Instance(bool create_if_needed = true) {              
    // 提供对唯一实例的全局访问点
    static classname *instance = nullptr;                                 
    if (!instance && create_if_needed) {                                  
      static std::once_flag flag;                                         
      std::call_once(flag,                                                
                     [&] { instance = new (std::nothrow) classname(); }); 
    }                                                                     
    return instance;                                                      
  }                                                                       
                                                                          
  static void CleanUp() {                                                 
    auto instance = Instance(false);                                      
    if (instance != nullptr) {                                            
      CallShutdown(instance);                                             
    }                                                                     
  }                                                                       
                                                                          
 private:                                                                 
  classname();                                                            
  DISALLOW_COPY_AND_ASSIGN(classname)

#endif  // CYBER_COMMON_MACROS_H_

DECLARE_SINGLETON(classname) 在预处理阶段会被替换为:
(1)静态方法 Instance
(2)私有的泛化默认构造函数和嵌套的宏定义 DISALLOW_COPY_AND_ASSIGN
(3) 静态方法 CleanUp

7.1 泛化的单例

DECLARE_SINGLETON(classname)如何将任意一个类修饰为单例类的:
(1)提供对唯一实例的全局访问点

static classname *instance = nullptr;

实例访问点的全局性通过静态方法 Instance 实现。

(2)多线程安全
实现方式的多线程安全性由 std::once_flagstd::call_once 保证,两者都是 C++11 定义于<mutex>中的新特性,配合使用可以确保多线程场景下可调用对象的唯一执行。
std::once_flagstd::call_once 的辅助结构体,在 GNU 中的实现如下:

struct once_flag
{
private:
  typedef __gthread_once_t __native_type;
  __native_type  _M_once = __GTHREAD_ONCE_INIT;

public:
  /// Constructor
  constexpr once_flag() noexcept = default;

  /// Deleted copy constructor
  once_flag(const once_flag&) = delete;
  /// Deleted assignment operator
  once_flag& operator=(const once_flag&) = delete;

  template<typename _Callable, typename... _Args>
    friend void
    call_once(once_flag& __once, _Callable&& __f, _Args&&... __args);
};

call_once 被声明为 once_flag 的友元函数,为的是 call_once 可以修改 once_flag 中的 _M_once 成员(可调用对象的调用状态)。

std::call_once 是一个可变参数模板函数
可变参数经完美转发传入可调用对象,具体到 Apollo 中,可调用对象指的是为实例指针分配动态内存的 lambda 表达式:

[&] { instance = new (std::nothrow) classname(); }

std::call_once通过间接调用 pthread_once 函数来确保传入的可调用对象即使在多线程场景下也只能被执行一次

(3) 防止私自创建实例

#define DISALLOW_COPY_AND_ASSIGN(classname) 
  classname(const classname &) = delete;    
  classname &operator=(const classname &) = delete;

分析CleanUp静态方法,该方法允许用户调用时执行一些自定义的清理工作:

static void CleanUp() {
  auto instance = Instance(false);
  if (instance != nullptr) {
    CallShutdown(instance);
  }
}

CallShutdown 模板函数包含两个经类型萃取(type traits)进行重载的实现

template <typename T>
typename std::enable_if<HasShutdown<T>::value>::type CallShutdown(T *instance) {
  instance->Shutdown();
}

template <typename T>
typename std::enable_if<!HasShutdown<T>::value>::type CallShutdown(
    T *instance) {
  (void)instance;
}

DEFINE_TYPE_TRAIT(HasShutdown, Shutdown)

#define DEFINE_TYPE_TRAIT(name, func)                     \
  template <typename T>                                   \
  struct name {                                           \
    template <typename Class>                             \
    static constexpr bool Test(decltype(&Class::func)*) { \
      return true;                                        \
    }                                                     \
    template <typename>                                   \
    static constexpr bool Test(...) {                     \
      return false;                                       \
    }                                                     \
                                                          \
    static constexpr bool value = Test<T>(nullptr);       \
  };                                                      \
                                                          \
  template <typename T>                                   \
  constexpr bool name<T>::value;

DEFINE_TYPE_TRAIT(HasShutdown, Shutdown) 的具体含义是:创建类型萃取模板类 HasShutdown,HasShutdown 可检查模板类型参数 T 中是否包含 Shutdown 方法。若是,则执行下面语句版本的 CallShutdown 会被 CleanUp 调用:

instance->Shutdown();

否则,执行下面语句版本的 CallShutdown 会被 CleanUp 调用:

(void)instance;
7.2 封装与验证

singleton.h

#ifndef SINGLETON_SINGLETON_H
#define SINGLETON_SINGLETON_H

#include <iostream>
#include <memory>
#include <mutex>
#include <type_traits>
#include <utility>

#define DEFINE_TYPE_TRAIT(name, func)                     \
  template <typename T>                                   \
  struct name {                                           \
    template <typename Class>                             \
    static constexpr bool Test(decltype(&Class::func)*) { \
      return true;                                        \
    }                                                     \
    template <typename>                                   \
    static constexpr bool Test(...) {                     \
      return false;                                       \
    }                                                     \
                                                          \
    static constexpr bool value = Test<T>(nullptr);       \
  };                                                      \
                                                          \
  template <typename T>                                   \
  constexpr bool name<T>::value;

DEFINE_TYPE_TRAIT(HasShutdown, Shutdown)

template <typename T>
typename std::enable_if<HasShutdown<T>::value>::type CallShutdown(T *instance) {
    instance->Shutdown();
}

template <typename T>
typename std::enable_if<!HasShutdown<T>::value>::type CallShutdown(
        T *instance) {
    (void)instance;
}

// There must be many copy-paste versions of these macros which are same
// things, undefine them to avoid conflict.
#undef UNUSED
#undef DISALLOW_COPY_AND_ASSIGN

#define UNUSED(param) (void)param

#define DISALLOW_COPY_AND_ASSIGN(classname) \
  classname(const classname &) = delete;    \
  classname &operator=(const classname &) = delete;

#define DECLARE_SINGLETON(classname)                                      \
 public:                                                                  \
  static classname *Instance(bool create_if_needed = true) {              \
    static classname *instance = nullptr;                                 \
    if (!instance && create_if_needed) {                                  \
      static std::once_flag flag;                                         \
      std::call_once(flag,                                                \
                     [&] { instance = new (std::nothrow) classname(); }); \
    }                                                                     \
    return instance;                                                      \
  }                                                                       \
                                                                          \
  static void CleanUp() {                                                 \
    auto instance = Instance(false);                                      \
    if (instance != nullptr) {                                            \
      CallShutdown(instance);                                             \
    }                                                                     \
  }                                                                       \
                                                                          \
 private:                                                                 \
  classname();                                                            \
  DISALLOW_COPY_AND_ASSIGN(classname)


#endif //SINGLETON_SINGLETON_H

singleton_a.h

#ifndef SINGLETON_SINGLETON_A_H
#define SINGLETON_SINGLETON_A_H
#include <iostream>
#include "singleton.h"
class SingletonA
{
private:
    ~SingletonA() = default;

private:
    static int num;

public:
    static void GetNum()
    {
        std::cout << "\n number of instances of SingletonA: " << num << std::endl;
    }
    DECLARE_SINGLETON(SingletonA)
};

int SingletonA::num = 0;

SingletonA::SingletonA()
{
    ++num;
}
#endif //SINGLETON_SINGLETON_A_H

singleton_b.h

#ifndef SINGLETON_SINGLETON_B_H
#define SINGLETON_SINGLETON_B_H
#include <iostream>
#include "singleton.h"
class SingletonB
{
private:
    ~SingletonB() = default;

private:
    static int num;

public:
    // `Shutdown` method should be declared as `public` for type traits
    void Shutdown();

    static void GetNum()
    {
        std::cout << "\n number of instances of SingletonB: " << num << std::endl;
    }
	DECLARE_SINGLETON(SingletonB)
};

int SingletonB::num = 0;

SingletonB::SingletonB()
{
    ++num;
}

void SingletonB::Shutdown()
{
    auto instance = Instance(false);
    if (instance != nullptr)
    {
        delete instance;
        num = 0;
    }

    std::cout << "\n SingletonB::Shutdown method was called." << std::endl;
}

#endif //SINGLETON_SINGLETON_B_H

main.cpp

#include <thread>
#include "singleton_b.h"
#include "singleton_a.h"

template <typename T>
void ThreadFunc()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    T *p = T::Instance();
}

int main()
{
    std::thread tA1(ThreadFunc<SingletonA>);
    std::thread tA2(ThreadFunc<SingletonA>);
    std::thread tB1(ThreadFunc<SingletonB>);
    std::thread tB2(ThreadFunc<SingletonB>);

    tA1.join();
    tA2.join();
    tB1.join();
    tB2.join();

    SingletonA::GetNum();
    SingletonB::GetNum();

    SingletonA::CleanUp();
    SingletonB::CleanUp();

    SingletonA::GetNum();
    SingletonB::GetNum();

    return 0;
}

CMakeList.txt

cmake_minimum_required(VERSION 3.25)
project(singleton)

set(CMAKE_CXX_STANDARD 17)

add_executable(singleton main.cpp singleton.h singleton_a.h singleton_b.h)

运行结果:

 number of instances of SingletonA: 1

 number of instances of SingletonB: 1

 SingletonB::Shutdown method was called.

 number of instances of SingletonA: 1

 number of instances of SingletonB: 0
  • 在调用 CleanUp 方法前,虽然 SingletonA 和 SingletonB 各自被两个线程调用 Instance 方法,但默认构造均只发生了一次(实例数量均为 1),说明满足多线程安全性;
  • 分别调用 SingletonA 和 SingletonB 的 CleanUp 方法后,SingletonB 的实例数量清零,因为其 Shutdown 方法被间接调用;SingletonA 实例数量仍为 1,因为其 CleanUp 方法什么也没做。

7 单例模式总结

  • 懒汉式:在需要用到对象时才实例化对象,正确的实现方式是:Double Check + Lock,解决了并发安全和性能低下问题
  • 饿汉式:在类加载时已经创建好该单例对象,在获取单例对象时直接返回对象即可,不会存在并发安全和性能问题。

如果在开发中如果对内存要求非常高,那么使用懒汉式写法,可以在特定时候才创建该对象;
如果对内存要求不高使用饿汉式写法,因为简单不易出错,且没有任何并发安全和性能问题

在这里插入图片描述
Meyers 单例不仅形式优雅,效率在多线程场景下也是最优的

class Singleton {
	public:
		static Singleton& getInstance() {
			static Singleton instance_;
			return instance_;	
		}
	private:
		Singleton() {};
		~Singleton() {};
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);	
};

至此,单例模式懒汉式和饿汉式讲解到此结束,不正之处望读者指正。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ssm 房屋销售管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 ssm 房屋销售管理系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模…

今天起,Windows可以一键召唤GPT-4了

现在&#xff0c;OpenAI 大模型加持的 Copilot 功能终于登陆 Windows 了。 把 Copilot 按钮放在 Windows 桌面的任务栏&#xff0c;甚至实体键盘上&#xff0c;用大模型提升每个人的生产效率。 美东时间 3 月 21 日周四&#xff0c;生成式 AI 领军的微软又为我们带来了一点小…

浅谈交直流混合微电网能量管理系统关键技术研究综述

摘要&#xff1a;为了提升交直流混合微电网健康有效发展&#xff0c;提高直流互联微电网中分布式电源的能源使用效率&#xff0c;提升区域微电网稳定发展&#xff0c;对交直流混合微电网能量管理系统关键技术进行分析和研究很有必要。文章主要从交直流混合微电网能量管理系统架…

Codigger开发者篇:开启全新的开发体验(二)

在数字化浪潮中&#xff0c;开发者们始终在追求更加高效、便捷的开发工具与环境。Codigger&#xff0c;作为新一代开发、运营、使用私人应用的分布式操作系统&#xff0c;正是为这些追求者们量身打造的利器&#xff0c;Codigger是一个跨时代的颠覆式的创新。今天&#xff0c;我…

贪心算法相关题目

文章目录 1. 什么是贪心&#xff1f;2. 分发饼干3. 摆动序列4. 最大子数组和5. 买卖股票的最佳时机 II6. 跳跃游戏7. 跳跃游戏 II8.K 次取反后最大化的数组和9.加油站10.分发糖果11.柠檬水找零12.根据身高重建队列13.用最少数量的箭引爆气球14. 无重叠区间15.划分字母区间16.合…

腾讯云2核2G服务器CVM S5和轻量应用服务器优惠价格

腾讯云2核2G服务器多少钱一年&#xff1f;轻量服务器61元一年&#xff0c;CVM 2核2G S5服务器313.2元15个月&#xff0c;腾讯云2核2G服务器优惠活动 txyfwq.com/go/txy 链接打开如下图&#xff1a; 腾讯云2核2G服务器价格 轻量61元一年&#xff1a;轻量2核2G3M、3M带宽、200GB月…

JWT认证原理

简介&#xff1a; JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally …

windows-MySQL5.7安装

1.安装包下载 https://downloads.mysql.com/archives/community/&#xff08;社区版下载链接&#xff09; 选择Archives可以下载历史包&#xff0c;此处使用5.7.43 2.解压文件 解压文件到你指定安装的目录&#xff1a;解压完成后在mysql-5.7.43-winx64下新建文件my.ini和d…

护眼台灯哪个牌子最好?五款骨灰级硬核机型推荐!

在快节奏的现代生活中&#xff0c;保护视力、提升学习和工作效率都离不开一盏优质的护眼台灯。然而&#xff0c;市面上的护眼台灯品牌琳琅满目&#xff0c;让人在选择时感到迷茫。为了帮助大家找到最适合自己的护眼台灯&#xff0c;本文将为大家推荐五款具备骨灰级硬核实力的护…

Mac添加和关闭开机应用

文章目录 mac添加和关闭开机应用添加开机应用删除/查看 mac添加和关闭开机应用 添加开机应用 删除/查看 打开&#xff1a;系统设置–》通用–》登录项–》查看登录时打开列表 选中打开项目&#xff0c;点击“-”符号

迁移Anaconda环境,无需重复安装

换电脑后&#xff0c;想要直接迁移Anaconda环境&#xff0c;直接复制Anaconda文件后&#xff0c;再做以下两步即可&#xff1a; 1.添加环境变量 2.添加Anaconda prompt winR输入cmd进入命令行 进入conda安装的目录&#xff0c;运行&#xff1a; python .\Lib\_nsis.py mkmenu…

【学习】软件测试行业有哪些从业方向

从事任何一个行业&#xff0c;不论想入行的新人还是已经在职的从业人员&#xff0c;一定要系统化的掌握自身的学习路线和发展方向&#xff0c;随时对自身的优劣点掌握清楚。尤其是对于软件测试这个岗位。测试职业所涉及的技能范围比较广&#xff0c;测试流程、测试计划、缺陷管…

MySQL中的数据备份

1. 逻辑备份 备份的是建表、建库、插入等操作所执行SQL语句&#xff0c;适用于中小型数据库&#xff0c;效率相对较低。 本质&#xff1a;导出的是SQL语句文件 优点&#xff1a;不论是什么存储引擎&#xff0c;都可以用mysqldump备成SQL语句 缺点&#xff1a;速度较慢&…

【第三方登录】Twitter

创建应用 APPID 和 相关回调配置 重新设置api key 和 api secret 设置回调和网址 还有 APP的类型 拿到ClientID 和 Client Secret 源码实现 获取Twitter 的登录地址 public function twitterUrl() {global $db,$request,$comId;require "inc/twitter_client/twitte…

【C语言】 scanf输入函数

文章目录 C语言中的输入函数&#xff1a;scanf详解与应用基本语法处理分隔符自动处理空白字符使用非空白字符作为分隔符 检查scanf的返回值灵活处理多个输入项C语言中的输入函数小知识点总结结语 C语言中的输入函数&#xff1a;scanf详解与应用 C语言提供了一种强大的输入函数—…

【APP_TYC】数据采集案例天眼APP查_抓包分析_①

一杯敬朝阳 一杯敬月光 唤醒我的向往 温柔了寒窗 于是可以不回头地逆风飞翔 不怕心头有雨 眼底有霜 一杯敬故乡 一杯敬远方 守着我的善良 催着我成长 所以南北的路从此不再漫长 灵魂不再无处安放 &#x1f3b5; 毛不易《消愁》 准备工作 在开始之前&…

护眼台灯什么牌子好一点?五款护眼台灯实力强强PK

在当下快节奏的生活中&#xff0c;一盏优质的护眼台灯对于保护视力、提升学习和工作效率至关重要。然而&#xff0c;市面上的护眼台灯品牌众多&#xff0c;让人眼花缭乱。究竟哪个品牌的护眼台灯更好呢&#xff1f;本文将为大家介绍五款实力强劲的护眼台灯&#xff0c;从品牌实…

AI电影剪辑-巧用字幕批量剪辑电影短视频(一)

引言 实现AI电影剪辑是一项非常复杂和困难的任务&#xff0c;它涉及到多个领域和技术的交叉和融合&#xff0c;比如计算机视觉&#xff0c;自然语言处理&#xff0c;多媒体处理&#xff0c;机器学习&#xff0c;深度学习等。 目前&#xff0c;AI电影剪辑还处于一个初级的阶段…

ZC706+AD9361 运行 open WiFi

先到github上下载img&#xff0c;网页链接如下&#xff1a; https://github.com/open-sdr/openwifi?tabreadme-ov-file 用win32 Disk lmager 把文件写入到SD卡中&#xff0c;这一步操作会把SD卡重新清空&#xff0c;注意保存数据。这个软件我会放在最后的网盘链接中 打开linu…

【详细讲解如果Tomcat启动后闪退的解决方法】

&#x1f308;个人主页:程序员不想敲代码啊&#x1f308; &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家&#x1f3c6; &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提…
最新文章