结构体系列

一.结构体的初始化

方法1

struct x
{
    int a;
    int b;
};
struct x m = {1,2}

方法2

struct x
{
    int a;
    int b;
};
struct x m = {.a=1,.b=2};

注意在C99上才可以这样用

二.结构体变量之间的赋值

方法1

struct x m1;
struct x m2 = {.a=1,.b = 2};
m1.a = m2.a
ma.b = m2.b

方法2

struct x m1;
struct x m2 = {.a=1,.b = 2};
m1 = m2;

方法3

struct x m1;
struct x m2 = {.a=1,.b = 2};
memcpy(&m1,&m2,sizeof(m1));

需要理解的本质是,不论是变量、数组、结构体都是内存中的一块空间而已。他们之间的赋值只不过是把一块空间中的内容赋值到另一块空间中而已。

三.空结构体的占位作用

struct x
{
};

定义一个空结构体struct x,用sizeof(x)输出为1。说明一个空的结构体也会占用1个字节的空间。扩展开来,C语言中每一个定义的内容都是要占用空间的。

四.用结构体指针简化数据的解析

1.将数组接收到的数据通过结构体指针解析

#pragma pack(1)//强制对齐方式为一字节对齐
typedef struct
{
    unsigned char Cmd;
    short Length;
    unsigned char Data[100];
} Message_Stru;

#pragma pack()//恢复默认对齐方式


unsigned char Rec_data[103] = {1,0x06,0x00,'1','2','3','4','5','6'};//原始数据
unsigned char Pro_data[103] = {};
int main(void) {
    int i = 0;
    Message_Stru *x = (Message_Stru *)Rec_data;//将数组的地址强制转化为结构体指针,这样后续就可以按照结构的数据组织方式操作数据了
    memcpy(Pro_data,x->Data,x->Length);//赋值
    printf("Result is %s",Pro_data);//打印结果
    return 0;
}

输出结果:

        结构体和数组都只是内存中的一块空间而已,将数组通过结构体指针强转赋值给Message_Stru *x,数据在内存中的分布并没有改变,只不过当我们操作结构体指针时,会按照结构体的格式进行读取(即内容没变,读写的方式变了),此时我们操作x->Length就会自动操作偏离数组起始地址1字节的地方(这里结构体对齐方式为1字节对齐)。

        总结:将一个数组中的内容通过结构体强转赋值给一个结构体指针,是我们解析数据的一种常用技巧。

2.通过结构体中的类型字,选用不同的结构体解析

struct x 
{
    uint8_t header;
    float f;
    char str[10];
};

struct abc 
{
    uint32_t a;
    uint32_t b;
    uint32_t c;
    uint16_t d;
    uint32_t e;
};
struct def
{
    float f;
    char str[10];
}

uint8_t uart_rec_buf[100];
uint8_t data_buf[100];

void test(void) 
{ 
    struct x *pbuf=uart_rcv_buf; 
    struct abc *pabc; 
    struct def *pdf; 
    memcpy(data_buf,pbuf->buf,pbuf->len);

    if(1 == pbuf->datatype)//如果数据类型为1
    {
        pabc=(struct abc *)pbuf->buf;//用abc结构体格式解析
        printf("%a %b %c %d %e",pabc->a,pabc->b,,pabc->c,,pabc->d);
    }

    if(2 == pbuf->datatype)//如果数据类型为2
    {
        pdef=(struct def *)pbuf->buf;//用def结构体格式解析
        printf("%f%s",pdef->f,pdef->str);
    }
}
其实结构体指针强转只是将一块固定内存空间的数据,按照不同的方式去解读而已。

五.结构体和函数指针封装在一起的作用

1.适配器:应用层和驱动层之间的神秘通道

//定义一个结构体,里面定义了操作ADC芯片的3个函数指针
struct adc_ops 
{
int (*p_config)(void);
int (*p_start_convert)(void);
int (*p_read_value)(void);
};

//如果我们项目中用了AD7606,我们把相应的函数赋值给结构体定义的指针
struct adc_ops _adc_ops = {\
.p_config = AD7606_Config;\
.p_start_convert = AD7606_Start_Convert;\
.p_read_value = AD7606_Read_Value;\
};

//如果我们项目中用了TLC5615,我们修改下函数指针的赋值
_adc_ops.p_config = TLC5615_Config;
_adc_ops_p_start_convert = TLC5615_Start_Convert;
_adc_ops_p_read_value = TLC5615_Read_Value;

//应用层函数直接调用的是结构体中的函数指针,即使芯片换了,应用层代码也不用更改
int ReadAD(void)
{
    int AdVal; 
    AdVal = (*(_adc_ops.p_config))();   
    return AdVal;
}

