数组与指针
1. 两者在什么情况下是不同的
声明与定义时,两者并不等价
下面代码无法运行,其本质原因就是定义与声明时,两者并不等价,若要引用外部数组,就必须声明为数组
//文件1
int mango[100];
//文件2
extern int *mango; //❌报错,编译失败
要明白上面的程序为什么失败,首先要懂得程序执行的细节,首先用变量与指针举例:
如图1所示,程序通过a读取内存的值时,只要去a的地址,直接取值就能获取到数据,但是通过*b读取到同样位置的值,首先要去b的地址拿b的值(也就是a的地址),解引用的过程就是去a的地址并获取到地址上存储的值,其中 通过b获取内存中的地址值、通过a获取内存中的数值 这两个过程是一样的,都是通过变量拿到内存中的值,但是通过解引用拿到目标数据的过程,是指针方式独有的、相比于变量取值多出来的操作。
现在将问题换成数组与指针,首先分析正常用指针访问数组的过程:
如图2所示
①通过数组名[下标]的方式访问数组时,只要两步:从数组名a拿到起始地址 → 计算(arr+偏移)目标地址并取值
②通过指针访(或指针[下标])访问数组的过程需要三步:从指针名拿到指针变量b的地址 → 拿到此地址存储的值(数组起始地址) → 计算(起始地址+偏移)得到目标地址并取值
???
表示未知的内存区域存储的数据
理清上述过程,便可以分析最初的代码失败的原因:
文件1中:定义 int mongo[10];
文件2中:声明 extern int *mongo;
由于文件2中声明的 mongo 与文件1中定义的mongo是有本质区别的,所以编译会失败,下面图示是在假设编译通过后,在文件2中使用mongo指针访问数组时的情形;
根据图3 ,由于声明并不会创建新的变量,它所表达的意思是此处声明的mongo就是文件1中定义的mongo,因为文件2中以声明指针的方式表示要用文件1声明的mongo数组。所以如果编译通过,那么就会把文件2中的mongo作为数组名,取mongo[1]的值时将以数组的方式完成读取过程(因为声明的意思就是我要用已有的别处定义好的变量),于是,按照数组访问的流程:首先将mongo的地址(&mongo)当作起始地址,把 (&mongo+偏移) 作为目标数据的地址,然后取了 [mongo+偏移] 地址上对应的值,这显然是错误的,所以上述过程根本不会出现,因为两者根本就是两个东西,编译不通过(理由:在这种情况下,指针与数组是不等价的)
如果,在文件2中声明 extern int mongo[];,这样是可以的,因为这里的mongo和文件1的mongo类型是一样的,用这个mongo访问数组的过程与文件1中的mongo也是一致的。
两者本身的地址并不相同
int mango[100];
int *raisin;
mango数组的定义分配了100个int的空间。
而指针的定义则申请一个地址容纳该指针。指针的名字是raisin,它可以指向任何一个int变量(或int型数组)。指针变量raisin本身始终位于同一个地址,但它的内容可以随意变化,可以指向不同地址的int变量。这些不同的int变量可以有不同的值。mango数组的地址并不能改变,在不同的时候它的内容可以不同,但它总是表示100个连续的内存空间。
区别汇总
指针 | 数组 |
---|---|
保存数据的地址 | 保存数据 |
间接访问数据,首先取得指针的内容,把它作 为地址,然后从这个地址提取数据。 如果指针有一个下标[I],就把指针的内容加 上 I 作为地址,从中提取数据 | 直接访问数据,a[I]只是简单地以a+I为地址取得数据 |
通常用于动态数据结构 | 通常用于存储固定数目且数据类型相同的元素 |
相关的函数为malloc(), free() | 隐式分配和回收 |
通常指向匿名数据(用malloc分配的就是) | 自身即为数据名 |
2. 数组和指针等价的情形
注:同时汇总了不等价的情况以增强对比
-
数组
-
声明与定义
- 🔴声明 extern char a[];不能改写成指针形式
- 🔴定义 char a[10];不能改写成指针形式
- 🟢函数参数 func(char a[]);指针/数组形式都可以
-
在表达式中
- 🟢c=a[i];可以改写成指针形式(你不改编译器也会改)
-
🔴另外,下面三种情况中,对数组的引用不能用指向数组首个元素的指针代替
- 数组作为sizeof()的操作数一显然此时需要的是整个数组的大小,而不是指针所指向的第一个元素的大小
- 使用&操作符取数组的地址。(对数组名操作才能拿到“数组地址(带有数组长度信息”)
- 数组是一个字符串(或宽字符串)常量初始值。
本文是笔者对《C专家编程》第四章内容的个人理解与总结,欢迎指出其中的错误与不足!