c语言 结构体自定义类型

124 阅读5分钟

结构体

  • 结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
// 匿名结构体类型 没有标签 只能用一次
struct
{
	char c;
	int i;
	float c;
}s;

// struct 结构体关键字
struct Book
{
	// 描述书
	char name[20];
	int price;
	char id[12];

}b4, b5;// b4, b5 全局变量

int main()
{
	// 局部变量
	struct Book b1;
	struct Book b2;

	return 0;
}

// ----------------------------------------------------------------------

struct S
{
	char c;
	int i;
};

struct B
{
	struct S s2;
	int i;
};

int main()
{
	// 结构体初始化
	struct S s1 = {'w',111};

	struct B b1 = { {'b',66},99};

	printf("%d \n", s1.i);

	printf("%d \n", b1.i);

	return 0;
}

  • 结构体内存大小的计算
  • 结构体的内存对齐规则
  • 第一个成员在与结构体变量偏移量为0的地址处
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  • 对齐数=编译器默认的一个对齐数与该成员大小的较小值。
  • vS中默认的值为8
  • Linux 没有对齐数的概念
  • 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
  1. 结构体的第一个成员,放在结构体变量在内存中存储位置的0偏移处开始。
  2. 从第二个成员往后的所有成员,都放在一个对齐数(成员的大小和默认对齐数的较小值)的整数倍的地址处。
  3. 结构体的总大小是结构体的所有成员名的对齐数中最大的那个对齐数的整数倍。

z999.png

  • vs上 默认对齐数可以修改掉
  • #pragma pack(2)默认对齐数修改成 2
  • 限制了最大对齐数
# pragma pack(2)
struct W
{
	char c1;
	int aa;
	double dd;
};
# pragma pack()

int main()
{
	struct W s1 = {0};

	printf("%d", sizeof(s1));// 14

	return 0;
}


  • 结构体传参 传地址
  • 传结构体 效率不高
  • 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
struct W
{
	char c1;
	int aa;
	double dd;
};

void print1(struct W s1)
{
	printf("%d \n", s1.aa);
}

void print2(struct W* s2)
{
	printf("%d \n", s2->aa);
}

int main()
{
	struct W s1 = {'w',66,99.99};

	print1(s1);// 传结构体
	print2(&s1);// 传地址

	return 0;
}
  • 结构体实现位段的能力
  • 什么是位段?
  • 位段的声明和结构是类似的,有两个不同︰
  • 位段的成员必须是int.unsigned int或signed int。
  • 位段的成员名后边有一个冒号和一个数字。

位段的内存分配:

  • 位段的成员可以是int unsigned int signed int或者是char(属于整形家族)类型。
  • 位段的空间上是按照需要以4个字节( int)或者1个字节( char)的方式来开辟的。
  • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
  • 位段设置不能超过 int 类型的范围
struct W
{
	// 4 个字节 4 * 8 = 32  -  32个bit位
	int _a : 2;// _a 成员占2个bit位 
	int _b : 5;// _b 成员占5个bit位
	int _c : 10;// _c 成员占10个bit位
	// 还剩 15个字节 _d 要30个字节
	// 4 个字节 
	int _d : 30;// _d 成员占30个bit位
};

int main()
{
	printf("%d", sizeof(struct W));
	// 所以 sizeof 是 8 个字节
	return 0;
}

位段的跨平台问题:

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

总结:

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

枚举

  • 枚举顾名思义就是— —列举。
  • 把可能的取值一 一列举。
  • 比如我们现实生活中∶
  • 一周的星期一到星期日是有限的7天,可以— —列举。
  • 性别有:男、女、保密,也可以——列举。
  • 月份有12个月,也可以——列举,这里就可以使用枚举了。
  • 枚举类型的定义
// 声明枚举类型
enum Color
{
	// 枚举类型的可能取值
	// 常量
	RED,// 0
	GREEN,// 1
	BLUE  // 2 默认增长 1
};

int main()
{
	// 
	enum Color b = BLUE;

	return 0;
}

我们可以使用#define定义常量,为什么非要使用枚举?枚举的优点︰

  • 增加代码的可读性和可维护性
  • 和#define定义的标识符比较枚举有类型检查,更加严谨。
  • 防止了命名污染(封装)
  • 便于调试
  • 使用方便,一次可以定义多个常量
enum Color
{
	RED=1,
	BLUE,
	GREEN
};


int main()
{
	int input = 0;

	do {
		printf("输入1-3 》:\n");
		scanf("%d", &input);
		switch (input)
		{
		case RED:
			printf("RED \n");
			break;
		case BLUE:
			printf("BLUE \n");
			break;
		case GREEN:
			printf("GREEN \n");
			break;
		default:
			break;
		}
	} while (input);
	return 0;
}