C语言之结构体篇(简)

结构体是由我们自己创造的一种类型,使得C语言有能力描述复杂类型。比如学生包含了:名字、年龄、性别、学号…

结构体的认知

结构是一些值的集合,这些值叫做成员变量。每个成员可以是不同类型的变量。
————-----------

结构体与数组的区分:
数组:是一组相同类型的元素的集合。
结构体:是一些值的集合,但这些值可以是不同类型:可以是标量、数组、指针、甚至是其他结构体。

结构体的声明

一般声明

假如这里要描述一个学生:

//描述一个学生
struct Stu   //Stu是结构体变量名,可以自定义(根据实际需求)
{
    
    
	//里面是成员列表
	char name[20];//学生名字
	int age;      //学生年龄
	char id[10];  //学生学号
	//....

}s1,s2,s3;  //这里是结构体变量,是全局变量,
           //(创建结构体类型时顺带创建3个结构体全局变量)
          //这后面的分号不能丢
int main()
{
    
    
	struct Stu s4, s5; //创建结构体变量,这些是局部变量

	return 0;
}

特殊声明

就是再结构体声明的时候,可以不完全的声明。

匿名结构体类型

struct       //没有结构体变量名
{
    
    
	int a;
	char b;
	double c;
}s1;      //只能在这里创建结构体变量名,
         //且只能使用一次

结构体自引用

定义:结构体要能够找到和他同类型的下一个结构体;

  • 结构体中可以包含另一个结构体变量
struct book1
{
    
    
	char name[20];
	int age;
	int mony;
};

struct book
{
    
    
	char name[20];
	int mony;
	struct book1 s1;
};

但是结构体内不能有自身结构体变量,但是可以用指针指向一个本类型结构体来实现结构体自引用

struct Note
{
    
    
	int date;        //数据域
	struct Note* next;//指针域
};

关于typedef(定义类型)定义定义匿名结构体类型 :

typedef struct
{
    
    
	int data;//数据域
	struct Node* next;//指针域
} Node;
void main() {
    
    
	Node n;
 }

结构体变量的定义与初始化

结构体变量的定义

// struct book 是定义的数据类型的名字,它向编译系统声明这是一个“结构体类型”
struct book  
{
    
    
	int mony;
	char name[20];
}s1,s2;//全局变量(创建结构体类型时顺带创建2个结构体全局变量)
       //最后的分号千万不能省略
int main()
{
    
    
	struct book s3, s4;//局部变量
	return 0;
}

结构体变量的初始化

struct book
{
int mony;
char name[20];
}s1={30,"xiyouji"}, s2 = {20,"hongloumeng"};//s1,s2也是结构体变量,其是全局变量,
//并对其全部初始化,其实也可不初始化


int main()
{
struct book s3 = { 15,"三国演义" }, s4 = {25,"水浒传"};
//创建结构体对象并完成初始化
//其中初始化一个汉字占两个字节
return 0;
}

结构体嵌套初始化:


struct history
{
int age;
int id;
};


struct book
{
char name[20];
int mony;
struct history s1;
};


int main()
{
struct book s1 = { "abcdef",20,{1000,2023}};//初始化完成
return 0;
}

匿名结构体类型初始化


struct {
char name[20];
int price;
char id[12];
}s = { "git",7,"123" };

注意:

  • 结构体的声明和初始化也可以在函数里面进行
  • 结构体定义完就必须对其进行初始化
  • 结构体也可以通过操作符"."的形式来进行里面值的操作

结构体传参

struct BOOK
{
    
    
	char name[20];
	int money;
};

//传值调用
void test1(struct BOOK s)
{
    
    
	printf("%s %d", s.name, s.money);
}

//传址调用
void test2(struct BOOK* ps)
{
    
    
	printf("%s %d", ps->name, ps->money);
}

int main()
{
    
    
	struct BOOK s1 = {
    
     "shuihuzhuan",20 };

	test1(s1);//传结构体,不会改变结构体变量
	test2(&s1);//传结构体地址,可以改变结构体变量

	return 0;
}

注意:
结构体传参尽可能传地址,因为传参数需要压栈,如果传递一个结构体对象时,结构体过大,参数压栈的系统开稍过大,所以会导致性能下降;
传址调用时,可以用上const;结构体传参中const的作用:使原结构体只可以进行读取操作不可以进行更改操作,以防止误操作。

结构体内存对齐

现在我们已经了解结构体的基本使用了,接下来我们探讨一个深入的问题:
如何计算结构体的大小?
这里就涉及到结构体内存对齐
要了解这些,首先给大家介绍一个宏( offsetof() ),其可以计算结构体成员相对于结构体起始位置的偏移量

