结构体(一)

结构体(一),第1张

目录
  • 结构体的声明
    • 结构体的定义
    • 结构体的声明
    • 结构体的特殊声明
    • typedef的使用(结构体别名)
  • 结构体的自引用
  • 结构体变量的初始化
  • 结构体内存对齐
    • 结构体对其规则
    • 试题
      • 题目一
      • 题目二
      • 题目三
  • 修改默认对齐数
  • 结构体传参
  • 代码

结构体的声明 结构体的定义

结构体是一些值得集合,这些值被称为成员变量。成员变量可以是不同类型的值。

结构体的声明

struct tag
{
member_list;
}variable_lest;

struct student
{
         char name[20];
         int age;
         char id[15];
}s;
结构体的特殊声明

此种声明又可以称为结构体的不完全声明,或叫做匿名结构体类型。
结构体声明时可以去掉结构体标签(tag);

struct 
{     char name[20];
      int  age;
      char id[15];
}s;

/*注:此种声明创建变量时,只能在variable_list处创建,且为全局变量,不能再其他地方创建新的变量。

typedef的使用(结构体别名)

我们如果觉得在定义结构体变量时,书写过于麻烦,我们可以给它定义一个新的名称。

//typedef的使用
typedef struct student
{
	char name[20];
	int age;
	char id[20];
}stu;

int main()
{
	stu s1 = { "lisi", 30, "201817" };
	printf("%s  %d  %s\n", s1.name, s1.age, s1.id);
  • 这里我们就可以用 stu 来代替 struct student ,这样就方便结构体变量的创建;
  • 但是这样我们无法直接在结构体后面创建变量,取而代之的是结构体的别名;
  • 如果存在结构体嵌套的问题,我们在定义结构体时,是不使用别名的(要用全名),只有在定>义完结构体后,我们才能使用结构体。

结构体的自引用

在结构体中包含一个类型为该结构体本身的成员。

struct student 
{
       char name[20];
       int age;
       struct student* s;
};

/*注:这里我们不能直接把结构体放进去,如果这样做将导致结构体嵌套,结构体s里又有个结构体,这样无限嵌套,无限循环下去,同时也无法计算大小。


结构体变量的初始化
#include
  
  struct student
  {
        char name[20];
        int age;
        char id[15];
  }s1={"wangwu", 20, "201817"};
  int main()
  {
       struct student s2={"lisi", 21, "201816"};
       printf("%s  %d  %s\n", s1.name, s1.age, s1.id); 
       printf("%s  %d  %s\n", s2.name, s2.age, s2.id);
       return 0;
 }


结构体内存对齐

结构体的内存对齐,有利于对数据进行存储和读取,但是会浪费相对的空间。这导致结构体成员的大小之和不一定等于结构体总大小
为什么结构体对齐?

平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台在某些地址处访问访问某些特定类型的数据,否则抛出硬件异常。
性能原因
数据结构(尤其栈)应当尽可能的在边界上对齐。
原因在于为了访问未对齐的内存,处理器需要做两次内存访问,;而对齐的内存只需访问一次。

结构体对其规则
  1. 第一个成员与结构体变量偏移量为0的地址处。
  2. 其他结构体成员变量要对其到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数该成员大小 的较小值。
  3. 结构体总大小为最大对齐数(每个结构体成员变量都有一个对齐数)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍。
试题

我们计算下面结构体的大小。

题目一
//结构体内存对齐的问题
//默认对齐数是8
struct s
{
    char a;
    int  b;
    char c;
    int d;
}s1;

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

结果展示

题目解析


s是结构体起始的位置,以s开始计算偏移量。
a的对齐数为1(自身的大小)或8(默认对齐数)中的较小值,即为1,0是1的整数倍,放在偏移量为0的地方,占1个字节;
b的对齐数是4或8较小值为4,找偏移量为4的倍数处,把b放在其中,占4个字节,前面浪费3字节;
c的对齐数是1或8较小值为1,找偏移量为1的倍数处,把c放入其中,占1个字节,前面不浪费空间;
d的对齐数是4或8较小值为4,找偏移量为4的倍数处,把d放入其中,占4个字节,前面浪费3字节;
此时的总大小为16刚好为最大偏移量的整数倍,所以结构体总大小为16个字节

题目二
//试题二
//默认对齐数是8
struct s1
{
    char a;
    int  b;
    char c;
    int d;
}s1;

struct s2
{
    int a;
    int b;
    char c;
    char d;
}s2;

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

结果展示

题目解析
我们发现其实 s1 和 s2 成员变量其实类型和数量是相同的(两个 char 类型和两个 int 类型);
但是两个结构体所占的空间大小不同,我们重点分析一下 s2;

a的对齐数是4,在偏移量为0处,占4个字节;
b的对齐数是4,在偏移量为4处,占4个字节,前面浪费0个空间;
c的对齐数是1,在偏移量为8处,占1个字节,前面浪费0个空间;
d的对齐数是1,在偏移量为9处,占1个字节,前面浪费0个空间;
此时 s2 的最大偏移量为4,所以所占总空间为12个字节
我们发现把相同类型数据放到一起会节省部分空间,从而提高代码的质量。

题目三
//试题三
//默认对齐数为8
struct s1 
{
	int a;
	int b;
	double c;
}s1;

struct s2
{
	char a;
	int b;
	struct s1 s1;
}s2;

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

结果展示

题目解析
我们知道 s1 所占空间为8个字节
s2前两个结构体成员变量所占大小为8个字节
第三结果提成员变量为另一个结构体,这个结构体最大对齐数为8;
s2总大小为16 + 8 = 24个字节,刚好为最大对齐数的8 (这个8来源于结构体s1的最大对齐数)的倍数。所以总大小为24个字节

修改默认对齐数

我们能使用 pragma 这个预处理指令来修改默认的对齐数。

//对齐数的修改
#pragma pack(4)//设置默认对齐数为4
 
struct s
{
	char a;
	double b;
}s;

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

结果展示

这里我们把VS2019平台的默认对齐数8(每个平台默认对齐数不一样)改为了 4;
由此我们计算出结构体 s 的大小为12个字节


结构体传参

当我们需要把结构体当作参数传递给函数时,我们需要结构体传参。
这里我建议把结构体的地址当参数传递给函数,这样就会避免参数压栈导致性能下降的问题。

//结构体传参
struct s {
	int a;
	int b;
}s1;

void scanf_s1(struct s* s1)
{
	scanf("%d %d", &(s1->a), &(s1->b));
}

void printf_sum(struct s* s1)
{
	printf("%d\n", s1->a + s1->b);
}

int main()
{
	scanf_s1(&s1);
	printf_sum(&s1);
	return 0;
}

结果展示


代码

本片所有代码供大家使用。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/3002668.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-27
下一篇 2022-09-27

发表评论

登录后才能评论

评论列表(0条)

保存