C语言 13 指针

指针可以说是整个 C 语言中最难以理解的部分了。

什么是指针

还记得在前面谈到的通过函数交换两个变量的值吗?

#include <stdio.h>void swap(int, int);int main() {int a = 10, b = 20;swap(a, b);printf("a = %d, b = %d", a, b);
}void swap(int a, int b){// 这里对a和b的值进行交换int tmp = a;   a = b;b = tmp;
}

实际上这种写法是错误的,因为交换的并非是真正的 a 和 b,而是函数中的局部变量。

那么有没有办法能够直接对函数外部的变量进行操作呢?这就需要指针的帮助了。

程序中使用的变量实际上都是在内存中创建的,每个变量都会被保存在内存的某一个位置上(具体哪个位置由系统分配),所有的变量在对应的内存位置上都有一个地址(地址是独一无二的),可以通过这个地址寻找到这个变量本体,比如 int 占据 4 字节,因此 int 类型变量的地址就是这 4 个字节的起始地址,后面 32 个 bit 位全部都是用于存放此变量的值的。

这里的0x是十六进制的表示形式(10 - 15 用字母 A - F 表示)

如果能够知道变量的内存地址,那么无论身在何处,都可以通过地址找到这个变量了。

而指针的作用,就是专门用来保存这个内存地址的。

来看看如何创建一个指针变量用于保存变量的内存地址:

#include <stdio.h>int main() {int a = 10;// 指针类型需要与变量的类型相同,且后面需要添加一个*符号(注意这里不是乘法运算),表示是对于类型的指针// 这里的&并不是进行按位与运算,而是取地址操作,也就是拿到变量a的地址int* p = &a;                         // 地址使用%p表示printf("a在内存中的地址为:%p", p);  
}
a在内存中的地址为:00000000005ffe84

可以看到,通过取地址操作&,将变量 a 的地址保存到了一个地址变量p中。

拿到指针之后,就可以很轻松地获取指针所指地址上的值:

#include <stdio.h>int main() {int a = 666;int* p = &a;// 可以在指针变量前添加一个*号(间接运算符,也可以叫做解引用运算符)来获取对应地址存储的值printf("内存%p上存储的值为:%d", p, *p);  
}
内存00000000005ffe84上存储的值为:666

注意这里访问指针所指向地址的值时,是根据类型来获取的,比如 int 类型占据 4 个字节,那么就读取地址后面 4 个字节的内容作为一个 int 值,如果指针是 char 类型的,那么就只读取地址后面 1 个字节作为 char 类型的值。

同样的,也可以直接像这样去修改对应地址存放的值:

#include <stdio.h>int main() {int a = 666;int* p = &a;// 通过*来访问对应地址的值,并通过赋值运算对其进行修改*p = 999;  printf("a的值为:%d", a);
}
a的值为:999

实际上拿到一个变量的地址之后,完全不需要再使用这个变量,而是可以通过它的指针来对其进行各种修改。

因此,现在想要实现对两个变量的值进行交换的函数就很简单了:

#include <stdio.h>// 这里是两个指针类型的形参,其值为实参传入的地址,
// 虽然依然是值传递,但是这里传递的是地址
// 只要知道地址改变值就很容易了
void swap(int* a, int* b) {// 先暂存一下变量a地址上的值int tmp = *a;  // 将变量b地址上的值赋值给变量a地址上的值*a = *b;       // 最后将a的值赋值给b地址上的值,这样就成功交换两个变量的值了*b = tmp;      
}int main() {int a = 10, b = 20;// 只需要把a和b的内存地址给过去就行了,这里取一下地址swap(&a, &b);  printf("a = %d, b = %d", a, b);
}
a = 20, b = 10

通过地址操作,就轻松实现了使用函数交换两个变量的值了。


了解了指针的相关操作之后,再来看看scanf函数,实际上就很好理解了:

#include <stdio.h>int main(){int a;// 这里就是取地址,需要告诉scanf函数变量的地址,这样它才能通过指针访问变量的内存地址,对变量的值进行修改,这也是为什么scanf里面的变量(除数组外)前面都要进行一个取地址操作scanf("%d", &a);   printf("%d", a);
}

