一篇文章介绍结构体

这段时间迷迷糊糊学了指针,学校的考试范围还有结构体,然鹅我发现我学习了一些知识之后,并没有很好地掌握,于是乎,打算写一篇文章来巩固已学知识,并在文末总结学校作业里的写错的题目。(晚上还要苦苦复习高数和线代)

什么是结构?结构是一些值的集合。每个成员可以是不同类型的变量(数组是相同类型的集合)。

struct tag
{
   member-list;
}variable-list;

结构体类型的定义

写法1: 

struct Stu
{
	//学生的属性(成员列表)
	char name[20];
	int age;
};//无变量列表也可以

建立一个类型。

写法2:

struct Stu
{
	//学生的属性(成员列表)
	char name[20];
	int age;
}s1,s2;//无变量列表也可以

s1和s2利用上面的结构体类型,创建一个结构体变量,是struct Stu类型的变量(此时是结构体的全局变量)。

int main()
{
Struct Stu s3;
return 0;
}

此时是局部变量。

一些特殊的声明 

匿名结构体类型

struct 
{
	char name[20];
	int age;
}s1;

匿名结构体类型用一次就不能用了。

struct Node
{
	int data;//定义一个节点的时候,既能够包含一个数值,又能找到下一个节点
	struct Node *next;
};
struct
{
 int a;
 char b;
 float c;
}x;
struct
{
 int a;
 char b;
 float c;
}a[20], *p;
int main()
{
 p=&x;
 return 0;
}

当两个匿名结构体变量相同时, 我们把x的地址放在p里。此时编译器会报警告:从*到*的类型不兼容。

在数据结构中有一个链表的概念,链表中有顺序表。我们只需要让每一节点包含下一个节点,就能使每一个节点找到下一个节点。

struct Node
{
	int data;//定义一个节点的时候,既能够包含一个数值,又能找到下一个节点
	struct Node next;
};//但是这个结构体的字节无法计算,我们无法直接求出它的大小
int main()
{
	return 0;
}

但我们可以选择把下一个节点的地址给上一个节点,这样也可以使不同节点串起来。此时,一个节点被分为两个部分,一个部分存放数值,被称为数值域,另一个部分存放地址,被称为指针域。

一篇文章介绍结构体_第1张图片

struct Point
{
	int x;
    int y;
}p1 = { 3,2 };

struct score
{
	int n;
	char ch;
};

struct Stu
{
	char name[20];
	int age;
	char score s;
};
int main()
{
	struct Point p2 = { 3,4 };
	struct Stu s1 = { "zhangsan",20,{100,'q'}};
    printf("%s %d %d %c",s1.name,s1.age,s1.s.n,s1.s.ch);
}

 这样程序就可以打印了。

结构体内存对齐

这部分内容学校的书上甚至提都没有提,但是不妨碍我们要好好掌握。

struct S1
{
	char c1;
	int i;
	char c2;
};

int main()
{
	printf("%d\n", sizeof(struct s1));
	return 0;
}

计算s1所占字节数,我们可能会下意识认为:char占1个字节,int占8个字节,所以一共占10个字节。But totally wrong!结果是12。出现这个结果的原因是什么呢?

接下来我们介绍结构体对齐的情况:

1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

   对齐数 = 编译器默认的⼀个对⻬数 与 该成员变量大小的较小值。

- VS 中默认的值为 8 -

Linux中 gcc 没有默认对齐数,对齐数就是成员⾃⾝的大小

3. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对⻬数中最⼤的)的 整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。

什么是偏移量? 

一篇文章介绍结构体_第2张图片

第一个位置相对于起始位置的偏移量为0,第二个位置为1,以此类推。 

以struct1为例,演示其在内存中的存储过程。首先,char的内存数为1,与vs编译器默认8,取较小值为1,同理int类型为4,char类型为1。c1先在内存中占偏移量为0的位置,再看i,i需要在偏移量为4的位置占4个字节,但目前其偏移量为1,所以需要浪费3个字节。接着i在偏移量为4的位置占4的字节,此时c2的偏移量也为1,无论如何,都满足偏移量为1,所以接着向下存。但是此时为9,并不是正确答案,这是为什么呢?再看第三条规则:结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对⻬数中最⼤的)的 整数倍。此时,三个对齐数分别为1,1,4.。 最大对齐数为4,其整数倍为12。所以,我们还需要浪费3个字节。

一篇文章介绍结构体_第3张图片

如果我们想看我们分析的结果是否正确?如何做呢。

一篇文章介绍结构体_第4张图片

我们可以使用这个函数,来计算其所占内存空间。 

#include
struct S1
{
	char c1;
	int i;
	char c2;
};

int main()
{
printf("%d\n", sizeof(struct s1,c1));
printf("%d\n", sizeof(struct s1,i));
printf("%d\n", sizeof(struct s1,c2));

	return 0;
}

结果为0,4,8。

一篇文章介绍结构体_第5张图片

struct2为8。这也就表明,让占用空间的成员放在一起,会减小内存。一篇文章介绍结构体_第6张图片 

当s3放在s4内部,结果会是什么样呢?我们来看第四条规则: 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结(含嵌套结构体中成员的对⻬数)的整数倍。s3中最大对齐数为8。

一篇文章介绍结构体_第7张图片

这样就可以计算出此结构体的内存数为32。

内存对齐存在的原因

1移植原因。

2数据结构应尽可能在自然边界对齐。

 一篇文章介绍结构体_第8张图片

如果像左侧一样,不存在空间浪费地存放。编译器每次读取4个字节,像左侧还要读取两次才能读取到i,如果像右侧,只需要读取一次,中间必须浪费一些空间,得到效率上提升。 

修改默认对齐数

#pragma pack(8)//设置默认对齐数为8

#pragma pack()//取消默认对齐数,还原为默认

结构体传参

struct S
{
 int data[1000];
 int num;
};

void print1(struct S ss)
{
 int i=0;
 for(i=0;i<3,i++)
 {
     printf("%d",ss.data[i]);
 }
 printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S *ps)
{
 int i=0;
 for(i=0;i<3;i++)
 {
 printf("%d\n", ps->data[i]);
 }
}
int main()
{
struct S s = {{1,2,3}, 1000};
//结构体传参
 print1(s); //传结构体
 print2(&s); //传地址
 return 0;
}

print1(s)为传值调用,print2(&2)为传址调用。 一个地址的大小为4或8字节,效果更好。如果print1(s)传地址过去给ss,ss是不会影响s的,ps可能误操作s,这种实现方式不够安全。在struct S*p2前加上const就可以避免这种情况。函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下 降。使用指针的时候,这种情况就不会出现。

结论: 结构体传参的时候,要传结构体的地址。

以上就是结构体的全部内容,欢迎交流!2024快来了,新的一年也要加油呀。

你可能感兴趣的:(c语言)