C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)

本文详细介绍了C语言中的结构体(struct)、位段(bitfield)、枚举(enumerate)和联合体(union),包括它们的声明、初始化、内存对齐、应用以及相关编程示例。结构体的内存对齐规则、位段的内存分配、枚举的使用场景和联合体在大小端判断中的作用都有深入讲解。同时,文章还包含了相关笔试题,帮助读者巩固理解和应用这些自定义类型。
摘要由CSDN通过智能技术生成

目录

1.结构体(struct)

1.1 结构的基础知识

1.2 结构的声明

1.3 匿名结构体

1.4 结构的自引用

1.5 结构体变量的定义和初始化

1.6 结构体内存对齐

1.7 修改默认对齐数

1.8 结构体传参

2. 位段(bit field)

2.1 什么是位段

2.2 位段的内存分配

2.3 位段的跨平台问题

2.4 位段的应用

3.枚举(enumerate)

3.1 枚举类型的定义

3.2 枚举的优点

3.3 枚举的使用

3.4实际运用演示(计算器)

4. 联合体(union)(共用体)

4.1 联合类型的定义

4.2联合体的初始化

4.3联合体大小的计算

4.4实际运用演示(大小端)

5.笔试题

5.1第一题

5.2第二题

5.3第三题

5.4第四题

5.5第五题

5.6第六题

5.7第七题

5.8第八题


本篇将对C语言自定义类型进行讲解

1.结构体(struct)

前面简单讲过结构体,这里将会把前面结构体还没讲完的知识继续补充。复习链接:

C语言初阶⑧(结构体)知识点和作业_GR C的博客-CSDN博客

1.1 结构的基础知识

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

1.2 结构的声明


struct tag  //结构体关键字+标签  合起来是结构体类型
{
    member - list;   //成员变量列表
}variable - list;   //变量列表

例如描述一个学生:


struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
}; //分号不能丢

1.3 匿名结构体

//匿名结构体类型(一次性使用)


struct
{
    int a;
    char b;
    float c;
    double d;
} s;
 
struct
{
    int a;
    char b;
    float c;
    double d;
} *ps;

对于上面的代码如果进行如下操作,是非法的


int main()
{
    ps = &s; // error
    return 0;
}

1.4 结构的自引用

介绍:结构体中包含一个类型为该结构体本身的成员,包含同类型的结构体指针(不是包含同类型的结构体变量)


struct A
{
    int i;
    char c;
};
 
struct B
{
    char c;
    struct A sa;
    double d;
};

注意事项1:结构体不能自己包含自己,不能包含同类型的结构体变量


struct N
{
    int d;
    struct N n; //结构体里不能存在结构体自己类型的成员
};

为了加深理解,先引入一下数据结构的一些知识:

注意事项2:结构体自引用时,不要用匿名结构体:


struct  // 如果省略结构体名字
{
    int data;
    struct Node* next; // 这里的 struct Node* 是哪里来的?
};

即使使用 typedef 重新取名为 Node,也是不行的。因为要产生 Node 必须先有结构体类型之后才能重命名 Node,即先 Node* next 定义完成员之后才 typedef 才能对这个类型重命名为 Node。

所以这种方式仍然是不行的:


typedef struct
{
    int data;
    Node* next; // 先有鸡还是先有蛋???
} Node;

正确方法:


typedef struct Node
{
    int data;
    struct Node* next;
} Node;

1.5 结构体变量的定义和初始化

有了结构体类型,那如何定义变量,其实很简单。


struct S
{
    char c;
    int i;
} s1, s2; // 声明类型的同时创建变量
 
int main()
{
    struct S s3, s4;
 
    return 0;
}

创建变量的同时赋值(初始化)


struct S
{
    char c;
    int i;
} s1, s2;
 
int main()
{
    struct S s3 = {'x', 20};
//                  c    i
 
    return 0;
}

结构体包含结构体的初始化方法:


struct S
{
    char c;
    int i;
} s1, s2;
 
struct B
{
    double d;
    struct S s;
    char c;
};
 
int main()
{
    struct B sb = {3.14, {'w', 100}, 'q'};
    printf("%lf %c %d %c\n", sb.d, sb.s.c, sb.s.i, sb.c);
    
    return 0;
}

