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) |