C语言——指针(进阶详解)

news2024/9/21 2:46:40

文章目录

  • 指针概念的回顾
  • 1.字符指针
    • 1.1字符指针练习题
  • 2.指针数组
  • 3.数组指针
    • 3.1数组指针的定义
    • 3.2 &数组名和数组名的区别
    • 3.3数组指针的使用
    • 3.4一组简单的练习题
  • 4.数组和指针作为函数参数
    • 4.1一维数组传参
      • **总结**
    • 4.2二维数组传参
      • **总结**
    • 4.3一级指针传参
      • **总结**
    • 4.4二级指针传参
      • **总结**
  • 5.函数指针
    • 5.1什么是函数指针?
    • 5.2函数指针变量的定义和使用
    • 两个经典的函数指针代码问题
  • 6.函数指针数组
    • 6.1函数指针数组的声明
    • 6.2函数指针数组的使用
  • 7.指向函数指针数组的指针
  • 8.回调函数
    • 8.1qosrt函数的介绍
    • 8.2通过冒泡排序思想模拟实现qsort函数功能

指针概念的回顾

1、通常口头语中的指针其实是变量,即指针变量,用来存放地址。地址唯一标识一块内存空间。
2、指针的大小是固定的,在32位平台下是4个字节,在64位平台下是8个字节。
3、指针是有具体类型的,指针的类型决定了指针±整数的步长,也决定了指针解引用操作的访问权限。

1.字符指针

字符指针存放的是一个字符的地址。一个字符类型的变量占一个字节,但是字符指针是占4个字节空间的。在前面指针入门文章中,已经介绍了字符指针的概念,这里主要研究的是字符指针存放字符串首字符地址的问题。

#include<stdio.h>

int main()
{
	const char* str2 = "hello world";

	printf("%s\n",str2);
	
	return 0;
}

在这里插入图片描述

有些不了解指针的人会很好奇,为什么一个4个字节大小的指针变量却能存下12个字节大小的的字符串呢?
这是因为这里的str2字符指针变量其实保存的是字符串首字符 ‘h’ 的地址。
在这里插入图片描述

1.1字符指针练习题

该代码的输出结果是什么呢?

#include <stdio.h>
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存放的内容一致,但是str1数组 和 str2数组在内存中分别开辟了各自的空间。故str2 != str1 所以第一个打印的内容为 “str1 and str2 are not same”。两个const修饰的字符指针str3 和 str4各自指向了常量字符串的首字符 ‘h’ 的地址。所以str3 == str4。第二个打印的内容为 “str3 and str4 are same”。
在这里插入图片描述

字符指针指向的常量字符串要用const进行修饰。因为常量字符串是不可以被修改,如果不用const进行修饰,可能会导致对字符指针解引用操作后,访问权限冲突导致程序崩溃。

2.指针数组

在指针入门文中,已经介绍了指针数组的概念。这里也为下文介绍的数组指针做一下铺垫。

int* arr[5]; //一级指针数组
char** ch[5];//二级指针数组

指针数组本质是存放指针变量的数组。

3.数组指针

3.1数组指针的定义

int* arr[10]; //这是一个指针数组
int(*pi)[10]; //这是一个数组指针
//这里*和pi先结合,表示pi是一个指针变量。
//然后与int [10]结合表示该指针变量指向一个元素个数为10的整型数组。
//所以pi是一个数组指针。

3.2 &数组名和数组名的区别

#include<stdio.h>

int main()
{
	int arr[10];
	printf("%p\n",arr);
	printf("%p\n",&arr);
	return 0;
}

在这里插入图片描述
通过打印结果可以看到,两个结果是一样的,但是它们真的一样吗?

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

在这里插入图片描述
数组名arr,没有放在sizeof内部和&数组名中,表示的是数组首元素的地址,即为int类型的指针变量。+1跳过4个字节。&arr表示的是整个数组,即为int()[10]类型。+1跳过整个数组,数组的大小是40个字节。
虽然,以%p的格式打印arr和&arr的结果是一样的。但是,这不代表它们的类型是一样的。

3.3数组指针的使用

通过上边的介绍可以知道,数组指针就是指向数组的指针变量,数组指针存放的是数组的地址。下面就通过代码简单演示一下数组指针的使用。

