指针的深入讲解

news2024/12/20 22:46:42

本章重点:

  1. 字符指针
  2. 数组指针
  3. 指针数组
  4. 数组传参和指针传参
  5. 函数指针
  6. 函数指针数组
  7. 指向函数指针数组的指针
  8. 回调函数

我们在指针的初阶的时候主要讲了:

1.指针就是变量,用来存放地址,地址唯一标识一块内存空间

2.指针的大小是固定4个字节/8个字节(32为平台/64位平台)

3.指针是有类型的,指针的类型决定了指针+-整数的步长,指针解引用操作的时候的权限。

4.指针的运算

这里我们来探讨指针更高级的主题

1.字符指针

 字符指针就是char*类型的指针

一般使用:

使用1:

使用2:

一般表达式都有两个属性,值属性和类型属性。

char* p="abcdef",是把字符串首元素的地址放到p中。用%s打印时,只需要提供一个起始地址即可。这里"abcdef"是一个常量字符串,不能被该,所以我们要加上const修饰

#include<stdio.h>

//字符指针
int main()
{
	const char* p = "abcdef";//把字符串首元素的地址放到p中
	printf("%s", p);
	return 0;
}

要是想把字符串放进一个变量里面,需要创建一个数组

练习:分析下列代码及其结果产生的原因

首先p1,p2中存放的都是常量字符串,常量字符串不能被改,没必要存放多份,在只读数据区,相同的常量字符串只需要存一个,而p1,p2都是首元素a的地址,指向的是同一块内存空间,所以p1==p2。而使用相同的常量字符串来初始化数组时会开辟出不同的内存块,arr1和arr2是数组名,数组名表示首元素的地址,内存空间不同,所以首元素的地址不同。

2.指针数组

指针数组是一个存放指针的数组

int arr[10]是整型数组--用来存放整形数据的数组

char arr[10]是字符数组--用来存放字符型数据的数组

int *arr[10]是整形指针数组--用来存放整型指针类型数据的数组

char *arr[10]是字符型指针数组--用来存放字符型指针类型数据的数组

我们可以用一个指针数组来模拟一个二维数组(初阶讲过)

#include<stdio.h>

//指针数组
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* parr[] = { arr1,arr2,arr3 };//数组名表示首元素地址
	int i = 0;
	for (i = 0;i < 3;i++)
	{
		int j = 0;
		for (j = 0;j < 5;j++)
		{
			printf("%d ",*(parr[i]+j));
			//printf("%d ",parr[i][j]);
		}
		printf("\n");
	}

	return 0;
}

打印方法1:

parr[i]是找到下标为i的元素(整形指针类型)的地址,parr[0]-->arr1,就是arr1这个数组的首元素的地址,parr[1]-->arr2;parr[2]-->arr3;然后让你后我们要求第几个元素就再让这个地址加上几,求arr1[0]的地址就是arr1+0,arr1[1]的地址就是arr1+1,(指针与指针相减跳过的是元素的个数,所以指针+元素的个数,得到的就是下一个指针),我们在对地址进行解引用就可以得到再arr[i][j]处元素,从而模拟出一个二维数组。

打印方法2:

有两种理解

arr[i]-->*(p+i)-->*(arr+i)-->i[arr] 

所以*(arr[i]+j)-->arr[i][j]

还可以理解成arr[i]-->arr1,arr2,arr3,而访问元素通过下标来访问,就是arr1[0],访问数组1的第一个元素。

3.数组指针

[]的优先级高于*,所以要加上括号表示先结合)

整型指针--指向整形的指针int a=3; int* p=&a;指针变量p是int*类型的,指向的是元素是a,a是一个整形类型的数据,所以p是整形指针

数组指针--指向数组的指针int(*p)[10],p是一个数组指针,p可以指向一个数组,该数组有10个元素,每个元素是int类型的(指向一个有10个元素的整型数组)。

 这里p的类型是int(*)[10](数组指针类型),存放的是arr这个数组所有元素的地址(之前在数组章节说过arr表示的首元素地址,两种情况除外1.sizeof 2.&arr)

再举个例子: 

有个指针数组,char* arr[5]={0};

若要将这个指针数组整个数组的地址存放起来,需要用什么接收

char *(*p)[5],需要用一个数组指针接收,这个数组指针指向的内容是这个指针数组就是char*arr[5],这个数组指针的类型是char*(*)[5]。