当然,和变量一样,要是不给指针变量赋初始值的话,就不知道指向哪里了,因为指针变量也是变量,存放的对应变量的地址值也在内存中保存,如果不给初始值,那么存放变量地址的这块内存可能在其他地方使用过,这样就不知道初始值是多少了(那么指向的地址可能是一个很危险的地址,随意使用可能导致会出现严重错误),所以一定要记得给个初始值或是将其设定为 NULL,表示空指针,不指向任何内容。

#include <stdio.h>int main(){int* a = NULL;
}

接着来看看const类型的指针,这种指针比较特殊:

#include <stdio.h>int main() {int a = 9, b = 10;const int* p = &a;// 报错,因为被const标记的指针,所指地址上的值不允许发生修改*p = 20;// 但是指针指向的地址是可以发生改变的p = &b;
}

再来看另一种情况:

#include <stdio.h>int main() {int a = 9, b = 10;// const关键字被放在了类型后面int* const p = &a;// 允许修改所指地址上的值*p = 20;// 报错,不允许修改指针存储的地址值,其实就是反过来了p = &b;
}

当然也可以双管齐下:

#include <stdio.h>int main(){int a = 9, b = 10;const int * const p = &a;*p = 20;   //两个都直接报错,都不让改了p = &b;
}

指针与数组

前面介绍了指针的基本使用,来回顾一个问题,为什么数组可以原身在函数之间进行传递呢?

先说结论,数组表示法实际上是在变相地使用指针,甚至可以理解为数组变量其实就是一个指针变量,它存放的就是数组中第一个元素的起始地址

为什么这么说?

#include <stdio.h>int main() {char str[] = "Hello World!";// 为什么能直接把数组作为地址赋值给指针变量char* p = str;  // 还能正常使用,打印出第一个字符printf("%c", *p);  
}
H

还能这样玩:

int main() {char str[] = "Hello World!";char* p = str;// 还可以像在使用数组一样用指针printf("%c", p[1]);
}
e

怎么数组和指针还能这样混着用呢?先来看看数组在内存中是如何存放的:

数组在内存中是一块连续的空间,所以为什么声明数组一定要明确类型和大小,因为这一块连续的内存空间生成后就固定了。

而数组变量实际上存放的就是首元素的地址,而实际上之前一直使用的都是数组表示法来操作数组,这样可以很方便地对内存中的各个元素值进行操作:

int main(){char str[] = "Hello World!";// 直接在中括号中输入对应的下标就能访问对应位置上的数组了printf("%c", str[0]);   
}

而实际上str表示的就是数组的首地址,所以完全可以将其赋值给一个指针变量,因为指针变量也是存放的地址:

char str[] = "Hello World!";
// 直接把str代表的首元素地址给到p
char* p = str;   

而使用指针后,实际上可以使用另一种表示法来操作数组,这种表示法叫做指针表示法

#include <stdio.h>int main() {char str[] = "Hello World!";char* p = str;// 通过指针也可以表示对应位置上的值printf("第一个元素值为:%c,第二个元素值为:%c", *p, *(p + 1));
}
第一个元素值为:H,第二个元素值为:e

比如现在需要表示数组中的第二个元素:

  • 数组表示法:str[1]
  • 指针表示法:*(p+1)

虽然写法不同,但是他们表示的意义是完全相同的,都代表了数组中的第二个元素,其中指针表示法使用了p+1的形式表示第二个元素,这里的+1操作并不是让地址+1,而是让地址+ 一倍的对应类型大小,也就是说地址后移一个char 的长度,所以正好指向了第二个元素,然后通过*取到对应的值(注意这种操作仅对数组是有意义的,如果是普通的变量,虽然也可以获得后一个 char 的长度的数据,但是毫无意义)

这两种表示法都可以对内存中存放的数组内容进行操作,只是写法不同罢了,所以数组和指针混用也就不奇怪了。

了解了这些东西之后,再来看看下面的各个表达式分别代表什么:

#include <stdio.h>int main() {char str[] = "Hello World!";char* p = str;// 数组的第一个元素printf("*p的值:%c\n", *p);   // 数组的第一个元素的地址printf("p的值:%p\n", p);// 肯定是真,因为都是数组首元素地址printf("p == str的值:%d\n", p == str);   // 因为str就是首元素的地址,所以这里对地址加*就代表第一个元素,使用的是指针表示法printf("*str的值:%c\n", *str);    // 这里得到的实际上还是首元素的地址printf("&str[0]的值:%p\n", &str[0]);   // 代表第二个元素printf("*(p + 1)的值:%c\n", *(p + 1));   // 第二个元素的内存地址printf("p + 1的值:%p\n", p + 1);    // 注意*的优先级比+要高,所以这里代表的是首元素的值+1,得到字符'I'printf("*p + 1的值:%c\n", *p + 1);    
}
*p的值:H
p的值:00000000005ffe7b
p == str的值:1
*str的值:H
&str[0]的值:00000000005ffe7b
*(p + 1)的值:e
p + 1的值:00000000005ffe7c
*p + 1的值:I

所以不难理解,为什么printf函数的第一个参数是const char*了,实际上就是需要传入一个字符串而已,只不过这里采用的是指针表示法而已。

当然指针也可以进行自增和自减操作,比如:

#include <stdio.h>int main() {char str[] = "Hello World!";char* p = str;// 自增后相当于指针指向了第二个元素的地址p++;// 所以这里打印的就是第二个元素的值了printf("%c", *p);
}
e

一维数组看完了,再来看看二维数组,那么二维数组在内存中是如何表示的呢?

int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

这是一个2x3的二维数组,其中存放了两个能够容纳三个元素的数组,在内存中,是这样的:

所以也可以使用指针来进行访问:

#include <stdio.h>int main() {int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};// 因为是二维数组,注意这里要指向第一个元素,需要降一个维度才能正确给到指针int* p = arr[0];  // 同理如果这里是arr[1]的话那么就表示指向二维数组中第二个数组的首元素// 实际上这两种访问形式都是一样的printf("%d = %d", *(p + 4), arr[1][1]);  
}
5 = 5

多级指针

实际上指针本身也是一个变量,它存放的是目标的地址,但是它本身作为一个变量,也要将地址信息保存到内存中,所以,实际上当有指针之后:

实际上,还可以继续创建一个指向指针变量地址的指针,甚至可以创建更多级(比如指向指针的指针的指针)

比如现在要创建一个指向指针的指针:

落实到代码中:

#include <stdio.h>int main() {int a = 20;// 指向普通变量的指针int* p = &a;// 因为现在要指向一个int *类型的变量,所以类型为int* 再加一个*// 指向指针的指针(二级指针)int** pp = &p;// 指向指针的指针的指针(三级指针)int*** ppp = &pp;// 使用一次*表示二级指针指向的指针变量,继续使用一次*会继续解析成指针变量所指的普通变量printf("p = %p, a = %d", *pp, **pp);  
}
p = 00000000005ffe84, a = 20

本质其实就是一个套娃而已,只要把各个层次分清楚,实际上还是很好理解的。

特别提醒: 一级指针可以操作一维数组,那么二级指针是否可以操作二维数组呢?不能!因为二级指针的含义都不一样了,它是表示指针的指针,而不是表示某个元素的指针了。下面会认识数组指针,准确的说它才更贴近于二维数组的形式。

指针数组与数组指针

前面了解了指针的一些基本操作,包括它与数组的一些关系。接着来看指针数组和数组指针,这两词语看着就容易搞混,不过哪个词在后面就哪个,先来看指针数组,虽然名字很像数组指针,但是它本质上是一个数组,不过这个数组是用于存放指针的数组。

#include <stdio.h>int main() {int a, b, c;// 可以看到,实际上本质还是数组,只不过存的都是地址int* arr[3] = {&a, &b, &c};// []运算符的优先级更高,所以这里先通过[0]取出地址,然后再使用*将值赋值到对应的地址上*arr[0] = 999;  printf("%d", a);
}

当然也可以用二级指针变量来得到指针数组的首元素地址:

#include <stdio.h>int main(){int * p[3];   //因为数组内全是指针int ** pp = p;  //所以可以直接使用指向指针的指针来指向数组中的第一个指针元素
}

