当前位置:首页 » 《关注互联网》 » 正文

自定义类型:结构体,枚举,联合(C语言)_Solitudefire的博客

27 人参与  2022年01月13日 13:08  分类 : 《关注互联网》  评论

点击全文阅读


自定义类型:结构体,枚举,联合(C语言)

  • 前言
  • 结构体
    • 结构体类型的声明
    • 结构体的自引用
    • 结构体变量的定义和初始化
    • 结构体内存对齐
    • 结构体传参
    • 结构体实现位段(位段的填充&可移植性)
      • 位段的跨平台问题
  • 枚举
    • 枚举类型的定义
    • 枚举的优点
    • 枚举的使用
  • 联合
    • 联合类型的定义
    • 联合的特点
    • 联合大小的计算

前言

在C语言中有一些内置的类型:整型(int long short……),当然也有自定义类型,这次我们介绍关于常见的自定义类型(结构体、枚举、联合)。

结构体

结构体我们在前面简单介绍过,是用来描述复杂对象的,比如:人,我们在描述人时,不能仅仅通过整型或者浮点型来描述所谓的人,我们需要通过一系列的类型来描述人这个复杂对象,比如:年龄,身高,体重,地址,电话等等,因此我们需要自己创建一个类型,这个类型就是结构体。

结构体类型的声明

我们如何申明结构体呢?

struct tag
{
	member-list;
}variable-list;

struct是结构体关键字,tag是标签名,member-list是成员列表,variable-list是变量列表。我们来看例子:

struct Book
{
	char name[20];
	char author[20];
	int price;
}B1;

我们创建了一个Book的结构体,结构体中有name[20],author[20],price三个成员,B1就是一个创建的变量。

结构体的自引用

什么是结构体的自引用?自己类型的对象,要找到自己内部同一个类型的另一个对象,就叫自引用。在数据结构中我们会学到链表,链表的节点就是采用的结构体的自引用。

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

节点会分为两个区域,数据域和指针域,数据域是用来存放数据的,指针域用来存放地址。date就是存放数据,next就是存放指针,而指针的类型就是struct Node这个类型。

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

结构体变量的定义和初始化相对来说就比较的简单:

struct Point
{
	int x;
	int y;
}p3 = { 5,6 }, p4 = {7,8};
struct Point p2 = {1,2};
int main()
{
	struct Point p1 = {3,4};
	return 0;
}

我们创建了4个变量:p1,p2,p3,p4,这4个变量都是struct Point类型的,区别就在于p1是局部变量,而p2,p3,p4是全局变量。

结构体内存对齐

我们先看一个题目:

#include<stdio.h>
struct S1
{
	char c1;
	int a;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int a;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

打印结果是什么呢?
有同学会说,char是一个字节,int是两个字节,s1和s2就是int前后位置发生了变化,所以打印结果都是6。那么真的是这样嘛?
在这里插入图片描述
我们看到打印结果都不是6,为什么呢?这就涉及到了结构体内存对齐的知识。
我们先介绍关于结构体内存对齐的规则:

  1. 结构体的第一个成员永远放在结构体起始位置偏移量为0的位置。
  2. 结构体成员从第二个开始,总是放在偏移量为一个对齐数的整数倍处。
  3. 结构体的总大小必须是各个成员的对齐数中最大那个对齐数的整数倍。
    我们先介绍一下两个名词:偏移量和对齐数。
  4. 如果嵌套了结构体的情况,被嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍。
    偏移量:
    假设这是我们的内存单元:
    在这里插入图片描述
    我们假设结构体变量从0开始创建,那么起始位置偏移量为0还是0位置。也就是说从0开始放入。偏移量为4也就是4的位置
    在这里插入图片描述
    对齐数:
    对齐数是编译器默认的对齐数和变量自身大小的较小值。
    举个例子:
    在VS中默认对齐数是8,如果第二个成员是int型大小为4,那么较小值就是4,那么真正的对齐数就是4。
    现在我们来看例题:
    我们看如果创建S1类型的变量所占的内存:
    在这里插入图片描述
    我们从0位置开始存放,第一个成员是char,char是占一个字节,那么我们将地址“0”分配给char类型,第二个成员是int类型,那么我们用VS编译器的对齐数跟4比较(我用的是VS编译器),4是较小值,也就是说,int类型放在偏移量为4的倍数的位置,1不是,2不是,3不是,4是的,所以放在地址为“4”的位置并且放4个单元内存。第三个成员是char,跟8比较,较小值是1,也就是char放在偏移量为1的倍数的位置,8是1的倍数,所以char放在地址“8”的位置,但是并不是struct S1就占9个字节,还有第三个条件,我们将各个成员比较,较大者为int型,也就是4个字节,那么,总大小必须是4的倍数,也就是12个字节。如果听懂了的话可以自己分析一下struct S2。

结构体传参

我们先看一个例子:

#include<stdio.h>
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);
}
int main()
{
	struct S s = { {1,2,3,4,5,6,7,8,9,10},100 };
	print1(s);
	return 0;
}

