C语言—结构体

1.结构体类型的声明

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

1.1结构的简单声明

struct xxx
{
  
  member——list;
}xxxxx;

例如:描述一本书

struct BOOK
{
   char Book_Name[20];
   char author[20];
   float price;
   int id[18];
}BOOK1;// 全局的变量

int main()
{
  struct BOOK BOOK2;//局部的变量


  ......
}

1.2 结构的特殊声明

在声明结构的时候,可以不完全的声明。(匿名结构体类型)

这两个结构体类型都是匿名的,所以编译器会认为这是两个结构体定义,那么对应的地址肯定就是不一样的。

  • 编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
  • 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次。

1.3 结构的自引用

定义一个链表的结点:

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

上述代码是否正确?

其实是不正确的,因为当你计算这个结构体的大小时,在发现它一直在递归,这样大小就是无限大的,是不合理的。

所以正确的自引用方式:

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

在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,看看下述代码是否可行?

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

不行,因为Node是对前面的结构体类型的重命名产生的,但是在结构体内部提前使用Node类型来创建成员变量是不可以的。

所以要这样:

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

2.结构体内存对齐

不是6~

2.1对齐规则

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

2.2对齐规则存在的原因

  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成39的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被放在两个8字节内存块中。

总的来说:结构体的内存对齐就是拿空间来换取时间

所以在设计结构体的时候,我们要进行设计,使其满足对齐,也尽量节省空间:让空间小的放在一起。

2.3修改默认对齐数

#pragma 这个预处理指令,可以修改编译器的默认对齐数。

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

struct S2
{
    char c1;
    int n;
    char c2;
};
#pragma pack ()//取消设置的对齐数,还原为默认

int main()
{
    printf("%zd\n", sizeof(struct S1));
    printf("%zd\n", sizeof(struct S2));

    return 0;
}

3.结构体传参

#include <stdio.h>

struct S
{
    int arr[1000];
    int n;
    char ch;
};

void print1(struct S tmp)
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", tmp.arr[i]);
    }
    printf("\n");
    printf("n = %d\n", tmp.n);
    printf("ch = %c\n", tmp.ch);

}

void print2(struct S* ps)
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", ps->arr[i]);
    }
    printf("\n");
    printf("n = %d\n", ps->n);
    printf("ch = %c\n", ps->ch);
}

int main()
{
    struct S s = { {1,2,3,4,5,6,7,8,9,10},55,'L' };
    print1(s);
    print2(&s);

    return 0;
}

上面的print1和print2函数哪个好些?

首选print2函数。

原因:函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

           如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。

结论:

结构体传参的时候,要传结构体的地址。

4.结构体实现位段

4.1什么是位段

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

  1. 位段的成员必须是 int、unsigned int 或 signed int ,在C99中位段成员的类型也可以选择其他类型。
  2. 位段的成员名后边有一个冒号和一个数字。

位段中的位指的是二进制位。

一定程度上减少内存占用。

4.2位段的内存分配

  1. 位段的成员可以是int、unsigned int、signed int 或者是char等类型的
  2. 位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的
  3. 位段设计很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

4.3 位段的跨平台问题

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

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

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

    printf("%zd\n", sizeof(struct S));

    return 0;
}

这是一种方式,举个例子。

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

作者自述:本文主要针对C语言的结构体的知识。内容中包含了很多总结内容。本文制作不易,求求动动你们发财的小手点个赞和关注,这是对我创造最大的动力。后续我也会跟进内容,尽量一周至少一次,保证内容的质量。如果有想知道的内容或者有建议的地方,欢迎后台私信或者在本文留言哦。感谢各位的支持捏Thanks♪(・ω・)ノ。

  • 34
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值