C语言--结构体初阶

前言

在前面的C语言学习中,我们学习了形如char,short,int,float等的不同类型的变量,但是当我们要描述一个人的时候,我们发现单靠其中的某一种类型的变量很难完整的描述出一个人来,人有年龄,性别,身高,身份证编号等等的信息,所以人是一个复杂的对象,这时我们就要引出结构体类型,它可以把要描述的对象的不同信息放到里面去

结构体类型的声明

什么是结构

结构是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。
到目前为止,我们学习的数组也是一个集合,数组是一组相同元素的集合。而结构体也是一些值的集合,但是这些值的类型可以不同

结构的声明

结构的声明框架如下

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

假设我们要来定义一个人,看下面这段代码

struct Stu
{
    
    
	//成员变量
	char name[20];//名字
	int age;//年龄
	char id[20];//身份证号
};

注意,在这段代码的最后有一个分号,这是因为结构体类型也是一种类型,类比于int类型,我们在对int类型进行声明的时候最后也会用到分号,如图1.
图1

结构体变量的定义

有了结构体类型,那如何定义变量,其实很简单。
我们来创建一个对象 s

struct Stu
{
    
    
	//成员变量
	char name[20];//名字
	int age;//年龄
	char id[20];//身份证号
};

int main()
{
    
    
	struct Stu s;//对象
	return 0;
}

这段代码我们通过类型创建了一个对象出来,这就和现实生活中的通过图纸来造一个房子很类似,类型中有我们对这个对象的描述,而图纸中也有我们对房子的描述,客厅,厨房的位置等等。

我们现在反回去看结构体的声明框架,发现在大括号外还可以定义变量,如下面这段代码的s1,s2

struct Stu
{
    
    
	//成员变量
	char name[20];//名字
	int age;//年龄
	char id[20];//身份证号
}s1,s2;

int main()
{
    
    
	struct Stu s;//对象
	return 0;
}

这里的s1和s2也是结构体变量,只不过和s不同的是,s1,s2是全局变量,而s是局部变量

结构成员的类型

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

看下面这段代码

struct B
{
    
    
	char c;
	short s;
	double d;
};
struct Stu
{
    
    
	//成员变量
	struct B sb;
	char name[20];//名字
	int age;//年龄
	char id[20];//身份证号
}s1,s2;

在Stu这个结构体里面就有一个结构体成员sb。

结构体变量的初始化

类比数组的初始化,数组的初始化使用到了大括号,结构体变量的初始化也是用的大括号,然后我们只用按照结构体内的成员变量类型挨个进行初始化即可。如下面这段代码

struct B
{
    
    
	char c;
	short s;
	double d;
};
struct Stu
{
    
    
	//成员变量
	struct B sb;
	char name[20];//名字
	int age;//年龄
	char id[20];//身份证号
}s1,s2;

int main()
{
    
    
	struct Stu s = {
    
    {
    
    'w',20,3,14},"张三",30,"51192430020987"};
	return 0;
}

成员变量有结构体,就再用一个大括号对这个结构体初始化即可。
在对s初始化的时候我们同样可以对s1,s2初始化,这就叫做结构体的嵌套初始化

struct Stu        //类型声明
{
    
    
 char name[15];//名字
 int age;      //年龄
};
struct Stu s = {
    
    "zhangsan", 20};//初始化
struct Node
{
    
    
 int data;
 struct Point p;
 struct Node* next; 
}n1 = {
    
    10, {
    
    4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {
    
    20, {
    
    5, 6}, NULL};//结构体嵌套初始化

结构体的成员访问

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

struct B
{
    
    
	char c;
	short s;
	double d;
};
struct Stu
{
    
    
	//成员变量
	struct B sb;
	char name[20];//名字
	int age;//年龄
	char id[20];//身份证号
}s1,s2;

int main()
{
    
    
	struct Stu s = {
    
     {
    
    'w',20,3.14},"张三",30,"51192430020987" };
	printf("%c\n", s.sb.c);

	struct Stu* ps = &s;
	printf("%c\n",(*ps).sb.c);
	printf("%c\n", ps->sb.c);
	return 0;
}

代码运行结果如图2
图2
我们不难发现,(*ps).sb.c和ps->sb.c是等价的

结构体传参

我们在实现某些代码的时候,这个代码我们可能不会立马就要处理,可能要传给一个函数进行处理。
我们假设要写一个函数来打印s,看下面这段代码

void print1(struct Stu t)
{
    
    
	printf("%c %d %lf %s %d %s\n", t.sb.c, t.sb.s, t.sb.d, t.name, t.age, t.id);
}
int main()
{
    
    
	struct Stu s = {
    
     {
    
    'w',20,3.14},"张三",30,"51192430020987" };
	print1(s);
	return 0;
}

代码运行结果如图3
图3
我们在函数传参的学习中,学过两种调用方法:传值调用和传址调用。上面代码实现的是传值调用,我们再来写一段代码实现传址调用

void print2(struct Stu* t)
{
    
    
	printf("%c %d %lf %s %d %s\n", t->sb.c, t->sb.s, t->sb.d, t->name, t->age, t->id);
}
int main()
{
    
    
	struct Stu s = {
    
     {
    
    'w',20,3.14},"张三",30,"51192430020987" };
	print2(&s);
	return 0;
}

代码运行结果如图4
图4
那么在两种方法都能实现的情况下,是传值调用好呢还是传址调用好?
在前面的学习中,我们知道,传址调用要高效的多。传址调用中,形参要接收传递过来的实参,还得创建一个空间t,那么s有多大,t就有多大。而且·传递这么多数据还会花费一定的时间,在空间和时间上都有较大的浪费但是,如果我们传递的是地址,地址就只有4/8个字节,那么空间开辟就大大减少,而且如果是值传递,t只是s的一份临时拷贝,改变t的值并不会影响到s,而如果将地址传递过去,我们可以直接通过地址找到s,从而能直接修改s的数据,功能性更强一些
还有一方面的原因:
函数传参的时候,参数是需要压栈的。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

函数调用的参数压栈

什么是压栈呢?
栈是一种数据结构,先进后出,后进先出(类似于装子弹),如图5
图5
传参的时候也是类似,看下面这段代码

int Add(int x, int y)
{
    
    
	int z = 0;
	z = x + y;
	return z;
}//每一个函数调用都会在内存的栈区上开辟一块空间

int main()
{
    
    
	int a = 3;
	int b = 5;
	int c = 0;
	c = Add(a, b);
	return 0;
}

栈区 如图6
图6
从图中我们可以看出,如果是传值调用,参数压栈的开销就会比较大,性能就会下降。

以上就是对结构体类型的讲解,如有出入,欢迎指正。

猜你喜欢

转载自blog.csdn.net/m0_75233943/article/details/128811177