C语言指针与数组的关系

指针和数组到底有什么样的关系,他们之间没有任何关系!

指针就是指针,指针变量在32 位系统下,永远占4 个byte,其值为某一个内存的地址。指针可以指向任何地方,但不是任何地方你都能通过这个指针变量访问到。

数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存任何类型的数据,但不能存函数。

一、以指针的形式访问和以下标的形式访问

下面我们就详细讨论讨论它们之间似是而非的一些特点。例如,函数内部有如下定义:

A)

char *p = “abcdef”;

B)

char a[] = “123456”;

1、以指针的形式访问和以下标的形式访问指针

例子A)定义了一个指针变量p,p 本身在栈上占4 个byte,p 里存储的是一块内存的首地址。这块内存在静态区,其空间大小为7 个byte,这块内存也没有名字。对这块内存的访问完全是匿名的访问。比如现在需要读取字符‘e’,我们有两种方式:

1)

以指针的形式:*(p+4)。先取出p 里存储的地址值,假设为0x0000FF00,然后加上4 个字符的偏移量,得到新的地址0x0000FF04。然后取出0x0000FF04 地址上的值。

2)

以下标的形式:p[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。p[4]这个操作会被解析成:先取出p 里存储的地址值,然后加上中括号中4 个元素的偏移量,计算出新的地址,然后从新的地址中取出值。也就是说以下标的形式访问在本质上与以指针的形式访问没有区别,只是写法上不同罢了。

2、以指针的形式访问和以下标的形式访问数组

例子B)定义了一个数组a,a 拥有7 个char 类型的元素,其空间大小为7。数组a 本身在栈上面。对a 的元素的访问必须先根据数组的名字a 找到数组首元素的首地址,然后根据偏移量找到相应的值。这是一种典型的“具名+匿名”访问。比如现在需要读取字符‘5’,我们有两种方式:

1)

以指针的形式:*(a+4)。a 这时候代表的是数组首元素的首地址,假设为0x0000FF00,然后加上4 个字符的偏移量,得到新的地址0x0000FF04。然后取出0x0000FF04 地址上的值。

2)

以下标的形式:a[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。a[4]这个操作会被解析成:a 作为数组首元素的首地址,然后加上中括号中4 个元素的偏移量,计算出新的地址,然后从新的地址中取出值。

由上面的分析,我们可以看到,指针和数组根本就是两个完全不一样的东西。只是它们都可以“以指针形式”或“以下标形式”进行访问。一个是完全的匿名访问,一个是典型的具名+匿名访问。一定要注意的是这个“以XXX 的形式的访问”这种表达方式。

另外一个需要强调的是:上面所说的偏移量4 代表的是4 个元素,而不是4 个byte。只不过这里刚好是char 类型数据1 个字符的大小就为1 个byte。记住这个偏移量的单位是元素的个数而不是byte 数,在计算新地址时千万别弄错了。

二、指针数组和数组指针的内存布局

初学者总是分不出指针数组与数组指针的区别。其实很好理解:

指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。

数组指针:首先它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。

下面到底哪个是数组指针,哪个是指针数组呢:

A)

int *p1[10];

B)

int (*p2)[10];

每次上课问这个问题,总有弄不清楚的。这里需要明白一个符号之间的优先级问题。

“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。

至于p2 就更好理解了,在这里“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。我们可以借助下面的图加深理解:

三、a 和&a 的区别

①通过上面的分析,相信你已经明白数组和指针的访问方式了,下面再看这个例子:

main()

{

int a[5]={1,2,3,4,5};

int *ptr=(int *)(&a+1);

printf(“%d,%d”,*(a+1),*(ptr-1));

}

打印出来的值为多少呢? 这里主要是考查关于指针加减操作的理解。

对指针进行加1 操作,得到的是下一个元素的地址,而不是原有地址值直接加1。所以,一个类型为T 的指针的移动,以sizeof(T) 为移动单位。因此,对上题来说,a 是一个一维数组,数组中有5 个元素; ptr 是一个int 型的指针。

&a + 1,其中&a表示取数组a 的首地址,+1表示该地址的值加上sizeof(a) 的值,即&a + 5*sizeof(int),也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。

(int *)(&a+1): 则是把上一步计算出来的地址,强制转换为int * 类型,赋值给ptr。

*(a+1),其中的a和&a 的值是一样的,但意思不一样!a 是数组首元素的首地址,也就是a[0]的首地址,&a 是数组的首地址,a+1 是数组下一元素的首地址,即a[1]的首地址,&a+1 是下一个数组的首地址。所以*(a+1)输出为2;

*(ptr-1),其中因为ptr是指向已越界的a[5]的,并且ptr 是int * 类型,所以*(ptr-1) 是指向a[4] ,输出为5。

②现在再来看看下面的代码:

int main()

{

char a[5]={‘A’,’B’,’C’,’D’};

char (*p3)[5] = &a;

char (*p4)[5] = a;

return 0;

}

