目录
一、结构体
1、 结构体的定义与声明
基本架构:
typede stuct 名字
{
成员;
成员;
}变量;
typedef重命名结构体,接下来在创建结构体时可以直接使用重命名的名字
【注意】
结构体定义完成后,必须在后面加上一个分号(;)表示结束
//基本架构
stuct 名字
{
成员;
成员;
}变量;
//结构体普通声明
struct pop
{
char[20] name;
int age;
char sex[5];
};//注意分号不能丢
//typedef重命名结构体,接下来在创建结构体时可以直接使用重命名的名字
typedef struct num
{
int x;
int y;
}Num;
int main()
{
struct num a1 = { 1,2 };
Num a2 = { 1,2 };
printf("%d %d\n", a1.x, a1.y);// 1 2
printf("%d %d\n", a2.x, a2.y);// 1 2
return 0;
}
//结构体的特殊声明
//匿名结构体类型,这种类型只能使用一次
struct
{
int a;
int b;
}num;
int main()
{
//匿名结构体类型,这种类型只能使用一次
num.a=1;
printf("%d\n",num.a);// 1
return 0;
}
2、结构体变量的定义和初始化
struct num
{
int x;
int y;
}a1;//声明类型的同时定义变量
int main()
{
struct num a2;//定义结构体变量
struct num a3 = { 1,2 };//定义结构体变量并初始化
return 0;
}
3、结构体的自引用
结构体中可以包含一个类型为结构体本身的成员,但是必须要是指针类型的,链表就是基于结构体的自引用实现的
//错误版本
struct node
{
int a;
struct node b;
};
//在sizeof()计算大小时无法计算其大小
//正确版本
struct node
{
int a;
struct node* b;
};
4、结构体的内存对齐
引入
计算一下有着相同成员的结构体,成员位置不同的大小
//结构体的内存对齐
struct node1
{
char q1;// 1
char q2;// 1
int q3;// 4
};
struct node2
{
char q1;// 1
int q2;// 4
char q3;// 1
};
int main()
{
//分别计算两个结构体的大小
printf("%d\n", sizeof(struct node1));// 8
printf("%d\n", sizeof(struct node2));// 12
return 0;
}
为什么有着相同元素的结构体,只是单纯的调换了一下元素位置,所得到的结构体大写会不一样?
正是因为有着结构体的内存对齐,才会导致有着相同元素的结构体,因顺序不同,所占内存的大小可能会有所不同
结构体的内存对齐的规则
1、第一个成员在与结构体变量偏移量为0的地址处,其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
2、对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值
VS中默认的值为8
Linux中gcc没有默认对齐数,对齐数就是成员自身大小
3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
node1大小计算:
struct node1
{
//第一个成员在与结构体变量偏移量为0的地址处
char q1;// 1
//其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
//编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 1 取最小值为 1
//此时的偏移量为1,是对齐数的倍数
char q2;// 1
//编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 4 取最小值为 4
//此时的偏移量为2,不是对齐数的倍数,所以要偏移到最近的对齐数倍数,也就是4处
int q3;// 4
//结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
//此时的偏移量为7,不满足最大对齐数(4)的整数倍,所以要偏移到最近的最大对齐数倍数,也就是8处
//因此结构体的总大小为8
};
node2的大小计算:
struct node2
{
//第一个成员在与结构体变量偏移量为0的地址处
char q1;// 1
//其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
//编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 4 取最小值为 4
//此时的偏移量为1,不是对齐数的倍数,所以要偏移到最近的对齐数倍数,也就是4处
int q2;// 4
//编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 1 取最小值为 1
//此时的偏移量为8,是对齐数的倍数
char q3;// 1
//结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
//此时的偏移量为9,不满足最大对齐数(4)的整数倍,所以要偏移到最近的最大对齐数倍数,也就是12处
//因此结构体的总大小为12
};
嵌套结构体的大小计算:
struct node1
{
char q1;// 1
char q2;// 1
int q3;// 4
struct node2 q4;// 12
};
struct node2
{
char q1;// 1
int q2;// 4
char q3;// 1
};
//第一个成员在与结构体变量偏移量为0的地址处
char q1;// 1
//其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
//编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 1 取最小值为 1
//此时的偏移量为1,是对齐数的倍数
char q2;// 1
//编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 4 取最小值为 4
//此时的偏移量为2,不是对齐数的倍数,所以要偏移到最近的对齐数倍数,也就是4处
int q3;// 4
//嵌套的结构体对齐到自己的最大对齐数的整数倍处,struct node2 的最大对齐数为4
//此时的偏移量为8,是对齐数的倍数
struct node2 q4;// 12
//嵌套的结构体的结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
//此时的偏移量为20,满足最大对齐数(4)的整数倍
//因此结构体的总大小为20
#include <stdio.h>
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S1));
//输出的是12
return 0;
}
//结构体嵌套计算大小
#include <stdio.h>
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S3));
//输出的是16
printf("%d\n", sizeof(struct S4));
//输出的是32
return 0;
}
5、为什么要结构体的内存对齐
1、性能原因
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
2、平台原因
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法
6、结构体传参
结构体传参的时候,建议传结构体的地址
原因:
1、 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
2、如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降
#include <stdio.h>
struct S
{
int data[1000];
int num;
};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
struct S s = { {1,2,3,4}, 1000 };
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
二、 位段
位段的声明和结构是类似的,有两个不同:
1、位段的成员必须是 int、unsigned int 或signed int
2、位段的成员名后边有一个冒号和一个数字
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
printf("%d\n", sizeof(struct A));//8
return 0;
}
1、位端的内存分配
1、位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2、位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的
3、位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
struct A
{
char _a : 3;
char _b : 4;
char _c : 5;
char _d : 4;
};
int main()
{
struct A node = { 0 };
node._a = 10;
//10的二进制序列为 1010,但是 a 只占 3 个字节,因此在内存中只会存入 010
node._b = 12;
//12的二进制序列为 1100,但是 b 占 4 个字节,因此在内存中只会入 1100
node._c = 3;
//3的二进制序列为 11,但是 c 占 5 个字节,因此在内存中只会入 11
//此时之前开辟的一个字节已经不够 5 个字节,所以内存会重新开辟一个字节
node._d = 4;
//4的二进制序列为 100,但是 d 占 4 个字节,因此在内存中只会入 100
//此时之前开辟的一个字节已经不够 4 个字节,所以内存会重新开辟一个字节
//最后一个 struct A 所占 3 个字节的内存大小
//二进制序列为:01100010 00000011 00000100
//十六进制序列为:0x62 03 04
int a = 0;
return 0;
}
2、位段的跨平台问题
1、int 位段被当成有符号数还是无符号数是不确定的。
2、位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3、位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4、当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
5、总结跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在
3、位段使⽤的注意事项
1、位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的
2、不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A node = { 0 };
//不能对位段的成员使⽤&操作符
scanf("%d", &node._a);//erreor
int tmp = 0;
scanf("%d", &tmp);
node._a = tmp;//yes
return 0;
}
三、枚举
枚举顾名思义就是一一列举,把可能的取值一一列举
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
int main()
{
//创建枚举变量
enum Color q1=RED;
enum Sex q2=MALE;
return 0;
}
枚举的优点
1、增加代码的可读性和可维护性
2、和#define定义的标识符比较枚举有类型检查,更加严谨。
3、防止了命名污染(封装)
4、便于调试
5、使用方便,一次可以定义多个常量
四、联合体(共用体)
1、联合也是一种特殊的自定义类型这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)
2、改变一个值,其他的值也会随机改变
#include <stdio.h>
//联合体的定义
union Un
{
char c;
int i;
};
int main()
{
//创建联合体变量
union Un un;
//计算大小
printf("%d\n", sizeof(un));
//输出的是4
}
1、联合体的大小计算
1、联合的大小至少是最大成员的大小
2、当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
#include <stdio.h>
union Un1
{
char c[5];//对齐数是1
int i;
};
union Un2
{
short c[7];//对齐数是2
int i;
};
int main()
{
printf("%d\n", sizeof(union Un1));
//输出的是8
printf("%d\n", sizeof(union Un2));
//输出的是16
return 0;
}
2、联合体的特点
联合的成员是共⽤同⼀块内存空间的,这样⼀个联合变量的⼤⼩,⾄少是最⼤成员的⼤⼩
//联合体的定义
union Un
{
int o;
int i;
};
int main()
{
//创建联合体变量
union Un un;
//改变一个值,其他的值也会随机改变
un.o = 20;
un.i = 10;
printf("%d %d\n", un.o, un.i);
//10 10
return 0;
}
标签:位段,struct,自定义,int,C语言,char,类型,对齐,结构
From: https://blog.csdn.net/LVZHUO_2022/article/details/142887862