【C++】设计模式详解:单例模式

文章目录

  • Ⅰ. 设计一个类,不允许被拷贝
  • Ⅱ. 请设计一个类,只能在堆上创建对象
  • Ⅲ. 请设计一个类,只能在栈上创建对象
  • Ⅳ. 请设计一个类,不能被继承
  • Ⅴ. 请设计一个类,只能创建一个对象(单例模式)
  • 💥单例模式:
    • 1、饿汉模式
    • 2、懒汉模式
      • 第一种写法:
      • 第二种写法:

在这里插入图片描述

Ⅰ. 设计一个类,不允许被拷贝

​ 拷贝只会发生在两个场景中:拷贝构造函数赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。

  • C++98 的方式:

    1. 设置成私有:如果只声明而没有设置成 private,用户自己如果在类外定义了,还是等于可以拷贝

    2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就做不到防止成员函数内部拷贝了。

      class CopyBan
      {
      	// ......
      private:
      	// 设为私有,只声明不实现
      	CopyBan(const CopyBan&);
      	CopyBan& operator=(const CopyBan&);
      };
      
  • C++11 的方式:

    • C++11中扩展了 delete 的用法,delete 除了释放 new 申请的资源外,如果在默认成员函数后跟上 =delete,表示让编译器删除掉该默认成员函数。
    class CopyBan
    {
    	// 直接使用delete关键字
    	CopyBan(const CopyBan&) = delete;
    	CopyBan& operator=(const CopyBan&) = delete;
    
    	// ......
    };
    

Ⅱ. 请设计一个类,只能在堆上创建对象

实现方式:

  1. 将类的构造函数私有化并将拷贝构造的声明也私有化,防止别人调用拷贝在栈上生成对象。(或者用 defaultdelete 关键字也行)

  2. 提供一个完成堆对象创建的静态成员函数

​ 顺便提一下,有人采用将析构函数变成私有的方法来使类的默认构造函数、拷贝构造、赋值重载不会自动生成,这也是可以的,但是这时候就需要我们手动去写一个释放的函数来调用,所以一般我们也只用上面的方法,而这种 将析构函数私有的方法不常用

class HeapOnly
{
public:
	// static的好处就是我们不需要对象就可以在类外通过类名::函数名直接访问
	static HeapOnly* CreateObject()
	{
		return new HeapOnly;
	}
private:
	// 默认构造函数不能直接封掉,因为上面的CreateObject()需要调用
	// 可以只声明不实现,这里直接使用default关键字
	HeapOnly() = default;
	
	// 拷贝构造和赋值重载要封掉,防止拷贝产生栈空间对象
	HeapOnly(const HeapOnly&) = delete;
	HeapOnly& operator=(const HeapOnly&) = delete;
};

int main()
{
	HeapOnly* h1 = HeapOnly::CreateObject();

	// HeapOnly h2 = h1; // ❌
	// static HeapOnly h3; // ❌

	static HeapOnly* h3 = HeapOnly::CreateObject(); // 本质还是一个指向堆空间的对象
	cout << typeid(h3).name() << endl;
	return 0;
}

// 运行结果:
class HeapOnly * __ptr64

Ⅲ. 请设计一个类,只能在栈上创建对象

  • 方法一:同上面那种情况一样,将构造函数私有化,然后设计静态方法创建对象返回即可。

    class StackOnly
    {
    public:
    	static StackOnly CreateObject()
    	{
    		return StackOnly();
    	}
    private:
    	StackOnly() = default;
    
    	/* 或者只声明不定义
    	StackOnly()
    	{}
    	*/
    };
    
    int main()
    {
    	StackOnly s1 = StackOnly::CreateObject();
    
    	StackOnly* s2 = new StackOnly; // ❌
    	static StackOnly s3; // ❌
    	return 0;
    }
    
  • 方法二:屏蔽 operator newoperator delete。因为 new 在底层调用 void* operator new(size_t size) 函数,只需将该函数屏蔽掉即可。

    • 注意:要防止定位 new
    • 这种方法其实是不太好使的,因为就算我们禁用了 operator new 或者 operator delete,我们也很难防止其在静态区中产生对象,如果使用这种方法,那么还是得和方法一一样,将构造函数私有化,然后使用静态函数返回栈对象,那为何不直接使用第一种方法呢❓❓❓
    class StackOnly
    {
    public:
    	StackOnly() 
    	{}
    private:
    	void* operator new(size_t size);
    	void operator delete(void* p);
    };
    
    int main()
    {
    	StackOnly s1;
    
    	StackOnly* s2 = new StackOnly; // ❌
    	static StackOnly s3; // 仍然可以生成静态区对象
    	return 0;
    }
    

