结构体


  在C语言中有多种类型,比如有修饰整型变量的 int ,修饰字符的 char 等,这些常见的数据类型修饰的只是一些简单的变量,而对于复杂的数据就难以修饰了,本次就来介绍一个新的数据类型——结构体。

1. 结构体

1.1 结构体的介绍

  结构体是由一批数据组合而成的结构型数据。结构型数据是由多个数据组合而成的,每一个数据被称为结构型数据的"成员"。

数组也是构造类型的一种,也是由多个 “成员” 组成。

结构体和数组的最大不同是:
  数组是由多个相同的数据类型组成的。
  结构体可以由不同的数据类型组成。

1.2 结构体类型的声明

 struct tag {
 	member-list
 } variable-list ;

tag、member-list、variable-list这3部分至少要出现2个。下面将会详细介绍······

1.3 结构体变量的定义

  例如我们要定义一个 student 的结构体

struct student
{
	int age;         //年龄
	char name[10];   //姓名
	char sex[5];     //性别
	char class[10];  //班级
	float score;     //成绩
};

1.3.1 先定义结构体类型,再定义结构体变量

struct student
{
	int age;         
	char name[10];   
	char sex[5];     
	char class[10];  
	float score;     
};
struct student stu1;  //stu1是一个结构体类型的变量

1.3.2 结构体类型和结构体变量同时定义

struct student
{
	int age;         
	char name[10];   
	char sex[5];     
	char class[10];  
	float score;     
}stu2;   //stu2是一个结构体类型的变量

1.3.3 直接定义结构体变量

struct          //这里的student省略
{
	int age;         
	char name[10];   
	char sex[5];     
	char class[10]; 
	float score;     
}stu3;   //stu3是一个结构体类型的变量

这种情况属于匿名结构体类型,只能够用一次,为了避免出错,这种情况尽量不要使用。

1.3.4 typedef 的加入

  以上三种对结构体变量定义的情况很常见,选择任意一种熟悉的即可。但是还有一种情况例外,就是在 struct 前加上 typedef ,就不是定义变量了,而是数据类型。(typedef 的作用是为复杂的声明定义简单的别名)
具体情况参考以下代码:

typedef struct student  //这里student同样可以省略
{
	int age;
	char name[10];
	char sex[5];
	char class[10];
	float score;
}stu;  //相当于将 struct student 的变成 stu ,但功能都是相同的

stu stu1;  //等同于struct student stu1;

如果还不理解,就再举个例子:
  别人给李明同学取了一个外号叫做小明,虽然名字不一样,但是说的都是一个人。

1.4 结构体的变量的初始化

  变量创建好之后就可以开始初始化了。

#include<stdio.h>
struct student
{
	int age;
	char name[10];
	char sex[5];
	char class[10];
	float score;
}stu1 = { 18,"zhangsan","nan","一年级1班",99 };  //方法1
int main()
{
	struct student stu2 = { 19,"lisi","nv","一年级2班",99 };  //方法2
	struct student stu3 = { .age = 20,.score = 98,.class = "一年级3班",.sex = "nv",.name =
	"wangwu" }; //方法3

	printf("%d %s %s %s %f", stu3.age, stu3.class, stu3.name, stu3.sex, stu3.score);
	return 0;
}

以上三种方法都能够输出,注意:方法3 成员变量并不是按照结构体中成员的顺序排列的,也能够正常输出,所以顺序是不影响的。

方法1和方法2 、3 又有些不同,方法1 的初始化是全局变量,另外两种方法就属于局部变量,这里就不做过多说明。

2. 结构体内存对齐

  我们先来看一段代码:

#include<stdio.h>
struct
{
	char ch1;
	char ch2;
	int x;
}st1 = { 'a','b',10 };

struct
{
	char ch1;
	int x;
	char ch2;
}st2 = { 'a',10 ,'b' };
int main()
{
	printf("%d\n", sizeof(st1));  //计算字节数
	printf("%d\n", sizeof(st2));
	return 0;
}

