【C语言】 指针的进阶 看这一篇就够了

这篇具有很好参考价值的文章主要介绍了【C语言】 指针的进阶 看这一篇就够了。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录
1.字符指针
2.数组指针
3.指针数组
4.数组传参和指针传参
5.函数指针
6.函数指针数组
7.指向函数指针数组的指针
8.回调函数
9.qsort排序和冒泡排序

1.字符指针

让我们一起来回顾一下指针的概念!
1.指针就是一个变量,用来存放地址,地址唯一标识一块内存空间。
2.指针的大小是固定的4/8个字节(32位平台/64位平台)
3.指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作时候的权限。

int main()
{
	char ch = 'w';
	char* pc = &ch;//pc就是字符指针
	char* p = "abcdef";//常量字符串,p中存放的是首字符的地址
	//*p = 'w';error这是错误的,常量字符串不能被修改
	return 0;
}

如果我们想修改上面的常量字符串该怎么办呢?方法如下:

int main()
{
	char arr[] = "abcdef";
	char* p = arr;
	*p = 'w';
	printf("%s\n", arr);//wbcdef
	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;
}

【C语言】 指针的进阶 看这一篇就够了

2.指针数组——是数组

存放字符的指针数组——字符指针数组
char* arr3[5];
存放整型指针的数组——整型指针数组
int* arr[6];

int main()
{
	char* arr[] = { "abcdef","hehe","qwer" };//指针数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s\n", arr[i]);//每个字符串首字符地址
	}
	return 0;
}
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	//arr就是一个存放整型指针的数组
	//arr[i]=*(arr+i)
	int* arr[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			//printf("%d ", arr[i][j]);
			printf("%d ", *(arr[i] + j));
		}
		printf("\n");
	}
	return 0;
}

3.数组指针——是一种指针

数组指针就是指向数组的指针。

int arr[10];
&arr;取出的是数组的地址
int (*pa)[10]=&arr;//pa就是指针,pa指向的是数组arr

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

&数组名和数组名的区别

数组名绝大部分情况下是数组首元素地址,两个例外:
1.&数组名,取出的是整个数组的地址。从地址值角度来讲和数组首元素地址是一样的,但是意义不一样。
2.sizeof(数组名),数组名表示整个数组,计算得到的是数组的大小。

int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;//p是一个数组指针
	//类型:int(*)[10]
	//printf("%d", sizeof(arr));
	printf("%p\n", arr);//int*
	printf("%p\n", arr+1);//4
	printf("%p\n", &arr[0]);//int*
	printf("%p\n", &arr[0]+1);//4
	printf("%p\n", &arr);//int(*)[10]=&arr;
	printf("%p\n", &arr+1);//40
	return 0;
}

【C语言】 指针的进阶 看这一篇就够了
访问数组的几种方法:
使用下标的形式来访问数组:

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

使用指针的方式访问数组:

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

数组指针什么情况下使用呢?

//一维数组传参,形参是数组
void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);
	return 0;
}

//一维数组传参,形参是指针
void print(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		//printf("%d ", arr[i]);
		printf("%d ", *(arr + i));
	}
	printf("\n");
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);
	return 0;
}
//二维数组传参,形参部分写成二维数组
void print(int arr[3][5], int r, int c)
{
	int i = 9;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
	print(arr, 3, 5);
	return 0;
}

1.二维数组的数组名也表示首元素的地址
2.二维数组的首元素是第一行
3.首元素的地址就是第一行的地址,是一个一维数组的地址

//二维数组传参,形参部分写成指针
void print(int(*arr)[5], int r, int c)//通过上面引用的解读,所以这里传参,我们只传了第一行的地址,也就是数组指针
{
	int i = 9;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(arr + i) + j));
			//arr[i][j];
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
	print(arr, 3, 5);
	return 0;
}

4.数组传参和指针传参

一维数组传参,形参可以是数组,也可以是指针
当形参是指针的时候,要注意类型

void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int* arr)//ok
{}
void test2(int* arr[20])//ok
{}
void test2(int** arr)//No
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

二维数组传参
参数可以是指针,也可以是数组
如果是数组,行可以省略,但是列不能省略
如果是指针,传过去的是第一行的地址,形参就应该是数组指针

void test(int arr[3][5])//ok
{}
void test(int arr[][])//no
{}
void test(int arr[][5])//ok
{}
void test(int* arr)//no
{}
void test(int* arr[5])//no
{}
void test(int(*arr)[5])//ok
{}
void test(int** arr)//no
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

一级指针传参

void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}

二级指针传参

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

函数指针

注意:函数名和&函数名是一样的,二者没有区别。

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", Add);
	printf("%p\n", Add);
	return 0;
}

函数指针的地址要存起来,就要放在函数指针变量中

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf)(int, int) = Add;
	//pf就是函数指针变量,类型是int(*)(int,int)
}
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf)(int, int) = Add;
	/*int ret = (*pf)(3, 5);
	int ret = Add(3, 5);*/
	int ret = pf(3, 5);
	return 0;
}

注意!!!

