【C++修炼之路】33.特殊类设计

news2024/11/14 2:37:30

在这里插入图片描述
每一个不曾起舞的日子都是对生命的辜负

特殊类设计

  • 一.设计一个类,不能被拷贝
  • 二.设计一个类,只能在堆上创建对象
    • 1. 普通类的创建对象
    • 2.只能在堆上创建对象的类
  • 三.设计一个类,只能在栈上创建对象
  • 四.设计一个类,不能被继承
  • 五.单例模式
    • 1.什么是设计模式?
    • 2.单例模式

  • 掌握常见特殊类的设计方式

一.设计一个类,不能被拷贝

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

  • C++98

    将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。

class CopyBan
{
  // ...
 
private:
  CopyBan(const CopyBan&);
  CopyBan& operator=(const CopyBan&);
  //...
};

原因:

  1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
  2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
  • C++11

    C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

class CopyBan
{
  // ...
  CopyBan(const CopyBan&)=delete;
  CopyBan& operator=(const CopyBan&)=delete;
  //...
};

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

1. 普通类的创建对象

普通的类,可以在三种位置上创建对象:

  1. 静态区
#include<iostream>
using namespace std;
class HeapOnly
{};
int main()
{
	HeapOnly hp1;//栈
	HeapOnly* php2 = new HeapOnly;//堆
	static HeapOnly hp3;//静态区
	return 0;
}

2.只能在堆上创建对象的类

要想只能在堆上创建对象,那一定需要在构造函数上动手脚,因为构造函数默认在栈上创建对象。

实现方式:

  1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
  2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
#include<iostream>
using namespace std;
class HeapOnly
{
public:
	static HeapOnly* CreateObject()
	{
		return new HeapOnly;
	}
private:
	HeapOnly()
    {}
};

int main()
{
	HeapOnly* php = HeapOnly::CreateObject();
	return 0;
}

为什么要加上static?

如果CreateObject不加上static,那么在调用该方法就需要在存在对象的基础上才能使用该方法,而该对象默认一定会用构造函数,但是构造函数已经私有化,这就是一个先有鸡还是先有蛋的问题,因此一定要加上static。

但是就目前的情况,仍然可能在栈上开辟对象,首先友元一定是可以的。其次,拷贝构造函数没有显示化调用会默认生成,因此,如下方式仍可以在栈上创建对象:

int main()
{
	HeapOnly* php2 = HeapOnly::CreateObject();
	HeapOnly php3(*php2);//栈上创建对象
	return 0;
}

所以,拷贝构造函数同样需要禁掉,才是只能在堆上创建的类:

#include<iostream>
using namespace std;
class HeapOnly
{
public:
	static HeapOnly* CreateObject()
	{
		return new HeapOnly;
	}
private:
	HeapOnly() {}
	HeapOnly(const HeapOnly&) = delete;
};

int main()
{
	HeapOnly* php2 = HeapOnly::CreateObject();
	return 0;
}

只在堆上创建类的第二种方式:析构私有化

如果析构私有化,那么直接创建对象会显示没有合适的构造函数,从而无法在栈上创建对象。

class HeapOnly
{
public:
	HeapOnly()
	{}
private:
	~HeapOnly()
	{}

	HeapOnly(const HeapOnly&) = delete;
};

int main()
{
	HeapOnly hp1;
	return 0;
}

image-20230709125511089

但此时可以在堆上创建,那么此时分为如下步骤:

  1. 析构函数私有化
  2. 构造函数public显示调用
  3. 新增Destory方法,用来释放堆空间
class HeapOnly
{
public:
	HeapOnly()
	{}
	void Destory()
	{
		this->~HeapOnly();
	}
private:
	~HeapOnly()
	{}

	HeapOnly(const HeapOnly&) = delete;
};

int main()
{
	HeapOnly* php1 = new HeapOnly;
	php1->Destory();

	return 0;
}

image-20230709125822252

Destory对于static没有要求,用不用static修饰完全是我们自己所决定的。

注:在vs2019中,上面的this必须显示调用才没有错误。

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

方法一:(同上)

  1. 将构造函数私有化。
  2. 然后设计静态方法创建对象返回即可。
//请设计一个类,只能在栈上创建对象
class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		return StackOnly();
	}