#include <stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int(*pa)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
	//但是我们一般很少这样写代码
	return 0;
}

下面举一个数组指针作为函数参数的使用场景

#include <stdio.h>

void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
}

void print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (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,10 };
	print_arr1(arr, 3, 5);
	//数组名arr,表示首元素的地址
	//但是二维数组的首元素是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
	//可以数组指针来接收
	print_arr2(arr, 3, 5);
	return 0;
}

在这里插入图片描述

3.4一组简单的练习题

下面代码的类型分别是什么呢?
int arr[5];
int* parr1[10];
int(*parr2)[10];
int(*parr3[10])[5];

int main()
{
	int arr[5];//整型数组
	//类型是int[10];
	//每个元素的类型是int
	int* parr1[10];//指针数组	
	//类型是int*[10];
	//每个元素的类型是int*
	int(*parr2)[10];//数组指针
	//类型是	int(*)[10];
	int(*parr3[10])[5];//存放数组指针的数组
	//类型是int(*[10])[5];
	//每个元素的类型是int(*)[5];
	return 0;
}

4.数组和指针作为函数参数

当书写C语言代码时,难免需要使用函数,设计函数时我们要如何书写呢?下面我会通过一些代码的案例来对此进行分析。

4.1一维数组传参

void test1(int arr[])//OK,可以参考分析(1)
{
	;
}
void test1(int arr[5])//OK,可以参考分析(2)
{
	;
}
void test1(int* arr)//OK,可以参考分析(3)
{
	;
}

void test2(int *Parr[10])//OK,可以参考分析(4)
{
	;
}
void test2(int **Parr)//OK,可以参考分析(5)
{
	;
}

in t main()
{
	int arr[5] = {0};
	int* Parr[10] = {0};
	test1(arr);//调用函数
	test2(Parr);//调用函数
	return 0;
}

此刻,让我来带你分析一个个的函数参数设计案例来逐步分析一维数组传参的知识点。
首先,主函数内已经定义两个一维数组,分别是int [ ] 类型的整型数组arr,和int* [ ] 类型的整型指针数组Parr。它们分别调用了test1和test2函数。

分析(1): 从test1函数的参数设计来进行分析,int [5] 类型数组传参,在函数设计的参数部分使用 int [ ]来接收参数是OK的。因为,int arr [5]的类型就是 int [5],而C语言中一维数组的不完全初始化创建可以省略元素个数的。所以第一个test1函数参数部分,设计成 int arr[ ] 是没有问题的。
分析(2):从上述分析已知,调用函数时,传参的数据类型为int [5],在函数的设计时,将参数设计成int arr[5]是OK的。
分析(3):在C语言中,使用数组名有两个特殊情况,情况一,当数组名单独放在sizeof()内部时,此时的数组名表示整个数组。情况二,对数组名进行取地址操作时,取出来整个数组的地址,此时的数组名表示整个数组的地址。除去这两种特殊情况外,其余情况下数组名表示首元素的地址,int类型数据的地址理应使用int* 类型的指针来存放,所以参数部分设计成int* arr是OK的
分析(4):同分析(2)所述,test2函数参数设计成int* Parr[10]是OK的。
分析(5):同分析(3)所述,此时的数组名表示首元素的地址,而一维指针数组的首元素地址的类型为 int** 的二级指针类型。所以,参数部分设计为int** Parr是OK的。

总结

一维数组传参,函数的参数可以是数组也可以是指针。需要注意的两个点,设计参数为一维数组时,可以不指定数组元素的个数。当接收的参数为一维指针数组的首元素地址时,参数部分应该设计成二级指针类型。

4.2二维数组传参

void test(int arr[3][5])//OK,可以参考分析(1)
{
	;
}
void test(int arr[][])//error,可以参考分析(2)
{
	;
}
void test(int arr[][5])//OK,可以参考分析(3)
{
	;
}

void test(int *arr)//error,可以参考分析(4)
{
	;
}
void test(int* arr[5])//error,可以参考分析(5)
{
	;
}
void test(int (*arr)[5])//OK,可以参考分析(6)
{
	;
}
void test(int **arr)//error,可以参考分析(7)
{
	;
}


int main()
{
 int arr[3][5] = {0};
 test(arr);
}

