C语言中多种指针相关类型详解-创新互联
目录
创新互联是一家专业提供南乐企业网站建设,专注与成都网站制作、成都做网站、H5开发、小程序制作等业务。10年已为南乐众多企业、政府机构等服务。创新互联专业网站制作公司优惠进行中。指针数组
数组指针
arr与&arr的区别
数组指针
1.数组指针的创建格式:
2.数组指针的用处
数组参数,指针参数
一维数组传参
二维数组传参(注意事项)
一级指针传参
二级指针传参
函数指针
函数指针数组
函数指针真正用处用法
指针数组
指针数组是指在一组数中能够存放多个相同指针类型的一组数
指针数组的书写格式:
- int* * *arr[5];——存放int *类型的数组
- char* ch[10];——存放char *类型的数组
- int** pp[5];——存放int** 二级指针类型的数组
当然还可以类比出三级指针,四级指针等等等等....
指针的初始化:
如int* arr[5]={0};
在这里我们将数组arr里的每一个元素赋了0,也就相当于将指针数组里面的所有元素都变为了空指针,这是因为在编译器中,NULL相当于0被强制类型转换为了(void*)类型,所以NULL本质上就是0。
数组指针数组指针是一种指针类型,是一个指向数组的指针。
数组指针的书写格式:
#includeint main()
{
int arr[5]={0};
char s[5]={0};
int (*p)[5]=&arr;//这个就是数组指针的格式,
//也可以用其他类型如char等
char (*b)[5]=&s;
return 0;
}
arr与&arr的区别创造一个int型数组int arr[5]={0}; 我们都知道arr是数组名,也就是首元素的地址,但&arr表示的是什么?
在编译器中输入以下代码:
#includeint main()
{
int arr[5] = { 0 };
printf("%p\n", arr);
printf("%p\n",arr + 1);
printf("%p\n", &arr);
printf("%p\n", &arr + 1);
return 0;
}
最终输出结果如下:
观察上面的输出后的图,我们可以看到arr与&arr的地址是相同的,但arr与&arr其实是两个完全不同的概念。
我们可以看到,arr+1后的地址与arr的地址相差了4,这是因为arr是数组名,相当于存放了数组首元素的地址,也就是一个int*类型的指针,所以每加一后都会直接跳过四个字节,读取到数组中下一个元素。而我们再看向&arr与&arr+1的地址,经过计算后得到两地址相差的数值大小为20,我们会发现,这刚好是我们创建数组的所占的总共空间大小(20个字节)。
也就是说,&arr其实取到的并不是数组首元素地址,类型并不为int*指针,而取到的是整个数组的地址,类型为数组指针,所以当我们加一的时候,由于是数组指针加一,所以会跳过20个字节。那么数组指针具体又是什么呢?
数组指针就像int型会有int指针,char类型有char指针,数组也会有对应的数组指针,例如int型数组,数组指针的书写格式就为int(*)[],具体用法如下:
数组指针的应用
1.数组指针的创建格式:2.数组指针的用处Ⅰ:访问一维数组
#includeint main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*p)[10] = &arr;
int i = 0;
for (i = 0; i< 10; i++)
{
printf("%d ",*((*p) + i));
}
return 0;
}
这里虽然访问一维数组用这种方法繁冗且不建议使用,但还是做下解释。
Ⅱ:访问二维数组
如果要写一个函数,将遍历二维数组将每一个元素打印出来,我们会怎么写呢?
在以前会使用下面的方法:
#includevoid print(int a[3][5], int x, int y)
{
int i = 0;
int j = 0;
for (i = 0; i< x; i++)
{
for (j = 0; j< y; j++)
{
printf("%d ", a[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;
}
但现在,我们可以用数组指针的方式来实现:
#includevoid print(int(*p)[5], int x, int y)
{
int i = 0;
int j = 0;
for (i = 0; i< x; i++)
{
for (j = 0; j< y; 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 };
print(arr, 3, 5);
return 0;
}
在二维数组中例如arr[3][5],其中的“[3]”所代表的是有3行元素,“[5]”代表的是每行元素有5列。而arr[5]则代表一行的地址
所以当我们在传参时,只需要将形参定为int型的指针数组,先将每一行的地址传入,就可以实现遍历数组的作用。
再次理解:
#includeint main()
{
int a[10] = { 0 };
int* p = a;
//*(a+i)==*(p+i)==a[i]==p[i]
}
数组参数,指针参数
一维数组传参看下面代码,判断传参是否正确:
#includevoid 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)//ok?
{}
int main()
{
int arr[10]={0};
int *arr2[20]={0};
test(arr);
test2(arr2);
return 0;
}
一维数组传参经验总结:
- 在数组传参时可以使用数组形式,而且数组后可以写大小也可不写,因为数组在传参过程中不会创建数组,所以数组大小填错或不填都可以
- 数组传参传入的数组名是首元素地址,可以用指针来接收
所以上述代码中的传参都是正确的。
二维数组传参(注意事项)看下面代码,判断传参是否正确:
#includevoid test(int arr[3][5])//ok? ----1
{}
void test(int arr[][])//ok? ----2
{}
void test(int arr[][5])//ok? ----3
{}
void test(int *arr)//ok? ----4
{}
void test(int (*arr)[5])//ok? ----5
{}
void test(int **arr)//ok? ----6
{}
int main()
{
int arr[3][5]={0};
test(arr);
}
二维数组传参经验总结:
- 二维数组传参时可以写成形参二维数组的形式,行可以不写大小,但是列一定要写大小。
- 在传参时传入的是数组指针类型,所以不能用一级指针接收,也不能用指针数组、二级指针来接收,这样是类型上就不对的了
综上,2,4,6是错误的传参形式。
一级指针传参Q:当函数的参数为一级指针时,函数能接收什么参数?
A:可以传入一个地址,或数组名,或直接传入一个一级指针。
二级指针传参Q:当函数的参数为二级指针时,函数能接收什么参数?
A:可以传入一级指针的地址、二级指针、指针数组的数组名。
函数指针函数不同于数组,函数名与在函数名前加上&本质上是没有区别的,下面是函数指针的一些使用:
#includeint Add(int x, int y)
{
return x + y;
}
void Fun(char x)
{
;
}
int main()
{
//Add与&Add本质上没有区别,都表示为同一个地址
printf("%p\n", Add);
printf("%p\n", &Add);
//函数指针的创建举例
int (*p)(int, int) = Add;
int (*pa)(int, int) = &Add;
void (*ppa)(char) = Fun;
//函数指针的调用也同函数名一样,可以直接用名或者在前面加上解引用符
printf("%p\n", p);
printf("%p\n", *p);
//函数的调用
printf("%d\n", Add(1, 2));
printf("%d\n", (&Add)(1, 2));//这里特别注意,要将&Add用小括号括起来,否则Add就会先与后面的(1,2)结合,从而无法调用函数。
printf("%d\n", p(1, 2));
printf("%d\n", (*p)(1, 2));//*p也是先要用小括号括起来,否则会与后面的(1,2)结合,从而无法调用函数得到想得到的结果。
return 0;
}
特别注意:以上面代码为例,若要以&Add或者*p这种形式来使用,要先用小括号括起来,否则Add/p就会先与后面的(1,2)结合,从而无法调用函数。
在学习函数指针时,又偶然发现了一道很经典,非常,特别,诡异的题目:
//请分析下面这段代码:
(*(void(*)())0)();
??????????????????????????????????????????????????????????????????????????????????????
在刚看到这段代码时我的心情如上👆。心想:“魔法,这一定是魔法”。但其实冷静思考过后,最终还是理解了这一段离谱且诡异的代码:
首先从最里开始,void(*) (),这样单独拆开后我们不难发现,这是一个函数指针类型,只不过是一个void型的函数且不用传参,在这整体上再套多一个小括号并在后面加上数字0,变为(void(*) () )0的样子,这里最外一层的小括号其实是强制类型转换符,将0这个地址上放入一个函数指针(也就是放了一个函数)。而最终变为(*(void(*)())0)(),其实是调用了0地址上的这个参数为无参,返回类型是void的函数。所以总结来说,整个代码是在调用0地址处的函数。
但是!!!!
当我们将上面代码在编译器中正式运行时是不可能的,会出现运行中止或报错或根本不运行。这是因为地址0是我们普通用户禁止被使用的地址,我们是无法使用的,因为0地址是开机启动时才访问的地址。
再来看一条简单一点的代码(可能)
void (*signal(int,void(*)(int)))(int);
上面代码要这么看:void(* signal(int,void(*)(int)) )(int);
这样子就不难看出(应该不难)signal(int,void(*)(int))是一个函数的声明,因为函数的参数位置如果是调用的话,则不应该为类型,所以这个是函数的声明,这是一个函数名为signal,参数为int型和函数指针型的函数,函数名有了,参数也有了,现在缺少的就是返回类型,这个返回的类型便是剩下的→void ( *)(int)。
Tip:在这里要特别注意,*总是要和名字在一起,不可以写为void ( *)(int) signal(int,void(*)(int))的形式
看吧,不难吧(后仰摊手)
什么??还是难???那好吧,下面我们尝试着简化一下这样的代码(其实我也觉得难)
将void(*)(int)这样类型的函数指针类型重命名为pfun_t,这里要用到typedef,但注意!!不是写为这样:typedef void(* )(int) pfun_t。而是要写为:typedef void(* pfun_t)(int) 这里这么写是因为*总要与名字在一起(上面tip里有说过)。
经过函数指针的重命名后,上面的代码可以最终简化为:
pfun_t signal(int,pfun_t);
函数指针数组
按照字面意思,我们可以知道,这是一组存放函数指针类型元素的数组。
函数指针数组的使用:
#includeint 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 (*arr[5])(int, int) = { Add,Sub,Mul,Div };
int i = 0;
for (i = 0; i< 5; i++)
{
printf("%d\n",arr[i](8, 4));
}
return 0;
}
函数指针真正用处用法
函数指针可以利用写一个函数,使其形参的类型为函数指针类型,从而达到一个函数可以调用多个不同函数的一个函数。
我们先写下下面这样的一段代码:
#includevoid Menu()
{
printf("******************************\n");
printf("**** 1.Add 2.Sub ****\n");
printf("**** 3.Mul 4.Div 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;
do
{
int x = 0;
int y = 0;
Menu();
printf("请输入你要的操作");
scanf("%d", &input);
switch (input)
{
case 0:
printf("结束操作\n");
break;
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;
default:
printf("输入错误,请重新输入");
break;
}
} while (input);
}
在上面我们会发现case1,2,3,4大量代码是重复冗余的,且Add,Sub,Mul,Div函数都是返回的int型,且都有两个int型的参数,对于这种情况,我们可以利用写一个函数,来调用这些形式上差不多相同的函数,能够做到缩减代码量的目的,而调用这些函数以形参形式传过去,就需要用到函数指针类型。
在这里我们创建一个Cal函数,其参数形式为函数指针型,若以上面函数来举例,则写出如下代码:
void Cal(int (*fun)(int,int))
{}
然后再将重复的代码放入Cal函数内之后删去,便可以达到减少重复代码的目的了。下面是完整的更改后的代码:
#includevoid Menu()
{
printf("******************************\n");
printf("**** 1.Add 2.Sub ****\n");
printf("**** 3.Mul 4.Div 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 Cal(int (*fun)(int, int))
{
int x = 0;
int y = 0;
printf("请输入你想要操作的两个数:");
scanf("%d %d", &x, &y);
printf("%d\n", fun(x, y));
}
int main()
{
int input = 0;
do
{
Menu();
printf("请输入你要的操作");
scanf("%d", &input);
switch (input)
{
case 0:
printf("结束操作\n");
break;
case 1:
Cal(Add);
break;
case 2:
Cal(Sub);
break;
case 3:
Cal(Mul);
break;
case 4:
Cal(Div);
break;
default:
printf("输入错误,请重新输入");
break;
}
} while (input);
}
看起来比上一种写法干净整洁了许多。
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
文章标题:C语言中多种指针相关类型详解-创新互联
分享路径:http://cdiso.cn/article/cciido.html