C 语言高级3--函数指针回调函数,预处理,动态库的封装

目录

1.函数指针和回调函数

1.1 函数指针

1.1.1 函数类型

1.1.2 函数指针(指向函数的指针)

1.1.3 函数指针数组

       1.1.4 函数指针做函数参数(回调函数)

 2.预处理

2.1 预处理的基本概念

2.2 文件包含指令(#include)

2.2.1 文件包含处理

 2.2.2 #incude<>和#include""区别

2.3 宏定义

2.3.1 无参数的宏定义(宏常量)

2.3.2 带参数的宏定义(宏函数)

2.4 条件编译

2.4.1 基本概念

 2.4.2 条件编译

2.5 一些特殊的预定宏

3.动态库的封装和使用

3.1 库的基本概念

3.2 windows下静态库创建和使用

3.2.1 静态库的创建

3.2.2 静态库的使用

3.3 静态库优缺点

3.4 windows下动态库创建和使用

3.5 动态库的创建

3.6  动态库的使用

4. 递归函数

4.1 递归函数基本概念

4.2 普通函数调用

 4.3 递归函数调用

 4.4 递归实现字符串反转


1.函数指针和回调函数

1.1 函数指针

1.1.1 函数类型

通过什么来区分两个不同的函数?

一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址。

函数三要素: 名称、参数、返回值。C语言中的函数有自己特定的类型。

c语言中通过typedef为函数类型重命名:

typedef int f(int, int); // f 为函数类型

typedef void p(int); // p 为函数类型

这一点和数组一样,因此我们可以用一个指针变量来存放这个入口地址,然后通过该指针变量调用函数。

注意:通过函数类型定义的变量是不能够直接执行,因为没有函数体。只能通过类型定义一个函数指针指向某一个具体函数,才能调用。

typedef int(p)(int, int);void my_func(int a,int b){printf("%d %d\n",a,b);
}void test(){p p1;//p1(10,20); //错误,不能直接调用,只描述了函数类型,但是并没有定义函数体,没有函数体无法调用p* p2 = my_func;p2(10,20); //正确,指向有函数体的函数入口地址
}

 

1.1.2 函数指针(指向函数的指针)

  1.  函数指针定义方式(先定义函数类型,根据类型定义指针变量);
  2.  先定义函数指针类型,根据类型定义指针变量;
  3.  直接定义函数指针变量;
int my_func(int a,int b){printf("ret:%d\n", a + b);return 0;
}//1. 先定义函数类型,通过类型定义指针
void test01(){typedef int(FUNC_TYPE)(int, int);FUNC_TYPE* f = my_func;//如何调用?(*f)(10, 20);f(10, 20);
}//2. 定义函数指针类型
void test02(){typedef int(*FUNC_POINTER)(int, int);FUNC_POINTER f = my_func;//如何调用?(*f)(10, 20);f(10, 20);
}//3. 直接定义函数指针变量
void test03(){int(*f)(int, int) = my_func;//如何调用?(*f)(10, 20);f(10, 20);
}

1.1.3 函数指针数组

函数指针数组,每个元素都是函数指针。

void func01(int a){printf("func01:%d\n",a);
}
void func02(int a){printf("func02:%d\n", a);
}
void func03(int a){printf("func03:%d\n", a);
}void test(){#if 0//定义函数指针void(*func_array[])(int) = { func01, func02, func03 };
#elsevoid(*func_array[3])(int);func_array[0] = func01;func_array[1] = func02;func_array[2] = func03;
#endiffor (int i = 0; i < 3; i ++){func_array[i](10 + i);(*func_array[i])(10 + i);}
}

       1.1.4 函数指针做函数参数(回调函数)

函数参数除了是普通变量,还可以是函数指针变量。

//形参为普通变量
void fun( int x ){}
//形参为函数指针变量
void fun( int(*p)(int a) ){}

函数指针变量常见的用途之一是把指针作为参数传递到其他函数,指向函数的指针也可以作为参数,以实现函数地址的传递。

int plus(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int division(int a, int b)
{return a / b;
}//函数指针 做函数的参数 --- 回调函数
void Calculator(int(*myCalculate)(int, int), int a, int b)
{int ret = myCalculate(a, b); //dowork中不确定用户选择的内容,由后期来指定运算规则printf("ret = %d\n", ret);
}void test01()
{printf("请输入操作符\n");printf("1、+ \n");printf("2、- \n");printf("3、* \n");printf("4、/ \n");int select = -1;scanf("%d", &select);int num1 = 0;printf("请输入第一个操作数:\n");scanf("%d", &num1);int num2 = 0;printf("请输入第二个操作数:\n");scanf("%d", &num2);switch (select){case  1:Calculator(plus, num1, num2);break;case  2:Calculator(sub, num1, num2);break;case 3:Calculator(mul, num1, num2);break;case 4:Calculator(division, num1, num2);break;default:break;}}

注意:函数指针和指针函数的区别:

        1函数指针是指向函数的指针;

        2指针函数是返回类型为指针的函数;

 2.预处理

2.1 预处理的基本概念

C语言对源程序处理的四个步骤:预处理、编译、汇编、链接。

预处理是在程序源代码被编译之前,由预处理器(Preprocessor)对程序源代码进行的处理。这个过程并不对程序的源代码语法进行解析,但它会把源代码分割或处理成为特定的符号为下一步的编译做准备工作。

2.2 文件包含指令(#include)

2.2.1 文件包含处理

“文件包含处理”是指一个源文件可以将另外一个文件的全部内容包含进来。C语言提供了#include命令用来实现“文件包含”的操作。

 2.2.2 #incude<>和#include""区别

  1. "" 表示系统在file1.c所在的当前目录找file1.h,如果找不到,按系统指定的目录检索。
  2. < > 表示系统直接按系统指定的目录检索。

注意:

1. #include <>常用于包含库函数的头文件;

2. #include ""常用于包含自定义的头文件;

3. 理论上#include可以包含任意格式的文件(.c .h等) ,但一般用于头文件的包含;

2.3 宏定义

2.3.1 无参数的宏定义(宏常量)

如果在程序中大量使用到了100这个值,那么为了方便管理,我们可以将其定义为:

const int num = 100; 但是如果我们使用num定义一个数组,在不支持c99标准的编译器上是不支持的,因为num不是一个编译器常量,如果想得到了一个编译器常量,那么可以使用:

#define num 100

在编译预处理时,将程序中在该语句以后出现的所有的num都用100代替。这种方法使用户能以一个简单的名字代替一个长的字符串,在预编译时将宏名替换成字符串的过程称为“宏展开。宏定义,只在宏定义的文件中起作用。

#define PI 3.1415
void test(){double r = 10.0;double s = PI * r * r;printf("s = %lf\n", s);
}

 

说明:

  1. 宏名一般用大写,以便于与变量区别;
  2. 宏定义可以是常数、表达式等;
  3. 宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错;
  4. 宏定义不是C语言,不在行末加分号;
  5. 宏名有效范围为从定义到本源文件结束;
  6. 可以用#undef命令终止宏定义的作用域;

在宏定义中,可以引用已定义的宏名;

2.3.2 带参数的宏定义(宏函数)

在项目中,经常把一些短小而又频繁使用的函数写成宏函数,这是由于宏函数没有普通函数参数压栈、跳转、返回等的开销,可以调高程序的效率。宏通过使用参数,可以创建外形和作用都与函数类似地类函数宏(function-like macro). 宏的参数也用圆括号括起来。

#define SUM(x,y) (( x )+( y ))
void test(){//仅仅只是做文本替换 下例替换为 int ret = ((10)+(20));//不进行计算int ret = SUM(10, 20);printf("ret:%d\n",ret);
}

注意:

  1. 宏的名字中不能有空格,但是在替换的字符串中可以有空格。ANSI C允许在参数列表中使用空格;
  2. 用括号括住每一个参数,并括住宏的整体定义。
  3. 用大写字母表示宏的函数名。
  4. 如果打算宏代替函数来加快程序运行速度。假如在程序中只使用一次宏对程序的运行时间没有太大提高。

2.4 条件编译

2.4.1 基本概念

一般情况下,源程序中所有的行都参加编译。但有时希望对部分源程序行只在满足一定条件时才编译,即对这部分源程序行指定编译条件。

 2.4.2 条件编译

防止头文件被重复包含引用;

#ifndef _SOMEFILE_H
#define _SOMEFILE_H//需要声明的变量、函数
//宏定义
//结构体#endif

2.5 一些特殊的预定宏

C编译器,提供了几个特殊形式的预定义宏,在实际编程中可以直接使用,很方便。

//	__FILE__			宏所在文件的源文件名 
//	__LINE__			宏所在行的行号
//	__DATE__			代码编译的日期
//	__TIME__			代码编译的时间void test()
{printf("%s\n", __FILE__);printf("%d\n", __LINE__);printf("%s\n", __DATE__);printf("%s\n", __TIME__);
}

3.动态库的封装和使用

3.1 库的基本概念

库是已经写好的、成熟的、可复用的代码。每个程序都需要依赖很多底层库,不可能每个人的代码从零开始编写代码,因此库的存在具有非常重要的意义。在我们的开发的应用中经常有一些公共代码是需要反复使用的,就把这些代码编译为库文件。库可以简单看成一组目标文件的集合,将这些目标文件经过压缩打包之后形成的一个文件。像在Windows这样的平台上,最常用的c语言库是由集成按开发环境所附带的运行库,这些库一般由编译厂商提供。

3.2 windows下静态库创建和使用

3.2.1 静态库的创建

1. 创建一个新项目,在已安装的模板中选择“常规”,在右边的类型下选择“空项目”,在名称和解决方案名称中输入staticlib。点击确定。

2.在解决方案资源管理器的头文件中添加,mylib.h文件,在源文件添加mylib.c文件(即实现文件)。

3.在mylib.h文件中添加如下代码:

#ifndef TEST_H
#define TEST_Hint myadd(int a,int b);
#endif

4.在mylib.c文件中添加如下代码:

        

#include"test.h"
int myadd(int a, int b){return a + b;
}

5. 配置项目属性。因为这是一个静态链接库,所以应在项目属性的“配置属性”下选择“常规”,在其下的配置类型中选择“静态库(.lib)。

6.编译生成新的解决方案,在Debug文件夹下会得到mylib.lib (对象文件库),将该.lib文件和相应头文件给用户,用户就可以使用该库里的函数了。

3.2.2 静态库的使用

方法一:配置项目属性

A、添加工程的头文件目录:工程---属性---配置属性---c/c++---常规---附加包含目录:加上头文件存放目录。

B、添加文件引用的lib静态库路径:工程---属性---配置属性---链接器---常规---附加库目录:加上lib文件存放目录。

C  然后添加工程引用的lib文件名:工程---属性---配置属性---链接器---输入---附加依赖项:加上lib文件名。

 方法二:使用编译语句

#pragma comment(lib,"./mylib.lib")

 方法三:添加工程中

就像你添加.h和.c文件一样,把lib文件添加到工程文件列表中去.

切换到"解决方案视图",--->选中要添加lib的工程-->点击右键-->"添加"-->"现有项"-->选择lib文件-->确定.

3.3 静态库优缺点

  1. 静态库对函数库的链接是放在编译时期完成的,静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系;
  2. 程序在运行时与函数库再无瓜葛,移植方便。
  3. 浪费空间和资源,所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。

内存和磁盘空间

静态链接这种方法很简单,原理上也很容易理解,在操作系统和硬件不发达的早期,绝大部门系统采用这种方案。随着计算机软件的发展,这种方法的缺点很快暴露出来,那就是静态链接的方式对于计算机内存和磁盘空间浪费非常严重。特别是多进程操作系统下,静态链接极大的浪费了内存空间。在现在的linux系统中,一个普通程序会用到c语言静态库至少在1MB以上,那么如果磁盘中有2000个这样的程序,就要浪费将近2GB的磁盘空间。

 

程序开发和发布

空间浪费是静态链接的一个问题,另一个问题是静态链接对程序的更新、部署和发布也会带来很多麻烦。比如程序中所使用的mylib.lib是由一个第三方厂商提供的,当该厂商更新容量mylib.lib的时候,那么我们的程序就要拿到最新版的mylib.lib,然后将其重新编译链接后,将新的程序整个发布给用户。这样的做缺点很明显,即一旦程序中有任何模块更新,整个程序就要重新编译链接、发布给用户,用户要重新安装整个程序。

3.4 windows下动态库创建和使用

要解决空间浪费和更新困难这两个问题,最简单的办法就是把程序的模块相互分割开来,形成独立的文件,而不是将他们静态的链接在一起。简单地讲,就是不对哪些组成程序的目标程序进行链接,等程序运行的时候才进行链接。也就是说,把整个链接过程推迟到了运行时再进行,这就是动态链接的基本思想。

3.5 动态库的创建

1. 创建一个新项目,在已安装的模板中选择“常规”,在右边的类型下选择“空项目”,在名称和解决方案名称中输入mydll。点击确定。

2.在解决方案资源管理器的头文件中添加,mydll.h文件,在源文件添加mydll.c文件(即实现文件)。

3.在test.h文件中添加如下代码:

#ifndef TEST_H
#define TEST_H__declspec(dllexport) int myminus(int a, int b);#endif

test.c文件中添加如下代码:

#include"test.h"
__declspec(dllexport) int myminus(int a, int b){return a - b;
}

5. 配置项目属性。因为这是一个态链接库,所以应在项目属性的“配置属性”下选择“常规”,在其下的配置类型中选择“态库(.dll)。

6.编译生成新的解决方案,在Debug文件夹下会得到mydll.dll (对象文件库),将该.dll文件、.lib文件和相应头文件给用户,用户就可以使用该库里的函数了。

疑问一:__declspec(dllexport)是什么意思?

动态链接库中定义有两种函数:导出函数(export  function)和内部函数(internal  function)。 导出函数可以被其它模块调用,内部函数在定义它们的DLL程序内部使用。

疑问二:动态库的lib文件和静态库的lib文件的区别?

在使用动态库的时候,往往提供两个文件:一个引入库(.lib)文件(也称“导入库文件”)和一个DLL(.dll)文件。虽然引入库的后缀名也是“lib”,但是,动态库的引入库文件和静态库文件有着本质的区别,对一个DLL文件来说,其引入库文件(.lib)包含该DLL导出的函数和变量的符号名,而.dll文件包含该DLL实际的函数和数据。在使用动态库的情况下,在编译链接可执行文件时,只需要链接该DLL的引入库文件,该DLL中的函数代码和数据并不复制到可执行文件,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间中,然后访问DLL中导出的函数。

3.6  动态库的使用

方法一:隐式调用

     创建主程序TestDll,将mydll.h、mydll.dll和mydll.lib复制到源代码目录下。

(P.S:头文件Func.h并不是必需的,只是C++中使用外部函数时,需要先进行声明)

在程序中指定链接引用链接库 : #pragma comment(lib,"./mydll.lib")

方法二:显式调用

HANDLE hDll; //声明一个dll实例文件句柄

hDll = LoadLibrary("mydll.dll"); //导入动态链接库

MYFUNC minus_test; //创建函数指针

//获取导入函数的函数指针

minus_test = (MYFUNC)GetProcAddress(hDll, "myminus");

 

4. 递归函数

4.1 递归函数基本概念

C通过运行时堆栈来支持递归函数的实现。递归函数就是直接或间接调用自身的函数。

4.2 普通函数调用

void funB(int b){printf("b = %d\n", b);
}void funA(int a){funB(a - 1);printf("a = %d\n", a);
}int main(void){funA(2);printf("main\n");return 0;
}

函数的调用流程如下:

 4.3 递归函数调用

void fun(int a){if (a == 1){printf("a = %d\n", a);return; //中断函数很重要}fun(a - 1);printf("a = %d\n", a);
}int main(void){fun(2);printf("main\n");return 0;
}

函数的调用流程如下:

 

作业:

递归实现给出一个数8793,依次打印千位数字8、百位数字7、十位数字9、个位数字3。

void recursion(int val){

if (val == 0){

return;

}

int ret = val / 10;

recursion(ret);

printf("%d ",val % 10);

}

 4.4 递归实现字符串反转

int reverse1(char *str){if (str == NULL){return -1;}if (*str == '\0') // 函数递归调用结束条件{return 0;}reverse1(str + 1);printf("%c", *str);return 0;
}char buf[1024] = { 0 };  //全局变量int reverse2(char *str){if (str == NULL) {return -1;}if ( *str == '\0' ) // 函数递归调用结束条件{return 0;}reverse2(str + 1);strncat(buf, str, 1);return 0;
}int reverse3(char *str, char *dst){if (str == NULL || dst == NULL) {return -1;}if (*str == '\0') // 函数递归调用结束条件{return 0;}reverse3(str + 1);strncat(dst, str, 1);return 0;
}

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

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

相关文章

Jmeter之BeanShell取出参数进行四则运算,并判断是否正确

首先调用余额接口&#xff0c;使用正则提取响应中的余额字段&#xff0c;记作变量acctBal1做支付交易再次调用余额接口&#xff0c;使用正则提取响应中的余额字段&#xff0c;记作变量acctBal2最后在结果树中可以看到断言错误的信息&#xff0c;断言正确时没有提示以下是beansh…

Elasticsearch使用中出现的错误

Elasticsearch使用中出现的错误 1、分页查询异常 在分页的过程中出现了一个问题是当查询的数据超过10000条的时候报了异常&#xff1a; from size must be less than or equal to: [10000]这个问题最快捷的解决方式是增大窗口大小&#xff1a; curl -XPUT http://127.0.0.…

全角字符和半角字符

全角字符的由来 全角符号是双字节中文编码的历史遗留问题。当年在纯文本的界面中&#xff0c;为了让西文和中日韩的方块字对齐&#xff0c;就让西文字母、数字和标点也占用一个汉字的视觉空间&#xff0c;并使用 2 个字节存储。后来&#xff0c;其中的一些全角字符因为比较有用…

C++ 线性群体的概念

线性群体中的元素次序与其位置关系是对应的。 在线性群体中&#xff0c;可以按照访问元素的不同方法分为直接访问、顺序访问和索引访问。 &#xff08;1&#xff09;直接访问 对可直接访问的线性群体&#xff0c;我们可以直接访问群体中的任何一个元素&#xff0c;而不必首先访…

npm 报错 cb() never called!

不知道有没有跟我一样的情况&#xff0c;在使用npm i的时候一直报错&#xff1a;cb() never called! 换了很多个node版本&#xff0c;还是不行&#xff0c;无法解决这个问题 百度也只是让降低node版本请缓存&#xff0c;gpt给出的解决方案也是同样的 但是缓存清过很多次了&a…

修改 Ubuntu 系统的时区

修改 Ubuntu 系统的时区 如果 Ubuntu 系统的时区设置不正确&#xff0c;您可以按照以下步骤进行调整&#xff1a; 1. 查看当前的时区设置&#xff0c;可以使用以下命令&#xff1a; timedatectl 这将显示当前系统的日期、时间和时区信息。 2. 如果时区设置不正…

252_BOOST_线程中的定时器使用,使用【Boost.Asio类】来完成这个异步操作,从而不影响线程中的其他操作

大致整体解读 这段代码使用了 Boost 库的一些功能,主要集中在 Boost.Asio 部分。下面我会详细解释每个使用到的 Boost 函数的功能。boost::asio::io_context: 这是 Boost.Asio 提供的核心类,用于实现异步 I/O 操作和事件驱动编程。它提供了事件循环机制,可以处理异步操作、…

Python中enumerate用法详解

目录 1.简介 2.语法 3.参数 4.返回值 5.详解 6.实例 7.补充 1.简介 enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列&#xff0c;同时列出数据和数据下标&#xff0c;一般用在 for 循环当中。 2.语法 以下是 enumerate() 方法的语…

Linux 匿名页的生命周期

目录 匿名页的生成 匿名页生成时的状态 do_anonymous_page缺页中断源码 从匿名页加入Inactive lru引出 一个非常重要内核patch 匿名页何时回收 本文以Linux5.9源码讲述 匿名页的生成 用户空间malloc/mmap(非映射文件时&#xff09;来分配内存&#xff0c;在内核空间发生…

【小梦C嘎嘎——启航篇】类和对象(中篇)

【小梦C嘎嘎——启航篇】类和对象&#xff08;中篇&#xff09;&#x1f60e; 前言&#x1f64c;类的6个默认成员函数构造函数析构函数拷贝构造函数拷贝构造函数的特性有哪些&#xff1f;既然编译器可以自动生成一个拷贝构造函数&#xff0c;为什么我们还要自己设计实现呢&…

sql 分组讨论,二级分组(非2个字段分组),使用 窗口函数和普通分组实现

1. 二级分组需求 先按照一个字段分组&#xff0c;在按照 第二个字段分组。之后&#xff0c;如果 这个 二级分组中的数据&#xff0c;是 > 1条的。就筛选出来。 比如&#xff1a; 先按照 站点分组&#xff0c;再按照 设备分组&#xff0c; 即&#xff1a;如果站点上配置了…

【腾讯云 Cloud Studio 实战训练营】使用Cloud Studio构建SpringSecurity权限框架

1.Cloud Studio&#xff08;云端 IDE&#xff09;简介 Cloud Studio 是基于浏览器的集成式开发环境&#xff08;IDE&#xff09;&#xff0c;为开发者提供了一个永不间断的云端工作站。用户在使用 Cloud Studio 时无需安装&#xff0c;随时随地打开浏览器就能在线编程。 Clou…

Spring 知识点

Spring 1.1 Spring 简介 1.1.1 Spring 概念 Spring是一个轻量级Java开发框架&#xff0c;最早有Rod Johnson创建为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题Spring最根本的使命是解决企业级应用开发的复杂性&#xff0c;即简化Java开发。使现有的技术更加容易使…

Linux下进程的特点与环境变量

目录 进程的特点 进程特点的介绍 进程时如何实现并发性的 进程间如何切换 概念铺设 PC指针 上下文 环境变量 PATH 修改PATH HOME SHELL env 命令行参数 什么是命令行参数&#xff1f; 打印命令行参数 通过函数获得环境变量 getenv 命令行参数 env 修改环境变…

Ajax_3 Ajax原理+ (XMLHttpRequest + Promise )+ 封装一个axios插件库,实现功能。

Ajax_3 Ajax原理 01-Ajax原理-XMLHttpRequest 使用XMLHttpRequest 步骤&#xff1a; 创建XMLHttpRequest对象配置请求方法请求url网址监听loadend事件&#xff0c;接受响应结果发起请求 需求&#xff1a;使用XMLHttpRequest对象与服务器通信 代码示例 // 1. 创建 XMLHttpReq…

SpringBoot 项目使用 Redis 对用户 IP 进行接口限流

一、思路 使用接口限流的主要目的在于提高系统的稳定性&#xff0c;防止接口被恶意打击&#xff08;短时间内大量请求&#xff09;。 比如要求某接口在1分钟内请求次数不超过1000次&#xff0c;那么应该如何设计代码呢&#xff1f; 下面讲两种思路&#xff0c;如果想看代码可…

一分钟了解下Java追随和适应云原生的手段之Java Native Build(JNB)

文章首发地址 为了解决在云原生环境中&#xff0c;Java应用启动慢的问题&#xff0c;出现了很多派系&#xff0c;如拯救派&#xff0c;让应用在原有基础上启动更快&#xff08;一般都是用资源换时间&#xff09;&#xff0c;还有就是革命派&#xff0c;Java向Golang学习&#x…

MySql用户管理、权限管理

用户管理 1. 查看系统用户&#xff08;查询mysql系统数据库中的user表&#xff09; select * from mysql.user; 2. 创建用户 CREATE USER 用户名主机名 identified by 密码 -- 创建用户zhonghua,只能在当前主句localhost访问,密码为123456 create user zhonghualocalhost i…

springCache-缓存

SpringCache 简介&#xff1a;是一个框架&#xff0c;实现了基于注解的缓存功能&#xff0c;底层可以切换不同的cache的实现&#xff0c;具体是通过CacheManager接口实现 使用springcache,根据实现的缓存技术&#xff0c;如使用的redis,需要导入redis的依赖包 基于map缓存 …

MySQL 查询语句大全

目录 基础查询 直接查询 AS起别名 去重&#xff08;复&#xff09;查询 条件查询 算术运算符查询 逻辑运算符查询 正则表达式查询⭐ 模糊查询 范围查询 是否非空判断查询 排序查询 限制查询&#xff08;分页查询&#xff09; 随机查询 分组查询 HAVING 高级查询…