C语言程序设计——结构体【进阶】

一、结构体类型的声明

需要注意到的是,结构体类型的书写是关键字+标签:如 struct Stu

struct Stu a[ 20 ] ; //定义一个结构体数组

struct Stu * p ; //定义一个结构体指针

struct Stu
{
    int age;
    char name[10];
    char tele[12];
};

在之前我们已经学习了基础知识,现在学习一些特殊的形式。

typedef重命名

typedef struct Stu
{
    int age;
    char name[10];
}Stu;

struct Stu s1;
Stu s2; //typedef的效果

匿名结构体类型

即在声明的时候省略掉结构体标签。

struct 
{
    int a;
    char b;
    doubel c;
}x;

struct 
{
    int a;
    char b;
    doubel c;
}a[20],*p;

p = &x; //warning

注意,当对一个匿名结构体类型地址赋值是会有警告,因为编辑器会将两个匿名声明当作两个不同的类型。

二、结构的自引用

结构体自引用即在结构中包含一个结构自己这样的一个成员。

自引用体现出链表的数据结构,即找到第一个数据后,同时会指向下一个数据,如此使得需要的数据可以被连续起来。

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

如此自引用是错误的,因为每一次引用结构体其内都会含有一个结构体,如此无休止所以非法。

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

正确的自引用步骤应该是以一个指向结构体的指针为结构体成员,如此在定义结构体变量时在不需要引用时,赋值一个空指针即可。

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

//error

如此书写是错误的,因为无法确定时先重命名为Node还是先在结构体内定义指针。

三、结构体变量的定义和初始化

struct S 
{
    double weight;
    char c;
};

struct Stu
{
    int age;
    char name[10];
    struct S s;
    char tele[12];
}s1; //全局变量

struct Stu s2; //全局变量

int main()
{
    struct Stu s3 = {20;"张三";{56.3,'w'};"12345678900"}; //局部变量
    return 0;
}

四、结构体内存对齐

小试牛刀:计算结构体的大小

struct S1
{
    char c1;
    int a;
    char c2;
};

struct S2
{
    char c1;
    char c2;
    int a;
};
struct S3
{
    char c1;
    struct S1 s1;
    double d;
}

int main()
{
    struct S1 s1 = {0};
    printf("%d\n",sizeof(s1)); // 12
    struct S2 s2 = {0};
    printf("%d\n",sizeof(s2)); // 8
    struct S3 s3 = {0};
    printf("%d\n",sizeof(s3)); // 24
    return 0;
}

引出结构体储存方式,需要了解结构体对齐规则。

1)结构体第一个成员在结构体变量偏移量为0的地址处。

2)其余成员的偏移量和对齐数的整数倍对齐,跳过的空间被浪费。

        对齐数 = min(编译器默认对齐数,成员大小)。

vs默认对齐数为8,那么对于一个int成员,对齐数就是min(8,4)= 4 。

3)结构体总大小为成员中最大对齐数的整数倍(不足则扩大至满足条件)。

4)如果嵌套结构体,那么嵌套的结构体对齐到其成员最大对齐数的整数倍的偏移量地址,总结构体在计算总大小比较对齐数时,将嵌套的结构体内成员的对齐数也要考虑在内。

分析举例:

对于上述代码struct S1,char c1处于偏移量为0处,占一个字节;int对齐数为4,应和偏移量4的倍数对齐,即为4处,与char c1间的三个字节被浪费,int占4个字节;char c2对齐数为1,则可以储存到8处。结构体总大小应该为对齐数(1,4,1)最大者的整数倍,即4的整数倍,现在为9个字节,即浪费三个字节到11为止,总计为12个字节。

对于上述代码struct S2,char c1处于偏移量为0处,占一个字节;char c2对齐数为1,则可以储存到1处;int对齐数为4,应和偏移量4的倍数对齐,即为4处,与char c2间的两个字节被浪费,int占4个字节。那么结构体总大小应该为对齐数(1,1,4)最大者的整数倍,即4的整数倍,现在共为8个字节,所以最后储存空间为8个字节。

对于上述代码struct S3,char c1处于偏移量为0处,占一个字节;struct s1的最大对齐数为4,则找到偏移量4的为止储存该结构体,与char c1之间的3个字节浪费,前面计算占12个字节;double d对齐数为8,则此时偏移数为16,所以不空字节,直接储存。那么结构体总大小应该为对齐数(1,(1,1,4),8)最大者的整数倍,即8的整数倍,现在共为24个字节,所以最后储存空间为24个字节。

之所以采取这种内存对齐的方式,是出于移植原因和性能原因考虑,因为一些硬件平台只能在某些特定的位置访问数据,所以需要这样对齐存放,并且对其存放便于处理器访问,以空间换时间。

为了尽量节省空间我们可以让占用空间小的成员尽量集中在一起。如上述S1和S2大小就有所不同。

修改默认对齐数

利用#pragma这个预处理指令可以改变默认对齐数。

//设置默认对齐数为1
#pragma pack(1)

struct S1
{
    char c1;
    int a;
    char c2;
};

#pragma pack()
//取消设置的默认对齐数

int main()
{
    struct S1 s1 = {0};
    printf("%d\n",sizeof(s1)); // 6

    return 0;
}

offsetof计算偏移量

可以通过offsetof宏来实现对成员偏移量的计算。

struct S1
{
    char c1;
    int a;
    char c2;
};


int main()
{
    printf("%d\n",offsetof(struct S1,c1)); // 0
    printf("%d\n",offsetof(struct S1,a)); // 4
    printf("%d\n",offsetof(struct S1,c2)); // 8

    return 0;
}

五、结构体传参

之前强调了结构体的数据类型为 struct+标签,传参时也要注意。

注意传址调用和传值调用的区别与应用。

struct S
{
    int a;
    char b;
    double c;
};

void Init(struct S* ps)
{
    ps->a = 100;
    ps->b = 'w';
    ps->c = 3.14;
}

void Print(struct S s)
{
    printf("%d %c %lf",s.a,s.b,s.c);
}

int main()
{
    struct S s = {0};
    Init(&s); //传址

    Print(s); //传值

    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

犀利卓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值