目录
- 前言
- 结构体类型的声明
- 什么是结构
- 结构的声明
- 结构体变量的定义
- 结构成员的类型
- 结构体变量的初始化
- 结构体的成员访问
- 结构体传参
- 函数调用的参数压栈
前言
在前面的C语言学习中,我们学习了形如char,short,int,float等的不同类型的变量,但是当我们要描述一个人的时候,我们发现单靠其中的某一种类型的变量很难完整的描述出一个人来,人有年龄,性别,身高,身份证编号等等的信息,所以人是一个复杂的对象,这时我们就要引出结构体类型,它可以把要描述的对象的不同信息放到里面去
结构体类型的声明
什么是结构
结构是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。
到目前为止,我们学习的数组也是一个集合,数组是一组相同元素的集合。而结构体也是一些值的集合,但是这些值的类型可以不同。
结构的声明
结构的声明框架如下
struct tag
{
member-list;
}variable-list;
假设我们要来定义一个人,看下面这段代码
struct Stu
{
//成员变量
char name[20];//名字
int age;//年龄
char id[20];//身份证号
};
注意,在这段代码的最后有一个分号,这是因为结构体类型也是一种类型,类比于int类型,我们在对int类型进行声明的时候最后也会用到分号,如图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
我们不难发现,(*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
我们在函数传参的学习中,学过两种调用方法:传值调用和传址调用。上面代码实现的是传值调用,我们再来写一段代码实现传址调用
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
那么在两种方法都能实现的情况下,是传值调用好呢还是传址调用好?
在前面的学习中,我们知道,传址调用要高效的多。传址调用中,形参要接收传递过来的实参,还得创建一个空间t,那么s有多大,t就有多大。而且·传递这么多数据还会花费一定的时间,在空间和时间上都有较大的浪费但是,如果我们传递的是地址,地址就只有4/8个字节,那么空间开辟就大大减少,而且如果是值传递,t只是s的一份临时拷贝,改变t的值并不会影响到s,而如果将地址传递过去,我们可以直接通过地址找到s,从而能直接修改s的数据,功能性更强一些。
还有一方面的原因:
函数传参的时候,参数是需要压栈的。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
函数调用的参数压栈
什么是压栈呢?
栈是一种数据结构,先进后出,后进先出(类似于装子弹),如图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
从图中我们可以看出,如果是传值调用,参数压栈的开销就会比较大,性能就会下降。
以上就是对结构体类型的讲解,如有出入,欢迎指正。