传送门:C语言-第六章:结构体
目录
第一节:位段
1-1.位段是什么
位段是一种特殊的结构体,合理使用可以节约内存空间,它的声明与定义和结构体有两个不同:
(1)位段的成员必须是整型家族:int、unsigned int、short、long、long long、char
(2)位段的成员名后有冒号和数字,表示这个成员占用的内存大小(比特)
// 位段
struct A
{
int a : 2;
int b : 1;
};
struct A z;
上述代码表示成员 a 占用2个比特位,成员 b 占用1个比特位,那么 a 只有00、01、10、11,四种二进制,分别表示十进制的0、1、2、3;同理,b 只能表示0、1。
1-2.位段的大小
按照之前的规则这个结构体的大小应该是8字节,但是它实际上的大小是4字节:
#include <stdio.h>
// 位段
struct A
{
int a : 2;
int b : 1;
};
int main()
{
printf("它的大小是:%d\n",sizeof A);
return 0;
}
这是因为它的两个成员只占用了1个字节(可能是2个字节,具体看平台差异)的空间,但是int类型的对齐数是4,所以这个结构体的最大对齐数是4,结构体的大小至少是最大对齐数的整数倍,即4字节。
因此在表示的数字不太大时,合理使用位段可以节约空间。
第二节:联合体
2-1.联合体是什么
联合体也可以节约空间,它的所有成员共用内存空间,同一时刻只能使用其中的一个成员。
它的声明和定义如下:
// 联合体
union U
{
char c;
int a;
};
union U u;
2-2联合体的大小
联合体的大小有两个规则:
(1)联合体的大小至少是最大成员的大小
(2)当最大成员的大小不是最大对齐数的整数倍时(比如数组),联合体的大小也至少是最大对齐数的整数倍
第一条规则如下:
#include <stdio.h>
// 联合体
union U
{
char c; // 4字节
int a; // 1字节
};
union U u;
int main()
{
printf("联合体的大小:%d\n", sizeof U);
return 0;
}
第二条规则如下:
#include <stdio.h>
// 联合体
union U
{
char c[5]; // 大小为5,但是对齐数是1
int a; // 对齐数是4
};
union U u;
int main()
{
printf("联合体的大小:%d\n", sizeof U);
return 0;
}
根据规则1,联合体的大小比最大成员 c 的大小5还要大;根据规则2,联合体的大小也至少是最大对齐数的整数倍,最大对齐数是4,所以它的大小是4X2等于8。
第三节:枚举类型
3-1.枚举是什么
枚举是一种数据类型,它用于定义一组命名的整型常量。它使代码更具可读性和可维护性。
它的声明与定义如下:
// 枚举
enum color
{
RED,
GREEN,
BIUE
};
color e;
其中的变量 e 的类型是 color,它可以用整型或者 enum color 中的 RED, GREEN, BIUE赋值:
#include <stdio.h>
// 枚举
enum color
{
RED,
GREEN,
BIUE
};
int main()
{
color e1 = RED;
color e2 = GREEN;
color e3 = BIUE;
printf("%d\n", e1);
printf("%d\n", e2);
printf("%d\n", e3);
return 0;
}
可见,枚举类型中的整型常量默认从0开始赋值,我们也可以在任意位置改变它的数值:
#include <stdio.h>
// 枚举
enum color
{
RED=1, // 赋值
GREEN,
BIUE
};
int main()
{
color e1 = RED;
color e2 = GREEN;
color e3 = BIUE;
printf("%d\n", e1);
printf("%d\n", e2);
printf("%d\n", e3);
return 0;
}
总之,如果不改变中间的整型常量的大小,枚举类型的整型常量就是依次递增的。
第四节:结构体中的柔性数组
4-1.柔性数组是什么
柔性数组是定义在结构体中的数组,它在定义时不能写出数组大小,否则就是普通数组而不是柔性数组了:
// 含有柔性数组的结构体
struct A
{
int i;
int arr[]; // []中不能写入整数
};
在计算含有柔性数组的结构体的大小时,不考虑柔性数组,上述结构体的大小仍然是4字节:
#include <stdio.h>
// 含有柔性数组的结构体
struct A
{
int i;
int arr[]; // []中不能写入整数
};
struct A a;
int main()
{
printf("含有柔性数组的结构体大小:%d",sizeof A);
return 0;
}
4-2.为什么要有柔性数组
在C语言中,数组在定义时它的大小就必须确定了,即它的 [ ] 中只能写常量或者常量表达式:
int arr1[b]; // 错误写法,b是个变量
int arr1[2]; // 正确写法,2是个常量
int arr1[2*3]; // 正确写法,2*3是个常量表达式
这就意味着数组的大小不能改变,而柔性数组就可以改变大小,柔性数组利用 malloc 函数来改变自身的大小。
4-3.malloc 函数
之前我们说过,局部变量的空间在内存的栈区,全局变量的空间在内存的静态区,这个两个区由系统自己回收和管理空间;而堆区空间可以由用户自己申请。在程序运行过程中,申请多少堆区空间,什么时候回收,由用户自己决定。
malloc 函数的作用是在内存的堆区开辟一块大小确定空间,然后返回这块空间的首地址,供用户使用,它开辟柔性数组的使用方法如下:
struct A* ptr = (struct A*)malloc(sizeof A+20);
上述代码表示在堆区开辟一个 结构体大小+20 字节 的空间,其中的20就是你要开辟的柔性数组的大小。
它的示意图如下:
然后我们就可以用 ptr 来访问这个柔性数组了:
#include <stdio.h>
#include <stdlib.h>
// 含有柔性数组的结构体
struct A
{
int i;
int arr[]; // []中不能写入整数
};
int main()
{
struct A* ptr = (struct A*)malloc(sizeof A+20);
(ptr->arr)[0] = 0;
(ptr->arr)[1] = 1;
(ptr->arr)[2] = 2;
(ptr->arr)[3] = 3;
printf("%d ", (ptr->arr)[0]);
printf("%d ", (ptr->arr)[1]);
printf("%d ", (ptr->arr)[2]);
printf("%d\n",(ptr->arr)[3]);
return 0;
}
如果我们要再次改变柔性数组的大小,可以使用 realloc 函数(因为 malloc 不能保留原来空间的数据):
#include <stdio.h>
#include <stdlib.h>
// 含有柔性数组的结构体
struct A
{
int i;
int arr[]; // []中不能写入整数
};
int main()
{
struct A* ptr = (struct A*)malloc(sizeof A+20);
struct A* ptr1 = (struct A*)realloc(ptr,sizeof A + 24); // 再次申请
(ptr1->arr)[0] = 0;
(ptr1->arr)[1] = 1;
(ptr1->arr)[2] = 2;
(ptr1->arr)[3] = 3;
(ptr1->arr)[4] = 4;
printf("%d ", (ptr1->arr)[0]);
printf("%d ", (ptr1->arr)[1]);
printf("%d ", (ptr1->arr)[2]);
printf("%d ", (ptr1->arr)[3]);
printf("%d\n",(ptr1->arr)[4]);
return 0;
}
realloc 的第一个参数需要原来空间的指针,因为它需要拷贝原来空间中的数据到新空间中。这种灵活的内存分配方式叫做 动态内存分配。
关于更多动态内存分配相关的知识将在第七章讲述。
第五节:利用成员计算结构体的地址
计算结构体的地址需要以下1个已知条件:
(1)已知某个成员变量的地址
它的计算方式如下:
看上图,我们要得到结构体的地址,就需要得到红线的长度(也就是相对于地址0的差值),我们假设有一个相同类型的结构体变量对齐到地址为0的地方 ,那么黄线长度和红线长度是一样的。
我们用 已知的成员地址-虚拟结构体的成员地址 就可以得到结构体的地址,虚拟结构体的成员地址可以用 &((结构体类型*)0->成员变量名) 得到:
#include <stdio.h>
struct A
{
int i;
int b;
};
int main()
{
struct A a;
printf(" 原来的结构体地址:%p\n", &a);
printf("计算得到的的结构体地址:%p\n", (long long) & (a.b) - (long long) & ((struct A*)0)->b);
return 0;
}
用 (long long)强转是因为 指针-指针 的操作得到的不是地址的差值,而是获得偏移量。
用成员得到结构体地址在操作系统的内核数据结构中非常有用。
下期预告:
下一次我们将进入动态内存分配、字符和字符串函数的学习,具体要认识以下函数:
memcpy 内存拷贝函数
memmove 内存拷贝函数
memset 内存设置函数
memcmp 内存比较函数
strtok 字符串切割函数
strerror 错误码翻译函数
malloc calloc realloc 堆区申请函数
free 堆区释放函数
标签:arr,struct,自定义,int,C语言,加餐,数组,printf,柔性 From: https://blog.csdn.net/2303_78095330/article/details/141826986