C语言结构体详解+进阶内存等问题

结构体是很多初学C语言同学搞不太懂的问题,今天我们就来详细解读一下,如果我说的有什么不对的地方,或者太模糊,你可以指出来,相互交流学习!

第一部分:结构体总体的定义

要了解,要彻底搞懂一个东西,那么我们就需要从定义下手,这是非常重要的一点,在我们学习C语言的路途上,这个思想应该一直贯穿我们,我之前也是忽略 了许多问题,最后发现是没有搞懂很多定义,在之后的路上吃了很多亏,我希望,我们从一开始就搞懂定义,再来敲代码,不会耽搁很多时间,而且事半功倍!

一.结构体的定义

1.关于结构体

结构体和我们平时所接触的类型有些不同,但也有相同之处
相同点:
结构体也是一个类型,这是和其他类型是一样的,都是用来记录或储存变量的
不同点:
结构体是一个用户自行定义的类型:
C语言允许用户自己建立由不同类型数据组成的组合型的数据结构,它称为结构体,在一些其他的高级语言称为 记录
就如int,char,double 那么结构体也就有一个关键字了 :struct
不过它的创建和其他的变量有所不同,如下:

struct Student{
			int num;              //成员学号
			char name[20];        //成员姓名
			double score;         //成员成绩
		};

在上面的程序段里我们定义了一个结构体类型,就和int i,char b 是一样的,只是他里面有不同的类型成员
而 int ,char 里面所定义的类型是一致的
在上面的程序里我们描述了一个学生的信息: 里面有学号 是一个 int 类型,一个名字 是一个字符串类型
所以声明一个结构体变量的一般形式为:
struct 结构体名 {
成员列表
};
几点需要注意的地方:
1.上面的 Student 就是我们所定义的结构体名.
2.结构体类型名是由结构体关键字 struct 和结构体名组合而成,就如上面的 struct Student
3.结构体名字由我们用户来指定,称为结构体标记,用来区别其他结构体名而已

2.关于结构体成员:

花括号内是结构体所包含的子项,称为结构体成员,比如上面 num,name 都是结构体成员

对于每个成员都应该进行声明:

类型名 成员名

这时候的定义就和我们普通类型的定义是一样的,成员命名规则与变量名是相同的

3.关于结构体类型变量,我们有三种方法来定义

1.先声明结构体类型,再定义该类型的变量,我们以上面的程序为例:

		struct Student{
			int num;              //成员学号
			char name[20];        //成员姓名
			double score;         //成员成绩
		};
		struct Student student1,student2

这里的 struct Student 就是结构体类型名 就和 int char 这些类型是一样的
只不过这是我们自己定义的类型而已
student1,student2 就是结构体变量名 就如 int i,char a 一样,i和a都是变量名.
在定义了结构体变量之后,系统会专门分配内存单元,比如上面的程序在 vs2010 中占用了32个字节
也牵扯了字节对齐问题,这个我们随后会展开来说.

2.在声明类型的同时定义变量

struct Student {
		int num;
		char name[20];
		double  score;
		}student1,student2;

它的作用与第一种方法相同,但是在定义struct Student 类型的同时定义了两个struct Struct类型的变量
student1,student2.这种定义方法的一般形式为:
struct 结构体名{
成员列表
}变量名列表;

3.不指定类型名而直接定义结构体变量

struct {
		int num;
		char name[20];
		double  score;
		}student1,student2;

指定了一个无名的结构体类型,他没有名字,显然不能再以此结构体类型去定义其他变量,这种方法用的不多

需要注意的两点!

1.结构体类型与结构体变量是不同的概念,不能混为一谈,只能对变量进行操作
就如上面的 Student 是一个类型 而student1,student2 是变量名,我们在初学的时候容易发蒙在这块.
2.结构体变量名可以和程序中的变量名相同,二者是不同的对象

二.结构体的初始化

前面我们已经说完了结构体的定义,现在来看看怎么初始化结构体

首先我们来定义一个结构体类型

struct Student{
		int num;              //成员学号
		char name[20];        //成员姓名
		double  score;        //成员成绩
	}student2={1001,"zhangsan",78};
	printf("学号:%d\n姓名:%s\n成绩:%f\n",student.num,student.name,student.score);

如上面的程序是最简单的初始化,我们将常量依次赋值给student2
上面的程序中我们是在定义结构体变量时候,顺便对成员进行了初始化,如果我们想手动输入
也是可以的

结构体的初始化是和普通变量没有多大差的
需要注意的是
1.同类的结构体变量之间也可以相互赋值
比如:

student1 = student2;
  1. "."是成员运算符,他在所有运算符中优先级最高,可以把 student1.num 当做一个整体来对待
    3.普通结构体变量的成员可以像普通变量一样进行各种运算
    比如:
student1.num++;

因为"."运算符最高级所以先和student1结合在和num结合,而不是num++,这样student1.num就是一个成员变量

三.结构体内存对齐

这是面试时候可能会考到的知识点 如何计算?

那么定义我们就必须掌握!

我们如何计算定义的结构体的内存,那么我们必须了解的是结构体的对齐规则
1.第一个成员在结构体变量偏移量为 0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
3.Linux中的默认值为4 3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
结构体的整体大小就是 所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

为什么存在内存对齐?

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

总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。

举例

struct Student{
				int num;              
				char name;        
				double  score;       
			};

计算上面的结构体 第一个成员int 为4个字节 (这里我们用1 来表示他所占的位置) 1111
第二个成员为 char 1个字节
根据第二条规则,对齐到char的整数倍(同上,用2表示char 成员在结构体的位置) 11112
第三个成员是 double 为8个字节
根据第二条规则 那么他在内存的排序为 11112xx33333333
那么总的字节数,根据第三条规则,总字节数为 16
可能不太明白有一个图就轻松多了
在这里插入图片描述

几个练习

struct S1{
				char c1;              
				int  i;        
				char c2;       
			};//12

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

			struct S3{
				double d;               
				char c;        
				int  i;       
			};
			//16
			
			struct S4{
				char c1;             
				struct S3 s3;        
				double b;       
			};//32

未完待续…

猜你喜欢

转载自blog.csdn.net/weixin_44077227/article/details/89353835