接下来让我们分析每个函数参数设计是否可以进行二维数组传参。

分析(1):二维数组传参,参数设计部分使用二位数组接收。这样是OK的
分析(2):二维数组传参,可以不指定第一个[ ] 的内容,但是不能省略第二个[ ] 的内容。因为不初始化列数,这样编译器不知道该开辟多少的内存空间。所以这样设计参数是错误的。
分析(3):二维数组传参,参数部分可以省略行数,即第一个[ ]内容,所以这样设计参数是OK的。
分析(4):二维数组传参,参数部分若要设计成指针来接参数,形参和实参的类型应当匹配。这里函数调用部分传递的参数是arr,即数组名。二维数组的数组名,如果没有放在sizeof()内部和&地址数组名,表示的是首元素的地址,也就是第一行的地址,类型为数组指针 int (*arr)[5] 类型的数组指针。在函数参数部分设计成一级指针是错误的
分析(5):同上述,函数的形式参数设计成指针数组,类型不匹配。所以是错误的。
分析(6):正确的,因为形参和实参的类型匹配,指针数组传参,指针数组接收。
分析(7):二级指针作为参数,和实际传递的参数数组指针不匹配,所以是错误的。

总结

二维数组传参,函数形参的设计只能省略第一个[]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。二维数组的数组名,除了单独放在sizeof内部和&数组名的情况下,都表示首元素的地址,即第一行的地址,类型为int(*)[](数组指针类型)。

4.3一级指针传参

#include<stdio.h>

void Print(int* pa, size_t sz)
{
	size_t i = 0;
	for(i = 0; i < sz; i++)
	{
		printf("%d ",*(pa+i));
	}
	printf("\n"); 
}

int main()
{
	int arr[] = {1,2,3,4,5,6};
	size_t sz = sizeof(arr) / sizeof(arr[0]);
	int* pa = arr;
	Print(arr,sz); //OK,可以参考分析(1)
	Print(pa,sz); //OK,可以参考分析(2)
	return 0;
}

在这里插入图片描述

分析(1):当一维数组的数组名没有单独放在sizeof()内部和&数组名时,数组名表示首元素的地址,int类型的地址,即int类型的指针。所以当函数参数为int 类型的参数时,传一维整型数组的数组名是OK的。
分析(2):将一维数组的数组名存放在整型指针变量pa中,调用Print()函数时,传入pa作为第一个参数是OK的。

总结

当函数的参数设计成一级指针变量时(假设为int* 类型的整型一级指针变量),使用者调用该函数,可以传入一级整型指针变量、一维整型数组的数组名,以及整型变量的地址。

4.4二级指针传参

我简单的描述一下二级指针的概念:二级指针即,数据类型** 变量名称。例如这里我定义一个字符型二级指针变量ppc,可以写作char* * ppc;

#include <stdio.h>

void test(int** ptr)
{
 printf("num = %d\n", **ptr); 
}
int main()
{
 int n = 6;
 int*p = &n;
 int **pp = &p;
 
 test(pp);//OK,可以参考分析(1)

 test(&p);//OK,可以参考分析(2)
 
 return 0;
}

在这里插入图片描述

分析(1):当函数的参数部分设计成一个二级指针变量时,直接传入一个二级指针变量是OK的。
分析(2):当函数的参数部分设计成一个二级指针变量时,直接传入一个一级指针变量的地址也是OK的。

总结

当函数的参数设计成二级指针变量时(假设为char* 类型的字符型二级指针变量),使用者调用该函数,可以传入二级字符型指针变量、一维字符型指针数组的数组名,以及一级字符指针型变量的地址。

5.函数指针

5.1什么是函数指针?

函数指针的概念:在C语言中如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针

请看下面代码

#include<stdio.h>

void test()
{
	printf("hello world\n");
}

int main()
{
	printf("%p/n",test);
	printf("%p/n",&test);
	return 0;
}

在这里插入图片描述
通过上述代码运行结果可知,定义C语言中的函数确实会有在内存中开辟一块空间用于存放函数的地址。函数名是等价于&函数名的。

5.2函数指针变量的定义和使用

函数指针变量的定义:函数的返回类型 (*函数名)(函数参数)

#include<stdio.h>

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

