c语言 结构体类型(建议收藏哦!)


一 结构体类型

1.1 结构体类型的声明

结构体的作用是将不同类型的数据整合在一起
结构体的结构包括:关键字struct ,我们设定的结构体名student, 用来描述属性的结构体成员。
struct student {//struct 是关键字
               //student是结构体类型名
	           //结构体类型名不同于结构体变量名
	char name[20];//结构体成员
	int age;      //结构体成员
};

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

//创建一个结构体变量,分为局部变量与全局变量,在函数之外的是全局,之内为局部

变量的创建:

struct student {
  
	char name[20];
	int age;
}stu1; //stu1是全局变量
struct student stu3;//stu3也是全局变量
int main() {
	struct student stu2;//stu2是局部变量
	return 0;
}
结构体全局变量有两种创建方式,局部变量只有一种创建方式,如代码所示!

变量的初始化:
结构体变量的初始化形式:

//第一种需要对应结构体类型赋值
	struct student s1 = {"张三",18};
//第二种:
    struct student s2 = { .age = 20,.name = "wangwu" };
//成员单独赋值
    s2.name = "zhangsi";

2 结构体的特殊声明

省略掉结构体标签即类型名的声明

2.1 匿名结构体如何使用

struct
{
	int a;
	char b;
	double c;
}s1 = {6,'a',6.0}//可以
int main(){
//struct s = { 4,'a',3.0 };//这种使用方式是错误的
// s1 = { 4,'a',3.0 };    //也不行
//s1.a = 4;                  //可以
printf("%d\n", s1.a);
return 0;
}

2.2 无标签的结构体不能与无标签的结构体进行运算,自身定义的除外

struct
{
	int a;
	char b;
	double c;
}s1 = {6,'a',6.0},*p1;
struct
{
	int a;
	char b;
	double c;
}*p2;
int main(){
p1 = &s1; //可以使用
printf("%d", (*p1).a);
//*p2 = &s1;//错误代码
//因为系统不能识别两个无符号的结构体是不是同一类型
return 0 ;
}

2.3 可以再为无标签结构体设置名字

typedef struct
{
	int a;
	char b;
	double c;
}stu; //将名字设成stu 

3 结构体的自引用

struct student {
	char name[10];
	struct student* Node; //链表的实现,可以指向下一个结构体类型
};                       //不可以用结构体名连接下一个结构体变量,
                         //否则拿单个结构体中的数据时,
                         // 会拿出一连串的结构体类型变量

在这里插入图片描述

4 访问结构体变量中的成员

访问分直接访问与间接访问两种:
struct student {
	char name[20];
	int age;
}stu1;
int main(){
  stu1 = {"张三",19};
//直接访问操作符.  操作数为结构体变量名与结构体成员名
printf("%s\n", stu1.name);
printf("%d\n", stu1.age);
//间接访问操作符 ->,操作数为指针与结构体成员名
struct student* p = &stu1;
printf("%s\n", p->name);
printf("%d\n", p->age);
//也可以访问指针再用直接访问操作符,
//直接访问操作符比解引用的操作符优先级高,故需要+()
printf("%s\n", (*p).name);
printf("%d\n", (*p).age);
return 0 ;
}

5 typedef对结构体的操作

//在声明结构体后,当我们创建结构体变量或结构体类型指针时,往往名字过长,导致代码可读性变差
struct Student {
	char name[10];
	int age;

};
int main() {
//当每次创建变量与指针时,都必须加上struct  
	struct Student s1 = { 0 };
	struct Student* p1 = NULL;

	return 0;
}


我们可以用**typedef**来修改结构体类型名与关键字struct ,组合成一个标识符
     也可以来修改结构体指针类型名为一个标识符
#include<stdio.h>
//typedef 修改名称的两种形式: 
//第一种:在结构体的声明中重设名称(结构体名与struct的组合)或者(结构体指针类型)
typedef  struct Student {
	char name[10];
	int age;
} Student,* PStudent;
// 重设结构体名与struct的组合 为:Student
// 重设结构体指针类型 :   PStudent //省去了* 

// 第二种:
// 重设结构体名与struct的组合 为:Student
 typedef struct Student  Student ;
// 重设结构体指针类型 :   PStudent  // 这样省去了* 
 typedef struct Student*  PStudent
// 第一种与第二种的修改名称的形式作用等同。

6 计算结构体类型的大小

(1)题

struct str {
	char a;
	int b;
	char c;
  };
int main() {
	//判断结构体的类型的空间大小
	struct str s = { 0 };
	size_t sz = sizeof(s);
	printf("%zd\n", sz);
	return 0;
}

按照一般的思维,结构体类型的大小为各成员类型大小的总和,此例应为6个字节,真是如此吗?

在这里插入图片描述
之所以出现这个情况,要搞清楚结构体类型在内存中的存储情况

(2) 结构体在内存中的存储

结构体内存存储的规则:
1  第一个成员放置在偏移量为0的空间
2  第二个成员放置在偏移量是采用对齐数整数倍的地址
3  往后的成员依次按照第二个成员放置的规则
4  结构体的总大小为最大采用对齐数的整数倍
5  如果出现了嵌套了结构体的情况,即结构体变量作为结构体成员的情况
嵌套结构体对齐自己成员的最大采用对齐数的整数倍,
本结构体的大小为所有采用对齐数中(包括嵌套结构体中各成员的采用对齐数)
最大采用对齐数的整数倍

什么是偏移量?

  以结构体首个地址为原点,首地址与原点的偏移量为0
  ,第二个地址与原点的偏移量为1
  往后的地址依次类推。

