c/c++开发,无可避免的函数指针使用案例

news2024/11/18 7:49:40

一、函数指针简介

        函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关。例如:

char* (*pf1)(char * p1,char *p2);

        这是一个函数指针,其真实词意如果转换一下,似乎更好理解,只是编译器不会这样排版而已:

char* (*)(char * p1,char *p2) pf1;

        将 pf 声明为指向函数的指针,它所指向的函数带有两个 char* 类型的形参和 char*类型的返回值。注意*pf两侧的圆括号是必需的,否则就成了返回char**类型的普通函数声明了。参数类型是必须的,但参数名不是必须的,可以省略。

char* (*pf1)(char*,char*); //char* (*)(char*,char*) pf1;

        通常我们在开发中,尤其是应用层级开发中,较少使用函数指针,也不建议使用函数指针。但是由于函数指针其特殊性,可以迸发出很多巧妙的代码组织方法,尤其是很多底层驱动开发中,不少地方都会用到函数指针。

        函数指针类型相当地冗长。使用 typedef 为指针类型定义同义词,可将函数指针的使用大大简化:

typedef char* (*pfunc)(char*, char*);

        声明函数指针后,需要给予初始化或赋值才能使用:

//先定义一个真实函数
char* fun1(char * p1,char *p2)
{
	return ((0== strcmp(p1,p2))?p1:p2);
};

//char* (*pf1)(char*, char*) = &fun1;//直接初始化
char* (*pf1)(char*, char*);
pf1 = &fun1;	            //fun1等同于char* (*)(char*, char*)

char p1[]="abc";
char p2[]="bcd";
printf("%s\n", (*pf1)(p1,p2));

        函数名作为右值时,在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针,因此采用&func或func给函数指针赋值都可以,以下四种中可以达成目的。

//func1除了用作函数调用的左操作数以外,对 fun1的任何使用都被解释为char* (*)(char*, char*)
char* (*pf1)(char*, char*) = fun1;//直接初始化
char* (*pf2)(char*, char*) = &fun1;//直接初始化
char* (*pf3)(char*, char*);//
char* (*pf4)(char*, char*);//
pf3 = func1;
pf4 = &func1;
typedef char* (*pfunc)(char*, char*);
pfunc pf5 = fun1;						
pfunc pf6 = &fun1;						

        函数指针只能通过同类型的函数或函数指针或 0 值常量表达式进行初始化或赋值。

char* (*pf1)(char*, char*);
pf1 = &fun1;						
//
pfunc pf3 = 0;		//初始化为 0,表示该指针不指向任何函数
pf3 = pf1;

char fun3(char * p1,char *p2)
{
	return ((0== strcmp(p1,p2))?p1:p2);
};

char* fun4(char  p1,char p2)
{
	return ((0== strcmp(p1,p2))?p1:p2);
};

pf3 = fun3;//eroor,返回类型不一致
pf3 = &fun4;//eroor,参数类型不一致

        指向函数的指针可用于调用它所指向的函数,支持显式或隐式方式。

    char p1[]="abc";
	char p2[]="bcd";
	pfunc pf3 = fun1;
	printf("%s\n",pf3(p1,p2));		//隐式调用
	printf("%s\n",(*pf3)(p1,p2));	//显式调用

二、函数指针数组

        函数指针数组,就是将函数指针存储在一个数组里,假设定义一个函数指针:

void (*FuncPtr)();

        那么FuncPtr就是一个函数指针,既然 FuncPtr是一个指针,那就可以储存在一个数组里。

void (*FuncPtr[])();
或
void (*FuncPtr[2])();

         定义一个函数指针数组。它是一个数组,数组名为 FuncPtr,数组内存储了 3 个或未知个数的指向函数的指针。这些指针指向一些返回值类型为void、无参数的函数。当然也可以通过typedef 修饰一下,就像定义变量数组一样:

typedef void (*FuncPtr)(); 

FuncPtr pfunc[3];

        同样,函数指针数组也要给其赋值,指向真实函数地址才能使用

typedef void (*FuncPtr)(); 

void doSomething1()
{
	printf("doSomething1\n");
};

void doSomething2()
{
	printf("doSomething2\n");
};

void (*fp[2])(); 
fp[0] = &doSomething1;
fp[1] = doSomething2;	
fp[0]();    //函数调用