​ 这里要说明一个点,就是我们还是 没办法预防产生静态变量,如下面代码:

class StackOnly
{
public:
	static StackOnly CreateObject()
	{
		return StackOnly();
	}
private:
	StackOnly() = default;
    
    // 不能封掉拷贝构造,不然CreateObject无法return
    // StackOnly(const StackOnly&) = delete;
};
int main()
{
	StackOnly s1 = StackOnly::CreateObject();

	// 无法封掉这种情况,因为如果封掉拷贝构造的话,那么我们就无法在CreateObject中return一个临时栈对象了
	static StackOnly s2 = StackOnly::CreateObject(); 
	cout << typeid(s2).name() << endl;
	return 0;
}

// 运行结果:
class StackOnly

​ 如果我们想避开这种情况,唯一的方法就是 不使用栈对象,我们只通过 CreateObject() 来调用类中的某些函数,但是一般这么做就有点一次性那味。

Ⅳ. 请设计一个类,不能被继承

  • C++98 的方式:

    // C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
    class NonInherit
    {
        public:
        static NonInherit GetInstance()
        {
            return NonInherit();
        }
    private:
        NonInherit()
        {}
    };
    
  • C++11 的方式

    • 使用 final 修饰类,表示该类不能被继承。
    class A  final
    {
        // ....
    };
    

Ⅴ. 请设计一个类,只能创建一个对象(单例模式)

