最近看C++编程思想,看到第十三章动态内存管理的时候把自己给绕进去了,主要是在数据和指针这块弄混了。现在把找到的一些资料总结如下:

1. 数组是数组,指针是指针,两者并不等价;

2.数组在作为左值的时候一般是数组自己,而放在右值往往被转换成一个常量指针;

3.下标运算符其实是针对指针的,只是数组变成指针后也适用于了数组;

4.对数组取地址符在很早以前是错误的,因为数组名本身就是数组的地址,对地址去地址显然不对,后来被C++标准定为合法的,表示指向数组对象的指针。

对于 int a[100]; int b[10][100];

1. a表示数组本身,放在右值时是数组第一个元素的地址,其类型是&a[0],因此可以有*(a+1); a[i],但是如果是sizeof(a),则为数组的大小100*4。

2. &a的类型是int (*)[100],因此&a + 1得到的地址是a的地址加上整个数组的大小400,但是sizeof(&a)得到的是a的大小400。

3.下标符只能由指针使用,除非是重载,p[1] 等价于*(p+1),在使用二维数组可能会陷入混乱,例如:

  int **ptr = (int **)&b;

ptr[i]的值类型是指针(int *),但是其值是b的元素值(按照内存中的位置顺序取),如果使用ptr[1][0]很可能是未定义的,因为这时是取了b的元素并把它作为指针再取一次值,除非b的元素本来就是指针。

4. a+1的类型是指向a的行元素的指针,对它取值*(a+1)得到的类型是a的行元素的类型。这里是int型指针,和int型的值。

 如果是b的话,b+1的值和*(b+1)的值相同,但是类型不同,b+1表示的是指向数组的普通指针,而*(b+1)表示的是类型为int [100]的数组。

因此sizeof(b+1)=4, 而sizeof(*(b+1)) = 400,但是两者的值都是相同的地址值。

资料:

C语言中,数组和指针都有a[N]取元素的语法,因此也是初学者容易混淆的地方。这个问题要知其所以然并不容易。理解这个问题,需要涉及到C语言中最基本但也最容易忽略的概念:左值/右值 (lvalue/rvalue)。

在开始这个问题之前,首先要区别“变量”和“值”(/对象)是两个不同的概念。“变量”是一块有名字的存储区域,“值”是某种类型的二进制数据,可能存储在某段内存,或者在寄存器、或者是代码中的字面量。“变量”是对某个值起一个名字,这样可以索引到值的存储位置。

C语言中,表达式(expression)的结果是一个“值”。单独的一个变量名(如a)或者单独的字面量(如1)也是表达式。左值/右值 是值的一个基本属性,C语言中非左值即右值。左值表达式表示了某个值的存储位置,右值表达式表示某个值所存储的数据。

左值性的具体讨论请参看参考链接1。一般来说,赋值操作等号左边是(可修改的)左值,右边取右值。所以

int i,j;
i = 0; // OK: i is lvalue, 1 is rvalue
0 = i; // error: 0 is not lvalue
i = j; // OK. why?

为什么可以写 i = j 这样的赋值操作呢?因为C语言中有一个叫做lvalue-to-rvalue conversion的默认类型转换。当赋值操作右边是一个左值时,会默认转换为右值。这样的一个转换代表了对左值表达式 j 所存储内容的一次“读取”。类似的,C语言中按值传参,用左值表达式作为函数参数时,也会进行 lvalue-to-rvalue conversion。

void foo(int);
foo(0); // OK, need rvalue
foo(i); // OK. lvalue-to-rvalue conversion

讲到这里,我们切入正题:数组、指针是左值吗?答案是当然,因为他们都代表了一个存储位置。不同的是,数组a作为变量,所代表的位置是数组元素的存储位置;指针a作为值(右值),是一个内存地址,而作为变量(左值),所代表的位置是这个地址的存储位置。数组是一个不可修改的左值,指针如果没有指定const,则是可以修改的。

int a[4];
int *p, i, j;
a = 1; // error
p = &i; // OK
p = &(a[1]); // OK

数组和指针的存储区域如图所示(仅为示意,并不代表实际的内存layout)

理解了这个概念,“取地址”怎么用的问题就很显然了。“取地址”的操作就是对于一个左值表达式,取出其表示的内存位置。(仅为示意,并不代表实际的内存layout)

&a; // 0xa000 类型为int (*) [4],数组的地址
&(a[1]); // 0xa004 类型为 int*,某个整型元素的地址
&p; // 0xa010 类型为 int**,指针的地址

现在问题来了,挖掘机..哦不,数组,为什么可以赋值给指针?

p = a; // OK. why?

在C语言中,对于数组类型的左值,lvalue-to-rvalue conversion有一个特例,叫做array-to-pointer conversion(类似的还有function-to-pointer conversion)。在需要右值的场合,如果给出一个数组名,那么数组名会被转换为其首个元素的地址,类型为指向元素的指针。

void foo(int *p);
foo(a); // OK. array-to-pointer conversion
int **q = &a; // warning: initialization from incompatible pointer type

在需要左值的场合,a仍然是数组类型,所以&a的类型是指向数组的指针,而非指向元素的指针!

那么,下标运算符[]是什么?为什么数组和指针兼容下标运算?下标运算符的左侧表达式,是左值还是右值?稍稍想一下会发现,原来下标运算符只需要一个指针类型的右值即可。

int k = a[1]; // equivalent to *(a+1)
int **q;
(*q)[2]; // OK. *q is rvalue of int*