FuncPtr pfunc[2] = {doSomething1,doSomething2};
pfunc[0]();    //函数调用

FuncPtr funcPtrArray[2];
funcPtrArray[0] = doSomething1;		//可以直接用函数名
funcPtrArray[1] = &doSomething2;	//可以用函数名加上取地址符
funcPtrArray[1]();    //函数调用

        如果函数返回值有差异,但又想通过函数指针数组归一化起来,在确保安全前提下,通过reinterpret_cast进行函数指针类型转换,注意c中是没有reinterpret_cast的。

typedef void (*FuncPtr)(); 

int doSomething3()
{
	printf("doSomething3\n");
	return 0;
};

FuncPtr funcPtrArray[3];
funcPtrArray[0] = doSomething1;		//可以直接用函数名
funcPtrArray[1] = &doSomething2;	//可以用函数名加上取地址符
//注意doSomething3直接传递是类型不符合的,它对于funcPtrArray是一个错误类型
//c++的reinterpret_cast 可以让你迫使编译器以你的方法去处理,
//转换函数指针,C++不保证所有的函数指针都被用一样的方法表示,在一些情况下这样的转换会产生不正确的结果.
funcPtrArray[2] = reinterpret_cast<FuncPtr>(&doSomething3);//g++编译时生效

        c语言编译时,可仿c++的reinterpret_cast创建一个类似的带参宏定义

#define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))    //仿c++的reinterpret_cast
//此处省略声明定义
funcPtrArray[2] = reinterpret_cast(FuncPtr, &doSomething3);//gcc或g++编译时都生效
funcPtrArray[2]();

三、指向重载函数的指针

        C++语言允许使用函数指针指向重载的函数,假设在func.cpp中定义了函数func_test

//func.cpp
int func_test(int p1, int p2)
{
	return (p1<p2)?p1:p2;
};

        在test.cpp中通过extern实现重载。

//test.cpp
extern int func_test(int, int);

//指向重载函数的指针
int (*pft)(int, int) = &func_test;
printf("min_val:%d\n", (*pft)(6,7));

        注意指针的类型必须与重载函数的精确匹配。如果没有精确匹配的函数,则对该指针的初始化或赋值都将导致编译错误:

//test.cpp
int (*pf2)(int) = &func_test;    //error,invalid parameter

void (*pf3)(int, int);
pf3 = &func_test;                // error, invalid return type

四、函数指针作为类成员

        函数指针也是指针,既然是指针,就可以作为类成员变量来使用。

        假设有如下类CallBack,其成员变量是一个函数指针CallBackPtr及void*的指针,初始化时传递一个函数指针和void变量指针实现初始化,通过doCallBack函数具体调用CallBackPtr实现函数回调:

//在传递函数指针时,建议进行这种异常规格的检查
typedef void (*CallBackPtr)(void *data_);
class CallBack
{
	public:
	CallBack(CallBackPtr fPtr, void *data_)
	: func(fPtr), data(data_) {}
	void doCallBack() const throw();
private:
	CallBackPtr func; 	// function to call when
						// callback is made
	void *data; 		// data to pass to callback
}; 

void CallBack::doCallBack() const throw()
{
	func(data);
}

        该类使用时,定义具体业务实现的真实函数,将这些函数指针传递给CallBack类,业务实际需要滞后到实现具体业务场景明确时,实现其逻辑,如下:


void callBackFcn1(void *data_)
{
	printf("callBackFcn1\n");
};

void callBackFcn2(void *data_) throw()
{
	printf("callBackFcn2\n");
};
//回调,将具体实现放置构造传递进去的具体函数指针
void *callBackData;
CallBack c_instance(callBackFcn1,callBackData);
c_instance.doCallBack();

        在传递函数指针时,建议进行这种异常规格的检查,大家可以想想如下函数指针定义时,指向的真实函数定义该如何设计。

typedef void (*CallBackPtr)(void *data_) throw();//如果不提供异常说明,该指针就可以指向能够抛出任意类型异常的具有匹配类型的函数
typedef void (*CallBackPtr)(void *data_) throw(runtime_error);//指定异常时,只能抛出 runtime_error 类型的异常
typedef void (*CallBackPtr)(void *data_) const throw();//类型定义更严格,源指针的异常说明必须至少与目标指针的一样严格

