【C++学习】特殊类设计 | 类型转换

news2024/11/27 11:51:27

🐱作者:一只大喵咪1201
🐱专栏:《C++学习》
🔥格言:你只管努力,剩下的交给时间!
图

特殊类设计 | 类型转换

  • 一、特殊类设计
    • 1.1 不能被拷贝的类
    • 1.2 只能在堆上创建的类
    • 1.3 只能在栈上创建的类
    • 1.4 不能被继承的类
    • 单例模拟
  • 二、类型转换
    • 2.1 static_cast
    • 2.2 reinterpret_cast
    • 2.3 const_cast
    • 2.4 dynamic_cast
  • 三、总结

一、特殊类设计

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

1.1 不能被拷贝的类

  • 拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载。
  • 禁止拷贝的类只需要让该类禁止调用拷贝构造函数和赋值运算符重载函数即可。

C++98中的方式:

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

将拷贝构造函数和赋值运算符重载函数只声明不定义,并且设置成私有。

原因:

  • 只声明不定义:在调用拷贝构造和赋值运算符重载函数的时候,由于没有定义就会产生链接错误,在编译阶段就报错。
  • 设置成私有:防止在类外定义拷贝构造和赋值运算符重载函数,此时即使定义了,因为私有的原因,在类外也无法调用。
  • 对拷贝构造和赋值运算符重载函数的定义没有任何意义,因为不会调用,不写的话反而更简单。

图

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

  • 拷贝构造以及赋值运算符重载等成员函数,在调用时都是编译器在域外调用,所以必须是公有的。

C++11的方式:

图

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

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

1.2 只能在堆上创建的类

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

  • 只能在创建在堆上时,就需要让该对象只能通过new来创建,并且调用构造函数来初始化。
class HeapOnly
{
public:
	static HeapOnly* CreateObject()
	{
		return new HeapOnly;
	}
private:
	//构造函数
	HeapOnly()
	{}
	HeapOnly(const HeapOnly& hp) = delete;//禁止拷贝
};

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

原因:

  • 使用静态成员函数new一个HeapOnly对象。

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

这样的前提是先有一个HeapOnly对象,但是构造函数设置成了私有,就无法创建这样一个对象。

而静态成员函数的调用不用传this指针,也就不用必须有HeapOnly对象,只需要类域::静态成员函数即可。

静态成员函数属于HeapOnly域内,所以在new一个对象的时候,可以调用私有的构造函数。

  • 禁止调用拷贝构造函数,并且私有化。

这样的目的是为了禁止拷贝,防止使用堆区上的HeapOnly对象在栈区上拷贝,如下面代码:
图
而禁止了拷贝构造就杜绝了这一行为,从而保证了HeapOnly对象只能在堆上创建。

1.3 只能在栈上创建的类

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

方法1:

所以防止在堆上创建类对象就是要禁止调用这两个函数。

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

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

图
此时在堆上创建对象时就会报错,尝试引用已经删除的函数。

方法2:

class StackOnly
{
public:
	static StackOnly CreateObject()
	{
		return StackOnly();
	}
private:
	StackOnly()
	{}
};

另一种方式就是和之前一样,通过一个静态成员函数在栈区上创建一个类对象,并且将默认构造函数私有化。

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

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

static StackOnly so = StackOnly::CreateObject();

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

1.4 不能被继承的类

C++98的方式:

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

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

原因:

  • 基类的构造函数私有,派生类在创建对象的时候,无法调用基类的构造函数。

此时我们就设计出了不能继承的类。

C++11的方式:

class NonInherit final
{}

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

单例模拟

单例模式是一种设计模式,所谓设计模式就是一套反复被使用,多数人知晓,经过分类的代码设计经验总结。也就是一种代码设计模板,按照这套模板可以使写出的代码更加高效稳定。

  • 单例模式:一个类只能创建一个对象。
  • 该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
  • 全局只有一个实例对象,所以将单例对象放在静态区,或者堆区(保证只创建一次)。
  • 为了防止其他位置创建该对象,将构造函数私有。
  • 为了防止拷贝,使用delete修饰拷贝构造和赋值运算符重载函数。

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

饿汉模式:

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;//在程序入口之前就完成单例对象初始化

  • 静态成员变量只能在类域外进行定义初始化。

所以在main函数之前就将单例对象定义初始化,此时该单例对象创建在静态区上,而且仅有一个,后面就无法再创建。

  • 想要获取该单例对象只能通过静态成员函数GetInstance()来获取。
  • 静态成员函数可以直接访问静态成员变量m_instance
int main()
{
	Singleton* s = Singleton::GetInstance();//获取单例对象

	return 0;
}
  • 为什么称之为饿汉模式呢?

不管将来会不会使用到这个单例对象,但是在程序一启动还没有进入main函数之前就创建一个唯一的实例对象。

