【C语言】带你手把手拿捏指针(3)(含转移表)

在这里插入图片描述

文章目录

  • 一、字符指针变量
  • 二、数组指针变量
    • 1.数组指针变量是什么
    • 2.数组指针变量的初始化
  • 三、二维数组传参的本质
  • 四、函数指针变量
    • 1. 函数指针变量的创建
    • 2.函数指针的使用
    • 3.案例解析:
  • 五、typedof关键字
  • 六、函数指针数组和转移表
    • 1.函数指针数组
    • 2.转移表

一、字符指针变量

   在指针的类型中我们知道有⼀种指针类型为字符指针 char* ,⼀般使⽤的方式如下:
在这里插入图片描述
   这里我们将字符变量a的地址交给指针变量p,然后进行使用,但还有一种方式如下:

#include <stdio.h>
int main()
{const char* pstr = "hello bit.";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?printf("%s\n", pstr);return 0;
}

   代码 const char* pstr = “hello bit.”,特别容易让同学以为是把字符串 hello bit. 放到字符指针 pstr ⾥了,但是本质是把字符串 hello bit. ⾸字符的地址放到了pstr中,如图:
在这里插入图片描述
   上⾯代码的意思是把⼀个常量字符串的⾸字符 h 的地址存放到指针变量 pstr 中。《剑指offer》中收录了⼀道和字符串相关的笔试题,我们⼀起来学习⼀下:

#include <stdio.h>
int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char *str3 = "hello bit.";const char *str4 = "hello bit.";if(str1 ==str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if(str3 ==str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

   请问最后代码会输出什么信息呢?我们来看看运行结果,看看与你的想法是否一致:
在这里插入图片描述
   为什么呢?我们可以思考一下,如果是两个变量,那么它们会开辟两个空间存放相应的数据,可能数据不同,所以本质上它们并不相同,而一个常量字符串
   本身就是常量,不会被修改,所以不会像变量一样开辟两个空间存放,而是会将一个常量字符串存在同一个空间,所以str1和str2不同,str3和str4相同

二、数组指针变量

1.数组指针变量是什么

   之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)
   数组指针变量是指针变量?还是数组?答案是:指针变量
我们已经熟悉:

  • 整形指针变量: int * pt; 存放的是整形变量的地址,能够指向整形数据的指针。
  • 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

   那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量

我们来看下面两个,哪个是数组指针变量:

1.int *p1[10];
2.int (*p2)[10];

思考一下p1、p2分别是什么?
   我们先来看看第一个,这里的 * 号会和前面的int结合,为什么呢?因为[]操作符的优先级是比 * 号高的,也就导致了p1和[10]结合, * 和int结合, 变成了一个类型为int*,数组名为p1,元素个数为10的指针数组,如图:
在这里插入图片描述
所以这里p1并不是一个数组指针,而是一个指针数组

   而第二段代码中,我们将p2和 * 用小括号()括起来了,也就改变了优先级,p先和 * 结合,说明p是⼀个指针变量,然后指针指向的是⼀个大小为10个整型的数组。所以p是⼀个指针,指向⼀个数组,叫数组指针
这⾥要注意:[]的优先级要⾼于 * 号的,所以必须加上()来保证p先和*结合

2.数组指针变量的初始化

   数组指针变量是⽤来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的 &数组名,如下:

int arr[10] = {0};
int (*p)[10] = &arr;//得到的就是数组的地址

数组指针类型解析:

int (*p) [10] = &arr;|    |   ||    |   ||    |   p指向数组的元素个数|    p是数组指针变量名p指向的数组的元素类型

   我们之前也做过比较,我们发现数组名,也就是首元素地址和数组地址看起来是一样的,如下:
在这里插入图片描述
它们三个的关系是什么呢?
   我们知道这里数组名arr就是首元素地址,也就是&arr[0],但是这里的&arr是取出的数组的地址,它和另外两个的区别就是,前两个是元素的地址,±整数是跳过相应的元素个数,而&arr±整数时,则是跳过相应的数组个数
   为了加深理解,也同时为我们的二维数组传参本质讲解做铺垫,这里我们举一个例子:
   如何使用数组指针访问一维数组?我们来看一个代码,看看它是否正确,如下:

int main()
{int arr[5] = { 1,2,3,4,5 };int(*p)[5] = &arr;int i = 0;for (i = 0; i < 5; i++){printf("%d ", *(p + i));}return 0;
}

   这个代码正确吗?根据我们前面学的知识,很容易判断出它是错误的,那么为什么呢?就是因为这里p实际上是&arr,也就是整个数组的地址,对它解引用应该是拿到整个数组,如下代码:
在这里插入图片描述
   可以看到,我们对p解引用后拿到的是整个数组,那么p与这个一维数组的联系到底是什么呢?如下:

p = &arr
*p = *&arr
*p=arr 

   通过上文比较我们可以看到, * p本质上就是数组的数组名,那么我们就把 * p当作数组名来使用,如下:

int main()
{int arr[5] = { 1,2,3,4,5 };int(*p)[5] = &arr;for (int i = 0; i < 5; i++){printf("%d ", (*p)[i]);}return 0;
}

   我们来看看运行结果:
在这里插入图片描述
   可以看到确实实现了用数组指针访问一维数组,但是总感觉怪怪的,感觉就是为了完成任务硬拼起来的,那数组指针到底会在什么场景出现呢?我们就在讲解二维数组传参的本质时介绍

三、二维数组传参的本质

   有了数组指针的理解,我们就能够讲⼀下⼆维数组传参的本质了
   过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:

#include <stdio.h>
void print(int a[3][5], int r, int c)
{int i = 0;int j = 0;
for(i=0; i<r; i++){for(j=0; j<c; j++){printf("%d ", a[i][j]);}printf("\n");}
}int main()
{int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};print(arr, 3, 5);return 0;
}

   这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
   ⾸先我们再次理解⼀下⼆维数组,⼆维数组其实可以看做是每个元素是⼀维数组的数组,我们在传参时会传这个二维数组的数组名,我们也都知道一个数组的数组名是首元素的地址,那么二维数组的首元素地址是什么呢?
   由于二维数组可以看做是一维数组的数组,那么第一行就是它的第一个元素,第二行就是它的第二个元素,依此类推,所以⼆维数组的首元素就是第⼀⾏,是个⼀维数组,如下图:
在这里插入图片描述
   所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀维数组的地址。根据上⾯的例⼦,第一行的⼀维数组的类型就是 int [5] ,所以第一行的地址的类型就是数组指针类型 int(*)[5]
   那就意味着⼆维数组传参本质上也是传递了地址,传递的是二维数组第一行这个⼀维数组的地址,是个数组,那么形参也是可以写成数组指针形式的,如下:

void print(int (*p)[5}, int x, int y)

   经过上一节的分析,我们也就知道了p其实就是二维数组第一行的地址,也就是二维数组的首元素的地址,也就相当于二维数组的数组名arr,那么我们此时就可以使用p访问二维数组,如下代码:

void print(int(*p)[5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", p[i][j]);}printf("\n");}
}int main()
{int arr[3][5] = { 1,2,3,4,5 ,2,3,4,5,6 ,3,4,5,6,7};print(arr, 3, 5);return 0;
}

运行结果如图:
在这里插入图片描述
   可以看到确实使用这种方式可以完美访问二维数组了,那么还有其它方法吗?我们这里再讲一个更加深入,更加贴近指针用法的方法
   首先我们来再深入一点了解二维数组,我们说二维数组的每一行都是一个一维数组,那么这个一维数组有数组名吗?当然有,如下图:
在这里插入图片描述
   然后我们继续深入理解,p是二维数组中第一个一维数组的地址,那么p+1就应该是第二个一维数组的地址,因为p是数组指针,±整数可以跳过对应的数组个数,p+2就是第三个一维数组的地址,如图:
在这里插入图片描述
   那么* (p+0),* (p+1),* (p+2),之前通过学习对一维数组的地址进行解引用就会得到该一维数组的数组名,也就是首元素的地址,如下例:

int arr[3][5] = { 1,2,3,4,5 ,2,3,4,5,6 ,3,4,5,6,7};
p+0 = &arr[0];
p+1 = &arr[1];
p+3 = &arr[2];
那么:
*(p+0) = *&arr[0]=arr[0];
*(p+1) = *&arr[1]=arr[1];
*(p+3) = *&arr[2]=arr[2];

   所以我们可以总结为下图:
在这里插入图片描述
   现在有了每个一维数组的数组名,也就是首元素地址,我们再对首元素地址进行±就可以得到其它元素的地址,然后解引用就可以得到对应的元素,如下图:
在这里插入图片描述
   于是最终我们可以利用指针数组写出如下代码:

void print(int(*p)[5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", *(*(p+i)+j));}printf("\n");}
}int main()
{int arr[3][5] = { 1,2,3,4,5 ,2,3,4,5,6 ,3,4,5,6,7};print(arr, 3, 5);return 0;
}

   运行结果如下:
在这里插入图片描述
   总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式,它的本质就是使用数组指针,来访问二维数组的元素

四、函数指针变量

1. 函数指针变量的创建

什么是函数指针变量呢?
   根据前⾯学习整型指针,数组指针的时候,我们的类⽐关系,我们不难得出结论:
   函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。
那么函数是否有地址呢?我们做个测试:

#include <stdio.h>
void test()
{printf("hehe\n");
}
int main()
{printf("test: %p\n", test);printf("&test: %p\n", &test);return 0;
}

   我们来看看运行结果:
在这里插入图片描述
   确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的方式获得函数的地址
   如果我们要将函数的地址存放起来,就得创建函数指针变量,函数指针变量的写法其实和数组指针非常类似
   它也需要把变量名和*用()括在一起,最前面写上函数的返回类型,不同的是,数组后面跟的是元素的个数,而函数指针变量后面是一对小括号,里面写上函数的参数,参数的类型必须写出来,但是参数名可以省略,现在举两个例子演示:
举例1:函数无参数时:

void test()
{printf("hehe\n");
}
int main()
{void (*P)() = test;//或写成&test;return 0;
}

举例2:函数有参数时:

int Add(int x, int y)
{return x + y;
}int main()
{int (*p)(int x, int y) = Add;
//或写成:int (*p)(int, int) = Add;return 0;
}

函数指针类型解析:

int (*p) (int x, int y)|    |   ------------ |    |        ||    |    p指向函数的参数类型和个数的交代|   函数指针变量名p指向函数的返回类型
int (*) (int x, int y) //p函数指针变量的类型

2.函数指针的使用

   可以通过函数指针调用指针指向的函数,由于函数名就是函数的地址,所以我们在使用时,可以直接用函数指针变量名替换函数名,如下例:

int Add(int x, int y)
{return x + y;
}int main()
{int (*p)(int x, int y) = Add;printf("%d\n", p(2, 3));return 0;
}

输出结果:
在这里插入图片描述
   或者我们也可以对p进行解引用,也可以拿到函数的地址,如下:

printf("%d\n", (*p)(2, 3));

输出结果:
在这里插入图片描述

3.案例解析:

   我们来看两段代码,猜测它们的含义:

  1. 代码1
(*(void (*)())0)();

这段代码中比较特殊的就是数字0,随后我们来看它的左边是什么:

(void (*)())

   很明显在括号里面的是一种函数指针类型,它的返回类型是void,参数为空,没有变量名,说明它只是一种函数指针类型,那么把它放在0的前面有什么用呢?我们举一个简单的例子:

(int)3.14

   在一个数据前加上一个括号,里面写上类型,很明显就是我们的强制类型转换,这里是把浮点型3.14强制转换为整型,上述例子也是如此,将0强制转换为了一个函数指针类型,现在相当于0是一个指针
然后前面的*对它进行解引用,相当于就是这个函数本身,这个函数就是前面一堆:

(*(void (*)())0)

   最后的一个小阔号就是函数本身的括号,如函数Add(int x,int y),函数名Add后面的括号,只是这个函数没有参数,而Add函数有参数

  1. 代码2
void (*signal(int , void(*)(int)))(int);

   这个代码更加不可思议,我们首先可以从signal下手,它看起来很像一个函数声明,它的参数分别是int类型,以及一个返回类型为void,参数为int类型的函数指针类型,如下:

signal(int , void(*)(int))

   那么剩下的那些是什么呢?既然我们猜测它是一个函数,那么现在函数名有了,函数参数也有了,是不是还差一个返回类型,我们把中间这一段去掉,如下:

void (*)(int);

   很明显它变成了一个函数指针类型,小伙伴们肯定也可以猜到,这就是刚刚那个函数的返回类型,可是为什么这么奇怪,要把除了返回类型的东西塞进返回类型,不像传统这样写:

void (*)(int)  signal(int , void(*)(int))

   这样似乎更好理解,但是这是C中未定义的,还是要使用上面的写法,否则会出错
   最后总结一下:这段代码是函数signal的声明,它的一个参数是int,一个参数是一种函数指针类型,返回类型也是一个函数指针类型

五、typedof关键字

   typedef 是⽤来类型重命名的,可以将复杂的类型,简单化
   比如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使用:

typedef unsigned int uint;
//将unsigned int 重命名为uint

   如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:

typedef int* ptr_t

   但是对于数组指针和函数指针稍微有点区别,比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int(*parr_t)[5]; 
//新的类型名必须在*的右边

   函数指针类型的重命名也是⼀样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:

typedef void (*pf_t)(int)
//新的类型名必须在*的右边

那么要简化上面的第四点中的代码2,可以这样写:

typedef void(*pf_t)(int);
pf_t signal(int, pfun_t);

六、函数指针数组和转移表

1.函数指针数组

   数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组,比如:

int * arr[10];
//数组的每个元素是int*

   那要把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?我们现在写出多种可能,来猜一下是哪一个:

1.int (*parr1[3])();2.int *parr2[3]();3.int (*)() parr3[3];

   是哪一种呢?一般来说跟函数指针有关的时候,我们一般会把一些声明写进函数类型中,所以在上面的三个例子中,正确的写法是1
   parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?是 int (*)() 类型的函数指针
   那么函数指针数组有什么作用呢?就要涉及到下一个内容:转移表

2.转移表

   函数指针数组的⽤途:转移表
   举例:计算器的实现,要求可以根据菜单使用加减乘除
我们最初写过一个最简单的加法函数,如下:

int add(int x, int y)
{return x + y;
}

那么另外的函数也只需要一个一个实现,如下:

int add(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 div(int a, int b)
{return a / b;
}

   到现在这一步还算简单,随后我们开始写主函数中的内容,我们可以设计一个菜单menu,用来打印选择输入0关闭计算器,输入1,2,3,4就对应加减乘除,具体的实现可以使用Switch语句,如下:
menu函数:

void menu()
{printf("*************************\n");printf(" 1:add              2:sub \n");printf(" 3:mul              4:div \n");printf("          0:exit \n");printf("*************************\n\n");printf("请选择:");
}

主函数:

int main()
{int x, y;int input = 0;int ret = 0;do{menu();scanf("%d", &input);switch (input){case 0:printf("已退出计算器!");break;case 1:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;}} while (input);return 0;
}

   这样我们的计算器就完成了,但是我们发现了严重的问题,这样写代码虽然简单,但是实在有点笨,有很多内容我们都没有必要重复,那么我们该怎么避免呢?这时我们发现这些函数的参数和返回类型都是一致的,我们就可以尝试使用函数指针数组:
   我们现在创建一个函数指针数组,如下:

int (*pf[5])(int , int);

   接下来我们对它们进行初始化,分别将每个函数的地址放进去,由于函数名就是函数地址,我们只需要填上函数名:

int (*pf[5])(int , int)={ add,sub,mul,div };

   但是这时候我们就会发现一个小问题,add在数组中的下标是0,但是实际上在Switch语句中,输入1应该才是add,这时候有个小技巧就是,在最前面加上一个元素0,就可以让它们到对应的位置上,如:

int (*pf[5])(int , int)={ 0,add,sub,mul,div };

   随后我们就可以利用这个数组方便的访问函数,pf就是数组名,input就是要访问的对应的下标,所以pf[input]相当于拿到对应函数的地址,也就是函数名,随后将其正常使用即可,如下:

pf[input](x,y);

   知道了这个,我们代码就好写了,可以简化许多内容,所以完整的计算器代码如下:

int add(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 div(int a, int b)
{return a / b;
}void menu()
{printf("*************************\n");printf(" 1:add              2:sub \n");printf(" 3:mul              4:div \n");printf("          0:exit \n");printf("*************************\n\n");printf("请选择:");
}int main()
{int x, y;int input = 0;int ret = 0;int (*pf[5]) (int, int) = { 0,add,sub,mul,div };do{menu();scanf("%d", &input);if (input>=1 && input<=4){printf("请输入两个整型数据:");scanf("%d %d", &x, &y);ret = *pf[input](x, y);printf("结果为:%d\n\n", ret);}else if(input==0){printf("已退出计算器\n"); \break;}else{printf("选择错误,请重新输入!\n\n");}} while (input);return 0;
}

   那这个跟转移表有什么关系呢?其实它所指的就是运用函数指针数组以数组方式去调用里面的函数,从而在某些情况下替代冗长的switch函数,所以简单的说函数指针数组就叫转移表

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

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

相关文章

问题:WINCC 7.5 结构变量只能是内部变量吗?

问题&#xff1a;WINCC 7.5 结构变量只能是内部变量吗&#xff1f; 答案&#xff1a;不是的呢&#xff0c;你创建结构的时候可以选择外部变量的 如图&#xff1a;工控人加入PLC工业自动化精英社群 #WINCC 7.5##变量##结构##西门子工业支持中心#

Spring Cloud Alibaba-(1)搭建项目环境

1.Spring Cloud Alibaba&#xff08;官网&#xff1a;https://sca.aliyun.com/&#xff09; Spring Cloud Alibaba 是阿里巴巴结合自身丰富的微服务实践而推出的微服务开发的一站式解决方案&#xff0c;是 Spring Cloud 第二代实现的主要组成部分。吸收了 Spring Cloud Netflix…

如何让源码加密后还能运行?五种企业源代码加密措施推荐

随着企业越来越依赖技术创新&#xff0c;保护源代码的安全变得尤为重要。源代码是企业的核心资产之一&#xff0c;包含了重要的业务逻辑和技术创新。未经授权的访问、篡改或泄露都可能给公司带来巨大的损失。因此&#xff0c;实施有效的源代码加密措施至关重要。本文将推荐五种…

龙海家园的免费停车点探寻

​第一次去龙海家园就把我羡慕到了&#xff0c;楼下就是鲤鱼门地铁&#xff0c;龙海家园底商的餐饮好吃又实惠&#xff0c;还有特别多的超市&#xff0c;空中花园也很大&#xff0c;还可以共享前海基金小镇的花园环境。虽然我看到很多车排队等进龙海家园&#xff0c;但是我还是…

传输层协议(TCP和UDP)

目录 一、UDP 1、UDPAPI 2、UDPAPI的使用 二、TCP 1、TCPAPI 2、TCP的相关特性 2.1 确认应答 2.2 超时重传 2.3 连接管理&#xff08;三次握手&#xff0c;四次挥手&#xff09; 2.4 滑动窗口 2.5 流量控制 2.6 拥塞控制 2.7 延时应答 2.8 捎带应答 2.9 面向字节…

北森笔试测评之言语理解到底难不难

前篇笔记我提到过&#xff0c;言语理解是最难的&#xff0c;有同学质疑了。言语我都会做呀&#xff0c;甚至有的可以搜到&#xff0c;而图标和图形我有的不会。这里需要指出&#xff0c;会做不等于作对&#xff0c;可以回顾到高中甚至初中的时候&#xff0c;感觉做的好的一般都…

YOLO的使用

目录 一、安装ultralytics 二、数据集准备 三、训练代码 五、训练结果的目录结构 六、附录 1、部分参数 2、文件介绍 一、安装ultralytics pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple 介绍&#xff1a;Ultralytics YOLO Docs --- Train - …

计算机毕业设计 家电销售展示平台的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

deepspeed安装报错 No module named ‘dskernels‘解决

pip install deepseek安装报错 Using cached https://pypi.tuna.tsinghua.edu.cn/packages/61/e6/04e2f2de08253e6b779fe7706f2e06d8fb48353e1d33a2fd7805062213d4/deepspeed-0.12.3.tar.gz (1.2 MB)Preparing metadata (setup.py) ... errorerror: subprocess-exited-with-err…

刘德华这么拼为了什么?

​马上就到63岁的生日了&#xff0c;可是刘德华今年在演唱会的连续三场的表演里边&#xff0c;出现了严重的舞台危险&#xff0c;差点命丧舞台。这一把年纪了&#xff0c;有必要这么拼吗&#xff1f;你要看画面&#xff0c;你就知道那是真的危险&#xff0c;他不是在作秀。 那他…

select系统调用(实现I/O复用)

API 在一段指定时间内&#xff0c;监听用户感兴趣的文件描述符上的可读、可写、异常事件。 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);文件描述符集合fd_set 是一个用于管理文件描述符集合的结构体。select调用…

MICE MIXTURE OF CONTRASTIVE EXPERTS FOR UNSUPERVISED IMAGE CLUSTERING

由于论文和代码和我想的不太一致&#xff0c;因此不附上精读内容&#xff0c;感兴趣可以自行阅读 代码TsungWeiTsai/MiCE: Pytorch implementation for ICLR 2021 paper - MiCE: Mixture of Contrastive Experts for Unsupervised Image Clustering (github.com) 论文&#xff…

体感魂斗罗(一)

文章目录 体感魂斗罗实现步骤设备读取摄像头视频流使用电脑摄像头读取局域网内手机摄像头效果示意IP摄像头底部工具栏 体感魂斗罗实现步骤 目前想到的有如下步骤 读取摄像头视频流图像检测人体关键点关键点转换为人体姿势固定姿势转换键盘键位 设备 摄像头&#xff08;可用手…

rsync 服务详解

目录 1.前言 2. rsync 常用选项 3.rsync应用场景 4.rsync使用模式 5.rsync推与拉​编辑 5.1rsync推送 ​编辑5.2rsync拉取 6.rsync本地模式 7.远程模式 8.rsync守护进程 8.2改配置文件 8.3添加虚拟用户 8.4创建密码文件 8.5给文件给予权…

linux操作系统的引导和修复

1.磁盘引导 mbr 主引导记录 0 磁道 1 扇区 446 作用 &#xff1a; 记录 grub2 引导文件的位置 当 mbr 数据丢失系统会因为找不到启动分区而停止启动 问题模拟方式 系统磁盘 / dev / sda dd if/ dev / zero of / dev / vda bs 446 count 1 ## 清空系统 / dev / sda …

盘点常见网络安全术语(建议收藏)

1、黑帽 为非法目的进行黑客攻击的人&#xff0c;通常是为了经济利益。他们进入安全网络以销毁&#xff0c;赎回&#xff0c;修改或窃取数据&#xff0c;或使网络无法用于授权用户。这个名字来源于这样一个事实&#xff1a;老式的黑白西部电影中的恶棍很容易被电影观众识别&…

283. 移动零(快慢指针)

算法分析&#xff1a; 如果数组没有0&#xff0c;快慢指针同步移动&#xff0c;元素会被自己复制&#xff1b;如果有0&#xff0c;快指针找到非零元素&#xff0c;将其复制到慢指针位置最终将剩余位置填充为0。 代码&#xff1a; class Solution {public void moveZeroes(i…

python 环境问题

日常环境问题记录 1、pycharm 终端禁止运行脚本1.1 问题描述1.2 以管理员身份运行powershell1.3 修改权限 1、pycharm 终端禁止运行脚本 1.1 问题描述 当我在pycharm终端执行脚本&#xff0c;比如 activate激活虚拟环境时&#xff0c;会报错不让执行 这类问题的出现原因是没…

基于SpringBoot+Vue+MySQL的家乡特色推荐系统

系统展示 用户前台界面 管理员后台界面 系统背景 在当今数字化时代&#xff0c;随着旅游业的蓬勃发展和人们对本土文化探索的热情日益增长&#xff0c;一个基于SpringBoot、Vue.js与MySQL的家乡特色推荐系统应运而生。该系统旨在通过现代互联网技术&#xff0c;深度挖掘并展示各…

【计算机毕设-大数据方向】基于Hadoop的智能交通数据分析可视化系统的设计与实现

&#x1f497;博主介绍&#xff1a;✌全平台粉丝5W,高级大厂开发程序员&#x1f603;&#xff0c;博客之星、掘金/知乎/华为云/阿里云等平台优质作者。 【源码获取】关注并且私信我 【联系方式】&#x1f447;&#x1f447;&#x1f447;最下边&#x1f447;&#x1f447;&…