【C语言进阶】结构体&&位断

1.结构体声明

先来回顾一下结构体的基本概念:结构体是一些值的集合(可以为不同类型的数据),其中这些值被称为成员变量。

[1]结构体声明

struct+结构体标签
{
变量1;
变量2;
变量3;
... ...
}变量名;所组成
//例如创建一个结构体描述学生信息
struct Stu//这一部分相当于结构体类型,与int,char用法类似
{
    char name[10];
    char sex[5];
    int age;
    char id[10]; 
}s1,s2;//全局变量
//可以在变量名处创建新的变量,这种变量属于全局变量
//也可以在main函数内部定义结构体变量
int main()
{
    struct Stu s3;
    //此时创建的结构体变量属于局部变量
    return 0;
}

通常为了简便,我们会采用typedef来进行结构体类型的重定义

typedef struct Stu
{
    char name[10];
    char sex[5];
    int age;
    char id[10]; 
}Stu;
//在创建新的结构体变量的时候便可以这么使用
Stu s1,s2;
//typedef struct Stu Stu;
//也可以将typedef单独使用

[2]特殊声明

在声明结构的时候,可以不完全声明。称为匿名结构体类型 ,在声明时省略结构体标签,但要在结构体声明的时候就创建变量,且只能使用一次。

struct
{
    char name[10];
    char sex[5];
    int age;
    char id[10];
}a;

struct
{
    char name[10];
    char sex[5];
    int age;
    char id[10];
}*p;

int main()
{
    //struct s3;
    //这种写法是错误的,没有完整的结构类型
    return 0;
}

注意:p=&a编译器并不认为这两种变量的类型一致,因此这种写法是错误的

当然也可以对这种匿名结构体类型进行typedef重定义

typedef struct
{
    char name[10];
    char sex[10];
    int age;
    char id[10];
}node;
//struct
//{
//    char name[10];
//   char sex[5];
//    int age;
//    char id[10];
//};
//typedef struct node;
//而这种写法却不行,node并不算一个完整地结构体类型
//不能单独去重定义一个匿名结构体类型
int main()
{
    node s1={"Tom","female",12,"u1023"};
    node s2={"Jerry","male",15,"u1239"};
    //使用重定义过的结构体类型可以创建多个变量
    return 0;
}

[3]结构体的自引用

struct Node
{
    int data;
    struct Node next;
};
//在结构体中包含一个类型为该结构体本身的成员,这样就无法确定结构体的大小了
//在struct Stu大小还未确定的情况下,在结构体内部使用该结构体类型创建新的结构体成员
//因此这种写法本质上是错误的
struct Node
{
    int data;
    struct Node* next;
};
//这种写法是可行的,因为结构体指针大小是明确的4/8字节

2.结构体初始化

struct Stu
{
    char name[10];
    int age;
    char sex[10];
}s1={"Tom",19,"male"},s2={"Jerry",20,"female"};//全局变量

int main()
{
    struct Stu s3 = { "lisi",26,"male" };//局部变量
    printf("%d %d %d", s1.age, s2.age, s3.age);
    return 0;
}
注意:全局变量未初始化时,整形变量会默认为0,字符型会默认为'\0'而局部变量未初始化则是随机值,若局部变量写为={ }则默认为0('\0')
1.当全局变量未初始化时
struct Stu
{
    char name[10];
    int age;
    char sex[10];
}s1,s2;//全局变量

int main()
{
    struct Stu s3 = { "lisi",26,"male" };//局部变量
    printf("%d %d %d ", s1.age, s2.age, s3.age);
    printf("%s %s %s ", s1.name, s2.name, s3.name);
    printf("%s %s %s ", s1.sex, s2.sex, s3.sex);
    return 0;
}

输出结果为:

2.当局部变量未初始化时
struct Stu
{
    char name[10];
    int age;
    char sex[10];
}s1 = { "Tom",19,"male" }, s2 = { "Jerry",20,"female" };//全局变量

int main()
{
    struct Stu s3;//局部变量
    printf("%d %d %d", s1.age, s2.age, s3.age);
    printf("%s %s %s ", s1.name, s2.name, s3.name);
    printf("%s %s %s ", s1.sex, s2.sex, s3.sex);
    return 0;
}

