C++之单例模式

news2024/11/16 17:35:29

目录

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

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

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

C++98

C++11

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

C++98

C++11

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

设计模式

单例模式

单例模式的两种实现方式 

饿汉模式 

懒汉模式 

单例对象的释放 

懒汉模式与饿汉模式的优缺点

现代懒汉模式的写法

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

正常情况下,一个类的对象既可以在堆上又可以在栈上
实现方式:
  • 1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
  • 2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
class HeapOnly
{
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}

	HeapOnly(const HeapOnly& ho) = delete; //防止通过拷贝构造开在栈上
private:
	HeapOnly()
		:_a(0)
	{}

private:
	int _a;
};

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

同上将构造函数私有化,然后设计静态方法创建对象返回即可。

目前看似杜绝在堆上创建出对象,但是我们却可以通过这样的方式在堆上创建出对象 。

我这里的new虽然没有调用构造函数,但是调用了拷贝构造,又因为获取对象的函数是传值返回,所以我们不能删除拷贝构造,但是我们却可以屏蔽new,因为new在底层调用void* operator new(size_t size)函数,只需将该函数屏蔽掉即可。 一个类可以重载它专属的operator new,没有重载之前调用的是全局的new,但是重载专属的operator new以后,调用的就是专属的operator new。

class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		return StackOnly(); //不能删除拷贝构造,因为这里是传值返回
	}
	void* operator new(size_t size)=delete;
	void operator delete(void* p) = delete;
private:
	StackOnly()
		:_a(0)
	{}

private:
	int _a;
};

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

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

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;
 //...
};

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

C++98

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

C++11

final关键字,final修饰类,表示该类不能被继承
class A NonInherit
{
 // ....
};

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

设计模式

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

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

迭代器就是一种设计模式,被广泛的用于数据结构中。这个数据结构可能是个数组,图,哈希表,二叉树等等,如果要去访问它的话要把数据结构的底层全部都暴露出来,但是C++是不希望暴露结构的,暴露的缺点在于:1.访问不方便(需要熟悉底层结构) 2.暴露底层结构,别人直接访问修改数据,不方便管理。所以就出现了迭代器模式,好处在于:1.统一方式封装访问结构。底层结构不暴露。2.可以使用统一的方式轻松访问容器,不关心底层是树,还是链表等。

单例模式

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

我们直接从实例入手,对于下面的这段代码(我们是采取分成三个部分写的,快排的定义放在一个文件里,实现放在一个文件里,最终的调用也放在一个文件里),我要进行统计快速排序递归调用次数。我们一般的思路就是定义一个全局变量。每次递归,这个全局变量就++,最终拿到这个全局变量就是一共递归调用的次数。

Singleton.h

#pragma once

#include<iostream>
#include<time.h>
using namespace std;

int callCount = 0; //统计次数的全局变量
// 统计快速排序递归调用次数
void QuickSort(int* a, int left, int right);

Singleton.cpp

#include"Singleton.h"
void QuickSort(int* a, int left, int right)
{
	++callCount;

	if (left >= right)
	{
		return;
	}
	int keyi = left;
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			swap(a[cur], a[prev]);
		}
		++cur;
	}
	swap(a[prev], a[keyi]);
	keyi = prev;
	// [left, keyi-1]keyi[keyi+1, right]
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

test.cpp

#include"Singleton.h"

void TestOP()
{
	srand(time(0));
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
	}

	int begin5 = clock();
	QuickSort(a1, 0, N - 1);
	int end5 = clock();

	printf("QuickSort:%d\n", end5 - begin5);
	printf("QuickSort Call Time:%d\n", callCount);

	free(a1);

}
int main()
{
	TestOP();
	return 0;
}

但是对这段代码进行编译的时候,会有报错

为什么会报一个全局变量callCount重定义的错误呢?