typedef void(*pf_t2)(int);//pf_t2是类型名
void(*pf)(int);//pf是函数指针变量的名字

函数指针数组

数组的每个元素是一个函数指针。

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[4])(int, int) = { Add,Sub,Mul,Div };//放到函数指针数组里的函数类型应保持一致
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = pf[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 main()
{
	int (*pf)(int, int) = Add;
	//函数指针数组
	int (*pfArr[4])(int, int) = { Add,Sub };
	int (*(*ppfArr)[4])(int, int) = &pfArr;//ppfArr是一个指向函数指针数组的指针
	return 0;
}

回调函数

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

使用回调函数实现计算器:

//解决了普通语句的冗余
void menu()
{
	printf("**********************\n");
	printf("***1.Add      2.Sub***\n");
	printf("***3.Mul      4.Div***\n");
	printf("***0.exit           ***\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(*pf)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:>\n");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%d\n", ret);
}
int main()
{
	int input = 0;

	do
	{
		menu();
		printf("请输入选项:>\n");
		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;
}

qsort排序和冒泡排序

冒泡排序:

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz-1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	print_arr(arr, sz);
	return 0;
}

冒泡排序的缺点是什么呢?
它只能排序整型家族类型,而对于结构体等一些其他类型有一定的限制,所以我们引出qsort快速排序。

qsort排序

qsort可以直接使用。
qsort可以排任意类型的数据。
排什么类型的数据,要排序的数据就直接提供比较方法。

void qsort(void* base,//指向待排序数组的第一个元素
	      size_t num, //待排序的元素个数
	      size_t width, //每个元素的大小,单位是字节
	      int(__cdecl* compare)(const void* elem1, const void* elem2));
	      //指向一个函数,这个函数可以比较两个元素的大小

【C语言】 指针的进阶 看这一篇就够了

在进行举例之前,我们还需要知道下面的知识:
因为qsort可以排任意类型的数据,所以在传参的方面就要注意。

我们在普通的地址接收时,不能出现以下不兼容的情况。

这是错误的:

int main()
{
	int a = 0;
	char* p = &a;//error
	return 0;
}

在这里我们可以使用void * 无具体类型的指针,它可以接收任何类型的地址。但是需要注意:
1.void 的指针不能解引用操作符
2.如果是void
p,也不能进行p++
3.使用时,我们可以将它进行强制类型转换为其他类型的指针再进行解引用使用。

int main()
{
	int a = 10;
	void* p = &a;
	*(int*)p;
}

对于qsort的各参数类型,我们都不陌生,我们为什么要引出上面使用void*进行传参呢?

对于我们程序员来说,我们清楚的知道接下来我们要排序的数据类型是什么,但是在进行传参的过程中,很容易出现参数类型不兼容的情况,所以我们使用void*来接收各个类型的参数,之后按照什么类型排序就将它强制类型转换为什么形式,这样就会很清晰。

测试qsort排序函数排序整数

#include <stdlib.h>
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int cmp_int(const void* p1, const void* p2)
{
	//升序
	return *(int*)p1 - *(int*)p2;
	//降序
	//return *(int*)p2 - *(int*)p1;

}
test()
{
	int arr[10] = { 2,1,3,5,6,4,8,7,9,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//提供一个比较函数
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}
int main()
{
	test();
	return 0;
}

测试qsort排序函数排序结构体数据

#include <stdlib.h>
#include <string.h>
struct Stu
{
	char name[20];
	int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
test()
{
	struct Stu s[] = { {"zhangsan",30},{"lisi",40},{"wangwu",50} };
	int sz = sizeof(s) / sizeof(s[0]);
	//测试按照年龄来排序
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	//测试按照姓名来排序
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
	test();
	return 0;
}

利用冒泡排序的思想来模拟实现qsort功能的冒泡排序函数bubble_sort()。
1.实现整型排序

int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}
void Swap(char* buf1, char* buf2, size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
void bubble_sort(void* base, size_t num, size_t width, int(*cmp)(const void* p1, const void* p2))
{
	//确定趟数
	size_t i = 0;
	int flag=1;//假设有序
	for (i = 0; i < num - 1; i++)
	{
		//一趟冒泡排序的过程
		size_t j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			//两个相邻元素的比较
			//arr[j] arr[j+1]
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
			//降序<0
			{
				//交换
				flag=0;
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
			if (flag == 1)
			{
				break;
			}
		}
	}
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[10] = { 2,1,3,6,4,5,7,8,9,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
	return 0;
}

实现结构体排序:

#include <string.h>
struct Stu
{
	char name[20];
	int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void Swap(char* buf1, char* buf2, size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
void print_s(struct Stu s[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n", s[i].name,s[i].age);
	}
}
void bubble_sort(void* base, size_t num, size_t width, int(*cmp)(const void* p1, const void* p2))
{
	//确定趟数
	size_t i = 0;
	int flag = 1;//假设有序
	for (i = 0; i < num - 1; i++)
	{
		//一趟冒泡排序的过程
		size_t j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			//两个相邻元素的比较
			//arr[j] arr[j+1]
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
			{
				//交换
				flag = 0;
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
			if (flag == 1)
			{
				break;
			}
		}
	}
}
int main()
{
	struct Stu s[] = { {"zhangsan",30},{"lisi",40},{"wangwu",50} };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	print_s(s, sz);
	return 0;
}

感谢友友们阅读,欢迎批评指正!文章来源地址https://www.toymoban.com/news/detail-421344.html

到了这里,关于【C语言】 指针的进阶 看这一篇就够了的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 精通线程池,看这一篇就够了

    当我们运用多线程技术处理任务时,需要不断通过new的方式创建线程,这样频繁创建和销毁线程,会造成cpu消耗过多。那么有没有什么办法 避免频繁创建线程 呢? 当然有,和我们以前学习过多连接池技术类似,线程池通过提前创建好线程保存在线程池中, 在任务要执行时取

    2023年04月17日
    浏览(90)
  • CSS基础——看这一篇就够了

    目录 一、CSS简介 1.CSS是什么? 2.CSS的作用 3.CSS的构成 二、CSS选择器 1.基础选择器 (1).标签选择器 (2)类选择器 (3)标签选择器 (4) 通配符选择器 2.复合选择器 (1)后代选择器(包含选择器) (2)子选择器 (3)并集选择器 (4)伪类选择器  三、基本属性 1.字体属性

    2024年02月09日
    浏览(60)
  • 超图(HyperGraph)学习,看这一篇就够了

    最近事多,好久没更新了,随便写写(Ctrl+V)点 一、超图定义 通常图论中的图,一条edge只能连接2个vertex,在超图中,不限量 如何理解呢,就用我正在做的KT问题来看:7道题目-7个顶点;4种概念-4条超边,其中第1,2,3题都是考察概念1的,则构建一个包含了这仨的超边,以此类

    2024年02月02日
    浏览(58)
  • 还不会二分查找?看这一篇就够了

    二分查找分为整数二分和浮点数二分,一般所说的二分查找都是指整数二分。 满足单调性的数组一定可以使用二分查找,但可以使用二分查找的数组不一定需要满足单调性。 不妨假设我们找到了条件 C 1 C_1 C 1 ​ ,它和它的 对立条件 C 2 C_2 C 2 ​ 能够将数组 a a a 一分为二,

    2024年01月19日
    浏览(48)
  • SourceTree使用看这一篇就够了

     你梦想有一天成为git大师,然而面对复杂的git命令,你感觉TMD这我能记得住吗?你曾经羡慕从命令行敲git命令,才会更加炫酷,然而时间一长,TMD命令我有忘了。那么今天我介绍的这款工具会让你从git命令中解救出来,这就是git可视化工具SourcTree。 事实上Git的功能十分强大

    2024年02月08日
    浏览(60)
  • CAS自旋锁,看这一篇就够了

    前序 时隔多年,杰伦终于出了新专辑,《最伟大的作品》让我们穿越到1920年,见到了马格利特的绿苹果、大利的超现实、常玉画的大腿、莫奈的睡莲、徐志摩的诗… 他说“最伟大的作品”并不是自己的歌,而是这个世界上最伟大的艺术作品们。 为什么要写CAS自旋锁呢?最近

    2023年04月08日
    浏览(39)
  • Docker Volume 看这一篇就够了

    默认情况下,在容器内创建的所有文件都存储在可写容器层上。这意味着: 当该容器不再存在时,数据不会持续存在,并且如果另一个进程需要数据,则可能很难将数据从容器中取出。 容器的可写层与运行容器的主机紧密耦合。您无法轻松地将数据移动到其他地方。 写入容

    2024年02月02日
    浏览(93)
  • 还不会拓扑排序?看这一篇就够了

    拓扑排序是一种有向无环图(DAG)的顶点排序方法,它将一个有向无环图中的所有顶点排成一个线性序列,使得图中 任意一条有向边上的起点排在终点的前面 。 这样说还不够具体,我们先来看一个例子。假设某大学的课程安排如下: 课程编号 课程名称 先修课程 1 1 1 高等数

    2023年04月08日
    浏览(102)
  • ElasticSearch常见用法,看这一篇就够了

    2024送书福利正式起航 关注「哪吒编程」,提升Java技能 文末送3本《一本书讲透Elasticsearch:原理、进阶与工程实践》 大家好,我是哪吒。 ElasticSearch是一款由Java开发的开源搜索引擎,它以其出色的实时搜索、稳定可靠、快速安装和方便使用的特性,在Java开发社区中赢得了广

    2024年03月19日
    浏览(64)
  • memcmp函数详解 看这一篇就够了-C语言(函数讲解、函数实现、使用用法举例、作用、自己实现函数 )

    memcmp()函数用于:比较两个内存块 函数声明:int memcmp ( const void * ptr1, const void * ptr2, size_t num ); 参数: ptr1:指向内存块的指针。 ptr2:指向内存块的指针。 数字:要比较的字节数。 返回值: 0: 在两个内存块中不匹配的第一个字节在  ptr1  中的值低于 在 ptr2  中的值(如果计

    2023年04月09日
    浏览(61)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包