指针专题(3)

1.前言

本节我们书接上文,继续进行指针专题的学习,夯实指针的基础,那么废话不多说,我们正式进入今天的学习

2.字符指针变量

我们知道,字符指针的形式为char*,我们可以取出一个字符的地址,并且将它存入到字符指针变量里面去。我们可以通过字符指针变量来打印或者修改字符

int main(void)
{
	char ch = 'w';
	char* pc = &ch;
	printf("%c\n", *pc);
	*pc = 'q';
	printf("%c\n", *pc);
	return 0;
}

这种情况是一般的使用情况,起始字符指针变量还有另外一种使用方式:

当我们以这种形式写代码的时候,并不是表示把字符串--hello world放入指针变量p中。在这种情况下我们可以把hello world理解为一个字符数组,这种写法相当于把这个字符数组的首字符的地址传给了p

	char* p = "hello world!";
	//等价于:
	//char arr[] = "hello world";
	//char* p = arr;

我们可以来验证一下,验证代码如下:

int main(void)
{
	char* p = "hello world!";
	printf("%c\n", *p);
	return 0;
}

但是这种特殊的写法只能等价于写成字符数组的形式,并不是真的就是字符数组的形式,它们之间存在着差异:

我们知道字符数里面的元素组是可以被改变的,而我们这种写法下,hello world是一个常量字符串,常量字符串是不能被修改的

因为常量字符串是不能被修改的,我们可以在字符指针变量前面加上一个const来修饰,保证代码的严谨性

虽然常量字符串不能被修改,但是我们还是可以正常的访问常量字符串里面的内容的:

我们有三种方式来打印常量字符串:

int main(void)
{
	const char* p = "hello world!";
	//第一种
	printf("%s\n", p);
	//第二种
	printf("%s\n", "hello world!");
	return 0;
}

第二种方法中其实使用的不是字符串本身,也和第一种方法一样,使用的是首元素h的地址

还有一个较为复杂的方法,我们可以通过遍历来打印常量字符串:

int main(void)
{
	const char* p = "hello world!";
	//第一种
	printf("%s\n", p);
	//第二种
	printf("%s\n", "hello world!");
	//第三种
	int len = strlen(p);
	for (int i = 0; i < len; i++)
	{
		printf("%c", *(p + i));
	}
	return 0;
}

笔试题

(此题目收录于《剑指offer》)

int main()
{
	char str1[] = "hello world.";
	char str2[] = "hello world.";
	const char* str3 = "hello world.";
	const char* str4 = "hello world.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");
	return 0;
}

这个题目为什么会产生这样的结果呢?我们来分析一下:

我们先分析一下前两句代码,这两句代码的意思是:创建两个字符数组str1和str2,里面都存入hello world. ,因为这是两个不同的数组,它们在内存之中存在于不同的空间,它们之间仅仅是字符数组里面的内容相同,因为数组名是首元素的地址,str1和str2的首元素并不在同一块内存空间中,所以它们两个不相同

我们再分析一下后面的两句代码,后面的hello world. 是一个常量字符串,因为常量字符串是不能被修改的,所以内容相同的常量字符串只会保存一份,不会额外的开辟空间存储两个一样的常量字符串,所以后面的两个字符指针变量都是指向同一个常量字符串hello world. ,两个变量的首地址相同

3.数组指针变量

我们前面学习了指针数组,它的里面既含有指针又含有数组,但是它的本质是一个存放指针的数组

而现在我们要学习的是数组指针变量,它的里面也同时含有数组和指针,而它的本质又是什么呢?我们再次运用类比的方法探究一下:

我们之前学过:

1.字符指针 char*p  它是一个指向字符的指针,存放的是字符的地址

	char ch = 'w';
	char* pc = &ch;

2.整型指针 int*p     它是一个指向整型的指针,存放的是整型的地址

	int n = 0;
	int* p = &n;

所以数组指针通过类比可以知道:数组指针是指向数组的指针,存放的是数组的地址