五、函数指针作为形参

        函数指针也是指针,既然是指针,就可以作为参数传递给函数,函数就可以在其内部使用该函数指针,从而实现对函数指针指向的函数进行调用,实现函数回调。想STL标准了内的排序函数 模板就是通过传递一个数值比较的函数指针来实现的。

//函数指针作为形参
class Aclass
{	
public:
	Aclass(void* data_) : vdata(data_)
	{
		
	};
	void doSomething1()
	{
		printf("Aclass doSomething1\n");
	};
	void doSomething2()
	{
		printf("Aclass doSomething2\n");
	};
	void* getData()
	{
		return vdata;
	}
private:
	void* vdata;
};

void myfunc(Aclass* ptr,void* data,bool (*compare)(void const* pd1, void const* pd2))
{
	if(compare(ptr->getData(),data))
		ptr->doSomething1();
	else
		ptr->doSomething2();
};

bool compare_int(void const* pd1, void const* pd2)
{
	return (*(int*)pd1=*(int*)pd2)?false:true;
}

        如上述代码,myfunc函数参数很复杂,将类对象,数值及函数指针作为参数传递进去。函数指针通过重定义一下:

typedef bool (*Func_compare)(void const* pd1, void const* pd2);

void myfunc(Aclass* ptr,void* data,Func_compare compare);

        函数调用如下:

int aval = 10;
int bval = 8;
Aclass a_obj(&aval);
myfunc(&a_obj,&bval,compare_int);	//

六、 转换表

        有以下一种功能要求,这是一个实现操作符计算的分支设计,如果有上百个操作符需要实现呢,这个switch语句就会很长:

switch(oper)
{
    case ADD:
        ret = add(val1,val2);
        break;
    case SUB:
        ret = sub(val1,val2);
        break;
    case MUL:
        ret = mul(val1,val2);
        break;
    case DIV:
        ret = div(val1,val2);
        break;
    ......
    default:
        ret = 0.0;
        break;
}

        通过建立一个函数指针数组作为转换表,就可以实现类似switch的代码功能:

enum Method{
	ADD=0,
	SUB,
	MUL,
	DIV
};

typedef float (*operFunc)(float fa, float fb);

operFunc operf[] = {add,sub,mul,div};

        其调用如下,确保这些操作符函数定义在初始化列表之前,初始化列表的函数名顺序取决与程序用来表示每个操作符的整型代码。

//操作符具体实现函数
float add(float fa, float fb)
{
	return fa+fb;
};
float sub(float fa, float fb)
{
	return fa-fb;
};
float mul(float fa, float fb)
{
	return fa*fb;
};

const float abs_min_val = 0.0000001;
float div(float fa, float fb)
{
	if((fb<abs_min_val)&&(fb>(-abs_min_val)))
	{
		return fa/abs_min_val;
	}
	return fa/fb;
};
//函数指针数组调用,实现类switch功能
float afval = 7.8, bfval = 8.5;
float ret_fval = operf[Method::ADD](afval,bfval);
printf("%0.4f\n",ret_fval);

七、类函数指针成员与注册函数

        函数指针是指针,就可以作为类的成员变量,通常用于将具体业务实现剥离在类外定义实现,在业务执行是注册到类中,从而针对具体业务来调用需要的业务模块(业务函数)。

//函数指针成员,注册函数
class BClass
{
public:
	BClass()
	{
		my_compare = NULL;
	};
	typedef bool (*compare)(void const* pd1, void const* pd2);
	void my_compare_do(void const* cm1, void const* cm2)    //具体实现通过调用外部函数
	{
		if(NULL==my_compare)
			printf("please register func first!\n");
		if(my_compare(cm1,cm2))    //外部函数调用
			printf("do it A\n");
		else
			printf("do it B\n");
	};
	void register_func(compare cp1)    //注册函数
	{
		my_compare = cp1;
	};
	compare my_compare;	//函数指针变量
};

        在具体业务逻辑过程中,先给类实例注入调用函数,在调用具体函数实现业务逻辑。

	//注册函数,实现外部函数调用
	BClass bObject;
	bObject.register_func(compare_int);
	bObject.my_compare_do(&aval,&bval);

