自定义类型(结构体+枚举+联合体)

本文详细探讨了C/C++中的结构体声明、初始化、内存对齐规则,包括匿名结构体、typedef应用、结构体自引用、位段与枚举,以及结构体在参数传递和位段存储上的实践。还介绍了如何通过#pragma pack调整内存对齐,以及C/C++中的联合概念及其在判断大小端和计算大小上的技巧。
摘要由CSDN通过智能技术生成

结构体

声明

struct tag
{
    member-list
}variable-list;
struct Book
{
	char name[20];
    char author[20];
}s1;
struct Book s2;
int main()
{
    struct Book s3; 
}



## 特殊声明 匿名结构体:
struct //匿名结构体类型
{
    int a;
    char c;
    double d;
}s1,s2;///声明的时候创建对象

struct 
{
    int a;
    char c;
    double d;
}* ps
int main()
{
    ps=&s1;///waring:编译器认为不兼容
}

使用typedef的形式如下:

typedef struct 
{
    int data;
    Node* next;
}Node; ///error
typedef struct Node
{
    int data;
    struct Node* next;
}Node;///把这个结构体struct Node --->Node
///typedef xxx Node; //中间的正常结构体命名就是xxx

结构的自引用
数据结构:描述了数据在内存中存储的结构

自己能够找到自己类型的数据

