SpringMVC数据响应

TortoiseGit推送代码错误remote: error: GE007: Your push would publish a private email address.

  返回  

12C语言结构体(进阶)

2021/7/21 0:09:37 浏览:

在之前 C语言中内置类型了解了内置类型,本章将带大家了解C语言中的自定义类型,包括结构体、枚举和联合体,其中应该重点了解结构体的对齐数以及如何计算结构体大小。


文章目录

  • 一、结构体
    • 1.1匿名结构体类型
    • 1.2结构体的自引用
    • 1.3结构体内存对齐
      • 1.3.1内存对齐的原因
      • 1.3.2修改默认对齐数
      • 1.3.3计算结构体中某变量相对于首地址的偏移量
  • 二、位段
  • 三、枚举
    • 3.1枚举类型的定义
  • 四、联合(共用体)
    • 4.1联合类型的声明和定义
    • 4.2联合体大小计算


一、结构体

结构体的声明在之前结构体(初级)中已经说明过

1.1匿名结构体类型

匿名结构体类型就是在声明结构体的时候,不完全的声明,省略标签(名字),这种结构体只能在声明的时候创建变量,后续无法继续创建变量:

//匿名结构体类型
struct
{
 int a;
 char b;
 float c; 
}x;
struct
{
 int a;
 char b;
 float c; 
}a[20], *p;//创建结构体数组和指针

虽然上面两个结构体中的内容相同,但编译器会认为这两个结构体是两个不同的结构体,因此无法用指针p存放x的地址:
在这里插入图片描述

1.2结构体的自引用

在结构中包含一个类型为该结构本身的成员叫做结构体的自引用。

但是结构体是无法将自己作为成员变量的,否则会不断套娃递归:
在这里插入图片描述

结构体的自引用要用结构体指针,用来存放下一个结构体的地址:

struct Node
{
	int data;
	struct Node* next;
};

在这里插入图片描述

如果用typedef重命名结构体,则不能用重命名后的名字来定义结构体指针:

//代码3
typedef struct
{
 int age;
 Stu* next; //由于是在结构体声明以后重命名的,所以在创建成员变量时不能用重命名后的名字
}Stu;

//正确写法:
typedef struct Stu
{
 int age;
 struct Stu* next; 
}Stu;

1.3结构体内存对齐

结构体大小的计算比较复杂,必须要了解结构体的内存对齐,这是一个特别重要的内容。

结构体的对齐规则:

  1. 以结构体第一个变量存放的位置为偏移量为0的地址处。
  2. 其他成员变量存放在偏移量为对齐数的整数倍的地址处。
    成员变量的对齐数是编译器 默认的一个对齐数 与 该成员大小中较小的那个值。
    比如VS编译器中默认的对齐数的值为8,int型变量的对齐数是4,char型变量是1
  3. 结构体的总大小是所有成员的对齐数中最大对齐数的整数倍。
  4. 如果结构体中嵌套另一个结构体,则嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有对齐数中(包含嵌套结构体的对齐数)最大对齐数的整数倍。

以下面的程序为例:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

计算嵌套结构体的大小:

在这里插入图片描述
在这里插入图片描述

1.3.1内存对齐的原因

  1. 计算机在内存读取数据时,只能在规定的地址处读数据,而不是内存中任意地址都是可以读取的。
  2. 在访问一些数据时,对于访问未对齐的内存,处理器需要进行两次访问;而对于对齐的内存,只需要访问一次就可以。 其实这是一种以空间换时间的做法。

内存对齐可以使读取效率更高,比如一次读取4个字节的32位平台,如果不存在内存对齐,可能会出现读取多次的情况:
在这里插入图片描述

在这里插入图片描述

因此在设计结构体时,为了节省空间,就得做到:

将内存小的成员写在一起,中间不要插入其他类型的成员

struct S1
{
 char c1;
 int i;
 char c2;
};
struct S2
{
 char c1;
 char c2;
 int i;
};
//S2的空间比S1小

1.3.2修改默认对齐数

使用#pragma这个预处理指令,可以改变默认对齐数,一般将默认对齐数设置为2的次方数。

#include<stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
	char c1;
	int i;
	char c2;

};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

在这里插入图片描述

1.3.3计算结构体中某变量相对于首地址的偏移量

想要计算结构体中某变量相对于首地址的偏移量,只需要取出结构体中变量的地址和结构体地址,将它们全部转化为char*指针后相减即可:

#include<stdio.h>
struct S1
{
	char c1;
	int i;
	char c2;

}s;
int main()
{
	printf("%d\n", (char*)(&s.i) - (char*)(&s));

	return 0;
}

在这里插入图片描述


