结构体(C保姆级讲解)

前言:

为什么会有结构体,结构体可以用来面熟一个复杂对象,我们知道C语言中有哪些数据类型,有整型,有浮点型,有字符型,但是在生活中,我们需要描述一些比较复杂的东西,比如说一本书,有书名,有价格,有出版社等等,所以我们可以用结构体去描述。

结构体的声明

一般结构体声明时,形式如图所示:

cpp 复制代码
struct book
{
	int price;
	char name[20];
}p1,p2;

解析:

struct为关键字,在声明结构体的时候,必须有关键字struct。

book表示类型,就类似于int、char、float,只不过book是我们自定义的类型而已。

{}里面的是描述book这个类型里面的成员,称为成员变量。

{}外边的p1,p2是两个定义的变量名,这里可写也可以不用写,在主函数中也可以定义。

类似于:

cpp 复制代码
struct book
{
	int price;
	char name[20];
};
int main()
{
	struct book p1;
	struct book p2;
	return 0;
}

放在主函数中定义!

特殊的声明:

在声明结构的时候,可以不完全的声明;

cpp 复制代码
//匿名结构体类型
struct  //省略了结构体类型
{
	int a;
	char b;
	float c;
}x;

这样声明结构体是可以的

但是,我如果一个源文件中有两个匿名结构体可不可以呢?

答案是不行的,不然初始化的时候不知带是对哪一个结构体进行初始化的。

类似于这样是不可取的:

cpp 复制代码
//匿名结构体类型
struct
{
 int a;
 char b;
 float c;
}x;
struct
{
 int a;
 char b;
 float c;
}a[20], *p;

这里声明了两个匿名结构体。

注:这里两个匿名结构体是相互独立的,谁有谁的地址,当我定义*p后,我用*p = &x是不行滴!!

初始化结构体

声明好结构体后,接下来就是初始化结构体

cpp 复制代码
struct book
{
	int price;
	char name[20];
};
int main()
{
	struct book a1 = { 3,"lisi" };//一般初始化
    struct book a2[2] = {{4,"zhangsan"},{"10","wangwu"}};//结构体数组初始化
    
	return 0;
}

结构体也可以嵌套初始化:

cpp 复制代码
struct Stu        //类型声明
{
	char name[15];//名字
	int age;      //年龄
};
struct Node
{
	int data;
	struct Stu p;//嵌套声明
	struct Node* next;
}n1 = { 10, {"lisi",5}, &n1};//嵌套初始化

结构体成员访问:

结构体成员的访问有两种方式,第一种:

cpp 复制代码
#include<stdio.h>
struct book
{
	int price;
	char name[20];
};
int main()
{
	struct book p1 = {10,"lisi"};
	printf("%d\n", p1.price);
	printf("%s\n", p1.name);
	return 0;
}

访问结构体中的成员变量用**.**去访问。

第二种用指针访问:

cpp 复制代码
#include<stdio.h>
struct book
{
	int price;
	char name[20];
};
int main()
{
	struct book p1 = {10,"lisi"};
	struct book *p = &p1;
	printf("%d\n", p->price);
	printf("%s\n", p->name);
	return 0;
}

访问结构体中的成员变量用**->**去访问。

结构体传参:

代码如下:

cpp 复制代码
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;
}

有两种方式,在这里通过代码去分析。

可以传地址进去,用指针接收。

也可以传结构体进去,用结构体接收。

结构体大小(内存对齐):

由于结构体是由问我们自己定义的,那么结构体大小该如如何计算?

计算如下结构体大小:

cpp 复制代码
struct arr
{
	int a;
	char b;
	char c;
};
int main()
{
	printf("%d\n", sizeof(struct arr));
	return 0;
}

如果直接想,答案应该是4+1+1 = 6;

但是:

答案是8!!

再来看一组:

cpp 复制代码
struct arr
{
	char b;
	int a;
	char c;
};
int main()
{
	printf("%d\n", sizeof(struct arr));
	return 0;
}

和第一组相比,我交换了一下int a和char b的位置,答案是多少?

是6?还是8?

答案是:

竟然是12!

为什么呢?

按照结构体的对齐规则:

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

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

  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8

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

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

了解了对齐规则,我们再来看第一个案例,答案为什么是8?

cpp 复制代码
struct arr
{
	int a;
	char b;
	char c;
};
int main()
{
	printf("%d\n", sizeof(struct arr));
	return 0;
}

通过画图讲解:

所以答案是8,可以自己画图分析。

继续分析第二个,交换顺序后大小为什么会变?

可以自己练习一个:

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

答案应该是多少呢?

答案是16!!

嵌套结构体大小:

cpp 复制代码
struct arr
{
	char b;
	char c;
    int a;
};
struct S3
{
	double d;
	char c;
	int i;
	struct arr a;
};
int main()
{
	printf("%d\n", sizeof(struct S3));
	return 0;
}

它的大小是多少呢?

根据内存对齐规则第4条:

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

画图解释:

最后的大小是最大对齐数的整数倍,最大对齐数是8,所以总大小是24。

为什么会存在内存对齐

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

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

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

修改默认对齐数

在vs中,默认对齐数是8,当然我们也可以进行修改,用到#pragma预处理指令。

