嵌入式学习笔记DAY17(结构体、共用体、枚举、typedef、位运算)

发布于:2025-05-15 ⋅ 阅读:(12) ⋅ 点赞:(0)

一、结构体 

       在之前,我们学了很多数据类型int char …等,还学到了同类型元素构成的数组,以及这些数据类型的指针,在一些小应用可以灵活使用,然而,在实际应用中,每一种变量进行一次声明,再结合起来过于麻烦,类如一位学生的信息管理,他可能有,姓名(char),学号(int)成绩(float)等多种数据。如果把这些数据分别单独定义,就会特别松散、复杂,难以规划,因此我们需要把一些相关的变量组合起来,以一个整体形式对对象进行描述,这就是结构体的好处。

1. 结构体的相关概念

                    

  • 一般结构体的第一个字母大写;
  • 花括号之后要记得加上分号;
  • 该程序表示指定了一个新的结构体类型struct Student (struct 是声明结构体类型时所必须使用的关键字,不可省略),它向编译系统声明这是一个“结构体类型”,它包括id,name,score等不同类型的数据项;
  • 结构体的指定并不分配内存空间,只有在定义了变量或者进行初始化之后才分配地址;
 2. 结构体变量的定义

(1)先定义结构体类型,再声明变量          

// 定义结构体类型
struct Student {
    int id;
    char name[20];
    float score;
};

// 声明结构体变量
struct Student s1, s2;  // 声明两个Student类型的变量

 (2)定义类型的时候同时声明变量

struct Student {
    int id;
    char name[20];
    float score;
} s1, s2;  // 定义类型的同时声明变量

// 后续还可以继续声明该类型的变量
struct Student s3;
3. 结构体变量的初始化

(1)按顺序赋值

s.id = 1;

s.name = "zs";                 //错误写法,字符数组不可以被整体赋值

strcpy(s.name,"zs");        //正确方式

s.score = 90;

(2) 直接初始化,不挨个赋值

注意:这种方式,次序不能颠倒,所以不利于有时候我们只需要对部分信息进行初始化;

(3)部分初始化

此时,如果是局部变量,其余没有进行初始化的信息都为0;

 

!!

结构体变量互相之间可以进行赋值

s1 = s;  两个打印出来的值是一样的

4. 计算结构体的字节数

        在 C 语言中,计算结构体的字节数(即sizeof的结果)需要考虑内存对齐规则。内存对齐是编译器为了提高内存访问效率而采用的一种优化策略,它会在结构体成员之间插入填充字节。硬件通常按字长为单位读取内存,例如:32位系统每次从内存读取4字节,64位系统每次读取8字节。

        对齐访问:数据地址是其大小的整数倍,此时硬件可一次性读取数据。(如果是非对齐访问,此时硬件需要多次读取数据并进行拼接数据,导致性能下降)。

内存对齐的基本原则:
1. 成员对齐规则
  • 基本类型对齐值:每个基本类型(如charintdouble)都有一个对齐值,通常等于其自身大小(如int为 4 字节,double为 8 字节)。
  • 结构体成员偏移量:每个成员的起始地址必须是其对齐值的整数倍。如果前一个成员结束后剩余空间不足,则会插入填充字节。
2. 结构体整体对齐规则
  • 结构体总大小:必须是结构体中最大对齐值的整数倍。如果不足,则在结构体末尾插入填充字节。
计算步骤——简单结构体:

 

   struct S1

    {

         char a;        // 1字节

         int b;         // 4字节 

         short c;       // 2字节

    };

  • a 从偏移量 0 开始,占用 1 字节(地址 0);
  • b 的对齐值为 4,需从偏移量 4 开始,因此a后填充 3 字节(地址 1~3),b占用 4 字节(地址 4~7);
  • c 的对齐值为 2,从偏移量 8 开始,占用 2 字节(地址 8~9);
  • 结构体总大小需是最大对齐值(4)的整数倍,因此总大小为 12 字节(地址 0~11),末尾填充 2 字节(地址 10~11)。
计算步骤——包含数组和指针:
struct S2 {
    char a;        // 1字节
    int* p;        // 指针:64位系统为8字节
    short arr[3];  // 数组:3*2=6字节
};
  • a 从偏移量 0 开始,占用 1 字节(地址 0);
  • p 的对齐值为 8,需从偏移量 8 开始,因此a后填充 7 字节(地址 1~7),p占用 8 字节(地址 8~15);
  • arr 的对齐值为 2(short类型),从偏移量 16 开始,占用 6 字节(地址 16~21);
  • 结构体总大小需是最大对齐值(8)的整数倍,因此总大小为 24 字节(地址 0~23),末尾填充 2 字节(地址 22~23)。