1.6 结构体内存对齐

现在已经掌握了结构体的基本使用了。

深入讨论一个问题:计算结构体的大小。

这也是一个特别热门的考点: 结构体内存对齐

首先得掌握结构体的对齐规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的值为8)

3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整

体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。


//练习1
#include <stdio.h>
struct S
{
     char c1;
     int i;
     char c2;
};
int main()
{
    struct S s = {0};
    printf("%d\n", sizeof(s));//12
    return 0;
}

//练习2
struct S2
{
    char c1;
    char c2;
    int i;
};
printf("%d\n", sizeof(struct S2));//8

//练习3
struct S3
{
    double d;//8
    char c;//1
    int i;//4
};
printf("%d\n", sizeof(struct S3));//16

//练习4-结构体嵌套问题
#include <stdio.h>
struct S4
{
    double d;
    char c;
    int i;
};
struct S5
{
    char c1;
    struct S4 s4;
    double d;
};
int main()
{
    struct S5 s5 = {0};
    printf("%d\n", sizeof(s5));
    return 0;
}
//上面第4点: 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
//结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
//此题嵌套的结构体对齐到自己的最大对齐数就是8(所以浪费了下面的7个字节)且32是8的倍数

为什么存在内存对齐? 大部分的书籍都是这样说的:

1. 平台原因(移植原因)

不是所有的硬件平台都能访问任意地址上的任意数据的;

某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2. 性能原因

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;

而对齐的内存访问仅需要一次访问。

总体来说:

结构体的内存对齐是拿空间来换取时间的做法。

那在设计结构体的时候,既要满足对齐,又要节省空间,如何做到:

让占用空间小的成员尽量集中在一起。


//例如:
struct S1
{
    char c1;
    int i;
    char c2;
};

struct S2
{
    char c1;
    char c2;
    int i;
};
//S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。
//S1大小是12 而S2大小是8

1.7 修改默认对齐数

之前我们见过了 #pragma 这个预处理指令,这里再次使用,可以改变默认对齐数。


#include <stdio.h>
// 默认对齐数是8
#pragma pack(2) // 把默认对齐数改为2(一般改为2的几次方)
struct S
{
    char c1; //1
    int i; // 4
    char c2; // 1
};
#pragma pack() // 取消
int main()
{
    printf("%d\n", sizeof(struct S)); //修改默认对齐数后12变为8
    return 0;
}

所以在结构在对齐方式不合适的时候,可以自己更改默认对齐数。


百度笔试题:

写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明

注:这里还没学习宏,可以放在宏讲解完后再自己实现。

考察: offsetof 宏的实现

该宏用于求结构体中一个成员在该结构体中的偏移量。

头文件: stddef.h

使用方法演示:


#include <stdio.h>
#include <stddef.h>
struct S
{
    char c1; //1
    int i; // 4
    char c2; // 1
};
int main()
{
    printf("%d\n", offsetof(struct S, c1));//0
    printf("%d\n", offsetof(struct S, i));//4
    printf("%d\n", offsetof(struct S, c2));//8
    return 0;
}

1.8 结构体传参

直接看代码:


#include <stdio.h>
struct S
{
    int data[1000];
    int num;
};
struct S s = { {1, 2, 3, 4}, 1000 };
//结构体传参
void print1(struct S s)
{
    printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
    printf("%d\n", ps->num);
}
int main()
{
    print1(s);  //传结构体
    print2(&s); //传地址
    return 0;
}

上面的 print1 和 print2 函数哪个好些?

答案是:首选print2函数。

原因:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

所以:结构体传参的时候,尽量传结构体的地址。


2. 位段(bit field)

结构体讲完就得讲讲结构体实现 位段 的能力

2.1 什么是位段

定义:位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,

这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。

位段的声明和结构体是类似的,有两个不同:

① 位段的成员只能是: int、unsigned int、signed int

② 位段的成员名后面有一个冒号和一个数字:member_name : number

比如:


struct A
{
    int _a:2;
    int _b:5;
    int _c:10;
    int _d:30;
}
// A就是一个位段类型

那么问题来了,位段A的大小是多少?


