【C语言 | 指针】指针和数组关系——剪不断,理还乱

博客主页:https://blog.csdn.net/wkd_007
博客内容:嵌入式开发、Linux、C语言、C++、数据结构、音视频
本文内容:介绍C语言指针和数组的关系
金句分享:你不能选择最好的,但最好的会来选择你——泰戈尔

本文未经允许,不得转发!!!

目录

  • 一、指针和数组有什么关系
  • 二、指针和数组名
  • 三、指针和数组访问方式
    • ✨3.1 以指针的形式访问
    • ✨3.2 以下标的形式访问
  • 四、指针和数组的定义和声明
  • 五、指针数组 和 数组指针
  • 六、总结


在这里插入图片描述

一、指针和数组有什么关系

指针和数组有什么关系呢?

C语言中,并没有明确规定指针和数组的关系,也就是说,指针和数组实际上并没有什么关系,只是它们的很多用法很相似,才令很多初学者感到困惑。

定义一个指针变量后,系统会分配一块内存(32位系统是4个字节,64位系统是8个字节)。然后不管这块内存在之后放了什么内容,编译器都会把他当成一个内存地址来处理。指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到。

数组的本质是一块连续内存区域,里面分成若干个相同大小的内存区域,这些小的内存块就是数组元素。数组的大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存任何类型的数据,但不能存函数(但可以存函数指针)。

在这里插入图片描述

二、指针和数组名

数组名就是让初学者混淆指针和数组的一个重要原因。

上篇文章 提到,数组的数组名其实可以看作一个指向数组首个元素的指针。既然是指针,我们就清楚它关于指针的4个方面内容:指针所在的地址、指针的值、指针的类型、指针所指向的类型。

  • 数组名作为指针所在的地址:数组名本身的地址由编译器另外存储,存储在哪里,我们并不知道;
  • 数组名作为指针的值:是数组首个元素的地址,可以通过 printf 打印出来,会等于&a[0]
  • 数组名作为指针的类型:数组元素类型*
  • 数组名作为指针所指向的类型:数组元素类型。

下面看例子2.1,看看是怎样混淆的:

// array_name.c
#include 
int main()
{
	int arr[5] = {1,2,3,4,5};
	int *pa = arr; // pa指针指向 arr数组首个元素
	printf("num1=%d num2=%d unm3=%d\n", *arr, *(arr+1), arr[2]);
	printf("num1=%d num2=%d unm3=%d\n", *pa , *(pa+1) , pa[2]);
	pa++;
	//arr++;  // 保存,数组名的值不能修改
}

pa指针指向 arr数组首个元素之后,下面使用了完全相同的用法,不去看定义的话,都分不清谁是指针,谁是数组。

那有哪些区别吗?数组名是常量指针,它不能作为左值使用,它的值无法被修改,所以不能使用arr++。

在这里插入图片描述

三、指针和数组访问方式

✨3.1 以指针的形式访问

指针的形式访问内存,就是指利用指针加减运算来访问内存空间。

指针和数组都可以用指针的形式来访问内存空间,但不同的是,指针访问的是指针值所指向的内存空间,而数组访问的是自己的内存空间。

看例子3.1.1:

// array_access.c 
#include 
#include 
int main()
{
	int arr[5];	// 定义一个数组
	int *pa = (int*)malloc(5*sizeof(int));// 定义一个指针指向malloc分配的内存
	
	// 数组的指针形式访问:
	int i=0;
	for(i=0; i<sizeof(arr)/sizeof(*arr); i++)
	{
		*(arr+i) = i;
		printf("*(arr+%d)=%d, ",i,*(arr+i));
	}
	printf("\n\n");
	
	// 指针以指针形式访问:
	for(i=0; i<5; i++)
	{
		*(pa+i) = i;
		printf("*(pa+%d)=%d, ",i,*(pa+i));
	}
	printf("\n");
	free(pa);
	return 0;
}

例子中,数组arr利用数组名是数组首元素地址的特性,使用指针运算完成了对数组内容的修改和读取。
指针pa定义时指向了malloc函数分配的内存,内存大小5*sizeof(int),然后使用使用指针运算完成了对该段内存空间的读写。

