深度刨析程序中的指针

news2024/11/24 20:42:25

前面我们已经学习过了指针的一下性质:

  • 指针就是个变量,用来存放地址,地址唯一标识的一块内存空间
  • 指针的大小是固定的4/8个字节(32位平台/64位平台)
  • 指针是有类型,指针的类型决定了指针的加减整数的步长,指针解引用操作时的权限。
  • 两个指针相减返回的是两指针间元素的个数(同类型指针)

文章目录

  • 1.字符指针
  • 2.指针数组
  • 3.数组指针
    • 3.1 数组指针的定义
    • 3.2 &数组名与数组名
    • 3.3 数组指针的使用
  • 4.数组传参、指针参数
    • 4.1 一维数组传参
    • 4.2 二维数组传参
    • 4.3 一级指针传参
    • 4.4 二级指针传参
  • 5.函数指针
  • 6.函数指针数组
  • 7.指向函数指针数组的指针
  • 8.回调函数

1.字符指针

在指针的类型中我们知道有一种指针类型为字符指针char*
一般使用情况

#include <stdio.h>
int main()
{
	char c = 'a';
	char* pc = &c;
	*pc = 'y';
	return 0;
}

还有一种情况

#include <stdio.h>
int main()
{
	const char* str = "hello world";//把"hello world"的首元素的地址给了str
	//但是不能单纯的理解为数组,这里的"hello world"是存放代码区中的不可修改,是常量字符串,所以我们在前面加了const修饰
	printf("%s\n",str);
	return 0;
}

指针

本质就是把常量字符串hello world的首元素的地址放到了str当中,也就是将常量字符串的首元素h的地址放到str中
练习

#include <stdio.h>
int main()
{
	char str1[] = "hello world";
	char str2[] = "hello world";

	const char* str3 = "hello bit";
	const char* str4 = "hello bit";

	if(str1==str2)
		printf("same\n");
	else
		printf("not same\n");
	if(str3==str4)
		printf("same\n");
	else
		printf("not same\n");
	return 0;
}

//打印结果:
/*
not same
same
*/

这里比较的都是地址。
str1和str2都是数组,当用相同的常量字符串去初始化不同的数组的时候就会开辟不同的空间。而str3和str4指向的同一个常量字符串。在c/c++中会把常量字符串单独存储在一个内存区域(代码段),当我们用几个指针去指向同一个字符串时,它们实际会指向同一块内存的。
可以这么理解:str1和str2是可以修改数组中的元素的,如果不同数组间的修改会相互影响,那岂不是乱遭了。而str3和str4是不可以被修改的,那么让它们两指向同一块空间也是完全没有问题的。

2.指针数组

指针数组就是存放指针的数组。
我们可以进行类比:
整型数组是存放整型的数组,字符数组是存放字符的数组。那么指针数组肯定就是存放指针的数组咯。

int* arr1[10];//整型指针的数组
char* arr2[4];//一级字符指针数组
char** arr3[10];//二级字符指针数组

3.数组指针

3.1 数组指针的定义

我们知道整型指针是指向整型的指针(存放整型变量的地址的指针变量)
还有字符指针是指向字符的指针(存放字符变量的地址的指针变量)
如此类比的话
数组指针就是指向数组的指针(存放数组变量的地址的指针变量)
数组指针的正确写法

int *p1[10];//错
int (*p2)[10];//对
int (*p)[10];
//p与*结合,说明p是一个指针变量,然后指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//加()的原因是因为,根据操作符的优先性,[]的优先级是要高于*的,为了保证*与p的结合需要添加括号

3.2 &数组名与数组名

int arr[10];

arr&arr分别是什么呢?
arr是数组名,数组名又表示数组首元素的地址。
&arr表示的整个数组的地址。

#include <stdio.h>
int main()
{
	int arr[10];
	printf("%p\n",arr);
	printf("%p\n",&arr);
	return 0;
}
//打印结果:
/*
00DCFBDC
00DCFBDC
*/

打印它们的地址可以发现是一样的。但其实又不完全一样。

#include <stdio.h>
int main()
{
	int arr[10];
	printf("%p\n",arr);
	printf("%p\n",arr+1);
	
	printf("%p\n",&arr);
	printf("%p\n",&arr+1);
	return 0;
}
//打印结果:
/*
004FF970
004FF974
004FF970
004FF998
*/

