数组与指针关系

来源:互联网 发布:steam免费mac游戏推荐 编辑:程序博客网 时间:2024/06/05 02:32

1.数组并不是指针

    首先,对数组的引用总是可以写成对指针的引用,这是数组极其普遍的用法。但并非所有情况都可以这样。

    访问方式:

     编译器为每个变量分配一个地址,该地址在编译时可知,存储于变量中的值,在运行时可知,数组是不可修改的左值,其地址在编译时可知,因此不需要增加指令取地址,但对于指针,首先必须在运行时取得其当前值,然后才能对其解除引用操作。

    对于数组元素的引用,只要获取数组地址,加上相应的偏移即可。对于指针,首先要在根据相应符号的地址取得该地址处的内容,再取内容所指的内容。也就是说,数组的地址,在编译器中一直可知,但指针的地址,需要编译器取得符号表P的地址,提取存储于此处的指针。

    字符串常量初始化:

两者都可以,但实现机制不一样。

      定义指针时,编译器只是为该指针分配地址,除非在定义同时给该指针一个字符串常量初始化,否则不会为所指向的对象分配空间。且,只有字符串常量才可以这样。在ANSI C中,初始化指针时所创建的字符串常量被定义为只读,如果试图用指针修改这个字符串的值,就会出现未定义的行为,在有些编译器中,字符串常量被放在只允许读取的文本段,以防止被修改。

    数组也可以用字符串常量初始化,其中字符可以在后期修改。

对于编译器而言,一个数组就是一个地址,一个指针就是一个地址的地址。

2.数组和指针可以互换

C语言标准要求编译器必须具备对数组的引用在编译时改写成指针的形式的能力。编译器可以自动计算偏移的实际步长,这就是为什么指针总是有类型限制。

有一种说法,在编写数组算法时,使用指针比使用数组更有效率,这个颇为人们所接受的说法在通常情况下时错误的,在现代的产品质量优化的编译器,一维数组和指针引用所产生的代码并不具有显著的差别,数组的下标是定义在指针基础上的,所以优化器常常把它转换为更有效率的指针表达式,并生成相同的机器指令。

     实际上,如果一个经过良好优化的编译器进行代码分析,并把基本变量放在高速寄存器中来确认循环是否继续,那么最终在循环访问指针和数组所产生的代码很可能是相同的,在处理一维数组时,指针并不见的比数组快,C语言把数组下标改写成指针偏移量的根本原因是指针和偏移量是底层硬件所使用的基本模型。

    对于数组和指针相同的地方,只是在表达式中,数组和指针可以互换。因为他们最终都被编译器改写为指针形式,都可以进行取下标操作。就像加法一样,取下标操作符的的操作数顺序是可以互换,即a[6]和6[a]都可以取同一个内容。只是6[a]这种形式不用而已。

   另外,作为新参传的数组名和指针也是等价的,这主要是处于效率的考虑。

3.多维数组

    实际上,C语言只支持数组的数组,术语上称为多维数组。

指向字符串的一维指针数组

可以通过声明一个一维指针数组,其中每个指针指向一个字符串来取得类似二维字符数组的效果。例如:char *array[4];

用于实现多维数组的指针数组有多种名字,如Iliffe向量,display,dope向量。这种数组必须用指向为字符串而分配的内存的指针进行初始化,可以为每个指针分配内存,也可以一次性分配,如p[i][j],一次性分配方法:

malloc(x * y * sizeof(T)),然后使用一个循环,用指针指向这块内存的各个区域,整个数组能够保证在连续的内存中,这种方法减少了malloc的开销,但无法单独释放一个字符串。

   Iliffe向量,用来处理存储各行长度不一样的表以及一个函数调用中传递一个字符数组,如果声明一个字符串指针数组,并根据字符串的长度分配内存,将大大节省系统资源,有人把这个数组称为锯齿状数组。例如:

char *turnip[10];

char my_string[] = "hello";

turnip[i] = &my_string[0];

但是,Iliffe向量可能会使分配的字符串在不同的页面中,这就违反了局部引用的原则,并导致频繁的页面交换,具体如何取决于怎样访问数据以及访问的频度。

数组名被改写成一个指针参数,并不是一个递归定义,即数组的数组被修改为数组的指针,而不是指针的指针。

在C语言中没有办法向函数传递一个普通的多维数组。因为我们需要知道每一维的长度,以便为地址运算提供正确的单位长度,在C语言中我们没有办法在实参和形参之间交流这种数据,因为它在每次调用时会改变。因此必须提供除了最左边一维以外的所有维长度。也就是说,你定义的函数只能为:

T fun(T array[][2][2]);这样除了最左侧的一维其他长度固定的特殊多维数组,进行如下调用:

T arrayTmp[2][2][2];

fun(arrayTmp);

如果除最左边的一维其他维长度与形参不一样,编译不通过。

但上面的方法不能满足一般情况,因此,将多维数组传递给函数,有以下几种:

方法1.

直接传递,my_fun(T my_array[2][2]);即只能传递特殊的多维数组。

方法2.

省略一维长度,

my_fun(T my_array[][2]);也可以my_fun(T (*my_array)[2]);

方法3.

放弃二维数组,将其构建成Iliffe向量,my_fun(T **my_array);

注意:只有把二维数组改为一个指向向量的指针数组的前提下才可以这样。

因为字符串和指针都有一个显式的边界NUL和NULL,可以作为结束标记。因此,必须是指针数组,必须是指向字符串的指针数组。

方法4.

将多维数组放弃,解释为一维数组,创建自己的下标。

T my_array[row_size * i + j] = ...


注意:对于三维以上的多维数组,必须把它分解为维数更少的数组,这是C语言存在的一个内在限制,这使得C语言在编写某些特定的程序时非常困难,例如数值分析算法。 

那么,用函数返回一个数组,也是无法直接实现的,但可以让函数返回一个指向任何结构体的指针,即返回一个指向数组的指针。

int (*fum())[20];

int (*fum())[20]

{

int (*pear)[20];

pear = calloc(20,sizeof(int));

if (!pear)  longjmp(error,1);

return pear;

}

调用时:

int (*result)[20];

result = fum();//函数调用

(*result)[3] = 12;//访问结果数组

 

由于数组是静态的,如果不知道长度,那么很难生成静态内存,甚至,下面这种形式,编译器都会报错

const int a = 10;

char my_array[a];

 动态增长内存

如果你不是直接声明一个数组,因为长度不可知,那么可以使用realloc函数,它能对一块现在的内存重新分配,而不会丢失原始内容:

1.对表进行检查,看它是否已经填满

2.如果确实已经 填满,使用realloc函数扩展长度,并进行检查,确保函数成功执行。

3.在表中增加项目

在实践中,不要把realloc函数的返回值,直接赋值给字符指针,如果函数失败,它会使该指针的值变为NULL,这样就无法实现对现有表的访问。

当然也可以使用链表,但只能线性访问,影响性能。