上海公司网站制作价格,穿着西裤做的网站,小影wordpress主题,东莞网站建设乐云seo在线制作文章目录 前言一、结构体1.结构体的声明2.结构体的自引用3.结构体变量的定义和初始化4.结构体成员的访问5.结构体内存对齐#xff08;重点#xff09;6.#pragma修改默认对齐数7.结构体传参 二、位段1.位段的声明2.位段的内存分配3.位段的跨平台问题 三、枚举四、联合 #x… 文章目录 前言一、结构体1.结构体的声明2.结构体的自引用3.结构体变量的定义和初始化4.结构体成员的访问5.结构体内存对齐重点6.#pragma修改默认对齐数7.结构体传参 二、位段1.位段的声明2.位段的内存分配3.位段的跨平台问题 三、枚举四、联合 共用体 前言
本篇笔记主要总结C语言自定义类型结构体枚举联合。重点内容有结构体的声明和使用、结构体成员的访问结构体大小和内存对齐、位段等。
一、结构体
1.结构体的声明
结构体就是一些值的集合这些值称为成员变量。结构的每个成员可以是不同类型的变量可以是标量、数组、指针甚至是其他结构体。与数组不同数组的每个成员是相同类型的元素集合。
比如定义一个结构体来描述学生的信息
struct Student//定义结构体类型或结构体名
{char name[20];//姓名int age; //年龄int class; //班级char id[20]; //学号
};
struct Student s1; //定义结构体变量也可以在定义结构体的时候同时定义结构体变量
struct Student//定义结构体类型或结构体名
{char name[20];//姓名int age; //年龄
}s1,s2; //定义结构体变量s1和s2且是全局变量如果嫌每次定义结构体变量太麻烦可以用typedef对结构体重命名
typedef struct Student//定义结构体类型或结构体名
{char name[20];//姓名int age; //年龄
}Stu; //注意这里的Stu是结构体类型的重命名不是结构体变量Stu s1; //定义结构体变量特殊的结构体声明形式不建议使用
匿名结构体类型只能定义的时候同时定时变量且只能用一次。
struct //没有类型名
{char name[20];int age;
}s1; //结构体变量这样声明结构体省略了类型名自然后面也无法再创建变量只能再定义时创建边带所以是一次性消耗品。
但是对匿名结构体的定义进行重命名可以继续创建变量
typedef struct //没有类型名
{char name[20];int age;
}stu; //结构体类型名stu s1,s2;一般不建议使用匿名结构体容易出错。
注结构体定义时是不分配空间的只有定义结构体变量时才分配空间。
2.结构体的自引用
结构体的自引用就是在结构体的内部再创建一个同类型的结构体指针。
为什么必须是同类型指针呢同类型不行吗
struct Node
{int data;struct Node next;
};类似嵌套循环结构体变量next里无限嵌套着结构体变量next和一个4字节大小的int类型变量我们知道结构体的大小也是有限的不可能无限大所以这样写错误的。
正确写法
struct Node
{int data;struct Node* next;
};每个节点存储下一个节点的地址通过地址去寻找访问下一个结点的内容。一个指针大小是4或8个字节这样就比较合理了。
数据结构中的链表就是这个原理。
3.结构体变量的定义和初始化
#includestdio.h
struct Student//定义结构体类型或结构体名
{char name[20];//姓名int age; //年龄int class; //班级char id[20]; //学号
};int main()
{struct Student s1;//直接定义struct Student s2 { Seven, 20, 11, 1234567 };//变量初始化return 0;
}结构体嵌套
#includestdio.h
struct Student//定义结构体类型或结构体名
{char name[20];//姓名int age; //年龄int class; //班级char id[20]; //学号
};
struct S
{int num;struct Student s;
};
int main()
{struct Student s1;struct S s2 { 100, {Seven, 20, 11, 1234567} };//嵌套初始化return 0;
}4.结构体成员的访问
前面操作符中提到过.用来访问结构体成员-用来访问结构体指针成员。
#includestdio.htypedef struct Student
{char name[20];int age;
}Stu;int main()
{Stu s1 { Jim, 18 };printf(%s %d\n, s1.name, s1.age);Stu* ps s1;ps-age 25;printf(%s %d\n, ps-name, ps-age);return 0;
}5.结构体内存对齐重点
这也是结构体大小的计算方法。 在此之前自己试过用sizeof来计算结构体大小的小伙伴应该清楚结构体的大小并非简单的将结构体每个成员的大小相加结构体有一套自己的内存对齐规则。
结构体的对齐规则 1.结构体第一个成员存放在结构体变量起始位置的0偏移量的位置。 2.其他成员变量要对齐到对齐数的整数倍的地址处。 3.对齐数编译器默认的一个对齐数与该成员大小的较小值。vs默认对齐数是8,linux和gcc没有默认对齐数 4.结构体总大小为最大对齐数每个成员变量都有一个对齐数的整数倍。 5.如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍。 光看文字是不是有点难理解很正常做几道练习题就明白了。 练习一
struct S1
{char c1;int i;char c2;
};分析结构体第一个成员从0偏移量开始占1个字节第二个成员 i 大小是4字节小于vs默认对齐数8所以 i 的对齐数是4需要对齐到4的倍数而1 2 3都不是4的倍数所以从偏移量4处开始存储变量 i 同理第三个成员c2对齐数是18是1的倍数所以c2放在偏移量8的位置目前总共占用9个字节。而结构体的整体大小是4的整数倍所以偏移量9 10 11的位置浪费掉对齐到12所以结构体大小为12个字节。 练习二
struct S2
{char c1;char c2;int i;
};分析c1从偏移量0处存放占1个字节c2对齐数是1刚好从偏移量1处存放i 的对齐数是42和3不是4的倍数所以浪费掉i 对齐到偏移量4处占4个字节目前占用8个字节满足结构体的整体大小是最大对齐数4的整数倍所以该结构体大小为8个字节。 练习三
//嵌套结构体类型 求s4大小
struct s3
{double d;char c;int i;
};struct s4
{char c1;struct s3 s3;double d;
};如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍。 嵌套结构体s3自身的最大对齐数是8s4结构体的整体大小是所有最大对齐数s3的8和s4的8的整数倍。
为什么存在内存对齐 1.平台原因(移植原因) 不是所有的硬件平台都能访问任意地址上的任意数据的某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常。 2.性能原因 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于为了访问未对齐的内存处理器需要作两次内存访问而对齐的内存访问仅需要一次访问 总而言之: 结构体的内存对齐是拿空间来换取时间的做法。 tips: 在设计结构体的时候让占用空间小的成员尽量集中在一起这样可以节省空间。
6.#pragma修改默认对齐数
预处理指令#pragma可以改变默认对齐数
#pragma pack(1)//设置默认对齐数为1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消设置的默认对齐数还原为默认int main()
{printf(%d\n, sizeof(struct S));//6return 0;
}7.结构体传参
前面我们学习过函数传参的时候参数是需要压栈会有时间和空间上的系统开销。如果结构体所占内存过大那么参数压栈的的系统开销也会很大会导致性能的下降。所以结构体传参我们最好使用传址调用。
二、位段
C语言允许在一个结构体中以位为单位来指定其成员所占内存长度这种以位为单位的成员称为位段。利用位段能够用较少的位数存储数据。
1.位段的声明
位段的声明和结构体是类似的有两个不同 1.位段的成员必须是 int、unsigned int 或signed int 或者char类型 2.位段的成员名后边有一个冒号和一个数字。数字表示所占bit位 比如
struct A
{int _a:2;int _b:5;int _c:10;int _d:30;
};struct A的大小为8先开辟一个int大小空间4字节32bit_a _b _c总共占251017个bit位还剩下15个bit位不够_d存储所以再开辟4字节存_d总共8字节。 2.位段的内存分配 1.位段的成员可以是 int unsigned int signed int 或者是 char 类型 2.位段的空间上是按照需要以4个字节 int 或者1个字节 char 的方式来开辟的。 3.位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使用位段。 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;空间是怎样开辟的 该图是在vs2019的环境中测试的 因为全是char类型所以空间按照需求以1个字节的方式开辟先开辟一个字节存放a的低3个bit位先从字节的低位开始存放该字节还剩5个bit位而b只占4个bit位所以可以放下b剩下1个bit位不够c存放所以再往后开辟1个字节空间存放c然后剩下3个bit位也不够放d于是再往后开辟1个字节放d。空间的开辟是从低地址到高地址的方向。 3.位段的跨平台问题 1.int 位段被当成有符号数还是无符号数是不确定的。 2.位段中最大位的数目不能确定。16位机器最大1632位机器最大32写成27在16位机器会出问题。) 3.位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义。 4.当一个结构包含两个位段第二个位段成员比较大无法容纳于第一个位段剩余的位时是舍弃剩余的位还是利用这是不确定的。 跟结构体相比位段可以达到同样的效果可以很好的节省空间但是有跨平台的问题存在。
三、枚举
前面我们简单提到过枚举。枚举就是列举把可能得结果一 一列举
enum Day//星期
{Mon, //注意是逗号Tues,Wed,Thur,Fri,Sat,Sun
};
int main()
{enum Day a Mon;//0//a 10; -- 错误写法 不能改变值a Thur; //正确写法拿枚举常量来赋值enum Day b Fri;//4enum Day c Sun;//6return 0;
}{ } 中的内容是枚举类型的可能取值也叫枚举常量或者枚举元素枚举常量的默认是从0开始依次向下递增1的。
因为枚举常量不是变量所以不能改变其值。但是可以在定义的时候赋初值后面没赋值的仍遵循1原则。
enum Color//颜色
{RED 1,GREEN 2,BLUE,YELLOW 5
};
int main()
{enum Day a BLUE;//3return 0;
}枚举变量的取值为{ }内的任意一个值有且只能有其中一个值而这个值是int型的在32位系统中int型的数据占内存4个字节所以枚举变量的大小为4 枚举类型的大小也为4。
枚举的优点 增加代码的可读性和可维护性和 #define 定义的标识符比较枚举有类型检查更加严谨。防止了命名污染封装便于调试使用方便一次可以定义多个常量 四、联合 共用体
联合类型的定义联合也是一种特殊的自定义类型这种类型定义的变量也包含一系列的成员特征是这些成员公用同一块空间所以联合也叫共用体。 联合的特点联合的成员是共用同一块内存空间的这样一个联合变量的大小至少是最大成员的大小因为联合至少得有能力保存最大的那个成员。
在数据在内存中的存储笔记中我们给出了一种大小端字节序的判断方法现在我们可以利用联合体的特点来判断大小端。
#includestdio.h
int check()
{union Un{int i;char c;}u;u.i 1;return u.c;
}int main()
{int ret cheak();if (1 ret)printf(小端\n);elseprintf(大端\n);return 0;
}联合大小的计算
联合大小的计算满足三条规律 1.联合的大小至少是最大成员的大小。 2.当最大成员大小不是最大对齐数的整数倍的时候就要对齐到最大对齐数的整数倍。 3.数组的对齐数是看其类型大小而不是总大小 #includestdio.h
union Un1
{char c[5];//1*5 对齐数1int i; //4-最大对齐数
};
union Un2
{short c[7];//2*714 对齐数2int i; //4-最大对齐数
};
int main()
{printf(%d\n, sizeof(union Un1));//8printf(%d\n, sizeof(union Un2));//16return 0;
}今天的总结就到这里感谢您的观看点赞关注不迷路哦