从C语言到C++_37(特殊类设计和C++类型转换)单例模式

news2024/11/29 8:41:20

目录

1. 特殊类设计

1.1 不能被拷贝的类

1.2 只能在堆上创建的类

1.3 只能在栈上创建的类

1.4 不能被继承的类

1.5 只能创建一个对象的类(单例模式)(重点)

1.5.1 饿汉模式

1.5.2 懒汉模式

2. 类型转换

2.1 static_cast

2.2 reinterpret_cast

2.3 const_cast

2.4 dynamic_cast

3. RTTI(了解)和类型转换常见面试题

本篇完。


1. 特殊类设计

普通类的设计基础上,提出一些限制条件设计的类就是特殊类。

1.1 不能被拷贝的类

拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载。

因此禁止拷贝的类只需要让该类禁止调用拷贝构造函数和赋值运算符重载函数即可。

C++98中的方式:将拷贝构造函数和赋值运算符重载函数只声明不定义,并设置成私有:

class CopyBan
{
public:
	CopyBan()
	{}

private:
	CopyBan(const CopyBan& cb); // 拷贝构造函数声明
	CopyBan& operator=(const CopyBan& cb); // 赋值运算符重载声明
};

原因:

① 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
② 只声明不定义:在调用拷贝构造和赋值运算符重载函数的时候,由于没有定义就会产生链接错误,在编译阶段就报错。不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

如上图代码,在对这个特殊类进行拷贝和赋值的时候,因为这两个成员函数私有而无法调用。

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

class CopyBan
{
public:
	CopyBan()
	{}

private:
	CopyBan(const CopyBan&) = delete;
	CopyBan& operator=(const CopyBan&) = delete;
};

使用C++11中的给delete新赋予的意义来禁止生产拷贝构造和赋值运算符重载函数。

此时编译器也不会自动生成默认的拷贝构造函数和赋值运算符重载函数。

1.2 只能在堆上创建的类

正常创建类对象时,会在栈上创建,并且自动调用构造函数来初始化。

只能在创建在堆上时,就需要让该对象只能通过new来创建,并且调用构造函数来初始化。

class HeapOnly
{
public:
	static HeapOnly* CreateObject()
	{
		return new HeapOnly;
	}
	HeapOnly(const HeapOnly& hp) = delete;//禁止拷贝
	HeapOnly& operator = (const HeapOnly& hp) = delete; // 禁止拷贝

private:
	HeapOnly() //构造函数
	{}
};

定义一个静态成员函数,在该函数内部new一个HeapOnly对象。将构造函数私有,并且禁止生成拷贝构造函数。

使用静态成员函数new一个HeapOnly对象的原因:
非静态成员函数在调用的时候,必须使用点(.)操作符来调用,这一步是为了传this指针。

这样的前提是先有一个HeapOnly对象,但是构造函数设置成了私有,就无法创建这样一个对象。而静态成员函数的调用不用传this指针,也就不用必须有HeapOnly对象,只需要类域::静态成员函数即可。否则就面临了先有鸡还是先有蛋的问题:非静态成员函数调用需要传对象,此时对象又只能调用非静态成员函数创建。静态成员函数属于HeapOnly域内,所以在new一个对象的时候,可以调用私有的构造函数。

禁止调用拷贝构造函数,并且私有化的原因:
这样的目的是为了禁止拷贝,防止使用堆区上的HeapOnly对象在栈区上拷贝,如下面代码:

 而禁止了拷贝构造就杜绝了这一行为,从而保证了HeapOnly对象只能在堆上创建。

1.3 只能在栈上创建的类

只能主要做到不能在堆上创建类对象。
new一个对象的时候,会调用该类的operator new(size_t size)函数,在释放资源的时候又会调用该类的operator delete(void* p)函数。

	StackOnly1 st1;
	static StackOnly1 st2;
	StackOnly1* st3 = new StackOnly1;

此时就是想办法禁止下面两行代码的使用了

