数组名和指针详解

来源:互联网 发布:区间交易软件 编辑:程序博客网 时间:2024/04/28 14:44

首先要说的是,数组名不是指针。

 

我们来看数组int test[3],这里test[3]是整型,test这个数组名的值是一个指针常量,也就是数组第一个元素的地址。总结一下:

数组名的类型(如int test[3])就是“指向某类型(int型)的常量指针”。

只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。

 

但是数组名和指针是不相同的。

 

我们先来看一个程序

int test[3] = {1, 2, 3};

cout << sizeof(test) << endl;

输出的答案是:12。这个数是3 * sizeof(int) = 3 * 4 = 12得到的。如果test是一个数组的话,输出应该是4才对。这表明,数组名不是指针。

 

根据《C和指针》这本书的论述的第2条可以解释上面的问题。

指针与数组2(c和指针.P141.)

·1、数组的名的值是一个指针常量,不能试图将一个地址赋值给数组名;

·2、当数组名作为sizeof操作符的操作数时,sizeof(arrayname)返回的是整个数组的长度,而不是指向数组的指针的长度;

·3、当数组名作为单目操作符&的操作数,取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针。

其实看这第2条,只是表述了现象,没有上升到抽象理论。

 

我这里就有这种理论的表述:

(1)数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组;

(2)数组名的外延在于其可以转换为指向其指代实体的指针,而且是一个指针常量;

(3)指向数组的指针则是另外一种变量类型(在WIN32平台下,长度为4),仅仅意味着数组的存放地址!

上面第一条说的很透彻,sizeof通过数组名看到了数组的数据结构,因此输出了12。

 

结合第二条的知识,   

int a[5]= {1,2,3,4,5};int *ptr1=(int *)(&a+1);nt *ptr2=(int *)(a+1);printf("%x, %x\n", ptr1[-1], *ptr2);// 输出5,2int *ptr1=(int *)(&a+1);int *ptr2=(int *)(a+1);


ptr1定义时,用的是&a,编译器产生了一个指向数组的指针,通过&数组名反映了数组的数据结构,因此&a + 1就一下子到了数组最后一个元素的后一个元素,以为这里的加1是1 * sizeof(a)。因此用ptr1[-1]才能访问到数组的最后一个元素,这里的加1是1 * sizeof(int)。

 

与之相比,ptr2定义时,使用的是a + 1,编译器产生了一个常量指针,指向第一个元素,加1后指针指向了第二个元素。

 

于是,得到了输出5,2。

 

下面我们接着看《C和指针》第三条,当数组名作为&的操作数时,数组名也不是指针常量了。也就是说,遇到sizeof和&数组名都不是指针常量,而是指向数组的指针。

 

下面我们要深入一点,来看看数组名作为&的操作数时,数组名为指向数组的指针的情形。

要看这个例子,我们先要明白:“指针数组”和“数组指针”

这两个概念其实不难,

“指针数组”是一个数组!数组的元素是指针。

“数组指针”是一个指针!指向的是数组。

 

好了知道这个了我们来看例子

int (*ptr)[3];  // 数组指针

int *ptr[3];    // 指针数组

我们要注意:[]下标运算符的优先级高于*解引用操作符,(解引用的英文是dereference,就是取值的意思,深入讨论这个奇怪名字的话可以参见,http://topic.csdn.net/u/20080704/08/93ccd5b3-7dc8-47b9-8574-c31b80954fc9.html)

 

我们知道优先级了,第一个定义int (*ptr)[3];  就是先进行解引用,意思就是定义一个指针,指针的类型是int [3]即ptr是一个指针,它指向有3个元素的数组。要习惯int [3]这种表示类型的方式,后面分析时要用到!

 

第一个定义int *ptr[3];    // 指针数组  意思是先进行ptr[3]操作,声明了一个数组,数组存的变量的类型是int *即数组存放的变量是int型指针。

 

下面就要开始看数组名作为&的操作数时,数组名为指向数组的指针的情形了。

int (*iPointer)[3]; // 数组指针
int test[3] = {1, 2, 3};
iPointer = &test;
cout << iPointer[0][1] << endl;

输出内容为:2。

 

我们要讨论的是iPointer = &test;这种定义方式,不是说数组名的类型就是“指向某类型(int型)的常量指针”吗?数组名是一个指针常量,也就是数组第一个元素的地址的吗?

 

为什么iPointer = test这样不行呢?

我们发现

cout << test << endl;

cout << &test << endl;

cout << &test[0] << endl;

的数值都是一样的,就是数组第一个元素的地址,即数组的首地址。

那么,为什么iPointer = test这样不行呢?从数值上来说,iPointer就是要存储数组的首地址呀。

 

我们来看,如果是不加&那么报错内容是 cannot convert 'int [3]' to 'int (*)[3]' in assignment。

意思是说,iPointer这个指针的类型是int [3],它指向一个含有3个元素的数组!。而test的类型是一个指向int的常量数组,它只指向一个元素。也就是说,数值对了,iPointer指向了一个数组,test指向了一个元素,类型不匹配。

因此,我们可以得知

iPointer = &test[0]; 

也是不对的。报错相同。

 

我们可以这样修改:

强制转换一下就对了:

iPointer = (int (*)[3])test;

 

这个错误其实没有说穿本质,iPointer需要的右值类型是“指向一个数组”的指针,test这个数组名的值是一个指向数组第一个元素的指针,类型不匹配,而当数组名遇到sizeof和&时,产生的是一个指向数组的指针,不是一个指向指针常量值的指针(指针的类型不是指向第一个元素,而是指向全数组即int (*)[3])

 

因此,我们可以改成

iPointer = &test;

我们回过头来看:

int *iter;int test[3] = {1, 2, 3};iter = test;cout << iter[2] << endl;


这是正确的,因为test的类型是指向int的常量指针,与iter相符。

 

指针数组做参数举例

#include <stdio.h>#include <iostream>#include <string.h>#include <stdlib.h>using namespace std;void printMany( char *strings[] )//指针数组,数组的元素是指针,[]优先级高于*{    int i = 0;    while( strings[i] != NULL )    {        printf("%s, ", strings[i++]);    }    printf("\n");}void printMany2( char *strings ){    for (int i = 0; i < 7; i++)    {        printf("%c", strings[i]);    }}void strsort(char *s[],int n)// 排序{     int i,j;     char *t;     for(i=0;i<n-1;i++)         for(j=i+1;j<n;j++)         {             if(strcmp(s[i],s[j])>0)             {                 t=s[i];                 s[i]=s[j];                 s[j]=t;             }          }}int main(){    char strings2[] = "abcdef";    char *strings[] = {"C","Basic","Foxpro","Visual Studio", NULL};  // 指针数组,里面存的内容是指针,每个指针指向一个字符串    //strings = strdup(strings2);    printMany(strings);    printMany2(strings2);    cout << sizeof(strings)/4 << endl;   // 求指针数组中含有的指针的个数    strsort(strings, sizeof(strings)/4 - 1);    printMany(strings);    //printf("%X, %X", strings2, &strings2);    //free(strings);    return 0;}