文章目录
- 结构的基本知识(结构体的声明,初始化,结构体传参,结构体的自引用,结构体内存对齐)
- 一、位段
- 二、枚举
- 三、共用体和结合体
结构的基本知识
结构是一些值的集合。这些值称为成员变量。结构的每个成员可以是不同类型的变量(可以是标量,数组,指针或者其他结构体)
1.结构体的声明:
struct tag{
number-1list;
}variable-list;
(1) 例如描述一个学生:
struct 结构体关键字 stu结构体标签(struct stu即结构体类型){
char name[20];(成员变量)
short age;
char sex[5];
char id[5];}s1,s2,s3;(s1,s2,s3为三个全局变量)
(2)定义结构体变量的方式: (note:typedef指给strcut stu重新起个名字为stu)
struct point
{
int x;
int y;
}p1;//声明类型同时定义p1
struct point p2;//定义变量p2
typedef struct stu
{
char name[10];
char sex[2];
}stu;
int main()
{ stu s1;
return 0;}
匿名结构体类型:
struct{
char name[2];
char sex[2];
}x;
note:即使我们看来一样的类型,但在编译器眼中是不同的。如下代码会报warning
struct
{ char name[2];
char sex[2];
}sa;
struct
{ char name[2];
char sex[2];
}*psa;
int main
{
psa=&a;
}
2.结构体的初始化和访问: stu s1={"张三“,20,”男“,”11233“};
struct s
{
int a;
char c;
char arr[20];
double d;
};
struct t{
char ch[10];
struct s s1;
char*pc;};
int main()
{
char a[]="Hello bit\n";
struct t t1={"hehe",(100,'w',"hi",2.0),NULL};
printf("%s",t.ch)//打印hehe
printf("%s",t.s.arr);
}
结构体变量访问成员 成员内部通过(.)实现的。点操作符可以同时接受俩个操作数。
3.结构体传参
(传入结构体变量)
typedef struct stu
{
char name[10];
short age[2];
char tele[12];
}stu;
void print(str tem)
{
printf("%s",tem.name);
printf("%d",tem.age);}
int main()
{
stu s={"李四”,40,“123212332”};
print(s);
}
(传入结构体变量的地址:函数内部想改变函数外部的值)
typedef struct stu{
char name[2];
char sex[2];
short age;
}stu;
void print(stu*p)
{
printf("%s\n",p->name);
printf(“%s",p->sex);
}
int main()
{
stu s={"李四","男”,12);
print(&s);
}
结论:传地址进去能够更加节省时间和空间(函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以导致性能下降)
4.结构体的自引用(结构体自己包含自己不行的,但是可以通过指针访问下一个)
数据域 | 地址域 |
1 | 0 |
2 | 0 |
3 | 0 |
typedef struct Node
{
double d;
int date;
struct Node*next;
}node;
int main()
{
struct Node n1;
node n2;
return 0;
}
5.结构体的内存对齐(拿空间换取时间)
对齐规则:第一个成员在结构体变量偏移量为0的地址处
其他成员变量要对齐到某个数字的整数倍的地址处(对齐数为编译器默认的一个对齐 数与该成员大小的最小值 vs默认为8 gcc为4)
结构体的大小为最大对齐数的整数倍
如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整数 大小就是所有最大对齐数的整数倍
struct s1{
char c1;
int a;
char c2;
};
struct s2{
char c1;
char c2;
int a;
}
int main()
{
struct s1 s={0};
struct s2 s={0];
printf("%d\n",sizeof(s1));
printf("%d\n",sizeof(s2));
}
s1的所占内存:(12) 同理s2(8)
为什么存在内存对齐:(1)平台原因:不是所有的硬件平台都能访问任意地址上的任何数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则出现硬件异常。
(2)性能原因:数据结构应该在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做俩次内存访问;而对齐的内存只需做一次内存访问。
设计结构体的时候,我们既要满足对齐,又要节省空间,则应该让占用空间小的成员尽量集中在一起
修改默认对齐数:
#prafma pack(4)//设置默认对齐数
struct s
{
char c1;
int c2;
double c3;
}
#prafma pack()//取消默认对齐数
offsetof(相对于起始偏移量的偏移量)
一.位段(我们讲讲用结构体实现位段的能力)
1.结构的声明和位段类似,但也有不同:
位段的成员必须是int,unsigned int,signed int,char;
位段的成员名后边必须有一个冒号和一个数字
位段的空间是按照需要以4个字节或者1个字节开辟的
位段涉及很多不确定因素,位段是不跨空间的,注重可移植性的程序应该避免使用位段
struct s{
int a: 2;//只需2个比特位就可以表示a
int b:5;//只需5个比特位就可以表示b
int c: 10;
int d:30;
};
//47比特位————6个字节
int main()
{
struct s s1;
printf("%d",s1);---8
}
#include<stdio.h>
struct s{
char a:3;
char b:4;
char c:5;
char d:4;
}
int main()
{
struct s s1;
s1.a=10;
s2.b=20;
s3.c=30;
s4.d=4;
}
从右边向左边放进去
2.位段的不跨平台问题
(1)int位段当成有符号数还是无符号数不确定
(2) 位段中的最大为数目不确定(16位机器最大16,32位机器最大32,写成27在16位机器会有问题)
(3)位段在内存中从左向右分配,还是从右向左分配。
(4)当一个结构包含俩个位段的时候,第二个位段成员比较大,无法容纳第一个位段剩余的位的时候,剩余的位舍弃还是利用不确定的。
3.位段的应用。
上网在网络上传数据的时候,对数据包进行分装,使得数据包尽量小一些。
二.枚举:把可能出现的值一一列举
1.定义:
enum sex//枚举类型
{
MALE;
FEMALE;
SECRET;//枚举的可能取值(枚举常量)
};
int main{
enum sex x=MALE;
printf("%d %d %d",MALE,FEMALE,SECRET);//默认初始值0 1 2 但是可以自己修改
return 0;
}
2.优点:增加代码的可读性和可阅读性
#define仅仅可以定义一种符号,但enum是有类型的
防止了命名污染
便于调试
使用方便,一次可以定义多个常量
三、共用体和结合体
union u
{
char c;
int i;
};
int main
{
union u s;
printf("%d",u);
}
由于i和c共用一块空间,字节为4,i和c和u取地址之后相同。
//检测机器大小端
#include<stdio.h>
int check()
{
union um
{
char c;
int i;
}u;
u.i=1;
return u.c;
}
int main()
{
int m=check();
if(m==1)
printf("大端“);
else printf("小端”);
}
联合大小的计算:联合的大小至少是最大成员的大小。当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。