C语言结构体和位段(位域)

本文详细介绍了C语言中结构体的定义、初始化方式,包括结构体指针、内存对齐规则,以及位段的概念、应用和跨平台问题。还探讨了结构体在实际编程中的使用和注意事项,以及位段在节省空间和网络协议中的优势。
摘要由CSDN通过智能技术生成

1.结构体

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

结构体是一种数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据、需要存储空间;

1.1结构体的定义形式和初始化

struct tag
{
    member-list;
}variable-list;

结构体定义的基本原则:

1. 定义结构体之后一定要以分号结束
2. 结构体中不能定义函数

形式一:

//最基本形式
struct Student{
    int number;    //学号
    int age;
    bool gender;    //性别,1女,0男
};
 
//单独定义结构体变量,按照顺序初始化
struct Student stu1={2,3,1};
//按照指定顺序初始化
struct Student stu2={.age = 3, .name = 2, .gender = 1};

形式二:

//在定义结构体变量的同时就进行变量声明
#include <stdio.h>  
#include<stdbool.h>
struct Student{
       int age;
       int number;
       bool gender;
}stu1,stu2={1,2,0};
​
//初始化
int main()
{
    struct Student stu1 = { 18,1,0 };
    struct Student stu3 = { 19,1,0 };
    printf("%d,%d,%d\n", stu1.age, stu1.number, stu1.gender);
    printf("%d,%d,%d\n", stu2.age, stu2.number, stu2.gender);
    printf("%d,%d,%d\n", stu3.age, stu3.number, stu3.gender);
​
    return 0;
}

efb544e330f24c35bd3245376a951673.png

形式三:匿名结构体

该种方法并没有定义结构体名称,不能再在后续代码中声明该结构体变量,因此只能存在stu1,stu2两个结构体变量

通常我们在嵌套结构或联合中使用它们。

//不定义结构体名称,在定义结构体时直接声明结构体变量
struct {
    int number;
    int age;
    bool gender;
}stu1,stu2;

匿名结构体的使用:结构体嵌套结构体使用

#include <stdio.h>  
#include<stdbool.h>
struct person
{
    char name[20];
    struct
    {
        int number;
        int age;
        bool gender;
    };
};
​
//初始化
int main(void)
{
    struct person x = { "zhangsan",1,18,1};
​
    printf("stu1 = %s,%d,%d,%d", x.name,x.number, x.age, x.gender);
    return 0;
}

提前对外部结构体创建结构体变量

提前对匿名结构体创建结构体变量

#include <stdio.h>  
struct person
{
    char name[20];
    struct
    {
        int number;
        int age;
    }stu1, stu2;
}a;
​
//初始化
int main(void)
{
    struct person a = { "z",{1,18},{2,19} };//两个结构体,分开初始化
    printf("%s\n", a.name);
    printf("stu1 = %d,%d\n", a.stu1.number, a.stu1.age);
    printf("stu2 = %d,%d\n", a.stu2.number, a.stu2.age);
​
    return 0;
}

形式四:

typedef struct{
    int number;
    int age; 
}Stu;
 
//初始化
int main(){
    Stu stu1={1,2};
    printf("学号是%d\n",stu1.number);
    printf("年龄是%d\n",stu1.age);
}

形式五

//使用typedef加上struct定义结构体
typedef struct Student{
    int number;
    int age;
    bool gender;
}Stu;
 
//声明结构体变量
Stu stu1,stu2;

结构体指针变量

4a0f90f9deb549f0a445e242a1d9345b.png

结构体嵌套结构体

b35dcc43d6034a52b8d4a197c177eeeb.png

1.3结构体成员访问和传参

1.3.1结构体成员访问和传参

结构体变量使用 . 访问;

结构体变量.对象