方法1:通过一个静态成员函数在栈区上创建一个类对象,并且将默认构造函数私有化。

class StackOnly1
{
public:
	static StackOnly1 CreateObject()
	{
		return StackOnly1();
	}

private:
	StackOnly1()
	{}
};

 

 此时new一个对象的时候,由于默认构造函数私有无法调用,所以报错。

但是此时我可以调用拷贝构造来在栈上创建:

如果加上防拷贝,第一种创建都创建不了了:

所以并没有很好的解决方式。

方法2:防止在堆上创建类对象就是要禁止调用这两个函数。

class StackOnly2
{
public:
	StackOnly2() //构造函数
	{}
	void* operator new(size_t size) = delete; // 禁止调用new
	void operator delete(void* p) = delete; // 禁止调用delete
};

使用delete来禁止这两个函数的调用,那么在new一个对象的时候,就会产生编译错误,从而无法在堆区上创建类对象。此时在堆上创建对象时就会报错,尝试引用已经删除的函数。

这俩种设计方法共同的一个漏洞,类对象可以在静态区(数据段)上创建:

所以设计只能在栈上创建的类并没有什么很好的方案。

设计特殊类的核心点:只能通过静态成员函数来创建类,封掉其他所有创建方式

1.4 不能被继承的类

C++98的方式:基类的构造函数私有,派生类在创建对象的时候,无法调用基类的构造函数。

class NonInherit // 基类
{
private:
	NonInherit() //基类构造函数私有
	{}
};

class B : public NonInherit // 派生类
{};

C++11的方式:使用C++11中的 final 关键字修饰基类,这个基类就无法继承。

此时不实例化也报错了。

1.5 只能创建一个对象的类(单例模式)(重点)

单例模式是一种设计模式。

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

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

单例模式:一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

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

1.5.1 饿汉模式

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

class Singleton
{
public:
	static Singleton* GetInstance() // 获取单例对象接口
	{
		return &m_instance;
	}

private:
	Singleton() // 构造函数私有化
	{}
	Singleton(const Singleton& s) = delete; // 禁止使用拷贝构造
	Singleton& operator=(const Singleton& s) = delete; // 禁止使用赋值运算符重载

	//保证单例对象在静态区且只有一个
	static Singleton m_instance;//单例对象
};

Singleton Singleton::m_instance;//在程序入口之前就完成单例对象初始化

int main()
{
	Singleton* s = Singleton::GetInstance();//获取单例对象

	return 0;
}

静态成员变量只能在类域外进行定义初始化。所以在main函数之前就将单例对象定义初始化,此时该单例对象创建在静态区上,而且仅有一个,后面就无法再创建。

想要获取该单例对象只能通过静态成员函数GetInstance()来获取。

静态成员函数可以直接访问静态成员变量m_instance。

为什么称之为饿汉模式呢?不管将来会不会使用到这个单例对象,但是在程序一启动还没有进入main函数之前就创建一个唯一的实例对象。就像一个饿汉一样,一上来就先吃(创建单例对象)。

缺点:

  • 可能会导致进程启动较慢,如过实例对象很复杂,在创建实例对象时就会花费很多时间。
  • 实例顺序不确定,如果有多个单例对象,并且对象之间存在互相依赖关系,由于对象的实例对象不确定(和代码顺序无关,由编译器决定),此时就会发生错误。

优点:简单、没有线程安全问题。

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

1.5.2 懒汉模式

懒汉模式:

class Singleton
{
public:
	static Singleton* GetInstance()
	{
		// 如果单例对象没有创建则在堆区创建
		if (m_pInstance == nullptr)
		{
			m_pInstance = new Singleton;
		}

		return m_pInstance;
	}

private:
	Singleton() // 构造函数
	{}
	Singleton(const Singleton& s) = delete; // 禁止拷贝
	Singleton& operator=(const Singleton& s) = delete; // 禁止赋值

	// 静态单例对象指针
	static Singleton* m_pInstance; // 单例对象指针
};