运行结果:

8
12

  经过观察,发现 sizeof 计算两结构体变量字节数有些出入,两结构体的成员只是位置不同,字节数却也会不同,这就要对结构体大小的计算进行探讨了。

2.1 内存对齐规则

  1. 结构体的第⼀个成员对齐到相对结构体变量起始位置偏移量为0的地址处。(简单来说就是从头开始)
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的⼀个对齐数 与 该成员变量大小的较小值
  • 比如 int 是 4 个字节 ,VS默认对齐数为 8 ,那么 4 和 8 相比 ,4<8,所以 int 的对齐数是 4 (较小的一个)。
  • int 类型变量就只能对齐到 4 的整数倍(4,8,12······)的位置,具体位置以实际情况为准
  • 每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
  1. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的整数倍
  • 上文代码为例,在 char (1),int(4) ,char(1)三个数据类型中,最大对齐数为 4,所以结构体总大小为 4 的整数倍(4,8,12,16,20······)
  1. 结构体也可以嵌套,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍
  • 如果发生嵌套的情况,可以先计算最里面一层结构体的总大小,再一步步计算。最重要的是把多个结构体内最大对齐数记住,再来根据最大对齐数来存放

图解:

st1:
内存对齐2

st2:
内存对齐2

2.2 为什么要内存对齐

根据资料:
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
简单来说:内存对齐提升了效率。
所以尽量把相同类型的变量放在一起

2.3 修改默认对齐数

  前面提到了对对齐数的修改,现在就来实践一下:
(如果有需求,要更改默认对齐数,修改的值最好为 2 的次方数)

#include<stdio.h>
#pragma pack(1) //将默认对齐数修改为 1
struct
{
	char ch1;  // 1个字节
	int x;     // 4个字节
	char ch2;  // 1个字节
}st2 = { 'a',10 ,'b' };
#pragma pack()  //取消修改默认对齐数

int main()
{
	printf("%d\n", sizeof(st2));
	return 0;
}

运行结果:

6

3.结构体的传参

  通常情况下,传参有两种方法,1是传值调用,2是传址调用。结构体的传参两种方法都可以使用,只是优劣的问题。
  对于结构体来说,传值调用的情况下形参需是实参的一份临时拷贝,结构体中的成员可能会有极大的数组或者其他占用空间大的值,这样会很占用空间;而传址调用则会直接根据地址指向相对应的位置,就没有拷贝这一说法。

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

  在访问结构体成员用到的操作符有两种:

#include<stdio.h>
struct student
{
	char name[10];
	int age;
};
void print1(struct student* t1)  //指针接收
{
	printf("%s\n", t1->name);
}
void print2(struct student t2)
{
	printf("%d", t2.age);
}
int main()
{
	struct student s1 = { "zhangsan",20 };
	print1(&s1);   //传址
	print2(s1);    //传值
	return 0;
}

运行结果:

zhangsan
20

总结为以下:
      结构体变量 . 成员;
      结构体指针 -> 成员;
变量对应 .
指针对应 ->

5. 位段

  位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。(1. 位段基于结构体;2. 为了节省空间;)

5.1 位段的声明和结构

声明:

  • 位段的成员必须是 intunsigned intsigned int (在C99中位段成员的类型也可以选择其他类型。)

结构:

  • 位段的成员名后边是⼀个冒号和一个数字
struct X
{
	int a : 2;    //数字表示 bit 位
	int a : 5;
	int a : 8;
	int a : 12;
};    //位段 X 所占内存大小为 4

5.2 位段内存的分配

  1. 位段的成员是 intunsigned intsigned int
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  3. 位段不跨平台。

开辟了 1 个字节后,bit 位的使用的方向是不确定的。
当一个空间使用后,剩余空间不足以给下一个成员使用时,剩下的空间是否补齐是 不确定的。

位段
由此可见,在VS中不会使用不足的空间,而是直接开辟新的空间。

5.3 位段的跨平台问题

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

  
  本章在理解上有些难度,注意多加思考!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值