C语言指针详解——后篇

目录

一、指针与数组

​编辑

二、数组传参

2.1一维数组传参

2.2二维数组传参

三、函数指针及应用

四、函数指针数组


一、指针与数组

先来看以下代码及运行的结果

#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    return 0; 
}

 这说明,数组名所表示的含义就是数组首元素的地址。

但是在两个操作符下,数组名表示的不是首元素地址,而是整个数组。

sizeof(数组名),此时数组名表示整个数组,计算得出的结果是整个数组的大小

&数组名,此时数组名也表示整个数组,取出的是整个数组的地址

除此之外,一般数组名就是首元素地址,因此,可以建立指针与数组的联系,并利用指针对整个数组进行遍历或修改等一系列操作

int arr[5] = {1,2,3,4,5};
int* p = arr; // p中存放首元素地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(int i = 0 ; i < sz ; i++)
{
    priintf("%d ", *(p+i)); //遍历数组
}

指针p首先指向数组第一个元素,再通过+i的方式向后跨越相应的步长,再解引用得到对应下标的元素

指针遍历操作也可以用下一种方法表示

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

而我们最初遍历数组时,最常用方法的就是

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

对比上面两段代码,很可能会不太理解为什么指针p能够用[]下标引用操作符来访问数组成员,但是实际上,正如上文所述,一般情况下,数组名表示数组的首元素,也就是说,数组名本身也是一个指针,指向数组首元素。既然数组名这个指针可以使用[],指针p当然也可以使用[]来访问其成员。

二、数组传参

2.1一维数组传参

一般情况下,我们不会在主函数内对数组进行修改,而是调用其他函数来完成,将数组作为参数传递给函数。为了简单,以下均以遍历数组为例

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

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

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,0};
	int sz = sizeof(arr) / sizeof(arr[0]);
	print1(arr, sz);
    printf("\n");
    print2(arr, sz);
	return 0;
}

如上文所述,数组名本质是一个指向首元素的指针,所以在print函数中,需要用一个指针来接收数组,如函数print1。也可以像print2那样,创建一个名为pa的数组,[]内可以填任意正数(写太大也会报错的),但是填写数字没有意义。

调用print2上面这段代码的处理是,创建一个int* 类型的名为pa的指针,指向实参数组的首元素地址,和调用print1时一样。所以[]内没必要填入任何数字,没有意义。

数组传参时接收用print1和print2中两种方法都可以,一种更体现本质,一种更容易理解,具体用那种看个人喜好。

2.2二维数组传参

所谓二维数组,其实是多个一维数组拼接起来形成的。

二维数组的数组名也是一个指针,也指向数组中的首元素,但是指针类型是一个数组指针。上代码

int arr[3][3] = {1,2,3,4,5,6,7,8,9};
int (*p)[3] = arr;
//第二行代码中必须加括号
//因为[]操作符的优先级大于*
//不加括号会导致p成为一个指针数组。

这里的arr是一个二维数组,由三个容量为3的一维数组拼接而成。

数组名表示首元素地址。这里的首元素不是1,而是第一行的整个一维数组地址。即arr为指向一个容量为3,元素类型为int的数组的数组指针。如下图所示

知道了二维数组数组名表示的是什么,就可以用相应类型的变量来接收它,仍以遍历为例

void print1(int (*p)[3], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", *(*(p+i)+j) );
		}
	}
}

void print2(int p[][3], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", p[i][j]);
		}
	}
}

int main()
{
	int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
	print1(arr, 3, 3);
	printf("\n");
	print2(arr, 3, 3);
	return 0;
}

对一个三行三列的二维数组进行遍历。

print1中,*(p+i)先定位到行,如当i为0时,定位到二维数组第一行,之后  *( *(p+i) + j),再定位到列。一开始,p是一个数组指针,其步长为所指一维数组的大小,因此对p+i后会跨越一行。在第一次解引用后,p是一个int类型的指针,指向某行的第一个元素,对其+j即可确定列。这样,p就可以指向 i+1 行 j+1 列的元素(因为数组下标是从0开始,所以要+1),再对其解引用,即可获取该元素

print2要好理解的多,p[i][j],先由 i 确定行,再由 j 确定列,然后获取元素。需要注意的是,参数中的int p[][3],后面[3]中数字不可省略且必须与实参一致,因为这关系到指针的步长。前面[]中的数字无意义,不需要填写

如print2这样写读起来也比较方便,推荐二维数组操作时像print2这样用下标进行访问,可读性强

三、函数指针及应用

函数也有其地址,也可以用&函数名取出函数的地址,但是不加&符,一样会得到函数的地址

void f(int* a)
{
    *a = 10;
}

int main()
{
    printf("%p\n",f);
    printf("%p\n",&f);
}
//二者打印完全一样,没有区别

这里不能类比数组。对函数而言,获取其地址时,这两种方式的意义完全一样,没有区别

还要说明的是,函数指针p在使用时,加*与不加*效果也完全一样,看如下代码及结果,编译器没有提示错误。

void f(int* p)
{
	*p = 10;
}

int main()
{
	void (*p)(int*) = f;
	int a = 0;
	p(&a);
	printf("第一次调用后a = %d", a);
	a = 0;
	(*p)(&a);
	printf("第二次调用后a = %d", a);
}

 甚至于可以这样写

void f(int* p)
{
	*p = 10;
}

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

对于函数名来说,加*或加&与否都无所谓,摆设而已。但是*可以无限加,&只能加一个,因为两个&&就是按位与了

函数指针的应用是函数回调,如在c语言内置库函数快速排序qsort中,就用到了函数回调,下面是快速排序的函数声明

 第一个参数是待排序数组,第二个参数是数组元素个数,第三个参数是数组单个元素大小,第四个参数就是函数指针

这个函数指针是一个指向返回值为int,参数为两个const void*的函数。

这里需要的是一个比较函数,当返回值为正数时,p1会排到p2前,返回负数时,p1排到p2后,返回0时,不动。

所以,我们要排序一个数组,可以这样来调用

int cmp(const void* p1, const void* p2)
{
    return (*(int*)p2) - (*(int*)p1);  //降序
    //return (*(int*)p1) - (*(int*)p2); //升序
}

int main()
{
    int arr[9] = {2,321,41,1,45,6,78,43,2};
    qsort(arr,9,4,cmp);
}

cmp函数之所以会用const void*做参数,是因为这种类型可以接收所有指针,可以比较更多类型的元素,不会局限于某一种。传入参数之后,我们对p1,p2进行相应的类型转换,再解引用即可得到其原本的数值

这样,我们就可以得到降序排列的数组,如果修改cmp为注释掉的返回方法,则会得到升序排列的数组。快排的具体实现相对复杂一点,这里先简单介绍用法。

快排的内部就是调用了我们的cmp函数,通过函数指针的方式来回调cmp函数,从而知道如何对元素进行排序,即排升序还是降序。

函数指针并不是很常用,一般做到认识即可

四、函数指针数组

数组可以存放相同类型的元素,当然也可以用来存放函数指针,只不过这个用法也少见,了解一下即可

int (*parr1[10])();

这是一个容量为10,存储返回值为int,参数为空的函数指针的一个函数指针数组,听起来有点绕。换一种方法,去掉数组名parr1,及容量[10],还剩下int (*) (),前面的int是返回值类型,后面的()是参数列表,通过这种方法可以相对容易的判断所存储的函数指针类型。

函数指针数组也叫转移表,其用途之一是计算器,若是感兴趣可以查一下,因为转移表本身不常见不常用,在此就不写了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值