Singleton* Singleton::m_pInstance = nullptr; // 初始化为空

int main()
{
	Singleton* ps = Singleton::GetInstance();//获取单例对象

	return 0;
}

同样将构造函数私有,拷贝构造和赋值运算符重载函数禁止调用,用来保证单例模式的唯一性。

增加静态单例对象指针成员变量。

在类外实例化静态指针变量的时候,并没有创建单例对象,而是将其初始化为空。

在获取单例对象的时候,如果是第一次使用,那么就会在堆区上new一个单例对象,并且将所在地址赋值给静态的成员指针变量。

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

优点:

① 第一次使用单例对象时才创建对象,进程启动过程中无负载。

② 多个互相依赖的单例可以控制启动顺序(通过代码顺序)。

缺点:

① 相对复杂。(线程安全问题没讲,后面学了一半Linux再讲)

② 线程安全问题要处理好

单例对象释放问题:

① 一般情况下,单例对象不需要释放的。因为一般整个程序运行期间都可能会用它。

单例对象在进程正常结束后,也会资源释放。

② 有些特殊场景需要释放,比如单例对象析构时,要进行一些持久化(往文件、数据库写)操作。

2. 类型转换

在C语言中,如果赋值运算符(=)两边的类型不同,或者形参和实参类型不匹配,或者返回值类型和接收值类型不一致,就需要发生类型转换。

C语言中有两种类型转换:

  • 隐式类型转换:编译器在编译阶段自动进行,能转就转,不能转就编译失败。
  • 显式类型转换:需要用户自己处理。

double d = i是发生了隐式类型转换,将整形转换成了double类型。
隐式类型转换只发生在相近类型,比如整形家族直接,或者这些int,double等表示数值直接的变量类型。

int address = (int)p是发生了显式类型转换,将int * 类型的变量转换为int类型。
显式类型需要用户自己维护,在两种类型没有任何关系的时候需要进行显式类型转换,比如将指针类型转换成普通类型等等。

C语言的类型转换存在缺陷:

隐式类型转换有些情况下会出现问题,比如数据精度发生丢失(整形提升等)。
显式类型转换将所有情况混合在一起,代码不够清晰。

所以C++提出了自己的类型转换风格,但是仍然可以使用C语言的转换风格,因为要兼容C语言。

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:

static_cast、reinterpret_cast、const_cast、dynamic_cast。下面我们一一了解。

2.1 static_cast

C语言的隐式类型转换(两个相近的类型转换)在C++中就可以使用static_cast来转换,但是不能用于两个不相关的类型进行转换。

int main()
{
	double a = 7.14;
	int b = static_cast<int>(a);

	cout << a << " " << b << endl;

	return 0;
}

 double类型转int类型,在C语言中是隐式类型转换,在C++中为了更加明确使用了static_cast。

static_cast后的<>里放要转换的类型,()里放被转换的类型。
如果将static_cast看成是类模板,<int>就是在实例化,(d)就是在拷贝构造,siatic_cast<int>(d)就是在用d构建一个匿名对象。

2.2 reinterpret_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释用于将一种类型转换为另一种不同的类型。C语言的显式类型转换在C++中就可以reinterpret_cast。

int main()
{
	int a = 7;
	int* pa = &a;
	int address = reinterpret_cast<int>(pa);

	cout << a << " " << pa << " " << address << endl;

	return 0;
}

int*类型转换为int类型,在C语言中是显式类型转换,在C++中为了不混乱使用了reinterpret_cast。

这里如果使用static_cast进行类型转换的话会报错,必须使用reinterpret_cast:

2.1反过来用也会报错:

2.3 const_cast

const_cast最常用的用途就是删除变量的const属性,方便赋值:

int main()
{
	const int a = 2;
	int* p = const_cast<int*>(&a);
	*p = 3;
	cout << a << " " << *p << endl;

	return 0;
}

 变量a原本是const属性的,不能被修改,使用了const_cast以后去除了常量属性,可以修改了,如*p = 3。

