【Amazing! C】自定义类型:结构体,枚举,联合

目录

一、结构体

1.1 结构体类型的声明

1.2 特殊声明

1.3 结构的自引用

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

1.5 结构体内存对齐

1.6 修改默认的对齐数

1.7 结构体传参

二、位段

2.1 位段的定义

2.2 位段的内存分配

三、枚举

3.1 枚举类型的定义

3.2 枚举的优点

四、联合体(共用体)

4.1 联合体的定义

4.2 联合体大小的计算

4.3 联合体的应用


一、结构体

        接之前的【初始结构体】。

1.1 结构体类型的声明

struct tag        //tag	-	自己设定
{
member-list;      //成员列表
}variable-list;   //变量列表,可有可无

        基于此,我们可以对复杂对象进行描述,代码如下: 

#include<stdio.h>

struct Student
{
	char name[20];
	int age;
	char sex[5];
	float score;
}s1,s2,s3;//s1,s2,s3是三个结构体变量	-	全局变量

struct Book
{
	char name[20];
	char author[12];
	float price;
};

int main()
{
	struct Student s4, s5, s6;//s4,s5,s6也是三个结构体变量	-	局部变量
	return 0;
}

1.2 特殊声明

        就是在声明结构体的时候,可以把结构体的名字省略掉,即匿名结构体类型。

写法1:

struct Book
{
    char name[20];
    char author[12];
    float price;
}b1;        //使用结构体类型的时候创建了b1

写法2:

struct//在创建结构体类型的时候顺带创建了b1,这时就可以把book删掉,就是匿名结构体类型,但只能用一次
{
	char name[20];
	char author[12];
	float price;
}b1;

        注意:在创建匿名结构体的时候,不能省略变量名。

易混淆的点:

        在刚开始学习结构体的时候,总是认为结构体 struct 后边已经加上 Book ,误认为 Book 是变量名,后边 b1 又重命名。是因为之前形成了固有思维,例如:认为创建整型变量int a,就是类型+变量名,然后带入到结构体,就认为struct Book也是类型+变量名,这是一个误区。实际上,struct是声明结构体的关键字结构体类型名Bookb1结构体类型变量

        所以,我们可以写一个代码证明:

#include<stdio.h>

struct
{
   	char name[20];
	char author[12];
	float price;
}b;

struct
{
   	char name[20];
	char author[12];
	float price;
}*p;

int main()
{
    p = &b;
    return 0;
}

        这种写法正确吗?虽然结构体一模一样,但是编译器仍然认为是两种结构体类型,当放在等号两边时,编译器认为结构体的类型不一样。

1.3 结构的自引用

        在结构中包含一个类型为该结构本身的成员是不可以的。例如:

struct Node
{
    int data;
    struct Node next;
};        //如果可以,那sizeof(struct Node)是多少

        那么结构体是如何自引用呢?比如:链式数据,在内存上是不连续的,但是可以通过n1找到n2的地址,n2->n3,n3->n4...

        正确的自引用方式如下:

struct Node
{
    int data;
    struct Node* next;
};

        但是,值得注意的是:

typedef struct
{
	int data;
	Node* n;
}Node;

        这样也是不行的!
        因为对上述的结构体重命名为Node,也就是说这个结构体类型是完整的可用的才能产生Node,而这个Node还没有产生就要用。

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

#include<stdio.h>

struct Point
{
    int x;
    int y;
}p1 = { 1, 2 };

struct Point p3 = { 4, 5 };

struct Stu
{
	char name[15];
	int age;
};

int main()
{
    int a = 10;
    int b = 5;
    struct Stu p2 = { a, b };

    struct Stu s1 = { "zhangsan", 18 };
    //struct Stu s1 = { 18, "zhangsan" };//err
    struct Stu s2 = { .age = 18, .name = "zhangsan" };
    printf("%s %d", s.age, s.name);
    return 0;
}

        当结构体比较复杂时:

#include<stdio.h>

struct Point
{
    int x;
    int y;
}p1 = { 1, 2 };

