【C语言】结构体详解:声明、使用与优化技巧

C语言学习

结构体基础
友情链接:C语言专栏


文章目录


前言:

结构体是C语言中组织复杂数据的利器。本文将用最精炼的方式,带你快速掌握结构体的核心用法:从基础声明到自引用技巧,从成员访问到传参优化。让你彻底玩转结构体编程!


一、结构体类型的声明

1.1 结构的基础知识

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

1.2 结构的声明

**struct解释:**在C语言中,struct 是一个关键字,用于声明和定义结构体类型。
结构体描述学生:

c 复制代码
struct student
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	int id[20];//学号
};

咱们还可以利用关键字typedef,对这个类型重命名,创建更简洁的类型名,代码实现:

c 复制代码
typedef struct student
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	int id[20];//学号
}Stu;

可知:此时struct studentStu等效,这两种声明方式是等价的,都会创建相同类型的结构体变量。

1.3 特殊的声明

在声明结构的时候,可以不完全的声明,即结构在声明的时候省略掉了结构体标签(tag)。

c 复制代码
struct
{
	int a;
	char b;
	float c;
}x;
struct
{
	int a;
	char b;
	float c;
}a[20], * p;

那么问题来了,咱们这样写的话 p = &x 可以吗?

答案是不行,解释:

编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。

1.4 结构的自引用

**结构的自引用解释:**在结构中包含一个类型为该结构本身的成员。

看代码:

c 复制代码
struct Node
{
	int data;
	struct Node next;
};

这种写法是否可行?

如果可以,那sizeof(struct Node)是多少?很显然,它会无限递归。
禁止直接嵌套:结构体不能包含自身类型的完整对象(会导致无限递归)。

那么咱们应该怎么来嵌套呢,指针,指针在固定的机器下是固定的。所以咱们可以这样来自引用:

c 复制代码
struct Node
{
	int data;
	struct Node* next;//利用指针来实现自引用
}

咱们再来看个代码:

c 复制代码
//是否可行?
typedef struct
{
	int data;
	Node* next;
}Node;

答案是不行,会报错,解释:

类型定义的顺序问题,

在typedef完成之前(typedef在遇到分号;时才算真正完成),Node这个名字还不存在。

但在结构体内部,已经尝试使用Node*,此时编译器会报错。

所以咱们应该怎么写呢?看正确代码:

c 复制代码
typedef struct Node 
{ 
    int data;
    struct Node* next;  // 使用"struct Node"引用自身
} Node;                // typedef别名

1.5 结构成员的类型

结构的成员可以是标量、数组、指针,甚至是其他结构体。

1.6 结构体变量的定义和初始化

有了结构体类型,那如何定义变量,其实很简单。咱们以前的int、char、......怎么定义,结构体变量就怎么定义。

咱们还要辨析一个概念,结构体类型与咱们已知的int、char、float......都是相似的,只不过结构体类型是咱们自己根据需求来自己定义的类型而已。即结构体是用户根据需求自定义的复合类型

那咱们来看看结构体变量的定义和初始化:

c 复制代码
//1. 声明类型的同时定义变量p1
struct Point
{
	int x;
	int y;
}p1; 
//2. 单独定义结构体变量p2
struct Point p2; 

//初始化:以上两种定义变量同时都可以赋初值(初始化)。
struct Point p3 = { 1, 2 };

//结构体2声明
struct Stu //类型声明
{
	char name[15];//名字
	int age; //年龄
};
struct Stu s = { "zhangsan", 20 };//初始化

//结构体也可以嵌套定义和初始化
struct Node
{
	int data;
	struct Point p;//结构体嵌套定义
}n1 = { 10, {4,5} }; //结构体嵌套初始化
struct Node n2 = { 20, {5, 6} };//结构体嵌套初始化

二、结构体成员的访问

咱们现在已知结构的声明、结构体的定义......那么怎么修改、访问结构体成员呢?

这里主要涉及到两个操作符. ->,咱们来看看怎么用呢?
结构体变量访问成员

结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数。

看代码:

c 复制代码
typedef struct Stu
{
	char name[20];
	int age;
}Stu;
int main()
{
	Stu s;//定义结构体变量
	strcpy(s.name, "zhangsan");//使用.访问name成员
	//注意:这里不能写为s.name = "zhangsan"
	s.age =18;//使用.访问age成员
	return 0;
}

看下调试结果:

结构体指针访问指向变量的成员

有时候我们得到的不是一个结构体变量,而是指向一个结构体的指针。

那该如何访问成员。

看代码:

c 复制代码
typedef struct Stu
{
	char name[20];
	int age;
}Stu;
void print(Stu* ps)
{
	//结构体变量直接访问成员
	printf("name = %s age = %d\n", (*ps).name, (*ps).age);
	//使用结构体指针访问指向对象的成员
	printf("name = %s age = %d\n", ps->name, ps->age);
}
int main()
{
	Stu s = { "zhangsan", 18 };
	print(&s);//结构体地址传参
	return 0;
}

输出:

三、结构体传参

直接看代码:

c 复制代码
struct S
{
	int data[1000];
	int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
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(&s); //传地址
	return 0;
}

上面的print1print2函数哪个好些?

答案是:首选print2函数。

原因:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的

下降。

总结:

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


总结

希望本文能帮助你建立扎实的结构体知识体系,为后续的C语言开发打下坚实基础。如有任何疑问,欢迎在评论区交流讨论!

附录

上文链接

《浮点数在内存中的存储:从科学计数法到内存存储》

下文链接

《结构体内存对齐:为什么你的结构体比想象中更大?》

专栏

C语言专栏