arr+1跳过的4个字节的地址。而&arr跳过的是40个字节的地址。
正如前面所说&arr是整个数组的地址,整个数组大小就是40个字节。
本例中&arr的类型就是int(*)[10],是一种数组指针类型。
数组地址+1,跳过整个数组的大小,所以&arr+1相对于&arr的差值就是40.

3.3 数组指针的使用

了解到数组指针指向的数组,那么数组指针中存放的就是数组的地址。

#include <stdio.h>
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,0};
	int(*p)[10] = &arr;
	//把整个数组的地址存放在数组指针变量当中
	//但是很少这么写
	return 0;
}

数组指针的使用

#include <stdio.h>
void print1(int arr[3][5],int row,int col)
{
	for(int i = 0;i<row;++i)
	{
		for(int j = 0;j<col;++j)
		{
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
}

void print2(int (*arr)[5],int row,int col)
{
	for(int i = 0;i<row;++i)
	{
		for(int j = 0;j<col;++j)
		{
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = {1,2,3,4,5,6,7,8,9,0};
	print1(arr,3,5);
	//arr是数组的数组名,表示数组首元素的地址。而这又是一个二维数组,二维数组的首元素地址就是第一行的地址,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,可以利用数组指针接收。
	print2(arr,3,5);
	
	return 0;
}

区分

int arr[5];//整型数组
int* parr1[10];//整型指针数组
int (*parr2)[10];//数组指针
int (*parr3[10])[5];//数组指针数组

4.数组传参、指针参数

在写代码时不可避免的要把【数组】或者【指针】传递给函数,那么函数的参数设计要怎么做呢?

4.1 一维数组传参

#include <stdio.h>
void test(int arr[])//可行,最容易理解的写法([]内的数字可以随便写,不影响系统的判断)该传参的本质就是int* arr
{}
void test(int arr[10])//可行,最容易理解的写法。([]内的数字可以随便写,不影响系统的判断)该传参的本质就是int* arr
{}
void test(int* arr)//可行,实参传递的是arr代表数组首元素的地址,利用整型指针接收合情合理
{}
void test2(int *arr[20])//可行,本质是int** arr
{}
void test2(int **arr)//可行,实参传递的是arr2也代表首元素的地址,因为arr2是指针数组,一维指针的地址要有二级指针接收,合情合理
{}

int main()
{
	int arr[10] = {0};
	int* arr2[20] = {0};
	test(arr);
	test2(arr2);
	return 0;
}

4.2 二维数组传参

#include <stdio.h>

void test(int arr[3][5])//可行,最容易理解的写法
{}
void test(int arr[][])//不可行,
{}
void test(int arr[][5])//可行
{}
//二维数组传参,函数形参的设计只能省略第一个[]的数字。
//对一个二维数组,可以不知道又多少行,但是必须知道要有多少列。
//因为在内存的二维数组的存放也是线性的,全存一行。知道列数才能知道有多少行。
void test(int* arr)//不可行,二维数组的数组名代表的是数组第一行的地址,是数组指针。要存放这个数组指针是无法用整型指针存放
{}
void test(int* arr[5])//不可行,二维数组的数组名代表的是数组第一行的地址,是数组指针。而这个表示的是指针数组
{}
void test(int (*arr)[5])//可行
{}
void test(int** arr)//不可行,二维数组的数组名代表的是数组第一行的地址,是数组指针.这里是二级指针,不一致。
{}
int main()
{
	int arr[3][5] = {0};
	test(arr);
	return 0;
}

4.3 一级指针传参

#include <stdio.h>
void print(int* arr,int sz)
{
	for(int i = 0;i<sz;++i)
	{
		printf("%d ",*(arr+i));
	}
}
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,0};
	int sz = 10;
	int* p = arr;
	print(arr,sz);
	return 0;
}
//打印结果:1 2 3 4 5 6 7 8 9 0

当函数的参数部分为1级指针的时候,函数能接受的的参数为该一级指针对应类型的地址。

4.4 二级指针传参

#include <stdio.h>
void test(int** ptr)
{
	printf("num = %d\n",**ptr);
}
int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);
	test(&p);
	return 0;
}