#include <stdio.h>
struct A
{
    int _a:2;  // _a 成员占2个比特位
    int _b:5;  // _b 成员占5个比特位
    int _c:10; // _c 成员占10个比特位
    int _d:30; // _d 成员占30个比特位
};
int main()
{
    printf("%d\n", sizeof(struct A));//8
    return 0;
}

运行结果居然是8,四个成员占47个比特位,而8个字节是64个比特位,为什么会这样呢?

看看下面的位段的内存分配:

2.2 位段的内存分配

位段的意义:位段在一定程度上帮助我们节省空间。

① 位段的成员可以是 int、unsigned int、signed int 或者是 char (属于整形家族)类型。

② 位段的空间上是 按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

③ 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

空间是如何开辟的?


struct S
{
    char a : 3;
    char b : 4;
    char c : 5;
    char d : 4;
};
 
int main()
{
    struct S s = { 0 };
    s.a = 10;
    s.b = 12;
    s.c = 3;
    s.d = 4;
}

(VS2019环境下)

2.3 位段的跨平台问题

1. int 位段被当成有符号数还是无符号数是不确定的。

2. 位段中最大位的数目不能确定。

(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。)

3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,

是舍弃剩余的位还是利用,这是不确定的。

总结:

跟结构相比,位段可以达到同样的效果,而且可以很好的节省空间,但是有跨平台的问题存在。

2.4 位段的应用

在网络底层的引用:(IP分装包的一种格式)


3.枚举(enumerate)

在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。
是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。 [ 百度百科 ]

枚举,顾名思义就是壹壹列举,把可能的取值壹壹列举。

一年有12个月,可以把每个月都壹壹列举。

3.1 枚举类型的定义


enum Day //星期
{
    // 枚举常量
    Mon,
    Tues,
    Wed,
    Thur,
    Fri,
    Sat,
    Sun
};
 
enum Color //颜色
{
    // 枚举常量
    RED,
    GREEN,
    BLUE
};

以上定义的 enum Day , enum Color 都是枚举类型。

{ }中的内容是枚举类型的可能取值,也叫 枚举常量 。

枚举的内容一般和#define 定义的常量一样,用大写

这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。

例如:


#include <stdio.h>
enum Color //颜色
{
    RED = 1,
    GREEN = 2,
    BLUE = 4
};

int main()
{
    enum Color c = GREEN;
    printf("%d\n", c);//2
    c = 5;
    printf("%d\n", c);//5
    c = BLUE;
    printf("%d\n", c);//4
    return 0;
}

3.2 枚举的优点

为什么使用枚举?

可以使用 #define 定义常量,为什么非要使用枚举?

枚举的优点:

1. 增加代码的可读性和可维护性

2. 和#define定义的标识符比较枚举有类型检查,更加严谨。

3. 防止了命名污染(封装)

4. 便于调试

5. 使用方便,一次可以定义多个常量

3.3 枚举的使用

注意事项:

① 默认从0开始,依次递增1。(可赋初值,上面赋值如果下面不赋,随上一个赋的值 +1 )

② 枚举常量是不能改变的。 (MALE = 3 error!)


#include <stdio.h>
enum Color//颜色
{
    // 枚举常量
    RED = 3,// 赋初值为3
    GREEN,// 不赋初值,默认随上一个枚举常量,+1为4
    BLUE// +1为5
};
int main(void)
{
    enum Sex s = RED;
    printf("%d\n", RED);//3
    // RED= 3 error  不可修改
    printf("%d\n", GREEN);//4
    printf("%d\n", BLUE);//5
    return 0;
}

③ 枚举常量虽然是不能改变的,但是通过枚举常量创造出来的变量是可以改变的


#include <stdio.h>
enum Color
{
    // 枚举常量
    RED,
    YEELOW,
    BULE
};
int main(void)
{
    enum Color c = BULE; // 我们创建一个变量c,并将BULE赋给它
    printf("%d\n", c);//2
    printf("%d\n", BULE);//2
    c = YEELOW; // 这时将YEELOW赋给它,完全没有问题
    //BULE = 6; // error!枚举常量是不能改变的
    printf("%d\n", c);//1
    return 0;
}