private:
	StackOnly()
	{}

};
int main()
{
	StackOnly so1 = StackOnly::CreateObj();
	// 下面两种静态区和堆的位置都不能创建
	//static StackOnly so2;
	//StackOnly* pso3 = new StackOnly;
	return 0;
}

实际上,这种方法也没有彻底的封死,下面这种方式仍然可以在静态区创建:

int main()
{
	static StackOnly so2 = StackOnly::CreateObj();
	return 0;
}

image-20230709143846050

解决这个问题的方式:

这里设计到的强制类型转换,强制类型转换中间会生成一个临时对象,将这个临时对象拷贝给需要定义的对象,若把拷贝构造封住,那么不仅这个会报错,前面的也会报错,因为前者的赋值也是将返回的对象临时拷贝:

image-20230709144616655

因此,没有什么很好的办法去完全的封死。但硬要封死,即把拷贝构造封住,那就不要用 = 获取,而是直接调用,如下:

//请设计一个类,只能在栈上创建对象
class StackOnly
{
public:
	static StackOnly&& CreateObj()
	{
		return StackOnly();
	}

	void Print() const
	{
		cout << "StackOnly::Print()" << endl;
	}
private:
	StackOnly()
	{}
	StackOnly(const StackOnly&) = delete;

};
int main()
{
	/*StackOnly so1 = StackOnly::CreateObj();
	static StackOnly so2 = StackOnly::CreateObj();*/

	StackOnly::CreateObj().Print();
	const StackOnly& so4 = StackOnly::CreateObj();
	so4.Print();
	return 0;
}

目的就是防止拷贝。

ps,由于StackOnly()是局部对象,出了作用域被销毁,因此采用右值引用才可以传出。


方法二:封注operator newoperator delete

class StackOnly
{
public:
    StackOnly()
	{}
	static StackOnly CreateObj()
	{
		return StackOnly();
	}
	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
	
};

这种方式同样可以,因为在new对象的过程中,一定会存在operator new的步骤。但是这种方法只能封住堆上的,却无法封住静态的。

所以最好的方式就是用方式一。

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

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

final关键字,final修饰类,表示该类不能被继承。

class A  final
{
  // ....
};

五.单例模式

  • 单例模式:只能创建一个对象。

1.什么是设计模式?

设计模式是在软件工程中经过反复实践证明的一套解决问题的经验总结,用于解决常见的设计问题。以下是一些常见的设计模式:

  1. 创建型模式(Creational Patterns):

    • 工厂方法模式(Factory Method Pattern)
    • 抽象工厂模式(Abstract Factory Pattern)
    • 单例模式(Singleton Pattern)
    • 建造者模式(Builder Pattern)
    • 原型模式(Prototype Pattern)
  2. 结构型模式(Structural Patterns):

    • 适配器模式(Adapter Pattern)
    • 桥接模式(Bridge Pattern)
    • 组合模式(Composite Pattern)
    • 装饰者模式(Decorator Pattern)
    • 外观模式(Facade Pattern)
    • 享元模式(Flyweight Pattern)
    • 代理模式(Proxy Pattern)
  3. 行为型模式(Behavioral Patterns):

    • 观察者模式(Observer Pattern)
    • 状态模式(State Pattern)
    • 策略模式(Strategy Pattern)
    • 命令模式(Command Pattern)
    • 职责链模式(Chain of Responsibility Pattern)
    • 迭代器模式(Iterator Pattern)
    • 中介者模式(Mediator Pattern)
    • 备忘录模式(Memento Pattern)
    • 访问者模式(Visitor Pattern)
    • 模板方法模式(Template Method Pattern)
  4. 并发型模式(Concurrent Patterns):

    • 信号量模式(Semaphore Pattern)
    • 线程池模式(Thread Pool Pattern)
    • 读写锁模式(Read-Write Lock Pattern)
    • 生产者消费者模式(Producer-Consumer Pattern)

以上仅是一些常见的设计模式,实际上还有其他的设计模式。每个设计模式都有特定的应用场景和解决问题的方式。请注意,在使用设计模式时,应根据具体的需求和情况来选择适当的设计模式。

比如迭代器模式,把复杂的东西给封装好,使用时就可以避免接触复杂的底层结构。

比如配接器模式等等,也是这个意思。

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

2.单例模式

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

由于全局对象只能有一个,换句话说是获取这个对象,那就需要对构造函数进行操作。

单例模式有两种实现模式:饿汉模式、懒汉模式

