结构体:
-
结构体的基础
通过类型创建变量的过程叫做实例化
struct tag——类型(tag是类型标识符)
{
member-list;——成员列表(结构体成员变量可以是不同类型)0
}name;——变量名列表(全局变量)最后的分号不能省略
-
匿名结构体声明
struct——可以省略(tag标识符)
{
member-list;
}name;
-
结构体的成员访问
struct tag
{
char name[20];
int age;
};
访问成员方法:
1.使用结构体变量名+ . +结构体成员变量名来访问
struct tag s;//定义结构体变量
s.age = 20;
strcpy(s.name,"zhangpeng");
2.通过使用结构体指针的形式来访问成员
结构体指针变量名+ -> +结构体成员变量名来访问
struct s*ps;//结构体指针的定义
ps->age = 20;
strcpy(ps->name,"zhangpeng");
-
结构体的自引用
在结构体成员变量中有指向本身结构体的结构体指针
1.
struct node
{
int date;
struct node*next
};
2.
typedef struct node
{
int date;
struct node*next;
} node;
-
结构体的定义和初始化
struct point
{
int x;
char name[15];
}p2; // 声明类型的同时定义结构体变量p2;
struct point p1;//定义结构体变量p2;
struct point p3={"zhangpeng",20}; //初始化结构体成员变量
struct node
{
int date;
struct point p1;
struct node* next;
}n1={10,{"zhangpeng",20},NULL};//结构体嵌套初始化
struct node n2 = {20,{"yang",20},NULL};//结构体嵌套初始化
-
结构体内存对齐
1.计算结构体内存大小(结构体内存对齐)
struct s1
{ 地址 类型占的空间大小
char c1; 0 + 1
int i; 4的倍数处(4) + 4// 整型i对其到4(对齐数)的倍数的地址处
char c2; 8地址处 + 1
}
printf("%d\n",sizeof(struct s1)); //1+3+4+1=9->12 但是结构体的总大小要为结构体成员变量的最大对齐数的倍数(4的倍数)
struct s1
{ 地址 类型占的空间大小
char c1; 0 + 1
char c2; 1 + 1
int i; 4 的倍数处(4) + 4// 整型i对其到4(对齐数)的倍数的地址处
}
printf("%d\n",sizeof(struct s1)); //1+1+2+4=8->8 结构体的总大小要为结构体成员变量的最大对齐数的倍数(4的倍数)8正好是4的倍数
struct s3
{ 地址
char c1; 0 + 1
struct s2; 4的倍数处(4) + 4 //结构体嵌套结构体对齐到自身结构体的最大对齐数地址处(嵌套结构体最大对齐数是自身结构体成员的最大对齐数)s2最大对齐数是i的对齐数4
double d; 8的倍数(8) + 8//double类型的对齐数是8
}
printf("%d\n",sizeof(struct s3)); //1+3+4+8=16->8 结构体的总大小要为结构体成员变量(包含嵌套结构体的最大对齐数)的整数倍(4)的最大对齐数的倍数(8的倍数)16正好是8的倍数
-
结构体的内存对齐规则
-
第一个成员在与结构体变量偏移量为0的地址处
-
其他成员变量要对齐到某数字处(对齐数)的整数倍的地址处。对齐数=编译器默认的一个对齐数与该成员大小的较小值。(vs中默认的值是8 linux中的默认值为4)
-
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
-
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处(嵌套结构体最大对齐数是自身结构体成员的最大对齐数),结构体的总大小要为结构体成员变量(包含嵌套结构体的最大对齐数)的整数倍。
-
内存对齐规则的存在原因
-
平台原因(移植原因)
不是所有平台都能访问任意地址上的数据;某些硬件平台只能在某些地址处取某些特定的数据,否则抛处硬件异常。
2.性能原因
数据结构(尤其是栈)应尽可能地在自然边界上对齐
原因在于,为访问未对齐的内存时,处理器需要做两次内存访问;而对齐的内存仅需要一次访问。
总结:结构体的内存对齐时拿时间来换取时间的做法(访问速度快)
在设计结构体的时候,我们既要满足对齐,又要节省空间,——让占用空间较小的尽量集中放到一起。
-
结构体传参
struct S
{
int date[3];
int num;
};
struct S s={{1,2,3}1000};//初始化
void print1(struct S s) //结构体参(值)
{
printf("%d\n",s.num);
}
void print2(struct S* ps)//结构体传址
{
printf("%d\n",s.num);
}
int main()
{
print(s);//传结构体
print(&s);//传址
return 0;
}
结构体传参时首选传址,因为函数帧栈中,传参需要压栈,参数是需要压栈的。
如果传一个结构体对象时,(传参时接受的形参是结构体本身的临时拷贝),结构体占用空间过大,参数压栈的开销比较大,所以会导致性能下降。
但传址则不同,结构体传址时(传参时接受的形参是指针只占4个字节),压栈时就比较快速了。
结论:结构体传参时,要传结构体的地址。
位段
-
位段概念
struct A
{ (单位时bite) 开辟内存
int a:2; 4个字节(32个bite) 用了 2个bite 剩余30bite
int b:5; 用了 5个bite 剩余25bite
int c:10; 用了 10个bite 剩余15bite 下来 整型d要用了 30个bite 剩余15bite不够用则
int d:30; 再开辟4个字节(32个bite) 用了30bite 剩余2bite
};
printf("%d\n",sizeof(struct A)); // 4+4=8 (字节) 剩余的空间不使用
-
位段的内存分配
-
位段的成员可以是unsigned int,signed int ,或者时char 类型(属于整型家族)
-
位段的空间上是按照需要4个字节(int)或者1个字节(char)的方式来开辟的。
-
位段涉及很多不确定因素,位段往往是不跨平台的,注意可移植的程序应该避免使用位段(使用的化要利用条件编译)。
struct S 空间开辟
{
char a:3; 最大能赋值为2^3-1=7 111
char b:4; 最大能赋值为2^4-1=15 1111
char c:5; 最大能赋值为2^5-1=31 11111
char d:4; 最大能赋值为2^4-1=15 1111
};
struct S s = {0};
s.a=10; 开辟1字节内存(8bite)使用3bite 剩余5bite 10的补码是0000000000000000000000001010 取3个bite存储10放不下 截断 存储 010 2
(截断后从左向右存放还是从右向左存放不确定,平台不一样有可能存放方式也不同)
s.b=12; 使用4bite 剩余1bite 12的补码是0000000000000000000000001100 取4个bite存储12能放下 存储1100 12
(舍弃剩余位还是利用是不确定的)
s.c=3; 重新开辟1字节内存(8bite)使用5bite 剩余3bite 3的补码是 0000000000000000000000000011 取5个bite存储 3 能放下 存储00011 3
s.d=4; 重新开辟1字节内存(8bite) 使用4bite 剩余4bite 4的补码是 0000000000000000000000000100 取4个bite存储 4 能放下 存储100 4
printf("%d\n",sizeof(struct A)); //1+1+1=3(字节) 剩余空间未使用
-
位段的跨平台问题
-
int位段被当成有符号还是无符号是不确定的。
-
位段中最大位数的不确定。(16位机器最大为16,32位机器最大为32,27位在16位机器上会出问题)
-
位段中的成员在内存中从左向右分配还是从右向左分配,标准尚未定义。
-
当一个结构体包含两个位段时,第二个位段比较大,无法容纳第一个位段剩余的空间,是舍弃剩余位段还是利用,这还是不确定的。
-
总结与结构体相比
总结,跟结构体相比位段可以达到同样的效果,但是可以用更好的节省空间,但是有跨平台的问题
位段的应用:IP数据包
可以写不同平台的代码来实现跨平台性(条件编译)
枚举
-
枚举类型的定义
eunm Color
{
RED;//0 (RED=1;)
GREEN;//1 (GREEN=2;) //——枚举常量,这些可能取值默认从0开始,在定义的时候可以赋初值。
BULE;//2 (BLUE=3;)
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型差异
-
枚举的优点
-
增加代码的可读性和可维护性。
-
和#include定义的标识符相比枚举有类型检查,更加严谨。
-
防止命名污染(命名冲突)
-
便于调试
-
使用方便,一次可以定义多个变量。
联合体(共用体)
-
联合体类型的定义
union UN ///联合体类型的声明
{
char c;
int i;
};
union UN un;//联合体变量的定义
un.c="adc";//访问成员变量
un.i=10;
union UN*PS;//联合体指针的定义
ps->c="abc";//访问成员变量
ps->i=10;
unoin UN un={"abc",10};//联合体的初始化
printtf("%d\n",sizeof(un)); //联合体成员变量公用一块空间//4(大小至少位为最大成员大小且当最大成员大小不是最大对齐数时,就要对齐到最大对齐数的整数倍)
-
联合的特点
联合体的成员是共用一块内存空间的,这样一个联合体的大小,至少是最大成员的大小(因为联合体至少有能力保存最大的那个成员。)
-
判断大小端的储存!!!!!!!
int check_sys()
{
union UN
{
int i;
char c;
}un;
un.i=1;
return un.c;//返回1表示小端 , 返回0表示大端。
}
int main()
{
int ret = check_sys();
if(ret==0)
{
printf("大端");
}
if(ret==1)
{
printf("小端");
}
return 0;
}
int check_sys()
{
int i=0;
return (*(char*)i);返回1表示小端 , 返回0表示大端。
}
int main()
{
int ret = check_sys();
if(ret==0)
{
printf("大端");
}
if(ret==1)
{
printf("小端");
}
return 0;
}
-
联合体的大小计算
联合体大小至少位为最大成员大小且当最大成员大小不是最大对齐数时,就要对齐到最大对齐数的整数倍。
union UN
{
char c[5]; //总大小为5,是连续存放5个char ,最大对齐数是1
int i; //大小是4,最大对齐数是4
}un;
printf("%d\n",sizeof(union UN));// 8 因为最大对齐数是4但是最大成员占用空间为5大于最大对齐数,所以总大小为最大对齐数的整数倍(8);
-
联合和结构的巧妙使用
//将long型地址的ip地址,转化为点分10进制的表示形式。
union ip_addr
{
unsigned long addr;
struct
{
unsigned char c1;
unsigned char c1;
unsigned char c1;
unsigned char c1;
}ip;
};
unio ip_addr my_ip;
my_ip.addr=176238479;
printf("%d %d %d %d %d",my_ip.ip.c4,my_ip.ip.c3,my_ip.ip.c2,my_ip.ip.c1 );//利用了结构体可定义多个成员变量,和联合体成员变量是公用同一个空间。
我从我的笔记中粘贴过来的具体可以参看https://app.yinxiang.com/shard/s61/nl/20759013/4caa72c2-ae8c-4ee2-bd81-de912abc9b02