大家好啊,因为上个星期生病的原因,没有及时更新,本期这就补上。
本期主要内容是深入解析指针,让大家能够进一步了解指针,希望对各位小伙伴有所帮助!
C语言里面的指针可以说是非常重要的了,不管是以后学习数据结构还是什么其他内容,指针是重中之重,
我本期除了会讲解指针的内容以外,还会有一套指针笔试题的讲解,话不多说,咋们进入正片。
1、指针其实本质上叫做指针变量,只不过我们为了更方便解读就
口语化式的叫做指针
2、指针在32位平台占4个字节内存大小,在64位平台占8个字节
内存大小
字符指针的写法为:char *
常用的字符指针写法是:
char ch = 'a';
char* p = &ch;
*p = 'b';
printf("%c\n", ch);
还有一种写法是:
const char ch = "abcdef";
char* p = &ch;
可别小看这两行代码,里面蕴藏着许多内容:
1、这里可不是把abcdef全部放到指针p里面去了,而是把这个定义的字符串首元素a的地址赋给了指针p
当有了首元素地址之后,依次向后面打印就可以打印出整个字符串了;
2、这个字符指针的方法使用的时候,字符串“abcdef”是不可以被改变的:
这里大家可以看到’a’没有被改为‘w’,并且程序还挂了
这是为什么呢?
是因为,这里的“abcdef”是一个常量字符串,常量字符串是不可被修改的。p的权限变大了,因为p没有被修饰,所以p敢改这个字符串,但是程序会死掉,并且出现错误,所以我们才在前面加上一个const,来表明这里的常量字符串,不能被修改。
## 小例题
char arr1[] = "abcdef";
char arr2[] = "abcdef";
char *p1 = "abcdef";
char *p2 = "abcdef";
if (arr1 == arr2)
printf("yes\n");
else
printf("no\n");
if (*p1 == *p2)
printf("yes\n");
else
printf("no\n");
其实很好理解,就是:字符串相同,因为数组不同,所以两个字符串地址不同;而两个指针是指向同一个字符串的,所以两个指针地址相同
int* arr1[10];//整型指针数组(也叫一级整型指针数组)
int* *arr2[10];//二级整型指针数组
char* arr3[10];//一级字符指针数组
char* *arr4[10];//二级字符指针数组
数组指针本质上是一个指针,这个指针指向一个数组
结合上面的指针数组一起来看看:
int arr[5] = {1,2,3,4,5};
int(*p)[5] = &arr;
int arr[10] = { 0 };
int (*p)[10] = &arr;
char* arr2[10] = { 0 };
char* (*pa)[10] = &arr2;
以上就是数组指针与指针数组的区别,大家可千万不敢搞混了!!!
1、一般情况下数组名就是首元素地址
2、在sizeof和&arr的情况下,arr表示整个数组
大家看看两段代码:
大家可能会疑惑,接下来我为大家进行讲解:
大家只需要记住数组名代表整个数组的情况即可,后面我们有一组列题来专门讲这个数组名知识点的
#include
void pri1(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 pri2(int(*p)[5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%d ", p[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 };
pri1(arr, 3, 5);
pri2(arr, 3, 5);
return 0;
}
int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
printf("%p\n", arr);
printf("%p\n", arr + 1);
可以看到二维数组加1,地址加了20个字节,也就是5个元素,正好就是二维数组的一整行元素
也就是说:
int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
pri1(arr, 3, 5);
pri2(arr, 3, 5);
我们来总结一下几个知识点
#include
int main()
{
int a[5];
int* b[5];
int(*c)[5];
int(*d[10])[5];
return 0;
}
让我们看一组代码:
#include
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int *arr2[])
{}
void test2(int *arr2[20])
{}
void test2(int **arr2)
{}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
return 0;
}
大家知道答案吗?我直接公布答案了:
答案就是全部正确,大家可能知道1,2,4,5是怎么来的,那大家知道为什么3和4也是正确的吗?
请各位小伙伴们听我详细分解:
int *p1//一级指针
int **p2//二级指针
int***p3//三级指针
.
.
.
以此类推
二级指针是用来存放一级指针地址的;三级指针是用来存放二级指针地址的,也是以此类推
#include
void test(int arr[3][5]])
{}
void test(int arr[][5]])
{}
void test(int arr[][])
{}
void test(int *arr)
{}
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);
}
大家知道这些函数接收参数是正确的吗?
为什么是以上的答案呢?我们一起来分析分析:
注意:上图内容很重要,小伙伴们一定要熟记于心!!!
当函数参数是一个指针的时候,实参会是什么数据呢?
1、int arr[10]={0};
pri(arr);
可以是一个整型数组数组名
2、int a=10;
pri(&a);
可以是一个整型变量的地址
3、int *p=&a;
pri§;
可以是一个整型指针
只要是传参的值本质上是一个整型指针就行
通过上面的例题我们就可以得知:只要传参本质是一个二级指针就行
1、int n = 10;int* p = &n; int** p1 = &p; pri(p1); pri(&p);
2、指针数组的数组名也可以
int*parr[10];
pri(parr)
函数指针是一个指针,该指针指向一个函数,也就是指向函数的指针
int(*p1)[10];//这个是整型数组指针
char(*p1)[10];//这个是字符数组指针
int*(*p1)[10];//这个是一个指向存放10个整型指针数组的指针
//重点来了
int (*p2)(int ,int)//这个就是函数指针
#include
int add(int x, int y)
{
return x + y;
}
void pri(int(*add)(int, int))
{
int a = 5;
int b = 5;
int ret = add(a, b);
printf("%d\n", ret);
}
int main()
{
pri(add);
return 0;
}
上面就是函数指针的基础用法。
我们来仔细分析:
以上就是函数指针的大体模板,只不过参数部分可以变得很复杂,小伙伴们一定要用心观察。
函数名本质上并不是函数的地址。函数名可以当做函数地址来使用,这是因为发生了隐式转换
,我们以后会讲到隐式转换
,我们可以拿着用。
( * ( void ( * ) ( ) ) 0 )( )
void ( * arr ( int , void ( * ) ( int ) ) ) ( int )
可能又有一部分人不理解这行代码了,我们继续:
以上代码有过多重复部分,我们可以进行简化:
void ( * arr ( int , void ( * ) ( int ) ) ) ( int )
typedef void(*p)(int);
p arr(int, p);
这里我们用到了typedef重定义,可能有很多人问了,为什么不是【typedef void(*)(int) p】这样把p定义在最后呢?因为C语言的语法不是这样定义的。【typedef void(p)(int)】就相当于把【typedef void()(int)】重定义为了p。
函数指针有什么用呢?
接下来我们看看函数指针能怎么使用:
首先我们来看看最基础的一个计算器的实现:
#define _CRT_SECURE_NO_WARNINGS
#include
void menu()
{
printf("*****0.exit*****\n");
printf("*****1.add *****\n");
printf("*****2.sub *****\n");
printf("*****3.mul *****\n");
printf("*****4.div *****\n");
}
int add(int i, int j)
{
return i + j;
}
int sub(int i, int j)
{
return i - j;
}
int mul(int i, int j)
{
return i * j;
}
int div(int i, int j)
{
return i / j;
}
int main()
{
int n = 0;
int i = 0;
int j = 0;
int ret = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &n);
switch (n)
{
case 0:
printf("退出程序\n");
break;
case 1:
printf("请输入两个操作数:");
scanf("%d %d", &i, &j);
ret = add(i, j);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数:");
scanf("%d %d", &i, &j);
ret = sub(i, j);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数:");
scanf("%d %d", &i, &j);
ret = mul(i, j);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数:");
scanf("%d %d", &i, &j);
ret = div(i, j);
printf("%d\n", ret);
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (n);
return 0;
}
可以看到一个简单的计算器已经实现了,但是有很多代码重复了:
能不能更简单一点呢?
答案是可以的!
#include
void menu()
{
printf("*****0.exit*****\n");
printf("*****1.add *****\n");
printf("*****2.sub *****\n");
printf("*****3.mul *****\n");
printf("*****4.div *****\n");
}
int add(int i, int j)
{
return i + j;
}
int sub(int i, int j)
{
return i - j;
}
int mul(int i, int j)
{
return i * j;
}
int div(int i, int j)
{
return i / j;
}
void calc(int(*p)(int ,int ))//函数指针(计算器)
{
int i = 0;
int j = 0;
int ret = 0;
printf("请输入两个操作数:");
scanf("%d %d", &i, &j);
ret = p(i, j);
printf("%d\n", ret);
}
int main()
{
int n = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &n);
switch (n)
{
case 0:
printf("退出程序\n");
break;
case 1:
calc(add);
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(div);
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (n);
return 0;
}
可以看出代码变得更简单了,函数变得不那么冗余了。当然还可以让这个代码变得更简洁,我们下面将
讲
函数指针数组是一个数组,数组里面的元素都是函数指针。所以函数指针数组是一个存放函数指针的数组。
int( * p [10] )( )
#include
void menu()
{
printf("*****0.exit*****\n");
printf("*****1.add *****\n");
printf("*****2.sub *****\n");
printf("*****3.mul *****\n");
printf("*****4.div *****\n");
}
int add(int i, int j)
{
return i + j;
}
int sub(int i, int j)
{
return i - j;
}
int mul(int i, int j)
{
return i * j;
}
int div(int i, int j)
{
return i / j;
}
int main()
{
int(*p[4])(int, int) = { add,sub,mul,div };
int i = 0;
for (i = 0; i < 4; i++)
{
int ret = p[i](8, 4);
printf("%d\n", ret);
}
return 0;
}
可以看到将函数指针放到数组里面是很便捷的,接下来运用一下函数指针数组
上面我们做了一个简单的计算器,但是当计算器需要计算各种方法的时候(比如说取余,异或,按位,移位)难道要一个个的调用函数指针吗?
其实不用那么麻烦
#include
void menu()
{
printf("*****0.exit*****\n");
printf("*****1.add *****\n");
printf("*****2.sub *****\n");
printf("*****3.mul *****\n");
printf("*****4.div *****\n");
printf("*****5.qy *****\n");
printf("*****6.awy *****\n");
printf("*****7.awh *****\n");
printf("*****8.yh *****\n");
printf("*****9.ljy *****\n");
printf("*****10.ljh*****\n");
printf("*****11.zy *****\n");
printf("*****12.yy *****\n");
}
int add(int i, int j)
{
return i + j;
}
int sub(int i, int j)
{
return i - j;
}
int mul(int i, int j)
{
return i * j;
}
int div(int i, int j)
{
return i / j;
}
int qy(int i, int j)
{
return i % j;
}
int awy(int i, int j)
{
return i & j;
}
int awh(int i, int j)
{
return i | j;
}
int yh(int i, int j)
{
return i ^ j;
}
int ljy(int i, int j)
{
return i && j;
}
int ljh(int i, int j)
{
return i || j;
}
int zy(int i, int j)
{
return i << j;
}
int yy(int i, int j)
{
return i >> j;
}
void calc(int (*pfarr)(int,int))
{
int n = 0;
int i = 0;
int j = 0;
int ret = 0;
printf("请输入两个操作数:");
scanf("%d %d", &i, &j);
ret = pfarr(i, j);
printf("%d\n", ret);
}
int main()
{
int n = 0;
int(*pfarr[])(int, int) = { 0,add,sub,mul,div,qy,awy,awh,yh,ljy,ljh,zy,yy };
do
{
menu();
printf("请选择:");
scanf("%d", &n);
if (n == 0)
{
printf("退出程序\n");
}
else if (n >= 1 && n <= 12)
{
calc(pfarr[n]);
}
else
{
printf("选择错误\n");
}
} while (n);
return 0;
}
很多人看到这个名字就已经晕了,其实我们仔细分析会发现不难理解
int(*p[10])()
int ( * ( * parr ) [5] )( ) = &p
我们只需要了解指向函数指针数组的指针是什么样子就行,不用深究。当然,相信各位小伙伴们已经发现了,这个东西又是一个指针,我们又可以放到一个数组里面,像这样一直套娃下去…
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
#include
void bubble(int* arr, int sz)
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)//一共要排序多少躺
{
int flag = 1;//假设原来的数组内容已经是正确的顺序了
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;
flag = 0;//如果flag等于0那么就表示数组内容顺序有问题,已经排序完一次了
}
}
if (flag == 1)//如果顺序没有问题就退出
{
break;
}
}
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
上面的冒泡排序我们已经就行优化了,但是不管再怎么优化也只能排列整型数据,能排浮点型、字符、结构体类型的数据吗?
显而易见是不能的,那么就要用到我们的qsort函数了
qsort函数就是快速排序
上图中是qsort函数的具体内容,需要引头文件stdlib.h
qsort函数4个内容:
1、排序内容的起始地址;
2、待排序的元素个数;
3、待排序元素字节大小;
4、排序的方法
注:qsort默认是排升序的
下面我们会用qsort来排序整型数据和结构体数据。
void*是无具体类型的指针,可以接受任意类型的地址
void*是无具体类型的指针,所以不能解引用操作和+-整数的操作
#include
#include
int cmp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
可以看出用了qsort函数之后结果不仅正确了,而且还节约时间,空间,可以说是非常方便了。
#include
#include
#include
struct stu
{
char name[20];
int age;
};
int cmp_by_name(const void* e1,const void* e2)
{
return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
int cmp_by_age(const void* e1, const void* e2)
{
return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
void test2()
{
struct stu s[] = { {"zhangsan",15}, {"lisi",30} ,{"wangwu",20} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_by_name);
qsort(s, sz, sizeof(s[0]), cmp_by_age);
}
int main()
{
test2();//排序结构体数据
return 0;
}
我们来看看结果:
名字排序:
年龄排序:
可以看到qsort把结构体的数据已经排序出来了,由此可见qsort的强大之处。
#include
void swap(char* b1, char* b2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *b1;
*b1 = *b2;
*b2 = tmp;
b1++;
b2++;
}
}
void bubble(void*base,int sz,int width,int (*cmp)(const void* e1,const void* e2))
{
int i = 0;
int j = 0;
int flag = 1;//假设原来的数组内容已经是正确的顺序了
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
//if (arr[j] > arr[j + 1])
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
{
swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
flag = 0;//如果flag等于0那么就表示数组内容顺序有问题,已经排序完一次了
}
}
if (flag == 1)//如果顺序没有问题就退出
{
break;
}
}
}
int cmp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void test3()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble(arr, sz, sizeof(arr[0]), cmp);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
test3();
return 0;
}
到目前为止,我们的指针进阶的内容基本上完成了,但是可没有结束哦!下面有几组笔试指针题目,有许多小陷阱,欢迎大家挑战哦!我也会为大家进行讲解的!
我们不多说废话,直接题目+答案+讲解
#include
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a+0));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a+1));
printf("%d\n", sizeof(a[1]));
printf("%d\n", sizeof(&a));
printf("%d\n", sizeof(*&a));
printf("%d\n", sizeof(&a+1));
printf("%d\n", sizeof(&a[0]));
printf("%d\n", sizeof(&a[0]+1));
return 0;
}
#include
int main()
{
char a[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a + 0));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(a[1]));
printf("%d\n", sizeof(&a));
printf("%d\n", sizeof(*&a));
printf("%d\n", sizeof(&a + 1));
printf("%d\n", sizeof(&a[0]));
printf("%d\n", sizeof(&a[0] + 1));
return 0;
}
strlen遇到\0才停止,\0不是字符串的内容,\0只是结束标志
#include
int main()
{
char a[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(a));
printf("%d\n", strlen(a + 0));
printf("%d\n", strlen(*a));
printf("%d\n", strlen(a[1]));
printf("%d\n", strlen(&a));
printf("%d\n", strlen(&a + 1));
printf("%d\n", strlen(&a[0] + 1));
return 0;
}
这一组题有许多陷阱,不能直接编译出结果,我为大家一一讲解:
可以看到还是有很多陷阱的,小伙伴们以后别像小编一样粗心大意哦!
sizeof计算字符串的时候会把\0算进去,因为\0也占了空间位置
#include
int main()
{
char a[] = "abcdef";
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a + 0));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a[1]));
printf("%d\n", sizeof(&a));
printf("%d\n", sizeof(&a + 1));
printf("%d\n", sizeof(&a[0] + 1));
return 0;
}
char a[]=“”;[]未表明个数,双引号定义字符串的时候会默认带上\0。前提是方括号【】内的数字也就是空间足够大
#include
int main()
{
char a[] = "abcdef";
printf("%d\n", strlen(a));
printf("%d\n", strlen(a + 0));
printf("%d\n", strlen(*a));
printf("%d\n", strlen(a[1]));
printf("%d\n", strlen(&a));
printf("%d\n", strlen(&a + 1));
printf("%d\n", strlen(&a[0] + 1));
return 0;
}
其实只要找到双引号自带\0就很简单啦!
#include
int main()
{
char* a = "abcdef";
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a[0]));
printf("%d\n", sizeof(&a));
printf("%d\n", sizeof(&a + 1));
printf("%d\n", sizeof(&a[0] + 1));
return 0;
}
}
#include
int main()
{
char a[] = "abcdef";
printf("%d\n", strlen(a));
printf("%d\n", strlen(a + 1));
printf("%d\n", strlen(*a));
printf("%d\n", strlen(a[0]));
printf("%d\n", strlen(&a));
printf("%d\n", strlen(&a + 1));
printf("%d\n", strlen(&a[0] + 1));
return 0;
}
我们之前说过二维数组数组名代表的就是第一行数组的地址
#include
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a[0][0]));
printf("%d\n", sizeof(a[0]));
printf("%d\n", sizeof(a[0]+1));
printf("%d\n", sizeof(*(a[0] + 1)));
printf("%d\n", sizeof(a+1));
printf("%d\n", sizeof(*(a + 1)));
printf("%d\n", sizeof(&a[0]+1));
printf("%d\n", sizeof(*(&a[0] + 1)));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a[3]));
return 0;
}
#include
int main()
{
int a[5] = { 1,2,3,4,5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
#include
struct stu
{
int num;
char* a;
short date;
char b[2];
short arr[4];
}*p;
int main()
{
p = (struct stu*)0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
#include
int main()
{
int a[4] = { 1,2,3,4 };
int* p1 = (int*)(&a + 1);
int* p2 = (int*)((int)a + 1);
printf("%x,%x", p1[-1], *p2);
return 0;
}
#include
int main()
{
int a[3][2] = { (0,1),(2,3),(4,5) };
int* p;
p = a[0];
printf("%d\n", p[0]);
return 0;
}
#include
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n",&p[4][2]-&a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
#include
int main()
{
int a[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
int* arr1 = (int*)(&a + 1);
int* arr2 = (int*)(*(a + 1));
printf("%d,%d\n", *(arr1 - 1), *(arr2 - 1));
return 0;
}
#include
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
#include
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}
这一题小编在本子上做了,有点乱,大家可以自己做做这道题:
本章花费了小编好几天时间,干货还是有的,希望能够给各位小伙伴带来新的收获,同时希望各位观众能够点点赞,最后一道题可能一些观众不理解,如果不理解的话可以私信小编,小编为为你详细解答的,希望各位小伙伴能够与小编一起进步,我们下期见啦!