3.4实际运用演示(计算器)

之前在实现计算器的时候是这么写代码的:(仅演示部分代码)


#include <stdio.h>
void menu()
{
    printf("*****************************\n");
    printf("**    1. add     2. sub    **\n");
    printf("**    3. mul     4. div    **\n");
    printf("**         0. exit         **\n");
    printf("*****************************\n");
}
int main()
{
    int input = 0;
    do
    {
        menu();
        printf("请选择:> ");
        scanf("%d", &input);
        switch(input)
        {
        case 1: // 替换后就好多了,代码的可读性大大增加
            printf("+\n");
            break;
        case 2:
            printf("-\n");
            break;
        case 3:
            printf("*\n");
            break;
        case 4:
            printf("/\n");
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误,重新选择\n");
            break;
        }
    } while (input);
    return 0;
}

阅读代码的时候如果不看上面的 menu,是很难知道 case 中的 12340 分别是什么的。1 为什么是加?2 为什么是减?看到数字的时候联想不到它的到底是干什么的。

为了提高代码的可读性,我们可以使用枚举来解决:


#include <stdio.h>
void menu()
{
    printf("*****************************\n");
    printf("**    1. add     2. sub    **\n");
    printf("**    3. mul     4. div    **\n");
    printf("**         0. exit         **\n");
    printf("*****************************\n");
}
enum Option
{
    EXIT, // 0
    ADD,  // 1
    SUB,  // 2
    MUL,  // 3
    DIV,  // 4
};
int main()
{
    int input = 0;
    do
    {
        menu();
        printf("请选择:> ");
        scanf("%d", &input);
        switch(input)
        {
        case ADD: // 替换后就好多了,代码的可读性大大增加
            printf("+\n");
            break;
        case SUB:
            printf("-\n");
            break;
        case MUL:
            printf("*\n");
            break;
        case DIV:
            printf("/\n");
            break;
        case EXIT:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误,重新选择\n");
            break;
        }
    } while (input);
    return 0;
}

4. 联合体(union)(共用体)

4.1 联合类型的定义

联合也是一种特殊的自定义类型

可以在相同的内存位置存储不同的数据类型。

可以定义一个带有多成员的联合体,但是任何时候只能有一个成员带有值。

这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。

比如


#include <stdio.h>
union Un
{
    char c; // 1
    int i; // 4
};
int main()
{
    union Un u; // 创建一个联合体变量
    printf("%d\n", sizeof(u)); // 计算联合体变量的大小 打印出了4
    return 0;
}

为什么是4个字节呢?来试着观察下它的内存:


#include <stdio.h>
union Un
{
    char c; // 1
    int i; // 4
};
int main()
{
    union Un u;
    printf("%p\n", &u);
    printf("%p\n", &(u.c));
    printf("%p\n", &(u.i));   //发现三个输出的地址都是一样的
    return 0;
}

运行结果如下:

//发现三个输出的地址都是一样的

结论:联合体的成员是共用同一块内存空间的。因为联合至少要有保存最大的那个成员的能力,

所以一个联合变量的大小至少是最大成员的大小。

4.2联合体的初始化


#include <stdio.h>
union Un
{
    char c; // 1
    int i; // 4
};
int main()
{
    union Un u = {10};
    return 0;
}

调试:打开监视后,我们可以看到 i 和 c 是是共用一个10的:

如果想在每个成员里放上独立的值呢?


#include <stdio.h>
union Un
{
    char c; // 1
    int i; // 4
};
int main()
{
    union Un u = {10}; 
    u.i = 1000;   
    u.c = 100;
    return 0;
}

观察调试过程:

结论:在同一时间内你只可以使用联合体中的一个成员。

4.3联合体大小的计算


#include <stdio.h>
union Un
{
    char a[5]; // 5个元素,一共5个字节
    int i; // 4  把int 改成char下面输出就是5(和上面共用)
};
int main()
{
    union Un u;
    printf("%d\n", sizeof(u));//8
    return 0;
}

为什么又是8个字节了?

其实联合体也是存在对齐的,我们来更加系统地、详细的探究下联合体的大小规则:

联合体大小的计算:

① 联合的大小至少是最大成员的大小。

② 当最大成员的大小不是最大对齐数的整数倍时,对要对齐到最大对齐数的整数倍。


union Un
{
    char a[5]; // 对齐数是1
    int i; // 对齐数是4
};
// 所以最后取了8个字节为该联合体的大小

4.4实际运用演示(大小端)

大小端复习:C语言进阶⑩(数据的存储)(知识点+练习+作业)_GR C的博客-CSDN博客

之前学的方法:


#include <stdio.h>
int check_sys()
{
    int a = 1;
    return *(char*)&a;//返回1是小端,返回0是大端
}
int main()
{
    int ret = check_sys();
    if (ret == 1)
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
    return 0;
}

通过联合体的方式判断: (通过深刻理解联合体特点写出来的代码)


#include <stdio.h>
int check_sys()
{
    union U 
    {
        char c;
        int i;
    } u;
    u.i = 1;
    return u.c;//此时c和i共用字节,而c在i的第一个字节上
    // 返回1 就是小端
    // 返回0 就是大端
}
int main()
{
    int ret = check_sys();
    if (ret == 1)
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
    return 0;
}

5.笔试题

5.1第一题

在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是( )


struct A
{
    int a;
    short b;
    int c;
    char d;
};
struct B
{
    int a;
    short b;
    char c;
    int d;
};

解析:


struct A
{
    int a;  //4
    short b;  //2
    int c;   //4
    char d;   //1   4+2+2(浪费)+4+4+1+3(浪费)=16
};
struct B
{
    int a;  //4
    short b;//2
    char c;//1
    int d;//4   4+2+2(浪费)+1+3(浪费)+4=12
};

5.2第二题

下面代码的结果是:( )


#include<stdio.h>
#pragma pack(4)/*编译选项,表示4字节对齐 平台:VS2013。语言:C语言*/
int main()
{
    struct tagTest1
    {
        short a;
        char d;
        long b;
        long c;
    };
    struct tagTest2
    {
        long b;
        short c;
        char d;
        long a;
    };
    struct tagTest3
    {
        short c;
        long b;
        char d;
        long a;
    };
    struct tagTest1 stT1;
    struct tagTest2 stT2;
    struct tagTest3 stT3;

    printf("%d %d %d", sizeof(stT1), sizeof(stT2), sizeof(stT3));
    return 0;
}
#pragma pack()

解析:


#pragma pack(4)/*编译选项,表示4字节对齐 平台:VS2013。语言:C语言*/
int main(int argc, char* argv[])
{
    struct tagTest1
    {
        short a;  // 2
        char d;   // 1 
        long b;   //4
        long c;   //4  2+1+1(浪费)+4+4=12
    };
    struct tagTest2
    {
        long b;  //4
        short c;  //2
        char d;   //1
        long a;   //4   4+2+1+1(浪费)+4=12
    };
    struct tagTest3
    {
        short c;  //2
        long b;   //4
        char d;   //1
        long a;   //4   2+2(浪费)+4+1+3(浪费)+4=16
    };
    struct tagTest1 stT1;
    struct tagTest2 stT2;
    struct tagTest3 stT3;

    printf("%d %d %d", sizeof(stT1), sizeof(stT2), sizeof(stT3));
    return 0;
}
#pragma pack()

5.3第三题

在VS2013下,这个结构体所占的空间大小是( )字节


typedef struct
{
    int a;
    char b;
    short c;
    short d;
}AA_t;

解析:


#include<stdio.h>
typedef struct
{
    int a;  //4
    char b;  //1
    short c;   //2
    short d;  //2    4+1+1(浪费)+2+2+2(浪费)=12
}AA_t;
int main()
{
    printf("%d\n", sizeof(AA_t));//12
    return 0;
}

5.4第四题

下面代码的结果是:( )


#include <stdio.h>
union Un
{
    short s[7];
    int n;
};
int main()
{
  printf("%d\n", sizeof(union Un));
  return 0;
}

解析:


#include <stdio.h>
union Un
{
    short s[7];   //2*7=14
    int n;   //4
};
int main()
{
    printf("%d\n", sizeof(union Un));//16  共用,且默认对齐数是4
    return 0;
}

