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

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

目录

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语言进阶⑯(自定义类型)项目:静态通讯录,增删查改排序打印。_

代码下载地址: https://pan.quark.cn/s/a4b39357ea24 在计算机视觉技术中,数据集扮演着训练和评估模型的核心角色。Labelme作为一个广受欢迎的开源工具,能够支持用户以交互方式对图像进行标注,而COCO(Common Objects in Context)则是一种被广泛采纳的数据集标准格式,适用于包括物体检测、图像分割在内的多种任务。本文将详细阐述如何将Labelme生成的标注数据转换为COCO数据集的标准格式。 Labelme标注的图像在输出为JSON格式时,会包含以下核心内容: 1. `version`: 指明JSON文件的版本信息。 2. `flags`: 目前未定义或保持为空,预留用于未来的功能扩展。 3. `shapes`: 列表形式存储对象的形状信息,每个形状项包含`label`(对象类别名称),`points`(构成对象边缘的多边形顶点),以及`shape_type`(通常为“polygon”)。 4. `imagePath`和`imageData`: 提供原始图像的存储路径和二进制数据,便于后续图像的还原。 5. `imageHeight`和`imageWidth`: 明确标注图像的垂直和水平尺寸。 COCO数据集的标准格式中定义了三种主要的标注类型: 1. Object instances(目标实例):主要用于执行物体检测任务。 2. Object keypoints(目标上的关键点):适用于人体姿态估计相关应用。 3. Image captions(看图说话):用于生成图像的文本描述。 COCO的JSON结构中包含以下基本组成部分: 1. `images`:记录图像的基本属性,包括`height`(高度)、`...
内容概要:本文围绕基于Basisformer模型的时间序列锂离子电池SOC(State of Charge,荷电状态)预测展开研究,利用PyTorch深度学习框架构建并训练模型,旨在提升锂电池SOC估计的准确性与鲁棒性。该方法融合Transformer架构的核心机制,通过引入基函数(Basis)分解策略,有效捕捉电池充放电过程中长时序、非线性动态特征,增强模型对复杂工况的适应能力。研究不仅详细阐述了Basisformer的网络结构设计、注意力机制优化与训练流程,还提供了完整的Python代码实现方案,涵盖数据预处理、模型搭建、损失函数定义、训练验证及结果可视化等环节,便于科研人员快速复现、调优并拓展至其他电池状态预测任务。; 适合人群:具备一定深度学习与Python编程基础,熟悉PyTorch框架,从事电池管理系统(BMS)、新能源汽车、储能系统、智能传感等领域的高校研究生、科研人员及工程技术人员。; 使用场景及目标:①应用于动力电池与储能系统的实时SOC估算模块,提升系统安全性与能量利用效率;②作为学术研究的基础模型,用于复现、改进基于Transformer的时间序列预测方法在电化学系统中的应用;③为数据驱动的电池健康状态(SOH)、剩余使用寿命(RUL)联合估计提供可扩展的技术框架。; 阅读建议:建议读者结合所提供的代码与公开电池数据集(如NASA、CALCE等)进行动手实践,深入理解模型的输入输出结构与时序建模逻辑,同时可尝试引入温度、老化周期等多维特征,或融合物理模型构建混合预测架构,以进一步提升预测精度与泛化能力。
内容概要:本文系统阐述了基于动态规划算法优化插电式混合动力电动汽车(PHEV)能源管理的技术方案,结合Matlab与Simulink工具实现完整的仿真建模与代码开发。通过动态规划这一全局优化方法,在已知驾驶循环条件下,精确求解发动机、电机及电池之间的最优能量分配策略,以实现燃油消耗与排放的最小化目标,解决PHEV多能源路径规划中的复杂决策问题。文中提供了详尽的仿真模型构建流程与算法实现步骤,涵盖车辆动力学建模、能量管理架构设计、状态空间定义、代价函数构造、最优控制律求解及结果可视化分析等关键环节,全面揭示PHEV能量管理系统的内在机制与优化逻辑。; 适合人群:具备一定Matlab/Simulink编程基础,从事新能源汽车、智能控制、电力电子、自动化或交通运输工程等相关领域的研究生、科研人员及工程技术人员,尤其适合专注于车辆能量管理策略、节能控制算法研究的专业人士。; 使用场景及目标:①深入掌握动态规划在混合动力汽车能量管理中的理论基础与工程实现方法;②学习如何在Matlab/Simulink环境中搭建PHEV整车仿真平台并实施多目标优化仿真;③为学术研究、学位论文撰写或实际工程项目提供可复用的算法框架、模型模板与技术支持,支撑后续对等效燃油消耗最小化策略(ECMS)、模型预测控制(MPC)、实时优化算法等的对比研究与性能评估。; 阅读建议:建议读者结合所提供的完整代码与Simulink模型文件,逐模块调试运行,重点理解状态变量离散化处理、前后向递推求解过程、惩罚项设置以及边界条件处理等核心技术细节,同时可进一步拓展应用于不同工况场景、不同车型结构或与其他优化算法(如庞特里亚金极小值原理PMP)的对比验证,从而深化对PHEV能量管理实时性与全局性平衡问题的理解。
内容概要:本文围绕基于多虚拟同步发电机(VSG)的独立微网系统,开展多目标二次控制策略的MATLAB/Simulink建模与仿真研究。通过构建包含多个VSG单元的独立微网系统,设计并实现了能够同时实现频率与电压的无静差恢复、有功/无功功率精确分配以及环流有效抑制的综合控制目标的二次控制方法。研究重点在于控制策略的整体架构设计、关键控制模块的数学建模及其在Simulink环境中的精细化实现,通过大量仿真实验验证了所提控制策略在不同工况下的有效性、动态响应性能及系统鲁棒性。; 适合人群:具备电力系统分析、自动控制理论及现代电力电子技术等专业知识背景,熟悉MATLAB/Simulink仿真工具,从事新能源发电、微电网运行与控制、分布式能源系统集成等相关领域的科研人员、工程技术人员及高校研究生。; 使用场景及目标:① 深入掌握多VSG独立微网系统的建模方法与稳定性分析要点;② 理解并复现兼顾静态精度与动态品质的多目标二次协同控制算法;③ 为新型微网控制保护装置的研发及先进控制策略的工程化应用提供可靠的仿真验证平台和技术储备。; 阅读建议:学习者应在巩固电力系统基础理论的前提下,重点关注控制算法的设计逻辑、各控制环节间的耦合关系以及Simulink模块的搭建技巧,建议通过调整系统参数、设置不同的负载投切与故障扰动工况进行反复仿真,以深刻理解控制策略的内在机理与适应能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

GR鲸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值