int main()
{
	int (*pa)(int, int) = Add;//也可以写成:int (*pa)(int, int) = &Add

	int ret = pa(2, 3);
	//int ret = (*pa)(2, 3);这样写也没问题
	printf("%d\n", ret);
	return 0;
}

我简单分析下上面代码函数指针定义的那行代码,int (*pa)(int, int) = Add;,这里应该先从变量名入手进行分析,首先是(*pa),变量名pt旁边跟着 *表示pa是一个指针变量。紧接着是右边的(int, int),表示这是一个函数指针变量,且指向函数的参数为两个int类型变量。(*pa)的左边是int,表示该函数的返回类型为整型类型。最后是 = Add,表示该函数指针变量指向的函数为Add函数。

在这里插入图片描述

#include<stdio.h>

void test()
{
	printf("hello world\n");
}

int main()
{
	void (*pt)() = test;//定义函数指针变量pt,用于存放test函数的地址
	pt();//直接通过函数指针调用函数
	return 0;
}

在这里插入图片描述

我简单分析下上面代码函数指针定义的那行代码,void (*pt)() = test; ,这里应该先从变量名入手进行分析,首先是(*pt),变量名pt旁边跟着 *表示pt是一个指针变量。紧接着是右边的(),表示这是一个函数指针变量,且指向函数的无参数。(*pt)的左边是void,表示该函数的返回类型为无具体类型。最后是 = test,表示该函数指针变量指向的函数为test函数。

两个经典的函数指针代码问题

以下两端代码均出自《C陷阱和缺陷》。

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

代码1解读:先从(void ()())0)这里开始解读,这里是将0地址强制类型转换成void()()的指针。那么剩下的()()可以解读成函数的一次调用。这段代码是一次函数调用,调用了0地址出的一个函数。
代码2解读:首先,从变量名signal入手。signal先和右边的(int, void(
)(int))结合,说明signal是一个函数。函数的参数分别是整型和返回类型为无具体类型,参数为整型的函数指针。然后再把这段解读后的代码从源代码移除,这么做方便后续的解读void (*)(int);,剩下的是一个函数指针,参数是(int),返回类型为无返回类型。也就是signal 函数的返回类型。

//这里我用typedef简化一下这段代码void (*signal(int , void(*)(int)))(int);
int main()
{
	typedef void(*pf_t)(int);
	//void (*signal(int , void(*)(int)))(int);
	pf_t signal(int,pf_t);	
	return 0;
}

注意:这里typedef重名指针类型时,重命名符号需要跟在*后

6.函数指针数组

函数指针数组:顾名思义就是存放函数指针的数组。

6.1函数指针数组的声明

函数返回类型 (*数组名[元素个数])(函数参数);
int main()
{
	int (*cacl[5])(int, int);//函数指针声明
	return 0
}

简单解读一下这段代码:int (cacl[5])(int, int),首先从数组名入手,cacl先和[ ]结合,所以这是一个数组。数组的每个元素类型为int()(int,int),即返回类型为int,参数为两个int的函数指针。

6.2函数指针数组的使用

函数指针数组的用途:转移表

1、两个整数运算的计算器(简单版)
#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;
}

void test()
{
	int input = 0;
	do
	{
		int x = 0;
		int y = 0;
		int ret = 0;
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch(input)
		{
			case 1:
				printf("请输入两个整数:>");
				scanf("%d %d",&x ,&y);
				ret = Add(x, y);
				printf("%d\n",ret);
				break;
			case 2:
				printf("请输入两个整数:>");
				scanf("%d %d", &x, &y);
				ret = Sub(x, y);
				printf("%d\n", ret);	
				break;
			case 3:
				printf("请输入两个整数:>");
				scanf("%d %d", &x, &y);
				ret = Mul(x, y);
				printf("结果为:%d\n", ret);	
				break;
			case 4:
				printf("请输入两个整数:>");
				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);
}

int main()
{
	test();
	return 0;
}

2、两个整数运算的计算器(函数指针数组版)

#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 ret = 0;
	int input = 1;
	int x = 0;
	int y = 0;
	int(*cacl[5])(int, int) = { 0,Add,Sub,Mul,Div };//转移表

	do
	{
		menu();
		scanf("%d", &input);
		if ((input < 5) && (input > 0))
		{
			printf("请输入两个整数\n");
			scanf("%d %d", &x, &y);
			ret = cacl[input](x, y);
			printf("结果为:%d\n",ret);
		}
		else if(input==0)
		{
			printf("退出成功!\n");
		}
		else
		{
			printf("输入错误,请重试!\n");
		}
	} while (input);

	return 0;
}

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

