C语言--自定义类型详解(结构体+枚举+联合)

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

结构体

结构体类型的声明

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 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;
      };
      
    • #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
      }
      

百度笔试题:

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

考察: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)) ;
}

修改默认对齐数

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;
}

结构体的传参

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);
}

位段

讲的书比较少,C和指针提及。

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

  1. 位段的成员必须是int,unsigned in,signed int
  2. 位段的成员名后边有一个冒号和一个数字

位段–二进制位

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
}
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. 位段的成员可以是int,unsigned int,signed int,或者char(属于整型家族)类型
  2. 位段的空间上是按照需要以4个字节(int)或者1个字节(char)来一个个开辟
  3. 位段涉及很多不确定因素,位段不跨平台的。注意可移植程序避免。
  4. VS下一个一个给,剩下不够扔掉。从右边开始。C标准没规定。

在这里插入图片描述

位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总结

跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

位段的应用

用于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");   
		 }
		}

枚举的优点

我们可以使用#define来定义,为什么要枚举

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

每个是整型变量,大小固定是整型字节。

枚举的使用

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;                 
	       }  
	    }
}

联合(共用体)

这种类型定义的变量包含一系列的成员,特征是这些成员公用同一块空间

联合类型的定义

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));
}

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("大端");    
		}
}

联合大小的计算

  • 联合的大小至少是最大成员的大小
  • 当最大成员大小不是最大对齐数的整数倍的时候,要整数对齐
union Un1{
    
    	
		char c[5];//5 1    
		int i;//4 4
}
Union Un2{
    
        
		short c[7]; //14 2    
		int i;//4 8 4
}

练习–通讯录

  1. 存放1000个人的信息
    1. 信息:名字+性别+年龄+电话+住址
  2. 增加联系人
  3. 删除联系人
  4. 修改联系人
  5. 查找联系人
  6. 排序联系人
  7. 存文件

##练习

下面代码的结果是( )

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;
}

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;
}

注意这是小端机子。
所以s->i[0]=0x39。放在左边(即低地址)
s->i[1]=0x38。放在右边(即高地址)
两个字节合起来读。高地址的数字在高位。所以是3839
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zstuyyyyccccbbbb/article/details/120404949