c/c++开发,无可避免的函数指针使用案例
一、函数指针简介
函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关。例如:
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 parametervoid (*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 madevoid *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();elseptr->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");elseprintf("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 madevoid *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();elseptr->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");elseprintf("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)
测试程序运行输出如下:
相关文章:

c/c++开发,无可避免的函数指针使用案例
一、函数指针简介 函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关。例如: char* (*pf1)(char * p1,char *p2); 这是一个函数指针,其…...
QT(12)-QThreadPool
1 简介 QThreadPool是Qt框架中的一个类,提供了一组工作线程池。该线程池自动管理一组工作线程,在线程可用时分配任务。使用线程池的主要优点是,它可以减少创建和销毁线程的开销,因为可以重复使用线程。 线程池设计用于场景中&am…...

【Java|golang】1138. 字母板上的路径
我们从一块字母板上的位置 (0, 0) 出发,该坐标对应的字符为 board[0][0]。 在本题里,字母板为board [“abcde”, “fghij”, “klmno”, “pqrst”, “uvwxy”, “z”],如下所示。 我们可以按下面的指令规则行动: 如果方格存…...
Flink 1.14从简单到源码第三讲
文章目录 1.flink多流操作Api1.1split 分流操作1.2.侧输出流1.3.connect 连接操作1.4.union 操作1.5 coGroup 协同分组1.6 join1.7 broadcast 广播2.process3.并行度和Api3.1 任务提交简单流程3.2 task与算子链4. Flink 时间相关(窗口计算)4.1时间语义(窗口计算)4.2 新版api指定…...

淘宝API接口系列,获取购买到的商品订单列表,卖出的商品订单列表,订单详情,订单物流,买家信息,收货地址列表,买家token
custom自定义API操作buyer_order_list获取购买到的商品订单列表buyer_order_detail获取购买到的商品订单详情buyer_order_express获取购买到的商品订单物流buyer_address_list收货地址列表buyer_address_add添加收货地址buyer_info买家信息buyer_token买家tokenseller_order_li…...

ucos-ii 的任务调度原理和实现
ucosii 任务调度和原理1、ucos-ii 任务创建与任务调度 1.1、任务的创建 当你调用 OSTaskCreate( ) 进行任务的创建的时候,会初始化任务的堆栈、保存cpu的寄存器、创建任务的控制块(OS_TCB)等的操作; if (OSTCBPrioTbl[prio] (OS_…...
Solon2 开发之容器,七、切面与函数环绕拦截
想要环绕拦截一个 Bean 的函数。需要三个前置条件: 通过注解做为“切点”,进行拦截(不能无缘无故给拦了吧?费性能)Bean 的 method 是被代理的在 Bean 被扫描之前,完成环绕拦截的注册 1、定义切点和注册环…...

代码随想录第十天(28)
文章目录28. 找出字符串中第一个匹配项的下标看答案KMPnext数组(前缀表)最长公共前后缀如何计算前缀表前缀表与next数组时间复杂度分析28. 找出字符串中第一个匹配项的下标 莫得思路……好久没做题,都已经忘得差不多了 看答案 其实就是自己…...

循环队列来了解一下!!
笔者在之前的一篇文章,详细的介绍了:队列之单向链表与双向链表的模拟实现:https://blog.csdn.net/weixin_64308540/article/details/128742090?spm1001.2014.3001.5502 感兴趣的各位老铁,可以参考一下啦!下面进入循环…...

Idea打包springboot项目war包,测试通过
pom.xml文件 <!--包名以及版本号,这个是打包时候使用,版本可写可不写,建议写有利于维护系统--> <artifactId>tsgdemo</artifactId> <version>0.0.1-SNAPSHOT</version> <!--打包形式--> <packaging&…...

python+django高校师生健康信息管理系统pycharm
管理员功能模块 4.1登录页面 管理员登录,通过填写注册时输入的用户名、密码、角色进行登录,如图所示。 4.2系统首页 管理员登录进入师生健康信息管理系统可以查看个人中心、学生管理、教师管理、数据收集管理、问卷分类管理、疫情问卷管理、问卷调查管理…...

CUDA中的流序内存分配
文章目录CUDA中的流序内存分配1. Introduction2. Query for Support3. API Fundamentals (cudaMallocAsync and cudaFreeAsync)4. Memory Pools and the cudaMemPool_t注意:设备的内存池当前将是该设备的本地。因此,在不指定内存池的情况下进行分配将始终…...

开源、低成本的 Xilinx FPGA 下载器(高速30MHz)
目前主流的Xilinx下载器主要有两种:一种是Xilinx官方出品的Xilinx Platfom Cable USB,还有一个就是Xilinx的合作伙伴Digilent开发的JTAG-HS3 Programming Cable。 JTAG-HS系列最大支持30MHz下载速度,基于FTDI的FT2232方案。 JTAG-HS系列对比…...

Maven专题总结
1. 什么是Maven Maven 是一个项目管理工具,它包含了一个项目对象模型 (POM: Project Object Model),一组标准集合,一个项目生命周期(Project Lifecycle),一个依赖管理系统(Dependency Management System),和…...

谷粒商城--SPU和SKU
目录 1.SPU和SKU概念 2.表的关系理解 3.导入前端代码 4.完善后端接口 5.属性分组详情 6.规格参数详情 7. 销售属性详情 8.分组与属性关联 9.发布商品 10.仓库服务 1.SPU和SKU概念 SPU:standard product unit(标准化产品单元):是商品信息聚合的…...

二叉树OJ题(上)
✅每日一练:100. 相同的树 - 力扣(LeetCode) 题目的意思是俩棵树的结构不仅要相同,而且每个节点的值还要相同,如果满足上面2个条件,则成立! 解题思路: 从三个方面去考虑࿱…...

第一章 PDF语法
第一章 PDF语法PDF ObjectsNull ObjectsBoolean ObjectsNumeric ObjectsName ObjectsString ObjectsArray ObjectsDictionary ObjectsName treesNumber treesStream ObjectsDirect versus Indirect ObjectsFile StructureWhite-SpaceThe Four Sections of a PDFHeaderTrailerBo…...

IntelliJ IDEA 创建JavaFX项目运行
IntelliJ IDEA 创建JavaFX项目运行JavaFX官网文档:https://openjfx.io/openjfx-docs/ JavaFX 2008年12月05日诞生,是一个开源的下一代客户端应用程序平台,适用于基于 Java 构建的桌面、移动和嵌入式系统。这是许多个人和公司的协作努力&#…...

IC封装常见形式
参考:https://blog.csdn.net/dhs888888/article/details/127673300?utm_mediumdistribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-127673300-blog-115610343.pc_relevant_multi_platform_whitelistv4&spm1001.2101.3001.4242…...

Linux通配符、转义符讲解
目录 通配符 通过通配符定义匹配条件 转义符 将所有的逻辑操作符都转换成字符 通配符 通过通配符定义匹配条件 * 任意字符都可以通配(也可以匹配空值) ? 匹配单个字符 [a-z] 匹配单个的小写英文字母 [A-Z] 匹配单个的大写英文…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...