在我们编写程序的过程中,可能会遇到一些复杂的对象,例如:当我们描述一个学生时,姓名,性别,年龄,学号等都可以是这个学生的信息。用单个类型不能够很好的描述他,这时就可以定义一个结构体。结构体是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。
1.2结构体的声明 描述一个学生struct Stu
{
char name[20];//姓名
char sex[5];//性别
char id[10];//学号
int age;//年龄
}s1;
这时一个结构体类型就定义好了,把可以描述一个学生的值的集合放到了结构体中。要注意的是
结构体后面的分号不能省略。
s1是
声明类型的同时定义的变量,
后面会继续讨论结构体定义变量和初始化。
1.2.1匿名结构体类型
//匿名结构体类型
struct
{
int a;
char b;
float c; }x;
struct
{
int a;
char b;
float c; }a[20], *p;
和上述代码对比后发现,
匿名结构体省略掉了结构体标签。
匿名结构的特点是只能用一次。只能在创建的时候定义结构体变量。
1.2.2结构体的自引用
结构体的自引用, 就是在结构体内部,包含
指向自身类型结构体的指针。
struct Node
{
int data;
struct Node* next;
};
1.3结构体变量的定义和初始化
如何定义变量,其实很简单,和我们之前定义变量大同小异,只不过这次变量的类型是结构体。
struct Stu
{
char name[20];//姓名
char sex[5];//性别
char id[10];//学号
int age;//年龄
}s1,s2;
struct Stu s3;
int main()
{
struct Stu s4;
return 0;
}
s1,s2,s3是全局变量。s4是局部变量。变量定义好了,接下来对刚刚定义好的变量初始化。
struct Stu
{
char name[20];//姓名
char sex[5];//性别
char id[10];//学号
int age;//年龄
}s1 = {"翠花","女","20060238",21}, s2;
struct Stu s3 = {"张三","男","20060240",20};
int main()
{
struct Stu s4 = {"李四","男","20060237",19};
return 0;
}
1.3.1结构体嵌套初始化
struct Stu
{
char name[20];//姓名
char sex[5];//性别
char id[10];//学号
int age;//年龄
};
struct Exam
{
int grades;
struct Stu p1;
}n1 = { 98, {"王五","男","20060240",20}};
1.4结构体成员的访问
结构体变量
访问
成员
:
结构变量的成员通过点 *** 作符(.)访问
struct Stu
{
char name[20];//姓名
char sex[5];//性别
char id[10];//学号
int age;//年龄
}s1 = {"翠花","女","20060238",21}, s2;
struct Stu s3 = {"张三","男","20060240",20};
int main()
{
struct Stu s4 = { "李四","男","20060237",19 };
printf("name=%s sex=%s id=%s age=%d\n", s1.name, s1.sex, s1.id, s1.age);
printf("name=%s sex=%s id=%s age=%d\n", s3.name, s3.sex, s3.id, s3.age);
printf("name=%s sex=%s id=%s age=%d\n", s4.name, s4.sex, s4.id, s4.age);
return 0;
}
结构体指针
访问
指向变量的成员
:
有时候我们得到的不是一个结构体变量,而是指向一个结构体的指针。 例如:定义了一个结构体,然后声明一个指针指向这个结构体,那么我们要用指针取出结构体中的数据,就要用到“->”.
struct Stu
{
char name[20];//姓名
char sex[5];//性别
char id[10];//学号
int age;//年龄
} s1 = { "翠花","女","20060238",21 };
int main()
{
struct Stu* p = &s1;
printf("name=%s sex=%s id=%s age=%d\n", s1.name, s1.sex, s1.id, s1.age);
printf("name=%s sex=%s id=%s age=%d\n", p->name, p->sex, p->id, p->age);
return 0;
}
此处p 是一个指针,因此不能使用 *** 作符(.)访问内部成员(即不能p.name),应该使用->。但s1.name是可以的,因为s1不是指针,是结构体变量。
2.结构体传参结构体传参的两种方式,传结构体、传地址。
struct Stu
{
char name[20];//姓名
char sex[5];//性别
char id[10];//学号
int age;//年龄
} s1 = { "翠花","女","20060238",21 };
//结构体传参
void print1(struct Stu s) {
printf("name=%s sex=%s id=%s age=%d\n", s1.name, s1.sex, s1.id, s1.age);
}
//结构体地址传参
void print2(const struct Stu* p) {
printf("name=%s sex=%s id=%s age=%d\n", p->name, p->sex, p->id, p->age);
}
int main()
{
print1(s1); //传结构体
print2(&s1); //传地址
return 0;
}
毫无疑问,这两种传参方式都是没问题的。 但是当我们需要结构体传参时,建议首选地址传参。因为函数传参的时候,形参是实参的一份临时拷贝,需要开辟和实参相同大小的一块空间。参数是需要压栈的。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,会导致性能的下降。但是地址传参的弊端是指针指向的内容可能会被改变,如果不希望这样的情况发生,用const修饰即可。const可以保护被修饰的东西,防止意外修改,增强程序的健壮性。
3.结构体的内存对齐 3.1如何计算结构体的大小:想要计算结构体的大小,首先要掌握结构体的对齐规则。
规则1:结构体的第一个成员,对齐到相对于结构体变量起始位置为0的偏移处。
规则2:从第二个成员开始,要对齐到某个(对齐数)整数倍的偏移处。
对齐数:结构体成员自身大小和默认对齐数的较小值。
默认对齐数:VS环境下的默认对齐数是8,(这个默认对齐数可以根据你的需求进行修改,后面会继续讨论)。Linux环境下默认不设有对齐数(默认对齐数的大小就是成员本身的大小)。
规则3:结构体的总大小,必须是最大对齐数的整数倍。
最大对齐数:取所有成员对齐数中最大的一个作为最大对齐数。
规则4:如果结构体中嵌套了结构体
4.1:嵌套的结构体对齐到自身最大对齐数的整数倍。
4.2:结构的总大小:所有对齐数中(包括嵌套结构体中的对齐数)中,取最大的一个作为最大对齐数。
3.2计算结构体大小练习
练习1:
练习2://练习1 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; }
S1绘图分析:
结构体的第一个成员c1对齐到0偏移处,第二个成员的对齐数是4,对齐到4的整数倍。
第三个成员的对齐数是1,对齐到1的整数倍既可。最后发现结构体占用的空间大小是9不是最大对齐数4的整数倍,继续占用空间直至结构体的总大小是最大对齐数的整数倍。计算结果:12。
S2绘图分析:
结构体的第一个成员c1对齐到0偏移处,第二个成员c2的对齐数是1,对齐到1的整数倍。
第三个成员的对齐数是4,对齐到4的整数倍。最终结体占用的空间大小是8,是最大对齐数4的整数倍。计算结果8。
答案:
综上所述,我们可以发现一个问题,S1和S2类型的成员一模一样,仅仅是顺序不同,但是S1和S2所占空间的大小有了一些区别。所以当我们自己设计结构体的时候,可以让占用空间小的成员尽量集中在一起,这样就可以有效的节省空间。
练习3:struct S3 { double d; char c; int i; }; int main() { printf("%d\n", sizeof(struct S3)); return 0; }
S3绘图分析:
结构体的第一个成员d对齐到0偏移处,第二个成员c的对齐数是1,对齐到1的整数倍。
第三个成员的对齐数是4,对齐到4的整数倍。最终结体占用的空间大小是16,是最大对齐数8的整数倍。计算结果16。
3.3修改默认对齐数
struct S3 { double d; char c; int i; }; //练习4-结构体嵌套问题 struct S4 { char c1; struct S3 s3; double d; }; int main() { printf("%d\n", sizeof(struct S4)); return 0; }
S4绘图分析:
S4的第一个成员c1对齐到0偏移处,第二个成员是结构体,嵌套的结构体对齐到自身最大对齐数8的整数倍。第三个成员的对齐数是8,对齐到8的整数倍。结构体中嵌套结构体:结构总大小为所有对齐数中(包括嵌套结构体中的对齐数)中,取最大的一个作为最大对齐数。本题中的最大对齐数是8。32是8的整数倍。计算结果:32
在上面提过,结构在对齐方式不合适的时候,可以自己更改默认对齐数。
使用 #pragma 这个预处理指令,可以改变默认对齐数。#pragma pack(2)//设置默认对齐数为2 struct S1 { char c1; int i; char c2; }; #pragma pack(1)//设置默认对齐数为1 struct S2 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认8 struct S3 { char c1; int i; char c2; }; int main() { printf("默认对齐数为2时:%d\n", sizeof(struct S1)); printf("默认对齐数为1时:%d\n", sizeof(struct S2)); printf("默认对齐数为8时:%d\n", sizeof(struct S3)); return 0; }
分析的过程同上,这里就不画图了。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)