结构体
前言
我们已经学了很多数据类型,列如int, char, float, double等。但还不能满足需求,在我们实际应用中,每一种变量进行一次声明,再结合起来显然是复杂的,列如一位学生的信息管理,他可能有,性别 (char),姓名(char),学号(int)成绩(float)等多种数据。如果把这些数据分别单独定义,就会特别松散、复杂,难以规划,因此我们需要把一些相关的变量组合起来,以一个整体形式对对象进行描述,因此我们需要自定义一种类型来规划它们,于是就有了结构体类型。
1、什么是结构体
结构体就是把多种类型组合起来的集合体。多种类型可以是int, float,char,指针等。可以想一下,数组是相同类型的多个元素组成的集合;而结构体是多种类型组成的集合。两者有区别,不可混淆。
2、结构体类型的声明
形式:
struct 类型名
{
结构体成员列表;
}
如:定义一个学生类型的结构体
struct Stu
{
char name[20];//姓名
char sex[5];//性别
int age;//年龄
char id[20];//身份证
};
这段代码就完成了结构体的声明。
3、结构体变量的创建和初始化
结构体变量创建
方式一:在声明结构体的同时创建变量。
struct Stu
{
char name[20];
char sex[5];
int age;
char id[20];
}s1;
这里创建了学生类型的结构体变量s1。
方式二:struct Stu s1;
struct Stu
{
char name[20];
char sex[5];
int age;
char id[20];
};
int main()
{
struct Stu s1;//结构体变量创建
return 0;
}
结构体的初始化
结构体初始化有两种方式,看如下代码:
//结构体声明
struct Stu
{
char name[20];
char sex[5];
int age;
char id[20];
};
int main()
{
//顺序初始化
struct Stu s1 = { "小敏", "女", 18, "202413230" };
//指定顺序初始化
struct Stu s2 = { .age = 20, .id = "202413232", .name = "晓东", "男" };
return 0;
}
4、结构成员访问操作符
有两种,分别是:" . “成员操作符和”->"成员操作符
struct Stu
{
char name[20];
char sex[5];
int age;
char id[20];
};
int main()
{
//顺序初始化
struct Stu s1 = { "小敏", "女", 18, "202413230" };
//.操作符访问
printf("name:%s\n", s1.name);
printf("sex:%s\n", s1.sex);
printf("age:%d\n", s1.age);
printf("id:%s\n", s1.id);
//指定顺序初始化
struct Stu s2 = { .age = 20, .id = "202413232", .name = "晓东", "男" };
//->操作符访问
struct Stu* p = &s2;
printf("name:%s\n", p->name);
printf("sex:%s\n", p->sex);
printf("age:%d\n", p->age);
printf("id:%s\n", p->id);
return 0;
}
5、结构体内存对齐
对齐规则:
- 结构体的大小一定是最大成员的整数倍。
- 成员的偏移量一定是当前成员的整数倍。
- . 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构
体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
代码展示:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
int i;
char c1;
char c2;
};
struct S3
{
double d;
char c1;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%zd\n", sizeof(struct S1));//输出12
printf("%zd\n", sizeof(struct S2));//输出8
printf("%zd\n", sizeof(struct S3));//输出16
printf("%zd\n", sizeof(struct S4));//输出32
return 0;
}
6、存在内存对齐的原因
- 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定
类型的数据,否则抛出硬件异常。 - 性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要
作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地
址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以
⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两
个8字节内存块中。
简单来说,结构体的内存对⻬是拿空间来换取时间的做法。以此来提高效率。
所以设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间。方法:让占⽤空间⼩的成员尽量集中在⼀起。
代码展示:
struct S1
{
char c1;
char c2;
int i;
};
struct S2
{
char c1;
int i;
char c2;
};
int main()
{
printf("%zd\n", sizeof(struct S1));//8
printf("%zd\n", sizeof(struct S2));//12
return 0;
}
结构体S1和S2类型的成员都一样,但是S1占的空间小于S2,这就是让占⽤空间⼩的成员尽量集中在⼀起的效果。
7、结构体传参
传参方式:
- 结构体传参(类似传值方式)
- 结构体地址传参
看如下代码:判断那种传参方式更好
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4},100 };
struct S* ps = &s;
//结构体传参
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(ps);
return 0;
}
输出结果:
答案是:print2函数更好
原因:函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。总之,结构体传参的时候,要传结构体的地址。
8、结构体实现位段
位段定义: 位段是C语言中结构体的一种数据类型。位段允许在结构体中定义具有指定位数的成员,这些成员可以占用结构体变量内部的连续比特位。位段的声明和结构体的声明是类似的。位段是基于结构体的,位段的出现就是为了节省空间。
与结构体的两个不同点:
- 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以选择其他类型。
- 位段的成员名后边有⼀个冒号和⼀个数字。这个数字代表了该成员变量在结构体内占用的bit位数。它用来限定成员变量的范围和存储空间。
位段中的位指的是二进制位(bit位),如下面的_a成员变量就占两个比特位,_b成员变量就占5个比特位。
列如:A就是一个位段。
//位段声明
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
//结构体声明
struct B
{
int _a;
int _b;
int _c;
int _d;
};
int main()
{
printf("位段A大小=%zd\n", sizeof(struct A));//8
printf("结构体B大小=%zd\n", sizeof(struct B));//16
return 0;
}
位段的内存分配:
- 位段的成员可以是 int, unsigned int ,signed int 或者是 char 等类型。
- 位段的空间上是按照需要以4个字节( int ) 或者 1个字节( char ) 的⽅式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
位段的应用:
下图是⽹络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要⼏个bit位就能描述,这⾥
使⽤位段,能够实现想要的效果,也节省了空间,这样⽹络传输的数据报⼤⼩也会较⼩⼀些,对⽹络
的畅通是有帮助的。
位段使用的注意事项:
位段的一些成员可能共同占用⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员。
代码展示:
struct A
{
int _a : 2;
int _b : 15;
int _c : 10;
int _d : 32;
};
int main()
{
struct A s = { 0 };
//错误
/*scanf("%d", &b);*/
//正确做法
int b = 0;
scanf("%d", &b);
s._b = b;
printf("%d", s._b);
return 0;
}
总结:
这一章我们学习结构体的声明,变量的创建和初始化,成语访问操作符,传参,内存对齐,位段。后面两个是难点。相信大家都学会了吧!
那么写到这里,本节内容就结束了,这篇博客花费了很长时间,但写完有满满的成就感,希望能帮助到大家,如果文章有不足的地方,欢迎在评论区留言指正,我们一起学习交流! 希望能得到大家的关注、点赞、评论、收藏! 你的支持是我最大的动力!!
标签:位段,struct,自定义,int,收藏,char,printf,C语言,结构 From: https://blog.csdn.net/2301_81193162/article/details/139336546