八、类函数指针数组

        同样地,函数指针还可以通过函数指针数组作为类成员列表,这些成员的实现细节当然是可以在类内部,外部或外部模块来具体实现。例如,在很多程序会这样做,在程序中的每个类只要声明函数指针数组,然后在外部来具体实现业务,甚至是交给二次开发者依据具体业务场景来针对性设计功能函数。

//类函数指针数组test(成员列表)
class OperateKey
{
public:
	OperateKey()
	{
	};
	enum Directions { FORWARD, BACK, UP, DOWN };
	OperateKey& do_it(Directions direct_)
	{ 
        //记需要有效性判断
		(this->vtbl[direct_])();
		return *this;
	};
	typedef void (*Action)();
	static Action vtbl[];
};

void forward()
{
	printf("forward\n");
};
void  back()
{
	printf("back\n");
};
void up()
{
	printf("up\n");
};
void down()
{
	printf("down\n");
};
	
OperateKey::Action OperateKey::vtbl[] = {forward, back, up, down};

         类的函数指针数组成员列表就像平常类实例使用数组变量一样使用即可,只是数组变量是作为变量使用,而函数指针数组存储的是函数名(函数地址),作为一个个函数使用。

    //类函数指针数组成员列表
	OperateKey myKeys;
	myKeys.do_it(OperateKey::FORWARD);
	myKeys.do_it(OperateKey::DOWN);

九、函数指针测试案例

        创建test.cpp和func.cpp文件,主要是代码是在test.cpp中实现。

        func.cpp,用来测试函数指针重载

//
int func_test(int p1, int p2)
{
	return (p1<p2)?p1:p2;
};

        test.cpp

#include <string.h>
#include <stdio.h>

//函数指针test
char* fun1(char * p1,char *p2)
{
	return ((0== strcmp(p1,p2))?p1:p2);
};

char* fun2(char * p1,char *p2)
{
	return ((0== strcmp(p1,p2))?p2:p1);
};

typedef char* (*pfunc)(char*, char*);

//函数指针数组test
typedef void (*FuncPtr)(); 

#define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))

void doSomething1()
{
	printf("doSomething1\n");
};

void doSomething2()
{
	printf("doSomething2\n");
};

int doSomething3()
{
	printf("doSomething3\n");
	return 0;
};
//
extern int func_test(int, int);
//回调
//在传递函数指针时,建议进行这种异常规格的检查
typedef void (*CallBackPtr)(void *data_);
//typedef void (*CallBackPtr)(void *data_) throw();?//如果不提供异常说明,该指针就可以指向能够抛出任意类型异常的具有匹配类型的函数
//typedef void (*CallBackPtr)(void *data_) throw(runtime_error);?//指定异常时,只能抛出 runtime_error 类型的异常
//typedef void (*CallBackPtr)(void *data_) const throw();?//类型定义更严格,源指针的异常说明必须至少与目标指针的一样严格
class CallBack
{
	public:
	CallBack(CallBackPtr fPtr, void *data_)
	: func(fPtr), data(data_) {}
	void doCallBack() const throw();
private:
	CallBackPtr func; 	// function to call when
						// callback is made
	void *data; 		// data to pass to callback
}; 

void CallBack::doCallBack() const throw()
{
	func(data);
}

void callBackFcn1(void *data_)
{
	printf("callBackFcn1\n");
};

void callBackFcn2(void *data_) throw()
{
	printf("callBackFcn2\n");
};

//函数指针作为形参
class Aclass
{	
public:
	Aclass(void* data_) : vdata(data_)
	{
		
	};
	void doSomething1()
	{
		printf("Aclass doSomething1\n");
	};
	void doSomething2()
	{
		printf("Aclass doSomething2\n");
	};
	void* getData()
	{
		return vdata;
	}
private:
	void* vdata;
};

void myfunc(Aclass* ptr,void* data,bool (*compare)(void const* pd1, void const* pd2))
{
	if(compare(ptr->getData(),data))
		ptr->doSomething1();
	else
		ptr->doSomething2();
};

bool compare_int(void const* pd1, void const* pd2)
{
	return (*(int*)pd1=*(int*)pd2)?false:true;
}
//
float add(float fa, float fb)
{
	return fa+fb;
};
float sub(float fa, float fb)
{
	return fa-fb;
};
float mul(float fa, float fb)
{
	return fa*fb;
};