struct Node
{
    int data;
    struct Point p;
    struct Node* next;
};

int main()
{
	struct Node n = { 100,{20,21},NULL };
	printf("%d x=%d y=%d\n", n.data, n.p.x, n.p.y);//空指针指向的内容无法访问,所以也无法打印
	return 0;
}

1.5 结构体内存对齐

        我们在1.3中抛出一个问题,sizeof(struct Node)是多少?

#include<stddef.h>

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

int main()
{
	printf("%d\n", sizeof(struct S1));        //12
	printf("%d\n", sizeof(struct S2));        //8
	printf("%d\n", offsetof(struct S1, c1));  //0
	printf("%d\n", offsetof(struct S1, i));   //4
	printf("%d\n", offsetof(struct S1, c2));  //8
	//offsetof//宏 - 计算结构体成员相较于起始位置的偏移量

	return 0;
}

结构体对齐规则如下:

  1. 第一个成员在与结构体变量偏移量为0的地址处;
  2. 其他成员变量要对其到某个数字(对齐数)的整数倍处(vs中默认值是8);注:对齐数=编译器默认的一个对齐数与该成员大小的较小值
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数,所有变量类型最大者与默认对齐数参数取最小)的整数倍;
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

        在设计结构体时,我们既要满足对齐,又要节省空间,那么就要让占用空间小的成员尽量集中在一起。

1.6 修改默认的对齐数

        #pragma这个预处理指令,可以修改默认的对齐数。

#pragma pack(1)//设置对齐数为1
struct S2
{
	char c1;	//1
	int i;		//4
	char c2;	//1
};
#pragma pack()//取消对齐数设置,并恢复到8

int main()
{
	printf("%d\n", sizeof(struct S2));//6
	return 0;
}

1.7 结构体传参

struct S
{
	int data[1000];
	int num;
};

void print1(struct S t)
{
	printf("%d %d %d %d\n", t.data[0], t.data[1], t.data[2], t.num);
}
void print2(const struct S* ps)//整体效率高一些,但为了防止ps对原结构体的修改造成的不安全,需要加const并放在*左边
{
	printf("%d %d %d %d\n", ps->data[0], ps->data[1], ps->data[2], ps->num);
}

int main()
{
	struct S s = { {1,2,3},100 };
	print1(s);//传值调用
	print2(&s);//传址调用
	return 0;
}

        注意:结构体传参要传结构体的地址。

        函数传参时,参数是需要压栈的,会有时间和空间上的系统开销。如果传递一个结构体对象时,结构体过大,参数压栈的系统开销比较大,所以导致性能的下降。

二、位段

2.1 位段的定义

        位段的出现就是为了节省空间,位段的位就是二进制位。位段的声明和结构相似,有两个不同:

  1. 位段的成员必须时intunsigned intsigned in
  2. 位段的成员名后边有一个冒号一个数字

        例如:

struct A
{
	int _a : 2;		//:2	-	a占用2个bit位的空间
	int _b : 5;		//:5	-	b占用5个bit位的空间
	int _c : 10;
	int _d : 30;
};

        结构体创建时,有一些结构体成员的取值范围可能有限,我们假设 _a 成员的赋值只可能是0,1,2,3,0的二进制序列是001的二进制序列是012的二进制序列是103的二进制序列是11,我们发现只需要两个bit位就能放下来。而我们没有采用位段的形式,而是用 int,我们发现一个整型是 4 个字节,32个bit位,但 _a 只能用两个bit位,那么剩下的30个bit位就闲置了。

        上述代码如果不适用位段,sizeof(struct A) == 16 个字节,使用位段占多少字节呢?

2.2 位段的内存分配

位段的内存分配规则

  1. 位段的成员可以是int、unsigned int、signed int或者char(属于整型家族)类型;
  2. 位段的空间上是按照需要以4个字节 int 或者1个字节 char 的方式来开辟;
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应避免使用位段。

        以下代码主要是VS对位段的处理:

#include<stdio.h>