struct Stu s = { "张三", 20, "男", "20230818001" };
    printf("name: %s\n", s.name);
    printf("age : %d\n", s.age);
    printf("sex : %s\n", s.sex);
    printf("id : %s\n", s.id);
 
    struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥
    printf("name: %s\n", s2.name);
    printf("age : %d\n", s2.age);
    printf("sex : %s\n", s2.sex);
    printf("id : %s\n", s2.id);

注意:对结构体变量 整体赋值 有三种情况

(1)定义结构体变量(用{}初始化)

(2)用已定义的结构体变量初始化

(3)结构体类型相同的变量可以作为整体相互赋值;

在C语言中不存在结构体类型的强制转换。

ansi/iso C规定:“相同类型的结构体是可以直接赋值的”; 合法赋值: 如date1和date2都是date结构体类型的变量,可以这样赋值:date1= date2; 非法赋值: 假设申明了两个成员列表完全一样的两个结构体类型(dataA,dateB),即使他们的成员列表是一样的,编译器会当作两个完全不同的类型,令两个结构体类型的变量间赋值,则是非法的。 但是这种问题不能靠试验获得准确的答案,因为你用的编译器支持并不表明其他编译器也支持。ansi/iso C规定的合法行为,编译器是必须支持的。

#include "stdio.h"
​
struct date
{
    int i;
    float x;
} d1={20,9.1};
​
void main()
{
    struct date d2;
    d2=d1;
    printf("%d, %f\n",d1.i,d1.x);
    printf("%d, %f\n",d2.i,d2.x);
}
​

268c91bf8db84c5e8b199b19838cca59.png

1.3.2结构体变量和指针

结构体类型指针访问成员的获取和赋值(传参)形式:

(1)(*p). 成员名(.的优先级高于*,(*p)两边括号不能少)

(2) p->成员名(->指向符)

#include<stdio.h>
#include<string.h>
//#define _CRT_SECURE_NO_WARNINGS
struct Inventory//商品
{
    char description[20];//货物名
    int quantity;//库存数据
};
int main()
{
    struct Inventory sta = { "iphone",20 };
    struct Inventory* stp = &sta;
    char name[20] = { 0 };
    int num = 0;
    (*stp).quantity = 30;
    stp->quantity = 30;
    strcpy_s(name,sizeof(stp->description),stp->description);
    printf("%s %d\n", stp->description, stp->quantity);
    printf("%s %d\n", (*stp).description, (*stp).quantity);
    return 0;
}

fca6d048150048e1af8dac3fad1c35ee.png

1.3.3结构体和函数

#include<stdio.h>
#include<string.h>
#define _CRT_SECURE_NO_WARNINGS
struct School
{
    char s_name[20];//学校
    int s_age;
};
void Print_a(struct School sx)
{
    printf("%s %d\n", sx.s_name, sx.s_age);
}
void Print_c(struct School* sp)
{
    printf("%s %d\n", sp->s_name, sp->s_age);
}
int main()
{
    struct School sx = { "xi'an",100 };
    Print_a(sx);
    Print_c(&sx);
    return 0;
}

0f7352d766a5434387c949261e220ff7.png

结构体传参是对原数据进行了临时拷贝在进行传参,有空间浪费

结构体指针地址传参的好处:

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

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

1.4结构体与数组

结构体数组,是指数组中的每一个元素都是一个结构体类型

#include<stdio.h>
#include<string.h>
#define _CRT_SECURE_NO_WARNINGS
struct Student
{
    char s_name[20];//姓名
    int age;//年龄
    float score;//成绩
};
int main()
{
    struct Student cla[] =
    {
        {"liuwen",18,149.9},
        {"qnge",18,145},
        {"anan",19,188},
    };
    return 0;
}

1.5结构体内存对齐

1.5.1 对齐规则

  1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处

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

  3. 对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值

    VS 中默认的值为 8

    Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩

  4. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。

  5. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍

结构体内的数据对齐是看下标索引是否是整数倍,结构体整体看总字节数是否是整数倍