✨3.2 以下标的形式访问

我们初学C语言时,就知道数组允许以下标的形式来表示数组元素,arr[0]表示首个数组元素,arr[0]表示第二个数组元素,其他依此类推。但有点惊讶的是,指针也可以使用下标的形式访问内存空间。

看例子3.2.1:

// array_access2.c 
#include 
#include 
int main()
{
	int arr[5];	// 定义一个数组
	int *pa = (int*)malloc(5*sizeof(int));// 定义一个指针指向malloc分配的内存
	
	// 数组以下标形式访问:
	int i=0;
	for(i=0; i<sizeof(arr)/sizeof(*arr); i++)
	{
		arr[i] = i;
		printf("arr[%d]=%d, ",i,arr[i]);
	}
	printf("\n\n");
	
	// 指针以下标形式访问:
	for(i=0; i<5; i++)
	{
		pa[i] = i;
		printf("pa[%d]=%d, ",i,pa[i]);
	}
	printf("\n");
	free(pa);
	return 0;
}

实际上,编译器总是把以下标的形式的操作解析为以指针的形式的操作。
pa[i]这个操作会被解析成:先取出 pa 里存储的地址值,然后加上中括号中 i 个元素的偏移量,计算出新的地址,然后从新的地址中取出值。也就是说以下标的形式访问在本质上与以指针的形式访问没有区别,只是写法上不同罢了。

在这里插入图片描述

四、指针和数组的定义和声明

首先,复习一下,定义会分配内存的,而声明没有。定义只能出现一次,而声明可以出现多次。

前面说过,数组名可以看出数组首元素的地址,那么,在文件1定义数组char a[100];,在文件2声明为指针extern char *a;,这样的做法可以吗?
答案是不行的,因为数组和指针有着本质的区别,这里定义数组时分配了一块100个字节内存,并命名为a;而声明为 extern char *a 时,编译器理所当然的认为 a 是一个指针变量,在 32 位系统下,占 4 个 byte。这 4 个 byte 里保存了一个地址,这个地址上存的是字符类型数据。
总结:定义为数组,不能将该数组名声明为指针,但可以在声明数组时,去掉第一维的数组长度,因为声明不分配内存,只要求类型匹配。

定义为指针,声明为数组也是错误的。因为类型不匹配。

看例子4.1:

// array_extern1.c
char a[100];
char *p = "abcdefg";
int ai[10][100];
// array_extern2.c
#include 
//extern char *a; // 错误
extern char a[];

// extern char p[]; // 错误
extern char *p;

// extern int ai[][]; //error: array type has incomplete element type(数组类型具有不完整的元素类型)
extern int ai[][100];
int main()
{
	printf("%p %p %p\n",a, p, ai);
	return 0;
}

在这里插入图片描述

五、指针数组 和 数组指针

指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是储存指针的数组的简称。

数组指针:首先它是一个指针,它指向一个数组。在 32 位系统下永远是占 4 个字节,至于它指向的数组占多少字节,不知道。它是指向数组的指针的简称。

定义一个指针数组p1,根据右左法则,p1先跟[]结合,是个数组,然后再跟*结合,说明数组每个元素都是指针:

int *p1[10];

p1的内存布局如下:
【C语言 | 指针】指针和数组关系——剪不断,理还乱_第1张图片

定义一个数组指针p2,*p2 被括号括起来,改变优先级,p2先与*结合,说明p2是个指针,然后再和[]结合,说明p2指向一个数组,数组元素是int类型的,有10个数组元素。

int (*p2)[10];

p2的内存布局如下:

【C语言 | 指针】指针和数组关系——剪不断,理还乱_第2张图片

在这里插入图片描述

六、总结

本文介绍指针和数组的联系和区别:指针和数组名、指针和数组访问方式、指针和数组的定义和声明、指针数组和数组指针。

在这里插入图片描述
如果文章有帮助的话,点赞、收藏⭐,支持一波,谢谢

你可能感兴趣的:(C语言,c语言,指针,数组,数组名,指针数组,数组指针)