#include<stdio.h>
#inlcude<assert.h>

void PrintS(const char* str)
{
	assert(str);
	printf("%s\n",str);
}

int main()
{
	//函数指针pfun
	void (*pfun)(const char*) = PrintS;
	//函数指针的数组pfunArr
	void (*pfunArr[5])(const char* str);
	pfunArr[0] = PrintS;
	//指向函数指针数组pfunArr的指针ppfunArr
	void (*(*ppfunArr)[5])(const char*) = &pfunArr;
	return 0;
}

解读指向函数指针数组的指针的定义:void ((ppfunArr)[5])(const char) = &pfunArr;
首先,从(ppfunArr)入手,先和 * 结合说明是指针。然后,左边未解读代码如下void ([5])(const char
) 。说明这是一个函数指针数组类型。函数返回值为void,参数为(const char*)。数组元素个数为5个。最后,ppfunArr指针存放的是pfunArr函数指针数组的地址。

8.回调函数

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

下面我通过C标准库函数qsort来举例回调函数。

8.1qosrt函数的介绍

void qsort (void* base, size_t num, size_t size,int (*compar)(const void * e1,const void * e2));

首先,我们通过阅读文档来查看一下。
在这里插入图片描述

通过简单的阅读文档可以得知:qsort函数的返回类型为void,函数有共有四个参数,分别表示待排序数组(void * base,数组的元素个数(size_t num),数组的单个元素的大小,单位是字节(size_ t size),以及函数指针compar,返回类型是int ,参数分别是两个const修饰的无具体类型指针(const void * e1,const void e2)。C语言标准规定,使用者需要自己设计qosrt函数的第四个参数,当e1 > e2 是该函数返回一个大于0 的数,当e1 = e2时,函数返回0,当e1 < e2时,函数返回一个小于0的数。

void* 类型指针变量:void* 类型指针变量就是无具体类型指针变量,设计的初衷用来存放任意类型指针变量。void* 指针变量无法直接使用,因为void指针变量的步长未知,故解引用操作是非法的。使用void指针变量需要进行强制类型转换。

include<stdio.h>
#include<stdlib.h>

int CmpInt(const void* e1, const void* e2)//回调函数,用于排序整形数据
{
	return *((int*)e1) - *((int*)e2);
}

int main()
{
	int arr[] = { 1,3,5,7,9,2,4,6,8,10 };
	size_t sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;

	for (i = 0; i < (int)sz; i++)
	{
		printf("%d ", arr[i]);
	}

	printf("\n");

	qsort(arr, sz, sizeof(arr[0]), CmpInt);

	for (i = 0; i < (int)sz; i++)
	{
		printf("%d ",arr[i]);
	}
	printf("\n");

	return 0;
}

在这里插入图片描述

8.2通过冒泡排序思想模拟实现qsort函数功能

实现思路:当要排序任意类型数据排序时,首先要清楚,具体排序什么类型数据得看使用者的需求。所以函数默认接收的参数为void类型指针。但是要如何才能合理的使用void指针呢?这就不得不提字符指针重要性。因为,设计者也不清楚使用者会排序什么类型数据,如果把要比较的两个数据都强制转化成char指针。然后再一个一个字节进行比较,便可以实现任意类型数据的比较。在通过char*指针来进行交换两个元素的内容以达到排序的目的。

int CmpInt(const void* e1, const void* e2)
{
	return *((int*)e1) - *((int*)e2);
}


void Swap(char* c1, char* c2, size_t size)
{
	size_t i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *c1;
		*c1 = *c2;
		*c2 = tmp;
		c1++;
		c2++;
	}
}