二、位段

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

1.位段的成员有 char、int、unsigned int 或signed int 类型。
2.位段的成员名后面是冒号和数字,这个数字是该成员占的内存大小,单位是比特
3.位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
4.位段是不跨平台的。

使用位段可以节省空间

struct A 
{
 int a:1;
 int b:4;
 int c:11;
 int d:31;
};

位段冒号后面的数字num意味着其只需要占num个比特位,比如int a:1;意味着a的大小是1个比特位,因此A的大小就是1+4+11+31=47个字节。但在实际情况中,编译器会开辟4个字节的内存给a,b,c,剩下的内存不够存储d,因此编译器会再开辟4字节的内存给 d,所以其大小为8字节:
在这里插入图片描述

看下面的程序:

#include<stdio.h>
#include<string.h>
struct A 
{ 
	int a : 5;     
	int b : 3; 
};
int main()
{
	char str[100] = "0134abcd";     
	struct A d;    
	memcpy(&d, str, sizeof(A));     
	printf("%d\n", d.a);
	printf("%d\n", d.b);

	return 0;
}

在这里插入图片描述

上述程序中定义了位段A,其中a占用5比特位,b占用3比特位,所以a和b总共占用了结构A一个字节(低位的一个字节)。由于是32位处理器,而且结构体中a和b元素类型均为int(也是4个字节),所以结构体的A占用内存为4个字节。memcpy会拷贝str中四个字节的内容到d中:
在这里插入图片描述

这四个字节的内容也就是0134这四个字符的二进制位:

高位00110100 00110011 00110001 00110000低位

其中d.a和d.b占用d低位一个字节(00110000),d.a : 10000, d.b : 001
d.a内存中二进制表示为10000,由于d.a为有符号的整型变量,输出时要进行整形提升,所以结果为-16(二进制补码为11111111111111111111111111110000)

d.b内存中二进制表示为001,由于d.b为有符号的整型变量,输出时要进行整形提升,所以结果为1(二进制原反补码为00000000000000000000000000000001)

三、枚举

枚举就是把可能的取值一一列举。

3.1枚举类型的定义

如果对一周要进行枚举:

enum Week
{
 Monday,
 Tuesday,
 Wednesday,
 Thursday,
 Friday,
 Saturday,
 Sunday
};

以上定义的 enum Day就是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。
这些可能取值都是有值的,默认从0开始,依次递增1,当然在定义的时候也可以赋初值。 例如:

enum Week
{
 Monday=0,
 Tuesday,//1
 Wednesday,//2
 Thursday=4,
 Friday,//5
 Saturday,//6
 Sunday//7
};

在这里插入图片描述

上面的代码也可以使用宏#define完成,比如#define RED 1,但是相比于宏来说,枚举有以下的优点:

  1. 枚举的代码更直观,增加代码的可读性和可维护性
  2. 枚举有类型检查,因此更加严谨。
  3. 枚举不会和其他变量的命名冲突
  4. 枚举不会完全替换,因此便于调试
  5. 枚举一次可以定义多个常量,使用相对方便

四、联合(共用体)

与结构体类似,但是联合体的成员共用同一块空间。

4.1联合类型的声明和定义

//联合类型的声明
union U
{ 
 char c1; 
 int i1; 
}; 
//联合变量的定义
union U u; 

联合体的大小不会比最大的成员小:
在这里插入图片描述

可以看到联合类型的变量地址都是相同的:
在这里插入图片描述

在这里插入图片描述

既然联合体共用同一块空间,这也意味着如果改变其中一个变量,那么其他变量也会发生改变:
在这里插入图片描述

我们也可以使用联合体判断大小端:

#include<stdio.h>
int check_sys()
{
	union Un
	{
		char c;
		int i;
	}u;
	u.i = 1;
	//返回1表示小端
	//返回0表示大端
	return u.c;
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

4.2联合体大小计算

  • 联合的大小不会小于其最大成员的大小(因为存在内存对齐,所以可能会比最大成员大)。
  • 联合体的内存大小要对齐到成员中最大对齐数的整数倍。

数组的对齐数就是其类型的对齐数。

#include<stdio.h>
union U1
{
	char c[7];
	int i;
};

int main()
{
	printf("%d\n", sizeof(union U1));
}

在这里插入图片描述

#include<stdio.h>
union U2
{
	short c[11];
	int i;
};

int main()
{
	printf("%d\n", sizeof(union U2));
}

在这里插入图片描述

联系我们

如果您对我们的服务有兴趣,请及时和我们联系!

服务热线:18288888888
座机:18288888888
传真:
邮箱:888888@qq.com
地址:郑州市文化路红专路93号