在定义类型时int(*)[ ] ,一定要把[ ]里面的数加上,指明指向的数组有几个元素。

数组指针一般用在二维数组中。

对于一维数组:
我们如果想访问它的元素,或者通过他的地址改变它的元素,只需要存入首元素的地址,用一个指针变量存放即可,用数组指针存放整个数组的指针还会将问题复杂化。

#include<stdio.h>

//数组指针
int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int* p = arr;
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		printf("%d ", *(p + i));
	}

	return 0;
}
#include<stdio.h>

//数组指针
int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int (*p)[10] = &arr;
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		printf("%d ", *(*p + i));//解引用p就相当于通过p找到了arr这个数组,而arr又是数组名,数组名就是首元素的地址,再通过他在到底i个元素的地址,在解引用,才能访问到数组内容
	}
	//int a = 0;
	//int*p=&a
	return 0;
}

对于二维数组

#include<stdio.h>

//数组指针
void print_(int arr[][4], int r, int c)
{
	int i = 0;
	for (i = 0;i < r;i++)
	{
		int j = 0;
		for (j = 0;j < c;j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
	print_(arr, 3, 4);
	return 0;
}

根据下边实际传入地址我们知道列是不可以省略的。需要将第一行的元素都传入函数。 

我们也可以用数组指针表示,二维数组的数组名表示的也是首元素的地址,但是二维数组的首元素是他第一行的元素。我们接受这个二维数组,就应该用一个数组指针接受。

#include<stdio.h>

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

*(*(p + i) + j)的理解:存的是一行的元素的地址,(p+i)就相当于第i行的元素的地址(虽然数据是连续存储的,但是我们可以把发看成一个三行四列的)*(p+i)就是通过这个地址找到了第一行的元素,(而二维数组可以看成一维数组的数组,就是可以将指向的第一行看成arr[0]此时arr[0]是一个数组名,数组名又是这行元素的首元素的地址,所以,可以通过,首元素的地址+j,找到第j列的元素的地址,在对这个地址进行解引用,就得到了第i行,第j列元素的地址。

int(*p)[4];p的类型是:int(*)[4],p是指向一个整型数组的,数组5个元素 int[5],p+1,表示跳过5个int元素的数组。 

数组指针和指针数组:

 指针数组就是一个数组中放的元素都是由地址组成的数组,可以是&a,&b,&c,将几个元素的地址放在指针数组中,也可以是将几个数组的首元素的地址放在指针数组中,例如:模拟二维数组(我们就可以通过这个每个数组的首元素找到这个数组,在由数组名找到每一个元素,对他进行访问。

而数组指针是指向一个数组的指针,存放的是这个数组整个数组的地址,但我们一般不单于存放一个数组的地址,我们通常使用的是存放一个二维数组(数组名表示第一行元素的地址,将第一行元素传过去由一个数组指针接收,然后通过这个数组指针访问每一行的元素)

我们来分析下面代码的意思

int arr[5];

int *parr1[10];

int(*parr2)[5];

int(*parr3[5])[3];

arr是一个数组,存放5个整型元素

parr1是一个指针数组,存放指针变量的数组

parr2是一个数组指针,是一个指针,指向一个int [5]有五个元素组成的整型数组

parr3是一个存放数组指针的数组,首先在()里面parr3先和[]结合,构成一个数组,然后这个数组的类型是int(*)[3]是一个数组指针类型,表示一个数组里有5个元素,每个元素指向的都是一个有三个元素的数组。 

4.数组参数和指针参数

一维数组传参:

 void test1(int arr1[])//数组传参由数组接受,元素个数可以不写,传入的是首元素的地址
{ }
void test1(int arr1[10])
{ }
void test1(int *arr)//数组传参实际上传入的是首元素的地址,可以有指针变量接收
{ }
void test2(int*arr[])//指针数组由数组接受
{ }
void test2(int **arr)//指针数组是一个存放指针的数组,相当于存放的元素是指针变量,指针变量的地址应该由二级指针接收(二级指针是一个存放一级指针的指针)
{ }
int main()
{
    int arr1[10] = { 0 };
    int* arr2[20] = { 0 };
    test1(arr1);
    test2(arr2);
    return 0;
}

二维数组的传参: 

void test(int arr[3][4])//二维数组传参由一个二维数组接收
{ }
void test(int arr[][4])//行可以省略,列不能省略,传入的是第一行元素的地址,需要知道第一行有多少个元素
{ }
void test(int(*p)[4])//二维数组传参,实际上传的是第一行的地址,应该用一个数组指针接收一行的地址,还需要知道一行有多少个元素
{ }

int main()
{
    int arr[3][4];
    test(arr);
    return 0;
}

 一级指针传参:

void test(int *p)//一级指针传参由一级指针接收
{ }
int main()
{
    int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
    int* p = arr;
    test(p);
    return 0;
}

如果函数的形式参数部分是一级指针:传入的可能是一级指针变量,可能是某个元素的地址(地址由指针接收),也可能是数组名(数组名就表示首元素的地址),不过还要注意一级指针的类型

二级指针传参:

void test(int** p)
{ }
int main()
{
    int n = 0;
    int* p = &n;
    int** pp = &p;
    test(pp);//二级指针传参由二级指针接收
    test(&p);//一级指针的地址传参有二级指针接收
    return 0;
}

 如果函数的形式参数是二级指针,传入的可能是二级指针变量,也可能是一级指针的地址,也可能是指针数组(指针数组是一个存放指针的数组,相当于存放的元素是指针变量,指针变量的地址应该由二级指针接收(二级指针是一个存放一级指针的指针))。

 5.函数指针

指向函数的指针就是函数指针

函数在创建时就有地址,和全局变量一样,函数在代码区,代码区是只读的,不能被修改。

&数组名,是取出数组的地址,同理&函数名是取出函数的地址,但是对于函数来说&函数名和函数名都是取出函数的地址(对于函数没有首元素一说)

定义一个函数指针有两种写法:

int ADD(int x, int y)
{
    return x + y;
}
int main()
{
    int (*pf)(int, int) = &ADD;
    int (*pf)(int, int) = ADD;
    return 0;
}

这里(*pf)表示pf是一个指针,指向的是(int,int)是一个函数,函数的返回值类型为int,所以pf是一个函数指针,指针类型是int(*)(int,int)。

若果我们定义一个整形指针的话,我们可以解引用这个指针变量,访问它指向的元素,并可以对这个元素进行修改,那我们也可以通过解引用访问这个函数,从而进行传参就是(*pf)(x,y)。这里我们也可以不解引用,访问函数就行传参,再定义函数指针的第二种写法将函数名赋给指针变量pf那么pf就相当于这个函数名,我们一般的函数调用是ADD(x,y),直接调用函数传参,那么同理,我们也可以写成pf(x,y)。但需要注意加*号的时候一定要带上括号,否则会先调用函数(就是利用第二种调用方法,调用过后在对这个值进行解引用,此时这个值是函数返回的一个int类型的数不能对他进行解引用)

所以利用函数指针调用函数也有两种写法:

#include<stdio.h>

//函数指针
int ADD(int x, int y)
{
    return x + y;
}
int main()
{
    //int (*pf)(int, int) = &ADD;
    int (*pf)(int, int) = ADD;
    int ret1 = pf(3, 4);
    int ret2 = (*pf)(3,4);
    printf("%d\n", ret1);
    printf("%d\n", ret2);

    return 0;
}

函数指针的初步应用:
 可以在将这个函数以函数指针的形式传递给另一个函数,在另一个函数使用这个函数时,就不需要再调用这个函数了。

#include<stdio.h>

//函数指针的应用
int ADD(int x, int y)
{
	return x + y;
}
void calc(int(*p)(int, int))
{
	int a = 3;
	int b = 4;
	int ret = p(3, 4);
	printf("%d\n", ret);
}
int main()
{
	calc(ADD);
	return 0;
}

观察两段有趣的代码(来自于C陷阱和缺陷)

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

//void(*)(),是函数指针类型,(void(*)())0,是将int型的0,强制类型转换为函数指针类型,这个代码是一次函数调用,调用的是0作为地址处的函数,首先把0强制类型转换为:无参,返回值类型为void的函数的地址。在调用0地址处的这个函数。


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

//这是一个函数指针类型的函数声明,signal函数的的第一个参数类型为int,第二个参数的类型为 void(*)(int)函数指针类型,函数指针类型的函数声明的函数指针类型是一个指向函数的参数是int,指向函数的返回值类型void的函数指针。signal函数的返回值类型没写,也默认是void。

这里我们可以知道*p(int,int)是一个函数声明,先于函数结合,p函数返回值类型为void,(*p)(int,int)是一个函数指针,指向的函数的参数类型为int ,int型,指向函数的返回值类型也是void。()的优先级大于*。

我们将第二段代码简化一下:

//typedef unsigned int uint;//把unsigned int,定义为uint
typedef void(*pf_t)(int);//把void(*)(int)类型重命名为pf_t
int main()
{
    //void(*signal(int, void(*)(int)))(int);
    pf_t signal(int, pf_t);
    return 0;
}

进一步应用函数指针:实现简单加减乘除的计算器

初写代码

#define _CRT_SECURE_NO_WARNINGS
#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 x = 0;
	int y = 0;
	do
	{
		menu();
		printf("请输入你的选择->");
		scanf("%d", &input);
		printf("请输入两个整数->");
		scanf("%d %d", &x, &y);
		switch (input)
		{
		case 1:
			printf("%d\n", add(x, y));
			break;
		case 2:
			printf("%d\n", sub(x, y));
			break;
		case 3:
			printf("%d\n", mul(x, y));
			break;
		case 4:
			printf("%d\n", div(x, y));
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

这个代码我们可以实现简单的加减乘删除,但如果我们输入的0,或者输入错误的时候他不会直接提醒我们输入错误,而是会继续让我们输入两个整数,这个时候我们就应该考虑一下如何改进这个代码,初步改进:我们可以将输入两个整数的算法放到计算器内部

#define _CRT_SECURE_NO_WARNINGS
#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 x = 0;
	int y = 0;
	do
	{
		menu();
		printf("请输入两个整数->");
		scanf("%d %d", &x, &y);
		switch (input)
		{
		case 1:
			printf("请输入两个整数->");
			scanf("%d %d", &x, &y);
			printf("%d\n", add(x, y));
			break;
		case 2:
			printf("请输入两个整数->");
			scanf("%d %d", &x, &y);
			printf("%d\n", sub(x, y));
			break;
		case 3:
			printf("请输入两个整数->");
			scanf("%d %d", &x, &y);
			printf("%d\n", mul(x, y));
			break;
		case 4:
			printf("请输入两个整数->");
			scanf("%d %d", &x, &y);
			printf("%d\n", div(x, y));
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

但是这样写,显而易见有很多重复的步骤,所以我们可以进一步改进:用一个函数来分装这一个过程,在这里面调用加法器,减法器……,根据传入函数的不同,我们可以在一个函数里面进行不同的运算。要是直接在函数内部调用add,一次只能调用一个函数,本质上还是需要重复这个步骤,但是我么可以由函数指针接受不同的函数完成每一步的调用,大大减少了代码重复率。

#define _CRT_SECURE_NO_WARNINGS
#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 calc(int(*p)(int,int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个整数->");
	scanf("%d %d", &x, &y);
	int ret = p(x, y);
	printf("%d\n",ret);
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请输入你的选择->");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(add);
			break;
		case 2:
			calc(sub);
			break;
		case 3:
			calc(mul);
			break;
		case 4:
			calc(div);
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

这样我们就大大简化了计算机设计的步骤。 

6.函数指针数组

把函数的指针存到一个数组中,那这个数组就叫函数指针数组。

函数指针数组的定义:

//函数指针数组
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(*pf)(int, int) = add;
    int(*arr[4])(int, int) = { add,sub,mul,div };
}

 函数指针数组的调用:

int main()
{
    int(*pf)(int, int) = add;
    int(*arr[4])(int, int) = { add,sub,mul,div };
    int i = 0;
    for (i = 0;i < 4;i++)
    {
        int ret = arr[i](3, 4);//调用函数指针时可以解引用也可以不解引用
        printf("%d\n", ret);
    }
}

 由函数指针数组,我们还可以进一步把上面模拟简单计算器的实现,用函数指针数组的思想进行调用

#define _CRT_SECURE_NO_WARNINGS
#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(*arr[4])(int, int) = { add,sub,mul,div };
int calc()
{
	int(*arr[])(int, int) = { 0,add,sub,mul,div };

}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int(*arr[])(int, int) = { 0,add,sub,mul,div };
	do
	{
		menu();
		printf("请输入你的选择->");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出游戏");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个整数->");
			scanf("%d %d", &x, &y);
			int ret = arr[input](x, y);//通过数组下标访问,找到这个函数
			printf("%d\n", ret);
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	} while (input);
	return 0;
}

这种方法简化了修改代码,要是想要加别的计算,直接放到数组里面,在改变一下条件的范围即可。在数组首位补0,可以领输入的数直接跳转到需要的函数的位置,进而对函数进行调用。 

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

int(*(*p)[10])();;

//首先p先和*结合是一个指针变量,然后这个指针指向一个数组,所以是数组指针,这个数组有是函数指针类型int(*)()的数组,所以这是一个指向函数指针数组的指针。

8.回调函数

回调函数就是一个通过函数指针调用的函数,若果你把函数指针或者地址作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数(上面我们用函数指针和函数指针数组模拟简单计算器时都用到了回调函数)回调函数不是由该函数的实现方直接调用的,而是在特定的时间或者条件发生时由另外一方调用的,用于对该事件或者条件进行响应。

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

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

相关文章

网络多层的协议详述

网络层 1&#xff09;地址管理&#xff1a;制定一系列的规则&#xff0c;通过地址&#xff0c;在网络上描述出一个设备的位置 2&#xff09;路由选择&#xff1a;网络环境比较复杂&#xff0c;从一个节点到另一个节点&#xff0c;存在很多条不同的路径&#xff0c;需要规划出…

Zabbix6.0升级为6.4

为了体验一些新的功能&#xff0c;比如 Webhook 和问题抑制等&#xff0c;升级个小版本。 一、环境信息 1. 版本要求 一定要事先查看官方文档&#xff0c;确认组件要求的版本&#xff0c;否则版本过高或者过低都会出现问题。 2. 升级前后信息 环境升级前升级后操作系统CentOS…

UML复习题

用例与用户的4种关系对象图和类图有什么关系:对象图是类图某一时刻的快照组件图&#xff0c;体现的是静态图部署图&#xff0c;涉及到硬件的结点&#xff0c;实线链接 以上都是静态图 时序图&#xff0c;消息先后协作图 &#xff0c;谁和谁交互&#xff0c;对象之间的交互某一…

【MFC】多工具栏如何保存状态

MFC中的工具栏本来只有一个&#xff0c;如果想增加几个工具栏是比较简单&#xff0c;但现在一个重要的问题是&#xff0c;状态无法保存&#xff0c;导致每次打开&#xff0c;工具栏就会出现问题&#xff0c;要么偏移位置要么显示不出。 经过研究&#xff0c;发现是MFC框架中的…

Buck开关电源闭环控制的仿真研究15V/5V[Matlab/simulink源码+Word文档]

课题设计要求 ⑴输入直流电压(VIN)&#xff1a;15V ⑵输出电压(VO)&#xff1a;5.0V ⑶负载电阻&#xff1a;R2欧 ⑷输出电压纹波峰-峰值 Vpp≤50mV &#xff0c;电感电流脉动&#xff1a;输出电流的10% ⑸开关频率(fs)&#xff1a;100kHz ⑹BUCK主电路二极管的通态压降VD0.5V…

鸿蒙项目云捐助第十八讲云捐助我的页面下半部分的实现

鸿蒙项目云捐助第十八讲云捐助我的页面下半部分的实现 在一般的应用app中都会有一个“我的”页面&#xff0c;在“我的”页面中可以完成某些设置&#xff0c;也可以完成某些附加功能&#xff0c;如“修改密码”等相关功能。这里的鸿蒙云捐助也有一个“我的”功能页面。这里对“…

Flink2.0未来趋势中需要注意的一些问题

手机打字&#xff0c;篇幅不长&#xff0c;主要讲一下FFA中关于Flink2.0的未来趋势&#xff0c;直接看重点。 Flink Forward Asia 2024主会场有一场关于Flink2.0的演讲&#xff0c;很精彩&#xff0c;官方也发布了一些关于Flink2.0的展望和要解决的问题。 1.0时代和2.0时代避免…

《深入浅出Apache Spark》系列⑤:Spark SQL的表达式优化

导读&#xff1a;随着数据量的快速增长&#xff0c;传统的数据处理方法难以满足对计算速度、资源利用率以及查询响应时间的要求。为了应对这些挑战&#xff0c;Spark SQL 引入了多种优化技术&#xff0c;以提高查询效率&#xff0c;降低计算开销。本文从表达式层面探讨了 Spark…

在Tomcat中部署应用时,如何通过域名访问而不加端口号

--江上往来人&#xff0c;但爱鲈鱼美。 --君看一叶舟&#xff0c;出没风波里。 在Tomcat中部署应用时&#xff0c;如果你希望通过域名访问而不加端口号&#xff08;默认HTTP端口80或HTTPS端口443&#xff09;&#xff0c;你通常需要在前端使用一个反向代理服务器&#xff08;如…

如何测量分辨率

一、什么是分辨率&#xff1f; 分辨率指的是分清物体细节的能力。分辨率是一个成像系统还原空间频率的能力。一些人只是简单的用分辨率去描述极限分辨率&#xff0c;但是相机在在不同的对比度的情况下还原低&#xff0c;中和高频率的能力&#xff0c;也可以显示全面综合的信息。…

Leetcode分隔链表

java 实现 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; }* }*/ class …

maui开发成生安卓apk,运行提示该应用与此设备的CPU不兼容

在生成.NET MAUI安卓应用时遇到“该应用与此设备的CPU不兼容”的问题&#xff0c;确保你的.NET MAUI应用支持的Android目标框架与设备CPU架构相匹配。例如&#xff0c;如果你的应用是为ARM64架构编译的&#xff0c;而你的设备是x86架构&#xff0c;就会出现不兼容的问题。 一、…

在 Unity 6 中使用APV为您的世界创建全局照明的新方法(一)

Unity 6 中推出的新照明功能让您能够更快速、更高效的完成对烘焙场景的照明工作&#xff0c;在本文中我们将与大家详细分享在 Unity 6 中应用自适应探针卷创建快速全局光照的更多细节与具体应用方法。由于内容比较丰富&#xff0c;我们将把内容分为三篇文章&#xff0c;以便大家…

深度学习之超分辨率算法——FRCNN

– 对之前SRCNN算法的改进 输出层采用转置卷积层放大尺寸&#xff0c;这样可以直接将低分辨率图片输入模型中&#xff0c;解决了输入尺度问题。改变特征维数&#xff0c;使用更小的卷积核和使用更多的映射层。卷积核更小&#xff0c;加入了更多的激活层。共享其中的映射层&…

VSCode 搭建Python编程环境 2024新版图文安装教程(Python环境搭建+VSCode安装+运行测试+背景图设置)

名人说&#xff1a;一点浩然气&#xff0c;千里快哉风。—— 苏轼《水调歌头》 创作者&#xff1a;Code_流苏(CSDN) 目录 一、Python环境安装二、VScode下载及安装三、VSCode配置Python环境四、运行测试五、背景图设置 很高兴你打开了这篇博客&#xff0c;更多详细的安装教程&…

使用Docker启用MySQL8.0.11

目录 一、Docker减小镜像大小的方式 1、基础镜像选择 2、减少镜像层数 3、清理无用文件和缓存 4、优化文件复制&#xff08;COPY和ADD指令&#xff09; 二、Docker镜像多阶段构建 1、什么是dockers镜像多阶段构建 1.1 概念介绍 1.2 构建过程和优势 2、怎样在Dockerfil…

Windows安全中心(病毒和威胁防护)的注册

文章目录 Windows安全中心&#xff08;病毒和威胁防护&#xff09;的注册1. 简介2. WSC注册初探3. WSC注册原理分析4. 关于AMPPL5. 参考 Windows安全中心&#xff08;病毒和威胁防护&#xff09;的注册 本文我们来分析一下Windows安全中心&#xff08;Windows Security Center…

Hive其一,简介、体系结构和内嵌模式、本地模式的安装

目录 一、Hive简介 二、体系结构 三、安装 1、内嵌模式 2、测试内嵌模式 3、本地模式--最常使用的模式 一、Hive简介 Hive 是一个框架&#xff0c;可以通过编写sql的方式&#xff0c;自动的编译为MR任务的一个工具。 在这个世界上&#xff0c;会写SQL的人远远大于会写ja…

时空AI赋能低空智能科技创新

随着人工智能技术的不断进步&#xff0c;时空人工智能&#xff08;Spatio-Temporal AI&#xff0c;简称时空AI&#xff09;正在逐渐成为推动低空经济发展的新引擎。时空AI结合了地理空间智能、城市空间智能和时空大数据智能&#xff0c;为低空智能科技创新提供了强大的数据支持…

java 通过jdbc连接sql2000方法

1、java通过jdbc连接sql2000 需要到三个jar包&#xff1a;msbase.jar mssqlserver.jar msutil.jar 下载地址&#xff1a;https://download.csdn.net/download/sunfor/90145580 2、将三个jar包解压到程序中的LIB下&#xff1a; 导入方法&#xff1a; ①在当前目录下&#xff…