就像一个饿汉一样,一上来就先吃(创建单例对象)。

缺点:

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

懒汉模式:

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一个单例对象,并且将所在地址赋值给静态的成员指针变量。

  • 为什么称之为懒汉模式呢?

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

优点:

  • 第一次使用单例对象时才创建对象,进程启动过程中无负载。
  • 多个互相依赖的单例可以控制启动顺序(通过代码顺序)。

线程安全问题:

在C++11之后饿汉模式是没有线程安全问题的(做了相关补丁),因为单例对象是在main函数之前就实例化的,而多线程都是在main函数里面启动的。

但是懒汉模式是存在线程安全问题的,当多个线程使用到单例对象时候,在使用GetInstance()获取对象时,用因为调度问题出现误判,导致new多个单例对象。

图

  • GetInstance()中判断是否创建单例的时候加锁,判断完后再解锁,让多线程串行访问单例对象的指针。

互斥锁必须放在静态区:

  • 单例对象只有一个,所以全局只使用一把锁即可,放在静态区刚合适。
  • GetInstance()是静态成员函数,没有this指针,如果互斥锁是普通成员变量的化,无法访问。而静态成员函数可以直接访问静态成员变量。

通过加锁的方式就能保证懒汉模式的线程安全问题。

双检查加锁:

  • 此时,每个调用GetInstance()的线程都需要申请锁然后释放锁,对锁的操作也是有开销的,会有效率上的损失。

单例模式在单例一经创建以后就不会再创建了,无论多少线程在访问已经创建的单例对象时都不会再创建,线程就已经安全了。所以在单例对象创建以后,根本没有必要再去申请锁和释放锁。

图
GetInstance()原本加锁代码放在一个if(m_pInstacne == nullptr)的代码快中,进行双重检查。

  • 当单例对象已经被new出来以后,就不会再进行加锁检查了,可以直接通过if进行判断,提高了性能。

图
千万不能写成这样,因为仍然存在线程安全问题,这样只是让多线程串行执行new单例对象的行为,但是没有串行去判断m_pInstance指针变量的值。

new出来对象的释放:

  • new出来的单例对象如何释放呢?

一般情况下我们选择不释放,因为全局只有一个单例对象,而且会被一直使用,所以就不用释放,释放了反而后面的使用会出问题。

当进程结束的时候,操作系统会回收该进程的所有资源,包括堆区上的资源。

  • 如果就要主动释放呢?在释放的同时将一些信息保存到磁盘。

图
如上图代码所示,在单例模式的类中创建一个内部类,内部类是外部类的友元。

  • 在内部类对象Gabro的析构函数中释放new出来的单例对象,并且进行相关的文件操作。
  • 内部类对象同样设置成静态成员,在类域外进行定义初始化。

本喵在解锁饿汉模式的时候仅说了它的缺点,没有说它的优点,在介绍懒汉模式的时候仅说了它的优点,没有说它的缺点。

可以看到,懒汉模式中需要加互斥锁,还要考虑线程安全,进行双检查加锁,相对于饿汉模式来说比较复杂,所以饿汉模式的优点就是简单,而懒汉模式的缺点就是复杂

具体要根据应用场景来决定是使用饿汉模式还是懒汉模式。

懒汉模式的另一种写法:

class Singleton
{
public:
	static Singleton& GetInstance()
	{
		static Singleton sinst;
		return sinst;
	}
	void print()
	{
		cout << "Singleton" << endl;
	}
private:
	Singleton()
	{}
	Singleton(const Singleton& s) = delete;//禁止拷贝
	Singleton& operator=(const Singleton& s) = delete;//禁止赋值
};

同样的需要将构造函数私有化,并且禁掉拷贝构造和赋值运算符重载函数。

在第一次使用GetInstance()获取单例对象的时候,创建了一个静态的单例对象,后面再获取的时候,就不会再创建了,充分利用了静态变量的特性。

图
但是在使用的时候,只能通过上面代码所示的方式。

  • GetInstance获取后不接收,直接使用获取的结果来调用成员函数。
  • 使用Singleton的引用来接收GetInstance的返回值,因为禁止拷贝。

因为禁止拷贝,所以无法使用:

Singleton s = Singleton::GetInstance()

来接收单例对象。

二、类型转换

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

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

  • 隐式类型转换:编译器在编译阶段自动进行,能转就转,不能转就编译失败。
  • 显式类型转换:需要用户自己处理。
int main()
{
	int i = 1;
	double d = i;//隐式类型转换
	printf("%d, %.2f\n", i, d);

	int* p = &i;
	int address = (int)p;//显式类型转换
	printf("%x, %d\n", p, address);

	return 0;
}
  • double d = i是发生了隐式类型转换,将整形转换成了double类型。

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

  • int address = (int)p是发生了显式类型转换,将int*类型的变量转换为int类型。

