关于“指针与数组”,本来想自己整理个原创专题的,但写出来的DD实在太水,刚碰巧看到一篇这个主题很不错的文章,拿过来和大家分享,链接如下:
http://c.chinaitlab.com/c/basic/200811/770070.html
下面是我在读原文的基础上,标注了一些自己的理解和旁白。
建议大家点开原文看下,然后选择一个自己比较喜欢的风格仔细看下这篇文章,会有收获的。
基本解释
1、指针的本质是一个与地址相关的复合类型,它的值是数据存放的位置(地址);数组的本质则是一系列的变量。
==》指针的本质是地址,数组的本质是变量。
2、数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。
指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。
==》这里我个人不是很赞同这句话的表述“指针可以随时指向任意类型的内存块”。
指针中保存的是其所指向对象的地址,而计算机中地址是无符号整数,单从地址上我们看不出数据类型的区别。
但指针在定义时也需要标明其类型,并且在使用过程中指针的类型需要和其所指对象的数据类型一致。
用以下例子demo一下:
#include<stdio.h>
int main()
{
int i = 0;
double d = 0.0;
int *i_point;
double *d_point;
i_point = &i;//正确
d_point = &d;//正确
//i_point = &d;//错误使用
//d_point = &i;//错误使用
return 0;
}
如果打开标注的两句错误使用,编译器给出如下提示:
3、当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
==》当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。(重要,金典,理解)
一、指针与数组
问题:听说char a[]与char *a是一致的,是不是这样呢?
答案与分析:
指针和数组存在着一些本质的区别。当然,在某种情况下,比如数组作为函数的参数进行传递时,由于该数组自动退化为同类型的指针,所以在函数内部,作为函数参数传递进来的指针与数组确实具有一定的一致性,但这只是一种比较特殊的情况而已,在本质上,两者是有区别的。请看以下的例子:
char a[] = "Hi, pig!"; char *p = "Hi, pig!"; |
上述两个变量的内存布局分别如下:
数组a需要在内存中占用8个字节的空间,这段内存区通过名字a来标志。
指针p则需要4个字节的空间来存放地址,这4个字节用名字p来标志。其中存放的地址几乎可以指向任何地方,也可以哪里都不指,即空指针。目前这个p指向某地连续的8个字节,即字符串“Hi, pig!”。
==》准确说,目前这个p指向某地连续的8个字节的首地址,即字符串“Hi, pig!”。其实,数组名a也仅标识的首地址。
之前在文章中专门讨论过“指针的大小”问题,不同类型的指针变量占据相同数目的内存单元。
(这句话不能理解的看这里 http://blog.youkuaiyun.com/linan_nwu/article/details/7866776)
另外,例如:对于a[2]和p[2],二者都返回字符‘i’,但是编译器产生的执行代码却不一样。对于a[2],执行代码是从a的位置开始,向后移 动2两个字节,然后取出其中的字符。对于p[2],执行代码是从p的位置取出一个地址,在其上加2,然后取出对应内存中的字符。
==》注意,这里说的加2并不是简单的将指针加(减)这个整数,而是要将指针加上(减去)2个目标数据元素所占据的内存单元数目作为表达式运算的结果。这里存在一个“步进”的问题,只是因为定义为char类型不太容易看清这其中的区别。
二、数组指针
问题:为什么在有些时候我们需要定义指向数组而不是指向数组元素的指针?如何定义?
答案与分析:
使用指针,目的是用来保存某个元素的地址,从而来利用指针独有的优点,那么在元素需要是数组的情况下,就理所当然要用到指向数组的指针,比如在高维需要动态生成情况下的多维数组。
定义例子如下: int (*pElement)[2].
下面是一个例子:
int array[2][3] = {{1,2,3},{4,5,6}}; int (*pa)[3]; //定义一个指向数组的指针 pa = &array[0]; // '&'符号能够体现pa的含义,表示是指向数组的指针 printf ("%d", (*pa)[0]); //将打印array[0][0],即1 pa++; // 猜一猜,它指向谁?array[1]?对了! printf ("%d", (*pa)[0]); // 将打印array[1][0],即4 |
上述这个例子充分说明了数组指针—一种指向整个数组的指针的定义和使用。
需要说明的是,按照我们在第四篇讨论过的,指针的步进是参照其所指对象的大小的,因此,pa++将整个向后移 动一个数组的尺寸,而不是仅仅向后移 动一个数组元素的尺寸。
三、指针数组
有如下定义:
struct UT_TEST_STRUCT *pTo[2][MAX_NUM]; |
提问:请分析这个定义的意义,并尝试说明这样的定义可能有哪些好处?
答案与分析:
前面我们谈了数组指针,现在又提到了指针数组,两者形式很相似,那么,如何区分两者的定义呢?分析如下:
数组指针是:指向数组的指针,比如 int (*pA)[5].
指针数组是:指针构成的数组,比如int *pA[5].
==》这里不太容易理解,建议大家先从字面的解释去理解,然后再区分定义时“数组指针”与“指针数组”的差异。
至于上述指针数组的好处,大致有如下两个很普遍的原因:
a)、各个指针内容可以按需要动态生成,避免了空间浪费。
b)、各个指针呈数组形式排列,索引起来非常方便。
在实际编程中,选择使用指针数组大多都是想要获得如上两个好处。
四、指向指针的指针
在做一个文本处理程序的时候,有这样一个问题:什么样的数据结构适合于按行存储文本?
答案与分析:
首先,我们来分析文本的特点,文本的主要特征是具有很强的动态性,一行文本的字符个数或多或少不确定,整个文本所拥有的文本行数也是不确定的。这样的特征决定了用固定的二维数组存放文本行必然限制多多,缺乏灵活性。这种场合,使用指向指针的指针有很大的优越性。
现实中我们尝试用动态二维数组(本质就是指向指针的指针)来解决此问题:
图示是一个指针数组。所谓动态性指横向(对应每行文本的字符个数)和纵向(对应整个文本的行数)两个方向都可以变化。
就横向而言,因为指针的灵活性,它可以指向随意大小的字符数组,实现了横向动态性。
就竖向而言,可以动态生成及扩展需要的指针数组的大小。
下面的代码演示了这种动态数组的用途:
// 用于从文件中读取以 '\0'结尾的字符串的函数 while (p = getline(pFile)) |
五、指针数组、数组指针、指向指针的指针
指针和数组分别有如下的特征:
指针:动态分配,初始空间小
数组:索引方便,初始空间大
下面使用高维数组来说明指针数组、数组指针、指向指针的指针各自的适合场合。
多维静态数组:各维均确定,适用于整体空间需求不大的场合,此结构可方便索引,例a[10][40].
数组指针:低维确定,高维需要动态生成的场合,例a[x][40].
指针数组:高维确定,低维需要动态生成的场合,例a[10][y].
指向指针的指针:高、低维均需要动态生成的场合,例a[x][y].
六、数组名相关问题
假设有一个整数数组a,a和&a的区别是什么?
答案与分析:
a == &a == &a[0],数组名a不占用存储空间。需要引用数组(非字符串)首地址的地方,我一般使用&a[0],使用a容易和指针混淆,使用&a容易和非指针变量混淆。
区别在于二者的类型。对数组a的直接引用将产生一个指向数组第一个元素的指针,而&a的结果则产生一个指向全部数组的指针。例如:
#include<stdio.h>
int main()
{
int a[] = {1,2};
int *i_point;
i_point = a; //正确,i_point指向数组a的首地址
i_point = &a[0];//正确,i_point指向数组a[0]的地址
//i_point = &a;//错误
return 0;
}
注意:i_point = &a的用法是不对的,编译时的错误提示如下:
七、函数指针与指针函数
问题:如下定义是什么意思:
int *pF1(); int (*pF2)(); |
答案与分析:
首先清楚它们的定义:
指针函数:返回一个指针的函数。
函数指针:指向一个函数的指针。
可知:
pF1是一个指针函数,它返回一个指向int型数据的指针。
pF2是一个函数指针,它指向一个参数为空的函数,这个函数返回一个整数。
==》指针函数与函数指针这个主题,以及指向多维数组的指针数组,我理解的都不是很好,我找些相关资料看下,有比较好的我会拿来和大家分享。
这篇文章我看过后的想法都写成旁白了哈,还会再看,有新想法的时候会重新编辑出来。