Chinaunix首页 | 论坛 | 博客
  • 博客访问: 247812
  • 博文数量: 29
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 670
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-19 13:30
文章分类

全部博文(29)

文章存档

2011年(1)

2010年(10)

2009年(1)

2008年(17)

我的朋友

分类: C/C++

2008-05-25 00:54:18

C语言中数组与指针的结合

1.数组的内存布局
数组的元素在内存中的连续排列,也就是说数组元素的地址是连续增加的。定义数组,
    char c[6];
数组元素在内存中的排列是,c[0],c[1],c[2]...

对于多维数组,情况也是一样。C使用row major addressing,在地址增加时,最右边的一维的下标先变化,比如二维数组,
    char c[5][6];
在内存中的排列是,c[0][0],c[0][1],c[0][2]...c[1][5],c[1][0],c[1][1]...

实际上,C中的多维数组可以理解为数组的数组,比如按照上面的定义,c[0]就表示一个具有6个char类型元素的一维数组。

2.指向数组的指针
定义指针的方式,与定义指针指向类型的数据的方式相匹配,比如,
    char c;//定义char型变量
    char *pc;//定义指向char型变量的指针

定义一个指向数组的指针的方式就像定义其他类型的指针一样。
    char ca[6]; //定义char型数组,数组长度为6
    char (*pca)[6]; //定义指向具有6个元素的char型数组的指针
要注意的是[]运算符的优先级高过*,所以定义时要使用括号。

一个小问题,如果pca等于100,pca+1的值是多少?

回想一下关于指针的概念,指针
    pc=&c;
1.pc本身是个变量,这个变量的地址表示为&pc;
2.变量pc的值保存在地址&pc中,值等于指针所指向对象c的地址;
3.指针所指向对象c的值,用*pc表示,保存在地址pc中。
4.指针的下标操作是以指针所指的对象的大小为单位,pc+1表示紧邻c的下一个char型数据的地址,即pc的值加上sizeof(char)。

现在回答上面的问题,pca+1等于100加上sizeof(char [6]),即100+6*sizeof(char),char类型的长度为1,所以结果就是106。

3.表达式中的数组
C标准中规定:表达式中的数组,编译器当作一个指向该数组第一个元素的指针处理,而数组下标等价于指针的偏移量。所以访问一维数组的元

素c[i]时,c被当作指向数组的第一个元素c[0]的指针,i被当作指针的偏移量,c[i]等价于*(c+i)。
而对二维数组元素c[i][j]的访问,等价于(*(c+i))[j],注意*(c+i)的类型仍然为数组,因此进一步等价于*(*(c+i)+j)。

足够清楚了吗?下面详细的解释一下。定义数组,
    char c[5][6];
数组c的值,就是其首元素地址。数组元素是连续排列的,元素c[i][j]是首元素c[0][0]之后的第i*6+j个元素,访问元素c[i][j]就是访问地址

为c加上i*6+j的char类型数据。
而另一方面,c[i]等价于*(c+i),c的类型为指向数组的第一个元素c[0]的指针,所以c+i就等于c加上i*sizeof(char [6]),即c加上i*6,而*

(c+i)是char [6]类型数组,数组的值就是其首元素地址,仍为c加上i*6。接着*(c+i)又被当作指向char的指针,*(c+i)+j,就等于c加上i*6+

j。所以*(*(c+i)+j)就是地址为c加上i*6+j的char类型数据,和前面的结果完全一样。(参见C99 6.5.2.1)

[根据C99标准,Expert C Programming第9及第10章整理]

4.C语言中函数对数组的传递
实际上,在C语言中没有办法把数组本身传递给一个函数,如果将数组作为函数的实参,它将自动被改为指向数组的指针。C语言中函数参数的

传递采用的是值传递的方式,除了数组之外的所以数据类型在传递时,都是使用复制一份完整的新拷贝的方式。

出于效率的考虑,C在传递数组时,没有将整个数组复制,而是把数组的地址压入栈中传递给被调用函数,被调用函数从栈中获取地址值,由此

访问被传递的数组。因此,实际上被传递的参数是一个指向数组的指针,而原本数组大小的信息则被丢失了。所以无论传入的是数组还是指针

,被调用函数实际上得到的都只是指针。因此,C标准规定,在用作函数的形参时,以数组形式定义的形参被编译器当作指向该数组的指针类型



数组参数自动改写为指针的规则并不递归,二维数组被改成的类型是指向数组的指针,而不是指针的指针。所以,
    char c[5][6];
对应的形参类型是
    char (*p)[6];

而指针数组
    char *c[];
对应的形参类型才是
    char **c;

#include "iostream.h"
void fun(char **a);
void fun2(char (*a)[6]);
void main()
{
   char *a[2]={"abc","cde"};
   char b[2][6]={"fds","hgf"};
   fun(a);
   fun2(b);
}
void fun(char **a)
{
cout<for(int i=0;i<2;i++)
   cout<}
void fun2(char (*a)[6])
{
cout<for(int i=0;i<2;i++)
   cout<}

5.向函数传递数组
对于固定长度的数组传递,情况比较简单,如上面所说:
传递类型为
    char c[5][6];
的数组给函数,只需要把函数的形参类型声明为
    char (*p)[6];

    char c[5][6];
即可。

但是要注意的是,被调用函数得到的只是指针,所以需要某种途径把数组的长度告诉被调用函数。一种方式是用某种特定的数值来表示数组的

结尾,比如对于字符串,C语言定义了'\0',当检索到结尾标志时,函数就知道数组结束了。另一种方式是,使用一个额外的参数来传递数组长

度,比如大家的老朋友,
    int main(int argc, char *argv[]);

如果希望函数能接受的数组参数更灵活一些,能具有可变长度,就需要比较曲折的方法了。因为数组的第一维将被改为指针类型,数组形参定

义中最左边的一维中的数字没有效果。可以定义为,
    char c[];
或者
    char c2[][5];
这部分的解决了我们的问题,但如果希望c2的两维都可变,就不能通过这种方式的参数传递了,需要使用的是指针数组。


6.指针数组
指针数组的是以指针为元素的数组。它的访问方式和二维数组很像,定义
    char c[5][6];
    char *cp[];
都可以用连续下标的方式访问,
    c[2][3] = cp[2][3];

但是它们实际上的访问方式并不一样:
先看c[2][3],它是取符号表中记录的c地址,加上2*sizeof(char [6]),再加上3*sizeof(char),得到元素的地址。
而cp[2][3]则是取指针cp的值,加上2乘以指针宽度,得到指针cp[2]的地址;然后从地址取得内容,再以此内容为地址加上偏移量3*sizeof

(char),得到元素的地址。

传递可变二维数组的方法就是逐个把数组第n行第一个元素的地址,赋予指针数组的第n个元素,然后以指针数组为参数,传递给被调用函数。

当然,也可以不把指针数组作为二维数组的载体,而把它作为多个不连续的不定长一维数组的载体,不过记住,在传递时需要找到一种方法告

知被调用函数所有的这些一维数组的长度,对于字符串这会比较容易。


7.使用动态数组
在ANSI C中,不允许在运行时动态指定数组的长度(C99中允许,参考C99 6.7.5)。为了突破这个限制,可以使用malloc()动态分配内存,(

在cast为合适的类型后)以数组下标的形式访问这块内存。更进一步,为了让数组的大小能真正的动态变化,可以根据数组元素的实际数目,

使用realloc()为你的数组重新分配合适大小的内存。
阅读(3252) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~