所以说数组指针是一种存放数组地址的指针变量

那么我们该怎么定义数组指针变量呢?

因为数组指针本质上是一个指针,我们参考之前的整型指针和字符指针,所以我们需要用*parr来表示它指针的属性,因为数组里面有多个元素,我们要在其后面加上[n]表示它元素的个数,但是由于[ ]的优先级大于*,所以我们要加上( )来保证parr先与*结合,不加( )就是一个指针数组

int main(void)
{
	char ch = 'w';
	char* pc = &ch;

	int n = 0;
	int* p = &n;

	int arr[10] = { 0 };
	int(*parr)[10] = &arr;
	return 0;
}

该定义模式就表示:p是数组指针,p指向的是一个数组,数组里面有10个元素,每个元素的类型都是int

数组指针变量是用来存放数组地址的,那么我们该怎么获得数组的地址呢?我们需要使用之前学习的 &+数组名

数组指针变量的使用:

int main(void)
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", (*p)[i]);
	}
	return 0;
}

这种方式下解引用拿到的是整个数组的地址

4.二维数组传参的本质

刚才我们学习了数组指针变量,但是在一维数组中数组指针变量的使用通常都比较牵强,使用会多此一举,我们通常会在二维数组中使用数组指针变量

我们有了对数组指针变量的理解,我们就能更好地探究二维数组传参的本质了

我们先举一个例子,假设我们要对二维数组里面的元素进行打印,我们之前通常会这么写:

void test(int arr[3][5], 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(void)
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7 } };
	test(arr, 3, 5);
	return 0;
}

我们通过之前的学习知道了一维数组的数组名是数组首元素的地址,那么我们来想一下二维数组的数组名是什么呢?

二维数组的数组名是二维数组中的第一行的地址,二维数组的每一行都是一个元素;

二维数组的每一行都是一个一维数组;

 所以此时我们若是要对二维数组里面的元素进行打印,就可以使用数组指针变量:

void test(int(*p)[5], 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(void)
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7 } };
	test(arr, 3, 5);
	return 0;
}

5.函数指针变量

要想理解函数指针,我们同样的使用类比的思路来帮助我们理解

之前我们学习过:

1.字符指针--指向的是字符类型,用于存放字符的地址

2.整型指针--指向的是整型类型,用于存放整型的地址

3.数组指针--指向的是数组,       用于存放数组的地址

所以我们可以推导出:

函数指针指向的是函数,用于存放函数的地址

因为函数指针存放的是函数的地址,说明函数也是有地址的,我们可以先来观察一下函数的地址:

Add(int a, int b)
{
	return a + b;
}
int main(void)
{
	printf("%p\n", &Add);
	printf("%p\n", Add);
	return 0;
}

通过上述的代码我们可以知道,在函数指针里面& + 函数名和函数名都表示的是函数的地址,函数指针里面没有首元素的概念

函数指针变量的写法和数组指针变量的写法很相似,函数指针的写法为Type(*pf)(Type, Type....)

我们来举例说明:

Add(int a, int b)
{
	return a + b;
}
int main(void)
{
	int arr[8] = { 0 };
	int(*pf)(int, int) = &Add;
	return 0;
}

我们需要和数组指针一样考虑优先级问题

了解了这些以后,我们可能会产生疑问:函数指针具体要怎么使用呢?

函数指针的使用非常简单,直接用一串代码就可以说明:

Add(int a, int b)
{
	return a + b;
}
int main(void)
{
	int arr[8] = { 0 };
	int(*pf)(int, int) = &Add;
	int r = (*pf)(3, 7);
	printf("%d\n", r);
	return 0;
}

	int r = (pf)(3, 7);

在运用函数指针的时候可以不加解引用符号,也可以加多个解引用符号,解引用符号的多少都不会对其产生影响

此时我们可能又会产生疑问:我们觉得函数指针好像并没有什么用处,有点多此一举,不如直接用函数名来调用

函数指针在使用上确实不存在太多的意义,函数指针的存在仅仅是让程序有着更加多元的写法