当函数的参数部分为二级指针的时候,函数能接受的的参数为该二级指针对应一级指针类型的地址。

5.函数指针

其实函数也是有地址的。

#include <stdio.h>
void test()
{
	printf("hello\n");
}
int main()
{
	printf("%p\n",test);
	printf("%p\n",&test);
	return 0;
}
//打印结果
/*
00A41267
00A41267
*/

从这里可以看出,函数不仅有地址,而且函数的函数名就代表了函数的地址,&函数名同样也表示函数的地址。
既然函数有地址,那么也就说明可以利用变量来存储。这个存储函数地址的变量就是函数指针。
函数指针的正确写法

#include <stdio.h>
void test()
{
	printf("hello\n");
}
int main()
{
	void (*pf1)() = test;//正确写法
	void *pf2() = test;//错误写法
	return 0;
}

pf1可以存储,和数组指针类似,这里的*要先和pf1结合,确定pf1是一个指针,()的优先级有比较高。因此需要用()将*pf1括起来。pf1指向的是一个函数,指向函数无参数,返回类型为void。

#include <stdio.h>
int Add(int x,int y)
{
	return x+y;
}
int main()
{
	int (*pf)(int int) = Add//指向有参数的函数指针
	return 0;
}

练习

//代码1
(*(void(*)())0)();
/*
解释:
先看void(*)()这是一个函数指针类型。再往外看,这个函数指针类型被括号括住了(void(*)())
一个类型被()住就是表示强制类型转换的意思。也是说明0被强制类型转换成了函数指针类型。然后*表示对一个函数指针类型进行解引用取出指向的函数*(void(*)())0,最后再调用这个函数。
总结:调用0地址处的函数(实际是无法调用的)
*/

//代码2
void (*signal(int,void(*)(int)))(int);//signal为函数名
/*
解释:signal是函数名,那signal()中的就是函数的参数类型,类型分别为整型和函数指针类型,现在一个函数有了函数名和函数的参数,就差函数的返回类型,如果我们把signal(int,void(*)(int))删除就得到了void (*)(int)这不就是函数指针类型吗,那也就是说signal的函数的返回类型就是void(*)(int)
总结:这是一个函数的声明,找到其函数参数和函数返回类型就可以了。
*/

简化代码2

typedef void (*pf)(int);
pf signal (int,pf);//利用typedef将类型重命名,来简化代码

6.函数指针数组

数组是存放相同类型数据的存储空间
前面我们已经学习了指针数组

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

同样的我们也可以把函数指针存放进数组,就叫做函数指针数组,那函数指针数组的是如何定义的呢?

int (*pf)();//这是一个函数指针
//我们将[]添加到变量名后面就可以了
int (*pf[10])();//这就是函数指针数组

pf[]结合说明pf是一个数组,然后数组存放的类型就是int(*)()
函数指针数组的运用
下面以实现一个简单的计算器为例

#include <stdio.h>
void menu()
{
	printf("***************************\n");
	printf("**** 1.Add   2.Sub     ****\n");
	printf("**** 3.Mul   4.Div     ****\n");
	printf("**** 0.exit            ****\n");
	printf("***************************\n");
}
int Add(int x,int y)
{
	return x+y;
}
int Sub(int x,int y)
{
	return x-y;
}
int Mul(int x,int y)
{
	return x*y;
}
int Div(int x,int y)
{
	return x/y;
}
int main()
{
	int input = 0;
	int a = 0,b = 0;
	int ret = 0;
	do
	{
		menu();
		printf("选择你所要用到的功能>\n");
		scanf("%d",&input);
		switch(input)
		{
			case 1:
				printf("请输入两数>");
				scanf("%d %d",&a,&b);
				ret = Add(a,b);
				printf("ret = %d\n",ret);
				break;
			case 2:
				printf("请输入两数>");
				scanf("%d %d",&a,&b);
				ret = Sub(a,b);
				printf("ret = %d\n",ret);
				break;
			case 3:
				printf("请输入两数>");
				scanf("%d %d",&a,&b);
				ret = Mul(a,b);
				printf("ret = %d\n",ret);
				break;
			case 4:
				printf("请输入两数>");
				scanf("%d %d",&a,&b);
				ret = Div(a,b);
				printf("ret = %d\n",ret);
				break;
			case 0:
				printf("退出\n");
				break;
			default:
				printf("输入错误\n");
				break;
		}
	}while(input);
	return 0;
}

