一、结构体的基础知识
1、结构体的概念
(1)定义:结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
2、结构体的声明
struct tag
{
member - list;
}variable - list;
(1)struct:结构体关键字,是不可以省略的
(2)tag:标签,名字可以自己定
(3)member - list:成员列表
(4)variable - list:变量列表
(5)花括号括起来的是结构体成员的列表,结束时在花括号后输入分号“;”表示结构体的设计定义结束。
注:结构体的声明只是描述了一类对象的元素,并没有实际创建一个对象。
3、特殊的结构体声明
(1)匿名结构体类型
struct MyStruct
{
int a;
char b;
float c;
}x;
匿名结构体类型需要在创建后就建立变量,否则后面无法创建变量。
(2)结构体的重命名
每次定义结构体变量时都需要打struct,是不是很麻烦。C语言为我们提供了typedef语句,可以为结构体重命名,并不是定义新类型。
typedef struct
{
int data;
char c;
}Node;
Node x;
此时结构体就被重命名为Node了,之后创建变量直接写Node即可,不用写struct了。
(3)结构体的自引用
结构体的自引用简单来说就是结构体成员中包含该结构体
首先在结构体内包含一个类型为该结构本身的成员是不可以的
struct Node
{
int data;
struct Node n;//此处存放的下一个节点
//类型内包含自己的成员是不行的,是死递归
};
正确的做法应该是:
struct Node
{
int data;
struct Node* next;//此处存放的下一个节点的地址
};
4、定义结构体变量
(1)定义全局的结构体变量
struct Stu
{
char name[20];//名字
char tele[12];//电话
char sex[10];//性别
int age;//年龄
}s4;
也可以是:
struct Stu
{
char name[20];//名字
char tele[12];//电话
char sex[10];//性别
int age;//年龄
};
struct Stu s1;
(2)定义局部结构体变量
struct Stu
{
char name[20];//名字
char tele[12];//电话
char sex[10];//性别
int age;//年龄
};
int main()
{
struct Stu s1;
return 0;
}
5、结构体变量的引用
(1)不能将一个结构体变量作为一个整体进行输入和输出。
只能对结构体变量中的各个成员分别进行输入输出。引用结构体变量中①的成员的方式为:
结构体名.成员名
①. 是成员(分量)运算符,它在所有的运算符中优先级最高。
(2)如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低一级的成员。只能对最低的成员进行赋值或存取以及运算。
struct date // 声明一个结构体类型
{
int month;
int day;
int year;
}
struct student
{
int num;
char name[20];
char sex;
int age;
struct date birthday;
char addr[30];
}student1, student2;
例如:结构体变量 student1 可以这样访问各成员:
student1.num
student1.birthday.month
(3)对结构体变量的成员可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)。
student2.score = student1.score;
sum = student1.score + student2.score;
student1.age ++;
++ student1.age;
由于 . 运算符的优先级最高,因此 student1.age ++ 是对 student1.age 进行自加运算。而不是先对 age 进行自加运算。
(4)可以引用结构体变量成员的地址。也可以引用结构体变量的地址。如:
scanf("%d", &student1.num);// 输入 student1.num 的值
printf("%o", &student1);// 输出 student1 的首地址
但不能用以下语句整体读入结构体变量如:
scanf("%d,%s,%c,%d,%f,%s", &student1);
结构体变量的地址主要用于作函数参数,传递结构体的地址。
6、结构体的初始化
不能将一个结构体变量作为一个整体进行输入和输出。只能对结构体变量中的各个成员分别进行输入输出。
(1)一般情况下的初始化
struct S
{
char c;
int a;
double d;
char arr[20];
};
int main()
{
struct S s = { 'c',100,2,14,"hello" };
printf("%c %d %lf %s/n", s.c, s.a, s.d, s.arr);
return 0;
}
(2)结构体内部有结构体的初始化
struct S
{
char c;
struct T st;
int a;
double d;
char arr[20];
};
int main()
{
struct S s = { 'c',{55.6,30},100,2,14,"hello" };
return 0;
}
二、结构体的内存对齐
1、内存对齐的原因
(1)平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
(2)性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的2内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
2、内存对齐的规则
(1)第一个成员在与结构体变量偏移量为0的地址处存放。
(2)其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数=编译器默认的一个对齐数与该成员大小的较小值
vs中默认的值为8
gcc没有默认对齐数,没有默认对齐数时,成员大小为对齐数
(3)当成员全部存放进去后,结构体总大小为所有成员的最大对齐数(每个成员变量都有一个对齐数)的整数倍。如果不够,则浪费空间对齐。
(4)如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大的对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍。最大对齐数包含嵌套的结构体成员的对齐数。
struct S1
{
char c1;
int a;
char c2;
};
这个结构体的大小为8个字节
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
}s;
S的大小就是32个字节。
解释:c1只占一个字节,从0偏移开始。s3从第8个偏移位置开始存储,存储16个字节。d直接在24个偏移量处。32是所有最大对齐数的整数倍。
3、修改对齐数
#pragma pack(8)//设置默认对齐数
#pragma pack()//取消设置的默认对齐数
#pragma pack(1)//地址可以随便放了
一般设置成2的次方数
4、offsetof()
(1)作用:计算结构体成员的偏移量
(2)是一个宏
(3)头文件
#include<stddef.h>
(4)用法
printf("%d\n", offsetof(struct S1,a));
struct S1是结构体的名字,a为结构体的成员名称
5、在设计结构体时,尽量让占用小的成员集中在一起,节省空间。
三、结构体数组
一个结构体也可以像int类型等,存在结构体数组。结构体数组与以前介绍过的数据值型数组不同之处在于每个数组元素都一个结构体类型的数据,它们分别包括各个成员(分量)项。
1、定义结构体数组
就和定义int类型数组类似
struct student { int num; char name[20]; char sex; int age; float score; char addr[30]; }; struct student stu[3];
这样就定义了一个结构体数组stu,七元素为struct student数据,数组有3个元素。
2、结构体数组的初始化
struct student
{
int mum;
char name[20];
char sex;
int age;
float score;
char addr[30];
}stu[3] = { {10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},
{10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},
{10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"} };
四、结构体传参
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。因此传址效率更高
struct S
{
int a;
char c;
double d;
};
void Init(struct S* tmp)
{
tmp->a = 100;
tmp->c = 'w';
tmp->d = 3.14;
}
void Print1(struct S tmp)
{
printf("%d %c %lf\n", tmp.a, tmp.c, tmp.d);
}
int main()
{
struct S s;
Init(&s);//传地址才可以在函数内部改变函数外的数据
Print1(s);
return 0;
}
结构体传参的方式和其他数据类型传参类似
五,结构体指针
结构体数组以及元素也可以用指针变量来指向。
在指针数组中可以用->来访问,如p-> 成员名。
#include <stdio.h>
#include <stdlib.h>
struct student
{
int num;
char name[20];
char sex; int age;
};
struct student stu[3] = { {10101, "Li Lin", 'M', 18},{10102, "Zhang Fun", 'M', 19},{10103, "Wang Min", 'F', 20} };
int main()
{
struct student* p;
printf("No. name sex age\n");
for (p = stu; p < stu + 3; p++)
printf("%5d %-20s %2c %4d\n", p->num, p->name, p->sex, p->age); system("pause");
}
六、位段
1、概念
(1)位段的声明和结构体是类似的,但是有两个不同
①尾端的成员必须是int、unsigned int或者signed int,也可以是整形家族的其他类型,比如char类型。
②位段的成员名后必须有一个冒号和一个数字。
e.g
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
A就是一个位段类型
(2)位段的“位”是二进制位。_a就是2个二进制位,也就是2个比特位,_c就是10个比特位。
(3)位段的出现是为了节省空间,若a只用2个比特位就可以完全表达,那么就可以省下2个比特位的大小。
(4)位段的大小
位段A的大小就是47个比特位,是8个字节
2、位段的内存分配
(1)位段的空间上是按照需要以4个字节(int)或者1个字节(char)开辟的。
例如位段A,第一个是int,先开辟4个字节,32个比特位的大小,等到d的时候发现不够用了,于是开辟4个字节的大小,总大小就是8个字节。
在vs中一个内存是从右向左填入,不够的空间会被浪费
(2)位段设计很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
位段跨平台的问题
①int位段被当成有符号数还是无符号数是不确定的。
②位段中最大位数目不确定。例如16位机器最大是16,32位机器最大是32,写成30会在16位机器上出问题
③位段中的成员在内存中从左向右分配还是从右向左氛围标准尚未定义
④当一个结构包含两个位段,第二个位段成员比较大,无法容纳与第一个位段剩余的位时,是舍弃剩余的位还是利用也是不确定的。
(3)总结:与结构体相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
3、位段的应用
网络传输的数据包的格式。