结构体内存存在对齐的原因:结构体的内存对⻬是拿空间来换取时间的做法。

1.5.2修改默认对齐数

如何满足既对齐又节省空间?

#pragma 这个预处理指令,可以改变编译器的默认对⻬数

此时我们把默认对齐数改为1,计算方法跟结构体相同

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
    //输出的结果是什么?
    printf("%d\n", sizeof(struct S));
    return 0;
}

1515378fd6ad405187e37af18a09564c.png

1.6结构体实现位段

1.6.1什么是位段

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

  1. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以 选择其他类型。

  2. 位段的成员名后边有⼀个冒号和⼀个数字。

struct A
{
    int _a:2;
    int _b:5;
    int _c:10;
    int _d:30;
};

1.6.2位段的内存分配

1.位段的成员可以是 int, unsigned int, signed int 或者是 char 等类型

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

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

//⼀个例⼦ 
struct S
{
    char a:3;
    char b:4;
    char c:5;
    char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空间是如何开辟的? 

位段分配的内存中的比特位是从右向左使用的,分配剩余的比特位不够使用时,浪费掉剩余内存。

如下,由于位段的限制,二进制存储时要舍弃高位,留下低位3d9088761ccc47609ac2f80a2b61dee0.png

1.6.3位段注意点

注意一:位段占的二进制位数不能超过该基本类型所能表示的最大位数,即位段不能跨字节存储,比如在char是占1个字节,那么最多只能是8位;

struct S
{
    char a:9;//err
};

注意二:在位段中要给位段的成员起名字

无名的位段写在结构体中会计入空间大小,但不能对其进行使用

struct S
{
    char a:3;
    char b:4;
    char c:5;
    char : 8;//err,无名的位段不能使用
};
int main()
{
    struct A s = { 'a','b','c','d' };//此时字符'd不能存储到位段中'
    return 0;
}

注意三:不能对位段进行取地址操作

struct A {
    char _a : 2;
    char _b : 5;
    char _c : 8;
    char  : 8;
}s;
int main()
{
    printf("%d\n", sizeof(struct A));
    char *p = &(s._a);这里对位段中的元素取地址操作
    return 0;
}

801a62b6674540da80d3422debe375fd.png

注意四:如果位段中存储的数据大于位段自身大小就会发生截断问题,在输出时会将截断之后存储在该字节(char类型为例)的最高位当作符号位

struct A {
    char a : 2;
    char b : 5;
    char c : 8;
    char d: 8;
}s;
int main()
{
    s.a = 7;//111
    s.b = 51;//1 10011
    printf("%d\n", s.a);
    printf("%d\n",s.b);
    return 0;
}

630df74789e84ca1bffc75bf55b4f1bc.png

1001111二进制转换为十六进制就是4F,在内存中存储的就是4F

b0d36fce57474367bebe01ba19963f7b.png

在输出位段的具体值时,可以知道系统将最高位看作了符号位进行输出

91b9468fe2764b8986c72532b4be318e.png

那么我们将位段类型转换为无符号是否能打印正确的值呢

struct A {
    unsigned char a : 2;
    unsigned char b : 5;
    char c : 8;
    char d: 8;
}s;
int main()
{
    s.a = 7;//111
    s.b = 51;//1 10011
    printf("%d\n", s.a);
    printf("%d\n",s.b);
    return 0;
}

c905ced114dd4ae4b2ebeae24061966e.png

1.7位段的跨平台问题

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

  2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会 出问题。

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

  4. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃 剩余的位还是利⽤,这是不确定的。

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

1.8位段的应用

下图是⽹络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要⼏个bit位就能描述,这⾥ 使⽤位段,能够实现想要的效果,也节省了空间,这样⽹络传输的数据报⼤⼩也会较⼩⼀些,对⽹络 的畅通是有帮助的

780f2c07e6264396bb530d4722c2b146.png

  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值