结构体

92 阅读4分钟

自定义类型:结构体

内置类型: char short int float double

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

1.结构体

1.1 结构

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

1.2结构的声明

struct tag
{
	member - list;
};variable - list;
struct Stu
{
	//学生的相关属性
	char name[20];
	int age;
};//仅仅只创建了一个类型
struct Stu
{
	//学生的相关属性
	char name[20];
	int age;
}S1,S2;//S1和S2是struct Stu的变量,是全局变量还是局部类型取决于变量所在的位置
//s1和s2是全局变量
int main()
{
   struct Stu s3;//s3是局部变量
    return 0;
}

1.3特殊的声明

匿名结构体类型

struct
{
    char name[20];
    int age;
    
}s1;
int main()
{
    return 0;
}//一次性使用

1.4结构的自引用

数据结构

数据在内存中的存储结构

线形(顺序表 链表)

树形(二叉树)

数据结构直观图.png

struct Node
{
	int data;
	struct Node next;
};//error
//实现链表
struct Node
{
	int data;
	struct Node* next;
};
//或者使用typedef
typedef struct Node
{
	int data;
	struct Node* next;
}Node;
//struct Node n1;--->Node n2;

1.5结构体变量的定义和初始化

struct Point
{
	int x;
	int y;
}p1;


struct score
{
	int n;
	char ch;
};

struct Stu
{
	char name[20];
	int age;
	struct score s;
};

int main()
{
	struct Point p2 = { 3,4 };
	struct Stu s1 = { "zhangsan",20,{100,'q'} };//结构体嵌套初始化
	printf("%s %d %d %c\n", s1.name, s1.age, s1.s.n, s1.s.ch);
	return 0;
}

结构体变量的定义和初始化.png

1.6结构体内存对齐

struct S1
{
	char c1;//1
	int i;//4
	char c2;//1
};
struct S2
{
	char c1;//1
	char c2;//1
	int i;//4
	
};


int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;

结构体的内存对齐.png 对齐规则:

1.第一个成员在与结构体变量偏移量为0的地址处

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

对齐数=编译器默认的一个对齐数与该成员大小的较小值

(vs中默认的值为8)

3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍

4.如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所以最大对齐数(含嵌套结构体的对齐数)的整数倍

S1的对齐规则(浪费6个字节)

对齐方式直观图.png

#include<stddef.h>

struct S1
{
	char c1;//1
	int i;//4
	char c2;//1
};
struct S2
{
	char c1;//1
	char c2;//1
	int i;//4
	
};
//offsetof
//(type,member)

int main()
{
	/*printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));*/
	printf("%d\n", offsetof(struct S1, c1));
	printf("%d\n", offsetof(struct S1, i));
	printf("%d\n", offsetof(struct S1, c2));

	return 0;
}
int main()
{
	/*printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));*/
	/*printf("%d\n", offsetof(struct S1, c1));
	printf("%d\n", offsetof(struct S1, i));
	printf("%d\n", offsetof(struct S1, c2));*/
	printf("%d\n", offsetof(struct S2, c1));
	printf("%d\n", offsetof(struct S2, c2));
	printf("%d\n", offsetof(struct S2, i));

	return 0;
}

对齐结果.png

S2的结果.png

S2的对齐方式(仅浪费2个字节)

S2的直观图.png

为什么存在内存对齐?

1.平台原因:

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

2.性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

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

--如何做到既满足对齐,又要节省空间:

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

1.7修改默认对齐数

vs中为8

#pragma这个预处理指令可以改变我们的默认对齐数

#pragma pack(4)//开始
struct S
{
	int i;//0~3
	//4
	double d;//4~11
};
#pragma pack()//结束
int main()
{
	printf("%d\n", sizeof(struct S));//12
	return 0;
}

不想对齐的话:#pragma pack(1)

1.8结构体传参

struct S
{
	int data[1000];
	int num;
};

void print1(struct S ss)
{
	int i = 0;
	for (i = 0;i < 3;i++)
		printf("%d ", ss.data[i]);
	printf("%d\n", ss.num);
}
void print2(struct S* ps)
{
	int i = 0;
	for (i = 0;i < 3;i++)
		printf("%d ", ps->data[i]);
	printf("%d\n", ps->num);
}
int main()
{
	struct S s = { {1,2,3},100 };
	print1(s);//传值调用
	print2(&s);//传址调用
	return 0;
}

结构体传参2.png

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

首选print2函数,原因:

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

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