这样写的话其实是很繁琐的。实现这种简单的功能都写了这么长的代码,而且如果后续在添加什么函数功能的话,代码又要增加好的。所以我们要简化。
通过观察,这4个函数的参数和返回类型都是相同的,那么不就说明了可以写成函数指针数组吗。数组中存放这个函数指针类型就可以了。

#include <stdio.h>
void menu()
{
	printf("***************************\n");
	printf("**** 1.Add   2.Sub     ****\n");
	printf("**** 3.Mul   4.Div     ****\n");
	printf("**** 0.exit            ****\n");
	printf("***************************\n");
}
int Add(int x,int y)
{
	return x+y;
}
int Sub(int x,int y)
{
	return x-y;
}
int Mul(int x,int y)
{
	return x*y;
}
int Div(int x,int y)
{
	return x/y;
}
int main()
{
	int input = 0;
	int a = 0,b = 0;
	int ret = 0;
	do
	{
		menu();
		printf("选择你所要用到的功能>\n");
		scanf("%d",&input);
		int(*pf[5])(int,int) = {NULL,Add,Sub,Mul,Div};//存入NULL是为了可以和菜单对应
		if(input>0&&input<5)
		{
			printf("请输入两数>");
			scanf("%d %d",&a,&b);
			ret = pf[input](a,b);
			printf("ret = %d\n",ret);
		}
		else if(input == 0)
			printf("退出\n");
		else
			printf("输入有误\n");
	}while(input);
	return 0;
}

利用函数指针数组我们将该程序充分简化,而且如果后续还要添加类似的函数功能的话,我们只需要将新写的函数添加进数组,在改变一下判断条件即可。

7.指向函数指针数组的指针

指向函数指针数组的指针是一个指针。
指针指向一个数组,数组的元素都是函数指针;

void test(const char* str)
{
	printf("%s\n",str);
}
int main()
{
	//函数指针pfun
	void(*pfun)(const char*) = test;
	//函数指针的数组pfunarr
	void(*pfunarr[5])(const char* str);
	//指向函数指针数组pfunarr的指针ppfunarr
	void(*(*ppfunarr)[5])(const char*) = &pfunarr;
}

还可以再绕下去的

8.回调函数

回调函数就是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数,我们就说这是回调函数。回调函数不是由该函数的实现直接调用,而是再特定的事件或条件发生时由另一方的调用,用于对该事件或条件进行响应。

首先演示一下qsort函数的使用:

//对整型数组进行排序
#include<stdio.h>
int int_cmp(const void* a,const void* b)
{
	return (*(int*)a) - (*(int*)b);
}
int main()
{
	int arr[10] = {1,3,5,7,9,2,4,6,8,0};
	qsort(arr,10,sizeof(int),int_cmp);
	for(int i = 0;i<10;++i)
	{
		printf("%d ",arr[i]);
	}
	printf("\n");
	return 0;
}
//打印结果:
//0 1 2 3 4 5 6 7 8 9

//对结构体数组进行排序
#include <stdio.h>
#include <string.h>
struct stu
{
	int age;
	char name[10];
};
int struct_cmp_age(const void* a, const void* b)//利用年龄排序
{
	return ((struct stu*)a)->age - ((struct stu*)b)->age;
}
int struct_cmp_name(const void* a, const void* b)//利用名字排序,因为字符串无法相减
//所以这里利用了strcmp进行字符串的比较
{
	return strcmp(((struct stu*)a)->name,((struct stu*)b)->name);
}
int main()
{
	struct stu s[3] = { {17,"yui"},{14,"anna"},{20,"hua"} };
	qsort(s, 3, sizeof(s[0]), struct_cmp_age);
	printf("调用struct_cmp_age\n");
	for (int i = 0; i < 3; ++i)
	{
		printf("%d %s\n", s[i].age, s[i].name);
	}
	qsort(s, 3, sizeof(s[0]), struct_cmp_name);
	printf("调用struct_cmp_name\n");
	for (int i = 0; i < 3; ++i)
	{
		printf("%d %s\n", s[i].age, s[i].name);
	}
	return 0;
}
//打印结果:
/*
调用struct_cmp_age
14 anna
17 yui
20 hua
调用struct_cmp_name
14 anna
20 hua
17 yui

*/