实际上指针数组还是很好理解的,那么数组指针呢?可以看到指针在后,说明本质是一个指针,不过这个指针比较特殊,它是一个指向数组的指针(注意它的目标是整个数组,和之前认识的指针不同,之前认识的指针是指向某种类型变量的指针)

数组指针表示指向整个数组:

// 注意这里需要将*p括起来,因为[]的优先级更高
int (*p)[3];   

注意它的目标是整个数组,而不是普通的指针那样指向的是数组的首个元素:

int arr[3] = {111, 222, 333};
// 直接对整个数组再取一次地址(因为数组指针代表的是整个数组的地址,虽然和普通指针一样都是指向首元素地址,但是意义不同)
int (*p)[3] = &arr;  

那么现在已经取到了指向整个数组的指针,该怎么去使用呢?

#include <stdio.h>int main() {int arr[3] = {111, 222, 333};// 直接对整个数组再取一次地址int(*p)[3] = &arr;  // 要获取数组中的每个元素,稍微有点麻烦printf("%d, %d, %d", *(*p + 0), *(*p + 1), *(*p + 2));  
}
111, 222, 333

注意此时:

  • p代表整个数组的地址
  • *p表示所指向数组中首元素的地址
  • *p + i表示所指向数组中第i个(0 开始)元素的地址(实际上这里的 *p 就是指向首元素的指针)
  • *(*p + i)就是取对应地址上的值了

虽然在处理一维数组上感觉有点麻烦,但是它同样也可以处理二维数组:

#include <stdio.h>int main() {int arr[][3] = {{111, 222, 333}, {444, 555, 666}};// 二维数组不需要再取地址了,因为现在维度提升,数组指针指向的是二维数组中的其中一个元素(因为元素本身就是一个数组)int (*p)[3] = arr;// 现在想要访问第一个数组的第二个元素// 因为上面直接指向的就是第一个数组,所以想要获取第一个数组的第二个元素和之前是一模一样的printf("%d\n", *(*p + 1));// 现在想要获取第二个数组中的最后一个元素// 首先*(p + 1)为一个整体,表示第二个数组(因为是数组指针,所以这里 +1 一次性跳一个数组的长度),然后再到外层 +2 表示数组中的第三个元素,最后再取地址,就是第二个数组的第三个元素了printf("%d\n", *(*(p + 1) + 2));// 当然也可以使用数组表示法// 这就是二维数组的用法,甚至可以认为这两个是同一个东西printf("%d\n", p[1][2]);
}
222
666
666

指针函数与函数指针

函数可以返回一个指针类型的结果,这种函数就称为指针函数

#include <stdio.h>// 函数的返回值类型是int*指针类型的
int* test(int* a) {  return a;
}int main() {int a = 10;// 使用指针去接受函数的返回值int* p = test(&a);  printf("%d\n", *p);// 当然也可以直接把间接运算符在函数调用前面表示直接对返回的地址取地址上的值printf("%d\n", *test(&a));  
}
10
10

不过要注意指针函数不要尝试去返回一个局部变量的地址:

#include <stdio.h>int* test(int a) {int i = a;// 返回局部变量i的地址return &i;
}int main() {// 连续调用两次test函数int* p = test(20);  test(30);// 这里会报错printf("%d", *p);
}

为什么会这样呢?因为函数一旦返回,那么其中的局部变量就会全部销毁了,至于这段内存之后又会被怎么去使用,就不得而知了。


接着来看函数指针,实际上指针除了指向一个变量之外,也可以指向一个函数,当然函数指针本身还是一个指针,所以依然是用变量表示,但是它代表的是一个函数的地址(编译时系统会为函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址)

来看看如何定义:

#include <stdio.h>int sum(int a, int b) {return a + b;
}int main() {// 类型 (*指针变量名称)(函数参数...)  // 注意一定要把*和指针变量名称括起来,不然优先级不够int (*p)(int, int) = sum;printf("%p", p);
}
00007ff6524713b4

这样就拿到了函数的地址,既然拿到函数的地址,就可以通过函数的指针调用这个函数了:

