目录
一、结构体1、特殊的声明2、结构体自引用3、结构体变量的定义和初始化4、打印结构体二、==结构体内存对齐==1、内存对齐结构体嵌套如何求为什么存在内存对齐?2、修改默认对齐数3、宏offsetof(传参时可以传类型)4、结构体传参三、位段1、结构体实现 位段 的能力2、位段的内存分配3、位段的跨平台问题四、枚举五、联合体(共用体)1、联合类型的定义2、联合的特点3、联合大小的计算数据类型:
C语言内置类型:
— char short int long float double
自定义类型/构造类型:
— 结构体,枚举,联合
数据的存储 - 类型详解
一、结构体
1、特殊的声明
在声明结构体的时候,可以不完全地声明
匿名结构体
struct{int a;char c;double d;}s1, s2; // correctint main(){struct s3; // errreturn 0;}
匿名结构体类型
struct{int a;char c;double d;}s1, s2;struct{int a;char c;double d;}*ps;int main(){ps = &s1; // “=”: 从“*”到“*”的类型不兼容// 编译器会把上面两个声明当成完全不同的两个类型, 是非法的return 0;}
2、结构体自引用
数据结构:描述了数据在内存中存储的结构线性数据结构:顺序表,链表
同类型节点找到同类型节点
错误写法:
struct Node{int date;struct Node n; // 无线递归 err};
正确写法:存地址
struct Node{int date; // 数据域struct Node* next; // 指针域};
以下代码错在哪里,如何改正
typedef struct{int date;Node* next;}Node;
对匿名结构体重命名,而在使用时还没有产生
解决方案:
typedef struct Node{int date;struct Node* next;}Node;
3、结构体变量的定义和初始化
创建并初始化
struct Point{int x;int y;}p1 = {5,6 }, p2; // 全局struct Point p2 = {1,2 }; // 全局int main(){struct Point p1 = {3,4 }; // 局部// 程序运行到此创建// 全局变量在程序运行前就创建了return 0;}
结构体嵌套初始化
struct Point{int x;int y;}p1 = {5,6 }, p2;struct Point p2 = {1,2 };struct S{double d;struct Point p;char name[20];};int main(){struct Point p1 = {3,4 };struct S s = {3.14, {1, 5}, {"zhangsan"}};return 0;}
4、打印结构体
#include <stdio.h>struct Point{int x;int y;}p1 = {5,6 }, p2;struct Point p2 = {1,2 };struct S{double d;struct Point p;char name[20];int date[20];};int main(){struct Point p1 = {3,4 };struct S s = {3.14, {1, 5}, {"zhangsan"}, {1,2,3} };printf("%lf\n", s.d); // 3.140000printf("%d %d\n", s.p.x, s.p.y); // 1 5printf("%s\n", s.name); // zhangsanint i = 0;for (i = 0; i < 20; i++){printf("%d ", s.date[i]); // 1 2 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0}return 0;}
二、结构体内存对齐
为什么S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别?
#include <stdio.h>struct S1{char c1;int a;char c2;};struct S2{char c1;char c2;int a;};int main(){struct S1 s = {'x', 100, 'y' };printf("%d\n", sizeof(struct S1)); // 12printf("%d\n", sizeof(struct S2)); // 8printf("%d\n", sizeof(s)); // 12return 0;}
1、内存对齐
结构体的第一个成员永远放在结构体起始位置偏移为0的位置结构体成员从第二个成员开始,总是放在偏移为一个对起数的整数倍处结构体内存对齐规则:
对齐数 = 编译器默认的对齐数和变量自身大小的较小值
Linux - 没有默认对齐数(自身大小就是对齐数)
VS - 默认对齐数是8结构体的总大小必须是各个成员的对齐数中最大那个对齐数的整数倍
练习:
struct S3{double d;char c;int i;};
// 16
结构体嵌套如何求
#include <stdio.h>struct S3{double d;char c;int i;};struct S4{char c1;struct S3 s3;double d;};int main(){printf("%d\n", sizeof(struct S4));return 0;}
规则4:
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
为什么存在内存对齐?
平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器
需要作两次内存访问;而对齐的内存访问仅需要一次访问
总结:
结构体的内存对齐是拿空间来换取时间的做法
在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
2、修改默认对齐数
用预处理指令#pragma设置默认对齐数 并取消设置,还原默认
例:修改结构体S1对齐数为1
#include <stdio.h>// 紧挨着放 没有空间浪费 效率低#pragma pack(1)struct S1{char c1;int i;char c2;};#pragma pack()int main(){struct S1 s[] = {0 };printf("%d\n", sizeof(s)); // 6return 0;}
3、宏offsetof(传参时可以传类型)
计算一个结构体成员相对于结构体在内存中存储的起始位置的偏移量
size_t offsetof( structName, memberName );
#include <stdio.h>#include <stddef.h>struct S1{char c1;int i;char c2;};int main(){printf("%d\n", offsetof(struct S1, c1)); // 0printf("%d\n", offsetof(struct S1, i)); // 4printf("%d\n", offsetof(struct S1, c2)); // 8return 0;}
4、结构体传参
传值 / 传址
#include <stdio.h>struct S{int data[1000];int num;};// 结构体传参void print1(struct S tmp){int i = 0;for (i = 0; i < 10; i++){printf("%d ", tmp.data[i]);}printf("\nnum = %d\n", tmp.num);}// 结构体地址传参void print2(struct S* ps){int i = 0;for (i = 0; i < 10; i++){printf("%d ", ps->data[i]);}printf("\nnum = %d\n", ps->num);}int main(){struct S s = {{1,2,3,4,5,6,7,8,9,10}, 100 };print1(s); // 传结构体print2(&s); // 传地址return 0;}
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
三、位段
1、结构体实现 位段 的能力
位段是可以节省空间的位段 - 二进制位#include <stdio.h>struct A{int _a : 2; // _a - 2个bit为位int _b : 5; // _b - 5个bit为位int _c : 10; int _d : 30;};int main(){printf("%d\n", sizeof(struct A)); // 8return 0;}
2、位段的内存分配
位段的成员可以是 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; // 1010 - 010s.b = 12; // 00001100 - 1100s.c = 3; // 00000011 - 00011s.d = 4; // 00000100 - 0100}
空间是如何开辟的?
推测:char-> 先开辟1字节 00000000a-3bit 010b-4bit 1100剩下的空间不够用 再开辟1字节c-5bit 00011剩下的空间不够用 再开辟1字节d-4bit 0100左<-右 高0(1100)(010) 000(00011) 0000(0100)62 03 04
3、位段的跨平台问题
int 位段被当成有符号数还是无符号数是不确定的。位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
四、枚举
enum Color枚举类型。{}中的内容是枚举类型的可能取值,也叫 枚举常量
#include <stdio.h>enum Color{RED = 2, // 赋初值GREEN,BLUE,};int main(){enum Color c = GREEN; // 枚举类型赋可能取值if (c == GREEN){printf("绿色\n");}return 0;}
枚举的优点:
我们可以使用 #define 定义常量,为什么非要使用枚举
增加代码的可读性和可维护性和#define定义的标识符比较枚举有类型检查,更加严谨。防止了命名污染(封装)便于调试使用方便,一次可以定义多个常量
例:实现计算器清晰明了
enum Option{EXIT, // 0ADD, // 1SUB,MUL,DIV,};int main(){int input = 0;do{menu();printf("请选择:>\n");switch (input){case ADD:break;case SUB:break;case MUL:break;case DIV:break;case EXIT:break;}} while (input);return 0;}
五、联合体(共用体)
1、联合类型的定义
联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)
#include <stdio.h>//联合类型的声明union Un{char c;int i;};int main(){//联合变量的定义union Un u = {0 };// 地址一样printf("%p\n", &u);printf("%p\n", &(u.c));printf("%p\n", &(u.i));return 0;}
2、联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
判断当前计算机的大小端存储:
#include <stdio.h>// 1int main(){int a = 1;//0x 00 00 00 01//低---------------------> 高//01 00 00 00 - 小端存储//00 00 00 01 - 大端存储char* pc = (char*)&a;if (*pc == 1){printf("小端\n");}else{printf("大端\n");}}
使用联合体:
// 2int check_sys(){union U{char c;int i;}u;u.i = 1;return u.c;}int main(){if (check_sys() == 1){printf("小端\n");}else{printf("大端\n");}return 0;}
3、联合大小的计算
联合的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。例:计算以下代码结果
union Un1{char c[5]; // 5 1int i; // 4 84 // 联合体的总大小必须是4的倍数 在c后浪费3个字节 结果是8};union Un2{short c[7]; // 14 2int i; // 4 8 4// 14不是4的倍数 结果是16};int main(){printf("%d\n", sizeof(union Un1)); // 8printf("%d\n", sizeof(union Un2)); // 16}
通讯录程序