C语言指针与数组
在上一篇对指针介绍的文章当中,我们初次了解到了指针,并且知道了地址和内存间的关系,懂得了如何取地址和对指针的解引用,算是对指针有了一个初步的了解。而今天让我们对指针进行更深一步的了解吧~
一、指针与数组名
我们知道,指针变量是一个用来存放地址的变量,比如我们定义一个整形变量a,那么我们想要用指针变量来存放它的地址,就需要写成int* pa = &a的形式。而这样的形式仅仅能存放一个变量的地址,那么如果我们想要存放数组的地址呢?有些人可能会说,那就int* arr = &arr呗,那么这样的写法是对的嘛?让我们来看一个例子。欸?为什么不能把arr的地址存到指针变量当中呢,而此时当我们把取地址符&去掉后,我们会发现此时代码竟然不报错了!
为什么不报错了呢?让我们反推一下,我们知道int* arr一定是一个存储地址的指针变量,而此时代码正确,就充分说明了我们传入Print函数中的arr就是一个地址。那么arr作为一个被我们定义的数组,arr数组名地址究竟是代表整个数组?还是数组的某个元素?
既然实践出真知,让我们写一段代码分成几种情况分别来看一看数组名到底代表什么吧~
int main()
{int arr[5] = { 1,2,3,4,5 };printf("%p\n", arr);printf("%p\n", &arr[0]);return 0;
}
我们会发现对数组的第一个元素取地址,与数组名的地址是相同的,那么也就说明数组名所代表的就是数组首元素的地址~......欸?或许有些人就要问了:明明之前用sz求数组元素个数的时候,sizeof(arr)/sizeof(arr[0])中sizeof(arr)表示的是整个数组的大小呀,其实sizeof(arr)是一个例外的情况。在一般情况下数组名代表的就是数组首元素地址,而有两个例外的情况:
- sizeof(数组名):在这种情况下,数组名表示的是整个数组的大小,单位是字节,一般用于计算数组的元素个数。
- &数组名:这种情况下取地址符取的是整个数组的地址。
对于第一个例外我们已经充分了解过啦,那么让我们来说一下第二个情况&数组名。
依然是不变的实践出真知~让我们敲一个代码运行一下看看。
在这里我们可以看到&arr虽然表示整个数组,但输出地址还是数组首元素的形式,没错~让我们更深一步的探索&arr与arr的差别,再一次实践出真知:
int main()
{int arr[5] = { 1,2,3,4,5 };printf("arr: %p\n", arr);printf("&arr[0]: %p\n", &arr[0]);printf("&arr: %p\n", &arr);printf("arr+1: %p\n", arr+1);printf("&arr[0]+1: %p\n", &arr[0]+1);printf("&arr+1: %p\n", &arr+1);return 0;
}
这时候就能看出&arr和arr的差别了,我们定义的数组有五个元素,数组名+1表示的是跳过一个元素,而&数组名+1跳过了整个数组,这就是arr和&arr最本质的差别啦~
二、指针访问数组与数组传参
前面我们充分了解了数组名和&数组名以及两种例外情况,现在再来让我们学习一下如何使用刚刚的知识用指针来对数组进行访问吧~我们来尝试一下使用指针对数组元素进行输入并且输出。
int main()
{int arr[10] = { 0 };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){scanf("%d", arr + i);}printf("\n");for (i = 0; i < sz; i++){printf("%d ", *(arr + i));}return 0;
}
我们又学会了如何用指针访问数组,那么我们来看看数组的传参。
在进行传参的时候,是将传递的实参进行复制,然后将这个复制的值进入函数中变成一个形参。但我们想把一个数组传递进去的时候,如果把每一个元素都一个个的复制,一个个的放进函数,要是数组中元素很多,那么会使传参的工作变得繁琐,所以在进行传参时候,数组会退化为指针。
int sz(int* arr)
{int num = sizeof(arr) / sizeof(arr[0]);return num;
}
int main()
{int arr[10] = { 0 };int i = 0;int sz1 = sizeof(arr) / sizeof(arr[0]);int sz2 = sz(arr);printf("%d\n%d", sz1, sz2);return 0;
}
由这段代码我们能够清晰的看出数组退化成指针的过程。在将数组传参到函数里后,再使用sizeof(arr)/sizeof(arr[0])求出的值却是1,这就证明了数组的退化,因为在传参的时候将数组名传递给函数,本质上传递的是数组首元素的地址,而用数组首元素去除以数组首元素,得到的值理所应当就是1。
三、指针数组
指针数组,顾名思义是由多个指针类型元素组成的数组,也就是说指针数组是一个存放指针的数组。
我们在定义整型数组时,需要写成的格式是:int 数组名[元素个数]。定义字符数组时需要写成的格式是:char 数组名[元素个数]。前面是int还是char取决于想要创造的数组类型,那么由此我们能猜想出指针数组的写法:如果我们想要定义一个用于存放整型指针变量的数组,那么元素对应的类型就是int*,所以格式是:int* 数组名[元素个数]。
让我们试着用指针数组来模拟一维数组:
int main()
{int a = 1;int b = 2;int c = 3;int ar[3] = { a,b,c };int* arr[3] = { &ar[0],&ar[1],&ar[2]};int sz = sizeof(arr) / sizeof(arr[0]);int i = 0;for (i = 0; i < sz; i++){printf("地址为:%p ", *(arr + i));printf("所指向内容的值为:%d \n", *(*(arr + i)));}return 0;
}
在这段代码中我们定义了三个整形变量,再用一个整型数组来存放三个整形变量。然后我们又创造了一个整型指针数组,用来存放整型数组中各个元素的地址。需要注意的是:使用数组存放变量,输出时与arr[i]对应的格式是*(arr+i),而此时指针数组中存放的是地址,使用*(arr+i)的格式得到的是存放进指针数组中的指针变量,想要得到指针变量对应的值需要进一步对指针变量再一次解引用,写成*(*(arr + i))的形式。
我们接着再用指针数组模拟一个二维数组:
int main()
{int arr1[] = { 1,2,3 };int arr2[] = { 4,5,6 };int arr3[] = { 7,8,9 };int* parr[3] = { arr1, arr2, arr3 };//数组名是数组元素的地址,所以可存放int i = 0;int j = 0;printf("用数组形式:\n");for (i = 0; i < 3; i++){for (j = 0; j < 3; j++){printf("%d ", parr[i][j]);}printf("\n");}printf("用指针形式:\n");for (i = 0; i < 3; i++){for (j = 0; j < 3; j++){printf("%d ", *(*(parr+i)+j));}printf("\n");}return 0;
}
注:*(*(parr+i)+j)的形式,代表的是第i行第j个元素。
四、数组指针
我们之前学习指针的时候了解到:整形指针变量用来存放整形变量的地址,并且能够指向整形数据。字符指针变量用来存放字符型变量的地址,并且能够指向字符型数据。那么以此类推我们也应该能够看出,数组指针其实指的是一个指针变量,数组指针变量应该用来存放数组的地址,并且能够指向数组的数据。
数组指针的写法:
int (*p)[5];
注释:(*p)中,p先和*结合来说明p是一个指针变量,然后这个指针变量指向的是一个大小为5的整型的数组,因p为指针变量,指向数组,故而名为数组指针。
我们知道只要是指针就需要初始化,如果不初始化就会变成“野指针”,那么数组指针应该如何初始化呢?我们知道一个整形指针的初始化是将一个整形变量的地址给它:
int a = 5;
int* p = &a;
那么对应的,数组指针初始化应该就是将一个数组的地址传给数组变量,那有人会想:数组名不就是一个地址嘛,直接让int (*p)[5]=arr应该就可以了吧,不不不,大错特错。我们要清楚的了解arr,&arr,和&arr[]之间的区别。数组名确实是一个指针,但是它指的是数组的首元素地址,而这时候我们要给数组指针的是一个完整的数组,所以我们需要给它&arr:
int arr[5] = {0};
int(*p)[5] = &arr;
我们能看到&arr和p是一模一样的。所以数组指针变量用来存放数组的地址,并且能够指向数组的数据。
五、二维数组传参
其实上面在指针数组那一段中就出现了二维数组传参,这里我们更细致的讲解一下二维数组的传参到底该怎么用,该怎么写。在我们理解了指针数组和数组指针后我们能够更好地理解二维数组的传参。比如我们先定义一个三行三列的整型数组,那么就需要定义一个数组arr[3][3],用图片表达出来就像这个样子:
列\行 | 0 | 1 | 2 |
0 | 1 | 2 | 3 |
1 | 4 | 5 | 6 |
2 | 7 | 8 | 9 |
那除了arr[3][3]这种写法,我们再往回看一下刚刚讲到的指针数组的写法:int* 数组名[元素个数],并且再来看一下我们刚刚写的一段用指针数组模拟的二维数组的代码:
int arr1[] = { 1,2,3 };int arr2[] = { 4,5,6 };int arr3[] = { 7,8,9 };int* parr[3] = { arr1, arr2, arr3 };
通过这段代码我们不难看出,指针数组模拟二维数组时将二维数组分成了三个一维数组,而每一个一维数组代表每一行元素,接着我们来看这三个一维数组,每一个一维数组的类型都是int [3],而将int [3]以int* 数组名[元素个数]的形式来表示就是这样:int (*)[3]。这代表二维数组传参和指针数组传参一样,都是传递地址,所以用指针作为形参来传参也是可以的。写成代码是这样的:
void Print(int(*arr)[3], int x, int y)
{int i = 0;int j = 0;for (i = 0; i < x; i++){for (j = 0; j < y; j++){printf("%d ", *(*(arr + i) + j));//表示第i行第j列的元素}printf("\n");}
}
int main()
{int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} };Print(arr, 3, 3);return 0;
}
六、函数指针变量
通过上面学习的数组指针变量来看,不难猜出:
函数指针变量也是一种指针变量,作用是存放函数的地址,并且通过地址能调用对应的函数。可能有人看到这里,心里会想:函数也有地址吗?听过整形变量有地址,听过字符型变量有地址,甚至听过指针变量也有地址,但也没听过函数的地址呀。这次你就听到了~其实只要你把东西写进了代码中,那计算机就会为其分配新的空间。并且有关函数的地址,在之前有一篇关于函数递归的文章中我们就已经提到过了:我们每次递归一个函数,这个函数再次被调用,计算机就会为这个函数分配新的空间。通过函数递归的知识也变相证明了函数也是由地址的。多说无益,让我们用代码来尝试一下,看看函数的地址到底长什么样。
int Add(int a,int b)
{return a + b;
}
int main()
{printf("%p\n", &Add);printf("%p\n", Add);return 0;
}
编译结果是:
我们会发现&Add和Add是一样的,这也就证明了函数是有地址的,并且函数名就代表了函数的地址。那么既然要了解函数指针变量,还是先从函数指针变量的创建上开始。我们知道整型数组指针的定义格式为int(*)[ ],函数指针变量也和数组指针变量相似,但是由于函数的调用需要传参,所以整型函数指针变量的定义格式为:int(*函数名)(参数,参数)。让我们举个例子:还是拿加法函数Add来做示范:
int Add(int a,int b)
{return a + b;
}
int main()
{int a;int b;scanf("%d %d", &a, &b);int (*padd)(int, int) = Add;//也可以写成int a,int b 但也可以省略printf("%d", (*padd)(a,b));return 0;
}
由这段代码中我们能了解到,关于如何创建函数指针padd,并且如何通过函数指针padd来调用指针对应的函数。
七、函数指针数组
顾名思义,函数指针数组是一个数组,用于存放函数的指针(地址),并且可以通过对数组的访问来使用存在函数指针数组中的函数。
对于函数指针数组的定义,其实并不难,也并不需要过多的考虑,我们知道如果定义一个整型变量格式是int a;那么定义整型数组格式就是int arr[]。那我们还知道函数指针变量的定义格式为:int(*函数名)(参数),那么我们把它看成一个元素,定义函数指针数组的格式就是:int(*函数名[元素个数])(参数)。对于函数指针数组的使用,我们可以这样来操作:
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;
}
int main()
{int (*p[4])(int, int) = { Add,Sub,Mul,Div };int a;int b;scanf("%d %d", &a, &b);printf("%d", (*p[0])(a,b));return 0;
}
这样就成功的将加减乘除四个函数存放到函数指针数组p里面去了,并且通过*p[0],*p[1],*p[2],*p[3]来调用相应的加减乘除这四种算法。通过这段代码确实能够看懂函数指针数组的定义以及用法,但并不能充分的了解函数指针数组的作用,有人可能会觉得调用函数指针数组来使用加减乘除四个函数,还不如直接调用函数来的实在。那我们接着把这个加减乘除函数制作成一个计算机的代码,然后通过计算机代码让大家了解函数指针数组的重要性。
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;
}
int main()
{int a;int b;int k = 0;int num = 0;int m = 1;do{printf("***********************\n");printf("**** 1.Add 2.Sub ****\n");printf("**** 3.Mul 4.Div ****\n");printf("******** 0.quit *******\n");printf("***********************\n");printf("请选择:>");scanf("%d", &k);switch (k){case 1:printf("请输入两个运算数:");scanf("%d %d", &a, &b);num = Add(a, b);printf("num = %d\n", num);break;case 2:printf("请输入两个运算数:");scanf("%d %d", &a, &b);num = Sub(a, b);printf("num = %d\n", num);break;case 3:printf("请输入两个运算数:");scanf("%d %d", &a, &b);num = Mul(a, b);printf("num = %d\n", num);break;case 4:printf("请输入两个运算数:");scanf("%d %d", &a, &b);num = Div(a, b);printf("num = %d\n", num);break;case 0:printf("退出游戏");break;default:printf("选择错误,重新选择:\n");break;}} while (k);return 0;
}
这样就成功的创造出了一个用四个函数制作成的计算机,可以进行加减乘除四种运算并且可以选择开始和退出。虽然这个程序运行起来没有什么问题,但是我们仔细观看会发现,在switch选择语句中的case1,case2,case3,case4中一共只有四行代码,有三行都是重复的,这样就大大降低代码的效率,并且使代码也过多的冗余,导致代码非常长。那么这个时候就需要我们使用函数指针数组了~我们可以把四个函数都存放在函数指针数组中,通过输入的数字调用对应的函数,这样就能够实现用一段代码来替换这四段代码。
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;
}
int main()
{int a;int b;int k = 1;int (*p[5])(int x, int y) = { 0,Add,Sub,Mul,Div };do{printf("***********************\n");printf("**** 1.Add 2.Sub ****\n");printf("**** 3.Mul 4.Div ****\n");printf("******** 0.quit *******\n");printf("***********************\n");printf("请选择:>");scanf("%d", &k);if (k >= 1 && k <= 4){printf("请输入两个运算数:");scanf("%d %d", &a, &b);printf("结果为:%d\n", p[k](a, b));}else if (k == 0){printf("退出计算器");}else{printf("输入错误,请重新输入\n");}} while (k);return 0;
}
这段代码使代码量大大减少,也能够更具像的体现出函数指针数组的重要性和作用。
练习题:
字符串旋转
写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串。
例如:给定s1 =AABCD和s2 = BCDAA,返回1,给定s1=abcd和s2=ACBD,返回0。
AABCD左旋一个字符得到ABCDAAABCD左旋两个字符得到BCDAAAABCD右旋一个字符得到DAABC
思路:等你读到这里应该已经对指针和数组之间的关系掌握了不少了,利用指针与数组和数组传参的知识解题吧~我们可以知道一个字符串左旋是将第一个字符放到最右边,然后其余字符依次往前移一位,那么我们可以先定义一个字符型变量用来接收第一个字符,然后将所有其余字符向前移动一位,最后再把第一个字符利用接收的字符型变量放在最后一位,而右旋只是改变了转移的字母,两者其实差别不大~都可以用同一个思路。
int Print(char* arr1, char* arr2, int sz)
{int i = 0;int j = 0;for (j = 0; j < sz ; j++)//判定左旋{char tmp = *arr1;for (i = 0; i < sz - 1; i++){*(arr1 + i) = *(arr1 + i + 1);}*(arr1 + i) = tmp;if (strcmp(arr1, arr2) == 0)return 1;}for (j = 0; j < sz; j++)//判定右旋{char tmp = *(arr1 + sz - 1);for (i = sz - 1; i > 0; i--){*(arr1 + i) = *(arr1 + i - 1);}*arr1 = tmp;if (strcmp(arr1, arr2) == 0)return 1;}
}
int main()
{char arr1[] = "AABCD";char arr2[] = "BCDAA";//左旋可得char arr3[] = "DAABC";//右旋可得char arr4[] = "ABBCD";//得不到int sz = strlen(arr1);if ((Print(arr1, arr2, sz)) == 1){printf("YES\n");}else{printf("NO\n");}if ((Print(arr1, arr3, sz)) == 1){printf("YES\n");}else{printf("NO\n");}if ((Print(arr1, arr4, sz)) == 1){printf("YES\n");}else{printf("NO\n");}return 0;
}
注:有人可能将右旋的代码写成了以下的格式:
for (j = 0; j < sz; j++)//判定右旋{char tmp = *(arr1 + sz - 1);for (i = 0; i < sz - 1; i++){*(arr1 + i + 1) = *(arr1 + i);}*arr1 = tmp;if (strcmp(arr1, arr2) == 0)return 1;}
看似正确,也同样和左旋是一样的方法,但是!右旋是将所有字母都向后移一位,如果还让每一个字母的下一位等于上一位,比如AABCD,第一次转换变成AABCD,第二次就变成了AAACD,第三次变成AAAAD,所以我们需要转变一下思路,让移动从右往左~(其实还有使用strcpy,strcat的方法,更加的简便轻松,但是这不是我们这次要讲的东西,那么欲知后事如何,请听下回分解啦,我们下次再见~~~)
相关文章:

C语言指针与数组
在上一篇对指针介绍的文章当中,我们初次了解到了指针,并且知道了地址和内存间的关系,懂得了如何取地址和对指针的解引用,算是对指针有了一个初步的了解。而今天让我们对指针进行更深一步的了解吧~ 一、指针与数组名 我们知道&am…...

Stream 33
package Array.collection;import java.util.*; import java.util.stream.Stream;public class stream1 {public static void main(String[] args) {//、如何茯取List集合的Stream流?List<String> names new ArrayList<>();Collections. addAll(names,"方法…...
【Python Loguru】实现日志工具和日志饶接
【Python Loguru】实现日志工具和日志饶接 说明 代码使用第三方库Loguru实现了logging工具,可以直接改名字后就使用,并实现了日志绕接,使用前需要先安装loguru工具。 Code import os import json from loguru import logger# config_file…...

【Linux】-----进度条小程序
目录 前言 基本知识 Ⅰ、回车和换行 Ⅱ、缓冲区 两个有意思的现象 简单定义 刷新缓冲区 简易倒计时程序 进度条代码 多文件下makefile写法 一代(无任何场景) procs1.h代码 procs1.c代码 主函数main1.c 一代运行结果: 二代 (搭配下载场景) procs2.c代…...

普通人有必要学Python吗?学了之后能做什么?
目录 首先来说一下极其推荐的方向: 1、数据分析 2、科学计算 3、大数据框架 4、脚本开发 5、爬虫 6、Web框架 总结: 如果你还没有开始使用Python,答应我,把这个回答看完,如果你真的学习并深入使用过Python&…...

2023-2024年 Java开发岗面试题经验分享
在各行各业中,面试前我们总会思索一个问题:究竟什么样的求职者能获得面试官的青睐?作为求职者,我们又该如何准备,以应对各种面试官的挑战?在这激烈的竞争里,如何才能让自己从众多应聘者中脱颖而…...
JavaScript中URL和Blob
JavaScript中URL和Blob 常用于处理文件数据、图像数据、音频数据等。Blob对象通常用于在客户端处理文件,如上传文件、下载文件、处理图像等操作。Blob对象可以通过Blob构造函数创建,也可以通过其他方式获取,比如从File对象中获取。 使用场景…...
平舌、翘舌音学习: z、c、s--zh、ch、sh
平舌音翘舌音不分怎么办? 尝试整理了,如下一些材料: 一、 策略篇: 一年级拼音如何区分掌握:平舌音和翘舌音? 喜马拉雅, 平舌音翘舌音教学:普通话声母zh以及zh ch sh与z c s的发音练…...

Windows(Win10、Win11)本地部署开源大模型保姆级教程
目录 前言1.安装ollama2.安装大模型3.安装HyperV4.安装Docker5.安装聊天界面6.总结 点我去AIGIS公众号查看本文 本期教程用到的所有安装包已上传到百度网盘 链接:https://pan.baidu.com/s/1j281UcOF6gnOaumQP5XprA 提取码:wzw7 前言 最近开源大模型可谓闹…...

快速排序(下)
快速排序(下) 前言 在上一篇文章中我们了解了快速排序算法,但那是Hoare的版本,其实还有别的版本:一种是挖坑法,它们的区别主要在于如何找基准值。霍尔的版本思路难理解但代码好理解,挖坑法则是…...

LazyLLM:长上下文场景下提高LLM推理效率
LazyLLM旨在优化大型语言模型(LLM)在处理长文本语境下的推理效率。传统上,LLM的推理过程分为预填充和解码两个阶段,其中预填充阶段负责计算并存储输入提示的所有token的键值(KV)缓存,这一步骤在…...

PDF文件点击打印无反应?是何原因造成能解决吗?
PDF无法打印怎么处理?在我们工作中,经常会遇见各种各样的文件问题,当我们想要将PDF文件打印出来纸质版使用,却不知什么原因,显示PDF无法打印,这时应该怎么处理呢? 一般情况下,PDF文件…...

初学者友好!从零到一快速上手PyCharm安装的超详细图解+避坑指南教程
一,pycharm的官网下载 下载地址:www.jetbrains.com/pycharm/ 本文将从 Python解释器安装到Pycharm专业版安装和配置汉化等使用都进行了详细介绍,希望能够帮助到大家。 Python解释器&Pycharm安装包&Pycharm破姐插件我都打包好了。 …...

AI大模型需要什么样的数据?
数据将是未来AI大模型竞争的关键要素 人工智能发展的突破得益于高质量数据的发展。例如,大型语言模型的最新进展依赖于更高质量、更丰富的训练数据集:与GPT-2相比,GPT-3对模型架构只进行了微小的修改,但花费精力收集更大的高质量…...

Java每日一练_模拟面试题1(死锁)
一、死锁的条件 死锁通常发生在两个或者更多的线程相互等待对方释放资源,从而导致它们都无法继续执行。死锁的条件通常被描述为四个必要条件,也就是互斥条件、不可剥夺条件、占有并等待条件和循环等待条件。 互斥条件:资源不能被共享&#x…...

第三方库认识- Mysql 数据库 API 认识
文章目录 一、msyql数据库API接口1.初始化mysql_init()——mysql_init2.链接数据库mysql_real_connect——mysql_real_connect3.设置当前客户端的字符集——mysql_set_character_set4.选择操作的数据库——mysql_select_db5.执行sql语句——mysql_query6.保存查询结果到本地——…...

Python兼职接单全攻略:掌握技能,拓宽收入渠道
引言 随着Python在数据处理、Web开发、自动化办公、爬虫技术等多个领域的广泛应用,越来越多的人开始利用Python技能进行兼职接单,以此拓宽收入渠道。本文将详细介绍Python兼职接单的注意事项、所需技能水平、常见单子类型、接单途径及平台,帮…...

一键编译并启动一个 ARM Linux qemu 虚拟机
需要事先自己编译 qemu-system-arm 可执行文件; 1,编译创建ARM 虚拟机 #!/usr/bin/bash sudo lssudo apt-get install gcc-arm-linux-gnueabi#wget https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.10.tar.gztar zxf linux-kernel-v5.10…...

KubeVirt虚拟机存储及网络卸载加速解决方案
1. 方案背景 1.1. KubeVirt介绍 随着云计算和容器技术的飞速发展,Kubernetes已成为业界公认的容器编排标准,为用户提供了强大、灵活且可扩展的平台来部署和管理各类应用。然而,在企业的实际应用中,仍有许多传统应用或遗留系统难…...

JVM—对象已死?
参考资料:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明 在堆里面存放着 Java 世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”。 1、如何判…...

stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...

Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...