中国建设银行官网站汽车卡,网站建设便捷,软件网站开发培训,上海城市建设官方网站自定义类型 结构体结构体的声明特殊的声明结构的自引用结构体变量的定义和初始化结构体内存对齐修改默认对齐数offsetof宏 结构体传参 位段位段的定义#xff08;声明#xff09;位段的内存分配位段的跨平台问题位段的应用 枚举枚举类型的定义及使用枚举的优点 联合#xff… 自定义类型 结构体结构体的声明特殊的声明结构的自引用结构体变量的定义和初始化结构体内存对齐修改默认对齐数offsetof宏 结构体传参 位段位段的定义声明位段的内存分配位段的跨平台问题位段的应用 枚举枚举类型的定义及使用枚举的优点 联合共用体联合类型的定义联合的特点联合大小的计算 结构体
结构是一些值的集合这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构体的声明
struct tag
{member-list;
}variable-list;struct——关键字
tag——结构体的标签名
struct tag——结构体类型名
member-list——成员列表成员变量的列表
variable-list——变量列表实例一描述一本书
struct Book
{char name[20];int price;char id[12];
}b4,b5,b6;//b4,b5,b6是全局的int main()
{//b1,b2,b3是局部变量struct Book b1;struct Book b2;struct Book b3;return 0;
}特殊的声明
在声明结构的时候可以不完全的声明。
实例一:
//匿名结构体类型
//两个结构在声明的时候省略掉了结构体标签tag
struct
{int a;char b;float c;
}x;
struct
{int a;char b;float c;
}a[20], *p;int main()
{px;//会报警告//注在编译器看来虽然结构体中的成员是一模一样的但编译器仍然认为这两个结构体是不同的类型在编译器看来是不合理的return 0;
}
注匿名结构体类型创建好之后只能用一次没有标签构不成结构体类型
结构的自引用
实例一 struct Node
{int data;struct Node next;
};
//不可以这样定义这样会死递归的注在结构中包含一个类型为该结构本身的成员是不可以的
结构体自引用
struct Node
{int data;struct Node* next;
};注
数据结构是数据在内存中存储的结构线性数据结构顺序表、链表树形数据结构二叉树链表中的节点分为两部分数据域存储的是数据和指针域存放的是下一个节点的地址
总结结构体自引用不是包含同类型的结构体变量而是包含同类型的结构体的指针
实例二
typedef struct
{int data;Node* next;
}Node;
//这样写代码是错误的
//先有Node*定义出的成员才能对类型进行重命名产生Node陷入先有蛋还是先有鸡的问题//解决方案
typedef struct Node
{int data;struct Node* next; //重命名的Node不能在成员里用
}Node;结构体变量的定义和初始化
实例一
struct Point
{int x;int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2//初始化定义变量的同时赋初值。
struct Point p3 {x, y};struct Stu //类型声明
{char name[15];//名字int age; //年龄
};
struct Stu s {zhangsan, 20};//初始化struct Node
{int data;struct Point p;struct Node* next;
}n1 {10, {4,5}, NULL}; //结构体嵌套初始化struct Node n2 {20, {5, 6}, NULL};//结构体嵌套初始化实例二
struct S
{char c;int i;
}s1, s2;struct B
{double d;struct S s;char c;
};
int main()
{struct S s3 {x, 20};struct B sb { 3.14, {w, 100},q };//. //-printf(%lf %c %d %c\n, sb.d, sb.s.c, sb.s.i, sb.c);return 0;
}
注
结构体不完全初始化时其余没初始化的默认赋值0结构体是直接可以赋值的
结构体内存对齐
实例一
#includestdio.h
struct S
{char c;int i;char c2;
};
int main()
{struct S s { 0 };printf(%d\n, sizeof(s));return 0;
}分析
结构体的对齐规则
第一个成员在与结构体变量偏移量为0的地址处。其他成员变量要对齐到某个数字对齐数的整数倍的地址处。结构体总大小为最大对齐数每个成员变量都有一个对齐数的整数倍。如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍。
注
偏移量是结构体变量中成员的地址和结构体变量地址之差偏移量是相对于结构体起始位置的偏移量。VS默认对齐数为8linux无对齐数即该成员的字节大小为对齐数对齐数 编译器默认的一个对齐数 与 该成员大小的较小值。对于Linux中GCC编译器对齐数就是该成员的大小
实例二
#include stdio.hstruct S4
{double d;char c;int i;
};
struct S5
{char c1;struct S4 s4;double d;
};int main()
{struct S5 s5 {0};printf(%d\n, sizeof(s5)); //32return 0;
}解析 为什么存在内存对齐 大部分的参考资料都是如是说的
平台原因(移植原因) 不是所有的硬件平台都能访问任意地址上的任意数据的某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常。性能原因 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于为了访问未对齐的内存处理器需要作两次内存访问而对齐的内存访问仅需要一次访问。
总体来说结构体的内存对齐是拿空间来换取时间的做法。
在设计结构体的时候既要满足对齐又要节省空间。可以通过**让占用空间小的成员尽量集中在一起。**从而达到节省空间的目的
实例三 可以看出两个结构体的成员相同但顺序不同从而结构体所占内存的大小也就不同
修改默认对齐数
#pragmg pack(N) //设置默认对齐数为N实例一
#includestdio.h
//默认对齐数是8
//把默认对齐数改为2
#pragma pack(2)
struct S
{char c1; //结构体S按照默认对齐数为2进行内存对齐int i;char c2;
};
#pragma pack() //取消设置的默认对齐数还原为默认
int main()
{struct S s { 0 };printf(%d\n, sizeof(s));return 0;
}分析
注
设置默认对齐数时一般设置成2^n几乎不会设置成奇数#pragma pack1将默认对齐数设置成1不存在结构体的内存对齐修改默认对齐数的意义是结构在对齐方式不合适的时候程序员可以自己更改默认对齐数
offsetof宏
offsetof是C语言本身具有的宏用于计算结构体中某个成员相较于这个结构体变量起始位置的偏移量。 offsetof宏的使用 结构体传参
实例一
struct S
{int data[1000];int num;
};struct S s {{1,2,3,4}, 1000};//结构体传参
void print1(struct S s)
{printf(%d\n, s.num);
}//结构体地址传参
void print2(struct S* ps)
{printf(%d\n, ps-num);
}int main()
{print1(s); //传结构体print2(s); //传地址return 0;
}注printf1函数和printf2函数的功能一样但是printf2函数要比printf1函数的性能要好时间和空间消耗较低。原因是函数传参的时候参数是需要压栈会有时间和空间上的系统开销。如果传递一个结构体对象的时候结构体过大参数压栈的的系统开销比较大所以会导致性能的下降。因此结构体传参的时候要传结构体的地址。
位段
位段是依附于结构体的
位段的定义声明
位段的声明和结构是类似的有两个不同
位段的成员必须是 int、unsigned int 或signed int或char。位段的成员名后边有一个冒号和一个数字 位段的定义 struct A //A就是一个位段类型
{int _a:2;int _b:5;int _c:10;int _d:30;
};位段的内存分配
实例一
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;};
int main()
{printf(%d\n, sizeof(struct A));return 0;
}注冒号后数字是占多少个比特位。
分析
struct A
{//4个字节-32bitint _a : 2;//成员占2个bit位int _b : 5;//成员占5个bit位int _c : 10;//成员占10个bit位//4个字节-32bitint _d : 30;//成员占30个bit位 //int _d : 30;中的_d位段成员在内存中开辟30个比特位是用之前开辟完剩余的比特位和新开辟的32位比特位还是只用新开辟的32位比特位这是不确定的。因为C语言没有明确规定到底用不用前面的空间。
};
int main()
{printf(%d\n, sizeof(struct A));//448return 0;
}位段的内存分配 位段的成员可以是 int unsigned int signed int 或者是 char 属于整形家族类型位段的空间上是按照需要以4个字节 int 或者1个字节 char 的方式来开辟的。位段涉及很多不确定因素位段是不跨平台的不同平台上实现方式是不同的注重可移植的程序应该避免使用位段。
实例二
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
int main()
{struct S s { 0 };s.a 10;s.b 12;s.c 3;s.d 4;return 0;
}分析 该分析只适用于VS 注
在VS编译器下一个字节四个字节内部的数据在使用时先使用低比特位的数据再使用高比特位的数据从右向左使用当一块空间剩余的空间内容不够下一个成员使用的时候这块空间会浪费掉
位段的跨平台问题
int 位段被当成有符号数还是无符号数是不确定的。位段中最大位的数目不能确定。16位机器int最大1632位机器int最大32写成27在16位机器会出问题。位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义。当一个结构包含两个位段第二个位段成员比较大无法容纳于第一个位段剩余的位时是舍弃剩余的位时还是利用这是不确定的。
补充
在16位机器下整型类型占2个字节在32位机器下整型类型占4个字节
总结跟结构相比位段可以达到同样的效果但是可以很好的节省空间但是有跨平台的问题存在。
位段的应用
IP数据报的格式 枚举
枚举是把可能的取值一一列举。 枚举是创造新的类型这种类型取值是非常有限的明确的几种取值这时可以用枚举创造枚举类型
枚举类型的定义及使用
实例一
//声明枚举类型enum Day//星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};enum Sex//性别
{MALE,FEMALE,SECRET
}enum Color//颜色
{RED,GREEN,BLUE
};实例二
enum Color //enum枚举关键字 Color枚举的类型
{RED, GREEN, //枚举类型的可能取值 这些都是枚举常量BLUE
};
int main()
{enum Color c BLUE; //在C中可以这样写enum Color c Color::BLUE; ::限定符BLUE来自于Colorreturn 0;
}注
枚举类型是用来描述那些能够把值一一确定出来的有限的几种值的类型的一种描述枚举而结构体是为了描述复杂对象的。枚举类型是一种类型枚举那些可能取值的值的数值是整型
要点
{}中的内容是枚举类型的可能取值也叫枚举常量 。这些可能取值都是有值的默认从0开始一次递增1当然在定义的时候也可以赋初值。
实例三 注
不能写成enum Color c2;在C编译器中语法是不支持的int类型无法转换成enum Color类型类型不匹配枚举常量不能修改但可以赋初值
实例四
//声明枚举类型
enum Color
{RED5,//5GREEN8,//8BLUE//9
};int main()
{enum Color c BLUE;printf(%d\n, sizeof(c)); //4//计算枚举类型的大小时它只有一种可能取值所以它的大小就是整形大小return 0;
}注枚举类型所占内存的大小恒为4
枚举的优点
枚举与#define 定义常量的区别
增加代码的可读性数字替换成它所对应符号的名字这些名字具有可读性和可维护性和#define定义的标识符比较枚举有类型检查更加严谨。防止了命名污染封装便于调试使用方便一次可以定义多个常量
注调试代码是在生成可执行程序之后运行代码时才可以进行一定的调试
联合共用体
联合类型的定义
联合也是一种特殊的自定义类型。这种类型定义的变量也包含一系列的成员特征是这些成员公用同一块空间所以联合也叫共用体。
联合类型的声明、定义
//联合类型的声明
union Un
{ char c; int i;
}; //联合变量的定义
union Un un; //计算联合变量的大小
printf(%d\n, sizeof(un));实例一
union Un
{char c;//1int i;//4
};
int main()
{union Un u {10};//union Un u { 10,1000 }; //err 联合体多个成员共用一块空间初始化时不可能对多个成员进行初始化其实只初始化一个成员u.i 1000;u.c 100;printf(%p\n, u);printf(%p\n, (u.c));printf(%p\n, (u.i));printf(%d\n, sizeof(u)); //4return 0;
}注联合体在同一时间只能使用它其中的一个成员
实例二 注联合体初始化时会对联合体第一个成员进行初始化
联合的特点
联合的成员是共用同一块内存空间的这样一个联合变量的大小至少是最大成员的大小因为联合至少得有能力保存最大的那个成员。
实例一 经典面试 判断当前计算机的大小端存储
//判断当前计算机的大小端存储int check_sys1()
{int a 1;if ((*(char*)a) 1){return 1;//小端}else{return 0;//大端}
}int check_sys2()
{union U{char c;int i;}u;u.i 1;return u.c;//返回1 就是小端//返回0 就是大端
}int main()
{int ret check_sys2();if (ret 1)printf(小端\n);elseprintf(大端\n);return 0;
}联合大小的计算
联合体大小计算规则
联合的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候就要对齐到最大对齐数的整数倍。
实例一
union Un
{char a[5];//对齐数1相当于放了5个字符类型的变量 int i;//对齐数4};int main()
{union Un u;printf(%d\n, sizeof(u)); //8return 0;
}
实例二
union Un
{ short s[5];//对齐数2 int a;//对齐数4
};int main()
{union Un u;printf(%d\n, sizeof(u)); //12return 0;
}