const float abs_min_val = 0.0000001;
float div(float fa, float fb)
{
	if((fb<abs_min_val)&&(fb>(-abs_min_val)))
	{
		return fa/abs_min_val;
	}
	return fa/fb;
};

typedef float (*operFunc)(float fa, float fb);

enum Method{
	ADD=0,
	SUB,
	MUL,
	DIV
};
//函数指针成员,注册函数
class BClass
{
public:
	BClass()
	{
		my_compare = NULL;
	};
	typedef bool (*compare)(void const* pd1, void const* pd2);
	void my_compare_do(void const* cm1, void const* cm2)
	{
		if(NULL==my_compare)
			printf("please register func first!\n");
		if(my_compare(cm1,cm2))
			printf("do it A\n");
		else
			printf("do it B\n");
	};
	void register_func(compare cp1)
	{
		my_compare = cp1;
	};
	compare my_compare;	
};

//类函数指针数组test(成员列表)
class OperateKey
{
public:
	OperateKey()
	{
	};
	enum Directions { FORWARD, BACK, UP, DOWN };
	OperateKey& do_it(Directions direct_)
	{
		(this->vtbl[direct_])();
		return *this;
	};
	typedef void (*Action)();
	static Action vtbl[];
};

void forward()
{
	printf("forward\n");
};
void  back()
{
	printf("back\n");
};
void up()
{
	printf("up\n");
};
void down()
{
	printf("down\n");
};
	
OperateKey::Action OperateKey::vtbl[] = {forward, back, up, down};

int main(int argc, char* argv[])
{
	//char* (*pf1)(char * p1,char *p2);//定义一个函数指针变量,等同于下一句
	char* (*pf1)(char*, char*);
	char p1[]="abc";
	char p2[]="bcd";
	//char* (*pf1)(char*, char*) = &fun1;//直接初始化
	pf1 = &fun1;						//fun等同于char* (*)(char*, char*)
	printf("%s\n", (*pf1)(p1,p2));
	pfunc pf2=fun2;
	printf("%s\n",(*pf2)(p1,p2));
	//
	pfunc pf3 = 0;		//初始化为 0,表示该指针不指向任何函数
	pf3 = pf1;
	printf("%s\n",pf3(p1,p2));		//隐式调用
	pf3 = pf2;
	printf("%s\n",(*pf3)(p1,p2));	//显式调用
	//
	FuncPtr pfuncs[2] = {doSomething1,doSomething2};
	FuncPtr funcPtrArray[3];
	funcPtrArray[0] = doSomething1;		//可以直接用函数名
	funcPtrArray[1] = &doSomething2;	//可以用函数名加上取地址符
	//注意doSomething3直接传递是类型不符合的,它对于funcPtrArray是一个错误类型
	//c++的reinterpret_cast 可以让你迫使编译器以你的方法去处理,
	//转换函数指针,C++不保证所有的函数指针都被用一样的方法表示,在一些情况下这样的转换会产生不正确的结果.
	//funcPtrArray[2] = reinterpret_cast<FuncPtr>(&doSomething3);//g++编译时生效
	funcPtrArray[2] = reinterpret_cast(FuncPtr, &doSomething3);//gcc或g++编译时都生效
	for(int i=0; i<3; i++)
	{
		funcPtrArray[i]();
	}
	//指向重载函数的指针
	int (*pft)(int, int) = &func_test;
	printf("min_val:%d\n", (*pft)(6,7));
	//回调
	void *callBackData;
	CallBack c_instance(callBackFcn1,callBackData);
	c_instance.doCallBack();
	//
	int aval = 10;
	int bval = 8;
	Aclass a_obj(&aval);
	myfunc(&a_obj,&bval,compare_int);	//
	//
	operFunc operf[] = {add,sub,mul,div};
	float afval = 7.8, bfval = 8.5;
	float ret_fval = operf[Method::ADD](afval,bfval);
	printf("%0.4f\n",ret_fval);
	//注册函数
	BClass bObject;
	bObject.register_func(compare_int);
	bObject.my_compare_do(&aval,&bval);
	//类函数指针数组成员列表
	OperateKey myKeys;
	myKeys.do_it(OperateKey::FORWARD);
	myKeys.do_it(OperateKey::DOWN);

	return 0;
}

        编译

