结构体是很多初学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;
- "."是成员运算符,他在所有运算符中优先级最高,可以把 student1.num 当做一个整体来对待
3.普通结构体变量的成员可以像普通变量一样进行各种运算
比如:
student1.num++;
因为"."运算符最高级所以先和student1结合在和num结合,而不是num++,这样student1.num就是一个成员变量
三.结构体内存对齐
这是面试时候可能会考到的知识点 如何计算?
那么定义我们就必须掌握!
我们如何计算定义的结构体的内存,那么我们必须了解的是结构体的对齐规则
1.第一个成员在结构体变量偏移量为 0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
3.Linux中的默认值为4 3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
结构体的整体大小就是 所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
为什么存在内存对齐?
- 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址 处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理 器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
举例
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