什么是对齐数?

每一个结构体成员皆对应一个对齐数,对齐数的大小为结构体成员的大小,单位为字节
在VS编译器中,默认对齐数是8
Linus中gcc没有默认对齐数,大小直接为该成员的大小
在每个成员使用对齐数时,采用默认对齐数与成员自身对应的对齐数较小的值,得出
采用对齐数!
比如:char类型的大小为1个字节,对应的对齐数为1,而VS默认的对齐数为8,故采用对齐数1

(3)举例

举例1:
struct str {
	char a;
	int b;
	char c;
  };

在这里插入图片描述

举例2:

关于嵌套结构体的情况

struct str {
	char a;
	int b;
	char c;
  };
struct student {
	char name;
	int age;
	struct str st1;
};
int main() {
	struct student stu1 = { 0 };
	size_t sz = sizeof(stu1);
	printf("%zd\n", sz);
}

在这里插入图片描述
//此结果是如何得出:
在这里插入图片描述

(4)为什么结构体要采用这种对齐原则?

某些系统不能访问所有的地址,而只能在某些特定的地址一段一段地访问
这导致有时访问某一成员时需要访问多次,这浪费了时间
而将结构体成员存放到自身大小整数倍的地址处,这有效的减少了内存碎片
(有时访问的空间中有不是要成员的数据)的产生
通过填充字节,提高了成员访问的效率 
本质上来讲,对齐原则是用空间换取了时间

(5)如何修改编译器中默认的对齐数

修改对齐数用的命令:#pragma pack(1) ,()中的数据是要设定的默认对齐数
#pragma pack() ,()中什么都不写,则还原为默认对齐数


#pragma pack(1)  //将默认对齐数设置为1 ,相当于顺序存放
struct stu1 {
	char a;
	int b;
	char c;
};

#pragma pack()  //取消设置的对齐数,还原为默认!
struct stu2 {
	char a;
	int b;
	char c;
};
int main(){
	size_t len1 = sizeof(struct stu1);
	printf("%zd\n", len1);
	size_t len2 = sizeof(struct stu2);
	printf("%zd\n", len2);
	return 0 ;
}

在这里插入图片描述

 要注意的是当#pragma pack(1)作用第一个结构体类型后,即使再用#pragma pack()还原默认对齐数,也不会改变第一个结构体的大小

7结构体传参

与数组传参时相同,如果将形参设成结构体,则形参会申请与实参同样的空间
这会   造成大量资源的使用,如果将形参设置成结构体指针,则不会出现这种情况

二 位段

(1) 什么是位段

 位段(或称为位域)是结构体类型的一种形式,与常规结构体的差别在于成员不同
 在c99之前的版本,位段只能是 int, unsigned int, signed int 或者是 char 
(属于整形家族)类型
在c99版本中所有的整型家族均可作为成员,比如long ,short等

位段中的成员格式:举例:int a : 2; 
     // 其中int 代表位段成员类型  a代表成员名  :后面跟2,代表分配给此成员2个二进制位

	 位段的意义在于以比特位为单位,减少了内存空间的浪费
	 我们存储0,1,2,3时 
00 01 10 11  两个比特位足矣,而在之前的结构体变量中存放一个整型数据就要申请32位空间

代码:

//有位段名(域名)的位段
struct stu1 {
	int a1 : 10; //为成员a1分配10个比特位
	int a2 : 8;  //为成员a2分配8个比特位
	int a3 : 30; //为成员a3分配30个比特位
};
//无位段名(域名)的位段
struct {
	int a1 : 10; //为成员a1分配10个比特位
	int a2 : 8;  //为成员a2分配8个比特位
	int a3 : 30; //为成员a3分配30个比特位
}s1;

(2)位段在内存中的存储

位段根据首成员的类型,申请相对应的空间大小
1)位段的成员类型相同时:
  分配申请的空间给成员,剩余的空间不足矣满足下一成员的需求时,则重新申请一块空间
  (问题1,不足的空间是浪费掉,还是继续利用)
  (在VS编译器上不足的空间是浪费掉)
  (问题2,在对申请的空间分配比特位时,是从低地址到高地址分配,还是从高地址到低地址分配?)
  (在VS编译器上对申请的空间是从高地址到低地址分配空间),(注意这并不是数据在内存中存储的方式)

举例:

struct stu1 {
	char a1 : 3; //为成员a1分配10个比特位
	char a2 : 4;  //为成员a2分配8个比特位
	char a3 : 6; //为成员a3分配30个比特位
};
int main(){
struct stu1 s1;
size_t len = sizeof(s1);
printf("%zd\n", len);
return 0 ;
}

在这里插入图片描述

在这里插入图片描述

(3)位段的跨平台问题

位段的可移植性非常差,原因如下:
1 当位段的成员int型数据,在不同的机器上,int的大小可能不同,比如在32位机器上,int为4个字节, 在16位机器上,int为2个字节,如果我们给int 设置27位比特位,那么就会报错
2 在分配空间时,我们不能确定是从低地址到高地址分配,还是从高地址到低地址分配
3  不能确定不足的空间是浪费掉,还是继续利用
4 int 位段被当作有符号数还是无符号数,这个不能确定。

(4) 位段的应用:

在ip数据报中,一些数据段属性很小,但较多,所以可以用位段方式
来存放数据,节省空间!

(5)位段使用的注意事项

存储位段成员的基本单位是比特位,而地址对应的是字节,这就导致我们不能
通过 地址来访问数据,比如说scanf函数,操作符&,只能通过变量名,=  
.  ->等方式
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值