g++ test.cpp func.cpp -o test.exe

        或者构建Makefile文件,调用make指令

CX	=	g++

BIN 		:= .
TARGET      := test.exe
FLAGS		:= 

Include		:= .
source		:= test.cpp func.cpp
$(TARGET) :
	$(CX) $(FLAGS) $(source) -I$(Include) -o $(BIN)/$(TARGET)

clean:
	rm  $(BIN)/$(TARGET)

        测试程序运行输出如下:

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

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

相关文章

javassm超市在线配送管理系统

为了解决用户便捷地在网上购物&#xff0c;本文设计和开发了一个超市管理系统。本系统是基于web架构设计&#xff0c;SSM框架 &#xff0c;使用Mysql数据库管理&#xff0c;综合采用JSP模式来完成系统的相关功能。主要实现了管理员与用户的注册与登陆&#xff0c;个人中心、用户…

[标准库]STM32F103R8T6 高级定时器--PWM输出和带死区互补PWM输出

前言 STM32F103系列的MCU&#xff0c;相比普通的51单片机&#xff0c;在输出硬件PWM这个功能上要强不少&#xff0c;两者实现的方式都类似&#xff0c;都是通过一个定时器来启用硬件PWM输出&#xff0c;不过在输出PWM通道的数量上&#xff0c;32F103要强上不少。仅通过一个高级…

5.Redis 实现点赞 优化登陆(验证码 token..)

Redis&#xff08;1&#xff09;简介Redis 是一个高性能的 key-value 数据库原子 – Redis的所有操作都是原子性的。多个操作也支持事务&#xff0c;即原子性&#xff0c;通过MULTI和EXEC指令包起来。非关系形数据库数据全部存在内存中&#xff0c;性能高。&#xff08;2&#…

Docker中安装MySQL 8

前言 上一期在Windows中安装好了Docker环境&#xff0c;这一期在Docker中完成MySQL 8安装和配置。 启动Docker Desktop后在cmd窗口中输入docker -v即可查看到安装的docker版本 Docker启动容器的原理流程 Docker启动一个容器(应用)时&#xff0c;大致原理流程如下图&#x…

安全技术与防火墙工具iptables

目录 安全技术 安全技术 补充防水墙 防火墙的分类 按保护范围划分 按实现方式划分 按网络协议划分 iptables iptables的五表五链 三种报文流向 iptables基本语法 数据包常见的控制类型 iptables的基本选项 显示扩展模块 保存规则 持久保存规则 加载规则 开机自…

上岸!轻轻松松打工!Python数据分析证

俗话说的好&#xff0c;活到老学到&#x1f914;这个大内卷的时代掌握一项技能还是很重要&#x1f648;的&#xff0c;这不趁着下班时间的功夫&#xff0c;偷偷去考了个证 就是一个不论含金量&#xff0c;还是对实习和求职都非常有益的一个双协会认证的高含金量证书~BDA数据分析…

电影订票网站的设计与开发

技术&#xff1a;Java、JSP等摘要&#xff1a;随着科技的发展&#xff0c;时代的进步&#xff0c;互联网已经成为了人们生活中不可缺少的一部分&#xff0c;网上购物已然是一种时代的象征。纵观市场&#xff0c;电影行业的发展尤为迅速&#xff0c;电影种类和数量的增多导致客流…

Portraiture全新4.0最新版人像磨皮插件更新内容

Portraiture是一款智能磨皮插件&#xff0c;为Photoshop和Lightroom添加一键磨皮美化功能&#xff0c;快速对照片中皮肤、头发、眉毛等部位进行美化&#xff0c;无需手动调整&#xff0c;大大提高P图效率。全新4版本&#xff0c;升级AI算法&#xff0c;并独家支持多人及全身模式…

ROS小车研究笔记2/11/2023:使用ssh远程登录小车

1 SSH简介&#xff1a; SSH全称Secure Shell&#xff0c;是一种建立在应用层的安全网络协议。其安全性又非对称加密(RSA)实现 对称加密&#xff1a;使用同一密钥对信息进行加密和解密&#xff0c;但是一旦该密钥被窃取就会威胁通信安全 非对称加密&#xff1a;使用公钥和私钥。…

【Java基础】018 -- 面向对象阶段项目下(拼图小游戏扩展)

