自定义类型之结构体

发布于:2025-04-16 ⋅ 阅读:(39) ⋅ 点赞:(0)

1.结构体类型概述

        结构体类型是一种用户自定义的数据类型,用于将不同类型的数据组合成一个整体。在C语言中,结构体使用struct关键字定义,由一系列具有相同类型或不同类型的数据构成的数据集合,也称为结构。结构体中的数据在逻辑上是相互关联的,每个数据称为结构体的成员,成员可以有不同的数据类型,并且成员一般通过名字进行访问

2.结构体类型的特点

        每个成员具有独立的数据类型:这意味着可以在一个结构体内混合不同类型的数据,如整数、浮点数、字符数组等。

        定义结构体时,成员数量必须固定:一旦结构体类型被定义,其成员的数量和类型就不能改变。

        成员名固定唯一且不可与结构体类型相同:每个成员必须有一个唯一的名称,且这个名称不能与结构体类型名相同。

  • 结构体类型的声明
  • 结构体类型的自引用
  • 结构体类型变量的定义、初始化和访问
  • 结构体内存对齐
  • 结构体传参

3. 结构体类型的声明

#define  _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

//方法一
struct Stu
{
	char name[20];
	float score;
	int age;
}s1,s2;//全局变量

int main()
{
	//方法二
	struct Stu s3, s4;//全局变量
	return 0;
}

在C语言中,结构体(struct)是一种用户自定义的数据类型,它允许将多个不同类型的变量组合在一起。当你频繁地使用结构体时,每次都输入struct可能会显得繁琐。为了解决这个问题,C语言提供了typedef关键字,可以对结构体类型进行重命名,从而简化结构体的使用

#define  _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

//方法一
typedef struct 
{
	char name[20];
	float score;
	int age;
}Stu;//全局变量

int main()
{
	//方法二
	 Stu s3, s4;//全局变量
	return 0;
}

注:s1,s2,s3,s4是结构体变量

4. 结构体类型的自引用(引用同类型的下一个节点)

我们先来看一下链表中的数据结构:

我们首先来定义一个整型数组:int arr[]={ 1, 2, 3, 4, 5 };

那么在结构体的自引用中我们用到的就是链表

我们通过结点在结构体中寻找下一个结点

struct Node
{
	int data;
	struct Node next;
};

如果我们这样写,就有一个大问题,就是这个结构体的大小不确定,如果结点过多,大小会一直大下去

那么我们怎么来优化呢?

解答:我们可以使用指针,因为指针在编译器中的大小是固定的4/8字节

struct Node
{
	int data;
	struct Node* next;
};

5. 结构体类型变量的定义、初始化和访问

a. 结构体指针的定义

#define  _CRT_SECURE_NO_WARNINGS 1

//方法一
struct Point
{
	int a;
	int b;
}p1;

//方法二
struct Point p2;

int main()
{
	//方法三
	struct Point p3;
	return 0;
}

b. 结构体指针的初始化

#define  _CRT_SECURE_NO_WARNINGS 1

//方法一
struct Point
{
	int a;
	int b;
}p1 = {1,2};

//方法二
struct Point p2 = {3,4};

int main()
{
	//方法三
	struct Point p3 = {5,6};
	return 0;
}

c. 结构体指针的访问

#define  _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

struct Point
{
	int x;
	int y;
};

struct Stu
{
	int num;
	char ch;
	struct Point p;
	float d;
};

int main()
{
	//顺序访问
	struct Stu s1 = { 100,'a',{2,5},3.3 };
	//乱序访问
	struct Stu s2 = { .d = 8.9,.num = 245,.ch = 'h',.p.x = 55,.p.y = 78 };
	return 0;
}

6. 结构体内存对齐

我们先来看一下这个小题

#define  _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

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

struct S2
{
	char c1;
	char c2;
	int a;
};

int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));

	return 0;
}

输出结果:

在我们的认识中,char为1个字节,int为4个字节,那么两个都应该是6才对,那为什么确实不为6且不相同的数呢?

解答:

a. 结构体内存对齐规则

  • 第一个成员的对齐:结构体的第一个成员通常从偏移量0的地址开始存储。
  • 后续成员的对齐:其他成员变量的存储地址需要是对齐数的整数倍。