F11:

 在调试窗口中可以看到,成功修改了原本是const属性的变量a。

为什么a在调试窗口看到的是3,打印出来的是2?这就涉及到了以前提到的汇编里的寄存器的内容

此时代码已经被优化了,不同编译器的优化不一样,有些编译器(VS)会把a = 2放到寄存器(认为const类型的变量不会被修改,每次去内存取的话很慢),那么读的时候去寄存器取就好了,打印的时候不会去内存取,所以打印出来的是3,监视窗口去内存取的,是2。

如果加上一个volatile关键字就告诉编译器不要优化:

int main()
{
	//const int a = 2;
	//int* p = const_cast<int*>(&a);
	//*p = 3;
	//cout << a << " " << *p << endl; // 2 3
	volatile const int a = 2;
	int* p = const_cast<int*>(&a);
	*p = 3;
	cout << a << " " << *p << endl; // 3 3

	return 0;
}

可以用C语言的强制类型转化:

 这里用reinterpret_cast就不行:

const_cast更多的是一种警示,表示去除了const属性,要谨慎操作。

已经讲了三种C++的类型转化,应该也知道了是不能混着用的,

static_cast对标的是C语言的隐式类型转化reinterpret_cast和const_cast对标的都是C语言的强制类型转化,增加就是为了建议你规范的用。

下面的dynamic_cast是C++特有的。

2.4 dynamic_cast

dynamic_cast是C++特有的,因为dynamic_cast设计到继承和多态的内容。

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)

向上转换:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转换:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)

首先要知道:父类对象无论如何都是不允许转换成子类对象的

class A // 父类
{
public:
	virtual void f() // 父类必须含有虚函数
	{}

	int _a = 1;
};

class B : public A // 子类
{
public:
	int _b = 2;
};

int main()
{
	A aa;
	// 父类对象无论如何都是不允许转换成子类对象的
	B bb = dynamic_cast<B>(aa);
	B bb = (B)aa;
	B bb;

	return 0;
}

但是父类的指针/引用是允许转换成子类指针/引用的,但C语言这样的场景就会报错:

void Func(A* pa)
{
	B* bptr = (B*)pa;
	cout << bptr << endl;

	bptr->_a = 6;
	bptr->_b = 7;
}

int main()
{
	A aa;
	B bb;
	Func(&aa);

	return 0;
}

class A是父类,class B是子类,父类中有成员变量int _a,子类中有成员变量_b。

在main函数中,传父类指针&aa给函数,在函数中将A* pa父类指针接收该值,然后将其强转为子类指针B*,使用子类指针访问子类成员,bptr->_b = 7发生运行时错误。

形参A* pa是父类指针,接收的也是父类指针,所以强转成子类指针后访问子类成员_b会发生越界。
如果传的是子类指针就不会报错,因为即使形参是父类指针,强转成子类以后并不会越界。

C++使用dynamic_cast将父类指针强转为子类指针:

void Func(A* pa)
{
	B* bptr = dynamic_cast<B*>(pa);
	cout << bptr << endl;

	bptr->_a = 6;
	bptr->_b = 7;
}

int main()
{
	A aa;
	B bb;
	Func(&aa);

	return 0;
}

 传父类指针,然后强转为子类指针后,打印出来的结构是nullptr,表示该次转换不能进行。

 传子类指针:

void Func(A* pa)
{
	B* bptr = dynamic_cast<B*>(pa);
	cout << bptr << endl;

	bptr->_a = 6;
	bptr->_b = 7;
}

int main()
{
	A aa;
	B bb;
	// Func(&aa);
	Func(&bb);

	return 0;
}

传子类指针,形参的父类指针接收后再强转为子类,打印出来的结构是强转后的地址,表示该次强转可以成功。

注意:

  • dynamic_cast只能用于父类含有虚函数的类。
  • dynamic_cast会先检查是否能转换成功,能成功则转换并返回正确的地址,不能则返回nullptr。
  • dynamic_cast是安全的,直接使用C语言的转换方式是不安全的(因为有越界风险)。

