C语言:自定义类型:结构体,枚举,联合(1)

C语言:自定义类型:结构体,枚举,联合(1)_第1张图片

目录

结构体

1.1 结构的基础知识

1.2 结构体的声明

1.3 特殊的声明

1.4 结构的自引用

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

1.6 结构体内存对齐

1.7 修改默认对齐数offsetof ——宏

1.8 结构体传参


结构体

1.1 结构的基础知识

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

1.2 结构体的声明

//结构体的声明
struct Stu	//结构体关键字struct , tag结构体标签名
{
	char name[20];//成员列表
	int age;
	double score;
};//s1,s2,s3;//结构体变量列表

struct Stu s1;//全局结构体变量

struct Book
{
	char name[20];
	float price;
	char id[20];
};

int main()
{
	struct Stu s2,s3;//局部结构体变量
	struct Book b1;
	return 0;
}

1.3 特殊的声明

//匿名结构体类型__必须加上成员列表,且只能使用一次
struct
{
	char name[20];
	float price;
	char id[20];
}ss;
//匿名结构体类型
struct
{
 int a;
 char b;
 float c;
}x;
struct
{
 int a;
 char b;
 float c;
}a[20], *p;

//在上面代码的基础上,下面的代码合法吗?

p = &x;

匿名结构体的成员如果一样,但是在编译器看来也是不同类型的结构体

1.4 结构的自引用

在结构中包含一个类型为该结构本身的成员是否可以呢?

//定义一个链表的结构体
//typedef就是类型重命名
typedef struct Node
{
	int data;//数据域
	//struct Node n;//err 这里会无限递归下去,无法知道n有多大
	struct Node* next;//指针域
}Node;

int main()
{
	struct Node n;
	Node n2;
	return 0;
}
typedef struct
{
	int data;
	node* next;//err__匿名结构体重命名为node,结构体有了才重命名
	//而里面还没有node
}node;

int main()
{
	//struct node n;
	node n2;
	return 0;
}

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

//结构体变量的定义及初始化
struct Book
{
	char name[20];
	float price;
	char id[12];
}s = {"C语言",55.5f,"CYN001"};
struct Node
{
	struct Book b;
	struct Node* next;
};
int main()
{
	struct Book s2 = { "结构体",66.6f,"JGT001" };
	struct Node n = { {"java",77.7f,"java001"},NULL };

	return 0;
}

1.6 结构体内存对齐

//结构体内存对齐
//vs编译器默认的一个对齐数为8
//对齐数的计算 = 编译器默认的一个对齐数 与 该成员所占空间大小的较小值
struct S1
{
	char c1;//1	8 ——对齐数为1	第一个成员在结构体变量偏移为0的地址处
	int i;//4 8	——对齐数为4		其他成员要对齐到对齐数的整数倍地址处
	char c2;//1 8 ——对齐数为1
	//结构体的总大小 = 最大对齐数(每个成员都有对齐数)的整数倍
};
struct S2
{
	char c1;//1 ——对齐数为1
	char c2;//1 ——对齐数为1
	int i;//4 ——对齐数为4
	//结构体总大小为 8 
};
int main()
{
	struct S1 s;
	struct S2 ss;
	printf("%d\n", sizeof(s));//12个字节
	printf("%d\n", sizeof(ss));//8个字节

	return 0;
}

考点

如何计算?

首先得掌握结构体的对齐规则:

  • 1. 第一个成员在与结构体变量偏移量为0的地址处。
  • 2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值 VS中默认的值为8,Linux环境下没有默认对齐数,没有默认对齐数,成员变量自身大小就是它的对齐数
  • 3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  • 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
//练习
struct S3
{
	double d;//8
	char c;//1 ——对齐数1
	int i;//4 ——对齐数为4 
};
int main()
{
	printf("%d\n", sizeof(struct S3));//16
	return 0;
}
//嵌套结构体大小计算
struct S1
{
	char c1;//1	8 ——对齐数为1	第一个成员在结构体变量偏移为0的地址处  0
	int i;//4 8	——对齐数为4		其他成员要对齐到对齐数的整数倍地址处   4
	char c2;//1 8 ——对齐数为1										8
	//结构体的总大小 = 最大对齐数(每个成员都有对齐数)的整数倍
};
struct S2
{
	char c1;//1 ——对齐数为1		0
	char c2;//1 ——对齐数为1		1	
	int i;//4 ——对齐数为4		4
	//结构体总大小为 8 
};
struct S3
{
	double d;//8
	char c;//1 ——对齐数1
	int i;//4 ——对齐数为4 
};
struct S4
{
	char c1;//1
	struct S3 s3;//16 自身最大对齐数为8
	double d;//8

};
#include
int main()
{
	//如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
	//结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
	printf("%d\n", sizeof(struct S4));
	return 0;
}

为什么存在内存对齐?

大部分的参考资料都是如是说的:

  • 1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
  • 2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总体来说:

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

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

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

//例如:
struct S1
{
 char c1;
 int i;
 char c2;
};
struct S2
{
 char c1;
 char c2;
 int i;
};

1.7 修改默认对齐数offsetof ——宏

size_t offsetof( structName, memberName );

头文件

struct S1
{
	char c1;//1	8 ——对齐数为1	第一个成员在结构体变量偏移为0的地址处  0
	int i;//4 8	——对齐数为4		其他成员要对齐到对齐数的整数倍地址处   4
	char c2;//1 8 ——对齐数为1										8
	//结构体的总大小 = 最大对齐数(每个成员都有对齐数)的整数倍
};
struct S2
{
	char c1;//1 ——对齐数为1		0
	char c2;//1 ——对齐数为1		1	
	int i;//4 ——对齐数为4		4
	//结构体总大小为 8 
};
struct S3
{
	double d;//8
	char c;//1 ——对齐数1
	int i;//4 ——对齐数为4 
};
//offsetof——宏
//计算结构体成员相对于起始位置的偏移量
//头文件
#include
int main()
{
	printf("%u\n", offsetof(struct S3, d));//0
	printf("%u\n", offsetof(struct S3, c));//8
	printf("%u\n", offsetof(struct S3, i));//12
	struct S1 s;
	printf("%u\n", offsetof(struct S1, c1));//0
	printf("%u\n", offsetof(struct S1, i));//4
	printf("%u\n", offsetof(struct S1, c2));//8
	struct S2 ss;
	printf("%u\n", offsetof(struct S2, c1));//0
	printf("%u\n", offsetof(struct S2, c2));//1
	printf("%u\n", offsetof(struct S2, i));//4

	printf("%d\n", sizeof(s));//12个字节
	printf("%d\n", sizeof(ss));//8个字节
	printf("%d\n", sizeof(struct S3));//16
	return 0;
}
//修改默认对齐数
//#pragma pack(4)修改默认对齐数为4

struct S1
{
	char c;//1   对齐数1  
	double d;//8 对齐数8     计算的大小为16
};
#pragma pack(4)
struct S2
{
	char c;//1   对齐数1  
	double d;//8 对齐数4      计算的大小为12
};
#pragma pack()
int main()
{
	struct S1 s;
	printf("%u\n", sizeof(struct S1));//16
	printf("%u\n", sizeof(struct S2));//12

	return 0;
}

结论:

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

写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明

考察: offsetof 宏的实现

1.8 结构体传参

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); //传地址,仅仅传得是一个地址的大小4/8个字节
	return 0;
}

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

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

结论:

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

你可能感兴趣的:(C语言,c语言,c++,数据结构)