饿汉模式:不管你将来用不用,程序启动时就创建一个唯一的实例对象。

饿汉模式的条件:main函数之前就初始化

设计饿汉模式的步骤:

  1. 将构造函数设成private,以及封死拷贝构造和重载赋值
  2. 定义成员变量,变量类型为static 类型名
  3. 在类外初始化这个单例的对象
  4. 添加其它成员方法
//单例模式的类:全局只有一个唯一对象
// 饿汉模式(main函数之前初始化)
// 缺点:1、单例对象初始化时对象太多,导致启动慢 
//		 2、多个单例类有初始化依赖关系,饿汉模式无法控制
class InfoSingleton
{
public:
	static InfoSingleton& GetInstance()
	{
		return _sins;
	}

	void Insert(string name, int salary)
	{
		_info[name] = salary;
	}

	void Print()
	{
		for (auto kv : _info)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
private:
	InfoSingleton()
	{}
	InfoSingleton(const InfoSingleton&) = delete;
	InfoSingleton& operator=(const InfoSingleton& info) = delete;
	map<string, int> _info;
	// ...
private:
	static InfoSingleton _sins;
};

InfoSingleton InfoSingleton::_sins;

int main()
{
	InfoSingleton::GetInstance().Insert("张三", 10000);
	InfoSingleton& infosl = InfoSingleton::GetInstance();
	infosl.Insert("李四", 12000);
	infosl.Insert("王五", 15000);
	infosl.Insert("赵六", 11000);

	infosl.Print();
	cout << endl;
	InfoSingleton::GetInstance().Insert("张三", 18000);
	infosl.Insert("李四", 12000);
	infosl.Insert("王五", 15000);
	infosl.Insert("赵六", 11000);
	infosl.Print();
	return 0;
}

image-20230709192832157

可见在调用时可以通过引用来简化代码量。

饿汉模式的缺点:

  1. 单例对象初始化数据太多,导致启动慢
  2. 多个单例类有初始化依赖关系,饿汉模式无法控制

假设有两个单例类A和B,分别代表数据库和文件系统,要求先初始化A,再初始化B,并且B会依赖A,那么此时饿汉模式就无法控制顺序。

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

懒汉模式

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

懒汉模式的条件:

  1. 对象在main函数之后才会创建,不会影响启动顺序
  2. 可以主动控制创建顺序

设计懒汉模式的步骤:(与饿汉模式基本相同)

  1. 将构造函数设成private,以及封死拷贝构造和重载赋值
  2. 定义成员变量,变量类型为static 类型名
  3. 在类外初始化这个单例的对象
  4. 添加其它成员方法

与饿汉模式的区别:

  1. 对象在main函数之后才会创建,不会影响启动顺序
  2. 可以主动控制创建顺序
  3. 将对象的创建改为在堆上创建
  4. 懒汉模式存在多个对象一起调用GetInstance的情况,存在线程安全的风险,可能new出来多个对象,因此需要加锁,需要新增一个锁的成员对象,并定义为static类型;饿汉模式一开始就一个对象,不用创建,所以不用锁。

注意:锁不能被拷贝,因此定义锁的成员变量时可以用指针(地址)或者引用的方式定义,C++采用地址的行为不常见,用引用更好。

加锁也是有讲究的,如果像这样的代码:

//多个对象一起调用GetInstance,存在线程安全的风险,可能new出来多个对象,因此需要加锁
static InfoSingleton& GetInstance()
{
    _pmtx.lock();
    if (_psins == nullptr)//避免对象new出来以后每次都加锁,提高性能
    {
        _psins = new InfoSingleton;
    }
    _pmtx.unlock();
    return *_psins;
}

由于这种方式每次都需要加锁,但实际上只有第一次创建对象才需要加锁,所以为了避免锁影响效率,使用双层if检查;此外,对于new,一旦抛异常,就需要捕获,此时可以使用try-catch,但这种写法不可行,通过之前智能指针的RAII思想,我们可以自己设定一个类:基于RAII思想的管理类,来防止锁的问题。

为什么try-catch不可行,因为还在加锁阶段,一旦进行捕获跳转,那么这把锁会一直锁住,为了避免出现这种情况,才使用RAII的思想。C++线程库中也有对应的库函数方法,但是这里仍然可以手撕一个。

//RAII的锁管理类
template<class Lock>
class LockGuard
{
public:
	LockGuard(Lock& lk)
		:_lk(lk)
	{
		lk.lock();
	}

