自定义类型--结构体

今天来复习一下自定义类型中结构体类型的内容,并且深度分析

结构体

struct tag
{
member-list;
}variable-list;
struct Stu
{
    //成员变量
    char name[20];
    int age;
    float weight;
}s4,s5,s6;//全局变量
int main()
{
    struct Stu s1;//局部变量
    struct Stu s2;
    struct Stu s3;
    return 0;
}

结构体的特殊声明

//匿名结构体类型
struct
{
    char c;
    int a;
    double b;
}s1,s2;

struct
{
    char c
    int a;
    double b;
}*ps;
int main()
{
    s2;//只能用结构体创建好的全局变量
    //ps = $s1;//err
    return 0;
}//编译器会把上面的声明当作完全不同的两个类型

结构体的自引用

struct Node
{
    int data;
    struct Node*next;
};
int main()
{
    struct Node n1;
    struct Node n2;
    n1.next = &n2;
}//链表
//在一个结构体成员中存储的地址访问另一个结构体

结构体重命名

typedef struct
{
	int data;
	char c;
}S;//这里的S是结构体类型而不是全局变量
typedef struct
{
    int data;
    Node*next;
}Node;
//这样写存在问题,Node被调用,而定义结构体类型一开始未提及
//解决方案:
typedef struct Node
{
   	int data;
    struct Node* next;
}Node;

结构体初始化

#include<stdio.h>
struct S
{
    int a;
    char c;
}s1;
struct S s3;
struct B
{
    float f;
    struct S s;
};
int main()
{
    struct S s2 = {100,'q'};//默认的初始化方式
    struct S s3 = {.c = 'r',.a = 2000};//按照想法初始化
    struct B sb = {3.14f,{200,'w'}};//默认的初始化
    printf("%f,%d,%c\n",sb.f,sb.a,sb.s.c);
    return 0;
}
#include<stdio.h>
struct S
{
    char name[100];
    int*ptr;
};
int main()
{
    struct S s = {"David",NULL};
    return 0;
}

结构体传参

struct S
{
	int arr[100];
	int n;
};
void print1(struct S ss)
//struct S是类型
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ss.arr[i]);
	}
	printf("\n%d\n", ss.n);
}
void print2(struct S* ps)
{
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n%d\n ", ps->n);
}
int main()
{
	struct S s = { {1,2,3,4,5},100 };
	print1(s);
	print2(&s);
	return 0;
}

上面的 print1 和 print2 函数哪个好些?

答案是:首选 print2 函数。

原因:函数传参的时候,参数是需要压栈的。

如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

结论:

结构体传参的时候,要传结构体的地址。

结构体内存对齐

struct s1
{
    int a;
    char c;
};
struct s3
{
    char c1;
    int a;
    char c2;
};
struct s3
{
    char c1;
    int a;
    char c2;
    char c3;
}
int main()
{
    printf("%d\n",sizeof(struct s1));
    printf("%d\n",sizeof(struct s2));
    printf("%d\n",sizeof(struct s3));
}
//8 8 16

1.结构体的第一个成员永远放在0偏移处

2.从第二个成员开始,以后每个成员都要对齐到某个对齐数的整数倍处,这个对齐数是成员自身大小和默认对齐数的较小值

备注:VS环境下默认对齐数是8

gcc环境下没有默认对齐数,没有默认对齐数时,对齐数就是成员自身大小

3.当成员全部存放进去后

结构体的总大小必须是,所有成员的对齐数中最大对齐数的整数倍,如果不够,则浪费空间对齐。

4.如果嵌套了结构体,嵌套的结构体成员要对齐到自己成员的最大对齐数的整数倍处,整个结构体的大小,必须是最大对齐数的整数倍,最大对齐数包含中嵌套的结构体成员的对齐数。

为什么存在内存对齐?

1. 平台原因(移植原因):

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

2.性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问 。

总体来说:

结构体的内存对齐是拿空间来换取时间的做法

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

让占用空间小的成员尽量集中在一起

//例如:
struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int i;
};
//第一个空间占12
//第二个空间占8

S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别

#include<stido.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
    char c1;l
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

#pragma pack(1)//设置默认对齐数为1
struct s2
{
    char c1;
    int i;
    char c2;
};
int main()
{
    //输出结果?
    printf("%d\n",sizeof(struct S1));//6
    printf("%d\n",sizeof(struct S2));
    return 0;
}

在对齐方式不合适的时候,我们可以自己更改默认对齐数

offsetof:计算偏移量

结构体实现位段能力

位段的声明和结构是类似的,有两个不同:

1.位段的成员必须是int,unsigned int或signed int

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

比如:

//位段:二进制位(比特位)-节省空间按
//例如:a只有00 01 11这四个数,用不到后面int原本分配剩下的30个比特位
struct A
{
    int _a: 2;
    int _b: 5;
    int _c: 10;
    int _d: 30;
};
int main()
{
    struct A sa = {0};
    printf("%d\n",sizeof(sa));//47 bit
    						  //8个字节
}

位段的内存分配

  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

  1. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

  1. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

struct A
{
    int _a: 2;
    int _b: 5;
    int _c: 10;
    int _d: 30;
};
//先开辟4个字节
//32 - 2 -> 30
//30 - 5 -> 25
//25 - 10-> 15
//再开辟4个字节
//先用15特位还是用下4个字节32比特位取决于编译器

VS环境下的内存分配情况的验证

c//一个例子
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;
//假设分配的内存中的比特位是由右向左使用
//分配的内存剩余的比特位不够使用时,浪费掉
//10 -> 1010, 三个比特位就是 010
//12 -> 1100, 四个比特位就是1100
//3 -> 11,    五个比特位就是00011
//4 -> 100,   四个比特位就是0100
//画到内存  0110 0010 0000 0011 0000 0100
//			6     2   0    3    0    4

位段的跨平台问题
  1. int 位段被当成有符号数还是无符号数是不确定的。

  1. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。

  1. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

  1. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

总结:

跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

位段的应用

以上便是自定义类型中的结构体的全部笔记内容

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值