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] 匹配单个的大写英文…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...