对齐几大原则:

  1. 默认按照计算机位数对齐:64 / 8 = 8,最终大小必须为8的整数倍

  2. 从结构体中的成员中查找最大字节的成员,最终按此成员大小对齐

  3. 把每个成员按照声明顺序依次存放入内存,偏移量sizeof(成员)必须能够整除

5. 结构体的值传递和指针传递

(1)值传递:

          

(2)指针传递:比起值传递效率更高

        

6. 练习

  • 结构体遍历

          

  • 逆序

          

  • swap函数实现逆序

        

  • 排序

        无法直接使用结构体变量进行比较,但是结构体内部成员单独拎出来可以进行比较 

        

       

  • 采用qsort函数按学生姓名排序

      

二、共用体

1. 共用体的定义

             

2. 内存占用特点
  • 空间是重合的 共用体的所有成员共用了相同的内存空间,最终的内存大小和它最大的是一样的
  • 只有最后被赋值的那个成员是有效的
  • 共享总是从起始地址开始共享
3. 应用
  • 判断计算机大端序还是小端序
#include <stdio.h>
union Endian {
    int i;
    char c;
} e;
int main() {
    e.i = 1;
    if (e.c == 1) {
        printf("小端字节序\n");
    } else {
        printf("大端字节序\n");
    }
    return 0;
}

三、枚举

  • 实际上也是一种数据类型
  • 将变量的值全部列举出来,变量的值只限于列出来的值的范围内

           

       eg:创造一个存放星期的

        

注意:

  • 在c语言中,对枚举元素按常量处理,故称为枚举常量,他们不是变量,不能进行赋值;
  • 枚举元素作为常量,是有值的,按照顺序依次为0,1,2 ...;
  • 枚举值可以用来作判断比较:

      

四、typedef

  •  作用:给已有的数据类型起别名

int INT;

typedef int INT;          //相当于给原来的int 起了别名为INT

通过输出字节数得已验证:

   //运行结果为4

  • 作用:避免我们写很长的关键字

     

     此时,就可以写成Students   s的形式了,避免再去写struct了

  • 但并不是时时刻刻都可以用,不建议使用其进行简化,是的代码较难读懂

     

*PStudent 也变成类型名了,为student*型,所以说明t是个指针,此时t不带*了,容易分不清

输出结果为 80 ,是一个装有指针的数组

五、位运算

位运算是指二进制位的运算,常用来处理二进制位的问题,

  • 位运算符中除了~以外,均为双目运算符,即要求两侧各有一个运算量
  • 运算量只能是整形或者字符型的数据,不能为实型数据

1. 按位与 &

0 & 0 = 0;

0 & 1 = 0;

1 & 0 = 0;

1 & 1 = 1

作用:指定位清零

2. 按位或 |

0 | 0 = 0;

0 | 1 = 1;

1 | 0 = 1;

1 | 1 = 1;

作用:指定位置1

3. 按位异或 ^

参与运算的两个二进制位,同号则为0(假),异号则为1 (真)。

0 ^ 0 = 0

0 ^ 1 = 1;

1 ^ 0 = 1;

1 ^ 1 = 0;

作用:指定位翻转

4. 取反 ~

~ 0 = 1;

~ 1 = 0;

逻辑取反不等于按位取反 

5. 左移 <<

  • 将二进制位向左移动指定的位数,右侧空出的位用 0 填充,左侧溢出的位被丢弃。
unsigned int a = 5;  // 二进制: 0000 0000 0000 0000 0000 0000 0000 0101
unsigned int b = a << 2;  // 左移2位 → 0000 0000 0000 0000 0000 0000 0001 0100 → 十进制20

计算步骤:  5 << 2 → 5 * (2^2) → 5 * 4 = 20

  • 对负数的处理:对于有符号数,若符号位未被移出(即左移后符号不变),则结果仍为原符号的数值;若符号位被移出,则结果变为负数(溢出导致)。
int a = -5;  // 补码表示: 1111 1111 1111 1111 1111 1111 1111 1011
int b = a << 2;  // 左移2位 → 1111 1111 1111 1111 1111 1111 1110 1100 → 十进制-20

 6. 右移 >>

  • 将二进制位向右移动指定的位数,左侧空出的位根据原数的符号位填充(算术右移)或用 0 填充(逻辑右移)。

算术右移:用于有符号数,左侧补符号位(即正数补 0,负数补 1)。

逻辑右移:用于无符号数,左侧补 0。

unsigned int x = 0x80000000;  // 二进制: 1000 0000 ... 0000
unsigned int y = x >> 1;  // 逻辑右移 → 0100 0000 ... 0000 → 十进制2147483648

int a = 0x80000000;  // 有符号数,二进制: 1000 0000 ... 0000(表示-2147483648)
int b = a >> 1;  // 算术右移 → 1100 0000 ... 0000 → 十进制-1073741824
  • 应用:

                   


网站公告

今日签到

点亮在社区的每一天
去签到