	~LockGuard()
	{
		_lk.unlock();
	}
private:
	Lock& _lk;//成员变量用引用-->避免拷贝
};

//懒汉模式
//1、对象在main函数之后才会创建,不会影响启动顺序
//2、可以主动控制创建顺序

//问题:
class InfoSingleton
{
public:
	//多个对象一起调用GetInstance,存在线程安全的风险,可能new出来多个对象,因此需要加锁
	static InfoSingleton& GetInstance()
	{
		//第一次获取单例对象的时候创建对象
		//双检查加锁
		if (_psins == nullptr)//避免对象new出来以后每次都加锁,提高性能
		{
			// t1  t2
			LockGuard<mutex> mtx(_smtx);
			if (_psins == nullptr) //保证线程安全且只new一次
			{
				_psins = new InfoSingleton;
			}
		}

		return *_psins;
	}

	void Insert(string name, int salary)
	{
		_info[name] = salary;
	}

	void Print()
	{
		for (auto kv : _info)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
private:
	InfoSingleton()
	{}
	InfoSingleton(const InfoSingleton&) = delete;
	InfoSingleton& operator=(const InfoSingleton& info) = delete;
	map<string, int> _info;
	// ...
private:
	static InfoSingleton* _psins;
	static mutex _smtx;
};

InfoSingleton* InfoSingleton::_psins = nullptr;
mutex InfoSingleton::_smtx;

int main()
{
	InfoSingleton::GetInstance().Insert("张三", 10000);
	InfoSingleton& infosl = InfoSingleton::GetInstance();
	infosl.Insert("李四", 12000);
	infosl.Insert("王五", 15000);
	infosl.Insert("赵六", 11000);

	infosl.Print();
	cout << endl;
	InfoSingleton::GetInstance().Insert("张三", 18000);
	infosl.Insert("李四", 12000);
	infosl.Insert("王五", 15000);
	infosl.Insert("赵六", 11000);
	infosl.Print();
	return 0;
}

image-20230709203833667

库中也有对应的加锁方法:

static InfoSingleton& GetInstance()
{
    //第一次获取单例对象的时候创建对象
    //双检查加锁
    if (_psins == nullptr)//避免对象new出来以后每次都加锁,提高性能
    {
        // t1  t2
        //LockGuard<mutex> mtx(_smtx);
        std::lock_guard<mutex> lock(_smtx);//库中的方法
        if (_psins == nullptr) //保证线程安全且只new一次
        {
            _psins = new InfoSingleton;
        }
    }

    return *_psins;
}

new出来之后是否需要释放?

一般单例模式对象不需要考虑释放。单例模式的类的一个对象通常在整个程序运行期间都会使用,因此最后不delete也不会有问题,只要进程最终正常结束,对象的资源就会由OS自动释放。

什么时候单例模式的对象需要释放?

单例对象不用时,必须手动处理,一些资源需要保存。假设工资名单需要保存到文件里,要求系统结束之前将信息保存进去,此时就需要手动处理。所以,可以新增一个方法DelInstance(),是否需要调用取决于自己:

//懒汉模式
//1、对象在main函数之后才会创建,不会影响启动顺序
//2、可以主动控制创建顺序
class InfoSingleton
{
public:
	//多个对象一起调用GetInstance,存在线程安全的风险,可能new出来多个对象,因此需要加锁
	static InfoSingleton& GetInstance()
	{
		//第一次获取单例对象的时候创建对象
		//双检查加锁
		if (_psins == nullptr)//避免对象new出来以后每次都加锁,提高性能
		{
			// t1  t2
			//LockGuard<mutex> mtx(_smtx);
			std::lock_guard<mutex> lock(_smtx);
			if (_psins == nullptr) //保证线程安全且只new一次
			{
				_psins = new InfoSingleton;
			}
		}

		return *_psins;
	}

	//一般单例对象不需要考虑释放
	//单例对象不用时,必须手动处理,一些资源需要保存
	static void DelInstance()
	{
		//保存数据到文件
		// ...
		std::lock_guard<mutex> lock(_smtx);
		if (_psins)
		{
			delete _psins;
			_psins = nullptr;
		}
	}

	void Insert(string name, int salary)
	{
		_info[name] = salary;
	}

