variable
adj.易变的,多变的;时好时坏的;可变的,可调节的;
(数)(数字)变量的;(植,动)变异的,变型的;(齿轮)变速的
n.可变性,可变因素;(数学中的)变量,变元;
(计算机)变量(元);(天文)变星的简称;
(东北信风带以北或南半球的东南信风带与西风带之间的)变风区
前言:
在一开始学习C语言时,我们学习的是int,char...(内置类型),之后又学习数组(自定义类型),然后又学习了指针类型,基本的类型我们都学习的差不多了,今天在为大家带来自定义类型中的三大类型:结构体类型、共用体类型和枚举类型。
1.定义
结构体其实就是各种类型数据的结合体。
比如:对于一个学生而言,学生的属性就有他的名字,性别,年级,成绩等等,为了将这些属性都存储在一起,这时就需要新的自定义类型来进行将不同类型的数据进行整合,这就是结构体类型。
2.创建方式
⚀声明方式:
struct str_name
{
number_var;
number_var;
...
};
- struct 创建结构体类型时的关键字
- str_name struct_name结构体类型的名字
- number_var number_varible结构体内的成员变量
注意:
- 结构体类型的成员变量不能被初始化。
- struct str_name这两个合在一起才被称为结构体类型。
- 结构体的声明一定在使用前进行声明不能在后声明。
- 结构体成员变量可以是任何类型,但绝对不可以是成员变量含有自身结构体的结构体。
对于这种结构体是不允许创建的,因为你的结构体成员里面的又有自身的结构体,你这个成员变量的结构体里面又含有结构体,如此下去,结构体里含结构体,那么这个结构体的大小的有多大?答案是:不知道,很大很大很大!!!大到溢出!!!所以这种方式是不允许的!!!
例子:
//以一个学生为例,声明一个结构体
struct Stu
{
char name;//名字
int age;//年龄
int height;//体重
int arr[10];//各科的成绩
}
typedef重命名
//以一个学生为例,声明一个结构体
typedef struct Stu //定义的结构体前加上typedef关键字
{
char name;//名字
int age;//年龄
int height;//体重
int arr[10];//各科的成绩
}Stu;//在定义完后面加上重新命名的名字
struct Stu S1;//使用struct定义的名字创建变量
Stu S2;//使用typedef重命名的名字创建变量
//不在声明时重命名
typedef struct Stu Stu;//typedef关键字+原名+新名字;
⚁变量创建方式:
A.在声明时
//以一个学生为例,声明一个结构体
struct Stu
{
char name;//名字
int age;//年龄
int height;//体重
int arr[10];//各科的成绩
}S1,S2,S3;
//在声明完结构体后,在后面接着创建结构体变量
B.通过结构体类型
//以上面的struct Stu结构体为例
struct Stu S1;//通过结构体类型创建结构体变量
⚂特殊声明:
struct //不声明结构体类型名字
{
char name;//名字
int age;//年龄
int height;//体重
int arr[10];//各科的成绩
}S1;//创建结构体变量
- 这种在声明结构体时不声明结构体类型名,而在声明后接着创建结构体变量的操作就称为结构体的匿名声明。
- 对于这种没有将结构体类型名的结构体,就意味着不能通过结构体类型来创建结构体变量,这种只能使用它后面的结构体变量,可以称为一次性结构体。
⚃自引用:
在结构体声明时,我们讨论过不能在结构体内部创建自身的结构体变量进行使用,因为会使得结构体内存大小溢出,那么有什么方法可以实现能在结构体中自引用自己这个结构体呢?
答案是:自己的结构体指针
3.使用方式
⚀访问成员操作:
A.操作符' . '
结构体变量名.成员变量名
str_name.number_var_name1
S1.age = 21;
通过结构体类型变量访问
B.操作符" -> "
结构体指针->成员变量名
str_piont_name.number_var_name1
struct Stu* p;//结构体指针
p->age = 21;
通过结构体指针变量访问
⚁初始化操作:
按照结构体变量初始化的位置分:
A.在声明时创建变量时
B.通过结构体类型创建时
按照成员变量初始化的顺序分:
A.声明时顺序初始化:
B.指定顺序初始化:
结构体的传值or传址 ?
由于结构体在内存中的存储满足内存对齐,就会存在内存浪费,加之结构体时多种的数据类型的集合,内存空间就会非常大,就不推荐使用传值调用,会拷贝数据压栈(在栈区上开辟空间,会浪费空间,溢出等),所以结构体进行调用时最好时传址调用。
4.存储方式
首先我们来看一下下面这个结构体的内存大小:
Why?为啥不是6个字节,不是这几个类型得字节数相加?难道是2倍关系 ?
其实小编可以告诉大家不是啦,结构体在内存中得存储是一种新的方式:内存对齐
那么下面让我们一起看一下什么是内存对齐,小编先为大家介绍内存对齐的规则,嘻嘻
⚀内存对齐规则:
假设有段内存空间,结构体就存储在这上面,那么在存储前先要对这块空间的内存单元进行编号,而成员变量的存储方式就与这些编号有关,这种方式就是内存对齐。
- 第一个成员变量默认从编号0的位置开始存储。
- 后面的成员变量对齐的编号(对齐数)是:{编译器默认的对齐数,成员变量的大小(字节)}min的整数倍。
- 最后结构体的内存空间大小还需满足是:{编译器默认的对齐数,成员变量的大小(字节)}min的整数倍。
注意:
- 如果是结构体里面含有结构体,那么不是按照这结构体的大小去和编译器默认的对其数比较,而是该结构体内部最大的成员变量的字节数去进行比较。
- VS编译器默认的对其数是8,gcc编译器没有默认的对齐数,对其数就是成员变量本身。
例子:
⚁修改默认对齐数:
对于VS编译器,我们可以通过#pragma操作修改默认对其数:
⚂为什么存在内存对齐?
【补充】
结构体的内存对齐是为了**提高数据访问的效率以及满足特定硬件和操作系统的要求**。具体原因包括:
- 性能优化:CPU访问内存时,对齐的内存可以更快被读取。不对准的内存访问可能会导致多次内存访问才能获取全部数据,降低效率。
- 硬件要求:某些硬件平台要求特定的数据类型必须在特定地址边界上开始,如2的幂次方边界(4字节、8字节等)。
- 减少填充:对齐可以减少或消除数据结构中的填充字节,使得结构更紧凑,节省内存空间。
- 跨平台兼容性:不同的系统可能有不同的对齐要求,对齐可以确保代码在不同平台上的一致性和兼容性。
- 指令集优化:某些CPU指令集在处理对齐数据时更为高效,因此对齐可以提高指令执行的速度。
- 缓存机制:现代CPU的缓存机制通常以特定的对齐边界来存储和检索数据,对齐有助于提高缓存命中率。
- 数据结构对齐规则:结构体的第一个成员与结构体的起始地址对齐,其他成员根据其类型的最大对齐要求进行对齐。
- 默认对齐数:编译器通常有默认的对齐数值,但可以通过编译器指令或特定的编译器选项来修改。
- offsetof宏:在处理结构体时,`offsetof`宏可以用来获取成员在结构体中的偏移量,这对于理解和操作结构体非常有用。
综上所述,结构体的内存对齐是计算机编程中的一个重要概念,它关系到程序的性能、硬件兼容性以及代码的可维护性。了解并正确应用内存对齐规则,对于编写高效且健壮的程序至关重要。
【转载自百度】
5.位段
⚀定义:
- 结构体的位段是一种特殊类型的成员,它允许程序以bit(比特位)为单位来定义结构体成员变量在内存中所占空间的大小。
- 位段的定义是在成员变量后面+冒号+大小(比特位)进行限定大小。
- 成员必须是int,unsigned int或[signed] char,在C99标准中也可以使用其他类型。
- 位段的主要目的是节约存储空间。
int a:5; char b:3;
⚁存储方式:
- 含有位段的结构体进行存储时,按需来开辟空间的,如果是int类型就开4个字节,char类型就开1个字节
- 位段成员是优先存为上个位段开辟空间里,如果下个位段成员存不进上一个位段成员开辟的空间,是舍弃空间还是利用空间是无法确定的。
- 多个位段是可能共用同一块内存空间的。
- 因为只有内存单元有地址,那么对于位段的成员变量的存储是从内存单元的左边开始存还是右边开始存是未知的。
下面以VS编译器为例:
⚂注意事项:
- 位段中最大位的数目不能确定,比如16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
- 通过前面的分析,在位段的存储方式上和最大位数上有很大的不确定性,所以位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
- 位段是按位存储的,所以对于位段成员的结构体,不要用scanf取地址存数值,存的数值可能会被截取,就不是想要的数据。
本章内容结束,下章见,拜拜!!!