admin管理员组

文章数量:1794759

c语言——自定义类型:结构体

1.结构体类型的声明

代码语言:javascript代码运行次数:0运行复制
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

//struct:关键字 tag:自定义标签名(可随意修改)
struct tag
{
	//成员(一个或者多个,但是不能没有)
	char name[20];
	int age;
}variable_list;//变量列表

2.结构体变量的创建与初始化

结构的成员可以是标量、数组、指针,甚至是其他结构体。

初始化:定义变量的同时赋初值。

代码语言:javascript代码运行次数:0运行复制
struct Stu
{
	char name[20];
	int age;
	char sex[5];
};

int main()
{
	//按照结构体成员的顺序初始化
		struct Stu s = { "张三",20,"男" };//初始化
		printf("name:%s\n", s.name);
		printf("age:%d\n", s.age);
		printf("sex:%s\n", s.sex);
		return 0;
}

2.1结构体的特殊声明 

在声明结构体的时候,可以不完全的声明,也就是匿名结构体类型。

比如:

代码语言:javascript代码运行次数:0运行复制
struct
{
	int a;
	char b;
	float c;
}x;

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

在上面两个代码的基础上,我们是否可以认为下面的代码是合法的?

代码语言:javascript代码运行次数:0运行复制
p=&x

答案是不合法的,因为:

1.编译器会把上面的两个声明当成两个完全不同的两个类型,所以是非法的。

2.匿名的结构体类型如果没有对结构体进行重命名的话,基本上只能使用一次。

那么解决这个问题的最好办法就是定义结构体的时候不使用匿名结构体


3.结构体成员访问操作符

在结构体中,有  (. ) 和  (->) 两种运算符,其中 (.)  运算符是用来直接访问结构体或者联合体变量的成员,而(->)是用于通过指针来访问指向结构体或者联合体的成员


4.结构体内存对齐

首先了解一下对齐规则:

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

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

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

较小值看其编译器:在vs中默认值一般为8,而linux中gcc没有对齐数,对齐数就是成员自身大小

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

4.如果是嵌套结构体,那么嵌套的结构体成员对齐到自己成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍处

代码语言:javascript代码运行次数:0运行复制
//练习1
struct S1
{
 char c1;//1 8 1 小
 int i;//4 8 4 最大对齐数
 char c2;//1 8 1 小
};
printf("%d\n", sizeof(struct S1));//12
//练习2
struct S2
{
 char c1;//1 8 1 小
 char c2;//1 8 1 小
 int i;//4 8 4 最大对齐数
};
printf("%d\n", sizeof(struct S2));//8

 char:1个字节      int:4个字节       float:4个字节      double:8个字节   short:2个字节      long:8个字节

longlong:8个字节  

图中可以看到第一个成员放在起始地址为0的地址处,而第二个int类型是最大对齐数为4,因为1.2.3均不是4的倍数,所以从类型的倍数开始存放,可以看到只需要6个字节大小空间就能完成,但是却使用了12个字节的空间,浪费了大量的空间,总体来说,这是一种以空间换时间的做法 

那么在设计结构体的时候,我们既要满足对齐,又要节省空间,该如何做到呢?

答案就是:把占用空间小的成员尽量集中在一起

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

代码语言:javascript代码运行次数:0运行复制
#pragma pack(1)//设置默认对齐数为1
struct S
{
    char c1;
    char c2;
    int i;
};
#pragma pack()//取消设置的对齐数,还原默认

#pragma pack(1)//设置默认对齐数为1

#pragma pack()//取消设置的对齐数,还原默认

结构体在对齐方式不合适的时候,可以自己修改


5.结构体传参

代码语言:javascript代码运行次数:0运行复制
struct S
{
 int data[1000];
 int num;
};
struct S s = {{1,2,3,4}, 1000};//初始化
//结构体传参
void print1(struct S s)
{
 printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
 printf("%d\n", ps->num);
}
int main()
{
 print1(s);  //传结构体
 print2(&s); //传地址
 return 0;
}

上面的printf1与prinf2相比应该选择哪一个?答案是printf2

原因是:

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

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

简单来说,就是:如果直接传结构体的话,在函数内需要再次创建一个形参,而形参也会占用内存,如此就会对系统压栈造成压力,使系统性能下降。

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


6.结构体实现位段

6.1 什么是位段

一个字节8个比特位

位段:二进制位,位段是专门设计出来节省内存空间的

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

1.位段的成员必须是int ,unsigned int ,sigend int,在c99中位段成员的类型也可以选择其他类型

2.位段的成员名后面有一个冒号和一个数字

比如:

代码语言:javascript代码运行次数:0运行复制
一个整形32个bit
struct A
{
    int _a:2;加上:2表示只占2个比特位
    int _b:5;表示只占5个比特位
    int _c:10;表示只占10个比特位
    int _d:30;表示只占30个比特位
};

其中A就是一个位段类型

代码语言:javascript代码运行次数:0运行复制
prinft("%d\n",sizeof(struct A));

这段代码用来看位段A占多少内存大小

4.2 位段的内存分配

1.位段的成员可以是 int ,unsigned int ,signed int 或者是 char 类型。

2.位段的空间上是按照需要以每次4个字节( int )或者1个字节( char )的方式来开辟的。

一般来说,位段成员一般都是同类型的:

如果位段的成员全部是整型的,那就先给这个位段开辟4个字节的空间,如果不够用,那就再开辟4个字节的空间,还不够用继续开辟,以此类推。如果成员全部是char类型的,那就一次开辟1个字节的空间,直至放得下所有成员。

3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

4.3位段的跨平台问题

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

总结:与结构体相比位段可以达到相同的效果,并可以很好的节省空间,但是跨平台会有问题


感谢观看!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2024-08-21,如有侵权请联系 cloudcommunity@tencent 删除编译器跨平台内存int变量

本文标签: c语言自定义类型结构体