C语言结构体详解

前言:

        何为结构体,结构体又是什么呢,相信有很多小伙伴对结构体还没有一个清楚的概念,今天咱也一起来探讨一下何为结构体,在C语言当中有着许多的数据类型,如char,int,long,double等等这些类型分别用来存放着相对应的数据类型,而在日常的需求当中,很多时候我们所要用到的类型都不止一个,这个时候多种数据类型,放在代码当中很容易就搞混了,此时C语言为了应对这种情况的发生,引进了一个名为结构体的概念,专门用来存储多种数据类型,将这些数据类型封装在一起,统一使用

举个例子:一个学校要存储一个学生的信息,那用代码应该如何存储呢

int main()
{
	char name[20] = "wangwu";    //姓名
	int age = 20;   //年龄
	char speciality[20] = "yunjisuan";   //专业
	int ID = 5;     //学号
	return 0;

}

        没学结构体前,只能一个个赋值,这样写虽然没有错误,但是数据一旦多起来,会显得很麻烦,下边我们来看一下结构体又是如何存储这些数据的呢

struct Student
{
	char name[20];    //姓名
	int age;   //年龄
	char speciality[20];   //专业
	int ID;     //学号
};

int main()
{
	struct Student s = { "wangwu",20,"yunjisuan",5 };

	return 0;
}

        此时关于学生相关信息的赋值一条语句即可搞定,后续这个结构体还可重复调用,比起前边没有运用结构体的语句是不是简单明了很多呢

        下边我们来了解一下结构体在C语言当中应该如何正确的调用,赋值等

结构体的声明:

struct tag
{
     member-list;  //数据列表
}variable-list;   //变量列表

        举个例子(这边还是以上边的代码举例):

struct Student
{
	char name[20];    //姓名
	int age;   //年龄
	char speciality[20];   //专业
	int ID;     //学号
}zhangsan,lisi,wangwu;   //可以省略到后边主函数当中定义变量

int main()
{
	struct Student s = { "wangwu",20,"yunjisuan",5};

	return 0;
}

        相比之前的代码,这里在结构体结尾多了一个变量的初始化,但是上边并没有初始化变量,所以这里的variable-list是可以省略的,等后续需要的时候再去初始化

结构体的不完全声明:

        相比上边的完全声明情况下,还有一组特殊的声明方式,下边我们一起来看看它特殊在哪里:

//完全声明
struct Student
{
	char name[20];    //姓名
	int age;   //年龄
	char speciality[20];   //专业
	int ID;     //学号
}zhangsan,lisi,wangwu;

//不完全声明
struct
{
	char name[20];    //姓名
	int age;   //年龄
	char speciality[20];   //专业
	int ID;     //学号
}a,a1;

        上边两段代码,有着一些细微的差异,我们仔细观察会发现,它们不同的地方,是struct后边一个有标签(tag)Student,一个后边啥也没有,而这个啥也没有的,就是我们要讲的不完全声明,那么问题来了,居然它没有标签(tag)那么我们在后续调用的时候又该如何调用呢,如果有多个不完全声明呢,这个时候我们就要注意,当它有多个的时候,编译器也不知道该调用哪一个,所以在使用不完全声明的时候,要确保你只会使用一次该声明的情况下去使用,否则可能程序会发生错误

结构体变量的创建及初始化:

        变量的创建无非就是在主函数当中以结构体类型 ,创建一个变量,初始化则是在创建的同时给它赋值,下边我们来看一组例子:

struct Student
{
	char name[20];    //姓名
	int age;   //年龄
	char speciality[20];   //专业
	int ID;     //学号
}zhangsan,lisi,wangwu;   //可以省略到后边主函数当中定义变量

int main()
{
	struct Student s = { "wangwu",20,"yunjisuan",5 };  //变量的创建及初始化
    struct Student s1 = { "lisi",19,"dashuju",10 };
    struct Student s1 = { "zhangsan",18,"yuweng",18 };

	return 0;
}

结构体的自引用:

        在结构体当中可以引用自己嘛?答案是可以的,但需要注意以下几点:

1. 结构体自引用时,不可直接使用变量调用本身

2. 自引用时应避免使用typedef函数,否则可能会引起不必要的错误

(注:typedef函数作用是将要更改的函数重新命名,后续使用命名以后的名称起到同样的效果)

举例:

//错误引用方式
struct W
{
	char q[20];
	struct W s;  
    //很显然这个引用方式是错误的,如此引用,一层套一层,结构体只会无限变大,这不是我们想要的
};