深入理解函数指针

我们来看两串代码(两串代码均出自《C陷阱和缺陷》):

1:

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

我们来逐步分析这串代码:

首先,我们不难看出 void (*)( ) 是一个函数指针类型;

我们接着往后看,(函数指针类型)0可以理解为:将0强制类型转换为 void (*)( ) 形式的函数指针类型的地址,此时0自身表示的也是地址,此时代码的意思是在地址为0的地方放着一个返回类型为void,没有参数的一个函数。因为是函数指针,所以前面的*号不会产生任何影响

我们再接着往后看(0强制转换后的函数指针)( ),这串代码表示的是一次函数调用,而且没有往0地址处的函数传入参数

我们通过把代码拆开,实现了对代码理解,所以我们在遇到复杂的函数的时候可以尝试把它拆开来理解

2.

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

因为该代码里面的括号比较多,我们可以试着将括号配对,并且拆开来理解:

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

1.我们可以知道signal是一个名称,可能是一个函数的名称,也可能是变量的名称。我们通过对题目的分析可以知道:signal是一个函数的名字,它的第一个参数类型是int,它的第二个参数的类型是函数指针类型 void(*)(int)

2.我们令signal   (   int , void(*)   (int)部分为M,此时我们定义的M是一个函数,现在函数的名称和参数都有了,只差一个返回类型,我们把它带回到原代码中可以得到:void(*M)(int) 这里我们不难看出这是一个返回类型

所以说该代码是一个函数声明,声明的函数叫做signal,signal函数有两个参数;其中第一个参数的类型为int,第二个参数的类型是void(*)(int)的函数指针类型,该指针可以指向一个函数,指向的函数参数是int,返回类型是void;signal函数的返回类型是void(*)(int)的函数指针,该指针可以指向一个函数,指向的函数参数是int,返回类型是void

我们可以把这串代码理解为 void (*)(int) signal(int,void(*)(int)) 但是我们不能这么去书写,只能这样去理解

虽然不能用上述方法去书写,但是我们可以用typedef来进行简化,可以帮助我们理解

6.typedef关键字

typedef 是用来类型重命名的,可以将复杂的类型,简单化

例如:如果觉得unsigned int 写起来很麻烦,不够方便,写成 uint 就方便多了,那么我们可以使用typedef来将类型重命名

typedef unsigned int uint;

我们也可以使用#define PTR_T int* ,这种写法不需要加分号,两种方法都能在一定程度上实现类型重命名的功能,#define方法是替换,typedef是直接改变了类型的名字,二者在本质上存在区别,下面来举一个例子:

typedef int* ptr_t;
#define PTR_T int*
int main(void)
{
	ptr_t p1, p2;//p1和p2都是整型指针
	PTR_T p3, p4;//被替换成int* p3, p4,此时p3是整型指针,p4旁边没有*,此时p4是整型
	return 0;
}

代码ptr_t p1, p2中,p1和p2都是整型指针

而代码PTR_T p3, p4中,因为#define起的是的作用,原代码被替换成int* p3, p4,此时p3旁边加上了*,表示的是整型指针,因为p4旁边没有*,所以p4仅仅是整型

此时我们可能会想:typedef能不能对指针重命名呢?答案是肯定的

如果我们想要把int*重新命名为ptr_t,我们就可以这样书写:

 typedef int* ptr_t;

但是使用typedef重新命名数组指针和函数指针类型会与其他的稍微有点区别

int main(void)
{
	int arr[5] = { 0 };
	int(*p)[5] = &arr;//p是数组指针变量
	return 0;
}

像上面的代码,我们知道上面数组指针变量的类型为 int(*p)[5],这是一种数组指针类型,如果我们要把这个类型重新命名就需要使用不同的方式:

typedef int(*parr_t)[5];
int main(void)
{
	int arr[5] = { 0 };
	int(*p)[5] = &arr;//p是数组指针变量
	return 0;
}

我们此时就不能像之前一样把名称放到最后面,而是需要放到中间,这是一种语法规则

函数指针类型的重命名也是⼀样的,例如:我们要将 void(*)(int) 类型重命名为 pf_t ,就需要这样写:

typedef void(*pfun_t)(int);

新的类型名必须在*的右边

知道了typedef的用法此时我们就可以对前面的第二道题进行优化:

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

这么看,新的代码就比之前的代码更加容易理解一些

7.函数指针数组

函数指针数组应该如何定义呢?

我们还是来使用类比的方法:

我们之前学习过:

1.整型数组--整型数组是存放整型的数组

2.字符数组--字符数组是存放字符的数组

3.指针数组--指针数组是存放指针的数组

4.字符指针数组: char* arr1[10]

5.整型指针数组: int* arr2[10]

所以我们此时就可以知道函数指针数组就是把函数的地址存到⼀个数组中

假设我们要完成加减乘除的操作,同时把加减乘除定义为四个函数,此时我们就可以这么写:

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(void)
{
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pf3)(int, int) = Mul;
	int (*pf4)(int, int) = Div;
	return 0;
}