设计模式(Design Pattern是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

​ 我们之前在学习 C++ 的过程中其实早就接触到了设计模式,比如迭代器模式、适配器模式等等,下面我们就来讲一下单例模式:

💥单例模式:

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

​ ⚜️一般来说,单例模式下是不需要考虑资源释放的,因为我们这个单例对象是在主程序结束之后会自动释放的,如果没有特定需求说要提前释放,一般我们都不需要实现资源释放的功能,但是如果需要的话,比如说我们需要手动释放,因为一些资源可能要保存到日志等原因,所以我们就得实现释放资源函数比如说 DeleteInstance() 等等,下面的懒汉模式中会实现!

​ 单例模式有两种实现模式:

1、饿汉模式

​ 饿汉模式的宗旨就是说 不管你将来用不用,主程序启动时就创建一个唯一的实例对象。这就像一个饿汉一样,一旦遇到了食物,那么此时都是控制不住想直接去吃的,所以这种模式叫做饿汉模式!

  • 优点:
    • 控制简单
    • 因为是在执行主函数之前就生成了对象,所以 没有线程安全问题
    • 如果这个单例对象 在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,响应速度更好
  • 缺点:
    • 若单例对象的成员数据过多,那么会 导致整个程序启动变慢
    • 如果有多个单例类是相互依赖并且有初始化依赖顺序的,那么饿汉模式在创建的时候是控制不了这种依赖顺序。(可参考 Effective C++

实现方式:

​ 首先既然是单例模式,那么我们肯定要保证全局只能产生一个对象,那么我们想到的就是用静态变量,所以我们在 Singleton 类中定义一个静态变量 single_object,并且用一个静态成员函数 CreateObject() 返回该对象,而这个对象就是这个单例对象。

​ 接着为了防止生成一个栈对象、堆对象,我们得将拷贝构造和赋值重载封掉,并将构造函数私有化而且不实现!

// 饿汉模式
// 优点:简单
// 缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
class Singleton
{
public:
	static Singleton& CreateObject()
	{
		return single_object;
	}

	void Print()
	{
		cout << "饿汉模式::Print()" << endl;
	}
private:
	// 构造函数私有,并且不实现
	Singleton() {}

	// 拷贝构造以及赋值重载封掉
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;

public:
	static Singleton single_object; // 声明一个当前类的静态变量
};

Singleton Singleton::single_object; // 类外初始化这个静态变量


int main()
{
	// 第一种访问方法:通过CreateObject()直接调用相关接口
	Singleton::CreateObject().Print();

	// 第二种访问方法:可以使用引用接收CreateObject(),通过该对象调用相关接口
	Singleton& s = Singleton::CreateObject();
	s.Print();

	Singleton s1; // ❌无法通过编译
	Singleton* s2 = new Singleton; // ❌无法通过编译
	static Singleton s3; // ❌无法通过编译
	return 0;
}

// 运行结果:
饿汉模式::Print()
饿汉模式::Print()

​ 💥注意:上面我们在实现饿汉模式以及后面的懒汉模式的时候,都采用 c++11delete 关键字进行防止拷贝发生,而不采用 c++98 的方式!

2、懒汉模式

​ 如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

​ 但是懒汉模式的问题就是涉及到了多线程安全问题,所以实现起来当然会复杂许多。

  • 优点:
    • 因为对象在主程序之后才会创建,所以 程序启动比饿汉模式要快
    • 可以控制不同的单例类的依赖关系以及控制依赖顺序
  • 缺点:
    • 涉及到多线程安全问题,需要加锁,实现要复杂许多

第一种写法:

​ 这种写法涉及到了加锁的问题,所以会复杂一点。和饿汉模式一样,我们需要一个 静态成员函数 CreateObject() 用于获取这个单例对象,不同的是懒汉模式中我们不能直接在类内定义一个静态变量,因为我们要的效果是当我们调用 CreateObject() 的时候,单例对象才会被创建,而不是在程序启动之前就被创建了,所以我们在 类内定义的成员应该是一个静态指针 single_ptr,并且将其初始化为 nullptr,这样子当我们去调用 CreateObject() 的时候,如果判断 single_ptr 为空,则进行资源的初始化,否则说明已经被初始化过了,则不会再去初始化它,达到单例对象的目的!

​ 这个时候问题就来了,既然出现了判断以及对成员变量的操作,那么在多线程环境中就有可能会出现问题,所以我们就 需要加锁

​ 我们可以直接在类内定义一把 静态的锁 _mtx,然后在单例对象指针判空那个代码块前后分别上锁和解锁。但是这里其实还会涉及一个问题,因为我们的 single_ptr 是通过 new 出来的,那么 可能 new 还会抛异常导致死锁的问题,这个时候其实可以用异常捕获,但是我们一般不这么做,因为写法比较挫。

​ 一般我们都会用一个 守卫锁,在头文件 <mutex> 中就有一个函数模板 lock_guard 可以直接使用,这里为了便于理解,我们手动实现一个简易的守卫锁 LockGuard,具体看代码,其实不难,就是 在构造函数中上锁,在析构函数中解锁,所维护的就是一个锁对象!

​ 我们自行实现的守卫锁时候,可能会出现一些问题,比如我们在定义锁对象的时候,不能直接使用它对应的锁类型,因为我们在拷贝构造函数中初始化的时候,其实是通过拷贝一个锁对象来赋值的,但是问题来了,锁是不能拷贝的!那咋整❓❓❓

​ 这个时候我们就不能只是简单的定义一个锁对象,我们可以定义一个锁对象的引用或者指针,这里采用引用的方式!

​ 通过锁对象的引用,就必须在构造函数中进行初始化(C++ 语法规定),这样子的话我们引用的其实就是一个传过来的锁对象的别名,就能绕开锁不能赋值的问题了!

​ (搞不清楚这里在说什么的,可以看看下面代码中的守卫锁部分就懂了!)

​ 除此之外,我们会发现如果我们已经生成了一个单例对象,但是如果后续还有线程调用 CreateObject() 的时候,每次都会被我们的守卫锁卡住,这势必会导致我们程序的效率低下,所以这里我们 用两层 if 判空,减少对锁的消耗,虽说写起来比较冗余,但是这大大提高了程序的效率!

​ 另外,我们在上面提到过,单例模式一般来说是不需要释放的,但是还是避免不了有时候我们需要保存资源到日志啊等情况,那么我们就得对这个资源释放问题做一下处理,所以我们下面实现中多写了一个 静态成员函数 DeleteInstance() 用于处理资源释放问题,而我们可以在主程序或者其它函数中去手动释放它!

​ 但是如果我们想释放又忘记释放了呢❓❓❓所以为了保险,我们可以定义一个 自动回收资源类 GC 类(garbage control,实现并不难,我们可以将其定义在单例类里面,作为一个 内部类,这样子的话就能很方便的取到 Singleton 类中我们写的 DeleteInstance()

​ 而这个 GC 类实现的思想就是在析构函数中调用上述的 DeleteInstance(),要注意的是,因为有可能我们提前手动释放了这段空间,所以 我们需要判断 single_ptr 是否已经为空,是的话说明已经被释放,则我们不做任何动作,防止二次释放;如果不为空我们再去调用 DeleteInstance() 进行释放!

​ 这样子 GC 类就达到该目的:如果提前手动释放,则不会回收;如果没有提前手动释放,则会在这里自动释放!

​ 除此之外还有一个重点,就是我们定义的静态指针 single_ptr 得用 volatile 修饰,因为由于编译器可能会对代码进行优化,导致 重排序等问题,使用 volatile 关键字可以防止编译器优化,保证线程安全。

重排序的解释:

​ 重排序是指在编译器、处理器或者运行时系统中,由于优化等原因,指令执行的顺序可能会被改变,但是最终的执行结果与原本的代码保持一致。

在多线程编程中,重排序可能会导致线程安全问题,因为线程的执行顺序是不确定的,如果在代码中没有正确的同步措施,就有可能导致意想不到的结果。

​ 例如,在单例模式的实现中,如果不加同步措施,那么在多线程环境下,可能会出现多个实例被创建的情况。这是因为在创建实例的代码中,可能会发生指令重排序,导致另一个线程在检查实例是否为空之前,就已经获取到了一个未完成初始化的实例对象。

​ 为了避免这种情况,可以使用 volatile 关键字来修饰单例对象指针。volatile 关键字告诉编译器不要对这个变量进行重排序优化,从而保证了单例对象的正确创建。

// 这里单独写一个守卫锁是为了方便理解
template<class Lock>
class LockGuard
{
public:
	LockGuard(Lock& lock)
		:_lock(lock)
	{
		_lock.lock();
	}
	~LockGuard()
	{
		_lock.unlock();
	}
private:
	// 这里的_lock要用引用接收,不然如果只是一个Lock类型的对象,那么在构造函数中是不允许拷贝构造的(锁不允许拷贝)
	// 当然这里也可以用指针,只不过这里用引用更贴切c++的方式
	Lock& _lock; 
};

// 懒汉模式
// 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
// 缺点:复杂
class Singleton
{
public:
	static Singleton& CreateObject()
	{
		// 涉及多线程,要加锁
		// 但是有可能new会抛异常导致死锁,所以我们可以用一个守卫锁
		// 除此之外,当前对象已经new出来之后,为了防止后面来的线程都会被锁住影响效率,我们可以用双层判断来防止这种情况
		if (single_ptr == nullptr)
		{
			// std::lock_guard<mutex> lock(_mtx); // 当然也可以用库里的lock_guard

			LockGuard<mutex> lock(_mtx); // 使用守卫锁
			if (single_ptr == nullptr)
			{
				single_ptr = new Singleton;
			}
		}
		return *single_ptr;
	}

	// 自动回收资源的管理类:
	// 如果提前手动释放,则不会回收;
	// 如果没有提前手动释放,则会在这里自动释放
	class GC
	{
	public:
		~GC()
		{
			// 如果没有被提前手动释放,则才会去释放,防止不小心多次释放
			if (single_ptr != nullptr)
			{
				// 内部类是外部类的友元,可以直接调用
				DeleteInstance();
			}
		}
	};

	static GC _gc; // 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象

	// 一般我们不需要考虑释放
	// 但是如果我们想要保存资源的时候,就得处理一下
	static void DeleteInstance()
	{
		// 保存文件等操作,可自行添加
		// .......
		
		// 删除和保存的时候可能有多线程问题,要加锁
		LockGuard<mutex> lock(_mtx);
		if (single_ptr != nullptr)
		{
			delete single_ptr;
			single_ptr = nullptr; // 记得置空,下一个线程进来的时候判断后就不会进入该代码块
		}
		cout << "资源处理完成,释放成功" << endl;
	}

	void Print()
	{
		cout << "懒汉模式::Print()" << endl;
	}
private:
	// 构造函数私有,并且不实现
	Singleton() {}

	// 拷贝构造以及赋值重载封掉
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;

private:
	volatile static Singleton* single_ptr; // 单例对象指针,用volatile修饰,防止编译器过度优化
	static mutex _mtx;                     // 静态的互斥锁
};

volatile Singleton* Singleton::single_ptr = nullptr; // 初始化为空
mutex Singleton::_mtx;
Singleton::GC Singleton::_gc;

int main()
{
	// 第一种访问方法:通过CreateObject()直接调用相关接口
	Singleton::CreateObject().Print();

	// 第二种访问方法:可以使用引用接收CreateObject(),通过该对象调用相关接口
	Singleton& s = Singleton::CreateObject();
	s.Print();

	// s.DeleteInstance(); // 如果需要的话就提前手动释放一下

	//Singleton s1; // ❌
	//Singleton* s2 = new Singleton; // ❌
	//static Singleton s3; // ❌
	return 0;
}

// 运行结果:
懒汉模式::Print()
懒汉模式::Print()
资源处理完成,释放成功

第二种写法:

​ 这种写法相比上面就简单多了,其实利用的是 C++11 的一个新特性:局部静态变量初始化是线程安全的!

​ 注意,这在 C++11 之前都是不保证的,所以这种方法不是通用的,但是也是很好用的一种!

​ 其实就是在 静态成员函数 CreateObject() 中直接创建一个局部静态变量,并且返回它的引用,我们都知道,因为局部静态变量对于这个静态成员函数来说只有一份,如果其已经先被初始化了,那么后续进来之后是不会有任何初始化工作的!并且依靠 C++11 更新的这个特性,我们 无需上锁

class Singleton
{
public:
	// 会不会有线程安全问题???
	// c++11之前,这里是不能保证single_object初始化是线程安全的!
	// c++11之后,这里是线程安全的!
	// 也就是说,c++11之后,局部静态变量初始化是线程安全的!
	// 所以这种写法不是通用的,比较少用,但是也是可以用的!
	static Singleton& CreateObject()
	{
		// 直接在CreateObject()创建一个静态单例类对象直接返回
		static Singleton single_object;
		return single_object;
	}

	void Print()
	{
		cout << "懒汉模式::Print()" << endl;
	}
private:
	// 构造函数私有,并且不实现
	Singleton() {}

	// 拷贝构造以及赋值重载封掉
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
};

int main()
{
	// 第一种访问方法:通过CreateObject()直接调用相关接口
	Singleton::CreateObject().Print();

	// 第二种访问方法:可以使用引用接收CreateObject(),通过该对象调用相关接口
	Singleton& s = Singleton::CreateObject();
	s.Print();

	//Singleton s1; // ❌
	//Singleton* s2 = new Singleton; // ❌
	//static Singleton s3; // ❌
	return 0;
}

// 运行结果:
懒汉模式::Print()
懒汉模式::Print()

在这里插入图片描述

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

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

相关文章

LLM 推理

https://www.bilibili.com/video/BV16yqeYhELh/ 大模型推理加速目标&#xff1a;高吞吐、低延迟 TGI vLLM SGLang LMDeploy 商汤 和 上海人工智能实验室 一起开发 缺点 性能对比 分析总结 https://www.bilibili.com/video/BV16yqeYhELh/ 大模型推理加速目标&#xff1a;高吞吐…

UE(UltraEdit) 配置简易C/C++编译运行环境

该类型其他帖子 EmEditor 配置简易C/C 编译运行环境_emeditor 代码运行-CSDN博客 RJ TextEd 配置简易C/C 编译运行环境-CSDN博客 这种配置适合ACM竞赛&#xff0c;即要求不使用现代IDE&#xff0c;又想用一个比较好用、至少支持代码高亮的编辑器。 前提条件 1.Mingw GCC 已…

XSS 漏洞全面解析:原理、危害与防范

目录 前言​编辑 漏洞原理 XSS 漏洞的危害 检测 XSS 漏洞的方法 防范 XSS 漏洞的措施 前言 在网络安全的复杂版图中&#xff0c;XSS 漏洞&#xff0c;即跨站脚本攻击&#xff08;Cross - Site Scripting&#xff09;&#xff0c;是一类极为普遍且威胁巨大的安全隐患。随着互…

Alfresco Content Services dockerCompose自动化部署详尽操作

Alfresco Content Services docker社区部署文档 Alfresco Content Services简介 Alfresco Content Services&#xff08;简称ACS&#xff09;是一款功能完备的企业内容管理&#xff08;ECM&#xff09;解决方案&#xff0c;主要面向那些对企业级内容管理有高要求的组织。具体…

LCR 139.训练计划 I

目录 题目过程解法双指针法&#xff08;两端开始&#xff09;快慢指针 题目 教练使用整数数组 actions 记录一系列核心肌群训练项目编号。为增强训练趣味性&#xff0c;需要将所有奇数编号训练项目调整至偶数编号训练项目之前。请将调整后的训练项目编号以 数组 形式返回。 过…

AboutDialog组件的功能和用法

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了AlertDialog Widget相关的内容,本章回中将介绍AboutDialog Widget.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在这里说的AboutDialog是一种弹出式窗口&#xff0c;和上一章回中介绍的Al…

Redis学习之哨兵二

一、API 1.sentinel masters:展示被监控的主节点状态及相关的统计信息 2.sentinel master <master name>:展示指定的主节点的状态以及相关的统计信息 3.sentinel slaves <master name>:展示指定主节点的从节点状态以及相关的统计信息 4.sentinel sentinels <mas…

03链表+栈+队列(D2_栈)

目录 讲解一&#xff1a;栈 一、基本介绍 二、代码示例 ------------------------------ 讲解二&#xff1a;单调栈 一、基本介绍 二、适用场景 三、情形示例 1. 寻找左边第一个小于它的数 2. 寻找左边第一个小于它的数的下标 3. 寻找右边第一个大于它的数 4. 寻找右…

春晚魔术中的数学知识

蛇年春晚刘谦魔术又和大家普及了一下编程中的冒泡排序法&#xff0c;思考深入一点&#xff0c;它还涉及到群论和组合数学中的一些知识。 游戏规则和操作步骤&#xff0c;任意打乱三种餐具作为初始状态&#xff1a; 1.筷子和左边的东西互换&#xff0c;如果筷子就在左边&#…

OpenCV:开运算

目录 1. 简述 2. 用腐蚀和膨胀实现开运算 2.1 代码示例 2.2 运行结果 3. 开运算接口 3.1 参数详解 3.2 代码示例 3.3 运行结果 4. 开运算应用场景 5. 注意事项 6. 总结 相关阅读 OpenCV&#xff1a;图像的腐蚀与膨胀-CSDN博客 OpenCV&#xff1a;闭运算-CSDN博客 …

基于Springboot的健身房管理系统【附源码】

基于Springboot的健身房管理系统 效果如下&#xff1a; 系统登陆页面 管理员主页面 器材类型管理页面 健身房管理页面 教练管理页面 用户管理页面 个人信息页面 课程管理页面 研究背景 随着健康意识的不断增强和人们生活水平的提高&#xff0c;健身房已经成为了现代城市中不…

扣子平台音频功能:让声音也能“智能”起来。扣子免费系列教程(14)

在数字化时代&#xff0c;音频内容的重要性不言而喻。无论是在线课程、有声读物&#xff0c;还是各种多媒体应用&#xff0c;音频都是传递信息、增强体验的关键元素。扣子平台的音频功能&#xff0c;为开发者和内容创作者提供了一个强大而灵活的工具&#xff0c;让音频的使用和…

初始Python篇(8)—— 异常

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; Python 目录 异常介绍 异常的处理 try-except try-except-else try-except-else-finally 异常的抛出 常见的异常类型 异常介绍 在…

SSM-MyBatis-总结

文章目录 一、Hello MyBatis1.1 流程1.2 总结 二、Crud 的一些注意点三、参数传递3.1 #{ } VS ${ }3.2 单、复参数传递&#xff08;1&#xff09;单参数&#xff08;2&#xff09;多参数 -- Param&#xff08;3&#xff09;总结 四、查询结果返回--结果封装4.1 ResultType 一般…

【算法设计与分析】实验1:字符串匹配问题的算法设计与求解

目录 一、实验目的 二、实验环境 三、实验内容 四、核心代码 五、记录与处理 六、思考与总结 七、完整报告和成果文件提取链接 一、实验目的 给定一个文本&#xff0c;在该文本中查找并定位任意给定字符串。 1、深刻理解并掌握蛮力法的设计思想&#xff1b; 2、提高应用…

10.6.1 文本文件读、写和追加

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 文本文件的读写通常的做法是建立一个与文件关联的filestream&#xff0c;然后使用StreamReader读取或者StreamWriter写入。 为了详…

DevEco Studio 4.1中如何创建OpenHarmony的Native C++ (NAPI)程序

目录 引言 操作步骤 结语 引言 OpenHarmony的开发工具变化很快&#xff0c;有的时候你安装以前的教程进行操作时会发现界面和操作方式都变了&#xff0c;进行不下去了。比如要在OpenHarmony中通过NAPI调用C程序&#xff0c;很多博文&#xff08;如NAPI篇【1】——如何创建含…

达梦拷贝DM_HOME的复制安装

近期一个项目需求&#xff0c;需要在没有安装包的情况下&#xff0c;将达梦数据库安装到虚机上&#xff08;生产机上安装了达梦&#xff09;&#xff0c;故采用直接打包生产机DM_HOME的方式拷贝至虚机&#xff0c;再依次执行达梦的部分指令完成安装。以下为验证的步骤&#xff…

【MySQL】初始MySQL、库与表的操作

目录 基本使用 使用案例 SQL分类 存储引擎 库的操作 字符集和校验规则 查看系统默认字符集和校验规则 查看数据库支持的字符集 查看数据库支持的字符集校验规则 指定编码常见数据库 校验规则对数据库的影响 操纵数据库 库的备份与恢复 表的操作 创建表 查看表 …

AI大模型开发原理篇-2:语言模型雏形之词袋模型

基本概念 词袋模型&#xff08;Bag of Words&#xff0c;简称 BOW&#xff09;是自然语言处理和信息检索等领域中一种简单而常用的文本表示方法&#xff0c;它将文本看作是一组单词的集合&#xff0c;并忽略文本中的语法、词序等信息&#xff0c;仅关注每个词的出现频率。 文本…