显式类型需要用户自己维护,在两种类型没有任何关系的时候需要进行显式类型转换,比如将指针类型转换成普通类型等等。

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

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

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

2.1 static_cast

  • C语言的隐式类型转换在C++中就可以使用static_cast来转换,但是不能用于两个不相关的类型进行转换。
double d = 3.14;
int a = static_cast<int>(d);

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

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

2.2 reinterpret_cast

  • C语言的显式类型转换在C++中就可以reinterpret_cast,用于将一种类型转换为另一种不同的类型。
int a = 1;
int* pa = &a;
int address = reinterpret_cast<int>(pa);

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

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

2.3 const_cast

  • 用在删除变量的const属性,方便赋值。
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;

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

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

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

2.4 dynamic_cast

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

向上转换:子类对象的指针或引用 → 父类对象的指针或引用。(不发生类型转换,是语法允许的,发生了切片)
向下转换:父类对象的指针或引用 → 子类对象的指针或引用。(用dynamic_cast转换是安全的)

//父类
class A
{
public:
	virtual void f()
	{}

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

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

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

  • 形参A* pa是父类指针,接收的也是父类指针,所以强转成子类指针后访问子类成员_b会发生越界。

如果传的是子类指针就不会报错,因为即使形参是父类指针,强转成子类以后并不会越界。

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

	bptr->_a = 3;
	bptr->_b = 4;
}

使用dynamic_cast将父类指针强转为父类指针。

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

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

  • pa如果指向的是子类对象,那么子类向上转换为父类没有问题。
  • pa如果指向的是父类对象,那么父类向下转换为子类是存在越界风险的。

注意:

  • dynamic_cast只能用于父类含有虚函数的类。
  • dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回nullptr。
  • dynamic_cast是安全的,直接使用C语言的转换方式是不安全的。

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

三、总结

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

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

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

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

相关文章

SpringBoot整合ShardingSphere5.x实现数据加解密功能

环境&#xff1a;Springboot2.6.14 ShardingSphere5.3.0 准备环境 添加依赖 <dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc-core</artifactId><version>${shardingsphere.version}</ve…

【数据分享】1929-2022年全球站点的逐日最低气温(Shp\Excel\12000个站点)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01; 之前我们分享过1929-2022年全球气象站…

【资料分享】过压保护自锁控制电路

概述(电路类别、实现主要功能描述)&#xff1a; 在电源系统中&#xff0c;当反馈回路失效时&#xff0c;输出电压不受控&#xff0c;电压升高超出规定范围&#xff0c;此时过高的输出电压有可能造成后续电器设备的损坏。为解决这问题&#xff0c;通常在电源中增加过压保护电路…

神经网络学习率指数衰减ExponentialDecay策略的参数含义与使用方法详解

本文介绍在tensorflow库中&#xff0c;用于动态调整神经网络的学习率的一种方法——指数衰减ExponentialDecay()策略的参数含义及其具体用法。 在进行神经网络训练时&#xff0c;我们经常需要用到动态变化的学习率&#xff0c;其中指数衰减ExponentialDecay()策略是我们常用的一…

