C中 数组和指针的异同

时间:2021-02-03 07:36:28

数组在很多情况下是和指针等价的,数组的下标运算和指针的解引用也有等价形式:arr[i] == *(arr + 1);但是也有一些情况下数组和指针是不一样的:extern int arr[]; extern int *arr;

这里持续记录一下指针和数组的异同(以下来自于C专家编程)

首先明确一下几个定义:

  • 声明和定义:C语言中的对象必须有且只有一个定义,但是可以有多个extern声明。
定义 只能出现在一个地方 确定对象的类型并分配内存,用于创建新的对象。例如:int my_array[100]
声明 可以多次出现 描述对象的类型,用于指代其他地方定义的对象(例如在其他文件里)例如:extern int my_array[]
    • 区分定义和声明:
      • 声明相当于普通的声明:它所说明的并非自身,而是描述其他地方的创建的对象
      • 定义相当于特殊的声明:它为对象分配内存
  • 左值和右值(地址和地址的内容):

C中 数组和指针的异同

注:C语言引入了"可修改的左值"这个术语,可修改的左值指的是可以放在赋值语句左边。这个术语是为了与数组名区分,数组名也用于确定对象在内存中的位置,也是左值,但它不能作为赋值的对象。因此,数组名是个左值但不是可修改的左值。

  1. 指针与数组的不同:
    • 编译器为每个变量分配一个地址(左值),这个地址在编译时可知,而且该变量在运行时一直保存于这个地址。相反,存储于变量中的值(它的右值)只有在运行时才可知。如果需要用到变量中存储的值,编译器就要发出指令从指定地址读入变量值并将它存于寄存器中。所以,如果编译器需要一个地址来执行某种操作,它就可以直接进行操作(因为符号的地址在编译时可知),并不需要增加指令首先取得具体的地址。相反,对于指针,必须首先在运行时取得它的当前值(取得指针本身所存的右值),然后才能对它进行解除引用操作。
    • 故:int a[100] 和 extern int a[]都表明a是一个数组,也就是一个内存地址,数组内的字符可以从这个地址找到。从数组提取一个字符,只要简单的从符号表显示的a的地址加上下标,需要的字符就位于这个地址中。相反,如果声明extern int *p,它告诉编译器p是一个指针,它指向的对象时一个字符。为了取得这个字符,必须得到地址p的内容(注意数组a本身就是一个地址),把它作为字符的地址并从这个地址中取得这个字符,相比数组a来说多了一次额外的提取。取a中的字符和p所指的字符见下图:

C中 数组和指针的异同

    • 由上可知,int a[100] 告诉编译器a是一个数组,本身就是一个地址。int *p告诉编译器p是一个指针,p所在的地址中存的是一个int的地址。

下面继续对数组和指针进行解释:"当定义为指针,但以数组方式引用"时会发生什么

C中 数组和指针的异同

    • 这时编译器所执行的是对内存进行间接引用。之所以会如此是因为我们告诉编译器我们拥有的是一个指针。

对照上图的访问方式:
                    char *p = "abcdefg";    ...... p[3]
                    和
                    char a[] = "abcdefg";    ...... a[3]
                    这两种情况都可以取得字符'd',但两者的途径却很不一样。

当写了extern char *p的时候,编译器将会:
                     a.取得符号表中p的地址,提取存储于此处的指针
                     b.把下标所表示的偏移量与指针的值相加,产生一个地址
                     c.访问上面这个地址,取得字符。

    • 考虑如果p被声明为extern char *p;但是它原先的定义却是char p[10];这种情形。当用p[i]这种形式提取这个声明的内容的时候,实际上得到的是一个字符。但是按照上面的方法,编译器确把它当成是一个指针,就会先去取这个地址中所存的地址,但是其实p的地址中现在存的其实已经是一个int型了,吧ASCII字符解释为地址显然是驴唇不对马嘴。相反的,如果p被声明为extern char p[];但是它原先定义却是int *p;这种情形的话,那么p[i],编译器会直接取地址(p + i),显然这也是不对的,因为此时直接把p的地址和i相加其实是一个不存在的地址。
    • 可见数组和指针在这种情况下是不同的。在C语言中,对数组的引用其实引用的是地址本身(不可变左值),但是对指针的引用其实是引用指针所在地址中的内容(右值)。

数组和指针的其他区别

指针 数组
保存数据的地址 保存数据

间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据。

如果指针有一个下标[I],就把指针的内容加上I作为地址,从中提取数组

直接访问数据,a[I]只是简单的一a + I为地址取得数据
通常用于动态数据结构 通常用于存储固定数目且数据类型相同的元素
相关的函数为malloc(),free() 隐式分配和删除
通常指向匿名数据 自身即为数据名

2.数组和指针的相同点:


除了上面的几种不同的情况以外,很多情况下数组和指针是可以等同看待的,例如在作为函数的形参的时候,数组和指针是等价的:void foo(int *); == void foo(int[]);

很多情况下我们使用数组和指针的时候也是非常类似的,例如 arr[n] == *(arr + n)