5.5第五题

在X86下,有下列程序输出结果是( )


#include<stdio.h>
int main()
{
    union
    {
        short k;
        char i[2];
    }*s, a;
    s = &a;
    s->i[0] = 0x39;
    s->i[1] = 0x38;
    printf("%x\n", a.k);
    return 0;
}

解析:


#include<stdio.h>
int main()
{
    union
    {
        short k;//2
        char i[2];//2       //低地址    高地址
    }*s, a;                 //i[0]      i[1]
    s = &a;                 //一个空间  一个空间
    s->i[0] = 0x39;         //0x39      一个空间
    s->i[1] = 0x38;         //0x39      0x38
    printf("%x\n", a.k);    //3839 (小端存储)(地位放在低地址,高位放在高地址)
    return 0;
}

5.6第六题

下面代码的结果是( )


#include<stdio.h>
enum ENUM_A
{
    X1,
    Y1,
    Z1 = 255,
    A1,
    B1,
};
int main()
{
    enum ENUM_A enumA = Y1;
    enum ENUM_A enumB = B1;
    printf("%d %d\n", enumA, enumB);
    return 0;
}

解析:


#include<stdio.h>
enum ENUM_A
{
    X1,      //0
    Y1,      //1
    Z1 = 255,  //255
    A1,        //256
    B1,        //257
};
int main()
{
    enum ENUM_A enumA = Y1;
    enum ENUM_A enumB = B1;
    printf("%d %d\n", enumA, enumB);//  1 257
    return 0;
}

5.7第七题

下面代码的结果是( )


#include<stdio.h>
#include<string.h>
int main()
{
    unsigned char puc[4];
    struct tagPIM
    {
        unsigned char ucPim1;
        unsigned char ucData0 : 1;
        unsigned char ucData1 : 2;
        unsigned char ucData2 : 3;
    }*pstPimData;
    pstPimData = (struct tagPIM*)puc;
    memset(puc, 0, 4);
    pstPimData->ucPim1 = 2;
    pstPimData->ucData0 = 3;
    pstPimData->ucData1 = 4;
    pstPimData->ucData2 = 5;
    printf("%02x %02x %02x %02x\n", puc[0], puc[1], puc[2], puc[3]);
    return 0;
}

A.02 03 04 05

B.02 29 00 00

C.02 25 00 00

D.02 29 04 00

解析:

puc是一个char数组,每次跳转一个字节,结构体不是,它只有第一个元素单独享用一字节,

其他三个元素6个比特位一起共用一字节,所以puc被结构体填充后,本身只有两个字节会被写入,

后两个字节肯定是0,至此AD排除,然后第一个字节给2就是2了,第二个字节比较麻烦,

首先ucData0给了3其实是越界了,1位的数字只能是0或1,所以11截断后只有1,

同理ucData1给的4也是越界的,100截断后是00,只有5的101是正常的。

填充序列是类似小端的低地址在低位,所以排列顺序是00 101 00 1。也就是0010 1001,即0x29,

故选B

5.8第八题

有如下宏定义和结构定义当A=2, B=3时,pointer分配( )个字节的空间。

(其实是算结构体类型大小*2+3)


#define MAX_SIZE A+B
struct _Record_Struct
{
    unsigned char Env_Alarm_ID : 4;
    unsigned char Para1 : 2;
    unsigned char state;
    unsigned char avail : 1;
}*Env_Alarm_Record;
struct _Record_Struct* pointer = (struct _Record_Struct*)malloc
(sizeof(struct _Record_Struct) * MAX_SIZE);

解析:

结构体向最长的char对齐,前两个位段元素一共4+2比特位,不足8比特位,合起来占1字节,

第三个元素占一个字节,最后一个元素一个比特位单独1字节,一共3字节。

另外,#define执行的是查找替换, sizeof(struct _Record_Struct) * MAX_SIZE这个语句

其实是3*2+3,结果为9


本篇完。

下一篇:C语言进阶⑯(自定义类型)项目:静态通讯录,增删查改排序打印。

穿越回来贴个链接:C语言进阶⑯(自定义类型)项目:静态通讯录,增删查改排序打印。_

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GR鲸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值