再看一段代码:

class A1
{
public:
	virtual void f(){}
public:
	int _a1 = 0;
};

class A2
{
public:
	virtual void f(){}
public:
	int _a2 = 0;
};

class B : public A1, public A2
{
public:
	int _b = 1;
};

int main()
{
	B bb;
	A1* ptr1 = &bb;
	A2* ptr2 = &bb;
	cout << ptr1 << endl;
	cout << ptr2 << endl << endl;

	B* pb1 = (B*)ptr1;
	B* pb2 = (B*)ptr2;
	cout << pb1 << endl;
	cout << pb2 << endl << endl;

	B* pb3 = dynamic_cast<B*>(ptr1);
	B* pb4 = dynamic_cast<B*>(ptr2);
	cout << pb3 << endl;
	cout << pb4 << endl << endl;

	return 0;
}

结果可能和想的不一样,此时C++中dynamic_cast和C语言的强转就差了父类必须有虚函数:

class A1
{
public:
	//virtual void f(){}
public:
	int _a1 = 0;
};

class A2
{
public:
	//virtual void f(){}
public:
	int _a2 = 0;
};

class B : public A1, public A2
{
public:
	int _b = 1;
};

int main()
{
	B bb;
	A1* ptr1 = &bb;
	A2* ptr2 = &bb;
	cout << ptr1 << endl;
	cout << ptr2 << endl << endl;

	B* pb1 = (B*)ptr1;
	B* pb2 = (B*)ptr2;
	cout << pb1 << endl;
	cout << pb2 << endl << endl;

	B* pb3 = dynamic_cast<B*>(ptr1);
	B* pb4 = dynamic_cast<B*>(ptr2);
	cout << pb3 << endl;
	cout << pb4 << endl << endl;

	return 0;
}

屏蔽掉下面代码:

C++中的类型转换,尤其是前两种static_cast和reinterpret_cast是建议用法,可以采用也可以不采用。const_cast是一种新用法,但是存在风险,dynamic_cast是一种安全的类型转换。

3. RTTI(了解)和类型转换常见面试题

RTTI:Run - time Type identification的简称,即:运行时类型识别
C++通过以下方式来支持RTTI:
1. typeid运算符
2. dynamic_cast运算符
3. decltype

常见面试题
1. C++中的4中类型转化分别是:_________、_________、_________、_________。
2. 说说4中类型转化的应用场景。


① static_cast,命名上理解是静态类型转换
使用场景:
用于类层次结构中基类和派生类之间指针或引用的转换

注意: 上行转换(派生类 -> 基类)是安全的;

下行转换(基类 -> 派生类)由于没有动态类型检查,所以是不安全的。

用于基本数据类型之间的转换,如把int转换为char,这种带来安全性问题由程序员来保证

使用特点:
主要执行非多态的转换操作,用于代替C中通常的转换操作

隐式转换都建议使用static_cast进行标明和替换

② const_cast,字面上理解就是去const属性
使用场景:
常量指针转换为非常量指针,并且仍然指向原来的对象

常量引用被转换为非常量引用,并且仍然指向原来的对象

使用特点:
cosnt_cast是四种类型转换符中唯一可以对常量进行操作的转换符

去除常量性是一个危险的动作,尽量避免使用

③ reinterpreter_cast,仅仅重新解释类型,但没有进行二进制的转换
使用场景:
不到万不得已,不用使用这个转换符,高危操作

使用特点:
reinterpret_cast可以将整型转换为指针,也可以把指针转换为数组

reinterpret_cast可以在指针和引用里进行肆无忌惮的转换

④ dynamic_cast,命名上理解是动态类型转换
使用场景:
只有在派生类之间转换时才使用dynamic_cast,type-id必须是类指针,类引用或者void

使用特点:
基类必须要有虚函数