【鲁棒优化】具有可再生能源和储能的区域微电网的最优运行:针对不确定性的鲁棒性和非预测性解决方案(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【Python 类方法和静态方法】零基础也能轻松掌握的学习路线与参考资料

一、Python类方法和静态方法的概念 在Python中&#xff0c;类方法和静态方法是常见的两种方法。两者都是Python中的函数&#xff0c;可以在类和实例上调用。但是&#xff0c;它们的作用和用法有所不同。 类方法是绑定到类而不是实例的方法&#xff0c;它们可以在实例和类上运…

高性能软件负载OpenResty常用命令

目录 1 案例介绍2 中小公司的详情页方案2.1 缺点 3 大型公司的商品详情页的核心思想3.1 生成静态页3.2 推送到文件服务器3.3 布隆过滤器过滤请求3.4 lua直连Redis读取数据3.5 OpenResty 渲染数据 4 环境准备4.1 配置文件服务器4.2 配置资源反向代理4.3 访问测试 1 案例介绍 商品…

【Unity100个实用小技巧】Git报错:error: some local refs could not be updated;

☀️博客主页&#xff1a;CSDN博客主页 &#x1f4a8;本文由 我是小狼君 原创&#xff0c;首发于 CSDN&#x1f4a2; &#x1f525;学习专栏推荐&#xff1a;面试汇总 ❗️游戏框架专栏推荐&#xff1a;游戏实用框架专栏 ⛅️点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd;&…

SpringBoot+Mybatis+Thymeleaf实现的疫情防控物资管理系统

本系统具体使用的技术是&#xff1a;后端使用SpringBootMybatis&#xff0c;前端使用了Thymeleaf框架&#xff0c;数据库使用的是MySql 8.0。开发工具使用的是IDEA。 本系统前端是使用了前辈的管理系统模板&#xff0c;具体的系统模块功能如下图所示&#xff1a; 一、系统首页…

《环信开发者技术等级考试》有奖问答正在进行中。。。

欢迎参加《环信开发者技术等级考试》&#xff0c;希望大家凝神静气&#xff0c;考出水平。 值此环信十周年&#xff0c;参与本次考试所有考生均可凭实力获得周年大礼包。 考场设置 本次考试主要考察大家对集成环信IM以及环信开通服务的熟悉度。 题型包含单选和多选&#xff…

算法修炼之筑基篇——筑基一层初期(解决01背包问题)

✨博主&#xff1a;命运之光 ✨专栏&#xff1a;算法修炼之练气篇​​​​​ ✨博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;学习了算法修炼之练气篇想必各位蒟蒻们的基础已经非常的扎实了&#xff0c;下来我们进阶到算法修炼之筑基篇的学习。筑基期和练气期…

物联网网关,原来是这么回事,感谢!

《高并发系统实战派》-- 你值得拥有 文章目录 物联网网关是什么&#xff1f;为什么要搞物联网网关&#xff1f;物联网网关作用&#xff1f; 物联网网关技术原理物联网网关实战开发边缘计算与物联网网关的碰撞边缘计算的理解物联网网关结合边缘计算 物联网网关是什么&#xff1f…

Windows10下docker安装及遇到的问题并且在docker快速部署onlyoffice

docker安装官网地址 docker安装超链接点击进入下载 这里下载的是window版的docker&#xff0c;你们根据自身实际情况下载&#xff1b; 安装docker遇到的问题: 第一个问题 : 启动遇到Docker Desktop is unable to detect a Hypervisor 虚拟化技术没打开&#xff0c;需要打开…

有哪些你觉得非常好用的软件?

以下是我认为非常好用的软件&#xff1a; 1. 金鸣表格文字识别&#xff1a;由深圳市金鸣科技有限公司开发&#xff0c;系统采用超前AI&#xff0c;经深度学习&#xff0c;识别精准&#xff0c;可将图片、PDF等转为excel、word&#xff0c;同时支持证件、票据等批量合并转为结构…

“好不容易进的腾讯,你凭什么要离开?”

前几天&#xff0c;我在网上看到一个故事。 故事的主人翁是18届的校招生&#xff0c;目前入职腾讯&#xff0c;工作了一个月。这一个月给他的感受是大量的写测试用例&#xff0c;感觉自己写测试用例的能力熟练了不少&#xff0c;测试技能倒是没有多大的提高&#xff0c;真正需…

vue使用纪要

一、基础总结 1、构成 1&#xff09;位置目录 如下图所示&#xff1a; 一个页面一个vue文件&#xff0c;位置在src下边的views里边&#xff1b; 如下图&#xff1a; 一个vue文件&#xff0c;对应一个或多个js文件&#xff0c;js放在src下边的api中&#xff1b; 2&#xf…

开源杀毒引擎库libclamav的使用方法

《开源杀毒引擎ClamAV的源码编译安装》中我们讲了ClamAV的安装和使用方法&#xff0c;可以很方便的使用ClamAV提供的工具进行病毒扫描&#xff0c;当然我们也可以在我们的程序中集成它提供的libclamav开发库来实现病毒扫描&#xff0c;libclamav是一个功能强大的病毒扫描库&…

代码随想录刷题第46天|LeetCode139单词拆分、多重背包

1、LeetCode139单词拆分 题目链接&#xff1a;139单词拆分 1、dp[i] : 字符串长度为i的话&#xff0c;dp[i]为true&#xff0c;表示可以拆分为一个或多个在字典中出现的单词。 2、递推公式&#xff1a;如果确定dp[j] 是true&#xff0c;且 [j, i] 这个区间的子串出现在字典里…

Using index Using where和 Using where Using index有何区别

先把结论呈现出来&#xff1a; 1、Using index : 查询的列被索引覆盖&#xff0c;并且where筛选条件是索引的是前导列&#xff0c;Extra中为Using index 2、 Using index &#xff0c;Using where&#xff1a;查询的列被索引覆盖&#xff0c;数据都是先通过索引查询出来的…

语法篇JS基础

一、初识JavaScript 1.1前景 CSS 预处理器 CSS 预处理器定义了一种新的语言&#xff0c;其基本思想是&#xff0c;用一种专门的编程语言&#xff0c;为 CSS 增加了一些 编程的特性&#xff0c;将 CSS 作为目标生成文件&#xff0c;然后开发者就只要使用这种语言进行 CSS 的编码…