void MySort(void* base, size_t num, size_t size,
	int(*cmp)(const void* e1, const void* e2))
{
	size_t i = 0;
	size_t j = 0;
	for (i = 0; i < num - 1; i++)
	{
		for (j = 0; j < num - i - 1; j++)
		{
			if (cmp((char*)base + (size * j), (char*)base + size * (j + 1)) > 0)
			{
				Swap((char*)base + (size * j), (char*)base + size * (j + 1), size);
			}
		}
	}

}

int main()
{
	int arr[] = { 2,4,6,8,10,1,3,5,7,9 };
	size_t sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;

	for (i = 0; i < (int)sz; i++)
	{
		printf("%d ", arr[i]);
	}

	printf("\n");

	MySort(arr, sz, sizeof(arr[0]), CmpInt);

	for (i = 0; i < (int)sz; i++)
	{
		printf("%d ",arr[i]);
	}
	printf("\n");

	return 0;
}

在这里插入图片描述

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

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

相关文章

Spring Boot RCE到内存马探索

前言 SpringBootVulExploit是Spring Boot漏洞Check list&#xff0c;但在真正的环境中进行漏洞利用还是有一段距离的&#xff0c;因此衍生出了SpringBootExploit工具。本文是对该Check list到内存马探索之路的记录。再此过程中学到了很多知识&#xff0c;收获了很多&#xff0…

线性双功能PEG羧酸Acetic Acid-PEG-Acetic Acid,AA-PEG-AA,羧酸PEG羧酸

产品名称&#xff1a; 1、英文&#xff1a;Acetic Acid-PEG-Acetic Acid AA-PEG-AA 2、中文&#xff1a;羧酸-聚乙二醇-羧酸 产品介绍&#xff1a; AA-PEG-AA是一种线性双功能PEG羧酸试剂。PEG和COOH基团之间存在亚甲基&#xff08;CH2&#xff09;键。AA-PEG-AA也称为CM-P…

测试平台系列——编写oss管理页面

上一节我们编写好了oss相关的crud接口&#xff0c;那这一节我们就得为oss数据的管理编写一个新的页面了。 即将做的是一个极度精简的文件管理页面。 效果图 因为我每次都是写完一段代码&#xff0c;然后编写对应教程&#xff0c;所以效果图这种东西自然是不在话下: 图片可以…

更改SAP GUI登录界面信息

在SAP GUI的登录界面&#xff0c;左部输入登录信息如客户端、用户名、密码等&#xff0c;右部空余部分可维护一些登录信息文本&#xff0c;如登录的产品、客户端说明及注意事项等&#xff0c;此项操作详见SAP Notes 205487 – Own text on SAPGui logon screen 维护文档使用的…

从春节后央行的首批罚单,看金融反欺诈反洗钱的复杂性

目录 个人信息保护的问题 征信管理的问题 反洗钱与反欺诈的问题 金融欺诈愈加复杂多变 金融机构如何增强反欺诈反洗钱 春节后&#xff0c;央行公示首批罚单。其中&#xff0c;厦门银行被中国人民银行福州中心支行给予警告&#xff0c;并没收违法所得767.17元&#xff0c;处…

30个HTML+CSS前端开发案例(五)

30个HTMLCSS前端开发案例&#xff08;21-25&#xff09;本人说明全屏加载动画效果代码实现效果吃豆豆动画效果代码实现效果鼠标悬停3D翻转效果代码实现效果3D旋转木马效果代码实现效果flex弹性布局-酷狗音乐播放列表代码实现效果资源包本人说明 本专栏为记录博主的毕业设计而开…

论文阅读 | Restormer: Efficient Transformer for High-Resolution Image Restoration

前言&#xff1a;CVPR2022oral 用transformer应用到low-level任务 Restormer: Efficient Transformer for High-Resolution Image Restoration 引言 low-level task 如deblurring\denoising\dehazing等任务多是基于CNN做的&#xff0c;这样的局限性有二&#xff1a; 第一是卷…

文献综述,参考文献引用

知网完成文献综述 点击已选&#xff1a; 点导出题录 点自定义&#xff0c;点摘要&#xff0c;点预览 复制到剪切板 参考文献如何在论文中标注引用 国内外研究综述 简单介绍主题和所用理论&#xff0c;用第一段写综述的总结。 【 ...作...在西方国家已经有...多年的历史&…