我们发现者四个函数函数指针变量在去掉名字以后,类型是一模一样的

我们结合之前所学习过的数组,我们可以发现,数组里面的元素类型也是一模一样的,所以我们可以创建一个数组,并且在数组里面存入函数指针; 

如果要把多个相同类型的函数指针存放在一个数组中,那么这个数组就是函数指针数组;

函数指针数组的定义方式为:Type(*pfArr[4])(int, int) ={函数, 函数,函数.... }

所以我们此时就可以修改原代码:

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(void)
{
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pf3)(int, int) = Mul;
	int (*pf4)(int, int) = Div;
	int (*pfArr[4])(int, int) = { Add,Sub,Mul,Div };
	return 0;
}

既然我们知道了怎么定义函数指针数组,那么函数指针数组该怎么使用呢?

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(void)
{
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pf3)(int, int) = Mul;
	int (*pf4)(int, int) = Div;
	int (*pfArr[4])(int, int) = { Add,Sub,Mul,Div };
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = pfArr[i](8, 4);
		printf("%d\n", ret);
	}
	return 0;
}

此时我们可能会感觉函数指针数组好像并没有什么用,其实函数指针数组是有用的,下面我们来举一个例子:例如:我们想要写一个代码,完成计算器的功能,那么我们就可以用到函数指针数组:

如果我们没有学习过函数指针数组吗,我们可能会这样写计算器的代码:

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;
}

void menu()
{
	printf("******************************\n");
	printf("******  1.Add    2.Sub  ******\n");
	printf("******  3.Mul    4.Div  ******\n");
	printf("*********   0.exit   *********\n");
	printf("******************************\n");


}
int main(void)
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("结果是:%d\n", ret);
			break;
		case 2:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("结果是:%d\n", ret);
			break;
		case 3:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("结果是:%d\n", ret);
			break;
		case 4:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("结果是:%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

当我们这样写代码的时候,我们发现代码有很多重复的地方,显得有点冗余。而且如果我们需要扩展计算器的功能,代码量也会大量增加;

此时我们就可以使用函数指针数组来优化和简化代码:

我们先来创建一个函数指针数组:

	int (*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };

我们可以在四个函数的前面加上一个NULL,这样就可以让下标对应,使用起来就会很方便

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;
}

void menu()
{
	printf("******************************\n");
	printf("******  1.Add    2.Sub  ******\n");
	printf("******  3.Mul    4.Div  ******\n");
	printf("*********   0.exit   *********\n");
	printf("******************************\n");


}
int main(void)
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	//创建函数指针数组
	int (*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };

	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("结果是:%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else
		{
			printf("选择错误,重新选择\n");
		}
	} while (input);
	return 0;
}

通过这样的优化,代码就会简洁很多;

这样的函数指针数组称为转移表

结尾

