自定义类型:结构体

1.  结构体类型的声明

2.  结构体变量的创建和初始化

3.  结构体成员访问操作符

4.  结构体传参

5.  结构体实现位段

欢迎关注 熬夜学编程

创作不易,请多多支持

感谢大家的阅读、点赞、收藏和关注

如有问题,欢迎指正

1.结构体的类型的声明

1.1结构体

结构是一些值的结合,值被称为变量。结构体中的变量可以是不同类型的变量。

1.1.1 结构体的声明

struct tag
{
    member-list;
}variable-list;

例如描述一个学生:

struct student
{
    int id[20];  学号
    char name[20];名字
    char sex;性别
    int age;年龄
};

1.1.2 结构体变量创建和初始化

struct student
{
    char id[20]; //学号
    char name[20];//名字
    char sex;//性别
    int age;//年龄
};

int main()
{
    struct student s1 = { "23408090","zhangsan",'m',18 };
    struct student s2 = { "22334557","lisi",'f',20 };
    printf("%s%s%c%d\n", s1.id, s1.name, s1.sex, s1.age);
    printf("%s%s%c%d\n", s2.id, s2.name, s2.sex, s2.age);
    return 0;
}

程序结果:23408090 zhangsan m 18
               22334557 lisi              f  20

1.2结构的特殊声明

在声明结构体可以不完全声明。

比如:

struct
{
    int a;
    char s[20];
}b;

struct
{
    int a;
    char s[20];
}a,*p;
int main()
{
    p = &b;
    return 0;
}

编译器会报警告,匿名的结构体基本只使用一次。

1.3结构的自引用

在结构体定义一个自身的结构体,是否可行?

struct node
{
    int a;
    struct node next;
};

sizeof(struct node),一个结构体包含本身,那大小将无穷大,不可行。

正确引用方式:

    struct node
    {
        int a;
        struct node* next;
    };

用tyoedef对结构体进行重命名,看看下面的代码是否可以:

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

Node结构体变量未创建前就使用,不允许。

正确引用方式:

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

2.  结构体内存对齐(重点,难点)

2.1  对齐规则

结构体内存对齐规则:

1. 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处.。

对齐数=编译器默认的一个对齐数 与 该成员变量大小的较小值。

vs中 默认的值为 8。

Linux 中gcc没有默认对齐数,对齐数是成员自身所占内存空间的大小。

3. 结构体总大小为最大对齐数(结构体每个成员变量都有一个对齐数,所有对齐数中最大的整数倍。)

4. 如果嵌套结构体的情况,嵌套的结构体成员对齐到自己的成员最大对齐数的整数倍处,结构体整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍处。

例如:


struct s
{
    char c1;
    char c2;
    int a;
};
int main()
{
    struct s s1 = { 0 };
    printf("%zd\n", sizeof(s1));
    return 0;
}

struct s
{
    char c1;
    int a;
    char c2;
};
int main()
{
    struct s s1 = { 0 };
    printf("%zd\n", sizeof(s1));
    return 0;
}

可以看到两个完全相同的三个变量,而位置不一样,对应的大小反生改变,说明确实存在对齐现象。

画图解释一下上面代码:

0 c1123

4

a

567

8

c2

9

10111213141516

c1在偏移量为0的地址处即蓝色部分,a的对齐数为4,根据第二条规则要对齐到4的整数倍地址处,所以从4开始占四个字节,从上面可以看出应该是9,根据第三条最大对齐数要是结构体成员最大对齐数的整数倍。

2.2 为什么存在内存对齐

1. 平台原因(移植原因):

不是所有的硬件平台都能访问出任意地址上的任意数据;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2. 性能原因:

数据结构(尤其栈)应该尽可能在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存仅需要一次访问。假设一个处理器总是从内存中取8个字节,那地址必须是8的倍数。

总结:结构体内存对齐就是浪费空间来节省时间的。

2.3  修改默认对齐数

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

#pragma pack(1)//修改默认对齐数
struct s
{
    char c1;
    int a;
    char c2;
};
#pragma pack()//取消默认对齐数,还原为默认 

int main()
{
    struct s s1 = { 0 };
    printf("%zd\n", sizeof(s1));
    return 0;
}

3  结构体传参

struct S
{
    int arr[1000];
    int num;
};

//传值调用,行参是实参的一份临时拷贝
void print1(struct S s)
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", s.arr[i]);
    }
    printf("\n");
    printf("%d\n", s.num);
}
//传址调用
void print2(struct S* ps)
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", ps->arr[i]);
    }
    printf("\n");
    printf("%d\n", ps->num);
}
int main()
{
    struct S s = { {1,2,3,4,5,6,7,8,9,10},13 };
    print1(s);
    print2(&s);
    return 0;
}

程序结果:

print1()和print2()哪个好,首选print2

原因:
函数传参的时候,参数需要压栈,会有时间和空间上系统开销。
如果传参一个结构体为对象,结构体过大,参数压栈的系统开销过大,导致性能下降。

4  结构体实现位段

结构体实现位段的能力。

4.1 什么是位段

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

1. 位段的成员必须是int 、unsigned int、或signed int,在c99中位段成员的类型可以选择其他类型。

2. 位段的成员名后边有一个冒号和数字。

比如:

//位段声明
struct s
{
    int _a : 2;
    int _b : 3;
    int _c : 5;
    int _d : 30;
};

//结构体声明
struct s
{
    int _a;
    int _b;
    int _c;
    int _d;
};

4.2 位段的内存分配

1. 位段的成员可以是 int、unsigned int、signed int 或者是 char类型。

2. 位段的空间上是按照需要以4个字节(int)或一个字节(char)开辟的。

3. 位段是不跨平台,所以注重跨平台的程序应避免使用位段。

4.3 位段的跨平台问题

1. int位段被当作有符号数还是无符号数不确定。

2. 位段中最大位数目不能确定。(16位机器最大为16,32位机器最大32)

3. 位段中的成员在内存中从左向右分配,还是从右往左分配,标准尚未定义。

4. 当一个结构体包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位置时,是舍弃还是利用,不确定。

总结:位段可以节省空间,但不支持跨平台。

4.4 位段的应用

下图是⽹络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要⼏个bit位就能描述,这⾥ 使⽤位段,能够实现想要的效果,也节省了空间,这样⽹络传输的数据报⼤⼩也会较⼩⼀些,对⽹络 的畅通是有帮助的。

4.5 位段使用注意事项

位段的几个成员共用一个字节,这样有些成员并不会死起始位置,所以就不能使用&操作符,就不能通过scanf直接给位段成员赋值。

解决办法:只能先输入一个变量中,然后赋值给位段成员。

如:

正确方法:

完!

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值