文章目录切换游戏图片的业务分析:1&#xff0c;所需要的技术点2&#xff0c;分析业务逻辑项目实现步骤&#xff1a;添加组件绑定事件&#xff1a;代码实现登录界面的业务分析&#xff1a;1&#xff0c;所需要的技术点2&#xff0c;分析业务逻辑项目实现步骤&#xff1a;主界面设…

Day885.NextKeyLock加锁规则 -MySQL实战

NextKeyLock加锁规则 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于NextKeyLock加锁规则的内容。 加锁规则前提说明&#xff1a; MySQL 后面的版本可能会改变加锁策略&#xff0c;所以这个规则只限于截止到现在的最新版本&#xff0c;即 5.x 系列 <5.7.24&#…

搭建流媒体推流/拉流服务(RTMP/RTSP/HLS/HTTP-FLV)

一、什么是流媒体流媒体&#xff08;streaming media&#xff09;是指将一连串的媒体数据压缩后&#xff0c;经过网上分段发送数据&#xff0c;在网上即时传输影音以供观赏的一种技术与过程&#xff0c;此技术使得数据包得以像流水一样发送&#xff1b;如果不使用此技术&#x…

ffmpeg转码转封装小工具开发

如下图所示&#xff0c;是本人开发的一个转码转封装小工具 其中目标文件视频编码格式支持&#xff1a;H264&#xff0c;H265&#xff0c;VP8&#xff0c;VP9。 目标文件封装格式支持&#xff1a;mp4,mkv,avi,mov,flv。 目标文件音频编码格式支持两个&#xff0c;COPY和AAC&am…

安全寒假作业nginx反向代理+负载均衡上传webshell重难点+apache漏洞

1.应用场景 负载均衡作为现今解决web应用承载大流量访问问题的一种方案&#xff0c;在真实环境中得到广泛的部署。实现负载均衡的方式有很多种&#xff0c;比如 DNS 方式、HTTP 重定向方式、IP 负载均衡方式、反向代理方式等等。 比如基于dns的负载均衡&#xff1a; 当然还有…

LTD212次升级 | 官网社区支持PC端展示 • 官网新增证件查询应用,支持条形码扫码查询

1、新增证件查询应用&#xff0c;支持条形码扫码查询&#xff1b; 2、新增用户社区PC端功能&#xff1b; 01证件查询应用 1、新增证件查询应用功能 支持证件信息录入、打印功能&#xff0c;支持条形码扫码识别。 后台管理操作路径&#xff1a;官微中心 - 应用 - 证件查询 …

这才是CSDN最系统完整的网络安全学习路线(建议收藏)

01 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

linux head命令(head指令)(获取文件或管道输出结果前n行,默认前10行)与sed命令区别

head命令是一个在Linux系统中常用的命令&#xff0c;用于读取文件的前几行&#xff08;默认读取前10行&#xff09; 文章目录使用方法读取文件的前10行&#xff1a;head filename读取文件的前n行&#xff1a;head -n行数 filename读取多个文件的前几行&#xff1a;head -n 行数…

六、Java框架之SpringBoot

黑马课程 文章目录1. SpringBoot入门1.1 SpringBoot入门案例步骤1&#xff1a;创建SpringBoot项目高版本springboot常见错误步骤2&#xff1a;创建BookController步骤3&#xff1a;启动服务器并运行程序pom.xml示例1.2 官网创建SpringBoot1.3 SpringBoot工程快速启动问题导入打…

网络安全实验室6.解密关

6.解密关 1.以管理员身份登录系统 url&#xff1a;http://lab1.xseclab.com/password1_dc178aa12e73cfc184676a4100e07dac/index.php 进入网站点击忘记密码的链接&#xff0c;进入到重置密码的模块 输入aaa&#xff0c;点击抓包&#xff0c;发送到重放模块go 查看返回的链接…

VScode 结合clangd 构建linux源代码阅读环境

1、背景介绍上一篇文章&#xff1a;VScode 结合Global构建linux源代码阅读环境 &#xff0c;介绍了在VS Code工具中通过remote-ssh远程登陆到Linux远程服务器&#xff0c;使用Global构建linux源代码阅读环境&#xff0c;对linux kernel代码进行解析&#xff0c;实现全局搜索、自…