C语言(六)

目录

结构体

结构体的声明:

结构体自引用:

结构体内存对齐:

结构体对齐规则:

结构体传参:

结构体位段:

什么是位段:

位段的内存分配:

位段的跨平台问题:

枚举

枚举和结构体区别:

枚举的优点:

联合

判断当前计算机的大小端存储

联合体大小的计算


结构体:
    结构体类型的声明
    结构体的自引用
    结构体变量的定义和初始化
    结构体内存对齐
    结构体传参
    结构体实现位段(位段的填充&可移植性)
枚举:
    枚举类型的定义
    枚举的优点
    枚举的使用
联合:
    联合类型的定义
    联合的特点
    联合大小的计算

结构体

结构体的声明:


结构体是一些值集合,这些值称为成员变量。结构体每个成员可以是不同类型的变量
数组是一组相同类型的元素的集合
结构体是一种复杂类型
复杂类型:比如书籍:书名,作者,版号,定价,出版社
比如学生:姓名,名字,性别,地址,学校,班级......
struct book {
    char name[20];
    int age;
    char id[20];
}b,b4,b5,b6;
//结构体可以引用结构体
struct a {
    struct book b;
    int id;
};
//匿名结构体类型
//匿名结构体只能用这一次,即声明一次s

struct 
{
    char name[20];
    int age;
    char id[20];
}s;
//匿名结构体类型的指针
struct
{
    char name[20];
    int age;
    char id[20];
}*ps;
int main()
{
    ps = &s;
    struct book b1;
    struct book b2;
    struct book b3;
    return 0;
}
b1,b2,b3和b4,b5,b6类型是完全一样的
但是,b4,b5,b6是全局变量

结构体自引用:


数据结构:数据在内存中存储的结构
顺序存储结构(数组):1,2,3,4,5
链表:
 单向链表也叫做单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
 节点:就是链表的数据元素
顺序和链表统称为线性数据结构
树形数据结构:二叉树
 数据域+指针域(存放下一个节点的地址)
正确的自引用方式:不是包含同类型的结构体变量,而是包含同类型结构体的指针

struct node {
    int data;
    struct node* next;
};

结构体变量的定义和初始化
struct b {
    int arr[10];
    char ch[20];
};
struct c {
    double d;
    struct b b;
    char c;
};
int main()
{
    struct b n = { 0 };
    struct b n1 = { 0 };
    struct c c1 = { 3.14,{{1,2},"hello"},'w' };
    printf("%lf\n%s\n%c\n", c1.d, c1.b.ch, c1.c);
    return 0;
}

结构体内存对齐:


 第一个问题:
一个结构体占多大内存空间呢?

struct s1 {
    int i1;    //4
    char c1; //1
};

struct s2 {
    char c1;
    int i;
    char c2;
};
struct s3 {
    int i;
    char c1;
    char c2;
};
struct s4 {
    int i;            //偏移量0 = 0+1-1
    char c2;        //偏移量4 = 1*4+1 -1
    double c1;        //偏移量8 = 1*8 +1-1   (第9个地址,偏移量为8)
    int c3;            //偏移量16 = 2*8 +1 -1
    int c4;            //偏移量20   = 16+4+1-1
};                    //总大小:偏移量+1 == 占用了多少个字节 
                    //再+(最后一个成员变量的大小-1)因为这个成员变量的起始地址已经被计算进去了
struct s5 {
    int i;
    struct s4 s;
    char c2;
};
int main()
{
    struct s1 ss1 = { 0 };
    struct s2 ss2 = { 0 };
    struct s3 ss3 = { 0 };
    struct s4 ss4 = { 1,0,0,1,1 };
    struct s5 ss5 = { 0 };

    printf("%d\n", sizeof(ss1));
    printf("%d\n", sizeof(ss2));
    printf("%d\n", sizeof(ss3));
    printf("%d\n", sizeof(ss4));
    printf("%d\n", sizeof(ss5));
    return  0;
}


结构体对齐规则:


1,第一个成员在与结构体变量偏移量为0的地址处。
2,其他成员变量要对齐到(某个数字(对齐数)的整数倍地址偏移量-1)处。            //因为结构体是从0偏移量开始的,所以,到3偏移量,就已经是第四个地址了...所以要用对齐数倍数-1
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
 --  vs中默认为8
 -- linux中没有默认对齐数的概念
3,结构体总大小为结构体所有成员中的最大对齐数(每一个变量成员都有一个对齐数)的整数倍。
4,如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

为什么存在内存对齐?
大部份资料是如是说的:
1,平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;
某些平台只能在某些地址处取特定类型的数据否则抛出硬件异常
2,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐地内存,处理器需要做两次内存访问;而对齐地内存仅访问一次

总体来说:结构体地内存对齐是拿空间来换取时间的做法

修改默认对齐参数

当你觉得对齐数不合适的时候,可以修改默认对齐数
把默认对齐数改为2;
#pragma pack(2)
struct s1 {
    char c1;
    int i;
    char c2; 
};
//offsetof宏可以计算结构体的成员变量距离起始地址的偏移量
int main()
{
    printf("%d\n", sizeof(struct s1));
    printf("%d\n", offsetof(struct s1,c1));
    printf("%d\n", offsetof(struct s1,i));
    printf("%d\n", offsetof(struct s1,c2));
    return 0;
}

结构体传参:

struct s {
    int a[10];
    int num;
};
struct s s1 = { {1,2,3},1000 };
void print1(struct s s1)
{
    printf("%d\n", s1.num);
}
void print2(struct s *s1)
{
    printf("%d\n", s1->num);
}
int main()
{
    print1(s1);
    print2(&s1);
    return 0;
}
结构体传参时,结构体占用的空间一般很大
值传方式,需要开辟一个空间来存储这个值,浪费很多空间
传址方式,节省空间,效率更高
函数传参时,参数是需要压栈的,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降

结论:结构体传参的时候,需要传结构体的地址

结构体位段:


什么是位段:

位段的声明和结构体是类似的,有了两个不同:
1,位段的成员必须是int,unsigned int或signed int
2,位段的成员名后边有一个冒号和一个数字 
比如:
struct a {
    int a : 1;    //a成员占1个bit位
    int b : 2;    //b成员占2个bit位
    int c : 3;    //c成员占3个bit位
};
int main()
{
    printf("%d\n", sizeof(struct a));
    return 0;
}
性别:男,女,保密
位段一定程度上是在帮我们节省空间


位段的内存分配:


1,位段的成员可以是int,unsigned int,signed int或者是char(属于整型家族)类型
2,位段的空间上是按照需要4个字节(int)或者1个字节(char)的方式来开辟的
3,位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段

int main()
{
    int a = 0x12345678;
    struct s1 {
        char a : 3;
        char b : 4;
        char c : 5;
        char d : 4;
    };
    struct s1 s = { 0 };
    s.a = 10;
    s.b = 12;
    s.c = 3;
    s.d = 4;
    return 0;
}
这个位段规则仅仅适应于vs
一个字节(整形)内部的地址,先使用低地址位的数据,再使用高地址为的数据,从右向左使用
当一块字节空间内剩余的空间不足以下一个空间使用,那么会跳到下一个字节

位段的跨平台问题:


1,int 位段被当成有符号数还是无符号数是不确定的。
2,位段中最大的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)
3,位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4,当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段的剩余位时,是舍弃剩余的位还是利用,这是不确定的。

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

枚举

枚举 - 顾名思义就是一一列举
星期,性别,月份都是有限的数据,都可以一一列举
定于:
enum day {
    mon,
    tue,
    wed,
    thu,
    fri,
    sat,
    sun,
};
enum color {
    red,
    blue,
    green,
};
enum color1 {
    red1 = -1,        //从第一个开始,默认+1;
    blue1 ,
    green1 = 8,
};
int main()
{
    enum color c = blue;
    //enum color c =1 ;        //这样不可以,在c++编译器会报错
    printf("%d\n",++c);
    printf("%d\n",++c);
    printf("%d\n", red);
    printf("%d\n", blue);
    printf("%d\n", green);
    printf("%d\n", red1);
    printf("%d\n", blue1);
    printf("%d\n", green1);

    return 0;
}


枚举和结构体区别:


枚举类型括号内是可能取值,是常量
结构体括号内是结构体成员,是变量

枚举的优点:


1,增加代码的可读性和可维护性
 2,和#define定义的标识符比,枚举有类型检查,更加严谨
 3,可以避免命名污染(封装)
 4,便于调试
 5,使用方便,一次定义多个常量
 
 
编译 -->链接-->exe-->调试
编译:预编译,编译,汇编
define在编译期间就运行了;所以不方便调试

枚举是一种自定义类型
枚举大小就是一个整形的大小

联合


联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列成员,特征是这些成员公用同一块空间(所以联合体也叫共用体)
这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

union un {
    char c;
    int i;
};

int main()
{
    union un u = {10};          //联合体初始化
    printf("%d\n", sizeof(u));
    printf("%p\n", &u);
    printf("%p\n", &u.c);
    printf("%p\n", &u.i);
    return 0;
}
输出:
 4
000000a7eaaff684
000000a7eaaff684
000000a7eaaff684
联合体也叫共用体
 因为共用地址,所以在同一时间只能使用联合体中的一个成员
联合体和结构体的区别:


判断当前计算机的大小端存储


vs使用小端存储
小段:低位数据存储在低位地址,高位存高位地址
大段:低位数据存储在高位地址,高位存低位地址

做一个计算大小端的函数
int check_sys()
{
    int a = 1;
    if ((*(char*)&a) == 1)
    {
        return 1;
    }
    else
        return 0;
}
使用联合体的实现方式
int check_sys()
{
    union un {
        char c;
        int i;
    }u;
    u.i = 1;
    return u.c;
    //返回1,就是小端,返回0就是大端 

}
int main()
{
    //int a = 1;
    //&a是低地址,那么低地址存低位(1)就是小端
    //printf("%d\n", *((char*)&a));
    int ret = check_sys();
    if (ret == 1)
    {
        printf("小端");
    }
    else
        printf("大端");
    return 0;
}

联合体大小的计算


联合的大小至少是最大成员的大小
当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍
比如:

union un {
    char c[5];    //5
    int i;        //4
};
union un1 {
    char c1[5];    //5
    char c2;        //4
}u1;
int main()
{
    union un u;
    printf("%d\n", sizeof(u));
    printf("%d\n", sizeof(u1));
    return 0;
}
输出为:8
因为,联合体内部变量的最大大小为5;但是最大对齐数为4,所以应该是最大对齐数整数倍4*2 =8;
注意:联合体没有默认对齐数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值