结构体内容

目录

1.结构体类型的声明

1.1结构体的概念

1.2结构声明

1.3特殊声明

1.4结构体自引用

2.结构体变量创建和声明

3.结构成员访问操作符

4.结构体内存对齐

4.1对齐规则

4.2为什么存在内存对齐

4.2.1平台原因(移植原因):

4.2.2性能原因:

4.3修改默认对齐数

5.结构体传参

6.结构体实现位段

 6.1什么是位段

6.2位段的内存分配

6.3跨平台问题

6.4位段的应用


1.结构体类型的声明

1.1结构体的概念

结构是值的集合,这些值称为成员变量,每个成员可以是不同类型的变量

类型:

1.内置类型:char short int等

2.自定义类型:结构体、联合、枚举

1.2结构声明

struct tag
{
    memeber-list;//成员
}variable-list;//结构体变量的名字


描述学生:
struct stu
{
    char name[20];
    int age;
    char sex[5];
    char id[20];
};

int main()
{
    struct stu s1={"ss",20,98.5f};
    struct stu s2={.age=22,.name="cc",.score=55.5f};
    printf("%d",s1.name);
    return 0;
}

1.3特殊声明

可以不完全声明

//匿名结构体类型,也就是省略了结构体的名字
struct
{
    int a;
    char b;
    float;
}x;//这里是指用这个结构体类型创建了一个全局变量x
struct
{
    int a;
    char b=0;
    float c;
}a[20],*p;


int main()
{
    struct Stu s1,s2,s3;//这里是根据结构体Stu类型创建了3个变量
    return 0;
}
p=&x;
因为x变量的类型即结构体类型和p指针的类型即结构体类型是完全不一样的,因此x的地址赋给p是非法
匿名结构体若不重命名,则基本上只能使用一次

1.4结构体自引用

struct Node
{
    int data;
    struct Node next;
}
//这样自引用是错误的,结构体占据空间是无限大

正确自引用是:
struct Node
{
    int data;
    struct Node*next;//下一个节点的地址,从而串联一串在内存中分散的数据
}
typedef struct
{
    int data;
    Node*next;
}Node;
这里是非法的,因为提前在结构体内使用了Node 类型,struct后面没有写名字
也就没有重命名。

如果要用typedef,不要用匿名结构体

typedef struct Node
{
    int data;
    strct Node*next;
}Node;

2.结构体变量创建和声明

struct point
{
    itn x;
    int y;
}p1;//声明的同时定义一个类型为结构体point的全局变量p1;

struct point p2;定义结构体变量p2

struct point p3={x,y};//定义变量的同时初始化值

struct stu
{
    char name[15];
    int age;
};

struct stu s={"sdsda",20};//初始化
struct Node
{
    int data;
    struct point p;
    struct Node* next;
}n1={10,{4,5},NULL};//结构体嵌套初始化;


c99的指示器初始化,可以不按照成员顺序初始化

struct stu
{
    char name[15];
    int age;
};
struct stu s={.age=20,.name="ssd"};

3.结构成员访问操作符

结构体变量.成员变量名
结构体指针->成员变量名
struct Stu
{
 char name[15];//名字
 int age; //年龄
};
void print_stu(struct Stu s)//传递的是变量s的值
{
 printf("%s %d\n", s.name, s.age);
}
void set_stu(struct Stu* ps)//传递的结构体变量的地址
{
 strcpy(ps->name, "李四");
 ps->age = 28;
}
int main()
{
 struct Stu s = { "张三", 20 };
 print_stu(s);
 set_stu(&s);
 print_stu(s);
 return 0;
}

4.结构体内存对齐

 同样的的结构体,内部成员定义的顺序不一样,整个结构体类型的大小也不一样了。

4.1对齐规则

4.1.1

结构体的第一个成员对齐到相对结构体变量起始位置偏移量为0的地址处

4.1.2

其他成员要对齐到某个数字(对齐数)的整数倍的地址处

对齐数=编译器默认的一个对齐数与该成员变量大小的较小值(比如vs的8对齐数和变量的4大小,小的是4,那对齐数就是4),vs中默认8,linux没有默认,对齐数就是成员自身大小

4.1.3

结构体总大小为最大对齐数,(结构体中每个成员都有一个对齐数,所有对齐数中最大的)整数倍

4.1.4如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

//练习1
struct S1
{
 char c1;
 int i;
 char c2;
};
printf("%d\n", sizeof(struct S1));
这里结果是8,首先c1在偏移量0的位置

由于int类型,4比8,取4,i放在偏移量4的地方由于int占4字节,
所以要占4个偏移量,达到了8偏移量

接着c2是char类型,放偏移量9的地方,占一个偏移量
最大对齐数是4,9偏移量不是倍数处,
直到偏移量12才是4倍数处,所以最终结构体大小是12

浪费6字节

//练习2
struct S2
{
 char c1;
 char c2;
 int i;
};
printf("%d\n", sizeof(struct S2));
这里结果是8,首先c1在偏移量0的位置

接着因为1比8,取1,所以c2放在偏移量1的位置(1是1的倍数)

