C++单例模式的几种实现

2023-05-16

单例模式(Singleton Pattern)的概念

模式定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

饿汉单例和懒汉单例

常见的单例模式有两个分支,饿汉单例懒汉单例

  • 饿汉单例是指在程序初始化时就把单例对象创建出来。其优点是当要使用对象时可以直接获取,缺点是无论对象有没有被调用,都会被创建出来占据内存。
  • 懒汉单例是指当单例对象第一次被调用时才创建对应对象。其优点是不被调用的对象将不被创建,缺点是创建对象时会花费较多的时间,导致第一次调用的特殊性,且对象创建时是线程不安全的。

使用建议
应当采用饿汉单例,将单例对象的初始化直接放在主线程的开头位置执行
这样做有很多好处,首先保证创建对象时不会产生竞争条件(race condition),使得代码编写起来简单、清晰(如果不在主线程创建好,则要考虑多个线程同时创建对象,此时需要通过互斥量等手段加以限制,虽然C++11的static关键字可以确保只创建一个对象,但依然不推荐这么使用);其次可以保证获取对象的时间是固定的,不会像懒汉模式那样第一次调用会花费额外的时间。

单例模式的实现

1、最基本的饿汉单例模式的实现

#include <iostream>

class SingletonClass {
public:
	//单例对象的全局访问点
	static SingletonClass* getInstance() {
		if (m_pInstance == nullptr) {
			m_pInstance = new SingletonClass();
			return m_pInstance;
		}
		std::cout << "对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//销毁单例对象
	static void destoryInstance() {
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	//构造函数
	SingletonClass() {
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;

	static SingletonClass* m_pInstance;  //单例对象的声明
};

SingletonClass* SingletonClass::m_pInstance = nullptr;  //单例对象的定义

int main() {
	//在主线程最开始的地方就创建出单例对象,相当于饿汉单例
	SingletonClass::getInstance();

	//...

	//多次调用,获取的都是同一个对象
	SingletonClass* obj1 = SingletonClass::getInstance();
	SingletonClass* obj2 = SingletonClass::getInstance();
	SingletonClass* obj3 = SingletonClass::getInstance();

	//...

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

输出:
SingletonClass的单例对象被创建
对象的地址为 00000172D3499600
对象的地址为 00000172D3499600
对象的地址为 00000172D3499600
SingletonClass的单例对象被销毁

2、提供额外接口,让饿汉单例体现在接口设计上

在1中,虽然我们希望实现的是饿汉单例,但因为没有提供特定的接口,因此需要程序员保证在主函数中先调用一次SingletonClass::getInstance();来确保逻辑上的正确性,这显然是不合适的,因此可以通过增加一个接口,用于专门创建饿汉单例对象,在没有创建出单例对象前尝试获取单例对象会抛出异常,以此提醒程序员这是饿汉单例,应当在主函数中进行创建。

#include <iostream>
#include <exception>

class SingletonClass {
public:
	//单例对象的全局访问点
	static SingletonClass* getInstance() {
		if (m_pInstance == nullptr) {
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//创建单例对象
	static void constructInstance() {
		if (m_pInstance == nullptr) {
			m_pInstance = new SingletonClass();
		}
		return;
	}

	//销毁单例对象
	static void destoryInstance() {
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	//构造函数
	SingletonClass() {
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;

	static SingletonClass* m_pInstance;  //单例对象的声明
};

SingletonClass* SingletonClass::m_pInstance = nullptr;  //单例对象的定义

int main() {
	//若没有创建单例对象,就尝试获取它,会抛出异常
	try {
		SingletonClass* obj1 = SingletonClass::getInstance();
	}
	catch (const std::exception& e) {
		std::cout << e.what() << std::endl;
	}

	//创建单例对象
	SingletonClass::constructInstance();

	//...

	//多次调用,获取的都是同一个对象
	SingletonClass* obj1 = SingletonClass::getInstance();
	SingletonClass* obj2 = SingletonClass::getInstance();
	SingletonClass* obj3 = SingletonClass::getInstance();

	//...

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

输出:
this instance is not construct, please construct the instance in main() function first
SingletonClass的单例对象被创建
对象的地址为 000001F768EE9600
对象的地址为 000001F768EE9600
对象的地址为 000001F768EE9600
SingletonClass的单例对象被销毁

3、利用RAII机制,实现自动释放单例对象

在1、2中,需要程序员手动来回收单例对象,实际上,在特定情况下,可以利用RAII机制,实现自动释放单例对象。因为单例对象实际是类的静态成员,所以需要使用到特殊的技巧,通过一个类内类的对象的生命周期来管理单例对象的生命。
注意static对象的生命周期是和程序生命周期一致的,因此只要在主函数开始的时候初始化好对象,之后程序运行时都可以正常的获得到单例对象。
这种方式的弊端是没法自己决定销毁单例对象的时机了。

#include <iostream>
#include <exception>

class SingletonClass {
public:
	//单例对象的全局访问点
	static SingletonClass* getInstance() {
		if (m_pInstance == nullptr) {
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//初始化单例对象
	static void constructInstance() {
		if (m_pInstance == nullptr) {
			m_pInstance = new SingletonClass();
			static SingletonClass_Helper helper;  //利用此对象的生命周期来自动销毁单例对象
		}
		return;
	}

private:
	//构造函数
	SingletonClass() {
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;

	static SingletonClass* m_pInstance;  //单例对象的声明

	//内部类,用于辅助销毁单例对象
	class SingletonClass_Helper {
	public:
		~SingletonClass_Helper() {
			delete SingletonClass::m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
			m_pInstance = nullptr;
		}
	};
};

SingletonClass* SingletonClass::m_pInstance = nullptr;  //单例对象的定义

int main() {
	std::cout << "main() start" << std::endl;

	{
		std::cout << "Scope start" << std::endl;

		//创建单例对象
		SingletonClass::constructInstance();

		//...

		//多次调用,获取的都是同一个对象
		SingletonClass* obj1 = SingletonClass::getInstance();
		SingletonClass* obj2 = SingletonClass::getInstance();
		SingletonClass* obj3 = SingletonClass::getInstance();

		//...

		std::cout << "Scope end" << std::endl;
	}  //出作用域,static SingletonClass_Helper helper;也不会被销毁,因此单例也不会被销毁

	//依然可以获得单例对象
	SingletonClass* obj1 = SingletonClass::getInstance();
	SingletonClass* obj2 = SingletonClass::getInstance();
	SingletonClass* obj3 = SingletonClass::getInstance();

	std::cout << "main() end" << std::endl;

	return 0;
}

输出:
main() start
Scope start
SingletonClass的单例对象被创建
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
Scope end
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
main() end
SingletonClass的单例对象被销毁

4、C++11利用静态局部对象实现的懒汉单例模式

对于懒汉单例而言,必须要考虑多线程下同时调用全局访问点,而该单例对象并没有被创建的情况(也就是第一次被调用的情况),这种情况下,单例对象是有可能被创建两次的,其逻辑如下:
在这里插入图片描述

  • 存在线程安全问题的懒汉单例代码:
#include <iostream>
#include <thread>

class SingletonClass {
public:
	//这样的接口实际是一个懒汉单例
	static SingletonClass* getInstance() {
		if (m_pInstance == nullptr) {
			m_pInstance = new SingletonClass();
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//销毁单例对象
	static void destoryInstance() {
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}
private:
	//构造函数
	SingletonClass() {
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;

	static SingletonClass* m_pInstance;  //static data member,其本质是global object
};

SingletonClass* SingletonClass::m_pInstance = nullptr;

//线程初始化函数
void func() {
	SingletonClass::getInstance();
	return;
}

int main() {
	//多个线程同时去尝试创建单例对象
	std::thread th1(func);
	std::thread th2(func);

	th1.join();
	th2.join();

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

可能的输出:
SingletonClass的单例对象被创建
单例对象的地址为 000001750F380410
SingletonClass的单例对象被创建
单例对象的地址为 000001750F3802B0
SingletonClass的单例对象被销毁

在C++11前,一般会通过加互斥量来解决这个问题,当然这会使得代码编写较为麻烦,此时的逻辑如下:
在这里插入图片描述

  • 利用互斥量保证线程安全的懒汉单例代码(这里利用了双重锁定的技巧):
#include <iostream>
#include <thread>
#include <mutex>

class SingletonClass {
public:
	//这样的接口实际是一个懒汉单例
	static SingletonClass* getInstance() {
		if (m_pInstance == nullptr) {  //双重锁定 A
			std::unique_lock<std::mutex> my_unique_lock(m_mutex);   //自动上锁解锁
			if (m_pInstance == nullptr) {  //双重锁定 B
				m_pInstance = new SingletonClass();
			}
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//销毁单例对象
	static void destoryInstance() {
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}
private:
	//构造函数
	SingletonClass() {
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;

	static SingletonClass* m_pInstance;  //static data member,其本质是global object
	static std::mutex m_mutex;  //互斥量
};

SingletonClass* SingletonClass::m_pInstance = nullptr;
std::mutex SingletonClass::m_mutex;

//线程初始化函数
void func() {
	SingletonClass::getInstance();
	return;
}

int main() {
	//多个线程同时去尝试创建单例对象
	std::thread th1(func);
	std::thread th2(func);

	th1.join();
	th2.join();

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

输出:
SingletonClass的单例对象被创建
单例对象的地址为 0000027A7A2502C0
单例对象的地址为 0000027A7A2502C0
SingletonClass的单例对象被销毁

C++11标准保证,如果多个线程试图同时初始化同一静态局部对象,则初始化严格只发生一次。参考
需要注意这里保证的是静态局部对象,但并不是静态成员变量,所以1、2、3中实现的单例模式在创建对象时是线程不安全的(当然因为1、2、3的设计都是饿汉单例,所以不存在这个问题),因为其单例对象是static SingletonClass* m_pInstance;

  • 利用C++11静态局部对象实现的懒汉单例代码:
#include <iostream>
#include <thread>

class SingletonClass {
public:
	//这样的接口实际是一个懒汉单例
	static SingletonClass& getInstance() {
		static SingletonClass instance;  //function-local static object
		return instance;
	}

private:
	//构造函数
	SingletonClass() {
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;
};

//线程初始化函数
void func() {
	SingletonClass::getInstance();
	return;
}

int main() {
	//多个线程同时去尝试创建单例对象
	std::thread th1(func);
	std::thread th2(func);

	th1.join();
	th2.join();

	return 0;
}

输出:
SingletonClass的单例对象被创建
SingletonClass的单例对象被销毁

想要把懒汉单例改为饿汉单例也非常简单,只要确保在主函数中先获取一次单例对象即可。

对1、2、3、4实现的总结

在C++11标准后,4的实现方式较为流行,一般按照4这种方式使用即可。
但2的实现方式也很优秀,接口设计清晰,并且饿汉模式是较为推荐的使用方式。

5、利用模板实现一个较为通用的饿汉单例模式

在C++11前,模板的参数是固定的,因此常见的做法是只适配6个参数以内的构造函数。
代码如下:

#include <iostream>

template <typename T>  //模板参数应当是一个类类型
class Singleton {
public:
	//单例对象的全局访问点
	//因为不需要参数了,所以可以设计的很简单
	static T* getInstance() {
		if (m_pInstance == nullptr) {  //单例对象未创建,说明代码逻辑存在问题
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//创建单例对象 支持0个参数
	static void constructInstance() {
		if (m_pInstance == nullptr) {
			m_pInstance = new T();
		}
		return;
	}
	
	//创建单例对象 支持1个参数
	template <typename T0>
	static void constructInstance(T0 arg0) {
		if (m_pInstance == nullptr) {
			m_pInstance = new T(arg0);
		}
		return;
	}

	//创建单例对象 支持2个参数
	template <typename T0, typename T1>
	static void constructInstance(T0 arg0, T1 arg1) {
		if (m_pInstance == nullptr) {
			m_pInstance = new T(arg0, arg1);
		}
		return;
	}

	//创建单例对象 支持3个参数
	template <typename T0, typename T1, typename T2>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2) {
		if (m_pInstance == nullptr) {
			m_pInstance = new T(arg0, arg1, arg2);
		}
		return;
	}

	//创建单例对象 支持4个参数
	template <typename T0, typename T1, typename T2, typename T3>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
		if (m_pInstance == nullptr) {
			m_pInstance = new T(arg0, arg1, arg2, arg3);
		}
		return;
	}

	//创建单例对象 支持5个参数
	template <typename T0, typename T1, typename T2, typename T3, typename T4>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
		if (m_pInstance == nullptr) {
			m_pInstance = new T(arg0, arg1, arg2, arg3, arg4);
		}
		return;
	}

	//创建单例对象 支持6个参数
	template <typename T0, typename T1, typename T2, typename T3, typename T4, typename T5>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) {
		if (m_pInstance == nullptr) {
			m_pInstance = new T(arg0, arg1, arg2, arg3, arg4, arg5);
		}
		return;
	}

	//销毁单例对象
	static void destoryInstance() {
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	Singleton() = default;
	~Singleton() = default;
	//禁止拷贝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	static T* m_pInstance;
};

template <typename T>
T* Singleton<T>::m_pInstance = nullptr;

//测试部分
//类A,构造函数无参
struct A {
	A() {
		std::cout << "A类对象被创建" << std::endl;
	}

	~A() {
		std::cout << "A类对象被销毁" << std::endl;
	}
};

//类B,构造函数有参数
struct B {
	B(int x, int y, int z) {
		std::cout << "B类对象被创建" << std::endl;
	}

	~B() {
		std::cout << "B类对象被销毁" << std::endl;
	}
};

int main() {
	//创建单例对象
	Singleton<A>::constructInstance();  //创建A类对象单例
	Singleton<B>::constructInstance(1, 2, 3);  //创建B类对象单例

	//获取单例对象
	auto obj_a1 = Singleton<A>::getInstance();
	auto obj_a2 = Singleton<A>::getInstance();

	auto obj_b1 = Singleton<B>::getInstance();
	auto obj_b2 = Singleton<B>::getInstance();

	//销毁单例对象
	Singleton<A>::destoryInstance();
	Singleton<B>::destoryInstance();

	return 0;
}

输出:
A类对象被创建
B类对象被创建
单例对象的地址为 000001BA054D02E0
单例对象的地址为 000001BA054D02E0
单例对象的地址为 000001BA054D0400
单例对象的地址为 000001BA054D0400
A类对象被销毁
B类对象被销毁

在C++11后,引入了可变长模板、万能引用、完美转发,利用这些技术,可以编写出非常通用的模板。
代码如下:

#include <iostream>

template <typename T>  //模板参数应当是一个类类型
class Singleton {
public:
	//单例对象的全局访问点
	//因为不需要参数了,所以可以设计的很简单
	static T* getInstance() {
		if (m_pInstance == nullptr) {  //单例对象未创建,说明代码逻辑存在问题
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//创建单例对象 可以传入任意参数
	template <typename... Args>
	static void constructInstance(Args&&... args) {  //Args&&... args是万能引用
		if (m_pInstance == nullptr) {
			m_pInstance = new T(std::forward<Args>(args)...);  //完美转发
		}
		return;
	}

	//销毁单例对象
	static void destoryInstance() {
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	Singleton() = default;
	~Singleton() = default;
	//禁止拷贝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	static T* m_pInstance;
};

template <typename T>
T* Singleton<T>::m_pInstance = nullptr;

//测试部分
//类A,构造函数无参
struct A {
	A() {
		std::cout << "A类对象被创建" << std::endl;
	}

	~A() {
		std::cout << "A类对象被销毁" << std::endl;
	}
};

//类B,构造函数有参数
struct B {
	B(int x, int y, int z) {
		std::cout << "B类对象被创建" << std::endl;
	}

	~B() {
		std::cout << "B类对象被销毁" << std::endl;
	}
};

//类C,有带左值和带右值的构造函数
struct C {
	C(const std::string&) {
		std::cout << "C类对象被创建,参数为左值" << std::endl;
	}

	C(std::string&&) {
		std::cout << "C类对象被创建,参数为右值" << std::endl;
	}

	~C() {
		std::cout << "C类对象被销毁" << std::endl;
	}
};

int main() {
	//创建单例对象
	Singleton<A>::constructInstance();  //创建A类对象单例
	Singleton<B>::constructInstance(1, 2, 3);  //创建B类对象单例
	std::string c_arg = "hello";
	Singleton<C>::constructInstance(std::move(c_arg));  //创建C类对象单例

	//获取单例对象
	auto obj_a1 = Singleton<A>::getInstance();
	auto obj_a2 = Singleton<A>::getInstance();

	auto obj_b1 = Singleton<B>::getInstance();
	auto obj_b2 = Singleton<B>::getInstance();

	auto obj_c1 = Singleton<C>::getInstance();
	auto obj_c2 = Singleton<C>::getInstance();

	//销毁单例对象
	Singleton<A>::destoryInstance();
	Singleton<B>::destoryInstance();
	Singleton<C>::destoryInstance();

	return 0;
}

输出:
A类对象被创建
B类对象被创建
C类对象被创建,参数为右值
单例对象的地址为 0000019BE71E0340
单例对象的地址为 0000019BE71E0340
单例对象的地址为 0000019BE71E03B0
单例对象的地址为 0000019BE71E03B0
单例对象的地址为 0000019BE71E0380
单例对象的地址为 0000019BE71E0380
A类对象被销毁
B类对象被销毁
C类对象被销毁
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++单例模式的几种实现 的相关文章

  • go语言判断文件是否为UTF8编码

    一 思路 xff1a 1 UTF8编码规则 xff1a 对于单字节字符 xff0c 8个比特位最高位为0 对于多字节字符 xff0c 若字符由n个字节组成 xff0c 则第一个字节8个比特中最高n位都是1 xff0c 剩下n 1字节中最高位
  • qtcreator调试经常断点导致卡死问题解决

    一 问题描述 在qt开发中 xff0c 使用debuging进行调试 xff08 点击下面按钮 xff09 总会出现 xff0c 软件运行还好 xff0c 就是运行到断点的时候 xff0c 软件一直在等待卡死 在windows上面没有遇到过
  • python+opencv读取摄像头并显示

    解释看注释 xff0c 直接上代码 xff01 span class token keyword import span cv2 span class token comment 打开本地摄像头 span cap span class to
  • python+opencv拉流(串流)

    解释看注释 xff0c 直接上代码 xff01 span class token keyword import span cv2 span class token comment 流链接 span url span class token
  • python+opencv做一个视频录制器(mp4)

    1 功能说明 代码的功能就是读取摄像头视频显示 xff0c 并同时保存为mp4文件 xff0c 示例代码为1小时保存一个视频 2 代码 解释看注释 xff0c 直接上代码 xff01 span class token keyword imp
  • [Python] Pandas 中 read_csv 与 read_hdf 速度对比

    1 read csv VS read hdf 一般情况下 我们习惯使用 Pandas 中的 read csv 函数来读取 CSV 文件 但当 CSV 文件比较大时 read csv 的速度会显得有点慢 这时可以考虑使用 HDF5 格式来存储
  • inode节点(详解)

    首先 xff0c 要明确理解inode是理解Linux Unix文件系统和硬盘存储的基础 1 什么是inode xff1f 理解inode xff0c 要从文件存储说起 文件存储在硬盘上 xff0c 硬盘的最小存储单位叫做 扇区 每个扇区能
  • 8b/10b编码技术系列(一):Serdes、CDR、K码

    和大家分享一下关于8b 10b编码的知识点 xff0c 如有什么错误之处或大家有什么额外的见解欢迎大家公众号后台留言 xff01 一 Serdes高速收发器 在传统的源同步传输中 xff0c 数据和时钟分离 xff0c 在速率较低 lt 1
  • Java处理文件时常用的文件类型及对应的contentType

    2023 3 02 项目管理系统中对项目文档的处理 本来全部设置的是二进制流 xff0c 发现下载后没有文件类型和文件名称 对应的解决办法 xff1a 上传 下载的时候对文件类型进行说明 上传 span class token keywor
  • 51单片机入门-点亮第一个LED灯

    前期准备 keil软件的安装 可以参考我的另一篇文章 https blog csdn net weixin 42911200 article details 81590158 安装烧录软件和添加单片机 由于笔者所用的芯片为STC89C52
  • 基于Docker的容器集群调度机制的设计与实现

    本文来自于北京邮电大学2018年硕士论文的整理 xff0c 作者李战 论文主要分为如下四部分 一 集群调度架构总结 1 xff09 中央式架构 最原始 xff0c k8s xff0c swarm均是该调度方式 由于所有任务都由唯一的调度器处
  • Linux版的Mysql基本操作命令整理

    1 更改密码 SET PASSWORD 61 PASSWORD 39 123456 39 ALTER USER 39 root 39 64 39 localhost 39 PASSWORD EXPIRE NEVER flush privil
  • 关于MySQL5.7 密码策略/审计日志开启详细步骤

    一 配置密码策略 登录mysql xff1a mysql u root p 输入密码 xff1a xxxxxx xff08 以实际情况为例 xff0c 例如 xff1a 123456 xff09 目标密码策略 xff1a 至少一个大写 至少
  • 软件测试的基本理论-黑盒测试-1

    黑盒测试方法 一 黑盒测试方法1 xff0c 等价类划分法a 等价类划分概述等价类划分 b xff1a 设计测试用例等价类划分demo 2 边界值分析法边界值分析法概述边界值分析法demo 3 因果图方法因果图设计方法因果图设计demo 4
  • Python二维插值(scipy.interpolate.interp2d)注意输入参数维度问题

    问题描述 SGLI将数据的经纬度进行了重采样 xff0c 例如本来是10001000的图像重采样到了100100像元 xff0c 这导致在对影像插值时需要先把经 纬度重采样到1000 1000 xff0c 本文主要记录在重采样中遇到的输入数
  • 深度学习传统CV算法——边缘检测算法综述

    边缘检测 边缘概述认识边缘定义轮廓和边缘的关系边缘的类型 边缘检测的概念概念 边缘检测方法基本方法图像滤波图像增强图像检测图像定位 边缘检测算子的概念常见的边缘检测算子用梯度算子实现边缘检测的原理梯度算子边缘点梯度梯度算子 梯度如何衡量使用
  • Linux网络工具简介

    Linux网络工具简介 学习计算机网络不能只学习理论知识 xff0c 同时必须学会网络工具的使用 xff0c 用网络工具来快速判断自己程序的网络状态并发现问题 第一次使用网络工具前需要先下载安装 xff1a 安装网络工具包net tools
  • “操作无法完成,因为文件已在另一个程序中打开”怎么办?

    Step1 xff1a 通过报错 xff0c 找到进程名称 可以找到文件夹内的具体文件 Step2 xff1a 打开任务管理器 ctrl 43 alt 43 del 点击性能 Step3 xff1a 打开资源监视器 Step4 点击CPU
  • 将Mircrosoft Store下载的Ubuntu安装到指定位置方法,同时解决“你需要来自System的权限才能对此文件进行更改”问题

    一 概述 最近使用到WIndows的WSL功能 xff0c 需要安装ubuntu这个子系统进行仿真环境搭建 xff0c 但是又不愿意使用虚拟机 xff0c 不太方便 在安装过程中发现本身就岌岌可危的C盘经常突然爆满 xff0c 经过检查发现
  • Pixhawk基础—认识Pixhawk

    Pixhawk简介 pixhawk是由3DR联合APM小组与PX4小组于2014年推出的飞控PX4的升级版 xff0c 它同时拥有PX4和APM两套固件和相应的地面站软件 该飞控是目前全世界飞控产品中硬件规格最高的产品 Pixhawk基础

随机推荐