struct S        //定义了结构体类型
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	printf("%d\n", sizeof(struct S));//3

	return 0;
}

        C中没有规定,所以我们假设从小端开始存储,即从左往右:

0 | 0000 | 000        0000 | 0000        0000 | 0000
  |   b  |  a                  c                  d

10    -    0000 1010    -    a占3个bit位,所以拿出010放在上边的a处
12    -    0000 1100    -    b占4个bit位,所以拿出1100放在上边的b处
3     -    0000 0011    -    c占5个bit位,所以拿出00011放在上边的c处
4     -    0000 0100    -    d占4个bit位,所以拿出0100放在上边的d处
放上以后对应的是:
      6     2           0      3           0      4 

        运行结果如下:

三、枚举

3.1 枚举类型的定义

enum Day//enum是枚举的关键字 Day是我们定义枚举类型的名字
{
	//枚举的可能取值
	Mon,
	Tues,
	wed,
	thur,
	fri,
	sat,
	sun
};

enum Color
{
	RED,
	GREEN,
	BLUE
};

int main()
{
	printf("%d\n", RED);    //默认值为0.但在最开始时可以根据需求进行设定的
	printf("%d\n", GREEN);  //默认值为1
	printf("%d\n", BLUE);   //默认值为2

	enum Color color = BLUE;//color是枚举变量
    color = (Color)0;       //想赋值0,需要强转为枚举类型
	printf("%zd\n", sizeof(color));//4		zd打印sizeof

	return 0;
}

         枚举只能在给定的参数列表中赋值。

3.2 枚举的优点

  1. 可以增加代码的可读性和可维护性。
  2. 和#define定义的标识符比较,枚举有类型检查,更加严谨。
  3. 便于调试。
  4. 使用方便,一次可定义多个常量。

四、联合体(共用体)

4.1 联合体的定义

        联合体是一种特殊的自定义类型,关键字:union。这种类型定义的变量也包含一系列成员,特征是这些成员公用同一块空间。证明如下:

union Un
{
	char c;//1
	int i;//4
};
int main()
{
	union Un un;
	//printf("%d\n", sizeof(un));//4

	printf("%p\n", &un);//0055F944
	printf("%p\n", &(un.c));//0055F944
	printf("%p\n", &(un.i));//0055F944

	//所以同一时间只能使用一个
	return 0;
}

        基于联合体这个性质,可以将判断当前计算机是大端还是小端存储模式的代码进行改写:

#include<stdio.h>

int check_sys()
{
    Union Un
    {
        char c;
        int i;
    }u;
    
    u.i = 1;
    return n.c;
}

int main()
{
    int ret = check_sys();
   	if (ret == 1)
	{
		printf("小端\n");
	}
	else
    {
		printf("大端\n");
    }
    return 0;
}

4.2 联合体大小的计算

        联合体的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。比如:

#include<stdio.h>

union Un
{
	char c[5];//5,但他的对齐数应该按char来算,所以相比最大对齐数是5
	int i;//4
};

int main()
{
	printf("%zd\n", sizeof(union Un));//8
	return 0;
}

4.3 联合体的应用

        某些成员不会在同一时间使用。比如:礼品兑换单有三种商品:图书、杯子、衬衫,每种商品都有库存量、价格、商品类型和相关的其他信息。
        图书:书名、作者、页数
        杯子:设计
        衬衫:设计、可选颜色和尺寸

        不管描述哪一个,都会有空间浪费了;此外,不可能一个商品既是图书又是杯子,所以在同一时间只有一个信息使用。

struct gift_list
{
	//公共属性
	int stock_number;
	double price;
	int item_type;
	//特殊属性
	char title[20];
	char author[20];
	int num_page;

	char design[30];
	int colors;
	int sizes;
};

struct gift_list
{
	int stock_number;
	double price;
	int item_type;

	union
	{
		struct
		{
			char title[20];
			char author[20];
			int num_page;
		}book;
		struct
		{
			char design[30];
		}mug;
		struct
		{
			char design[30];
			int colors;
			int sizes;
		}shirt;
	}item;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值