栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言之自定义类型

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言之自定义类型

C语言之自定义类型
  • (一) 结构体
    • 结构体的声明
    • 结构体的自引用
    • 结构体成员的初始化
    • 结构体内存对齐
    • 修改默认对齐数
    • 结构体传参
    • 位段
  • (二) 枚举类型
  • (三) 联合体/共用体
    • 联合体的概念
    • 判断大小端
    • 联合体的大小

(一) 结构体 结构体的声明
struct Book
{
	char name[20];
	char author[20];
	int price;
}S1,S2;//全局变量
int main()
{
	struct Book S3;//局部变量
	return 0;
}

  在声明结构体的时候,也可以选择不完全声明

struct 
{
	char name[20];
	char author[20];
	int price;
}S1,S2;//全局变量
int main()
{
	//这里struct Book S3;就不可以使用了
	return 0;
}
结构体的自引用

  在数据结构中,存在线性表这一概念,线性表包括顺序表和链表,在内存中连续存储发方式叫线性表,串联数据的存储方式为链表,链表的在内存中的存储方式如下,一个结点包括数据域和指针域,分别存放数据和指向下一个结点。

  具体代码如下,

typedef struct Node//前方定义为了后方引用
{
	int data;
	struct Node* next;//结构体指针,指向下一个结点
}Node;
结构体成员的初始化
struct Student
{
	char name[20];
	int sorce;
}S1 = { "Zhangsan",67 }, S2 = {"Lisi",78};
int main()
{
	struct Student S3 = {"Wangwu",66};
	return 0;
}

  或者在结构体中嵌套结构体,

struct Sorce
{
	float math;
	float english;
}x1,x2;
struct Student
{
	char name[20];
	struct Sorce sorce;
}S1 = { "Zhangsan",{67.5,75.8} }, S2 = { "Lisi",{99.5,65.4} };
int main()
{
	printf("%s", S1.name);
	printf("%.2f %.2fn", S1.sorce.math, S1.sorce.english);
	return 0;
}
结构体内存对齐

  结构体在内存中对齐的规则如下,
  1.结构体第一个成员放在起始位置偏移量为0的地方。
  2.结构体从第二个成员开始,总是放在偏移量为一个对齐数的整数倍处。(这里的对齐数是指编译器默认的对齐数和变量自身大小)(在Linux编译器下,没有默认的对齐数,在VS环境下,对齐数是8)
  3.结构体的总大小必须是各个成员的对齐数中最大对齐数的整数倍。

#include 
struct S1
{
	char a;
	int b;
	char c;
};
int main()
{
	printf("%dn", sizeof(struct S1));
	return 0;
}

  如上例所示,输出为12,而不是简单的各变量内存相加。以VS2019为编译环境, 结构体中的对齐数如下。

  在内存中的存放情况如图所示,第一个变量为char类型的,大小占1个字节,第二个变量是int类型的,对齐数是4,所以从第四位开始,向后占4个内存空间,,最后一个是变量也是char类型的,占1个字节,开始位置是1的倍数即可,结构体的总大小要为4的整数倍,由0到11正好是12个字节,所以最终结果是12。

  当结构体中嵌套结构体时,所占内存又是多少呢?这里我们在追加一条关于结构体内存对齐的规则:4.如果嵌套了结构体,那么嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍(含嵌套结构体的对齐数),我们可以来看一个例子。

#include 
struct S1 
{
	char a;
	double b;
};
struct S2
{
	char c;
	struct S1 s;
	int d;
};
int main()
{
	printf("%dn", sizeof(struct S2));
	return 0;
}

  此时,嵌套结构体对应的对齐数如下,

  在内存中的存放情况如图所示,第一个char类型占一个字节,进入被嵌套的结构体,第二个也是char类型,占一个字节,然后是double类型,对齐数是8,占16个字节,随后是int类型对齐数是4,占4个字节,因为结构体中(含嵌套的结构体)最大字节数是8,所以结构体总的占内存是8的倍数,结果是32。

  为什么结构体的内存要对齐,有以下普遍认为的几个原因:
  1.平台原因(移植原因):
  不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2.性能原因:
  数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一-次访问。

修改默认对齐数

  修改默认对齐数代码指令#pragma pack(数字),内涵数字为默认对齐数,#pragma pack() 为默认对齐数销毁指令。

