指针篇之五 指针与数组 剪不断理还乱

来源:互联网 发布:直接翻译软件界面 编辑:程序博客网 时间:2024/06/08 11:45

 

该文章转载于http://blog.csdn.net/ipmux/article/details/17206775,感谢博主悉心讲解。

 

数组和指针可以说是一对纠缠不清的冤家,下面是一些常见的关于数组和指针的说法:一维数组是一级指针二维数组是二级指针数组名可以作指针用数组名是常量指针

   很遗憾,这些说法全部错误。数组名不是指针,也不存在什么常量指针!数组名代表一个地址,它可以作为右值赋给指针变量。至于指针与地址的关系,在第一篇最后已有说明。忘记了?回去看

   存放数组的区域是一块内存,数组名这个符号代表了这块内存的首地址。注意:不是数组名这个符号的值代表内存首地址(只有变量才能被赋值,数组名不是变量),而是数组名符号本身代表了首地址。换言之,数组名是符号常量,是右值。与此对应,指针是变量,是左值。左右值的不同代表二者根本属性的差别,任何混淆数组名和指针的表述都是错误的。

    但实际应用中,数组又的确经常和指针有所关联。下面列举几种容易误解和混淆的场合:

1)作为形参的数组,自动被编译器转换为指针

     C编译器会把作为形参的数组自动按指针编译,但这并不说明数组名就是指针。这种转换是因为制定ANSI C标准时,从执行效率出发,不主张参数传递时复制整个数组,而用传递数组首地址代替,被调函数再根据这个首地址处理数组内容,以避免数据在内存间搬运(结构体参数也有类似转换)。谁能容纳数组首地址呢?自然非指针莫属。但这种转换是编译器在不影响功能前提下的一种智能替代和优化,用传递藏宝图代替直接搬运宝藏,例如:

    a) Bool XXX_ReadKey(char *aCert, char PublicKey[KEY_SIZE]);

    b) Bool XXX_ReadKey(char *aCert, char *PublicKey);

    如果代码中出现a),编译器分析后认为b)效率更高,且效果相同,就会自动替换为b)。这类似于编译器有时用移位替换乘除法一样,只是特定场合的优化,不代表双方随时能互换。

2)函数形参是指针时,数组名可以作为实参传递,这又是怎么回事?

    函数 int fun(char *pch),调用时可以把数组char ctab[10]的名字ctab做实参传递,即fun(ctab),可以证明这里数组等于指针?

    我们知道,函数参数按值传递,即:形参期望得到的,并不是实参本身,而是实参的值!例如void fun(int i);可以用整型变量int n作实参,即fun(n);也能用整数常量做实参,如fun(10);那按这种逻辑,形参是整型变量,10可以充当实参,难道说10是一个整型变量?

    其实这里形参类型int,只是限定传递一个整数类型,并非要求一定是整型变量,所以变量n和常量10都满足。指针情形类似,指针形参并不要求实参必须是指针,它需要的是地址值,谁能给予它一个地址值?指针变量、地址常量都行,数组名作为地址常量自然也满足,所以这里数组名是赋给指针形参的地址实参!数组名等于指针,仍然是一个错觉。

3)指针与数组可以在访问形式上相互代替

    a.使用地址访问符号*号访问数组,比如:

    int array[10]={0,1,2,3,4,5,6,7,8,9},

    int value;

    value=*array;           //相当于value=array[0];

    value=*(array+3);    //相当于value=array[3];

    肯定有人会说,这里数组和指针不是混用了么!形式上array的确既代表数组名又似乎被当指针用,但盲点在于*号并不是指针变量专用继续看:

    array++;                 //错,数组名代表地址,为右值,不能改变自身。所以数组终归不是指针。

    ……

    不要一看到*号就认为后面是指针,实际上*号又称地址访问符,它后面可以跟任何形式的地址,绝不仅仅是指针变量(这个误解在很多人心里根深蒂固)作为地址常量的数组名当然也可以被*号访问。数组名和指针的本质区别体现在array++这种操作:左值的指针变量可变,而右值的数组名地址常量不行。

    b引用符号[]访问指针

    []号称为数组引用,但并不是只能用于数组定义和数组索引求值C89规定:进行数组引用时,[]号左边除了数组名,还可以是一个表达式,但表达式类型须为指针。因此类似:

    int a[10], *p = a;

    int x = p[1];

    int y=(p+1)[2];

    这样是合法的,因为pp+1都是指针,可以用数组引用符[]求值,不能一看到p[i]就把p当成数组。编译器只负责把p[i]计算式统统转换成*(p+i)无论p是数组还是指针。可很多人常忽略这一点,总把[]跟数组划等号,这样在下面情况下就容易犯错:

    int tab[4] = {10,20,30,40};

    int * pstart = tab;

    int a = *pstart++;

    int b = pstart[1];

    此时b值为多少?

    关键:pstart[1]tab[1]性质不同,pstart是指针而不是数组,pstart[1]相当于*(pstart+1),只是借用[]号的形式完成指针偏移访问。为a赋值后,指针pstart自增1,当计算b=pstart[1]时,b值为指针pstart再次1后指向的值,即b=tab[2]=30而不是tab[1]10

4)静态数组与动态指针内存各自适合的应用场合

    前面讲数组和指针语义上的区别和联系,应用中更深层的问题是什么时候适合用静态数组,而什么时候该用指针指向的动态堆内存。可从两个角度分析:

    首先二者占用的内存区域不同,局部数组位于栈内存,全局数组位于数据区,而malloc返回的指针指向动态分配的堆内存。选择前要考虑不同资源的占用情况,后续内存篇将详述。

    从数据结构角度看,数组是定义时就指定长度的静态分配,用于存储数目固定且类型相同的元素。而以指针为纽带的各种动态数据结构,如链表,常用于存储动态变化的数据。举例说明它们各自的擅长:

    例1)用库函数strcpy()strcat()来把两个字符串st连接成一个新字符串r,首先尝试用数组r,用来容纳st,即:

    char r[100];

    strcpy(r, s);

    strcat(r, t);

    这里定义数组长度为100,可一旦字符串s,t长度之和超过100,内存访问就越界。很明显,这种情况不适合用数组,因为数组定长,无法应对长度变化的外部输入字符串st,这里st就是动态数据,适合用动态内存解决问题,即:

    char *r;

    r = malloc(strlen(s) + strlen(t) + 1);

    strcpy(r, s);

    strcat(r, t);

注意字符串尾隐含一个nullstrlen()只返回实际字符数,不包括这个结尾空字符。所以strlen(s)为n,却需n+1字符的空间,要为null额外分配1字节。

    例2)要保存一个班级所有成员期中考试的成绩,班级成员数为30,则可以用数组记录:int score[30]={100, 99, ……, 60}

    而如果是某班级招生,事先不知道能招到多少人,又要记录所有生源信息,对这种动态变化的数据,最好方式是用指针和动态内存构建链表,来1人分配一块内存保存其信息,并用指针把这些内存块连在一起,以便统计和查询。(具体链表实现?查书)

    以上就是指针和数组的关联,一刀两断,不要继续纠缠不清哦微笑

0 0
原创粉丝点击