	void Print()
	{
		for (auto kv : _info)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
private:
	InfoSingleton()
	{}
	InfoSingleton(const InfoSingleton&) = delete;
	InfoSingleton& operator=(const InfoSingleton& info) = delete;
	map<string, int> _info;
	// ...
private:
	static InfoSingleton* _psins;
	static mutex _smtx;
};

InfoSingleton* InfoSingleton::_psins = nullptr;
mutex InfoSingleton::_smtx;

int main()
{
	InfoSingleton::GetInstance().Insert("张三", 10000);
	InfoSingleton& infosl = InfoSingleton::GetInstance();
	infosl.Insert("李四", 12000);
	infosl.Insert("王五", 15000);
	infosl.Insert("赵六", 11000);

	infosl.Print();
	cout << endl;
	InfoSingleton::GetInstance().Insert("张三", 18000);
	infosl.Insert("李四", 12000);
	infosl.Insert("王五", 15000);
	infosl.Insert("赵六", 11000);
	infosl.Print();

	InfoSingleton::DelInstance();//主动调用
	return 0;
}

如果忘记主动调用,同样会产生错误,因此仍需要设计一个能够自动回收的方式,这里采用新增一个内部类GC,利用RAII的思想,一旦忘主动回收,其在main函数结束时就会自动回收,此时就需要新增一个成员变量以及内部类:

注:内部类是外部类的友元

//懒汉模式
//1、对象在main函数之后才会创建,不会影响启动顺序
//2、可以主动控制创建顺序
class InfoSingleton
{
public:
	//多个对象一起调用GetInstance,存在线程安全的风险,可能new出来多个对象,因此需要加锁
	static InfoSingleton& GetInstance()
	{
		//第一次获取单例对象的时候创建对象
		//双检查加锁
		if (_psins == nullptr)//避免对象new出来以后每次都加锁,提高性能
		{
			// t1  t2
			//LockGuard<mutex> mtx(_smtx);
			std::lock_guard<mutex> lock(_smtx);
			if (_psins == nullptr) //保证线程安全且只new一次
			{
				_psins = new InfoSingleton;
			}
		}

		return *_psins;
	}

	//一般单例对象不需要考虑释放
	//单例对象不用时,必须手动处理,一些资源需要保存
	static void DelInstance()
	{
		//保存数据到文件
		// ...
		std::lock_guard<mutex> lock(_smtx);
		if (_psins)
		{
			delete _psins;
			_psins = nullptr;
		}
	}
	//忘记调用DelInstance(),自动回收
	class GC
	{
	public:
		~GC()
		{
			if (_psins)
			{
				cout << " ~GC()" << endl;
				DelInstance();
			}
		}
	};

	void Insert(string name, int salary)
	{
		_info[name] = salary;
	}

	void Print()
	{
		for (auto kv : _info)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
private:
	InfoSingleton()
	{}
	InfoSingleton(const InfoSingleton&) = delete;
	InfoSingleton& operator=(const InfoSingleton& info) = delete;
	map<string, int> _info;
	// ...
private:
	static InfoSingleton* _psins;
	static mutex _smtx;
	static GC _gc;
};

InfoSingleton* InfoSingleton::_psins = nullptr;
mutex InfoSingleton::_smtx;
InfoSingleton::GC InfoSingleton::_gc;

int main()
{
	InfoSingleton::GetInstance().Insert("张三", 10000);
	InfoSingleton& infosl = InfoSingleton::GetInstance();
	infosl.Insert("李四", 12000);
	infosl.Insert("王五", 15000);
	infosl.Insert("赵六", 11000);

	infosl.Print();
	cout << endl;
	InfoSingleton::GetInstance().Insert("张三", 18000);
	infosl.Insert("李四", 12000);
	infosl.Insert("王五", 15000);
	infosl.Insert("赵六", 11000);
	infosl.Print();
	return 0;
}

image-20230709205650451

因此,可以主动回收,也可以在程序结束时自动回收,但单例对象一般不需要回收。

实现懒汉的另一种方式:

懒汉模式的实现还有另一种方式,直接static,也是只创建一次对象,所以下面的方式也可以,但不是一种通用的方式。

//是懒汉:因为静态的局部变量是在main函数之后才创建初始化的:局部静态变量的初始化只初始化一次。
//C++11之前,不能保证sinst的初始化是线程安全的。
//C++11之后,可以。
class InfoSingleton
{
public:
	//多个对象一起调用GetInstance,存在线程安全的风险,可能new出来多个对象,因此需要加锁
	static InfoSingleton& GetInstance()
	{
		static InfoSingleton sinst;
		return sinst;
	}