对齐数是编译器默认的对齐数和该成员自身大小的较小值。

  • 结构体总大小的对齐:整个结构体的大小也会按照最大对齐数的整数倍进行对齐。这个最大对齐数是所有成员中自身对齐值最大的那个,以及指定对齐值中较小的一个。
  • 嵌套结构体的对齐:如果结构体内部有嵌套的结构体,那么嵌套的结构体也会按照上述规则进行对齐,最终结构体的整体大小将是所有最大对齐数(包括嵌套结构体的对齐数)的整数倍。
  • 编译器默认对齐数:在不同的编译器和平台上,默认的对齐数可能不同。例如,在Linux系统中,默认对齐数可能是4字节,而在某些64位系统或Visual Studio编译器中,默认对齐数可能是8字节

  • 结构的总大小,必须是所有成员的对齐数的整数倍

在这里我们可以看见这里只占了9个内存,还不足以满足对齐数的所有条件

b. why:为什么存在内存对齐数呢?

ⅰ. 提高CPU访问数据的效率

内存对齐可以提高CPU访问数据的效率。这是因为CPU在访问内存时通常是以固定大小的块(如4字节或8字节)进行读取的。如果数据按照特定的对齐规则进行排列,CPU可以在一次访问中读取到完整的数据块,从而减少访问次数,提高效率。例如,如果一个int类型的数据位于4字节的边界上,那么在32位系统中,CPU可以在一个读周期内读取到这个int数据,而不需要进行额外的操作来拼接数据2。

ⅱ. 兼容性和可移植性

不同的硬件平台可能有不同的内存访问限制。有些平台只能在特定地址上访问特定类型的数据,否则可能会引发硬件异常。内存对齐可以确保程序在不同硬件平台上的一致性和可移植性。例如,Windows操作系统默认的对齐数是8,而Linux默认的对齐数是4。

ⅲ. 减少内存碎片

内存对齐有助于减少内存碎片的产生。通过将数据按照对齐规则进行排列,可以避免数据分散在内存中的各个角落,形成难以利用的小块内存,从而提高内存的利用率3。

ⅳ. 结构体和联合体的对齐规则

在C和C++编程中,结构体和联合体的成员通常需要按照一定的对齐规则进行排列。这些规则包括数据成员的起始位置、结构体成员的对齐方式以及结构体总大小的对齐方式。通过对齐,可以确保结构体和联合体在内存中的布局符合特定的硬件平台要求,从而提高程序的执行效率和可移植性

c. 修改默认对齐数

我们用到 #pragma

#define  _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
#pragma pack(1)//设置默认对齐数

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

#pragma pack()//恢复默认对齐数

struct S2
{
	char c1;
	char c2;
	int a;
};


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

输出结果:

7. 结构体传参

a. 结构体传参分为两个方式

  • 结构体传参
  • 结构体地址传参
#define  _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

struct S
{
	int a;
	int b;
};

struct S s= { 4,5 };

//结构体传参
void Print1(struct S s)
{
	printf("%d\n", s.a);
}
//结构体地址传参
void Print2(struct S* pc)
{
	printf("%d\n", pc->b);
}
int main()
{
	Print1(s);
	Print2(&s);
	return 0;
}

那么结构体传参和结构体地址传参我们推荐哪一个呢?

答:推荐结构体地址传参

b. 为什么推荐结构体地址传参

在C语言中,结构体是一种复合数据类型,它可以包含多个不同类型的成员。当需要将结构体作为参数传递给函数时,有两种主要的方式:传值和传地址。下面我们将探讨为什么通常推荐使用结构体地址传参。

  • 减少内存开销
    当通过值传递结构体时,函数会收到结构体的一个完整副本。如果结构体非常大,这可能会导致大量的内存被不必要的复制,增加了内存开销。相比之下,通过地址传递结构体只需要传递一个指向结构体的指针,这通常只需要4字节(32位系统)或8字节(64位系统),大大减少了内存占用
  • 提高性能
    由于通过值传递结构体会创建结构体的副本,这不仅消耗更多的内存,还会增加CPU的负担,因为它需要额外的时间来进行数据复制。而通过地址传递则避免了这种复制,提高了程序的执行效率
  • 改变原始数据
    如果希望在函数内部对结构体的成员进行修改,并且这些修改应该反映在调用函数的上下文中,那么必须通过地址传递结构体。因为通过值传递只会传递结构体的副本,任何对副本的修改都不会影响原始结构体
  • 代码简洁性
    通过地址传递结构体可以使代码更加简洁和易读。例如,可以使用指向结构体的指针来调用结构体的方法,这在面向对象编程风格的C代码中很常见

网站公告

今日签到

点亮在社区的每一天
去签到