本节我们深入学习了指针的相关知识,通过理解和消化我们指针的基础变得更强了,我们也可以更好的运用指针的相关知识了,那么本节的内容也到此结束了,我们下一节还是学习指针的相关内容,谢谢您的浏览

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

游戏测试之常见控制技能(下)

备注&#xff1a;未经博主允许禁止转载 个人笔记&#xff08;整理不易&#xff0c;有帮助&#xff0c;收藏点赞评论&#xff0c;爱你们&#xff01;&#xff01;&#xff01;你的支持是我写作的动力&#xff09; 笔记目录&#xff1a;学习笔记目录_pytest和unittest、airtest_w…

4月21敲一篇猜数字游戏,封装函数,void,无限循环,快去体验体验

今天敲一篇猜数字游戏 目录 今天敲一篇猜数字游戏 1.打开先学goto语句&#xff1a; 2.开干&#xff1a; 首次我们学习随机数&#xff1a; 讲解一下&#xff1a; 改用srand; 加入时间变量&#xff1a; 获取时间&#xff1a;哈​编辑 3.我本来想已近够完美了&#xff0…

Redis 逻辑过期策略设计思路

引言&#xff1a; 当我们平常使用Redis缓存的时候&#xff0c;会出现一种场景&#xff0c; redis的key到过期时间了&#xff0c;总是需要到数据库里面去查一遍数据再set回redis&#xff0c;这个时候如果数据库响应比较慢&#xff0c;那么就会造成用户等待&#xff0c;如果刚好…

rancher-rke2 修改--service-cluster-ip-range

一、场景 因为需要部署新版本的ingress-nginx&#xff0c;而部署ingress-nginx的时候需要使用hostnetowrk以及nodeport的端口为80和443&#xff0c;service-node-port-range 默认为30000开始,部署会报错。 二、产生修改的需求 1、api-servier的配置文件位置 默认是没有的&…

【C++】双指针算法:移动零

学完了数据结构和C的STL库&#xff0c;我们需要开始学习算法了。有了前面的基础知识储备&#xff0c;再好好学习算法&#xff0c;有系统&#xff0c;有规律的刷题&#xff0c;总结&#xff0c;咱们的编程能力就会有质的飞跃&#xff01; 1.题目 我们用一个例题来讲解这个算法。…

Docker - 简介

原文地址&#xff0c;使用效果更佳&#xff01; Docker - 简介 | CoderMast编程桅杆https://www.codermast.com/dev-tools/docker/docker-introduce.html Docker是什么&#xff1f; Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从 Apache2.0 协议开源。 D…

AtCoder Beginner Contest 340

前面两道阅读理解直接跳过 C - Divide and Divide 大意 黑板上有一个数。 执行下列操作&#xff0c;直到黑板上的数全为1: 选择一个不小于2的整数&#xff0c;擦掉。写下和。需要的代价。 当不能继续操作时&#xff0c;总代价是多少&#xff1f; 思路 定义表示黑板上初…

nacos配置mysql(windows)

nacos默认是使用的内置数据库derby ,可通过配置修改成mysql,修改成mysql之后&#xff0c;之前配置在derby的数据会丢失 本文使用mysql版本为8.0.22 nacos版本为2.3.1 在mysql里面先创建一个数据库test(名称自定义&#xff0c;和后面配置文件里面的一样就好了) 在上面创建的数据…

6.SpringBoot 日志文件

文章目录 1.日志概述2.日志作用3.使用和观察日志3.1如何观察日志3.2使用日志3.3日志级别3.4日志持久化3.5日志分割 4.日志框架4.1门面模式(外观模式)4.2 SLF4J框架介绍4.3 日志格式的说明4.3.1日志名称 5.日志颜色设置6.总结 大家好&#xff0c;我是晓星航。今天为大家带来的是…

C# 开源SDK 工业相机库 调用海康相机 大恒相机