qsort
打开cplusplus网站->qsort
qsort

void qsort(void* base,//需要排序的数组首元素地址
		  size_t num,//需要排序的数组的元素个数
		  size_t size,//需要排序数组的单个元素的大小
		  int (*compar)(const void*,const void*)//传递函数指针,需要自己写
		  }

可以看到的时,这里接受数组首元素的地址是用void*来接收。
提问:为什么呢?
回答:Void*指针 是无具体类型的指针。Void* 类型的指针可以接任意类型的地址(这种类型的指针是不能直接解引用操作的,也不能直接进行指针运算的)。
所以用void*接收是没问题的。然后,这个qsort函数不仅可以对整型数组排序,还可以对字符数组,浮点型数组,甚至是结构体数组。这也就造成了不能使用特定类型指针来接收的情况,如果使用了特定的类型,那其它类型就不能被接收了,所以才会选择使用void*来接收。
模拟实现qsort,但是因为目前还没有学快速排序,所以这里我们利用冒泡排序替代。

//主要逻辑
void swap(char* a, char* b, int size)
{
	char tmp = 0;
	for (int i = 0; i < size; ++i)//交换的实质其实就是指针所指向内容的交换.
	//因为char只能指向一个字节,所以我们需要传递size了解到要交换的字节大小,然后一个字节一个字节的交换。
	{
		tmp = *a;
		*a = *b;
		*b = tmp;
		a += 1;
		b += 1;
	}
}
void bubble_sort(void* base, int num, int size, int(*cmp)(const void*, const void*))
{
	for (int i = 0; i < num - 1; ++i)
	{
		for (int j = 0; j < num - i - 1; ++j)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0)//因为void类型的指针是不能直接解引用操作的,也不
			//能直接进行指针运算的。为了拿到比较位置的地址,我们需要将base强转为(char*),因为char*的加减整数时只会跳过一个字节,
			//这是最小的位移距离了。所以我们可以通过强转后的base拿到base[j]和base[j+1]的地址进行比较。
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);//开始交换
			}
		}
	}
}

测试

#include <stdio.h>
#include <string.h>
struct stu
{
	int age;
	char name[10];
};
int int_cmp(const void* a, const void* b)
{
	return (*(int*)a) - (*(int*)b);
}
int struct_cmp_age(const void* a, const void* b)//利用年龄排序
{
	return ((struct stu*)a)->age - ((struct stu*)b)->age;
}
int struct_cmp_name(const void* a, const void* b)//利用名字排序,因为字符串无法相减
//所以这里利用了strcmp进行字符串的比较
{
	return strcmp(((struct stu*)a)->name,((struct stu*)b)->name);
}
void swap(char* a, char* b, int size)
{
	char tmp = 0;
	for (int i = 0; i < size; ++i)//交换的实质其实就是指针所指向内容的交换,因为char只能指向一个字节,所以我们需要传递size了解到要交换的字节大小,然后一个字节一个字节的交换。
	{
		tmp = *a;
		*a = *b;
		*b = tmp;
		a += 1;
		b += 1;
	}
}
void bubble_sort(void* base, int num, int size, int(*cmp)(const void*, const void*))
{
	for (int i = 0; i < num - 1; ++i)
	{
		for (int j = 0; j < num - i - 1; ++j)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0)//因为void类型的指针是不能直接解引用操作的,也不能直接进行指针运算的。为了拿到比较位置的地址,我们需要将base强转为(char*),因为char*的加减整数时只会跳过一个字节,这是最小的位移距离了。所以我们可以通过强转后的base拿到base[j]和base[j+1]的地址进行比较。
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}
int main()
{
	int arr[10] = { 1,3,5,7,9,2,4,6,8,0 };
	bubble_sort(arr, 10, sizeof(int), int_cmp);//冒泡排序
	printf("对arr排序:\n");
	for (int i = 0; i < 10; ++i)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	struct stu s[3] = { {17,"yui"},{14,"anna"},{20,"hua"} };
	bubble_sort(s, 3, sizeof(s[0]), struct_cmp_age);
	printf("调用struct_cmp_age:\n");
	for (int i = 0; i < 3; ++i)
	{
		printf("%d %s\n", s[i].age, s[i].name);
	}
	bubble_sort(s, 3, sizeof(s[0]), struct_cmp_name);
	printf("调用struct_cmp_name:\n");
	for (int i = 0; i < 3; ++i)
	{
		printf("%d %s\n", s[i].age, s[i].name);
	}
	return 0;
}
//打印结果:
/*
对arr排序:
0 1 2 3 4 5 6 7 8 9
调用struct_cmp_age:
14 anna
17 yui
20 hua
调用struct_cmp_name:
14 anna
20 hua
17 yui
*/

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

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

