结构体是包含一组类型可以不同的成员的集合(类似于python的类结构,struct -->class; 成员:--->属性)
如声明一个Student的结构体,包含name和age以及grade三个成员;
// 只定义和声明一个Student结构体类型
struct Student
{
char name[20];
int age;
double grade;
};
// 只定义和声明一个struct Student结构体类型并进行重命名为Student
typedef struct Student
{
char name[20];
int age;
double grade;
}Student;
// 声明Student结构体类型的同时定义2个结构体变量
struct Student
{
char name[20];
int age;
double grade;
}stu1, stu2;// 此时不能用typedef进行重命名操作。
// 声明匿名结构体类型的同时定义2个结构体变量
struct
{
char name[20];
int age;
double grade;
}stu1, stu2;// 此时不能用typedef进行重命名操作。
struct Student s1,s2;
Student s3,s4; // 这里的Student是typedef重命名后的名字
一个定义好的结构体变量,作为另外一个结构体的成员名字,如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
typedef struct Grade
{
double mathg;
double chineseg;
double historyg;
};
// 只定义和声明一个Student结构体类型并重命名
typedef struct Student
{
char name[20];
int age;
struct Grade grade; // 为一个结构体类型变量
}Student;
int main()
{
printf("ok\n");
return 0;
}
该结构体类型的指针变量作为该结构体的一个成员名,称为结构体的自引用,主要应用于链表数据结构中,例如:
#include <stdio.h>
#include <stdlib.h>
typedef struct Student
{
char name[20];
int age;
struct Student* next; // 注意这里的struct关键字不能省略,否则报语法错误。即先有成员,然后再有结构体类型
}Student;
int main()
{
printf("ok\n");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
typedef struct Grade
{
double mathg;
double chineseg;
}Grade;
typedef struct Student
{
char* name;
int age;
Grade grade;
}Student;
int main()
{
Student stu1; // 定义一个Student结构体类型的变量
Student stu2 = { "Lilei", 18, {88.0,99.0} }; // 定义一个Student结构体类型的变量,并初始化
Student stu3 = { "Hanmei", 19, {77, 88} };
Student* pstu; // 定义一个Student结构体类型的指针变量
pstu = &stu3;
// 结构体变量成员赋值
stu1.name = "Luxi";
stu1.age = 17;
stu1.grade.mathg = 99.0;
stu1.grade.chineseg = 100.0;
// 解引用操作,如果是结构体变量, 用.进行解引用
// 解引用操作,乳沟是结构体指针变量,用->进行解引用
printf("name = %s age = %d math = %.2lf, chinese = %.2lf\n", stu1.name, stu1.age, stu1.grade.mathg, stu1.grade.chineseg);
printf("name = %s age = %d math = %.2lf, chinese = %.2lf\n", stu2.name, stu2.age, stu2.grade.mathg, stu2.grade.chineseg);
printf("name = %s age = %d math = %.2lf, chinese = %.2lf\n", pstu->name, pstu->age, pstu->grade.mathg, pstu->grade.chineseg);
return 0;
}
1. 第一个成员的地址与结构体变量的地址相同,即其地址为结构体变量地址的0偏移量。
2. 其他成员变量要对齐到"对齐数"的整数倍的地址处。
3. 对齐数 = min{编译器默认的对齐数, 该成员所占字节大小}。
VC默认对齐数为:8
Linux默认对齐数为:没有默认大小,一般为变量类型大小
4. 结构体大小为所有成员中的对齐数中的最大的对齐数的的整数倍
5. 对于嵌套结构体,嵌套的结构体对齐到自身的最大对齐数的整数倍,而结构体的整体大小为所有成员中的最大对齐数的整数倍。
1. 有些硬件平台是不能访问任意地址上的数据的。
2. 提高内存的访问效率,在32位系统上,一次读取四个字节,对于char a; int b;只需要访问1次就可以访问到b,否则需要访问两次,才能访问到b。
3. 是用空间来换时间的做法。因为空间是可以增加的,但是时间是宝贵的。
1. 让占字节小的成员写到前面
2. 让占字节大的成员写到后面
3. 类型相同的成员放到一起
#pragma pack(4); // 修改默认对齐数为4
...
#pragma pack(); // 取消设置的默认对齐数,还原为默认值
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
struct Person
{
int age; // 第一个成员的地址与结构体变量的地址之间的偏移量为0,占4个字节,此时偏移量为4
double weight; // 对齐数为min{sizeof(double), 8} = 8,偏移量为对齐数的整数倍,偏移1倍8个字节,与第一个成员之间浪费4个字节,此时偏移量为16
char addr; // 对齐数为min{sizeof(char), 8} = 1, 偏移量为对齐数的整数倍,即其地址紧挨着weight,此时偏移量为17
// 结构体的大小为最大对齐数max{4,8,1} = 8的整数倍,最后一个成员之后偏移17,3 * 8 = 24,后面浪费7个字节为8的整数倍
// 该结构体大小为24
};
struct Student1
{
char name; // 第一个成员的地址与结构体变量的地址之间的偏移量为0,占1个字节,此时偏移量为1
int age; // 对齐数为min{ sizeof(int), 8 } = 4, 偏移量为对齐数的整数倍:4,放在偏移量为4的位置上之后,此时偏移为8
char sex; // 对齐数为1,放在偏移量为8的位置上,此时偏移量为9
// 结构体大小为最大对齐数4的整数倍,故为偏移量为12,后面再浪费3个字节空间
// 该结构体大小为12
};
struct Student2
{
char name; // 同上
char sex; // 对齐数为1,放在偏移量为1的位置,此时偏移量为2
int age; // 对齐数为4, 放在偏移量为4的位置,此时偏移量为8
// 结构体大小为最大对齐数4的倍数,且8 是4的整数倍
// 该结构体大小为8
};
struct Student3
{
char name; // 同上
struct Person p; // 该结构体占24个字节,对齐数为8,放在偏移量为8的位置上,此时偏移量为32
char sex; // 对齐数为1,放在偏移量为32的位置,此时偏移量为33
int age; // 对齐数为4, 放在偏移量为对齐数4的整数倍36的位置,此时偏移量为40
// 结构体大小为最大对齐数max{4 8 1 1 1 4} = 8的整数倍,40是8的整数倍
// 该结构体大小为40
};
int main()
{
printf("Student1 %d\n", sizeof(struct Student1));
printf("Student2 %d\n", sizeof(struct Student2));
printf("Student3 %d\n", sizeof(struct Student3));
printf("Person %d\n", sizeof(struct Person));
}
#include <stdio.h>
#include <stddef.h>
struct Person
{
int age; // 第一个成员的地址与结构体变量的地址之间的偏移量为0,占4个字节,此时偏移量为4
double weight; // 对齐数为min{sizeof(double), 8} = 8,偏移量为对齐数的整数倍,偏移1倍8个字节,与第一个成员之间浪费4个字节,此时偏移量为16
char addr; // 对齐数为min{sizeof(char), 8} = 1, 偏移量为对齐数的整数倍,即其地址紧挨着weight,此时偏移量为17
// 结构体的大小为最大对齐数max{4,8,1} = 8的整数倍,最后一个成员之后偏移17,3 * 8 = 24,后面浪费7个字节为8的整数倍
// 该结构体大小为24
};
struct Student3
{
char name; // 同上
struct Person p; // 该结构体占24个字节,对齐数为8,放在偏移量为8的位置上,此时偏移量为32
char sex; // 对齐数为1,放在偏移量为32的位置,此时偏移量为33
int age; // 对齐数为4, 放在偏移量为对齐数4的整数倍36的位置,此时偏移量为40
// 结构体大小为最大对齐数max{4 8 1 1 1 4} = 8的整数倍,40是8的整数倍
// 该结构体大小为40
};
int main()
{
printf("%d\n", offsetof(struct Student3, name));
printf("%d\n", offsetof(struct Student3, p));
printf("%d\n", offsetof(struct Student3, sex));
printf("%d\n", offsetof(struct Student3, age));
return 0;
}
位段的声明和结构结构体是一样的,关键字也是struct,或者是结构体的一种特例。
1. 其成员中必须是int 、unsigned int、short、char等整型类型的变量
2. 成员的类型最好相同,比如都是int 或者都是short等
3. 其成员名后面必须有一个冒号和一个数字
4. 冒号后面的数字代表的是该成员所占的二进制位(比特位)的个数
位段在内存空间上的开辟方式是:一次开辟4个字节(int类型)或者1个字节(char类型)的。
如果开辟的空间上有足够的位来存放一个成员,则存放,否则直接浪费剩余的位,直接开辟新的空间
注意:位段是不跨平台的,因此可移植性较差。
讲解下面的例子:
1. 由于成员的类型时int,所以一次开辟4个字节空间给成员
2. 成员a占3个比特位,放到了第一个字节的前3个比特位上,该字节剩余29个比特位。
3. 成员b占4个比特位,开辟的空间中有足够的比特位来存放该成员,因此该成员放到开辟空间的第四-第七个比特位上,此时开辟的空间还有25个比特位。
4. 成员c占10个比特位,开辟的空间有足够的空间,将其放在开辟空间的第八个比特位上,此时还剩下15个比特位。
5. 成员d占23个比特位,开辟的空间中没有足够的空间,因此需要再开辟4个字节,将d放在开辟空间的前23个比特位上,此时剩下9个比特位。
6. 成员e占29个比特位,开辟的空间没有足够的空间,需要再开辟4个字节。将e放到开辟空间的前29个比特位上,此时剩余3个比特位。
7. 共开辟了3次空间(3 *4 = 12)个字节,因此该位段所占字节为12,即大小为12.
a:1010,占三个字节为:010
b:10100,占4个字节为:0100
c:11:
d:100
e:101
abc在内存中为:
00000000 00000000 00000001 10100010
d在内存为:
00000000 00000000 00000000 00000100
e在内存为:
00000000 00000000 00000000 00000101
00000000 00000000 00000000 00000000
|c |b |a |
00000000 00000000 00000000 00000000
|d |
00000000 00000000 00000000 00000000
|e |
因此打印的结果为:418
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// 声明一个位段结构体:WEIDUAN
struct WD0
{
int a : 3;
int b : 4;
int c : 10;
int d : 23;
int e : 29;
};
int main()
{
struct WD0 wd0 = {0}; // 创建一个位段类型的变量 weiduan
int i;
int* p = (int*)&wd0;
wd0.a = 10;
wd0.b = 20;
wd0.c = 3;
wd0.d = 4;
wd0.e = 5;
printf("%d\n", sizeof(struct WD0)); // 12
for (i = 0; i < sizeof(struct WD0)/sizeof(int); i++)
{
printf("%d\n", *(p+i));
}
return 0;
}
例子2:char类型的成员
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
struct WD1
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
int a = 0x1102ccff;
struct WD1 wd1 = { 0 };
char* p = NULL;
p = (char*)&wd1;
wd1.a = 10;
wd1.b = 20;
wd1.c = 3;
wd1.d = 4;
printf("%d\n", sizeof(wd1));
printf("%#x\n", *(p+0));
printf("%#x\n", *(p + 1));
printf("%#x\n", *(p + 2));
return 0;
}
将有限个能够取到的值,放到一起,用enum关键字进行修饰的类型,称为枚举类型,枚举类型变量的关键字:enum,其中每个元素称为枚举元素,即枚举的可能取值。
如:enum Day {Mon, Tues, Wed, Thur, Fri, Sat, Sun};就是一个枚举类型的变量。
注释:枚举的可能取值是不能用双引号进行修饰。
枚举元素是有默认值的,第一个枚举元素的默认值为0,后面的值依次增加1.
枚举元素的值时可以修改的,如第一个枚举元素的值修改为3,则后面元素的值依次分别为4 5 6 ...
枚举元素的值可以是重复的,如Mon = 3, Fri = 3;
枚举元素的值不相同
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
enum Day1 { Mon, Tues, Wed, Thur, Fri, Sat, Sun };
//enum Day2 { Mon = 2, Tues, Wed, Thur = 2, Fri = 4, Sat = 6, Sun };
int main()
{
enum Day1 d1 = Mon;
if (Mon == 0)
{
printf("周末结束啦,又要上班啦\n");
}
printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun);
return 0;
}
枚举元素值有相同时
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
//enum Day1 { Mon, Tues, Wed, Thur, Fri, Sat, Sun };
enum Day2 { Mon = 2, Tues, Wed, Thur = 2, Fri = 4, Sat = 6, Sun };
int main()
{
enum Day1 d2 = Mon;
if (d2 == 0)
{
printf("周末结束啦,又要上班啦\n");
}
else
{
printf("哈哈,工作日居然不上班,爽!\n");
}
printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun);
return 0;
}
1. 增加代码的可维护性以及可读性。
2. 枚举有类型检查的功能,而#define仅仅做了替换(预编译阶段,该阶段会把所有的注释全部删除)的工作(源代码-预编译-编译-链接-可执行结果)
3. 防止命名交叉感染
4. 使用方便,因为可以一次定义多个常量,且该常量集合的性质一样。
是一种特殊的自定义类型,和结构体有点类似(只是关键字不同,这里为union),也会包含一系列的成员。
但是这些成员公用一块内存空间,即同一时刻只能使用一个成员。
例如:union desc {char sex;int age};
联合体大小为至少是最大成员所占字节的大小。
最大成员不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
union Desc
{
char sex;
int age;
};
int main()
{
union Desc des;
printf("%d\n", sizeof(des));
printf("%p\n", &des);
printf("%p\n", &(des.age));
printf("%p\n", &(des.sex));
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// 返回1:小端
// 返回0:大端
int bs_byte()
{
union BS
{
int a;
char c;
} bs;
bs.a = 1;
return (int)(bs.c);
}
int main()
{
int ret = bs_byte();
if (1 == ret)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
原文:https://blog.51cto.com/u_15132389/2729488