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

本文详细讲解了结构体的定义、声明、特殊声明、变量定义与初始化、嵌套结构体、内存对齐规则、结构体大小计算、内存对齐原因、修改默认对齐数以及结构体传参的优化。
摘要由CSDN通过智能技术生成

🐵本篇文章将对结构体相关知识进行讲解

1.结构体🖥️

1.1结构体定义

结构体(struct)是用户自定义的数据类型,用于组合一个或多个不同类型的数据成员

1.2结构体的声明

这里直接以代码为例

1.3特殊的声明

不完全声明或者说匿名声明

在这种情况下,该结构体类型只能使用一次,在创建该结构体类型变量时只能如上图所示创建,以下几种情况均错误:

//1
struct
{
	int age;
	float score;
} a;

int main()
{
	struct b;
	printf("%d", b.age); //报错,因为struct类型已经在上面创建a变量时使用过一次
	return 0;
}

//2
struct
{
	int age;
	float score;
};
int main()
{
	struct b;
	printf("%d", b.age);//由于是匿名结构体,编译器无法识别struct类型
	return 0;
}

那如果这样写代码:

struct
{
	int age;
	float score;
} b;

struct
{
	int age;
	float score;
}* p;

int main()
{
	p = &b;
	return 0;
}

该代码定义了两个匿名结构体类型的变量,且两个结构体的内部成员相同,现将结构体变量b的地址赋给结构体指针p发现,编译器会报警告

也就是说编译器会将这两个结构体类型视为不同的类型,所以不建议这样写代码

在一般情况下不会使用匿名结构体,如果该结构体只使用一次可以用匿名结构体

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

struct Student
{
	char name;
	int age;
}s1;  //在声明的同时定义变量,此时为全局变量

struct Student s2; //全局变量
int main()
{
	struct Student s3 = { "sans",3}; //局部变量
	return 0;
}

嵌套结构体的初始化和访问

struct Point
{
	int x;
	int y;
};

struct Node
{
	int data;
	struct Point p;
	struct Node* next;
};

int main()
{
	struct Node s3 = { 23, {2,3}, NULL };
	printf("%d %d %d", s3.data, s3.p.x, s3.p.y);//先访问到结构体Point在访问其内部成员
	return 0;
}

1.5结构体的内存对齐

1.5.1结构体大小的计算

接下来讲解如何计算结构体的大小,对于计算结构体的大小并非只是将其内部成员的大小加起来,而是通过结构体对齐规则来计算的:

  • 1. 第一个变量在与结构体变量偏移量为0的地址(初始地址)处

这里介绍一个宏:offsetof:用来计算结构体成员相较于起始地址偏移量的

struct S1
{
	char c1;
	int i;
	char c2;
};
int main()
{
    printf("%d", offsetof(struct S1, c1));//结果为0
    return 0;
}
  • 2. 其他成员变量要对齐到对齐数的整数倍的地址处
    对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
    vs的对齐数默认为8
  • 3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
  • 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己内部成员的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
     

下面通过例题进行讲解:

struct S1
{
	char c1;
	int i;
	char c2;
};
printf("%d\n", sizeof(struct S1));

首先c1是第一个成员,要将它放在结构体变量偏移量为0的地址处,下来i为整形,i的大小为4个字节,vs的默认对齐数是8,所以i的对齐数是8和4的较小值也就是4,要将i对齐到4的整数倍的地址处,就是下面偏移量为4的地址处,再下面是c2,c2的对齐数是1,要将c2对齐到1的整数倍的地址处,就是下面偏移量为8的地址处,此时整个结构体的大小为9个字节,规定结构体的总大小是最大对齐数的整数倍,该结构体的最大对齐数是4,所以应该是12个字节                         


struct S2
{
	char c1;
	char c2;
	int i;
};
printf("%d\n", sizeof(struct S2));

c1是第一个成员,将它放在结构体变量偏移量为0的地址处,c2的对齐数是1,将它对齐到1的整数倍的地址处,i的对齐数是4,将他对齐到4的整数倍的地址处,此时整个结构体的大小是8个字节,正好也是成员最大对齐数4的整数倍,所以这个结构体的大小就是8给字节


struct S3
{
	double d;
	char c;
	int i;
};
printf("%d\n", sizeof(struct S3));

d是第一个成员,将它放在结构体变量偏移量为0的地址处,c的对齐数是1,将它对齐到1的整数倍的地址处,i的对齐数为4,将它放在4的整数倍的地址处此时整个结构体的大小为16个字节,同时也是成员最大对齐数8的整数倍,所以结构体的大小就是16个字节


struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
printf("%d\n", sizeof(struct S4));

c1是第一个成员,要放在结构体变量偏移量为0的地址处,下来是一个嵌套的结构体,规则约定嵌套结构体要对齐到自己内部成员最大对齐数的整数倍处,其内部成员的最大对齐数是8,所以要对齐到8的整数倍的地址处,d的对齐数是8,要对齐到4的整数倍的地址处,此时结构体的大小为32个字节,正好是最大对齐数16的整数倍,所以结构体的大小就是32个字节

1.5.2结构体内存对齐的原因

1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据。

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

struct S1
{
	char c;
	int i;
};

上述代码中在没有对齐的情况下:

在对齐的情况下:

因此结构体的内存对齐可以理解为用空间换取时间的做法

但如果既要满足对齐又想节省时间,可以尽量将小的变量放在一起


1.5.3修改默认对齐数

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

#include<stdio.h>

#pragma pack(1) //将默认对齐数修改为1
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack() //修改后记得取消默认对齐数

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

int main()
{
	printf("%zd\n", sizeof(struct S1)); //6
	printf("%zd\n", sizeof(struct S2)); //12

	return 0;
}

1.6结构体传参

#include<stdio.h>

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

void print(struct S t)
{
	printf("%d %d %d %d", t.data[0], t.data[1], t.data[2], t.num);
}

int main()
{
	struct S s = { {1,2,3}, 10 };
	print(s); //传值调用

	return 0;
}

在函数调用时,形参是实参的一份临时拷贝,当我们传过去的结构体过于大时,形参也需要申请同样大的空间,除此之外还需要将结构体的数据一个一个传过去,既浪费时间也浪费空间,所以对结构体传参时通常会采用传址调用

#include<stdio.h>

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

void print(struct S* t)
{
	printf("%d %d %d %d", t->data[0], t->data[1], t->data[2], t->num);
}

int main()
{
	struct S s = { {1,2,3}, 10 };
	print(&s); //传址调用

	return 0;
}

🙉本篇对结构体的讲解结束,下一篇会对位段、枚举以及联合等相关知识进行讲解

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值