您的当前位置:首页正文

指针基础知识讲解(3/4)(数组与指针)

2024-11-30 来源:个人技术集锦

数组名的理解

我们在用指针访问数组的首元素时,可以这么写:

int main()
{
	int arr[10] = {0};
	int* p = &arr[0];
	return 0;
}

但其实数组名有着自己的独特的意义,我们来看看下面这段代码:

我们知道,如果是指针的话,其大小为4个或8个字节,那这里为什么会输出40呢?

·sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节;

可以看到,相比于"&arr[0]+1"和"arr+1"只增加了4个字节,"&arr+1"增加了40个字节,刚好是数组中所有元素的大小,这充分说明了&arr取出的是整个数组的大小。

使用指针访问数组

这是我们通常遍历和打印数组元素的方式:

 其实我们还可以用指针进行访问:

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	for (int i = 0; i < 10; i++)
	{
		scanf("%d", p + i);
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}
for (int i = 0; i < 10; i++)
{
	printf("%d ", *(arr + i));
}

也能这么表示:

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

所以我们可以得出结论:arr[i]=*(p+i)=*(arr+i)=p[i]。其实arr[i]这种写法,编译器在执行时,也会转换成*(arr+i)的方式执行,可以理解为数组的底层逻辑时指针。

在这里,我们要理解一个重要的关系,就是:

这有助于我们后续加深对指针的理解.

其实对数组的操作还有一个方式:

没错,我们可以将平常的arr[i]改写为i[arr],因为[ ]只不过是一种操作符,编译器运行的底层逻辑还是将arr[i]或者i[arr]转换成*(arr+i)或*(i+arr)进行操作。这种写法不被推荐,但有助于我们理解底层逻辑。

一维数组传参的本质

我们先来看看下面这段代码并猜测其运行结果:

void test(int arr[10])
{
	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;
}

按照常理,这不就是打印数组的长度吗?只不过一个在函数内完成,一个在主函数内完成,我们来看看结果:

我们由此可以得出以下结论:

1.函数的形参部分是不会真实创建一个和实参等大的数组的,那么我们在形参部分可以不写数组的大小;

2.函数形参部分应当使用指针变量来接收数组

传参(也能写成数组的形式,可以方便我们理解);

3.当我们想通过函数遍历数组的时候,可以把数组的长度作为一个整形变量单独传参。

二级指针

int main()
{
	int a = 10;
	int* p = &a;
	return 0;
}
int main()
{
	int a = 10;
	int* p = &a;//一级指针变量p,存放的是a的地址
	int** p2 = &p;//二级指针变量p2,存放的是指针p的地址
	return 0;
}

可以看到,二级指针里面是有两个“*”号的关于这两个“*”号,我们该怎么去理解呢?我们可以将二级指针这么写:

int* *p2;

"int*"是该指针要指向的数据的类型,即指向一个整形指针变量;而p2前面的那个“*”起到表示这是一个指针的作用。这样二级指针就和一级指针一样好理解了,三级或者更高级指针的理解方式与这个相同。

那么我们对二级指针的解引用就不难理解了:

指针数组

指针数组的概念

int main()
{
	int a = 0;
	int b = 5;
	int c = 6;
	int* arr[] = { &a,&b,&c };//指针数组
	return 0;
}

我们也可以想使用正常的数组一样去使用指针数组:

 用指针数组模拟二维数组

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	//             int* int*  int*
	int* arr[] = { arr1,arr2,arr3 };
	return 0;
}

我们也可以利用这个数组遍历打印二维数组的元素:

根据前文“数组名的理解”中的知识,我们知道此处的arr[i][j]可以视作*(*(arr+i)+j) ,其实这也是二位数组的本质。

数组指针变量

 下面我们来看看数组指针的书写方法:

int main()
{
	char c = 'a';
	char* pc = &c;//pc是一个字符指针

	int num = 10;
	int* pi = &num;//pi是一个整形指针

	int arr[10] = { 0 };
	int (*parr)[10] = &arr;//parr是一个数组指针变量,其指向长度为10整型数组
	return 0;
}

这里的parr是指针的名字,和其他类型指针相同,创建指针时,*号都是加在指针名的前面;另外,这里的*和parr必须用()括起来,如果不括起来,会得到:

int* parr[10] = &arr; 

由于[ ]的优先级是高于*的,*会和前面的int结合,parr会和[10]结合形成数组,整体会变成一个指针数组。

在创建各种类型的变量时,把其名字去掉,留下来的便是其类型:

int main()
{
	char c = 'a';//c的类型是char
	char* pc = &c;//pc的类型是char*

	int num = 10;//num的类型是int
	int* pi = &num;//pi的类型是int*

	int arr[10] = { 0 };//arr的类型是 int [10] 
	int (*parr)[10] = &arr;//parr的类型是 int(*)[10],这也是&arr的类型
	return 0;
}

既然是指针,我们也可以对数组指针进行解引用操作:

在上面那段代码中,parr储存的是&arr,那么*parr就是*(parr)----->*(&arr)----->arr,可以得到arr,即*(parr)就是数组名。

既然是数组名,也可以这样写,效果一样:

int main()
{
	int arr[10] = { 0 }; 
	int (*parr)[10] = &arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*((*parr)+i) = i + 1;//给数组元素赋值
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *((*parr)+i));//遍历打印数组元素
	}
	return 0;
}

二维数组传参的本质

我们先来看一个基本的二维数组传参:

数组的元素都是内置类型的,如果我们把一维数组作为数组的元素,这时候就是二维数组,二维数组作为数组元素的数组就是三维数组,二维数组以上的数组被称为多维数组。所以二维数组的本质其实是:由一维数组组成的数组,它的每个元素都是一个一维数组。

 那么把二维数组的每一行都看作一个元素,它的首元素就是其第一行,或者说是第一行的一维数组。

所以上面的代码也可以这么写:

void PrintArr(int (*p)[5], int x, int y)
{
	int i = 0, 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 };
	PrintArr(arr, 3, 5);
	return 0;
}

形参中的指针p就是一个数组指针,其指向一个长度为5的整形一维数组:

 我们也可以对形参的指针进行解引用来操作数组元素:

显示全文