#include <stdio.h>int sum(int a, int b) {return a + b;
}int main() {int (*p)(int, int) = sum;// 就像正常使用函数那样,(*p)表示这个函数,后面依然是在小括号里面填上实参int result1 = (*p)(1, 2);printf("%d\n", result1);// 当然也可以直接写函数指针变量名称,效果一样int result2 = p(1, 2);printf("%d\n", result2);
}
3
3

有了函数指针,就可以编写函数回调了(所谓回调就让别人去调用提供的函数,而不是主动来调别人的函数)

比如现在定义了一个函数,不过这个函数需要参数通过一个处理的逻辑才能正常运行,所以就还要给他一个其他函数的地址:

#include <stdio.h>// 将函数指针作为参数传入
int sum(int (*p)(int, int), int a, int b) {// 函数回调return p(a, b);
}// 这个函数实现了a + b
int sumImpl(int a, int b) {  return a + b;
}int main() {// 拿到实现那个函数的地址int (*p)(int, int) = sumImpl;  printf("%d", sum(p, 10, 20));
}
30

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

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

相关文章

Python编码系列—Python建造者模式:构建复杂对象的优雅之道

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

传知代码-融合经典与创新的图像分类新途径

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 概述 在当前的深度学习领域&#xff0c;构建兼具高性能与灵活性的卷积神经网络&#xff08;CNN&#xff09;已成为计算机视觉研究的核心课题。本文介绍了一种全新的卷积神经网络架构&#xff0c;该网络巧妙地结合…

OZON电子产品大幅增长,OZON跨境PS5销量激增

Top1 存储卡 Карта памяти Canvas Select Plus 128 ГБ 商品id&#xff1a;1548303593 月销量&#xff1a;2131 欢迎各位卖家朋友点击这里&#xff1a; &#x1f449; D。DDqbt。COm/74rD 免费体验 随着智能手机和平板电脑的普及&#xff0c;用户对于存储空…

vite + vue3 + ts 移动端开箱即用现代开发模板

中文 | English SouthernWind https://blog.csdn.net/nanchen_J?typeblog sw-template vite vue3 ts 移动端开箱即用现代开发模板 特点 &#x1f436; Vite 的Vue3 的文件路由布局系统Mock 后续支持Api 自动引入组件自动引入VueUse 支持TypeScript 的Tailwind css 的暗…

Gitlab实现多项目触发式自动CICD

工作中可能会遇到这种场景&#xff0c;存在上游项目A和下游项目B&#xff0c;项目B的功能依赖项目A&#xff08;比如B负责日志解析&#xff0c;A是日志描述语言代码&#xff09;&#xff0c;这种相互依赖的项目更新流程一般如下&#xff1a; A项目更新&#xff0c;通知B项目开发…

好用的电脑监控软件推荐!分享六个企业必备的电脑监控软件,赶紧Get吧!

数字化办公日益普及&#xff0c;由于工作的需要&#xff0c;几乎每个员工都有自己的电脑&#xff0c;并且大多数电脑都接入了互联网。 这使得电脑监控软件&#xff0c;变为企业管理中必不可少的一部分&#xff01;它们不仅能够帮助管理者实时了解员工的工作状态&#xff0c;提…

充电宝什么品牌比较好用?2024年最值得推荐充电宝品牌!

近年来&#xff0c;随着电子设备使用需求的增加&#xff0c;充电宝市场呈现出蓬勃发展的态势。优秀的充电宝产品不仅能够提供稳定的充电速度&#xff0c;还具备方便携带的体验&#xff0c;深受用户喜爱。然而&#xff0c;面对市场上众多品牌和型号的选择&#xff0c;如何找到最…

Linux云计算 |【第二阶段】SHELL-DAY5

主要内容&#xff1a; awk命令、内置变量&#xff08;FS、$0、$1、$2、NF、NR&#xff09;、过滤时机&#xff08;BEGIN{}、{}、END{}&#xff09;、处理条件&#xff08;正则、&&、||、~\!~、等&#xff09;、awk数组、监控脚本、安全检测脚本 一、awk介绍 awk 是一…

基于微信平台的旅游出行必备商城小程序+ssm(lw+演示+源码+运行)

摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个…

影视直冲?对接卡券特权充值接口对于用户来说有什么优势?

对用户来说有哪些优势&#xff1a; 便利性&#xff1a;用户可以直接在应用程序或网站上充值和使用卡券&#xff0c;无需通过多个平台或渠道&#xff0c;提高了用户体验。实时性&#xff1a;卡券充值和使用状态可以实时更新&#xff0c;用户可以立即看到余额变化和卡券状态。安…

移动硬盘无法读取?别慌!这些方法助你恢复数据!

在我们的日常工作和生活中&#xff0c;移动硬盘作为重要的数据存储工具&#xff0c;承载着珍贵资料。然而&#xff0c;移动硬盘无法被电脑读取的情况时有发生&#xff0c;令人焦急。别慌&#xff0c;下面为大家详细介绍恢复移动硬盘数据的有效方法。 一、检查硬件连接和驱动问题…

麒麟桌面操作系统:查看最近安装与卸载的软件包

麒麟桌面操作系统&#xff1a;查看最近安装与卸载的软件包 1、查看最近安装的deb包2、查看最近卸载的deb包 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在麒麟桌面操作系统中&#xff0c;快速查看最近安装与卸载的软件包非常简单。这里有…

【多因子分组箱线图】:附Origin详细画图教程

目录 No.1 理解箱线图 1 什么是箱线图 2 箱线图的组成 No.2 画图流程 1 导入数据并绘图 2 设置绘图细节 3 设置坐标轴 4 效果图 No.1 理解箱线图 1 什么是箱线图 箱线图&#xff0c;又称箱形图、盒须图或盒式图&#xff0c;用于体现数据分散情况的统计图。在视觉上辅助…

大数据新视界 --大数据大厂之数据挖掘入门:用 R 语言开启数据宝藏的探索之旅

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

猜数-while-python

题目要求&#xff1a; 设置一个范围1-100的随机整数变量&#xff0c;通过while循环&#xff0c;诶和input语句&#xff0c;判断输入的数字是否等于随机数 无限次机会&#xff0c;直到猜中为止每一次不猜中都&#xff0c;会提示大了小了猜完数字后&#xff0c;提示裁了几次 imp…

干耳朵要掏吗?高性价比的可视挖耳勺推荐

干耳朵的耳朵属于比较干爽的内部环境&#xff0c;如果耳道中耳屎过多建议使用专业的工具来掏耳朵。在掏耳的过程建议用可视挖耳勺&#xff0c;可以通过内窥镜来实时查看耳道内的情况&#xff0c;更加安全和精准。但市面上的可视挖耳勺枪品质良莠不齐&#xff0c;一些黑心商家只…

此mac无法连接Apple媒体服务,因为“”出现问题。

出现问题&#xff1a; 这是因为mac登陆过别人的appId下载过软件&#xff0c;但是没有完全退出登陆 解决 打开偏好设置&#xff0c;点击头像&#xff0c;点击媒体与已购项目&#xff0c;能看到弹框内AppleID登陆的应用&#xff0c;打开对应的那个应用&#xff0c;我这里是音…

Linux(CentOS8)服务器安装RabbitMQ

我安装了很久都没有成功, 各种问题, 每次的异常都不一样, 现将成功安装过程做个总结 安装前工作 确保已经安装了一些基础工具和组件库 下载安装包 https://www.erlang.org/patches/otp-24.3.4.5 https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.9.15/ra…

后台数据库查询记录

一、根据日期按天分组查询倒序 //mapper public List<Date> dateByPatientId(FollowScheme followScheme); <select id"dateByPatientId" parameterType"com.ruoyi.follow.domain.FollowScheme" resultType"java.util.Date">SELECT…

视频推镜拍摄SDK解决方案,创新短视频玩法

在当今社交媒体盛行的时代&#xff0c;短视频已成为人们分享生活、展示创意的重要方式。美摄科技推出的视频推镜拍摄SDK解决方案&#xff0c;为用户提供了一种全新的短视频创作体验。 一、什么是视频推镜拍摄SDK解决方案&#xff1f; 美摄科技的视频推镜拍摄SDK解决方案是一种…