对于下行转换,dynamic_cast是安全的(当类型不一致时,转换过来的是空指针),而static_cast是不安全的(当类型不一致时,转换过来的是错误意义的指针,可能造成踩内存,非法访问等各种问题)

总结:
去const属性用const_cast

基本类型转换用static_cast

不同类型的指针类型转换用reinterpreter_cast

多态类之间的类型转换用daynamic_cast

本篇完。

特殊类的设计中,要掌握好一点,就是只能通过一个接口来获取类,其他的方式不允许,让成员函数或私有或禁掉就可以。特别是单例模式,经常要用到。

对于类型转换,除了dynamic_cast是在多态转换中必须使用外,其他三种方式建议使用,可以增加代码的规范性。

下一篇:从C语言到C++_38(C++的IO流+空间适配器)STL六大组件联系。

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

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

相关文章

算法笔记——路径问题

在引入介绍如何写一个算法的时候&#xff0c;我们先引入一个题作为例子 1137. 第 N 个泰波那契数 - 力扣&#xff08;LeetCode&#xff09; 作为刚开始学习算法的我们&#xff0c;看到这个题目的时候&#xff0c;应该想好以下的问题&#xff1a; 1.状态表示 我们要用什么来表…

关于大模型参数微调的不同方法

Adapter Tuning 适配器模块&#xff08;Adapter Moudle&#xff09;可以生成一个紧凑且可扩展的模型&#xff1b;每个任务只需要添加少量可训练参数&#xff0c;并且可以在不重新访问之前任务的情况下添加新任务。原始网络的参数保持不变&#xff0c;实现了高度的参数共享 Pa…

android framework之Applicataion启动流程分析(三)

现在再回顾一下Application的启动流程&#xff0c;总的来说&#xff0c;虽然进程的发起是由ATMS服务发起的&#xff0c;但是进程的启动还是由AMS负责&#xff0c;所以需要调用AMS的startProcess()接口完成进程启动流程&#xff0c;AMS要处理的事情很多&#xff0c;它将事务交给…

代码随想录训练营第四十三天|1049. 最后一块石头的重量 II、 494. 目标和、 474.一和零

1049. 最后一块石头的重量 II 力扣题目链接(opens new window) 题目难度&#xff1a;中等 有一堆石头&#xff0c;每块石头的重量都是正整数。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x &l…

小程序快速备案助手代备案小程序开发

小程序快速备案助手代备案小程序开发 用户注册与登录&#xff1a;用户可以通过手机号或其他方式进行注册和登录&#xff0c;以便进行备案相关操作。备案信息填写&#xff1a;用户可以填写小程序的备案信息&#xff0c;包括小程序名称、小程序服务类目、域名等。备案材料上传&a…

GA遗传算法

储备知识 GA算法主要解决数学模型中最优化的搜索算法&#xff0c;是进化算法中的一种&#xff0c;基因算法借鉴了自然界基因的遗传的主要现象&#xff0c;分别为遗传&#xff0c;变异&#xff0c;自然选择&#xff0c;杂交等。 GA算法参数 GA算法的参数如下所示。 种群规模…

c++ vs2019 cpp20规范的STL库的map与multimap源码分析

map就是一个红黑树。 标准平衡二叉树&#xff0c;要求左右子树的高度差不超过1 。红黑树只要求左右子树的高度差不超过一倍即可。兼顾了树平衡与效率。避免了AVL树的频繁调整树平衡。 b站 的“可雷曼土”大师&#xff0c;讲红黑树的理论讲的很透彻&#xff0c;再结合看代码&…

va_list使用及两个注意项(可能导致崩溃和少1个字符)

两个注意项&#xff1a; 1、linux平台上vsnprintf会破坏va_list变量&#xff0c;需要重新调用va_start&#xff0c;否则可能访问错位崩溃。 2、vsnprintf会留一个字节补0结束&#xff0c;但返回值不包含&#xff0c;所以必须判断返回值小于分配的空间。 具体代码分析&#xff1…

【包过滤防火墙——firewalld动态防火墙】的简单使用

