C 指针进阶--数组指针 指针数组 函数指针及传参

文章详细解析了数组指针的概念,指明了它们与普通指针的区别,以及在函数参数传递中的应用,包括数组指针作为形参、指针数组和函数指针的使用实例。
摘要由CSDN通过智能技术生成

引导图

正文

1.数组指针

先不讨论什么是数组指针,举个例子

int* p1;
//p1是整型指针,它指向的是整型,指针类型是int*

char* p2;
//p2是字符指针,它指向的是字符,指针类型是char*

注意!!!为什么我写成了int* p1而不是int *p1,其实两种写法都可以,只不过为了方便理解,把int*看成一种类似于int,char,float的变量类型,同理char*也是如此,后文不再阐述。

So数组指针就是指向数组的指针!

int arr[5]={1,2,3,4,5};
//数组名arr相当于数组首元素arr[0]的地址,+1跨越了一个数组元素
//但是有一个例外,sizeof(数组名)计算的是整个数组的大小,而非一个数组元素的大小
//&arr才是数组真正的地址,+1跨越了一个数组

int (*parr)[5];//取数组地址&arr赋给数组指针parr
//因为*parr是带括号的!!所以*parr一体,所以把*parr拿掉,剩下int [5],左边表示元素类型int,右边表示元素个数[5],这就是数组指针的本质。

//具体如何理解呢?
//因为原数组arr有5个元素,所以数组指针parr指向的数组元素个数也得为5,写作[5],
就好比5个人乘车,车上必须有5个座位,如果有4个,总不能一个人坐车顶吧。

//而且,原数组arr元素皆为int,所以数组指针为int型,
那么在上句的基础上写作int [5],最后再加上(*parr),就有了int (*parr)[5]。
当然,若原数组arr元素为int*,那么数组指针写成int* (*parr)[5];

记住,它只是一个数组指针,只负责指向一个数组,至于数组的元素类型是int还是int*,元素个数是[5]还是[6],都是它所指向的数组决定的,而非指针决定,以下所有内容均可这样理解。至于为何写成(*parr)而不是*parr,这和符号优先级有关,看完下文的“指针数组”就明白了~

1.1.数组指针作为形参接收二维数组

void test1(int arr[][3])
{函数体}