相关文章

C语言------指针讲解(2)

目录 一、数组名的理解 二、使用指针访问数组 三、一维数组传参的本质 四、冒泡排序 五、二级指针 六、指针数组 七、指针数组模拟二维数组 一、数组名的理解 通过学习&#xff0c;我们知道&#xff1a;数组名和数组首元素的地址打印出来的结果一模一样&#xff0c;数组…

C语言函数:编程世界的魔法钥匙(1)

目录 1.C语言中的函数是什么&#xff1f; 2.函数的分类&#xff1a; 2.1 标准库函数 2.1.1 库函数的诞生&#xff1a; 2.1.2 库函数的作用&#xff1a; 2.1.3 如何学习使用库函数 2.2 自定义函数 2.2.1 函数的组成&#xff1a; 2.2.2 自定义函数的优点 2.2.3 例题 3…

Windows下安装Mujoco1.50

Windows下安装Mujoco1.50复现强化学习论文 很多经典强化学习算法&#xff08;DDPG、PPO&#xff09;使用Mujoco环境进行实验和评估&#xff0c;配置复现环境非常困难&#xff0c;有以下几点原因&#xff1a; 年代久远&#xff0c;Mujoco-py的依赖管理做的不好&#xff0c;仅限…

Apache trino的ldap认证开启

作者&#xff1a;櫰木 1、背景 由于trino 默认没有开启用户认证体系&#xff0c;需要ldap用户进行认证。开启tls和ldap用户认证&#xff0c;提高安全性。 2、配置 前置条件。 trino 集群已经部署完成 ldap 服务 openjdk 版本大于11.0.17 生成证书 keytool -genkeypair…

实现异步天气数据获取与Spring缓存集成

你好呀&#xff0c;我是小邹。 在Web应用中&#xff0c;实时天气数据的获取是一个常见的需求&#xff0c;特别是在需要频繁更新天气信息的场景下&#xff0c;如旅游网站、天气应用或任何需要展示地理位置相关天气的应用。然而&#xff0c;频繁的外部API调用不仅会增加服务器的…

Hive理论讲解

Hive介绍 1、Hive本质 Hive本质是【数仓设计方案】&#xff0c;hive本身并不存储数据【数据包含&#xff1a;元数据 (表)数据】。 2、hql和sql对比 sql 结构化查询语言【structured query language】hql hive/hadoop类sql查询语言【hive/hadoop query language like sql…

工业三防平板可优化工厂流程管理

在当今高度自动化和数字化的工业生产环境中&#xff0c;工业三防平板正逐渐成为优化工厂流程管理的关键工具。其强大的功能和卓越的性能&#xff0c;为工厂带来了更高的效率、更低的成本以及更出色的质量控制。 工业三防平板&#xff0c;顾名思义&#xff0c;具备防水、防尘、防…

(01)Unity使用在线AI大模型(使用百度千帆服务)

目录 一、概要 二、环境说明 三、申请百度千帆Key 四、使用千帆大模型 四、给大模型套壳 一、概要 在Unity中使用在线大模型分为两篇发布&#xff0c;此篇文档为在Python中使用千帆大模型&#xff0c;整体实现逻辑是&#xff1a;在Python中接入大模型—>发布为可传参的…

eclipse 新建类class文件增加copyright版权信息

1、Window -> Preferences 2、输入code,找到code templates Java > Code Style > Code Templates 比如进行如何的设置&#xff1a; 3、新增类文件&#xff0c;会自动增加版权&#xff1a;

