自定义类型:结构体

本文详细介绍了C语言中结构体的声明、内存对齐规则、自引用概念,以及位段的使用,包括内存分配、跨平台问题和注意事项,强调了内存对齐对性能的影响和结构体传参的最佳实践。
摘要由CSDN通过智能技术生成

目录

1.结构体类型的声明

1.1结构体回顾

1.1.1结构的声明

1.1.2结构体变量的创建和初始化

1.2结构体的特殊声明

1.3结构体的自引用

2.结构体内存对齐(重量级)

2.1对齐规则

2.2为什么存在内存对齐?

2.3修改默认对齐数

3.结构体传参

4.结构体实现位段

4.1什么是位段

4.2位段的内存分配

4.3位段的跨平台问题

4.4位段的应用

4.5位段使用的注意事项


C语言中有内置类型(本身支持的),也有自定义类型(struct-结构体,enum-枚举,union-联合体)

1.结构体类型的声明

1.1结构体回顾

结构体是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量

1.1.1结构的声明
struct tag
{
	member - list;//成员-1个或者多个
}variable-list;//变量列表

举例:

struct Book
{
	char name[20];
	char author[20];
	float price;
	char id[19];
	//...
}b3, b4;//全局变量
struct Book b2;//全局变量
int main()
{
	struct Book b1;//局部变量
	struct Book arr[5];//数组
}
1.1.2结构体变量的创建和初始化
struct Book
{
	char name[20];
	char author[20];
	float price;
	char id[19];
	//...
};
int main()
{
	struct Book b1 = { "C语言","peng",10.0,"1001" };//局部变量初始化
	//也可以乱序:
	struct Book b2 = { .id = "1002",.author = "shen",.name = "C",.price = 12.9 };
}
1.2结构体的特殊声明

在声明结构体的时候,可以不完全的声明:

比如:

struct
{
	int a;
	char b;
	float c;
}x;
struct
{
	int a;
	char b;
	float c;
}a[20], * p;

上面的两个结构体在声明的时候省略掉了结构体标签(tag)——匿名结构体

在上面代码的基础上,p=&x;是不合法的

警告:

  • 编译器会把上面两个声明当作完全不同的两个类型,所以非法
  • 匿名的结构体类型,如果没有对结构类型重命名的话,基本上只能用一次

这个时候在main函数中出现struct s就会出现报错

需要在最后的;前面命名,即:

#include <stdio.h>
struct
{
	int a;
	char b;
	double c;
}s = { 12,'x',1.0 };
//struct
//{
//	int a;
//	char b;
//	float c;
//}a[20], * p;
int main()
{
	printf("%d %c %lf\n", s.a, s.b, s.c);//12 x 1.000000
	return 0;
}

给匿名结构体再起一个名字:

typedef struct
{
	char c;
	int i;
	double d;
}S;
int main()
{
	S s;
	return 0;
}

这个代码无意义

1.3结构体的自引用

在结构体中包含一个类型为该结构体本身的成员是否可以?

比如,定义一个链表的节点:

数据结构:数据在内存中的存储和组织的结构

数据结构:线性数据结构(顺序表、链表、栈、队列)、树形数据结构(二叉树)、图

struct Node
{
	int data;
	struct Node n;//这种写法是不合理的,找不到sizeof(struct Node),无限进行下去
};

这种写法不合理,下面进行改进:

正确的自引用方式:

struct Node
{
	int data;
	struct Node* n;//指针——自引用
};
typedef struct Node
{
	int data;
	//Node* next;//出现报错——还没有Node类型
    //匿名的结构体类型不能实现这种效果
	struct Node* next;
}Node;
int main()
{
	struct Node n1;
	Node n2;
	return 0;
}

2.结构体内存对齐(重量级)

我们已经掌握了结构体的基本使用,现在深入讨论一个问题:计算结构体的的大小

这涉及到一个热门考点:结构体内存对齐

2.1对齐规则

下面看一段代码:

#include <stdio.h>
struct S
{
	char c1;//1
	int i;//4
	char c2;//1
};
int main()
{
	struct S s = { 0 };
	printf("%zd", sizeof(s));//12
	return 0;
}

结果为什么是12?原因:

结构体的对齐规则:

1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处

对齐数=编译器默认的一个对齐数与该成员变量大小的较小值

-VS中默认的值为8

-Linux中gcc没有默认对齐数,对齐数就是成员自身的大小

3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍

4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

练习2:

struct S2
{
	char c1;//1  8——1
	char c2;//1  8——1(对齐数为1)
	int i;//4  8——4
};
int main()
{
	//struct S s = { 0 };
	struct S2 s2 = { 0 };
	printf("%zd", sizeof(s2));//8
	return 0;
}

练习3:

#include <stdio.h>
struct S3
{
	double d;//8  8——8
	char c;//1  8——1
	int i;//4  8——4
};
int main()
{
	struct S3 s3 = { 0 };
	printf("%zd", sizeof(s3));//16
	return 0;
}

练习4:

#include <stdio.h>
struct S3
{
	double d;//8  8——8
	char c;//1  8——1
	int i;//4  8——4
};
struct S4
{
	char c1;//1  8——1
	struct S3 s3;//16  8——8
	double d;//8  8——8
};
int main()
{
	struct S4 s4 = { 0 };
	printf("%zd", sizeof(s4));//size_t
	//32
	return 0;
}

2.2为什么存在内存对齐?

1.平台原因(移植原因)

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定的数据,否则抛出硬件异常

2.性能原因

数据结构(尤其是栈)应该尽可能的在自然边界上对齐,原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了,否则,我们可能需要执行两次,因为对象可能被分在两个8字节内存块中(浪费时间)

总体来说:结构体的内存对齐是拿空间来换取时间的做法

那在设计结构体的时候,我们既要满足对齐,又要节省空间:

基于以上两个例子,我们可以得出让占用内存小的成员尽量集中在一起

2.3修改默认对齐数

结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数(一般改成2的次方数)

#pragma这个预处理指令,可以改变编译器的默认对齐数:

#include <stdio.h>
#pragma pack(1)//设置默认对齐数
struct S
{
	char c1;//1  1——1
	int i;//4  1——1
	char c2;//1  1——1
};
#pragma pack()//取消设置的对齐数,还原为默认
int main()
{
	//输出的结果是?
	printf("%d\n", sizeof(struct S));//6
	return 0;
}

3.结构体传参

#include <stdio.h>
struct S
{
	int arr[1000];
	int n;
	double d;
};
void print1(struct S tmp)//造成时间和空间上的浪费
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", tmp.arr[i]);
	}
	printf("%d ", tmp.n);
	printf("%lf", tmp.d);
}
void print2(const struct S* ps)//传的是地址,显然print2更好,效率更高
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("%d ", ps->n);
	printf("%lf", ps->d);
}
int main()
{
	struct S s = { {1,2,3,4,5},100,3.14 };
	//print1(s);//1 2 3 4 5 100 3.140000
	print2(&s);//1 2 3 4 5 100 3.140000
	return 0;
}

原因:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销

如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降

结论:

结构体传参的时候,要传结构体的地址

4.结构体实现位段

位段基于结构体

4.1什么是位段

专门用来节省内存

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

  1. 位段的成员必须是int ,unsigned int或signed int,在C99中位段成员的类型也可以选择其他类型
  2. 位段的成员名后边有一个冒号和一个数字

比如:

#include <stdio.h>
struct S
{
	int _a : 2;//_a仅仅占2个比特位(不再是32个,节省了30个)
	int _b : 5;//只占5个比特位
	int _c : 10;//只占10个比特位
	int _d : 30;//位段式的结构
	//位——2进制位

};
int main()
{
	printf("%zd", sizeof(struct S));//8
	return 0;
}

4.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", sizeof(s));//3
	return 0;
}
4.3位段的跨平台问题
  1. int位段被当成有符号数还是无符号数是不确定的
  2. 位段中最大的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)(32位和64位机器上int的长度是4个字节,16位机器上是2个字节)
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用是不确定的

总结:

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

因此根据不同的平台写不同的代码

4.4位段的应用

下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这里使用位段,能够实现想要的结果,也节省了空间,这样网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的

4.5位段使用的注意事项

位段的几个成员共用同一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的

所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	struct A sa = { 0 };
	//scanf("%d", &sa._b);//这是错误的
	//正确的示范
	int b = 0;
	scanf("%d", &b);
	sa._b = b;
	return 0;
}

  • 17
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园建设方案旨在通过融合先进技术,如物联网、大数据、人工智能等,实现校园的智能化管理与服务。政策的推动和技术的成熟为智慧校园的发展提供了基础。该方案强调了数据的重要性,提出通过数据的整合、开放和共享,构建产学研资用联动的服务体系,以促进校园的精细化治理。 智慧校园的核心建设任务包括数据标准体系和应用标准体系的建设,以及信息化安全与等级保护的实施。方案提出了一站式服务大厅和移动校园的概念,通过整合校内外资源,实现资源共享平台和产教融合就业平台的建设。此外,校园大脑的构建是实现智慧校园的关键,它涉及到数据中心化、数据资产化和数据业务化,以数据驱动业务自动化和智能化。 技术应用方面,方案提出了物联网平台、5G网络、人工智能平台等新技术的融合应用,以打造多场景融合的智慧校园大脑。这包括智慧教室、智慧实验室、智慧图书馆、智慧党建等多领域的智能化应用,旨在提升教学、科研、管理和服务的效率和质量。 在实施层面,智慧校园建设需要统筹规划和分步实施,确保项目的可行性和有效性。方案提出了主题梳理、场景梳理和数据梳理的方法,以及现有技术支持和项目分级的考虑,以指导智慧校园的建设。 最后,智慧校园建设的成功依赖于开放、协同和融合的组织建设。通过战略咨询、分步实施、生态建设和短板补充,可以构建符合学校特色的生态链,实现智慧校园的长远发展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值