Singleton.h中有一个callCount的定义,Singleton.h被Singleton.cpp和test.cpp都所包含。.h文件在.cpp文件中展开,.cpp会生成两个.obj(linux下叫做.o)然后这两个.obj中都有一个叫做callCount的变量。编译器链接的时候是都要包这些东西合在一起的,Singleton.h有callCount的定义,Singleton.cpp有一个callCount的变量,test.cpp中也有一个叫callCount的变量,这就会导致合并在一起的时候就冲突了,同一个域中不能有同名的变量,所以就会有重定义的错误。总之就是因为全局变量在多个文件中都可见就导致了这个问题。

static关键字

static这个关键字就可以解决这个问题,普通的全局变量在多个文件都可见(在Singleton.obj和test.obj的符号表里面都有,符号表里面除了有函数名还有变量。当别人要用这个变量的时候不知道是用你Singleton.obj的还是用test.obj的)。而静态全局变量只在当前文件可见,static除了会改变生命周期,还会影响变量和函数的链接属性,让他只在当前文件可见(意思就是Singleton.cpp和test.cpp中都有一个callCount的变量,但是Singleton.cpp中的callCount只在Singleton.cpp可见,test.cpp中的callCount只在test.cpp可见)这样链接的时候就不会看见他们冲突,因为他们只在内部可见。

但是也并没有真正的解决掉整个问题,我们发现最终的callCount是0。这是因为只在当前文件可见,导致 Singleton.cpp和test.cpp中的callCount并不是同一个。

我们通过打印地址发现并不是同一个

extern关键字

用extern关键字,我们在Singleton.h是定义,要做到把定义和声明分离就要有extern关键字。我们把声明放在一个.h中,定义也只放在一个.cpp中

这个时候只有Singleton.cpp生成的Singleton.obj中有这个变量,test.obj中是没有这个变量的,test.cpp包着Singleton.h只有callCount的声明,只有声明的话编译器是可以编过的,只是我不知道这个callCount的地址,我在链接的时候就可以访问到这个变量了。extern就告诉test.cpp说这个callCount变量是有的,是全局的,但是它在其他的.obj中定义的,你链接的时候自己去找吧,所以链接的时候除了要找这个QuickSort函数(因为他也是只有声明)的地址还要找这个callCount的地址,这个时候就可以把callCount这个变量访问到。

这个时候就可以统计出递归调用的次数了。 

ps:我们可以通过小区间优化,优化一下递归调用的次数,当排序的数据量变小用插排 

Singleton.cpp

#include"Singleton.h"

int callCount = 0; //然后我们在这个cpp里面放上定义

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		// x[0, end]
		int end = i;
		int x = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > x)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = x;
	}
}

