目录
一、结构体的声明和使用
1.1 结构体正常声明和创建
struct用来声明结构体变量,格式为struct 名字{内部成员};(这里有分号)后面还能直接设置全局变量,直接写变量名,比如struct Stu{};然后初始化一个变量名struct Stu s1,这个s1就是结构变量(struct Stu是一个结构体的类型是一个整体,而s1是这个变量名)。使用时需要使用{}里面用来设置初始化的,如果struct里面还有一个结构体,那么初始化时{}这个括号内也要再加一个括号给他初始化,引用时可以不按顺序来设置初始化,这时候就需要一个操作符“.”点这个操作符,格式为.内部变量名=初始化。当然还可以修改初始化内容就是结构体名.内部变量名=修改内容,但是数组就需要一个库函数strcpy(string.h),strcpy(结构体名.内部变量,修改内容);对于内部成员为指针,引用时可以用“->”指针取向操作符。
1.2 结构体特殊声明
对于特殊声明就是没有名字(指的是结构体名字),而如果没有名字的结构体内成员是一模一样的两个结构体,两个结构体中设置指针,将一个指针赋给另一个结构体的指针时,编译器会报警告(编译器默认这两个结构体指针类型是不一样的)。
#include<stdio.h>
struct
{
char str[20];
int a;
char b;
}*s1,a;
struct
{
char str[20];
int a;
char b;
}*s2;
int main()
{
s1 = &a;
s2 = s1;
return 0;
}
报警告!!!
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。
1.3 结构体的自引用
结构体的自引用就是链表,通过指针引用结构体进行内容访问寻找数据。
需要定义链表的节点
struct Stu
{
char name[20];//数据域
struct Stu* nest;//地址域
};
↑↑↑
正确方式
struct Stu
{
char name[20];
struct Stu nest;
};
↑↑↑
错误方式
扩展:数据结构,是描述数据在内存中的存储结构。结构体链表(结构体自引用),通过相同的节点进行连接,上一个节点有下一个节点的地址,而下个节点有下下个节点的地址。
这里夹杂了 typedef 对匿名结构体类型重命名 ,在函数中自引用如果使用不到位会报错,不能只写重命名的名字到里面,而是要写全,才能设置为指针域。
#include<stdio.h>
typedef struct node
{
int a;
//node* next;//因为上面typedef重命名还没命名完,所以这里需要写全
struct node* nest;//所以还需要自己来写
}node;//这里typedef已经重定义完了
int main()
{
return 0;
}
二、结构体内存对齐
2.1 对齐规则
第一条:结构体内部的第一个任何成员是在偏移量为0位置
第二条:结构体要对齐到最小对齐数的整数倍(最近的整数倍);最小对齐数为成员类型的字节数与编译器默认的对齐数的最小对齐数。这里以我的编译器vs来举例,该编译器的默认为8,我在结构体内部用了一个char一个int类型成员(按左到右按顺序)按照第一条char位于偏移量为0,而int类型的话,那就是要位于偏移量为4的位置了(int类型4个字节,默认8,最小为4)。
第三条:结构体总大小要为最大对齐数(这里指的是成员内最大对齐数,char最小是1,int最小是4,所以最大对齐数为4)的整数倍(最近)。同样的举一个例子来说明 内部有三个成员(按顺序)char、int和char类型的成员
第四条:嵌套结构体时,计算结构体总大小时,最大对齐数要和嵌套结构体的内部成员一起计算,嵌套结构体时可以将嵌套的那个结构体当作结构体内的一个成员,规则都和前面3条符号,就是计算总大小时的最大对齐数要配合嵌套结构体的内部成员。
#include<stdio.h>
struct test1
{
char a;
double b;
char c;
int d;
};
struct test2
{
int e;
struct test1;//默认8,这个成员字节为24,最小为8
};
int main()
{
printf("%zd\n", sizeof(struct test2));
return 0;
}
对于结构体的内存对齐一般是为了用空间换取时间的手段,方便了内存的读取(如果不对齐,可能需要读取两次,对齐只需读一次),提高读取效率,不过在对于这个规则,我们不能一味的用大量的空间去换取时间,可以将一些小的类型写在一起(相同类型),避免了大量空间的浪费,也确保了读取效率。
计算偏移量,一般用offsetof来表示宏,可以计算结构体成员相较于结构体起始位置的偏移量,他需要头文件stddef.h。用法为offsetof(结构体类型,结构体内成员) 。
2.2 #pragma修改
这个定义是用于改变默认对齐数的(改变的是编译器),一般编译器默认是8,改变为4或者2,也会影响结构体大小。
这里pragma要配合pack使用。不过一般修改都是以2的次方数进行修改的。
三、结构体传参
结构体传参有两种方式,一种是传值(结构体变量),一种是传址(传地址)。
#include<stdio.h>
struct Stu
{
char name[20];
int age;
};
void print2(struct Stu* s1)//传址
{
printf("%s\n", s1->name);
}
void print1(struct Stu s1)//传值
{
printf("%s\n", s1.name);
}
int main()
{
struct Stu s1 = { "zhangsan",20 };
print1(s1);
print2(&s1);
return 0;
}
一般我们写结构体的时候会选择传址调用,传值调用消耗内存太大了,如果结构体内部有一个很大的数组,那么这个结构体开辟空间会很大,不仅如此形参和实参的调用都会开辟这么多空间。传值调用,因为指针字节要么是4个字节要么是8个字节,就算开辟也不会占很大空间,所以一般会使用传址调用。
四、结构体位段
4.1 位段内存分配
结构体位段,用于节省空间,方便传输等。结构体内的成员必须是int、unsigned int、signed int类型,不过在c99中其他成员类型也是可以。
struct A
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};
这就是一个位段,后面接的是比特数 。
内存中的位段是
这里如果以上空间都正常使用的话,一共开辟2个字节空间,但是实际不是这样,因为他们不够连续在一起,就会再开辟(因为不是从左到右,这里是从右到左)
4.2 位段内存应用
位段在网络传输过程中用到很多位段,因为我们在传输的时候会考虑到传输速度和简单方面考虑可以类比卡车。
像第二张,这种小车就能够进行超车,效率会很快。
位段是没有跨平台性的,所以我们可以为一个平台专门设置位段进行节约空间。,不过位段也有缺点,就是不能取地址吗,所以就不能通过scanf来修改内的内容,只能先赋值在位段。
五、结构体中的柔性数组概念
柔性数组是位于结构体内最后的一位成员的数组,可以没有大小。特点就是计算结构体总大小时,不会计算数组大小,这个柔性数组是可以通过动态内存来确定数组大小(更加灵活),这个我后面会和动态内存一起讲。
六、union联合体
6.1 union联合体声明、使用和特点
union联合体声明和时候方法是和struct结构体是一样的,也有匿名联合体,唯一的区别就是在计算时总大小时是不一样的,union联合体大小是去内部成员内最大字节,其他的会重叠内存,改变一个字节就会影响另一个数。联合体相比结构体会更加节约空间。
//在表示商品共同属性和特殊属性时也可以使用union联合体
#include<stdio.h>
struct gift_list
{
int stock_number;//库存
double price;//定价
int item_type;//商品类型
union
{
struct
{
char tille[20];//书名
char author[20];//作者名
int num_pages;//页数
}book;
struct
{
char design[20];//设计
}mug;
struct
{
char design[20];//设计
int colors;//颜色
int sizes;//尺寸
}shirt;
};//这里都用匿名,是因为都只使用一次
}item;
//这样的设计就能节约空间(相比较全部用struct结构体)
int main()
{
return 0;
}
6.2 union联合体在内存的存储
union联合体在内存中是内存重叠。
这里用判断大小端来举例
#include<stdio.h>
union A
{
char a;
int b;
};
int check_sys()
{
union A n = { 0 };
n.b = 1;
if (n.a == 1)
{
return 1;
}
else
{
return 0;
}
}
int main()
{
int ret =check_sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
这里再来一个例子
#include<stdio.h>
union Un
{
char c;
int i;
};
int main()
{
//联合变量的定义
union Un un = { 0 };
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
return 0;
}
七、枚举类型
7.1 枚举类型的声明
枚举一般用于列举,比如生活中的星期、性别、月份和三原色等。声明和使用和结构体和联合体类似。
enum sex
{
boy,
girl,
secret
};//格式和结构体和
内部的值都是枚举常量,只能在内部改变。内部不赋值,那就默认从0开始递增。
7.2 枚举类型的优点
枚举的优点1.方便代码可读性,可维护性。2.相较于用#define定义的常量,枚举有类型的会更加严谨。3.使用方便,可以一次性列举多个常量。4.枚举是遵循定义域的,在函数中定义,就只能在函数中使用。