六.结构体数据在eeprom存储中的使用

1.存储与读取操作

struct pid_args abc;//abc参数赋值、设置、调整

1)往EEPROM保存参数:

eeprom_write(&abc,sizeof(abc));

2)将EEPROM中的参数读取到结构体变量中

eeprom_read(&abc,sizeof(abc));

2.魔码的使用

struct Message
{
    int magicCode;//魔码定义,当该值为0x55AA时,说明数据有效
    xxx;
    xxx;
}
        增加一个特殊的变量用来标记当前结构体的状态,比如新单片机读取eeprom以后,获取的第一个数据肯定不是魔码定义的值,这个时候我们就知道单片机eeprom中的数据无效

3.结构体中数据的访问速度

        结构体中前128个字节的内容访问速度很快,后面的则很慢。所以如果某些数据在程序运行中会反复使用,这个时候就可以考虑把变量放在开始的128字节上了。

七.结构体中变量的对齐方式

1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量都要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默 的一个对齐数与该成员大小的较小值。STM32 ARMCC 和 GCC 下的默认对齐数为4。
3.结构体总大小为最大对齐数(成员变量类型最长的)的整数倍。
strcut x
{
    char a;//第一个变量在偏移量为0的地方
    int b;//因为int占4字节,所以对齐到4
    char c;//因为占一字节,所以偏移地址为8
           //由于总偏移算出来是9,最大长度成员占4字节,所以总大小12字节,C后面的3字节空着
}
strcut x
{
    char a;//第一个变量在偏移量为0的地方
    double b;//因为double占4字节,当编译器默认对齐数为4时,选择较小的,所以偏移地址是4
    char c;//因为占一字节,所以偏移地址为12
           //由于总偏移算出来是13,最大长度成员占8字节,所以总大小16字节,C后面的3字节空着
}

4.强制对齐方式

1)#pragma pack(n)//强制对齐方式为n字节对齐
2)#pragma pack()//恢复编译器默认对齐方式

八.结构体的嵌套和初始化以及数组的特殊赋值方法

1.结构体的嵌套和初始化

struct x 
{
char a;
char b;
int c;
};
struct y
{
struct x a;//嵌套一个结构体
char str[10];
};

struct y xxx = {.a=.a=1,.b=2,.c=0}, .str="abc"};//初始化。只要再加一个括号就好了,在括号里又是跟普通结构体初始化赋值一样

2.对数组中某几个成员赋值的方法(有些编译器不支持)

int array[500] = {[400...403]={1,2,3,4}};//将数组下标400~403的内容赋值为1~4

九.Linux中几个C语言高阶操作

1.获取结构体成员在结构体中的偏移量

#define size_t int
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)//type是结构体类型,MEMBER是结构体成员
struct test
{
char  x;
int  y;
float z;
};

int main(void)
{
int temp = -1;
temp = offsetof(struct test, z);
printf("temp = %d\n", temp);
return 0;
}
输出结果:
在这里0被强制转化为struct test *型 它的作用 就是作为指向该结构体起始地址的指针 而&((struct test *)0->k) 的作用便是 求k到该起始指针的字节数

2.通过一个结构变量中一个成员找到这个结构体变量的首地址

    有了offsetof,就可以得到另外一个很有用的宏:
#define container_of(ptr, type, member) ({              \         
const typeof( ((type *)0)->member ) *__mptr = (ptr);    \         
(type *)( (char *)__mptr - offsetof(type,member) );})
说明:其中ptr是指向结构体某一成员的指针,contain_of的功能是通过指向成员的指针求成员在结构体变量的地址。
现在我们知道container_of()的作用就是找到结构体成员ptr所在的这个结构体的首地址(即0偏移地址)。

3.补充说明下typeof()函数的作用

十.位域

位域一般就是两个作用。(1)节约内存 (2)操作单片机寄存器

1.位域的定义

struct {
    unsigned int a: 3;    // a占用3位
    unsigned int b: 5;    // b占用5位
    unsigned int c: 8;    // c占用8位(即一个字节)
    unsigned int d: 16;   // d占用16位(即两个字节)
} bit_field;
如果是有几位不用怎么办呢?那几位不带位域名称就好了
struct {
    unsigned int a: 3;    // a占用3位
    unsigned int : 5;    // 把位域名称去掉就可以了
    unsigned int c: 8;    // c占用8位(即一个字节)
    unsigned int d: 16;   // d占用16位(即两个字节)
} bit_field;
需要注意的是, 位域定义必须是LSB,即前面的为低位。另外对位域成员赋值不能超过他最大能表示的范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小吴的嵌入式笔记

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

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

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

打赏作者

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

抵扣说明:

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

余额充值