网站备案怎么登陆,网站搜索优化方法,建设一个行业性的网站价格,wordpress固定链接怎么设置好结构体 结构体的声明与使用结构体的声明与初始化结构体的自引用 结构体的内存对齐对齐规则为什么存在内存对齐修改默认对齐数 结构体的传参结构体实现位段什么是位段位段的内存分配位段的跨平台问题位段使用的注意事项 结构体#xff1a;是一个自定义的类型#xff0c;成员可… 结构体 结构体的声明与使用结构体的声明与初始化结构体的自引用 结构体的内存对齐对齐规则为什么存在内存对齐修改默认对齐数 结构体的传参结构体实现位段什么是位段位段的内存分配位段的跨平台问题位段使用的注意事项 结构体是一个自定义的类型成员可以有一个或者多个类型可以不同。 结构体的声明与使用
结构体的声明与初始化
struct 结构体名
{成员变量....
};//结构体初始化
struct Stu
{char name[20];int age;char sex[10]
};
int main()
{struct Stu s1{lisi,18,man};return 0;
}结构体还存在一中特殊声明匿名结构体
struct
{成员变量;...
};这个结构体在声明时不需要写入结构体名但是在使用时只能使用一次 比如下面这段代码
struct
{int a;int b;float c;
}x;
struct
{int a;int b;float c;
}*p;
int main()
{px;return 0;
}这段代码在编译时会报警告因为编译器会把这两个声明当成两个不同的类型。 结构体变量的声明与初始化 直接上代码
//在声明结构体的同时声明结构体变量并初始化
struct stu
{int a;int b;float c;
}x{1,2,1.0};
int main()
{return 0;
}
///
struct stu
{int a;int b;float c;
};
int main()
{struct stu x{1,2,1.0};//声明结构体变量并初始化return 0;
}对结构体数组成员初始化。
struct Stu
{char name[20];int age;char sex[10]
};
int main()
{struct Stu s1{lisi,18,man};printf(%s %d %s,s1.name,s1.age,s1.sex);//输出结构体的值return 0;
}结构体的自引用
若结构体中包含一个结构体本身的成员自引用是否可以? 比如定义一个链表节点
struct Node
{int data;struct Node next;
};思考一下这段代码正确吗 答案当然是“错误”的。因为以这样的方式嵌套定义结构体那么这结构体的大小将是无穷大的。 正确的自引用应该是下面这样的
struct Node
{int data;struct Node*next;
};这在后面将会学到这其实是建立了一个链表的节点定义一个结构体指针让结构体指针指向结构体的数据域data再让被指向的结构体的指针成员next指向下一个结构体的数据域data。
typedef关键字 typedef关键字使用来对类型进行重命名的。 比如
typedef int i;
将int 重命名为i
typedef char c;
将char重命名为c。或许你会有这么一个想法“int、char一个就3个字母一个就4个字母我为什么还有重命名重命名它俩还要多打一个typedef不是更麻烦吗” 确实在这中情况下确实没必要对数据类型重命名但是在c语言中除了基本数据类型还有自定义的数据类型。就如我们这篇文章的所讲的结构体类型当我们定义了一个结构体类型后要声明一个结构体变量就需要将struct 结构体名 都打出来但是如果我们使用typedef关键字对它重命名就能方便很多了。
typedef struct Student
{int age;char name[20];
}S;
int mian()
{S s1{18,lisi};return 0;
}但是要注意一点重命名的类型需要重命名完后才能使用否则编译器过不了编译比如
typedef struct Student
{int age;S *next;
}S;这里提前使用了结构体struct Student类型重命名后的名字S是不可以的因为编译器是从上往下编译代码的当编译到S*next时S还没有被编译到所以对于编译器来说S类型是不存在的。正确的形式应该像下面这样子。
typedef struct Student
{int age;char name[20];
}S;
int main()
{S s1{18,lisi};return 0;
}结构体的内存对齐
到这里我们已经掌握了结构体的基本使用那么我们再接着思考“定义的结构体的大小是多少int是4字节char是1字节那么结构体呢” 在讨论结构体大小之前我们先了解一下结构体的对齐规则
对齐规则
结构体对齐规则是指结构体中每个成员变量的存放地址要满足一定的条件以提高内存的访问效率。 结构体对齐规则
结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处结构体其余成员对齐到对齐数的整数倍的地址处对齐数编译器默认的对齐数与该成员变量自身大小两者之间的较小值vs中默认对齐数为8Linux中gcc没有默认对齐数结构体总大小为最大对齐所以成员变量都有一个对齐数选最大的那一个的整数倍如果结构体2嵌套了一个结构体1则这个嵌套的结构体1的对齐数自身成员变量的最大对齐数结构体2的大小就是最大对齐数的整数倍
比如
struct Stu
{int age;char a;int b;
}S;则这个结构体的大小为计算过程 在思考的过程中你应该已经发现了内存对齐存在内存浪费。是的内存对齐就是一种用空间换时间的做法。
为什么存在内存对齐
硬件平台限制并不是所有硬件都能够任意读取任意地址处的数据的某些硬件只能访问某些特定地址下的数据如果不进行内存对齐则cpu将不会读取到特定地址处的数据。 性能问题cpu在读取内存时是按照一个固定的粒度来读取的比如一次四字节、8字节如果结构体不按照一定的规则进行内存对齐那么cpu在读取一份数据的时候可能需要读取两次、三次甚至更多次才能把一份数据读完而进行内存对齐后cpu则只需要读取一次就行了。
修改默认对齐数
#pragma这个预处理指令可以修改默认对齐数 。
#pragma(1);//将默认对齐数修改为1
struct Stu
{int age;char [name];
}s;
int main()
{printf(%zd,sizeof(struct Stu));//输出12//若把#pragma(1)注释掉默认对齐数恢复到8次数输出6return 0;
}当结构体的对齐数不合适的时候我们就可以通过#pragma来修改默认对齐数。
结构体的传参
struct S
{int age;char name[20];float i;
};struct S s {18,lisi,1.0};void print1(struct S s)
{printf(%d %s %lf\n,s.age,s.name,s.i);
}void print2(struct S *s)
{printf(%d %s %lf\n,s-age,s-name,s-i);
}int main()
{print1(s);print2(s);return 0;
}结构体传参也分为两类
传值调用传址调用
在传参时推荐使用传址调用因为函数传参的时候参数是需要压栈的会有时间与空间上的系统开销。 传值调用如果传入的参数过大这个开销也就会变大。 但如果传入地址地址大小就两种情况4字节/8字节所有推荐使用传址调用。
结构体实现位段
什么是位段
什么是位段是一种数据结构它允许将数据以位bit的形式紧凑地存储并允许程序员对此结构的位进行操作 位段的声明与结构体类似
struct A{int _a:2;int _b:5;int _c:10;int _d:30;};这段代码中struct A占8个字节 int占4个字节所以第一次开辟空间大小为4字节4字节32bit但是struct A里四个成员共占47bit所以4字节的空间无法存储struct A类型的数据需要在开辟一个Int类型的空间所以该位段大小为8字节。
位段的内存分配 位段的成员可以是Int家族或者char等 位段空间的分配按照需要以4个字节或者1个字节的方式来开辟的 位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使⽤位段 struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};给定空间后空间内存按照从左到右还是从右到左使用呢这个不确定标准没有定义 当剩下的空间不足以分配位段成员时是浪费这部分空间还是继续使用没有定义 我们假设空间是从右往左使用空间是被浪费的 此时最后一个字节就完全倍浪费掉了。
位段的跨平台问题
int 位段被当成有符号数还是⽆符号数是不确定的。位段中最⼤位的数⽬不能确定。16位机器最⼤1632位机器最⼤32写成27在16位机器会 出问题。位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义。当⼀个结构包含两个位段第⼆个位段成员⽐较⼤⽆法容纳于第⼀个位段剩余的位时是舍弃 剩余的位还是利⽤这是不确定的。 总结 跟结构相⽐位段可以达到同样的效果并且可以很好的节省空间但是有跨平台的问题存在 位段使用的注意事项
位段的有些成员的起始位置并不是字节的起始位置就好比上图中的‘a’,‘b’我们知道每个字节都有自己的地址1字节8bit这8bit是没有地址的所以在使用位段时我们不能对位段成员使用取地址操作符也不能进行scanf操作如果需要给位段成员赋值需要先给一个变量赋值在通过这个变量赋值给位段成员