size_t offsetof( structName, memberName );

1. 第一个参数是结构体名称
2.第二个参数是结构体成员
输入两个参数,就会返回结构体成员相对于结构体起始位置的偏移量。

以这个代码来探究:

struct s1
{
    
    
	char c1;
	int i;
	char c2;
};

struct s2
{
    
    
	int i;
	char c1;
	char c2;
};

int main()
{
    
    
	printf("%d\n", offsetof(s1, c1));
	printf("%d\n", offsetof(s1, i));
	printf("%d\n", offsetof(s1, c2));
	return 0;
}

在这里插入图片描述

当知道了偏移量,咱们就可以来分析结构体储存在内存中的方式;
结构体存储在栈区

在这里插入图片描述

这样的话应该是9个字节,但结果是12字节,这是为什么?

在这里插入图片描述

上面出现的问题,说明结构体成员不是按照顺序在内存中连续存放的,而是有一定规律的。

结构体内存对齐的规则:

1. 结构体的第一个成员永远放在相较于结构体变量起始位置偏移量为0的位置。

2.从第二个成员开始,往后的每个成员都要对齐到某个数(对齐数)的整数倍的地址处,
对齐数:编译器默认的一个对齐数与该成员大小值的较小值。
VS编译器上对齐数默认是:8
gcc:没有默认对齐数,对齐数就是该结构体成员的大小。

3.结构体的总大小必须是最大对齐数的整数倍。
最大对齐数:所有对齐数的最大值

4.如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍出,结构体的整数大小,就是所以最大对齐数(包含了嵌套结构体的对齐数)的整数倍。

那怎么运用这个规则呢?
看图说话:
在这里插入图片描述

特殊一点 : 如果成员变量是一个数组。


举个例子:int arr [3];则把其看成三个整形变量依次存放就好。

为什么要内存对齐?

1.平台原因:

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

2.性能原因

  • 数据结构应该尽可能的在自然边界上对齐。原因在于:为了访问未对齐的内存,处理器需要两次内存访问;而对齐的内存访问仅只需要一次访问。

3.如图解释:
在这里插入图片描述
总结:结构体内存对齐是拿空间换时间的做法

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

1.合理安排好结构体成员空间


2.也可以修改默认对齐数

修改默认对齐数:

#include <stdio.h>
//修改默认对齐数为1
#pragma pack(1)
struct Hand
{
    
    
	char c;
	int size;
	char q;
};
#pragma pack()//取消设置的默认对齐,还原默认
int main() {
    
    
	printf("%d\n", sizeof(struct Hand));//默认对齐数8时——12,默认对齐数1时——6
	return 0;
}

位段

相比结构体更能节省空间,但是有跨平台问题;

为什么存在位段:因为如果一个字节里有32bit,当你只用到了2个bit,其他的空间就都浪费了,而位段就可以选择你的每一个成员占多大的空间,所以可以更好的节约空间。

位段声明

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

1.位段成员可以是 int 、unsigned int 、signed int 或者是 char (整形家族)类型。
__
2.位段的成员名后边有一个冒号和一个数字。
__

举例:

** S 就是一个位段类型**

struct S
{
    
    
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

位段的内存分配

就拿上述的位段类型,来探究;

1. 位段的成员可以是 int 、unsigned int、signed int 或者是 char (整形家族)类型
2. 位段的空间上是按照需要以4个字节(int)或1个字节(char )的方式来开辟的
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
4.首先每一个成员后面的数字都是位,是二进制位

struct S
{
    
    
	//因为先是char类型,先开辟一个字节--8个bit位
	char a : 3;//a成员占3个bit
	char b : 4;//b成员占4个bit
	char c : 5;//这时用了7个bit,还剩1个,因为下面还是char类型,不够就再开辟一个字节
	//c成员在新开辟的字节占5个bit
	char d : 4;//这时还剩3个bit,char类型,开辟一个字节,d成员占4个bit
	//因此S位段类型所占3个字节
};

int main()
{
    
    
	struct S s = {
    
     0 };
	s.a = 10;
	s.b = 20;
	s.c = 3;
	s.d = 4;

	printf("%d ", sizeof S);//3
	return 0;
}

在VS编译器下一个字节内部的数据,先使用低比特位的地址,再使用高比特位的地址(在内存中分配从右往左使用)
在这里插入图片描述

位段跨平台问题:

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

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

连肝6个小时真不是盖的呀 ^ _ ^ 最后希望兄弟姐妹们小小支持一下呗,咱下次肝到爆。

猜你喜欢

转载自blog.csdn.net/m0_66780695/article/details/132054437
今日推荐