cpp 复制代码
#pragma pack(1)//设置默认对齐数是1
struct arr
{
	int a;
	char b;
	char c;
};
#pragma pack();//取消默认对齐数

可以设置默认对齐数为1,然后计算结构体大小。

cpp 复制代码
#pragma pack(1)
struct arr
{
	char a;
	char b;
	int c;
};
int main()
{
	printf("%d\n", sizeof(struct arr));
}

答案是多少呢?

答案应该是6;

位段:

2.1 什么是位段

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

1.位段的成员必须是 int、unsigned int 或signed int 和char(整型家族)。

2.位段的成员名后边有一个冒号和一个数字,单位是比特位。

代码如下:

cpp 复制代码
struct arr
{
	char a : 3;
	char b : 2;
	char c : 3;
};

此时,arr就是一个位段类型。

那么位段类型的大小是多少呢?

此时只占用了1个字节,答案是1;

如果是这样呢

cpp 复制代码
struct arr
{
	char a : 3;
	char b : 6;
	char c : 4;
};
int main()
{
	printf("%d\n", sizeof(struct arr));
}

错误示例:

答案应该是3,不是2!!

枚举

枚举顾名思义就是一一列举。

把可能的取值一一列举。

枚举的定义:

关键字是enum!

cpp 复制代码
enum Day//星期
{
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};
enum Sex//性别
{
 MALE,
 FEMALE,
 SECRET
};
enum Color//颜色
{
 RED,
 GREEN,
 BLUE
};

以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。

我们用%d打印一下,看看每个枚举类型中的变量的值。

cpp 复制代码
enum SEX
{
	MALE,
	FEMALE,
	SECRET
};
int main()
{
	printf("%d\n", MALE);
	printf("%d\n", FEMALE);
	printf("%d\n", SECRET);
}

那么这个对应的值可不可以修改呢?

答案是不可以的,因为是枚举常量,常量的值是不可以修改的。

但是在枚举{}内是可以修改的:

cpp 复制代码
enum SEX
{
	MALE = 10,
	FEMALE = 9,
	SECRET = 2
};
int main()
{
	printf("%d\n", MALE);
	printf("%d\n", FEMALE);
	printf("%d\n", SECRET);
	
}

枚举的使用:

cpp 复制代码
enum Color//颜色
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};
int main()
{
	enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
	
}

只能拿枚举常量给枚举常量赋值,才不会出现差异!

枚举的优点:

我们可以使用 #define 定义常量,为什么非要使用枚举?

枚举的优点:

1. 增加代码的可读性和可维护性

2. 和#define定义的标识符比较枚举有类型检查,更加严谨。

3. 防止了命名污染(封装)

4. 便于调试

5. 使用方便,一次可以定义多个常量

联合(共用体)

联合体用关键字:union

联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。 比如:

cpp 复制代码
//联合体的声明
union arr
{
	int a;
	char b;
};
int main()
{
	union arr a1;//联合体定义
	return 0;
}

联合体共用一块空间,可以看看联合体内这两个成员的地址是否相同?

cpp 复制代码
union arr
{
	int a;
	char b;
};
int main()
{
	union arr a1;//联合体定义
	printf("%p\n", &a1.a);
	printf("%p\n", &a1.b);
	return 0;
}

地址是一样的。

联合体的特点:

联合的成员是共用同一块内存空间的,

这样一个联合变量的大小,至少是最大成员的大小(因为联 合至少得有能力保存最大的那个成员)。

联合体大小的计算

联合的大小至少是最大成员的大小。

当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

例如:

cpp 复制代码
union Un1
{
	char c[5];
	int i;
};
union Un2
{
	short c[7];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
}

这两个的打印结果是多少呢?

联合体的应用

利用联合体判断大小端。

之前判断大小端的方法:

cpp 复制代码
int Print(int *a)
{
	return *(char*)a;
}
int main()
{
	int a = 1;
	int b = Print(&a);
	if (b == 1)
	{
		printf("小端存储");
	}
	else
		printf("大端存储\n");
	return 0;
}

利用联合体判断:

cpp 复制代码
union arr
{
	int a;
	char b;
};
int main()
{
	union arr a1 ;
	a1.a = 1;
	printf("%d\n", a1.b);
	if (a1.b == 1)
			{
				printf("小端存储");
			}
			else
				printf("大端存储\n");
	return 0;
}

将1存入int型变量中,之后用char类型变量将其取出。

相关推荐
小_太_阳5 分钟前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
向宇it6 分钟前
【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理
开发语言·unity·c#·编辑器·游戏引擎
智慧老师14 分钟前
Spring基础分析13-Spring Security框架
java·后端·spring
lxyzcm16 分钟前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
蜀黍@猿34 分钟前
C/C++基础错题归纳
c++
古希腊掌管学习的神36 分钟前
[搜广推]王树森推荐系统笔记——曝光过滤 & Bloom Filter
算法·推荐算法
qystca38 分钟前
洛谷 P1706 全排列问题 C语言
算法
古希腊掌管学习的神42 分钟前
[LeetCode-Python版]相向双指针——611. 有效三角形的个数
开发语言·python·leetcode
赵钰老师43 分钟前
【R语言遥感技术】“R+遥感”的水环境综合评价方法
开发语言·数据分析·r语言
浊酒南街43 分钟前
决策树(理论知识1)
算法·决策树·机器学习