	void Insert(string name, int salary)
	{
		_info[name] = salary;
	}

	void Print()
	{
		for (auto kv : _info)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
private:
	InfoSingleton()
	{
		cout << "InfoSingleton()" << endl;
	}
	InfoSingleton(const InfoSingleton&) = delete;
	InfoSingleton& operator=(const InfoSingleton& info) = delete;
	map<string, int> _info;
	// ...
private:
};

int main()
{
	InfoSingleton::GetInstance().Insert("张三", 10000);
	InfoSingleton& infosl = InfoSingleton::GetInstance();
	infosl.Insert("李四", 12000);
	infosl.Insert("王五", 15000);
	infosl.Insert("赵六", 11000);

	infosl.Print();
	cout << endl;
	InfoSingleton::GetInstance().Insert("张三", 18000);
	infosl.Insert("李四", 12000);
	infosl.Insert("王五", 15000);
	infosl.Insert("赵六", 11000);
	infosl.Print();
	return 0;
}

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

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

相关文章

Flink使用总结

本文主要是为Flink的java客户端使用和flink-sql使用的大致介绍&#xff0c;具体使用查看文档页面。 java client使用 文档 Apache Flink Documentation | Apache Flink 数据处理模型 maven依赖 <?xml version"1.0" encoding"UTF-8"?> <pro…

【YOLOv8-Seg】实战二:LabVIEW+OpenVINO加速YOLOv8-seg实例分割

‍‍&#x1f3e1;博客主页&#xff1a; virobotics的CSDN博客&#xff1a;LabVIEW深度学习、人工智能博主 &#x1f384;所属专栏&#xff1a;『LabVIEW深度学习实战』 &#x1f37b;上期文章&#xff1a; 【YOLOv8-seg】实战一&#xff1a;手把手教你使用YOLOv8实现实例分割 …

【数据分析 - 基础入门之NumPy⑥】- NumPy案例巩固强化

文章目录 前言一、NumPy基础训练1.1 创建一个长度为10的一维全为0的ndarray对象&#xff0c;并让第5个元素为11.2 创建一个元素为从10到49的ndarray对象1.3 将第2题的所有元素位置反转1.4 创建一个10*10的ndarray对象并打印最大最小元素1.5 创建一个10*10的ndarray对象&#xf…

程序设计相关概念

计算机概念 计算机是根据指令操作数据的设备。具有功能性和可编程性的特点。 功能性&#xff1a;对数据的操作&#xff0c;表现为数据计算、输入输出处理和结果存储等。 可编程性&#xff1a;根据一系列指令自动地、可预测地、准确地完成操作者的意图。 计算机的发展 计算机…

【LVS负载均衡集群】

文章目录 一.什么是集群1.集群的含义 二.集群使用在那个场景三.集群的分类1.负载均衡器集群2.高可用集群3.高性能运算集群 四.负载集群的架构1.第一层&#xff0c;负载调度器2.第二层&#xff0c;服务器池3.第三层&#xff0c;共享存储 五.负载均衡集群的工作模式1.地址转换 &a…

Unity减少等待快速进入运行

我们平时播放时一旦修改了c#的脚本总要加载进行等待&#xff0c;网上也缺乏如何设置&#xff0c;以及为什么&#xff1f;这样做可以达到这样的效果。 ------如何设置&#xff1f;【默认并不会开启】 Edit->Project Settings->Editor->Enter Player Mode Options 这样…

企业为什么要做自动化测试?如何成功实施自动化测试?

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 企业为什么需要自…

【LeetCode热题100】打卡第33天:环形链表LRU缓存

文章目录 【LeetCode热题100】打卡第33天&#xff1a;环形链表&LRU缓存⛅前言 环形链表&#x1f512;题目&#x1f511;题解 LRU缓存&#x1f512;题目&#x1f511;题解 【LeetCode热题100】打卡第33天&#xff1a;环形链表&LRU缓存 ⛅前言 大家好&#xff0c;我是知…

C++ 实现生产者消费者模型 (线程同步、互斥锁、条件变量锁)详细注释

代码结构 任务&#xff1a;这里用一个int类型的taskNumber代替任务任务队列类&#xff1a;封装了任务队列&#xff0c;存&#xff0c;取等操作。生产者工作函数&#xff1a;生产者执行的函数&#xff0c;向任务队列中添加任务&#xff0c;每个生产者生产3个任务消费者工作函数…

脱离产品怎么可能完成测试?

“脱离应用场景谈技术毫无意义”。其实很多东西都是如此&#xff0c;这个有点哲理的味道了。我们是做engineering&#xff0c;软件工程也是工程&#xff0c;工程的特点就是不能停留在理论和方法&#xff0c;最后要做出东西来&#xff0c;软的也好&#xff0c;硬的也好。 人有…

爬虫反反爬

目录 为什么要反爬&#xff1f; 经常被反爬的主要人群 常见的反爬策略 通过headers字段来反爬 通过headers中的User-Agent字段来反爬 通过referer字段或者是其他字段来反爬 通过cookie来反爬 通过请求参数来反爬 通过从html静态文件中获取请求数据(github登录数据) 通…

【Go】vscode 安装go环境gopls失败

项目场景&#xff1a; 想要在VSCode安装go环境&#xff0c;但是gopls下载失败&#xff0c;导致vscode无法使用language server 问题描述 自动下载失败&#xff0c;在打开命令面板&#xff08;CtrlshiftP&#xff09;之后&#xff0c;输入go install/update 下载也失败 $ g…

并发编程 - Event Bus 设计模式

文章目录 Pre设计CodeBus接口自定义注解 Subscribe同步EventBus异步EventBusSubscriber注册表RegistryEvent广播Dispatcher 测试简单的Subscriber同步Event Bus异步Event Bus 小结 Pre 我们在日常的工作中&#xff0c;都会使用到MQ这种组件&#xff0c; 某subscriber在消息中间…

PillarNext论文解读

这篇文章是轻舟智航23年的一篇论文&#xff0c;是对pillarNet进行改进。 改进方面&#xff1a; 1.训练更长的时间在检测头增加IOU预测score&#xff0c;这个iou分数预测不太清楚&#xff0c;不知道是不是iouloss 2.扩大感受野&#xff0c;包括Neck部分使用FPN或者BiFPN.使用…

3.zabbix操作二

文章目录 zabbix操作二部署zabbix代理服务器安装zabbix_proxy安装数据库配置代理服务器配置文件web端添加agent代理并连接主机 部署zabbix高可用群集zabbix监控Windows系统zabbix监控java应用zabbix监控SNMP zabbix操作二 部署zabbix代理服务器 分布式监控的作用&#xff1a;…

Flink web UI配置账号密码,权限控制

由于Flink自带的web UI界面没有账号密码&#xff0c;需要通过nginx实现该效果。 1.安装httpd-tools工具 yum install httpd-tools -y 2.生成用户名密码文件 htpasswd -c /usr/local/nginx/conf/flinkuser username passwd flinkuser&#xff1a;为生成的用户名密码文件名称 …

Apache Doris (二十一) :Doris Rollup物化索引创建与操作

目录 1. 创建测试表 2. 创建Rollup物化索引表 3. 查看Rollup物化索引表 4. 删除Rollup物化索引表 5. 验证Rollup物化索引使用 进入正文之前&#xff0c;欢迎订阅专题、对博文点赞、评论、收藏&#xff0c;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; 宝子们点…

open3d 通过vscode+ssh连接远程服务器将可视化界面本地显示

当使用远程服务器时&#xff0c;我们希望能像在本地一样写完代码后能立刻出现一些gui窗口。但是目前网络上的资料都不能很好的解决这个问题。本文尝试尽可能简短地解决这个问题。 步骤 1、在服务器上安装open3d 已经非常简化了&#xff0c;可以使用一行代码完成 pip3 insta…

【Java从入门到大牛】方法详解

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Java从入门到大牛 &#x1f320; 首发时间&#xff1a;2023年7月9日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e…

【计算机组成与体系结构Ⅰ】实验7 IP核的使用、D触发器

一、实验目的 1&#xff1a;学会设计用IP核和原理图的方式设计电路&#xff0c;完成涉及1位数据的2选1多路选择器。 2&#xff1a;设计带异步置零和写使能端的D触发器。 二、实验环境 软件&#xff1a;Vivado 2015.4操作系统&#xff1a;Windows 10 三、实验内容 2.2.1 多路…