c 做网站的六大对象,厦门网站模板,淄博新闻头条最新消息,wordpress加入购物文章目录 #x1f680;前言#x1f680;结构体✈️结构体类型的声明✈️结构体变量的创建与初始化✈️结构体类型的特殊声明✈️结构体的自引用✈️结构体的内存对齐#x1f681;修改默认对齐数 ✈️结构体传参 #x1f680;前言
在C语言中有着各种数据类型#xff0c;这… 文章目录 前言结构体✈️结构体类型的声明✈️结构体变量的创建与初始化✈️结构体类型的特殊声明✈️结构体的自引用✈️结构体的内存对齐修改默认对齐数 ✈️结构体传参 前言
在C语言中有着各种数据类型这些类型有配划分为内置类型和自定义类型两大类如下图。铁子们今天阿辉要分享的就是自定义类型中的结构体联合体和枚举将在下篇文章分享至于数组阿辉之前的文章数组篇中已经详细讲到铁子们感兴趣的话可以点击跳转不多bb直接开始我们今天的学习
结构体
铁子们是否有这样的疑问——C语言为什么要引入结构体这一自定义类型 别急听阿辉一一道来 其实结构体与数组有一点类似数组是存储同一种数据类型的集合而结构体是存储不同类型的集合比如当你想描述一个学生时你得有姓名、年龄、学号等等一系列特征 可是我们发现这不是某一种单一的数据类型能够描述的这时引入结构体这一自定义类型是非常有必要的 对于结构体有何用想必铁子们有了初步的认识咱们接着往下看
✈️结构体类型的声明
声明结构体的语法结构
struct tag
{member_list; 成员列表
}variable_list; 变量列表在结构体声明时就创建的变量
struct tag 这个整体属于类型名和intchar等等类型名一样我们来创建一个描述学生的结构体类型
struct stu
{char name[20];名字int age;年龄int id;学号char sex[5];性别
};//注意这里的分号不能丢了结构体类型的声明同样分为全局声明和局部声明结构体全局声明以及声明时创建的变量作用域都是整个程序而结构体的局部声明以及声明时创建的变量的作用域在该大括号内部{}
✈️结构体变量的创建与初始化
结构体变量有两种创建方式一种在结构体类型声明时就创建与结构体类型声明具有相同的作用域另一种在结构体类型声明后创建作用域与结构体类型声明无关咱直接上代码
struct stu
{char name[20];int age;
}s1;//s1属于全局变量int main()
{struct stu s2;//s2局部变量作用域在main函数内return 0;
}结构体变量初始化
#include stdio.h
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
};
int main()
{//按照结构体成员的顺序初始化struct Stu s1 { 张三, 20, 男, 20230818001 };//按照指定的顺序初始化struct Stu s2 { .age 18, .name lisi,.id 20230818002, .sex ⼥};return 0;
}✈️结构体类型的特殊声明
铁子们结构体还有一种特殊的声明这种声明把结构体的标签tag给干掉了这种特殊声明的结构体被称为匿名结构体类型 我们来上一组例子
struct
{int a;int b;
}a;struct
{int a;int b;
}*p;int main()
{p a;return 0;
}上⾯的两个结构在声明的时候省略掉了结构体标签tag 那么问题来了 p a这样写是否合法 对于匿名结构体类型上述两个结构体类型看似一样实则不同匿名结构体的变量只能在声明时创建且只能有一个变量上述编译器会把匿名结构体指针变量p与a当作两个不同的类型 ✈️结构体的自引用
结构体的自引用本质是结构体的递归定义但是这会存在很大问题如下面这个代码
struct node
{int data;struct node next;
}在编译期间编译器需要知道结构体变量大小为结构体变量分配空间但是上述这个结构体我们仔细想一下会发现这个结构体无限递归根本无法确定其大小为解决上述问题我们可以通过指针来间接引用结构体如下
struct node
{int data;struct node* next;
}上述就是正确的结构体自引用通过结构体自引用我们可以创建具有互相关联关系的数据结构如链表、树等在数据结构中结构体尤为重要。
✈️结构体的内存对齐
有了上面对于结构体的理解铁子们对结构体的基本使用应该不成问题了接下来咱们来研究一个深入的问题——结构体类型的大小 有的老铁可能会说不是很简单吗❓直接把所有变量所占字节空间大小全都加起来就完事了 但是真有这么简单吗我们接着看 其实结构体存在内存对齐这一规则 第一个成员在与结构体变量偏移量为0的地址处其他成员变量要对齐到某个数字对齐数的整数倍的地址处 对齐数 编译器默认的一个对齐数 与 该成员大小的较小值 VS中默认的值为8Linux中gcc没有默认对齐数对齐数就是该成员大小结构体总大小为最大对齐数每个成员变量都有一个对齐数的整数倍如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍 在VS中char的对齐数就是1int对齐数就是4double对齐数就是8 我们来看看一个是否如此
#include stdio.h
struct S1
{char c1;int i;char c2;
};
int main()
{printf(%d\n, sizeof(struct S1));return 0;
}我们可以看到对于两个char和一个int应该是6个字节但是通过sizeof却打印出了12 这里我用用图为铁子们解释 上图一个方块代表一个字节对于第一个成员c1就存在偏移量为0的地址处而对于第二个成员i它是int类型对齐数为4要存在为4的倍数的偏移量处也就是上图位置出第三个成员c2为char类型对齐数为1存在i成员后面现在整个大小只有9个字节并非最大对齐数4的整数倍所以还要补3个字节分配给它上图中蓝色方块代表浪费的内存 知道了结构体内存对齐的计算之后问题又来了为什么存在内存对齐❓ 有两个原因 平台原因(移植原因) 不是所有的硬件平台都能访问任意地址上的任意数据的某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常 性能原因 数据结构(尤其是栈)应该尽可能地在自然边界上对齐 原因在于为了访问未对齐的内存处理器需要作两次内存访问而对齐的内存访问仅需要一次访问 结构体的内存对齐是一种拿空间换时间的做法
在设计结构体的时候我们既要满足对齐又要节省空间这该如何做呢 在我们声明结构体时尽量让占用空间小的成员集中在一起如
#include stdio.h
//例如
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};
int main()
{printf(%d\n, sizeof(struct S1));printf(%d\n, sizeof(struct S2));return 0;
}struct s2就要比struct s1的空间小4个字节
修改默认对齐数
#pragma 这个预处理指令可以改变编译器的默认对齐数 例子
#pragma pack(1)//设置默认对齐数为1
struct S1
{char c1;int i;char c2;
};
#pragma pack()//取消设置的默认对齐数还原为默认
int main()
{//输出的结果是什么printf(%d\n, sizeof(struct S1));return 0;
}输出为6当默认对齐数为1时也就不存在对齐了直接把所有变量所占字节空间大小全都加起来就完事了
✈️结构体传参
与其他类型变量传参一样同样可以传址和传值
#include stdio.h
struct S
{int data[1000];int num;
};
struct S s { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{printf(%d\n, s.num);
}
//结构体地址传参
void print2(struct S* ps)
{printf(%d\n, ps-num);
}
int main()
{print1(s); //传结构体print2(s); //传地址return 0;
}上述代码都可以帮我们打印但是传址调用更好 原因函数传参的时候参数是需要压栈会有时间和空间上的系统开销。如果传递一个结构体对象的时候结构体过大参数压栈的的系统开销比较大所以会导致性能的下降 SO结构体在传参时要传递结构体地址 感谢老铁能看到这到这里结构体的分享就到此为止了如果觉得阿辉写得不错的话记得给个赞呗你们的支持是我创作的最大动力