//正确引用方式
struct W
{
	char q[20];
	struct W* s;//使用指针引用该结构体的地址
};

//typedef错误演示
typedef struct W
{
	char q[20];
	N* s; 
	// C语言程序执行是由上往下执行的,此时并没有执行到将结构体命名为N,
	// 使用程序中的N是什么结构体并不知道,所以错误
}N;

结构体内存对齐:

对齐规则:

  1.    结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
  2.    其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处,对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值, VS 中默认的值为 8 Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
  3.    结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍
  4.    如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍

举例:

struct S1
{
	char c1;  //1与8比,1小,所以此处字节+1
	int i;    //4与8比,4小,但由于1不是4的倍数,所以字节+3来到4字节的位置上,然后+4,来到9字节的位置上
	char c2;  //1与8比,1小,所以此处字节+1,来到9字节的位置上
//最后比较结构体中的最大对齐数 1=1《4,所以最大对齐数为4,9不是4的倍数,所以9+3,来到12字节的位置上
?、所以最后输出的结果为12

};

int main()
{
	printf("%d\n", sizeof(struct S1));
}

  题解分析:

          

为什么会有内存对齐注意说法呢,博主找了一些资料供兄弟们参考:

修改默认对齐数:

        前边我们了解到在VS当中默认对齐数为8,那我们有没有方法去修改它呢,答案是有,使用#pragme pack(对齐数)即可,下边我们来看一组例子:

#pragma pack(2)  //将默认对齐数修改为2
struct S1
{
	char c1;   //1和2,1小,字节+1,到1字节位置处
	int i;     //2和4,2小,由于1不是2的倍数,所以先+1,来到2字节位置处,然后+4,到6字节处
	char c2;   //1和2,1小,6是2的倍数,所以+1,到7,7不是最后结构体中最大对齐数4的倍数,所以+1到8
};
#pragma pack()  //重新恢复为默认对齐数8
int main()
{
	printf("%d\n", sizeof(struct S1));
}

//前边我们这段代码的执行结果为12
//现在再执行的话执行结果为8

 结构体传参:

        前边我们已经了解完了结构体如何创建及使用,那函数调用结构体时,又应该如何传参呢,下边我们一起来看一下:

(注:当变量为结构体变量时,采取(变量名.参数)的形式访问,当变量为指针的时候,采取(指针名->参数)的形式访问)

struct Mode
{
	char name[10];
	int age;
	double ID;
}s;

void Test(struct Mode s)  //此处传的是整个结构体,传入整个结构体导致调用函数时,内存又得重新开辟一个相同大小的空间,用来专门存储该变量
{
	s.age = 20;
	s.name[10] = "zhangsna";
	s.age = 3.14;
}

void Test(struct Mode* s)	//传的是结构体的地址,地址在内存当中占4/8个字节,所以相对上边大大减少需要申请的空间
{
	s->age = 20;
	s->name[10] = "zhangsna";
	s->age = 3.14;
}

        如此看来,在结构体传参的时候,我们的应该传的是地址,而不是结构体本身

结构体位段的实现:

 什么是位段:

举例:

struct Mode
{
	int a:2;  //代表一个int类型中访问两个bit,后续元素访问访问这个bit,直到该类型空间访问完以后,在开辟下一个int型空间
	int age:3;
	int ID:4;
}s;

位段的内存分配:

由于位段的特殊性,可能一个字节有多个元素在使用它,使用位段是不能用(&元素名)取地址的

(注:测试环境为VS2022,该平台位段存储顺序是从左往右存储,当剩余存储空间不够存储下一个元素时,舍弃该空间,重新开辟新的空间)

举例:


struct Mode
{
	char a:2;
	char age:3;
	char ID:4;
	char a2 : 5;
}s;

int main()
{
    s.a = 4;
    s.age = 6;
    s.ID = 15;
    s.a2 = 33;
	printf("%zd", sizeof(s));
}

题解分析:

        总结:由于位段的不确定性,所以一般不支持跨平台使用,因为每个平台对应位段的设定可能是不一样的,用得好,可以节省内存空间,但不清楚规则的情况下使用,可能会达不到你想要的效果,所以关于位段请谨慎使用

(今日分享到此结束,感谢支持,如有高见,欢迎评论区留言,兄弟们一起探讨,Thanks♪(・ω・)ノ)

  • 39
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值