文章目录 firewald与iptables区别firewalld九个区域firewalld配置方法firewalld参数和命令firewalld两种模式firewalld使用实验 firewalld不要与iptables混用 firewald与iptables区别 iptables 主要是基于接口&#xff0c;来设置规则&#xff0c;从而判断网络的安全性。firewa…

卡特兰数和算法

在组合数学中&#xff0c;卡特兰数是一系列自然数&#xff0c;出现在各种组合计数问题中&#xff0c;通常涉及递归定义的对象。它们以比利时数学家尤金查尔斯卡特兰&#xff08;Eugne Charles Catalan&#xff09;的名字命名。 卡特兰数序列是1, 1, 2, 5, 14, 42......&#xf…

AAC之处理码流分析工具(三十六)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…

websocket基础

下面就以代码来进行说明 1&#xff0c;先导入websocket依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency> 2.编写websocket相关bean管理配置 Config…

牛客网刷题

牛客网刷题-C&C 2023年9月3日15:58:392023年9月3日16:37:01 2023年9月3日15:58:39 2023年9月3日16:37:01 整型常量和实型常量的区别

一、了解[mysql]索引底层结构和算法

目录 一、索引1.索引的本质2.mysql的索引结构 二、存储引擎1.MyISAM2.InnoDB3.为什么建议InnoDB表要建立主键并且推荐int类型自增&#xff1f;4.innodb的主键索引和非主键索引&#xff08;二级索引&#xff09;区别5.联合索引 一、索引 1.索引的本质 索引:帮助mysql高效获取数…

01-为什么阿里巴巴强制要求使用包装类型定义属性?【Java面试题总结】

1.为什么阿里巴巴强制要求使用包装类型定义属性&#xff1f; 我认为主要有以下几个方面的原因&#xff1a; 默认值问题&#xff1a;使用基本数据类型定义属性时&#xff0c;如果没有给属性赋初始值&#xff0c;会使用默认值&#xff08;如 int 的默认值为 0&#xff09;&…

C++:初识类与this指针

文章目录 前言一、类类的定义和实例化类的访问限定符类的作用域计算类的大小 二、类的成员函数的this指针总结 个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》 前言 一、类 类的定义和实例化 注意类定义结束时后面分号( ; )不能省略。 类…

基于粒子群算法的考虑需求响应的风-光-柴-储容量优化配置

目录 文章摘要&#xff1a; 研究背景&#xff1a; 考虑柔性负荷的风、光、柴、储微电网模型&#xff1a; 储能配置模型&#xff1a; 粒子群算法&#xff1a; 运行结果&#xff1a; 1. 全年运行效果展示&#xff1a; 2. 典型日运行效果&#xff1a; Matlab代码数据分享…

博客系统自动化测试项目实战(测试系列9)

目录 前言&#xff1a; 1.博客前端页面测试用例图 2.测试用例的代码实现 2.1登录页面的测试 2.2博客列表页面的测试 2.3写博客测试 2.4博客详情页面的测试 2.5已发布博客的标题和时间的测试 2.6注销用户的测试 结束语&#xff1a; 前言&#xff1a; 之前小编给大家讲…

js实现点击查看全部/收起功能

在上一篇文章实现用js截取文本后&#xff0c;我的另一个需求也迎刃而解了。需求就是一段长文本需要溢出隐藏&#xff0c;然后点击全部时显示全部文本&#xff0c;点击收起又回到溢出隐藏的状态。实现的效果如下图&#xff1a; 实现的思路时点击全部时使用这条数据的原文本&…

04-过滤器和拦截器有什么区别?【Java面试题总结】

过滤器和拦截器有什么区别&#xff1f; 运行顺序不同&#xff1a;过滤器是在 Servlet 容器接收到请求之后&#xff0c;但在 Servlet被调用之前运行的&#xff1b;而拦截器则是在Servlet 被调用之后&#xff0c;但在响应被发送到客户端之前运行的。 过滤器Filter 依赖于 Servle…