再接着i,int类型,4比8,取4,i放在4倍数处,
也就是偏移量4的地方,由于int占4字节,所以要占4个偏移量,达到了8偏移量

最后整体大小,其中最大对齐数是4,8偏移量正好是4的倍数,所以整体大小是8

浪费2字节

//练习3
struct S3
{
 double d;
 char c;
 int i;
};
printf("%d\n", sizeof(struct S3));
首先double占8字节,所以达到了偏移量7(0-7),
接着char类型占1字节,所以达到了偏移量8(8)
int类型是占4字节,所以从偏移量12位置开始,达到了偏移量15(12-15)
由于最大对齐数是8,所以16正好是8的倍数处,所以16是结构体整体大小

//练习4-结构体嵌套问题

struct S4
{
 char c1;
 struct S3 s3;
 double d;
};
printf("%d\n", sizeof(struct S4));
首先c1是占偏移量0的字节

接着S3结构体,按该结构体最大对齐数,则从偏移量8开始,后面16个字节都是s3,

然后是double类型
从偏移量24开始(24是8的倍数),占8个字节,达到偏移量31,

最后整个结构体的大小,由于本身不含嵌套结构体的最大对齐数是8,
嵌套结构体的最大对齐数也是8,32正好是8的倍数,所以整个结构体就是32大小

offsetof()宏,用来计算结构体成员,相较于起始位置的偏移量

4.2为什么存在内存对齐

4.2.1平台原因(移植原因):

不是所有硬件平台都能访问任意地址上任意数据,有些硬件平台只能在某些地址处取某些特定类型的数据,否则会报出硬件异常

4.2.2性能原因:

数据结构(尤其是栈)应该尽可能在自然边界上对齐。为了访问未对齐内存,处理器要2次内存访问,对齐仅需要一次访问,假如处理器总是拿8个字节,地址必须是8的倍数,比如把double类型数据的地址都以8倍数对齐,那每次都能完整拿到一个数据,如果不对齐,可能一个double数据放在2个8倍数地址中,那处理器就需要读取2次内存,从而凑到完整的数据

因此内存对齐是拿空间换时间,提高效率

设计结构时,要将占空间小的成员尽量凑到一起

struct S2
{
 char c1;
 char c2;
 int i;
};//尽量用这种

struct S1
{
 char c1;
 int i;
 char c2;
};

4.3修改默认对齐数

#pragma 这个预处理民指令,可以改变编译器的默认对齐数

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对⻬数,还原为默认
int main()
{
 printf("%d\n", sizeof(struct S));
 return 0;
}

5.结构体传参

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;
}

//但由于函数传参,是需要创建临时空间给传递来的数据的
如果直接给结构体的值,假如结构体很大,那么这时候就会浪费时间和空间
如果给地址,就能节省时间和空间

6.结构体实现位段

 6.1什么是位段

位段的声明和结构是类似的

位段的成员必须是int、unsigned int 或signed int,在c99中位段成员的类型也可以选择其他类型

位段的成员名后边有一个冒号和一个数字

struct a
{
    int _a:2;
    int _b:10;
    int _c:5;
    int _d:30;
}//这就是一个a位段,其中成员必须是int 或unsigned int 或者signed int
数字指的是二进制位,也就是存储了几位二进制位,数字不能超过变量类型规定的大小

6.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;
};

struct s S={0};
S.a=10;
S.b=12;
S.c=3;
S.d=4;


在vs的环境下
10=1010,但a只占3位,所以截取低位,也就是010,放入第一个字节的低位;
接着12是1100,b占4位,所以正好全部存进去,放在010的左边;
3是00011,第一个字节不够存,舍弃剩余空间,申请新的一个字节,
然后将5位00011放入新字节的低位,剩余3位不够d变量存储,
于是再申请一个新的字节,4是0010,放入新字节的低位,
于是,将3个字节变成十六进制,于是在内存监控中我们就能看到,
内存中存储的是0x62 03 04

6.3跨平台问题

首先是,int位段被当成无符号数还是有符号数是不确定的

位段最大位的树木不确定,因为16位机器最大是16,32位机器是32,如果写成30,16位机器运行会出问题

位段成员在内存中从左向右还是从右向左分配是不确定的

如果该字节内剩余空间不足以后续的类型存放,那么将会申请一个新的字节
,但旧的字节中剩余的空间是否被使用也是不确定的

结构虽然相比位段庞大些,但可以跨平台,但位段不能跨平台,除非不同机器使用不同的位段

6.4位段的应用

网络协议中,ip数据包的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小,也会较小一些,对网络的畅通是有帮助的

6.5位段使用注意事项

由于位段中一些成员会公用一个字节,且一个字节有一个地址,但字节中的位是没有独立地址的,所以用scanf不能直接给位段成员赋值,必须先将值赋给一个变量,在将变量赋值给位段成员

struct A
{
 int _a : 2;
 int _b : 5;
 int _c : 10;
 int _d : 30;
};
int main()
{
 struct A sa = {0};

 int b = 0;
 scanf("%d", &b);
 sa._b = b;
 return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值