C# MG.CamCtrl 工业相机库 介绍一、使用案例二、使用介绍1、工厂模式创建实例2、枚举设备&#xff0c;初始化3、启动相机4、取图5、注销相机 三、接口1、相机操作2、启动方式3、取图4、设置/获取参数 介绍 c# 相机库&#xff0c;含海康、大恒品牌2D相机的常用功能。 底层采用回…

去除图像周围的0像素,调整大小

在做分割任务时&#xff0c;经常需要处理图像&#xff0c;如果图像周围有一圈0像素&#xff0c;需要去除掉&#xff0c;重新调整大小 数组的处理 如果图像的最外一圈为0&#xff0c;我们将图像最外圈的图像0去除掉。 import numpy as npdef remove_outer_zeros(arr):# 获取数…

电脑缺失d3dcompiler_43.dll如何修复?多种修复dll问题的有效方法分享

当用户尝试在个人计算机上运行特定的软件游戏时&#xff0c;系统弹出了一条错误提示信息&#xff0c;明确指出“d3dcompiler_43.dll”文件缺失。这个动态链接库文件(dll)是Direct3D编译器的重要组成部分&#xff0c;对于许多基于Windows操作系统的应用程序&#xff0c;尤其是那…

数据库mysql提权四种烧姿势--UDF反弹启动项MOF

免责声明:本问仅做技术交流与学习,请知法守法,不要乱搞等等 目录 前提条件 如何获取最高权限的密码? 一.UDF提权 利用条件: 信息收集 1-看有无plugin目录 2-开启外链 3-开启外连后,MSF启动~ 4-navicat--利用导出的.dll执行命令 利用原理: 执行命令: 二.反弹提权 …

B2024 输出浮点数 洛谷题单

首选需要进行了解的就是%a.bf所代表的含义就行了&#xff0c;直接莽了&#xff0c;没啥解释的笑脸&#x1f644; 在 Python 中&#xff0c;%a.bf 中的参数 a 和 b 是用来格式化浮点数的输出的&#xff0c;具体含义如下&#xff1a; a 表示总输出宽度&#xff0c;包括小数点、…

Pytorch入门实战: 06-VGG-16算法-Pytorch实现人脸识别

第P6周&#xff1a;VGG-16算法-Pytorch实现人脸识别 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊 &#x1f3e1; 我的环境&#xff1a; 语言环境&#xff1a;Python3.8 编译器&#xff1a;Jupyter La…

​​​​​​​iOS配置隐私清单文件App Privacy Configuration

推送到TestFlight后邮件收到警告信息如下&#xff0c;主要关于新的隐私政策需要补充&#xff1a; Hello, We noticed one or more issues with a recent submission for TestFlight review for the following app: AABBCC Version 10.10.10 Build 10 Although submission for …

堆的概念、堆的向下调整算法、堆的向上调整算法、堆的基本功能实现

目录 堆的介绍 堆的概念 堆的性质 堆的结构 堆的向下调整算法 基本思想&#xff08;以建小堆为例&#xff09; 代码 堆的向上调整算法 基本思想&#xff08;以建小堆为例&#xff09; 代码 堆功能的实现 堆的初始化 HeapInit 销毁堆 HeapDestroy 打印堆 HeapPrint …

Linux配置腾讯云yum源(保姆级教学)

1. 备份原有的 yum 源配置文件 例如&#xff1a; mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2. 下载腾讯云的 yum 源配置文件 例如&#xff1a; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/…

28.组件事件配合v-model使用

组件事件配合v-model使用 如果是用户输入&#xff0c;我们希望在获取数据的同时发送数据配合v-model来使用 <template><div><h3>ComponentA</h3><ComponentB some-event"getHandle" /><p>ComponentA接受的数据&#xff1a;{{ m…

【Linux文件系统开发】认知篇

【Linux文件系统开发】认知篇 文章目录 【Linux文件系统开发】认知篇一、文件系统的概念二、文件系统的种类&#xff08;文件管理系统的方法&#xff09;三、分区四、文件系统目录结构五、虚拟文件系统&#xff08;Virtual File System&#xff09;1.概念2.原因3.作用4.总结 一…