输出结果为:

VS上:会报错无法输出

vc++6.0上:会出现随机值

3.当局部变量写为={ }时
struct Stu
{
    char name[10];
    int age;
    char sex[10];
}s1 = { "Tom",19,"male" }, s2 = { "Jerry",20,"female" };//全局变量

int main()
{
    struct Stu s3={};//局部变量
    printf("%d %d %d ", s1.age, s2.age, s3.age);
    printf("%s %s %s ", s1.name, s2.name, s3.name);
    printf("%s %s %s ", s1.sex, s2.sex, s3.sex);
    return 0;
}

输出结果为:

3.结构体内存对齐

[1]结构体的对齐规则:

  • 结构体的第一个成员,对齐到结构体在内存中存放位置的0偏移处。

  • 从第二个成员开始,每个成员对齐到某个数字(对齐数)的整数倍偏移处。

对齐数:结构体成员自身大小和默认对齐数的较小值

默认对齐数根据编译器的不同而有所区别,在VS中默认对齐数为8,而在Linux gcc中没有默认对齐数,对齐数就是结构体成员的自身大小

  • 结构体的总大小,必须是所有成员对齐数中最大对齐数的整数倍。

  • 如果结构体中嵌套了另外一个结构体,要将结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小是最大对齐数(含嵌套结构体的对齐数)的整数倍。

  • 其中数组的对齐数为数据类型的大小(只看结构体成员的类型)eg:char arr[5]的对齐数是1(相当于5个char类型变量) int arr[10]的对齐数是4

下面举个栗子来感受一下吧

那么为什么要进行内存对齐呢?

[2]修改默认对齐数

VS默认对齐数是8修改默认对齐数通过预处理指令#pragma完成

#pragma pack(4)//修改默认对齐数为4
struct Stu
{
    char name[5];
    int age;
    char sex[5];
};
#pragma pack()//恢复编译器原有默认对齐数,为8
int main()
{
    printf("%d ", sizeof(struct Stu));
    return 0;
}

[3]结构体传参

struct Stu
{
    char name[10];
    int age;
    char sex[5];
};
void print1(struct Stu s)//结构体接收
{
    printf("%d", s.age);
}
void print2(struct Stu* s)//结构体指针接收
{
    printf("%d", s->age);
}
int main()
{
    struct Stu s1 = { "zhangsan",19,"male" };
    print1(s1);//传结构体
    print2(&s1);//传结构体的地址
    return 0;
}

上面的两种传参方式,显然第二种传地址的方式较好。

原因:传参是需要压栈的,会有时间以及空间上的消耗。由于传递一个结构体变量时,结构体过大,压栈时开销较大,会导致性能下降,因此首选结构体指针作为参数。

3.位断

【1】定义:

1.成员必须是int, unsigned int, signed int.(char 也可以 因为它属于整形家族)

2.成员名后必须有一个冒号和数字。

用结构体实现位断

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

【2】位断的内存分配

  1. 在空间上是按照4字节(int型)或1字节(char型)的方式开辟空间的。

  1. 位断涉及很多不确定因素,不跨平台,可移植程序应避免使用位断。

下面通过一个栗子来了解位断的内存分配:

通过查看内存存储发现确实如此:

4.位断的跨平台问题

根据上面栗子的分析可以总结出位断所存在的一些问题:

  1. int型位断被当做有符号数还是无符号数是不确定的

  1. 位断中最大位数目无法确定(在早期16位机器中,int是16比特位,也就是2字节,如果位断的整形部分写为27在16位机器上会报错;而现在32位机器上int是32比特位,也就是4字节)

  1. 位断中成员在内存中是由右向左分配空间,还是由左向右分配空间无法确定

  1. 当一个结构包含两个位断,其中第二个位断成员较大,无法容纳于第一个位断剩余空间时,是舍弃剩余空间还是利用剩余空间时不确定的

5.位断的应用

在我们日常聊天过程中,并非是简单的信息传输过程,而是有一套较为复杂的信息分装,这时就需要使用位断来节省空间,提高空间利用率。如果使用char/int等类型变量来进行存储,会浪费很多空间,因此选择位断来进行存储。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

伱恏呀呀呀呀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值