#include 
#pragma pack(1)//默认对齐数为1
struct S1
{
	char a;
	int b;
	char c;
};
#pragma pack() 
int main()
{
	printf("%dn", sizeof(struct S1));
	return 0;
}

  因为默认对齐数是1,根据上面的分析,最终打印结果为6。
  关于结构体内成员的偏移量可以参考文章利用offsetof计算起始偏移量

结构体传参
#include 
struct S1
{
	char a;
	int b[10];
};
void print(struct S1 t)
{
	for (int i = 0;i < 10 ;i++)
	{
		printf("%d ", t.b[i]);
	}
}
int main()
{
	struct S1 s = { 'x',{1,2,3,4,5,6,7,8,9,0} };
	print(s);
	return 0;
}

  上述例子为直接传参,创建的变量t把结构体中的数据全部拷贝。这时传递的结构体如果值过大,那么在内存中压栈系统开销比较大,效率比较低,除了这个之外,我们也可以通过结构体指针的方式传参,如下,

#include 
struct S1
{
	char a;
	int b[10];
};
void print(struct S1 *t)
{
	for (int i = 0;i < 10 ;i++)
	{
		printf("%d ", t->b[i]);
	}
}
int main()
{
	struct S1 s = { 'x',{1,2,3,4,5,6,7,8,9,0} };
	print(&s);
	return 0;
}

  这样通过传递地址来传参,效率比较高。

位段

  C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为"位段"。
  位段的成员必须是整型家族(int,unsigned int,signed int,char)且位段的成员后面必须有一个冒号和数字,数字代表二进制位。

#include 
struct S1
{
	int a : 2;//a占2个比特位
	int b : 4;//b占4个比特位
	int c : 10;//c占10个比特位
};

  位段的内存分配规则如下,
  1.位段的空间上按照需求以4个字节(int)或者1个字节(char)的方式来开辟的(1个字节=8个比特位)
  2.位段是不跨平台的,注重可移植的程序应避免使用位段。

#include 
struct S1
{
	char a : 2;
	char b : 4;
	char c : 5;
}s;
int main()
{
	s.a = 10;
	s.b = 5;
	s.c = 12;
	return 0;
}

  如图所示,以一个字节(8个比特位)为单位开辟空间,a,b,c在内存中所占字节数和赋值后的存入情况如下,

  最终的结果如下:   在内存中显示为: (二) 枚举类型

  可以被一一列举的对象就可以采用枚举类型,从第一个对象开始。默认值为0,往后依次加1。

enum Day
{
	Mon,//默认值为0
	Tue,//默认值为1
	Wed,//默认值为2
	Thu,//默认值为3
	Fri,//默认值为4
	Sat,//默认值为5
	Sun//默认值为6
};

  此时Day里为枚举常量,如果不想常量为默认值时,可以给其进行初始化。

enum Day
{
	Mon = 3,
	Tue = 5
};

  枚举常量在定义时进行初始化赋值,在 主函数中不能在做更改,但是我们可以设置枚举变量,给枚举变量赋枚举常量的值。

enum Day
{
	Mon,
	Tue,
	
};
int main()
{
	enum Day Thu = Mon;
	enum Day Thu = 5;//error不能把int类型的赋值给枚举类型
	printf("%d", Thu);
	return 0;
}
(三) 联合体/共用体 联合体的概念

  几个不同的变量共同占用一段内存的结构的自定义类型叫联合体(也叫共用体),定义如下。

#include 
union A
{
	int x;
	char y;
}n;
int main()
{
	printf("%pn", n);
	printf("%pn", n.x);
	printf("%pn", n.y);
	//三个地址相同,共用一块内存空间
	printf("%d", sizeof(n));
	//输出为4
	return 0;
}
判断大小端

  联合体还可以用来判断大端存储(从内存最高位字节地址上存储)还是小段存储(从内存最低位字节地址上存储),

#include 
union A
{
	int x;
	char y;
}n;
int main()
{
	n.x = 1;
	if (n.y == 0)
	{
		printf("大端");
	}
	else
	{
		printf("小端");
	}
	return 0;
}

  在内存中的存放方式如下,

联合体的大小

  因为联合体共用一段内存空间,所以联合体的大小至少是最大成员的大小,但是当最大成员的大小不是最大对齐数的整数倍时,需要对齐到最大对齐数的整数倍。

#include 
union A
{
	int x;
	char y[5];
}n;
int main()
{
	printf("%d", sizeof(n));//输出为8
	return 0;
}

  在联合体内部,int x占4个字节,对齐数为4;char y[5]占5个字节,最大对齐数是1,所以最终联合体的大小要是4的倍数,最终为8。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/290992.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号