C语言————自定义类型(结构体)

自定义类型包括结构体、枚举、联合等,本文着重讲解结构体。

我们知道变量类型有整型、字符型、浮点型等,但是在生活中,我们不局限于这几种类型,比如描述:人、书,树等。这些都不能用以前单一的类型来呈现。所以我们就有了自定义类型。首先,我们来看结构体。

1 结构体

1.1 介绍

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

1.2 声明

 例如:

假设定义一个学生类型

//定义结构体类型
struct Stu
{
//成员变量
 char name[20];//名字

 int age;//年龄

 char sex[5];//性别

 char id[20];//学号

}; //分号不能丢

1.3 变量的定义

我们已经介绍了如何创建一个结构体,那么如何使用呢?下面来看:

说,创建结构体变量,可以这样创建(这里我们使用上面创建好的结构体):

int main()
{
	//创建结构体变量
	struct Stu s1;
	struct Stu s2;
	return 0;
}

也可以:

//定义结构体类型
struct Stu
{
	//成员变量
	char name[20];
	int age;
	char sex[5];
	char id[20];

}s3,s4;// 结构体变量

这两种创建方式在于前面的是局部变量,后面的是全局变量。

1.4 特殊的声明

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

比如:

//匿名结构体
struct
{
	int a;
	char c;
}s1;//匿名结构体变量只能在这后面定义。

因为是匿名结构体,省略掉了类型名,所以无法在其余位置定义变量,只能在这后面定义。

我们现在看,这两种代码:

struct
{
 int a;
 char b;
 float c;
}x;

struct
{
 int a;
 char b;
 float c;
}a[20], *p;

我们发现,这两个结构体除了后面的变量名有所区别,其余基本一致。

那么,在上述代码的前提下,这个代码能否实现?

p = &x;

我们在编译后发现,编译器会发出警告。

警告:编译器会把上面的两个声明当成完全不同的两个类型。 所以是非法的。

现在,我们了解了匿名结构体,那我们想,匿名结构体可不可拥有名字呢?

答案当然是可行的。

我们来看:

typedef struct
{
	int a;
	char c;
}S;//注意:这里的S是用typedef重定义的类型名

要注意的是,S和我们前面的s1是不一样的!

1.5 结构体的自引用

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

什么意思呢?

比如:

 将同种结构体类型的变量,用这种链条式的结构串联起来,就叫结构体的自引用。

先看代码:

//代码1
struct Node
{
 int data;
 struct Node next;
};

好,问题来了,这样是否可行?

如果可行的话,那么sizeof(struct Node)是多少呢?

我们发现,无法求出它的大小,我们在结构体成员中定义了一个此结构体的变量,那如果我们要求结构体的大小,在结构体成员中int类型的大小是确定的,但是next变量的大小无法确定,因为我们还不知道结构体的大小。这样一来就自相矛盾了。所以上述代码是不对的。

我们应该这样写代码:

//正确的自引用方式:
//代码2
struct Node
{
	int data;
	struct Node* next;
};

定义一个此结构体类型的指针变量,我们知道指针就是地址,大小是固定的。所以此结构体的大小就固定。

同时,我们就可以这样来完成自引用:

struct Node
{
	int data;
	struct Node* next;
};
int main()
{
	struct Node n1;
	struct Node n2;
	n1.next = &n2;//将n2的地址传个n1的指针中
				  //实现n1调用n2
	return 0;
}

 现在,我们了解了如何自引用,那我们来想一个问题:

之前,我们知道了结构体是可以匿名的,匿名后又可以用typedef来重起一个名字。现在,又会了自引用,两者结合,看这样的代码可不可行?

代码:

typedef struct
{
	int data;
	Node* next;
}Node;

我们来看这段代码,我们知道匿名的结构体是无法在其他位置创建变量的,但现在,我们重命名了一下,使得它拥有名字,然后在成员里创建了一个这样类型的指针变量。那可不可行呢?

答案是不行。

这样创建有一个问题,就是,你的名字是在后面创建好的,而你现在要在成员里先用,那就存在一个先后矛盾的问题。所以,不行。

像这种奇怪的代码有很多,一定要注意思考!

1.6  结构体内存对齐

结构体我们已经基本了解了,现在,我们思考一个问题:计算结构体的大小

我们来看这样两个结构体:

struct s1
{
	char c1;
	int a;
	char c2;
};
struct s2
{
	char c1;
	char c2;
	int a;
};

这两个结构体中的成员变量一模一样,那他们的大小又是多少呢?相等?还是不等?

int main()
{
	printf("s1:%d\n", sizeof(struct s1));
	printf("s2:%d\n", sizeof(struct s2));
	return 0;
}

我们通过代码来计算得到:

 

 我们发现,大小是不一样的。这是为什么呢?

下面,来讲解一下内存对齐的问题:

首先,如何计算?

结构体的对齐规则:

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

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

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

  • VS编译器中默认的值为8

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

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

我们以一开始的两个结构体为例:

 同理,我们来分析  s2:

 那么如果遇到结构体嵌套结构体时,又该如何求大小呢?

这时,就要用到我们的第四条规则:

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

例如:

struct S2
{
	char c1;
	char c2;
	int a;
};
struct S4

{
	char c1;
	struct S2 s2;
	double d;
};
int main()
{
	printf("%d\n", sizeof(struct S4));
	return 0;
}

运行结果:

分析: 

 通过一系列的代码,我们了解了结构体是如何内存对齐的,那么为什么要内存对齐呢?

为什么存在内存对齐?

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

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

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

2. 性能原因:

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

总体来说:

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

1.7  修改默认对齐数

默认对齐数是可以修改的。

通过使用#pragma 这个预处理指令就可以修改。

如:

#pragma pack(1)//设置默认对齐数为1
struct s1
{
	char c1;
	int a;
	char c2;
};
int main()
{
	printf("%d\n", sizeof(struct s1));
	return 0;
}
#pragma pack()//取消设置的默认对齐数,还原为默认

结果:

 结束语

关于结构体,就说到这里,感谢观看,谢谢!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值