指针 数组

来源:互联网 发布:四川广电电视网络套餐 编辑:程序博客网 时间:2024/04/30 09:09

指针规律:

1.int *p其实应该写成int* p更合适,int*是一个模子,一个整体,一个类型。

2.指针的加减是对单元数量(比如int*,则加1表示增加4个字节)的加减,不是单纯的数学实数加减。

3.强制转换:

       struct Test
      {
        int Num;
        char *pcName;
        short sDate;
        char cha[2];
        short sBa[4];
       }*p;
        假设p的值为0x100000。 如下表表达式的值分别为多少?
     p + 0x1 = 0x100014 ?
     (unsigned long)p + 0x1 = 0x100001?
     (unsigned int*)p + 0x1 = 0x100004?     

4.凡是在指针定义处赋值的操作,都是针对指针变量本身的,而不是针对指针所指向的地址的内容的,

比如:int *p = NULL 和*p = NULL 有什么区别?

    很多初学者都无法分清这两者之间的区别。我们先看下面的代码:
    int *p = NULL;
    这时候我们可以通过编译器查看p的值为0x00000000。这句代码的意思是:定义一个指针变量p,其指向的内存里面保存的是int类型的数据;在定义变量p的同时把p的值设置为0x00000000,而不是把*p的值设置为0x00000000。这个过程叫做初始化,是在编译的时候进行的。
    明白了什么是初始化之后,再看下面的代码:
    int *p;
    *p = NULL;
    同样,我们可以在编译器上调试这两行代码。第一行代码,定义了一个指针变量p,其指向的内存里面保存的是int类型的数据;但是这时候变量p本身的值是多少不得而知,也就是说现在变量p保存的有可能是一个非法的地址。第二行代码,给*p赋值为NULL,即给p指向的内存赋值为NULL;但是由于p指向的内存可能是非法的,所以调试的时候编译器可
能会报告一个内存访问错误。这样的话,我们可以把上面的代码改写改写,使p指向一块合法的内存:
    int i = 10;
    int *p = &i;
    *p = NULL;

    在编译器上调试一下,我们发现p指向的内存由原来的10变为0了;而p本身的值, 即内存地址并没有改变。

    再比如:需要往内存0x12ff7c地址上存入一个整型数0x100。

    int *p = (int *)0x12ff7c;  // P是指针变量,存储的内容为内存地址0x12ff7c
    *p = 0x100;                     // 向内存地址0x12ff7c处写入0x100

    上述代码等同如下:*(int *)0x12ff7c = 0x100;


数组


    a[0],a[1]等为a的元素,但并非元素的名字。数组的每一个元素都是没有名字的。

    sizeof(a)的值为sizeof(int)*5,32位系统下为20。
    sizeof(a[0])的值为sizeof(int),32位系统下为4。
    sizeof(a[5])的值在32位系统下为4。并没有出错,为什么呢?我们讲过sizeof是关键字不是函数。函数求值是在运行的时候,而关键字sizeof求值是在编译的时候。虽然并不存在a[5]这个元素,但是这里也并没有去真正访问a[5],而是仅仅根据数组元素的类型来确定其值。所以这里使用a[5]并不会出错。
    sizeof(&a[0])的值在32位系下为4,这很好理解。取元素a[0]的首地址。
    sizeof(&a)的值在32位系统下也为4,这也很好理解。取数组a的首地址。

    a有两个含义:数组名、数组a[0]的地址(等于&a[0])。

    &a则表示数组的地址,值与a相等。

    这里&a[0]和&a到底有什么区别呢?a[0]是一个元素,a是整个数组,虽然&a[0]和&a的值一样,但其意义不一样。前者是数组首元素的首地址,而后者是数组的地址。举个
例子:湖南的省政府在长沙,而长沙的市政府也在长沙。两个政府都在长沙,但其代表的意义完全不同。这里也是同一个意思。

a作为左值与作为右值

    x=y。
    左值:在这个上下文环境中,编译器认为x的含义是x所代表的地址。这个地址只有编译器知道,在编译的时候确定,编译器在一个特定的区域保存这个地址,我们完全不必考虑这个地址保存在哪里。
    右值:在这个上下文环境中,编译器认为y的含义是y所代表的地址里面的内容。这个内容是什么,只有到运行时才知道。

    a作为右值时其意义与&a[0]是一样,代表的是数组首元素的首地址,而不是数组的地址。

    a不能作为左值。

    编译器会认为数组名作为左值代表的意思是数组a的地址,但是这个地址开始的一块内存是一个总体,我们只能访问数组的某个元素而无法把数组当一个总体进行访问。所以我们可以把a[i]当左值,而无法把a当左值。其实我们完全可以把a当一个普通的变量来看,只不过这个变量内部分为很多小块,我们只能通过分别访问这些小块来达到访问整个变量a的目的。

指针和数组关系

    指针和数组之间没有任何关系!
    指针就是指针,指针变量在32位系统下,永远占4个byte,其值为某一个内存的地址。指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到。
    数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存任何类型的数据,但不能存函数。

相似点

例子A)
    定义了一个指针变量p,p本身在栈上占4个byte,p里存储的是一块内存的首地址。这块内存在静态区,其空间大小为7个byte,这块内存也没有名字。对这块内存的访问完全是匿名的访问。
    比如现在需要读取字符‘e’,我们有两种方式:1),以指针的形式:*(p+4)。先取出p里存储的地址值,假设为0x0000FF00,然后加上4个字符的偏移量,得到新的地址0x0000FF04。然后取出0x0000FF04地址上的值。2),以下标的形式:p[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。p[4]这个操作会被解析成:先取出p里存储的地址值,然后加上中括号中4个元素的偏移量,计算出新的地址,然后从新的地址中取出值。也就是说以下标的形式访问在本质上与以指针的形式访问没有区别,只是写法上不同罢了。

例子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的形式的访问”这种表达方式。

    注:偏移量的单位是元素的个数而不是byte数。

a 和&a

    &a认为数组a是一个整体,不论对其进行+/-,都是作为一个整体进行的,比如定义为int a[5]的数组,&a+1就表示下一个a[5]数组的地址,即&a是int[5]型的变量地址。
    a有两种含义(见上),对其进行+/-,含义表示数组元素a[0]的地址,a+1就表示a[1]的地址。

理解

    int *p;
    *p=0x10;
    定义了一个变量p(变量p本身也会有一个存储他的地址),变量p存储了一个地址0x12345678,地址为0x12345678的内存存储着0x10这个数。
    int a[1];
    a[0]=0x11;
    定义了一个名字为a的数组(不存在变量),保存数组数据的起始地址为0x87654321,地址为0x87654321的内存存储着0x10这个数。
    访问0x10这个数的流程是,首先找到变量p,取出p的内容0x12345678,通过0x12345678找到0x10;
    访问0x11这个数的流程是,直接找到0x87654321,然后通过0x87654321找到0x11;
    所以能很好的解释如下的问题:
问题1:
    文件1中定义如下:
    char a[100];
    文件2中声明如下:
    extern char *a;
    这是错误的。
问题2:
    文件1
    char *p = “abcdefg”;
    文件2
    extern char p[];
    这是错误的。
    总结:
    

指针数组与数组指针


参考文献

    《C语言深度剖析》
原创粉丝点击