行政事业单位网站建设建议,重庆seo网站排名,专业网页网站设计图书,织梦网站程序模板我们知道#xff0c;C语言是允许我们自己来创造类型的#xff0c;这些类型就叫做——自定义类型。
自定义类型又包括结构体类型#xff0c;联合体类型还有枚举类型。
今天的文章#xff0c;我们就着重讲解这其中的结构体类型。
目录
结构体的声明
1.1结构的基础知识
…我们知道C语言是允许我们自己来创造类型的这些类型就叫做——自定义类型。
自定义类型又包括结构体类型联合体类型还有枚举类型。
今天的文章我们就着重讲解这其中的结构体类型。
目录
结构体的声明
1.1结构的基础知识
1.2结构的声明
1.3 匿名结构体的情况
1.4结构的自引用 1.5重命名匿名结构体的情况
1.6 结构体变量的定义和初始化 1.7 结构体内存对齐
1.8为什么存在内存对齐?
1.9我们可以耍些小聪明达到节省空间的效果。
2.1修改默认对齐数
2.2 结构体传参
3.1位段
3.2 位段的内存分配
3.3 位段的跨平台问题
结构体的声明
1.1结构的基础知识 结构是一些值的集合这些值称为成员变量。结构的每个成员可以是不同类型的变量。 1.2结构的声明 struct tag { member-list; }variable-list; 我们以这种方式来描述一个结构体。下面是简单的示范我们来描述一个学生
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}; //分号不能丢 定义局部变量和全局变量的关系
#define _CRT_SECURE_NO_WARNINGS
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}s1,s2,s3; //全局变量
int main()
{struct Stu s4;struct Stu s5;//局部变量return 0;
}
1.3 匿名结构体的情况
也可以省略不写结构体标签不过这样会导致一个结果结构体只能定义一次类型。
#define _CRT_SECURE_NO_WARNINGS
#includestdio.h
struct
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}s1; //全局变量
struct
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}*ps; //全局变量
int main()
{s1.age 1;printf(%d, s1. age);return 0;
} 在上述的代码中体现为定义结构体变量s1之后无法再次定义诸如s2s3等结构体类型。
不过要是你本来就准备只用一次结构体的话定义一个匿名结构体也不错就是了。
上面的两个结构在声明的时候省略掉了结构体标签, 那么问题来了 //在上面代码的基础上下面的代码合法吗 pss1; 答案是否定的及时两个结构体里面的元素都相同编译器也会他们当成两个完全不同的类型所以是非法的。
1.4结构的自引用
我们想要使用结构体实现类似于链表的功能。 在结构中包含一个类型为该结构本身的成员是否可以呢 #includestdio.h
struct Node
{int data;struct Node n;
};
int main()
{return 0;
}我们开动小脑筋立马就发现了错误。
struct Node这个节点它所占用的空间有多大呢
它不仅要存放一个整形还要存放一个n。
这就无限循环下去了struct Node里面还有一个struct Node。
大小是无法得出的这是一个错误示范。
我们转变战略用指针来实现。
#define _CRT_SECURE_NO_WARNINGS
#includestdio.h
struct Node
{int data;//4struct Node *next;//4/8
};
int main()
{struct Node n1;struct Node n2;n1.next n2;return 0;
}创建两个节点n1,n2把它们像链条一样串起来。 编译器没有报错这样的写法是正确的同时我们发现struct Node的大小可以轻而易举地算出我们得出一个结论 不是在自己的类型里面包含一个自己类型的变量而是在自己的类型里面包含一个自己类型的指针。这样的实现方式才是可行的。 1.5重命名匿名结构体的情况
下面的代码是否可行呢
#includestdio.h
typedef struct
{int data;
}S;
int main()
{return 0;
}可行不过S不再是匿名结构体的变量而是变成了匿名结构体类型。
怎么用呢这么用
#includestdio.h
typedef struct
{int data;
}S;
int main()
{S s;s.data 1;printf(%d, s.data);return 0;
}能用这种方式模拟实现上面的链表呢
这样写行吗
typedef struct
{int data;Node* next;
}Node;
不行在没有重命名出Node时就调用了Node。
在这种情况下我们只能老老实实地写出类型名了
typedef struct Node
{int data;struct Node* next;
}Node;
1.6 结构体变量的定义和初始化 有了结构体类型那如何定义变量其实很简单。 int x;int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量
struct Point
{
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};//结构体嵌套初始化 1.7 结构体内存对齐
我们已经掌握了结构体的基本使用了。 现在我们深入讨论一个问题计算结构体的大小。 这就到了本文的重中之重 结构体内存对齐。计算以下的结构体大小。
#includestdio.h
int main()
{struct S1{char c1;int i;char c2;};printf(%d\n, sizeof(struct S1));//练习2struct S2{char c1;char c2;int i;};printf(%d\n, sizeof(struct S2));//练习3struct S3{double d;char c;int i;};printf(%d\n, sizeof(struct S3));//练习4-结构体嵌套问题struct S4{char c1;struct S3 s3;double d;};printf(%d\n, sizeof(struct S4));
}
运行结果如下是不是跟想的完全不一样
没错结构体的大小并不是成员大小的简单相加而是有自己的一套规则的。 结构体的第一个成员永远是放在零偏移处。从第二个成员开始以后每个对齐成员都要对齐到某个对齐数的整数倍处。这个对齐数是成员自身大小和默认对齐数的较小值。VS中默认的值为8 结构体总大小为最大对齐数每个成员变量都有一个对齐数的整数倍。 如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍。如果不够则浪费空间来对齐。我们以s1为例子来试验一下上述规则如图所示。 因为从第二个成员开始以后每个对齐成员都要对齐到某个对齐数的整数倍处。
所以1,2,3三个字节被浪费int类型的存储从4开始到7char类型存到8处。
最后结构体总大小为最大对齐数每个成员变量都有一个对齐数的整数倍。 S1中最大对齐数为4,结构体总大小要为最大对齐数每个成员变量都有一个对齐数的整数倍。而现在大小为9为了让其变为4的倍数结构体S1的总大小变为12。
再看S4的情况 白色为浪费部分黄色为char绿色是double粉色是int。
1.8为什么存在内存对齐? 1.不同硬件平台不一定支持访问任意内存地址数据某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常。使用内存对齐可以保证每次访问都从块内存地址头部开始存取 2.提高cpu内存访问速度内存是分块的如两字节一块四字节一块考虑这种情况一个四字节变量存在一个四字节地址的后三位和下一个四字节地址的前一位这样cpu从内存中取数据便需要访问两个内存并将他们组合起来降低cpu性能 用内存对齐达到了用空间换时间的效果 1.9我们可以耍些小聪明达到节省空间的效果。 让占用空间小的成员尽量集中在一起。 //例如
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};S1和S2类型的成员一模一样但是S1和S2所占空间的大小有了一些区别。
2.1修改默认对齐数
我们可以通过#pragma pack()指令来修改默认对齐数。
#include stdio.h
#pragma pack(1)
//设置默认对齐数为1
struct S1
{char c1;int i;char c2;
};
int main()
{//输出的结果是什么printf(%d\n, sizeof(struct S1));return 0;
} 可以看到答案不再是12默认对齐数确实被修改了。
想要取消的话就引入一个空指令。
#include stdio.h
#pragma pack(1)//设置默认对齐数为1
#pragma pack()//取消设置的默认对齐数还原为默认
struct S1
{char c1;int i;char c2;
};int main()
{//输出的结果是什么printf(%d\n, sizeof(struct S1));return 0;
} 2.2 结构体传参 下面print1和print2那个比较好
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;
}
上面的 print1 和 print2 函数哪个好些 答案是首选print2函数。 原因函数传参的时候参数是需要压栈会有时间和空间上的系统开销。 如果传递一个结构体对象的时候结构体过大参数压栈的的系统开销比较大所以会导致性能的下降。 所以结构体传参数时要传结构体的地址。
3.1位段
结构体讲完就得讲讲结构体实现位段的能力。struct A
{int _a:2;int _b:5;int _c:10;int _d:30;
}; A就是一个位段的类型位段可以控制所给的空间大小达到节省空间的目的。 它所占空间是多大
#include stdio.h
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{printf(%d\n, sizeof(struct A));return 0;
} 它占了8*864个比特位。
从16个字节优化到8个字节位段的功能可以说是十分强大。
3.2 位段的内存分配 1. 位段的成员可以是 int unsigned int signed int 或者是 char 属于整形家族类型 2. 位段的空间上是按照需要以4个字节 int 或者1个字节 char 的方式来开辟的。 3. 位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使用位段。 #include stdio.h
//一个例子
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;
}
首先做一个假设假设内存中的比特位是由右向左使用的。一个字节内部剩余的比特位不够使用时直接浪费掉。我们猜想是这个样子。
转换成16进制为 62 03 04 我们来调试看看 我们的猜想是正确的
3.3 位段的跨平台问题 1. int 位段被当成有符号数还是无符号数是不确定的。 2. 位段中最大位的数目不能确定。16位机器最大1632位机器最大32写成27在16位机 器会出问题。 3. 位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义。 4. 当一个结构包含两个位段第二个位段成员比较大无法容纳于第一个位段剩余的位时是 舍弃剩余的位还是利用这是不确定的。 总结 跟结构相比位段可以达到同样的效果但是可以很好的节省空间但是有跨平台的问题存在。 这篇博客旨在总结我自己阶段性的学习要是能帮助到大家那可真是三生有幸如果觉得我写的不错的话还请点个赞和关注哦~我会持续输出编程的知识的