那么结论就很清楚了,在下标表达式中,数组会通过array-to-pointer conversion转换为指针右值,而指针类型,无论左值右值,都可以转换为指针右值。所以下标操作符自古以来就是为指针准备的,数组只是通过内置转换,捎带着实现了下标运算。这个结论,是否有些让人震惊呢?

参考链接:

    1. [FAQ] C/C++的左值 http://www.newsmth.net/nForum/article/CPlusPlus/179727?s=179727
    2. Is array name a pointer in C?

C++中的指针和数组的更多相关文章

  1. 转: 浅谈C/C++中的指针和数组(二)

    转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html 浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组 ...

  2. 转:浅谈C/C++中的指针和数组(一)

    再次读的时候实践了一下代码,结果和原文不一致 error C2372: 'p' : redefinition; different types of indirection 不同类型的间接寻址 /// ...

  3. 浅谈C中的指针和数组(一)

    本文转载地址:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242138.html 在原文的基础上加入自己的想法作为修改. 指针是C/C ...

  4. C++中的指针、数组指针与指针数组、函数指针与指针函数

    C++中的指针.数组指针与指针数组.函数指针与指针函数 本文从刚開始学习的人的角度,深入浅出地具体解释什么是指针.怎样使用指针.怎样定义指针.怎样定义数组指针和函数指针.并给出相应的实例演示.接着,差 ...

  5. 深入理解C语言中的指针与数组之指针篇

    转载于http://blog.csdn.net/hinyunsin/article/details/6662851     前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本 ...

  6. 深入理解C语言中的指针与数组之指针篇(转载)

    前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本所在.相信,任意一家公司如果想要考察一个人对C语言的理解,指针和数组绝对是必考的一部分. 但是之前一方面之前一直在忙各种事情 ...

  7. 浅谈C中的指针和数组(五)

    前面写了一些C指针和数组的一些知识,但是还有一些很重要的知识没有交代,这里做一个补充. 首先看一下,普通变量(指针也是变量)和数组名查看地址的方式是不同的. 查看数组变量的地址,不需要使用 & ...

  8. 浅谈C中的指针和数组(二)

    原文转载地址:http://see.xidian.edu.cn/cpp/html/475.html 在原文的基础上增加自己的想法作为修改 很多初学者弄不清指针和数组到底有什么样的关系.我现在就告诉你: ...

  9. 浅谈C中的指针和数组(三)

    上一个博客我们得到了一个结论: 指针和数组根本就是两个完全不一样的东西.只是它们都可以“以指针形式”或“以下标形式”进行访问.一个是完全的匿名访问,一个是典型的具名+匿名访问.一定要注意的是这个“以X ...

  10. 关于C++中的指针、数组

    C++中指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式:将整数变量加一后,其值将增加1:将指针变量加一后,增加的量等于其指向的数据类型的字节数: 指针中存储的是地址,地址在形式上和整数 ...

随机推荐

  1. bug-sqlite3

    [root@izj6c6b4i40od17ev77lhez Python-3.7.0]# python Python 3.7.0 (default, Sep 5 2018, 00:40:27) [GC ...

  2. django URL的补充 默认值 传多个参数

    url 后面还可以加上默认值 默认值 url(r'^index/', views.index, {'name': 'root'}), urls.py url对应关系 from django.conf. ...

  3. 注册表REG文件编写大全

    Windows 中的注册表文件( system.dat 和 user.dat )是 Windows 的核心数据库,因此,对 Windows 来说是非常重要的. 通过修改注册表文件中的数据,可以达到优化 ...

  4. 004-hadoop家族概述

    hadoop家族 名称 简介   Hadoop 分布式基础架构 Hadoop的框架最核心的设计就是:HDFS和MapReduce.HDFS为海量的数据提供了存储,则MapReduce为海量的数据提供了 ...

  5. 简明python教程十一----更多Python的内容

    特殊的方法 __init__(self,...):这个方法在新建对象恰好要被返回使用之前被调用 __del__(self):恰好在对象要被删除之前调用 __str__(self):我们对对象使用pri ...

  6. 面试题5:JS实现从尾到头打印单链表

    单链表,在内存中所占地址是不连续的.所以遍历单链表时:需要从头遍历.而题目要求输出的顺序:从尾到头.也就是说第一个遍历到的节点最后一个输出,而最后一个遍历到的节点第一个输出.这就是典型的“后进先出”, ...

  7. DB_FILE_MULTIBLOCK_READ_COUNT对物理读和IO次数的影响

    当执行SELECT语句时,如果在内存里找不到相应的数据,就会从磁盘读取进而缓存至LRU末端(冷端),这个过程就叫物理读.当相应数据已在内存,就会逻辑读. 物理读是磁盘读,逻辑读是内存读:内存读的速度远 ...

  8. SCADA必备函数 实际测试。

    一:GetTickCount() 综述: 这是一个Window的平台的API函数, 所以啊 在 MFC中 他的前面有两个冒号,像个和尚一样. 所以它不会受限于类,可以在MFC中任意位置使用. 这个函数 ...

  9. Java游戏服务器成长之路——弱联网游戏篇(源码分析)

    前言 前段时间由于公司的一款弱联网游戏急着上线,没能及时分享,现在基本做的差不多,剩下的就是测试阶段了(本来说元旦来分享一下服务器技术的).公司的这款游戏已经上线一年多了,在我来之前一直都是单机版本, ...

  10. Linux系统——date命令

    date命令 作用:用来显示或设定系统的日期与时间. 参数 -d<字符串>:显示字符串所指的日期与时间.字符串前后必须加上双引号: -s<字符串>:根据字符串来设置日期与时间. ...