自定义类型:结构体

自定义类型:结构体,第1张

1.结构体的声明 1.1什么是结构体

在我们编写程序的过程中,可能会遇到一些复杂的对象,例如:当我们描述一个学生时,姓名,性别,年龄,学号等都可以是这个学生的信息。用单个类型不能够很好的描述他,这时就可以定义一个结构体。结构体是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。

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:

//练习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。

答案:

 综上所述,我们可以发现一个问题,S1S2类型的成员一模一样,仅仅是顺序不同,但是S1S2所占空间的大小有了一些区别。所以当我们自己设计结构体的时候,可以让占用空间小的成员尽量集中在一起,这样就可以有效的节省空间。

练习2:
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:

 

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

 

3.3修改默认对齐数

在上面提过,结构在对齐方式不合适的时候,可以自己更改默认对齐数。 

使用  #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;
}

分析的过程同上,这里就不画图了。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/2991413.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-23
下一篇 2022-09-23

发表评论

登录后才能评论

评论列表(0条)

保存