void QuickSort(int* a, int left, int right)
{
	if (callCount == 0)
	{
		cout << "Singleton.cpp中的callCount的地址" << &callCount << endl;
	}
	++callCount;

	if (left >= right)
	{
		return;
	}

	if (right - left + 1 > 10)
	{
		int keyi = left;
		int prev = left;
		int cur = prev + 1;
		while (cur <= right)
		{
			if (a[cur] < a[keyi] && ++prev != cur)
			{
				swap(a[cur], a[prev]);
			}
			++cur;
		}
		swap(a[prev], a[keyi]);
		keyi = prev;
		// [left, keyi-1]keyi[keyi+1, right]
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
	else
	{
		InsertSort(a + left, right - left + 1);
	}

}

递归次数明显减少 

单例模式的两种实现方式 

假设我不仅要把全局变量保存下来,我还要有一个vector,要求把递归调用的区间也存储下来。你就又需要向之前一样一边声明一边定义。这个时候就可以用面向对象的思想来解决这个问题。

我们需要全局有一个变量或者对象,这个对象里面有一些信息,这个信息是全局的,并且全局只有唯一一份,且提供一个访问它的全局访问点 。我们就可以设计一个单例模式。

把需要统计的放在一个类里面,这个时候就不需要加extern,因为类里面的变量天生就是声明,类对象才是定义。我们同时不期望别人随便创建对象,我们就可以把构造函数私有化,并且我们还要提供一个让你获取对象的方式,并且这个对象是唯一的一个对象。这个时候我们就可以采用懒汉和饿汉两种方式获取。 

饿汉模式 

  • 就是说,我已经把饭提前准备好了,我随时可以吃;在main函数之前就创建好了单例对象,程序随时可以访问这个单例对象
  • 如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

这个_inst对象目前只有声明,它的定义可以放在Singleton.h,但是这样的话就又有之前全局变量的问题,Singleton.cpp和test.cpp就各有一份了,所以我们最好在Singleton.cpp中定义。

这样的话每次调用GetInstance都是调用的_inst这个对象,并且都是同一个对象。

接下来统计递归调用的次数

因为这里涉及到类成员变量的++,一种方式就是把成员变量设置成public,一种就是通过提供成员函数间接++。博主这里选择增加成员函数进行++ 

test.cpp则用成员函数获取总次数

运行结果: 

这个地方用类去封装,用一个单例的类是非常的好用的,避免我们统计的信息只有唯一一份。为了防止恶意拷贝,我们就可以把拷贝构造封死

//单例模式--任意一个类都可以设计成单例模式
//饿汉模式
class CallInfo
{
public:
	static CallInfo& GetInstance()
	{
		return _inst;
	}
	int GetCallCount()
	{
		return _callCount;
	}
	void AddCallCount(int n)
	{
		_callCount += n;
	}
	void Push(const pair<int, int>& kv)
	{
		_v.push_back(kv);
	}

	CallInfo(const CallInfo& cl) = delete;
private:
	CallInfo()
		:_callCount(0)
	{}
private:
	int _callCount;  //统计次数
	vector<pair<int, int>> _v; //记录区间信息
	static CallInfo _inst; //类里面定义的变量都是声明
	//构造函数虽然是私有的,但是静态的变量是可以调用构造函数初始化的,因为它的作用域是类里面
	//它也是类的成员,只不过是在类外定义的而已。
};

PS:在类里面定义对象,如果是普通的对象是不可以定义的,因为这就成了递归;但如果是静态的就没问题,因为静态的就相当于是全局的,就是你这个类定义好了,然后我又定义了一个全局的,这个对象属于类整体,相当于你就是定义了一个全局的变量,只是它的作用域被限定在这个类域里面,且在类里面只是一个声明和你定义一个全局的变量没有任何区别。

官方来讲:因为声明的静态数据成员不占用对象的内存。但是普通的数据成员在初始化呢的时候,首先要为对象分配内存,然后里面又有一个类,一直持续下去就不行,会无限迭代的分配内存。当然,引用和指针是可以的。引用和指针的大小是固定的。 

//例如:

class A
{

private:
int i;

static A a;
}

//A 的大小其实只是 i 的大小。

懒汉模式 

  • 事先没有准备好,只有第一次访问的时候,才创建单例对象
  • 如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
//懒汉模式
class CallInfo
{
public:
	static CallInfo& GetInstance()
	{
		if (_pInst == nullptr)
		{
			_pInst = new CallInfo;
		}
		return *_pInst;
	}
	int GetCallCount()
	{
		return _callCount;
	}
	void AddCallCount(int n)
	{
		_callCount += n;
	}
	void Push(const pair<int, int>& kv)
	{
		_v.push_back(kv);
	}

	CallInfo(const CallInfo& cl) = delete;
private:
	CallInfo()
		:_callCount(0)
	{}
private:
	int _callCount;  //统计次数
	vector<pair<int, int>> _v; //记录区间信息
	static CallInfo* _pInst; //类里面定义的变量都是声明

};

这就是懒汉模式,懒汉就是创建一个指针,静态指针执行的这个对象保证全局唯一,保证每次进来获取的都是那个唯一的对象。

但是当前写的懒汉模式的GetInstance是存在大问题的,是存在线程安全问题的。

如果你有一个t1和一个t2,t1和t2都调用GetInstance,导致统计的次数不准确。假设t1和t2都走到62行,t1和t2走到这里都是空指针,t1走到这时间片到了,t2先new,new完后,返回这个对象,然后去调用++,把次数加到1。然后t2的时间片到了,t1开始运行,虽然此时的指针已经不为空了,但是t1不会判断(因为一个线程回来的时候是从它切出去的那一行开始运行的),然后t1在new一个,返回。t2在来的时候发现不为空,但是获取到的是t1刚刚new的,所以次数又从0次开始,t2之前统计的次数就丢失了。而且后面new出来的还会把前面new出来的覆盖掉会存在内存泄露的问题。

eg:

我们可以通过加锁解决:

  

PS:用静态的锁一方面是要保证两个线程使用同一把锁,另外一方面就是成员函数只能访问成员,不能访问普通成员 

为什么不能在静态成员函数中使用非静态变量?

所谓静态就是程序编译好以后,就已经给它分配了内存区域,它一直在那里,所谓动态就是运行时候临时分配内存的变量。

程序最终都将在内存中执行,变量只有在内存中占有一席之地时才能被访问。因为静态是针对类的,而成员变量为对象所有。

静态成员函数不属于任何一个类对象,没有this指针,而非静态成员必须随类对象的产生而产生,所以静态成员函数”看不见”非静态成员,自然也就不能访问了

类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问;非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。

在一个类的静态成员中去访问其非静态成员之所以会出错是因为在类的非静态成员不存在的时候类的静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。

C++会区分两种类型的成员函数:静态成员函数和非静态成员函数。这两者之间的一个重大区别是,静态成员函数不接受隐含的this自变量。所以,它就无法访问自己类的非静态成员。
 

当前就可以保证不会发生线程安全的问题了。但是我们还可以进一步去优化,加锁是为了保护第一次获取对象。只要对象创建出来以后,就没有线程安全的问题,现在这种写法,后面每次获取对象都要进行加锁,就会影响效率。

所以我们可以采用双检查加锁

这个时候,单例对象创建好了以后,获取单例对象的时候,就不用每次进行加锁解锁。 

单例对象的释放 

 一般情况下,单例对象是不需要释放的,不用担心它内存泄露的问题,因为它是进程堆上的资源,通常也不大,main函数结束以后,进程就销毁了,进程结束后的资源都会还给系统。但是你非要释放的话也是可以的、

1.直接主动的去提供一个释放的接口,但是这样做并不常见

2.提供一个内部类进行回收

这个gc对象出了作用域就会去调用它的析构函数,这个析构函数就会把new的对象带走。

一般懒汉的单例对象,不需要回收,因为进程正常结束,资源都会还给系统,这个对象只有一个,系统自动回收也没什么问题,但是如果你在单例对象释放析构时,有一些要完成的动作,比如要记录日志等等。那么可以考虑搞一个类似下面的回收类帮助去完成这个事情。

懒汉模式与饿汉模式的优缺点

饿汉没有线程安全的问题,因为它在main函数之前就准备好了,main函数之前是没有多线程竞争的问题的。

饿汉优点:简单。缺点:无法控制单例创建初始化顺序(假设两个单例类A,B,要求A单例先创建,B单例后创建,B的创建依赖A。饿汉是无法实现这样的需求的);如果单例对象初始化很费时间,会导致程序启动慢,就像卡死一样。

懒汉优点:对应饿汉的两个缺点。缺点:1.相对复杂,尤其是还要控制线程安全的问题,

现代懒汉模式的写法

class CallInfo
{
public:
	static CallInfo& GetInstance()
	{
		static CallInfo sInst;
		return sInst;
	}

	int GetCallCount()
	{
		return _callCount;
	}
	void AddCallCount(int n)
	{
		_callCount += n;
	}
	void Push(const pair<int, int>& kv)
	{
		_v.push_back(kv);
	}

	CallInfo(const CallInfo& cl) = delete;
private:
	CallInfo()
		:_callCount(0)
	{
		cout << "CallInfo()" << endl;
	}
private:
	int _callCount;  //统计次数
	vector<pair<int, int>> _v; //记录区间信息

};

对于这样的写法,我在GetInstance中,先创建了一个局部的静态对象,对于局部的静态对象,只有第一个调用它的人会进行初始化,后面的人都不会初始化,直接返回对象,并且每次都返回同一个,局部的静态对象生命周期属于全局,但是它的作用域只在GetInstance中。

但是这样的写法在C++98中,多线程调用的时候,静态的对象的构造初始化并不能保证线程安全,C++11优化了这个问题,C++11中,静态对象的构造初始化是线程安全的。

所以双加锁的懒汉模式是在任何环境下都安全的,其次这种双加锁的好处是如果new出来的对象很大,new出来的对象在堆上,堆就很大,如果把这个对象放在数据段上相对而言没那么好。

设计模式的扩展:工厂模式,观察者模式

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

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

相关文章

在GoLand中编译cgo程序

GoLand的C使用问题为什么会出现fyne之类的包无法在GoLand中运行安装gcc安装make安装Choco安装指令使用Choco安装make将make加入Path将GoLand的make路径进行替换测试&#xff0c;编译fyne程序问题 本人go萌新&#xff0c;正在学习golang的界面包fyen&#xff0c;但是在使用GoLa…

Android面试题——JVM

Android平台的虚拟机是基于栈的吗&#xff1f; JVM运行时数据区 运行时栈 基于栈的虚拟机 第0行表示将一个Int型的1推送至操作数栈栈顶&#xff0c;程序计数器指向第0行。第一行字节码表示将栈顶的int型数值存入第一个本地变量&#xff0c;这两行代码就进行了给局部变量赋值的操…

【开源硬件】STM32F030R8T6系统板

【开源硬件】STM32F030R8T6系统板✅STM32F030R8T6系统板兼容极海APM32F030R8T6 &#x1f530;支持stm32cubemx工程配置成STM32F030R8T6生成的MDK工程&#xff0c;经过Keil编译后可以直接使用ST-Link v2烧录器上传到极海APM32F030R8T6芯片当中&#xff0c;完全做到平替使用&…

[Java·算法·中等]LeetCode22. 括号生成

每天一题&#xff0c;防止痴呆题目示例分析思路1题解1分析思路2题解2分析思路3题解3&#x1f449;️ 力扣原文 题目 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 输入&#xff1a;n 3 输出&…

Unity Lighting -- 配置阴影

本笔记使用URP管线进行说明 配置渲染管线阴影参数 在项目窗口中&#xff0c;主菜单中选择Edit -> ProjectSettings,然后选择Quality,查看当前项目所使用的URP资源&#xff0c;目前例子所使用的资源为UniversalRP_HighQuality资源 在工程窗口中&#xff0c;会有对应的渲染管…

1060:均值

一。题目 1060&#xff1a;均值 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 101855 通过数: 62518 【题目描述】 给出一组样本数据&#xff0c;包含n个浮点数&#xff0c;计算其均值&#xff0c;精确到小数点后4位。 【输入】 输入有两行&#xff0c;第一行包含…

【Echarts】在Vue中使用Echarts

在Vue中使用Echarts引入第三方库vue-echartsvue-echarts安装与使用vue2中使用Echartsvue-echarts安装vue2中使用Echartsvue3中使用Echartsvue-echarts安装vue3中使用Echarts引入第三方库vue-echarts Vue是数据驱动dom&#xff0c;改变数据让图表自动根据数据变化更新&#xff…

Ubuntu 18.04 出现GLIBC_2.28 not found的解决方法(亲测有效)

关于/lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.28’ not found出现报错&#xff0c;建议不要使用源码包去编译并升级。在下文有分享一个使用官方的Debian软件包去升级使用的方法。仅供参考&#xff01; 环境 # uname -a Linux Ubuntu 5.4.0-144-generic #161~18.04.…

ubuntu server 18.04使用tensorflow进行ddqn训练全过程

0. 前言 需要使用ddqn完成某项任务&#xff0c;为了快速训练&#xff0c;使用带有GPU的服务器进行训练。记录下整个过程&#xff0c;以及遇到的坑。 1. 选择模板代码 参考代码来源 GitHub 该代码最后一次更新是Mar 24, 2020。 环境配置&#xff1a; python3.8 运行安装脚本…

超全的命令(代码)执行漏洞无回显的姿势总结(附带详细代码和测试分析过程)

目录 漏洞代码 突破方式 重定向 dnslog外部通信 burpsuite burpcollaborator外部通信 日志监听 netcat监听 反弹shell的各种姿势 漏洞代码 <?php shell_exec($_GET[a]); ?>这里使用了无回显的shell执行函数shell_exec&#xff0c;给html目录的权限是777 突破方…

C++之多态 虚函数表

多态 多态是在不同继承关系的类对象&#xff0c;去调用同一函数&#xff0c;产生了不同的行为。 需要区分一下&#xff1a;1、菱形虚拟继承&#xff0c;是在继承方式前面加上virtual&#xff1b; class Person {}; class Student : virtual public Person {}; class Teacher…

【深蓝学院】手写VIO第2章--IMU传感器--笔记

0. 内容 1. 旋转运动学 角速度的推导&#xff1a; 左ω∧\omega^{\wedge}ω∧&#xff0c;而ω\omegaω是在z轴方向运动&#xff0c;θ′[0,0,1]T\theta^{\prime}[0,0,1]^Tθ′[0,0,1]T 两边取模后得到结论&#xff1a; 线速度大小半径 * 角速度大小 其中&#xff0c;对旋转矩…

Spring Security 实现自定义登录和认证(1):使用自定义的用户进行认证

1 SpringSecurity 1.1 导入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency>1.2 编写配置类 在spring最新版中禁用了WebSecurityConfigurerAdapter…

04-项目立项:项目方案、可行性分析、产品规划、立项评审

文章目录4.1 项目方案立项阶段4.2 可行性分析4.3 产品规划4.4 立项评审4.4.1 立项说明书的主要内容4.4.2 立项评审流程章节总结4.1 项目方案 学习目标&#xff1a; 能够输出产品项目方案 项目开发设计流程的主要阶段&#xff1a; 立项阶段 → 设计阶段 → 开发阶段 → 测试阶…

机器学习 | 实验一:线性回归

文章目录&#x1f4da;描述&#x1f4da;数据&#x1f4da;监督学习问题&#x1f4da;二维线性回归&#x1f4da;理解J(θ)⭐️对应笔记 单变量线性回归多变量线性回归 &#x1f4da;描述 第一个练习将提供线性回归练习。这些练习已经在Matlab上进行了广泛的测试。但它们也应该…

Spring Boot @Aspect 切面编程实现访问请求日志记录

aop切面编程想必大家都不陌生了&#xff0c;aspect可以很方便开发人员对请求指定拦截层&#xff0c;一般是根据条件切入到controller控制层&#xff0c;做一些鉴权、分析注解、获取类名方法名参数、记录操作日志等。 在SpringBoot中使用aop首先是要导入依赖如下&#xff1a; …

软工2023个人作业二——软件案例分析

项目内容这个作业属于哪个课程2023年北航敏捷软件工程这个作业的要求在哪里个人作业-软件案例分析我在这个课程的目标是学习并掌握现代软件开发和项目管理技术&#xff0c;体验敏捷开发工作流程这个作业在哪个具体方面帮助我实现目标从软件工程角度分析比较我们所熟悉的软件&am…

Doris集成Spark读写的简单示例

Doris集成Spark读写的简单示例 文章目录Doris集成Spark读写的简单示例0、写在前面1、Spark Doris Connector介绍2、基本示例2.1 提前准备表和数据2.2 新建项目2.3 使用SQL方式进行读写2.3.1 代码2.3.2 相关Error2.4 使用DataFrame方式读写数据&#xff08;**batch**&#xff09…

CS5261typec转HDMI|CS5260typec转VGA视频转换方案参考设计与PCB板开发

CS5261typec转HDMI|CS5260typec转VGA视频转换方案参考设计与PCB板开发 CS5261 CS5260分别是Type-C转HDMI或者VGA高性能 视频转换芯片&#xff0c;CS5261 是Type-C转HDMI 4K30HZ转换芯片 CS5260是Type-C转VGA 转换芯片。CS5261与CS5260两种芯片的功能和参数特性如下&#xff1…

热乎的面经——初出茅庐

⭐️前言⭐️ 本篇文章记录博主与2023.03.04面试上海柯布西公司&#xff0c;一面所被问及的面试问题&#xff0c;回答答案仅供参考。 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主将持续更新学习记录收获&am…