zoukankan      html  css  js  c++  java
  • C++中的指针和数组

    最近看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++的左值 
      2. Is array name a pointer in C?
  • 相关阅读:
    第 2 章 MySQL 架构组成
    MySql学习笔记
    大型项目成功的关键
    内连接区别外连接
    UML2.0
    软件架构师之路
    UVA
    ZOJ
    UVA
    UVA
  • 原文地址:https://www.cnblogs.com/chybot/p/4805546.html
Copyright © 2011-2022 走看看