struct Node
{
    int num;
    struct Node* next;
}
`

### 结构体变量的定义和初始化

 - 初始化用{}
 

struct P{
int x,y;
}p3={5,6},p4={7,8};

struct Point p2={1,2};

struct S
{
double d;
struct Point p;
char name[20];
int data[20];
};
int main()
{
struct Point p1={3,4};
struct S s={3.14,{1,5},“zhangsan”,{1,2,3}};
printf(“%lf\n”,s.d);
printf(“%d %d\n”,s.p.x,s.p.x);
printf(“%s\n”,s.name);
}

结构体的内存对齐(计算结构体的大小)
热门的点:

//练习1
struct S1
{
    char c1;
    int a;
    char c2;
};
struct S2
{
    char c1;
    char c2;
    int a;
};
int main()
{
    struct S1 s={'x',100,'y'};
    printf("%d",sizeof(struct S1));
    printf("%d",sizoef(struct S2));
    return 0;
}

**结构体内存对齐的规则
第一个成员在与结构体变量偏移量为0的地址处。
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
每次对齐数 = 编译器默认的一个对齐数 与 该变量大小的较小值。VS中默认的值为8.GCC中没有规定
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数(内部结构体的对齐数))的整数倍。
结构体对齐的意义
平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的数据,某些硬件平台只能在地址处访问特定类型的数据(比如4的倍数),不然会抛出硬件异常
性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
比如一次只能访问4个字节,char+int一次就访问不完,还要接着读取下面的int的一个字节
总的来说,结构体的内存对齐就是空间换时间**
使用的时候我们尽可能满足内存对齐,原则是让占用空间小的成员尽量集中在一起 packi

struct S1
{
    char c1;
    int i;
    char c2;

struct S2
{
	char c1;
     char c2;
	int i;
};
1
2
3
4
5
6
7
8
9
10
11
12
#pragma pack(1)
struct S1
{
    char c1; //1 1 1
    int i; //4 1 1
    char c2;//1 1 1
};
#pragma pack()
int main()
{
    struct S1 s;
    printf("%d\n",sizeof(s));//6
}
1
2
3
4
5
6
7
8
9
10
11
12
13
百度笔试题:

写一个宏,计算结构体中某变量相对于首地址的偏移。并给出说明。

考察:offset宏的实现

#include<stddef.h>
struct S1{    
	char c1;    
	int i;   
	char c2;
};
int main(){    
	printf("%d\n",offsetof(struct S1,c1));    			
	printf("%d\n",offsetof(struct S1,i)) ;
}
1
2
3
4
5
6
7
8
9
10
修改默认对齐数
VS的默认对齐数是8。

#pragma预处理指令可以改变默认对齐数字

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1{ 	
	char c1; 	
	int i; 	
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)
//设置默认对齐数为1
struct S2{ 	
	char c1; 	
	int i; 	
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main(){    
//输出的结果是什么?    
	printf("%d\n", sizeof(struct S1));    
	printf("%d\n", sizeof(struct S2));	
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
结构体的传参
struct S{    
	int data[1000];    
	int num;
};
void print1(struct S tmp){    
		int i=0;    
		for(i=0;i<10;i++){        
			printf("%d ",tmp.data[i]);    
		}    
		printf("\nnum=%d\n",tmp.num);}
void print2(const struct S* ps)//优先考虑使用这种形式比较优秀
{    
		int i=0;    
		for(int i=0;i<10;i++){       
			 printf("%d ",ps->data[i]);   
		}    
		printf("%d\n",ps->num);
}
int main(){    
		struct S s={ {1,2,3,4,5,6,7,8,9,10},100};   
		print1(s);	
		print2(&s);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
位段
讲的书比较少,C和指针提及。

位段的声明和结构是类似的,有两个不同。

位段的成员必须是int,unsigned in,signed int
位段的成员名后边有一个冒号和一个数字
位段–二进制位

eg.性别 男 女 保密

​ 00 01 10 11

//位段是可以节省空间的
struct A{	
		int _a:2;// 2 bit位 只给2位,截断    
		int _b:5;// 5 bit位    
		int _c:10;// 10 bit位    
		int _d:30;//30 bit 位};
		//47 bit ---6 byte 
		//8byte 
		//4byte -->32bit  2+5+10 此时不够30bit给_d
		//+4byte -->32bit  此时_d用哪里的bit呢?标准没有规定。但
		//是此时知道够用了整体为8字节
int main(){    
		printf("%d\n",sizeof(struct A));//8
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct S{    
		char a:3;    
		char b:4;    
		char c:5;    
		char d:4;};
int main(){    
		struct S s={0};   //0 0 0 0 0 0 0 0   
		//先左边3个bit还是右边3个,C没有交代。假设右边开始        					  
		s.a=10;    
		s.b=12;    
		s.c=3;    
		s.d=4;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
位段的内存分配
位段的成员可以是int,unsigned int,signed int,或者char(属于整型家族)类型
位段的空间上是按照需要以4个字节(int)或者1个字节(char)来一个个开辟
位段涉及很多不确定因素,位段不跨平台的。注意可移植程序避免。
VS下一个一个给,剩下不够扔掉。从右边开始。C标准没规定。


位段的跨平台问题
int 位段被当成有符号数还是无符号数是不确定的。
位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。
位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

位段的应用
用于IP数据报的数据传输,节省空间,提高传输效率。


枚举
枚举类型的定义
把可能的取值能一一列举。

enum Day{    
	Mon,    
	Thes,    
	Wed,    
	Thur,    
	Fri,    
	Sat,    
	Sun
};//枚举常量//字面常量,const常量,define常量,枚举常量
enum Color{    
	RED=5,    
	GREEN,//6    
	BLUE//7};//默认从0开始,向下递增,从最后一个赋值好的,剩余没初始化的递增
	int main(){    
		enum Color c=GREEN;    
		if(c==GREEN)    
		{       
		 printf("绿色\n");   
		 }
		}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
枚举的优点
我们可以使用#define来定义,为什么要枚举

增加代码的可读性和标识性
枚举有类型,和#define比起有类型检查,更加严谨
防止了命名污染(封装)
便于调试
会显示是枚举类型
使用方便,一次可以定义多个变量
每个是整型变量,大小固定是整型字节。

枚举的使用
enum OPTION{    
	EXIT,    
	ADD,    
	SUB,    
	MUL,    
	DIV}
void menu(){    }
int main(){    
	int input=0;    
	do { 
	       menu();       
	       printf("请选择:");    	
	       scanf("%d",&input);        
	       switch(input) {
	      	 case ADD:                      
	                 break;            
	      	 case SUB:
	      			 break;                 
	       }  
	    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
联合(共用体)
这种类型定义的变量包含一系列的成员,特征是这些成员公用同一块空间。

联合类型的定义
union Un{    
	char c;    
	int i;
}
int main(){    
		Union Un u={0};    
		printf("%d\n",sizeof(u));///4个字节    
		printf("%p\n",&u);    
		printf("%p\n",&(u.c));    
		printf("%p\n",&(u.i));
}
1
2
3
4
5
6
7
8
9
10
11
i和c同一时间只能用一个。改i也会改c,改c也会改i。

联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为 联合至少得有能力保存最大的那个成员)。

面试题:判断大小端

Union U{	
	char c;    
	int i;
}u;
int main(){    
		u.i=1;    
		if(u.c==1)    
		{        
				printf("小端");    
		}    
		else{    
				printf("大端");    
		}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
联合大小的计算
联合的大小至少是最大成员的大小
当最大成员大小不是最大对齐数的整数倍的时候,要整数对齐
union Un1{	
		char c[5];//5 1    
		int i;//4 4
}
Union Un2{    
		short c[7]; //14 2    
		int i;//4 8 4
}
1
2
3
4
5
6
7
8
练习–通讯录
存放1000个人的信息
信息:名字+性别+年龄+电话+住址
增加联系人
删除联系人
修改联系人
查找联系人
排序联系人
存文件
##练习

下面代码的结果是( )

int main()
{
  unsigned char puc[4];
  struct tagPIM
  {
    unsigned char ucPim1;
    unsigned char ucData0 : 1;
    unsigned char ucData1 : 2;
    unsigned char ucData2 : 3;
  }*pstPimData;
  pstPimData = (struct tagPIM*)puc;
  memset(puc,0,4);
  pstPimData->ucPim1 = 2; 
  pstPimData->ucData0 = 3;
  pstPimData->ucData1 = 4;
  pstPimData->ucData2 = 5;
  printf("%02x %02x %02x %02x\n",puc[0], puc[1], puc[2], puc[3]);
  return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
VS下的位段是右边开始赋值的。同时要注意,整个是一个字节内,所以这种情况下算值是
1 2 4 8 16 32…
填充序列是类似小端的低地址在低位,所以排列顺序是00 101 00 1。也就是0010 1001,即0x29。
因为为02 29 00 00

下面代码的结果是:

#include<stdio.h>
int main()
{
  union
  {
    short k;
    char i[2];
  }*s, a;
  s = &a;
  s->i[0] = 0x39;
  s->i[1] = 0x38;
  printf(“%x\n”,a.k);
  return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
注意这是小端机子。
所以s->i[0]=0x39。放在左边(即低地址)
s->i[1]=0x38。放在右边(即高地址)
两个字节合起来读。高地址的数字在高位。所以是3839


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值