C语言------指针(2)

时间:2024-03-31 17:55:08

前面已经向大家介绍了指针的一些基本内容,接下来,就在再我来先大家讲解一下指针的其他内容。

1. 数组名的理解

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

在学习数组的过程中,我们肯定会写过以上代码,我们知道 int 是该数组的数据类型,[10] 是该数组的大小,arr是数组名,那数组名代表这什么吗?它除了是数组的名字之外,还有其他意义吗?

接着还是用代码来探索其意义

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("arr=%p", arr);
	printf("arr[0]=p", &arr[0]);
	return 0;
}

我们写出以上代码并运行,分别打印其地址,运行后我们惊奇的发现arr的地址和arr[0]的地址恰好一样,而arr[0]有恰好是首元素。由此。我们得出数组名不仅仅是数组的名字,其也是数组首元素的地址。

那是不是在所有情况下,数组名都是首元素地址吗?

答案肯定不是,有两种情况除外。

(1)sizeof(arr) ,当我们用sizeof()来计算数组的大小时,此时数组名代表的是整个数组。

我们还是用代码来直观感受一下

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz1 = sizeof(arr);
	printf("arr=%d\n", sz1);
	int sz2 = sizeof(arr[0]);
	printf("arr[0]=%d", arr[0]);
	return 0;
}

用sz1来存储arr的大小,用sz2存储arr[0]的大小( 数组中一个元素的大小)。

运行代码

发现sz1的值为40,恰好是整个数组的大小,说明此时数组名代表整个数组。

(2)&arr 当数组名放在取地址操作符后面时,此时数组名也代表着整个数组。

我们还是用代码直接感受

int* p1 = &arr;
int* p2 = &arr[0];
printf("&arr  =%p\n", &arr);
printf("&arr+1=%p\n", arr + 1);
printf("&arr[0]  =%p\n", &arr[0]);
printf("&srr[0]+1=%p\n", &arr[0] + 1);

分别对arr和arr[0]取地址,分别将其存于指针变量p1和p2中

运行代码发现,分别对其加1时,指针p1跳过了40个字节,而指针p2却只跳过了一个字节。

前面我们学过,指针变量的类型绝定了其加1减1,一次能跳过的大小。

所以只有当指针p1指向整个数组时,p1才可能一次跳过40个字节。所以当数组名在&操作符后面时,此时数组名也代表整个数组。

除了这两种情况外,数组名都代表数组首元素地址。

2.使用指针访问数组名

既然数组名可以代表首元素地址,那么就可以用指针变量来保存其地址。

所以当我们用数组名作为一个函数的实参时,其形参可以写成数组的形式也可以用指针变量来接收。

我们还是用代码直接感受一下

int main()
{
	int arr[5] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d ", p + i); //这里arr本身就有 & 含义了,所以不用在家 &
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

 

从上面代码可以看出*(p+i)和arr[i]是等价的。

其实 *(p+i)写成p[i]也是可以运行的,所以说本质上*(p+i)是等同于p[i]的。

有个小技巧,我们可以将 [ ] 看成一个解引用操作符就好理解了。

总结:数组的访问,是以首元素地址的基础上进行的偏移量进行访问的。

3.一维数组传参的本质

当我们将一个一维数组传递给一个函数的时候,本质上如何传递的呢?接下来探讨一下。

以代码为例

void test(int arr[])
{
 int sz2 = sizeof(arr)/sizeof(arr[0]);
 printf("sz2 = %d\n", sz2);
}
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int sz1 = sizeof(arr)/sizeof(arr[0]);
 printf("sz1 = %d\n", sz1);
 test(arr);
 return 0;
}

 

从代码运行结果发现,sz1的值和sz2的值并不相同。这是为什么呢?

哦,原来当我们将一个数组名传过去的时候,其实是传过去了一个地址,则形参的类型就是一个指针类型,所以当我们在test()函数里面计算arr的大小时,其实并不是计算数组的大小,而是计算了一个指针的大小。正因为函数部分参数是一个指针,所以导致我们无法在函数内部计算数组的大小。

 总结:一维数组传参时,形参部分可以写成数组形式,也可以写成指针形式。

4.二级指针

既然变量都有属于自己的地址,那么指针变量也有属于它自己的地址,那要用什么来存储指针变量呢?

那便是二级指针。

这就是一个二级指针和一个一级指针的关系图

当我们对二级指针进行解引用,得到的是一级指针的地址,再对二级指针进行一次接应用,我们就可以得到一级指针里面保存的内容。

5.指针数组

紧接着我们再来将一个特别的数组---------指针数组。

通俗易懂,指针数组就是一个用来存放指针的数组。

 

6.指针数组模拟二维数组

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* parr[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

分析代码:

首先我们要清楚,二维数组本质上可以看成是有多个一维数组组成的,这里我们将每个数组的数组名都存储在一个指针数组中,其中arr1,arr2,arr3 分别是指针数组中的第1个,第2个,第3个元素,前面我们提到 [ ] 可以看成解引用操作符。假如我们对parr解引用一次,便得到了arr1,也就是arr1的首元素地址,在对其进行一次解引用,就进一步的到了arr1里面的内容。

简单来说,parr[1]就等于arr1,parr[1][i]就相当于arr[1][j]。后面的以此类推。

代码运行如下

以上就是我对数组名的理解。谢谢大家的观看。