我们利用print1()函数打印了s的信息,我们可以看到,我们是将s的值传给了tmp,也就是说我们又重新创建了一个struct S类型的结构体,然后打印重新创建结构体的内容,这样就会导致空间的浪费,那么还有什么方法呢?

#include<stdio.h>
struct S
{
	int data[1000];
	int num;
};
void print2(struct S *ps)
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ps->data[i]);
	}
	printf("\nnum=%d\n", ps->num);
}
int main()
{
	struct S s = { {1,2,3,4,5,6,7,8,9,10},100 };
	print2(&s);
	return 0;
}

print2()函数采用的是传结构体的地址,这样就只需要创建一个地址空间来存放目标的地址,然后在打印结构,效率就会远大于print1()函数,其实有点类似于传值和传址。所以在遇到结构体时一般采用传址操作,因为结构体包含很多类型,往往空间会比较大。

结构体实现位段(位段的填充&可移植性)

什么是位段?

  1. 位段的成员必须是int、unsigned int或signed int(char类型也可以)。
  2. 位段的成员名后边有一个冒号和一个数字。

我们来看一个例子:

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

这就是一个位段,那么位段是干什么用的呢?位段的作用其实就是用来节省空间的,“2”是分配给"_a"2个bit位,同理"5"是5个bit位,那么具体是如何分配的呢?以struct A为例:当我们创建时,编译器先看到int _a,那么开辟4个字节(32个bit位),_a需要2个,_b需要5个,_c需要10个,此时还剩15个bit位,无法分配给_d,因此在开辟4个字节,给_d使用因此,此时struct A的总大小位8个字节。
通过例子总结出位段的内存分配

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

位段的跨平台问题

位段是不具备跨平台的。

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

枚举

枚举顾名思义就是一一列举。把可能的取值一一列举。比如我们现实生活中:一周的星期一到星期日是有限的7天,可以一一列举。性别有:男、女、保密,也可以一一列举。月份有12个月,也可以一一列举颜色也可以一一列举。这里就可以使用枚举了。一般枚举是枚举有限多个,无限多个就不会采用枚举了

枚举类型的定义

我们看例子:

enum Day
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
enum Sex
{
	MALE,
	FEMALE,
	SECRET
};
enum Color
{
	RED,
	GREEN,
	BLUE
};

以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。这些可能取值都是有值的,默认从0开始,一次递增1。当然也可以自定义枚举常量的数值。

enum Color
{ 
 RED=1, 
 GREEN=2, 
 BLUE=4 
}; 

枚举的优点

我们在前面常量中学过使用#define可以定义常量,那么为什么还要有枚举常量呢?
枚举的优点:

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

枚举的使用

enum Color
{
	RED,
	GREEN,
	BLUE
};
int main()
{
	enum Color c = GREEN;
	if (c == GREEN)
	{
		printf("绿色\n");
	}
	return 0;
}

枚举的使用就是利用枚举的常属性进行逻辑的运算,当然这只是一方面。通过枚举的使用可以使代码的可读性提高,维护起来也相对方便。

联合

联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。

联合类型的定义

我们看例子:

union Un
{
	char c;
	int i;
};

这就是联合体的定义,我们可以看到联合体跟结构体很像但是,在内存分配上是不一样的。这就是联合体的特点。

联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。什么意思呢?我们拿上面的例子举例:

#include<stdio.h>
union Un
{
	char c;
	int i;
};
int main()
{
	union Un u = { 0 };
	printf("%d\n", sizeof(u));
	printf("%p\n", &u);
	printf("%p\n", &(u.c));
	printf("%p\n", &(u.i));
	return 0;
}

在这里插入图片描述
通过打印我们可以看到,联合体大小为4,并且每个成员的地址都是联合体的地址,这就意味着每个成员是共用内存的。
在这里插入图片描述
所以当c和i相互制约,当两者其中改变时(c所在的内存发生改变),会影响到另一个。

联合大小的计算

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

比如:

#include<stdio.h>
union Un1
{
	char c[5];
	int i;
};
union Un2
{
	short c[7];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
	return 0;
}

我们看Un1,char c[5]占5个字节,int i占4个字节,所以Un1为5个字节,但是第一个是char类型,对齐数是1;第二个是int,对齐数是4,最大对齐数是4,所以Un1大小应为4的倍数,所以Un1大小为8。同理Un2,short c[7]占14个字节,int i占4个字节,所以Un2为14个字节,但是short对齐数为2,int对齐数为4,所以Un2应为4的倍数,所以Un2为16。
在这里插入图片描述


关于自定义类型涉及的内容就是这么多了,希望能给各位一些帮助,如果有什么建议可以私信,我也会及时改正,谢谢大家

点击全文阅读


本文链接:http://zhangshiyu.com/post/33175.html

枚举  结构  类型  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1