基于应力的拓扑优化的高效3D灵敏度分析代码(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【FFMPEG源码分析】从ffplay源码摸清ffmpeg框架(三)

从ffplay源码分析这篇文章中可以知道int stream_component_open(VideoState *is, int stream_index)函数是创建和初始化decoder的入口。本篇文章从此入口看下ffmpeg中decoder的内部结构是什么样子。 同样先提出几个问题&#xff0c;带着问题梳理源码&#xff0c;效率贼快啊 ff…

Linux(Linux的连接使用)

连接Linux我们一般使用CRT或者Xshell工具进行连接使用。 如CRT使用SSH的方式 输出主机&#xff0c;账户&#xff0c;密码那些就可以连接上了。 Linux系统是一个文件型操作系统&#xff0c;有一句话说Linux的一切皆是文件。Linux系统的启动大致有下面几个步骤 Linux系统有7个运…

excel应用技巧:如何用函数制作简易抽奖动图

利用INDEX函数和随机整数函数RANDBETWEEN配合&#xff0c;在Excel中做一个简单的抽奖器&#xff0c;可以随机抽取姓名或者奖品。有兴趣的伙伴可以做出来试试&#xff0c;撞撞2023年好运气。每次年会大家最期待的就是抽奖环节。为了看看自己今年运气怎么样&#xff0c;会不会获奖…

中国版ChatGPT来了! 如何解读ChatGPT将带来的技术变革

最近这段时间&#xff0c;ChatGPT真的是太火了&#xff01;各平台都在铺天盖地式的宣传&#xff0c;相信在这么些天的宣传中&#xff0c;大家也对ChatGPT有了一个大概的了解&#xff0c;我们这边也就简单介绍一下。据ChatGPT自我介绍&#xff0c;它是一款预训练语言模型&#x…

ContextCapture Master 倾斜摄影测量实景三维建模技术

ContextCapture实景建模大师是一套无需人工干预&#xff0c;通过影像自动生成高分辨率的三维模型的软件解决方案。它集合了全球最先进数字影像处理、计算机虚拟现实以及计算机几何图形算法&#xff0c;在易用性、数据兼容性、运算性能、友好的人机交互及自由的硬件配置兼容性等…

Java设计模式-02工厂模式

为什么需要工厂模式&#xff0c;其作用什么&#xff1f;如何实现&#xff0c;代码演示解析优缺点。Q1:为什么需要工厂模式&#xff1f;工厂模式的作用(优点)是什么&#xff1f; 解耦。把对象的创建和使用的过程分开。就是Class A 想调用 Class B &#xff0c;那么A只是调用B的…

安装jdk8

目录标题一、下载地址&#xff08;一&#xff09;Linux下载&#xff08;二&#xff09;Win下载二、安装&#xff08;一&#xff09;Linux&#xff08;二&#xff09;Win三、卸载&#xff08;一&#xff09;Linux&#xff08;二&#xff09;Win一、下载地址 jdk8最新版 jdk8其他…

Tessent Mbist(5) 并行static Retention(静态保持)测试

本章描述当嵌入存储器被不同的memory BIST控制器所测试或者被不同的memory BIST controller steps按顺序进行测试时如何进行retention测试. 在跑PSRT(parallel static retention testing)之前,建议先跑BIST in HWDefault 或者 RunTimeProg 模式去保证有足够高的fault覆盖率;PSR…

iOS 奔溃EXC_BAD_ACCESS(KERN_INVALID_ADDRESS)分析

EXC_BAD_ACCESS (KERN_INVALID_ADDRESS)是一种常见的iOS应用程序崩溃错误&#xff0c;可能有以下原因&#xff1a; 尝试访问已释放的对象&#xff1a;即使是一个引用计数为0的对象&#xff0c;尝试访问它将导致崩溃。 尝试访问不正确的内存地址&#xff1a;例如&#xff0c;尝…

SpringBoot+Vue实现养老智慧服务平台

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏…

高姿态下的面部表情识别系统

效果展示&#xff1a; python表情、性别识别面部表情识别 (FER) 在计算机安全、神经科学、心理学和工程学方面有大量应用。由于其非侵入性&#xff0c;它被认为是打击犯罪的有用技术。然而&#xff0c;FER 面临着几个挑战&#xff0c;其中最严重的是它在严重的头部姿势下的预测…