上面对p3 和p4 的使用,哪个正确呢?p3+1 的值会是什么?p4+1 的值又会是什么?毫无疑问,p3 和p4 都是数组指针,指向的是整个数组。&a 是整个数组的首地址,a是数组首元素的首地址,其值相同但意义不同。在C 语言里,赋值符号“=”号两边的数据类型必须是相同的,如果不同需要显示或隐式的类型转换。p3 这个定义的“=”号两边的数据类型完全一致,而p4 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。在Visual C++6.0 上给出如下警告:

warning C4047: ‘initializing’ : ‘char (*)[5]‘ differs in levels of indirection from ‘char *’。

还好,这里虽然给出了警告,但由于&a 和a 的值一样,而变量作为右值时编译器只是取变量的值,所以运行并没有什么问题。不过我仍然警告你别这么用。

既然现在清楚了p3 和p4 都是指向整个数组的,那p3+1 和p4+1 的值就很好理解了。

但是如果修改一下代码,会有什么问题?p3+1 和p4+1 的值又是多少呢?

int main()

{

char a[5]={‘A’,’B’,’C’,’D’};

char (*p3)[3] = &a;

char (*p4)[3] = a;

return 0;

}

甚至还可以把代码再修改:

int main()

{

char a[5]={‘A’,’B’,’C’,’D’};

char (*p3)[10] = &a;

char (*p4)[10] = a;

return 0;

}

这个时候又会有什么样的问题?p3+1 和p4+1 的值又是多少?

四、地址的强制转换

先看下面这个例子:

struct Test

{

int Num;

char *pcName;

short sDate;

char cha[2];

short sBa[4];

}*p;

假设p 的值为0×100000。如下表表达式的值分别为多少?

p + 0×1 = 0x___ ?

(unsigned long)p + 0×1 = 0x___?

(unsigned int*)p + 0×1 = 0x___?

我相信会有很多人一开始没看明白这个问题是什么意思。其实我们再仔细看看,这个知识点似曾相识。一个指针变量与一个整数相加减,到底该怎么解析呢?

还记得前面我们的表达式“a+1”与“&a+1”之间的区别吗?其实这里也一样。指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数。这个整数的单位不是byte 而是元素的个数。所以:p + 0×1 的值为0×100000+sizof(Test)*0×1。至于此结构体的大小为20byte,前面的章节已经详细讲解过。所以p +0×1 的值为:0×100014。

(unsigned long)p + 0×1 的值呢?这里涉及到强制转换,将指针变量p 保存的值强制转换成无符号的长整型数。任何数值一旦被强制转换,其类型就改变了。所以这个表达式其实就是一个无符号的长整型数加上另一个整数。所以其值为:0×100001。

(unsigned int*)p + 0×1 的值呢?这里的p 被强制转换成一个指向无符号整型的指针。所以其值为:0×100000+sizof(unsigned int)*0×1,等于0×100004。

上面这个问题似乎还没啥技术含量,下面就来个有技术含量的:在x86 系统下,其值为多少?

intmain()

{

int a[4]={1,2,3,4};

int *ptr1=(int *)(&a+1);

int *ptr2=(int *)((int)a+1);

printf(“%x,%x”,ptr1[-1],*ptr2);

return 0;

}

下面就来分析分析这个问题:

根据上面的讲解,&a+1 与a+1 的区别已经清楚。

ptr1:将&a+1 的值强制转换成int*类型,赋值给int* 类型的变量ptr,ptr1 肯定指到数组a 的下一个int 类型数据了。ptr1[-1]被解析成*(ptr1-1),即ptr1 往后退4 个byte。所以其值为0×4。

ptr2:按照上面的讲解,(int)a+1 的值是元素a[0]的第二个字节的地址。然后把这个地址强制转换成int*类型的值赋给ptr2,也就是说*ptr2 的值应该为元素a[0]的第二个字节开始的连续4 个byte 的内容。

其内存布局如下图:

好,问题就来了,这连续4 个byte 里到底存了什么东西呢?也就是说元素a[0],a[1]里面的值到底怎么存储的。这就涉及到系统的大小端模式了,如果懂汇编的话,这根本就不是问题。既然不知道当前系统是什么模式,那就得想办法测试。大小端模式与测试的方法在第一章讲解union 关键字时已经详细讨论过了,请翻到彼处参看,这里就不再详述。我们可以用下面这个函数来测试当前系统的模式。

int checkSystem( )

{

union check

{

int i;

char ch;

} c;

c.i = 1;

return (c.ch ==1);

}

如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。也就是说如果此函数的返回值为1 的话,*ptr2 的值为0×2000000。如果此函数的返回值为0 的话,*ptr2 的值为0×100。

demo注释:https://github.com/byczd/CPointerArray

转自:http://www.blogfshare.com/c-ptr-and-array.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,432评论 3 44
  • 计算机二级C语言上机题库(南开版) 1.m个人的成绩存放在score数组中,请编写函数fun,它的功能是:将低于平...
    MrSunbeam阅读 6,340评论 1 42
  • 前言:复杂类型说明 要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解...
    有理想有暴富的小青年阅读 586评论 0 4
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,340评论 0 2
  • 数组在程序设计中,为了处理方便, 把具有相同类型的若干变量按有序的形式组织起来。这些按序排列的同类数据元素的集合称...
    朱森阅读 3,912评论 2 13