• C和指针 第10章 结构和联合 10.2 结构、指针和成员


    10.2 结构、指针和成员
        直接或通过指针访问结构和它们的成员操作符是相当简单的。但是当它们应用于复杂的情形时就有可能引起混淆。下面是几个能帮助大家更好地理解这两个操作符的工作原理的例子。这些例子的声明如下:
        typedef struct {
            int a;
            short b[2];
        } Ex2;
        typedef struct EX {
            int a;
            char b[3];
            Ex2 c;
            struct EX *d;
        } Ex;
        类型为EX的结构可以用下面的图表示。
        事实上,上图并不完全准确,因为编译器只要有可能,就会设法避免成员之间的浪费空间。
        第一个例子将使用这些声明:
        Ex x = { 10, "Hi", { 5, { -1, 25 } }, 0 };
        Ex *px = &x;

    /*
    **结构、指针和成员。 
    */
    #include <stdio.h>
    #include <stdlib.h>

    typedef struct {
        int a;
        short b[2];
    } Ex2;
    typedef struct EX {
        int a;
        char b[3];
        Ex2 c;
        struct EX *d;
    } Ex;

    int main( void ){
        Ex x = { 10, "Hi", { 5, { -1, 25 } }, 0 };
        Ex *px = &x;
        
        printf( "x.a = %d, x.b = %s, x.c.a = %d, x.c.b[0] = %d, x.c.b[1] = %d, x.d = %p\n",
        x.a, x.b, x.c.a, x.c.b[0], x.c.b[1], x.d );
        printf( "(*px).a = %d, (*px).b = %s, (*px).c.a = %d, (*px).c.b[0] = %d, (*px).c.b[1] = %d, (*px).d = %p\n",
        (*px).a, (*px).b, (*px).c.a, (*px).c.b[0], (*px).c.b[1], (*px).d );
        printf( "px->a = %d, px->b = %s, px->c.a = %d, px->c.b[0] = %d, px->c.b[1] = %d, px->d = %p\n",
        px->a, px->b, px->c.a, px->c.b[0], px->c.b[1], px->d );
         
        return EXIT_SUCCESS;
    }
    /* 输出:

    */ 

    10.2.2 访问结构
        可以使用*操作符对指针执行间接访问。表达式*px的右值是px所指向的整个结构。
        间接访问操作随箭头访问结构,所以使用实线显示,其结果就是整个结构。可以把这个表达式赋值给另一个类型相同的结构,也可以把它作为点操作符的左操作数,访问一个指定的成员。也可以把它作为参数传递给函数,还可以把它作为函数的返回值返回(不过,最后这两个操作需要考虑效率问题,以后将会对此详述)。表达式*px的左值如下所示。
        这里,结构将接受一个新值,或者更精确地说,它将接受它的所有成员的新值。作为左值,重要的是位置,而不是这个位置所保存的值。
        表达式*px + 1是非法的,因为*px的结构是一个结构。C语言并没有定义结构和整型值之间的加法运算。但表达式*(px + 1)又如何呢?如果x是一个数组的元素,这个表达式表示它后面的那个结构。但是,x是一个标量,所以这个表达式实际上是非法的。

    /*
    ** 访问结构。 
    */
    #include <stdio.h>
    #include <stdlib.h>

    typedef struct {
        int a;
        short b[2];
    } Ex2;
    typedef struct EX {
        int a;
        char b[3];
        Ex2 c;
        struct EX *d;
    } Ex;

    int main( void ){
        Ex x = { 10, "Hi", { 5, { -1, 25 } }, 0 };
        Ex x2 = { 11, "Hj", { 6, { 0, 26 } }, 0 };
        Ex array[2];
        Ex *px = &x;
        Ex *pa = array;
        Ex x3;
        
        printf( "x.a = %d, x.b = %s, x.c.a = %d, x.c.b[0] = %d, x.c.b[1] = %d, x.d = %p\n",
        x.a, x.b, x.c.a, x.c.b[0], x.c.b[1], x.d );
        printf( "(*px).a = %d, (*px).b = %s, (*px).c.a = %d, (*px).c.b[0] = %d, (*px).c.b[1] = %d, (*px).d = %p\n",
        (*px).a, (*px).b, (*px).c.a, (*px).c.b[0], (*px).c.b[1], (*px).d );
        
        /* 
        ** can't pass compilation.
        ** *px + 1;
        ** because C language doesn't definite the addition operation between integer and struct.
        */
        array[0] = x;
        array[1] = x2;
        
        /*
        ** running error
        ** In actual, it is a illegal behavior.
        ** because px points to a single variable, not a element in array.
        ** x3 = *(px + 1);
        */
        Ex x4 = *(pa + 1);
        printf( "after x4 = *(pa + 1):\n" ); 
        printf( "x4.a = %d, x4.b = %s, x4.c.a = %d, x4.c.b[0] = %d, x4.c.b[1] = %d, x4.d = %p\n",
        x4.a, x4.b, x4.c.a, x4.c.b[0], x4.c.b[1], x4.d );
         
        return EXIT_SUCCESS;
    }
    /* 输出:

    */ 

    10.2.3 访问结构成员
        看一下箭头操作符。表达式px->a的右值如下所示。
        ->操作符对px执行间接访问操作(由实现箭头表示),它首先得到它所指向的结构,然后访问成员a。如果拥有一个指向结构的指针但又不知道结构的名字,便可以使用表达式px->a。如果知道这个结构的名字,也可以使用功能相同的表达式x.a。
        在此我们稍作停顿,相互比较一下表达式*px和px->a。在这两个表达式中,px所保存的地址都用于寻找这个结构。但结构的第一个成员是a,所以a的地址和结构的地址是一样的。这样px看上去是指向整个结构,同时指向结构的第1个成员;毕竟,它们具有相同的地址。
    但是,这个分析只有一半是正确的。尽管两个地址是相等的,但它们的类型不同。变量px被声明为一个指向结构的指针,所以表达式*px的结果是整个结构,而不是它的第1个成员。
        创建一个指向整型的指针:
        int *pi;
        能不能让pi指向整型成员a?如果pi的值和px相同,那么表达式*pi的结果将是成员a。但是,表达式
        pi = px;
        是非法的,因为它们的类型不匹配。使用强制类型转换就能奏效:
        pi = (int *)px;
        但这种方式是危险的,因为它避开了编译器的类型检查。正确的表达式更为简单---使用&操作符取得一个指向px->a的指针:
        pi = &px->a;
        ->操作符的优先级高于&操作符的优先级,所以这个表达式无须使用括号。
        注意椭圆里的值是如何直接访问结构的成员a的,这与px相反,后者指向整个结构。在上面的赋值操作之后,pi和px具有相同的值。但它们的类型是不同的,所以对它们使用间接访问操作所获得到的结果也不一样:*px的结果是整个结构,*pi的结果是一个单一的整型值。
        这里还有一个使用箭头操作符的例子。表达式px->b的值是一个指针常量,因为b是一个数组。这个表达式不是一个合法的左值。
    下面是它的右值。
        如果对这个表达式执行间接访问操作,它将访问数组的第1个元素。使用下标引用或指针运算,还可以访问数组的其他元素。表达式px->b[1]访问数组的第2个元素,如下所示。

    /*
    ** 访问结构成员。 
    */
    #include <stdio.h>
    #include <stdlib.h>

    typedef struct {
        int a;
        short b[2];
    } Ex2;
    typedef struct EX {
        int a;
        char b[3];
        Ex2 c;
        struct EX *d;
    } Ex;

    int main( void ){
        Ex x = { 10, "Hi", { 5, { -1, 25 } }, 0 };
        Ex *px = &x;
        int *pi;
        
        printf( "x.a = %d, x.b = %s, x.c.a = %d, x.c.b[0] = %d, x.c.b[1] = %d, x.d = %p\n",
        x.a, x.b, x.c.a, x.c.b[0], x.c.b[1], x.d );
        printf( "px->a = %d, px->b = %s, px->c.a = %d, px->c.b[0] = %d, px->c.b[1] = %d, px->d = %p\n",
        px->a, px->b, px->c.a, px->c.b[0], px->c.b[1], px->d );
        pi = &x.a;
        printf( "after px = &x.a, pi = %p, px = %p\n", pi, px );
        /*
        ** can't pass compilation.
        ** because pi is a pointer points to an int, px is a pointer points to an Ex variable.
        ** type conversion fails.
        ** pi = px;
        */
        /*
        ** can pass compilation.
        ** It is a dangerous conversion, because it avoids the type check of compiler.
        ** pi = (int *)px;
        ** a good conversion is follow:
        ** pi = &px->a;
        ** or
        ** pi = &x.a;
        */
        printf( "(*px).a = %d, (*px).b = %s, (*px).c.a = %d, (*px).c.b[0] = %d, (*px).c.b[1] = %d, (*px).d = %p\n",
        (*px).a, (*px).b, (*px).c.a, (*px).c.b[0], (*px).c.b[1], (*px).d );
        printf( "*pi = %d\n", *pi ); 
         
        return EXIT_SUCCESS;
    }
    /* 输出:

    */ 

    10.2.4 访问嵌套的结构
        为了访问本身也是结构的成员c,可以使用表达式px->c。它的左值是整个结构。
        这个表达式可以使用点操作符访问c的特定成员。例如,表达式px->c.a具有下面的右值:
        这个表达式包含了点操作符,也包含了箭头操作符。之所以使用箭头操作符,是因为px并不是一个结构,而是一个指向结构的指针。接下来之所以要使用点操作符,是因为px->c的结构并不是一个指针,而是一个结构。
        这里有一个更为复杂的表达式:
        *px->c.b
        我们逐步分析。它有3个操作符,首先指向的是箭头操作符。px->c的结构是结构c。在表达式中增加.b来访问结构c的成员b。b是一个数组,所以px->c.b的结果是一个指针常量,它指向数组的第1个元素。最后对这个指针指向间接访问,所以表达式的最终结果是数组的第1个元素。这个表达式可以图解为如下形式。

    /*
    **访问嵌套的结构。 
    */
    #include <stdio.h>
    #include <stdlib.h>

    typedef struct {
        int a;
        short b[2];
    } Ex2;
    typedef struct EX {
        int a;
        char b[3];
        Ex2 c;
        struct EX *d;
    } Ex;

    int main( void ){
        Ex x = { 10, "Hi", { 5, { -1, 25 } }, 0 };
        Ex *px = &x;
        
        printf( "x.a = %d, x.b = %s, x.c.a = %d, x.c.b[0] = %d, x.c.b[1] = %d, x.d = %p\n",
        x.a, x.b, x.c.a, x.c.b[0], x.c.b[1], x.d );
        printf( "px->a = %d, px->b = %s, px->c.a = %d, *px->c.b = %d, *(px->c.b + 1) = %d, px->d = %p\n",
        px->a, px->b, px->c.a, *px->c.b, *(px->c.b + 1), px->d );
         
        return EXIT_SUCCESS;
    }
    /* 输出:

    */ 

        10.2.5 访问指针成员
        表达式px->d的结果如我们所料---它的右值是0,它的左值是它本身的内存位置。表达式*px->d更为有趣。这里间接访问操作符作用于成员d所存储的指针值。但d包含了一个NULL指针,所以它不指向任何东西。对一个NULL指针进行解引用操作是个错误,但正如以前讨论的那样,有些环境不会再运行时捕捉到这个错误。在这些机器上,程序将访问内存位置0的内容,把它也当做是结构成员之一,如果系统未发现错误,它还将继续下去。这个例子说明,对指针进行解引用操作之前检查一下它是否有效是非常重要的。创建另一个结构,并把x.d设置为指向它:
        Ex y;
        x.d = &y;
        现在可以对表达式*px->d求值。
        成员d指向一个结构,所以对它执行间接访问操作的结果是整个结构。这个新的结构并没有显式地初始化,所以在图中并没有显示它的成员的值。
        正如我们可以预料的那样,这个新结构的程序可以通过在表达式中增加更多的操作符进行访问。我们使用箭头操作符,因为d是一个指向结构的指针。下面这些表达式是执行什么任务的呢?
        px->d->a;
        px->d->b;
        px->d->c;
        px->d->c.a;
        px->d->c.b[1]; 

    /*
    ** 访问指针成员。 
    */
    #include <stdio.h>
    #include <stdlib.h>

    typedef struct {
        int a;
        short b[2];
    } Ex2;
    typedef struct EX {
        int a;
        char b[3];
        Ex2 c;
        struct EX *d;
    } Ex;

    int main( void ){
        Ex x = { 10, "Hi", { 5, { -1, 25 } }, 0 };
        Ex *px = &x;
        Ex x2 = { 11, "Hj", { 6, { 0, 26 } }, 0 };
        
        printf( "x.a = %d, x.b = %s, x.c.a = %d, x.c.b[0] = %d, x.c.b[1] = %d, x.d = %p\n",
        x.a, x.b, x.c.a, x.c.b[0], x.c.b[1], x.d );
        printf( "px->a = %d, px->b = %s, px->c.a = %d, px->c.b[0] = %d, px->c.b[1] = %d, px->d = %p\n",
        px->a, px->b, px->c.a, px->c.b[0], px->c.b[1], px->d );
        x.d = &x2;
        printf( "px->d->a = %d, px->d->b = %s, px->d->c.a = %d, px->d->c.b[0] = %d, px->d->c.b[1] = %d, px->d->d = %p\n",
        px->d->a, px->d->b, px->d->c.a, px->d->c.b[0], px->d->c.b[1], px->d->d );
         
        return EXIT_SUCCESS;
    }
    /* 输出:

    */ 

  • 相关阅读:
    QT+OSG/osgEarth编译之二十五:GeoTIFF+Qt编译(一套代码、一套框架,跨平台编译,版本:GeoTIFF-2.12.1)
    猿创征文 |【Ant Design Pro】使用ant design pro做为你的开发模板(二)新增一个页面与如何添加国际化
    前端数字计算精度问题
    Spring框架详解
    AdvanCell完成由晨兴创投领投的1,800万澳元B轮融资
    测试开发:10分钟Flask快速入门【建议收藏】
    EdgeMoE: Fast On-Device Inference of MoE-based Large Language Models
    计算机网络各层协议总结
    vue3 中的ref、reactive的介绍
    nginx https 如何将部分路径转移到 http
  • 原文地址:https://blog.csdn.net/weixin_40186813/article/details/125451582