[Vulnhub] digitalworld.local-JOY snmp+ProFTPD权限提升

信息收集 IP AddressOpening Ports192.168.101.150TCP:21,22,25,80,110,139,143,445,465,587,993,995 $ nmap -p- 192.168.101.150 --21,22,25,min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 21/tcp open ftp ProFTPD | ftp-anon: Anonymous FTP logi…

昇思25天学习打卡营第11天|NLP-LSTM-CRF序列标注

打卡 目录 打卡 序列标注 条件随机场(Conditional Random Field, CRF) SCORE计算 SCORE 计算的代码 Normalizer计算 Normalizer 计算的代码实现 Viterbi算法 CRF层组装 BiLSTMCRF模型 模型实例化 模型训练 模型预测 新造一个句子重新预测看效果 序列标注 序列标…

安装adb和常用命令

下载ADB安装包 https://dl.google.com/android/repository/platform-tools-latest-windows.zip 解压安装包 解压如上下载的安装包&#xff0c;然后复制adb.exe所在的文件地址 配置环境变量 我的电脑——>右键属性——>高级系统设置——>环境变量——>系统变量—…

solidity基础语法(以太坊solidity合约)

solidity基础语法&#xff08;以太坊solidity合约&#xff09; 1-值类型和取值范围2-引用类型3-引用类型高阶4-固定数组和动态数组 1-值类型和取值范围 https://learnblockchain.cn/docs/solidity/introduction-to-smart-contracts.html#subcurrency https://learnblockchain…

R语言包AMORE安装报错问题以及RStudio与Rtools环境配置

在使用R语言进行AMORE安装时会遇到报错&#xff0c;这时候需要采用解决办法&#xff1a; AMORE包安装&#xff0c;需要离线官网下载安装包&#xff1a; Index of /src/contrib/Archive/AMORE (r-project.org)https://cran.r-project.org/src/contrib/Archive/AMORE/ 一、出现…

MySQL第九次作业

1、安装redis&#xff0c;启动客户端、验证。 首先选择版本 进去之后是这样&#xff0c;一个是压缩包&#xff0c;一个是安装包 点击下载好的安装包&#xff0c;开始安装 选择“添加Redis目录到环境变量PATH中”&#xff0c;这样方便系统自动识别Redis执行文件在哪里。 其他一…

新版本 idea 创建不了 spring boot 2 【没有jkd8选项】

创建新项目 将地址换成如下 https://start.aliyun.com/

厂家置换电费如何达到最大化收益

新能源行业知识体系-------主目录-----持续更新https://blog.csdn.net/grd_java/article/details/140004020 文章目录 一、电能电费二、同时刻不同厂家置换&#xff0c;不会影响最终电能电费结果三、风险防范补偿和回收机制四、我们的数据如何考虑补偿和回收五、如何利用补偿和…

元宇宙深入解析

元宇宙&#xff08;Metaverse&#xff09;是一个新兴的概念&#xff0c;它激发了技术专家、艺术家和商业领袖的无限想象。它代表着数字互动的新前沿&#xff0c;提供了一个平行的数字宇宙&#xff0c;用户可以在其中实时互动&#xff0c;超越物理世界的限制。 元宇宙是什么&am…

计算机毕业设计django+hadoop+scrapy租房可视化 租房推荐系统 租房大屏可视化 租房爬虫 spark 58同城租房爬虫 房源推荐系统

python scrapy bootstrap jquery css javascript html 租房信息数据展示 租房地址数量分布 租房类型统计 租房价格统计分析 租房面积分析 房屋朝向分析 房屋户型平均价格统计分析 房屋楼层统计分析 房屋楼层与价格统计分析 房屋地址与价格统计分析 房屋相关信息词云展示 租房…

计算机网络——网络层(路由选择协议、路由器工作原理、IP多播、虚拟专用网和网络地址转换)

目录 路由选择协议 因特网的路由选择协议特点 路由信息协议RIP RIP衡量目的网络距离 RIP选择路由器的方式 RIP具有以下三个重要特点 RIP的基本工作流程 RIP的距离向量算法 ​编辑 ​编辑 RIP存在的问题——“坏消息传播得慢” RIP的封装 开放最短路径优先协议OSPF…