void test2(int (*parr)[3])
{
for(int i=0;i<2;i++)
    {	
        for(int j=0;j<3;j++)
        {
		    printf("%d ",*(*(parr+i)+j));
	    }
	    printf("\n");
    }

void test3(int* arr)
{函数体}

void test4(int* arr[3])
{函数体}

void test5(int** arr)
{函数体}

int main()
{
    int arr[2][3]={{9,8,7},{6,5,4}};
    test1(arr);    //√
    test2(arr);    //√
    test3(arr);    //×
    test4(arr);    //×
    test5(arr);    //×
}

test1中,二维数组作形参接收二维数组参数,没毛病;在test2中,首先要说明的是,二维数组作为参数传参,传过去的是二维数组的第一行的地址(看个人理解),也就是parr里面放的是{9,8,7}这个一维数组的地址。我们让其+i就是第i行的地址,然后*(parr+i),把第i行的地址取出来,再+j,此时得到第i行的第j个元素的地址(*(parr+i)+j),再对其解引用*(*(parr+i)+j)得到第i行第j个元素的值。至于test3,形参是整型指针,而实参是一个一维数组的地址,要用数组指针接收才行,错。test4,形参是整型指针数组,用来接收整型指针,而我们的实参是一维数组地址,驴唇不对马嘴,pass。test5,形参是二级指针,用来接收一级指针变量的地址或者二级指针,同样驴唇不对马嘴,pass。

2.指针数组

同上,先不讨论什么是指针数组,举个例子

int arr1[5]={1,2,3,4,5};
//这是 整型 数组,存放整型元素

char arr2[5]={'a','b','c','d','e'};
//这是 字符 数组,存放字符型元素

So指针数组就是存放指针的数组!

int arr[5]={1,2,3,4,5};
int* p;
int m;
char* k;

int* parr[3]={arr,p,&m};
//* parr没有带括号!!那么只把parr拿掉,只剩int* [3],和数组指针一样,左边表示元素类型int*,右边表示元素个数[3],这是指针数组的本质。
//当然还可以写成int* [4],只要≥3就行,因为parr至少要存放3个元素
//注意!!! k是字符型指针,无法存放到整型指针数组中

//如何理解?
//首先,arr和&m是地址,p是指针,一共3个元素全放到指针数组parr里,就有了元素个数[3]
//其次,arr和&m以及p都是int*(注意,m是int,&m取了地址就是int*,而arr本身就是地址),所以指针数组元素类型为int*,于是有了int* [3],数组取名为parr

没错,只要是int*类型的都可以放到数组parr里,因为parr就是存放整型指针的数组

不用管数组arr里面几个元素,我们已经在parr里面放了数组arr的首元素地址,那么就可以借此访问数组arr的所有元素。

3.指向指针数组的数组指针

顾名思义,这是一个数组指针,只不过它指向的是指针数组,而这个数组里面存放的是指针。

它长个什么样呢?

//6个指针数组的元素
int *p0,*p1,*p2;
int p3,p4;
int p5[]={7,8,9,10};

//2个指针数组
int *pa[3]={p0,&p3,p5};
int *pb[3]={p1,p2 ,&p4};

//OK,接下来我要用 数组指针 指着这俩 指针数组 ~
int* (pc[2])[3]={pa,pb};//pc就是指向指针数组的数组指针

//如何理解呢?
//首先pc[2]带了括号,那就把pc[2]拿掉,剩下int*  [3],这不就是个指针数组吗?左边元素类型int*,右边数组元素个数[3].
//当然可以写成int* [4],总之>=3就行,因为数组pa和pb元素个数都为3,至少得放得下3个
//如果再加上pc[2],那就是说明我有俩指针pc[0]和pc[1],分别指向两个数组pa和pb,pa和pb各有3个元素
//当然pc[2]可以写成pc[3],只要>=2就行,因为数组指针pc至少要指向2个指针数组pa和pb

4.存放数组指针的指针数组

//2个数组
int p1[3]={4,5,6};
int p5[3]={7,8,9};

//2个数组指针
int (*pa)[3]=p1;
int (*pb)[3]=p5;

//1个指针数组存放2个数组指针
int* (pc[2])[3]={pa,pb};//pc就是存放数组指针的指针数组

//如何理解?
//老规矩,pc[2]带了括号,是一体的,把pc[2]拿掉,剩下int* [3],
//左边就是元素类型int*,右边是数组指针指向的数组的元素个数[3],
//当然还可以写成int*  [4],总之>=3就行,因为数组p1和p5最多3个元素,至少得放得下3个;
//同理pc[2]可以写成pc[3],>=2就行,因为至少要存放2个数组指针pa和pb。

5.函数指针

5.1.函数指针作形参接收参数

先看看这个qsort函数,是C语言库函数之一,用于对任何元素类型的数组排序。它有4个形参,第一个是要排序数组的地址,第二个是数组元素个数,第三个是数组元素的字节大小,也就是跨越一个元素的步长,第四个就是本节重点,一个函数指针,用来接收数组排序所需的排序函数,比如冒泡,选择排序...

 OK,请看下面的代码:

#include<stdio.h> 

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

int sub(int x,int y)
{ return x-y;}

int Calc(int (*p)(int,int))//函数指针作为形参接收函数地址
{
	int x=0,y=0;
	printf("请输入两个操作数:>");
	scanf("%d %d",&x,&y);
	return (*p)(x,y);//p(x,y)也可以 
}
 
int main()
{
	int ret=Calc(add);//把add函数地址传过去,用函数指针接收
    printf("加法运算 %d\n",ret);//结果是8
    ret=Calc(sub);
    printf("减法运算 %d\n",ret);//结果是-2
    return 0;
}

运算结果如下:

这就是函数指针。。。

5.2.函数指针接收函数地址

如图:函数名本身就是函数的地址,和&函数名效果相同,*ptr说明其是指针,int (*ptr)说明函数的返回值是int,而(int , int)则是函数add的形参。当然,函数指针也可以和数组指针一样放在指针数组中,比如:

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

int sub(int x,int y)
{ return x-y;} 

int main()
{
    int (*p1)(int,int)=add;
    int (*p2)(int,int)=sub;
    int (*p[2])(int ,int)={p1,p2};//两个函数指针放到函数指数组里
    (*p1)(3,5);//结果是8
    (*p2)(3,5);//结果是-2
}

当然我还可以再找一个指针指向这个函数指针数组,然后把这个指针再放到指针数组里,这样无限套娃.....

玩点好玩的

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

这个出自《C语言陷阱与缺陷》这本书,作用是调用首地址为0地址的子例程。首先0,用(void(*)())

把它转化成函数指针,也就是(void(*)()) 0,然后对这个函数指针调用(* (void(*)())  0) ( ),显然形参为空。看解释:

//调用0地址处的函数
//该函数无参,返回类型是void
//1.void(*) () ---函数指针类型
//2.(void(*) ()) 0 ---对0强制类型转化,成为一个函数地址
//3.*(void(*) ()) 0 ---我TM直接对地址解引用
//4.(*(void(*) ()) 0) () ---调用0地址处的函数

OK,本文到此为止,NND写了我四五个小时....

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值