结构体初阶
一、结构体类型的声明
1、什么是结构?
数组是一组相同类型的元素的集合。
结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量。
2、结构的声明
问题:什么是类型?
这是我们创造出的一个类型struct Stu
。(分号不能丢)
如果我们用int类型创造一个变量的时候会使用:int a = 10;
。意思是:我们使用类型int
创造了一个变量a。
struct Stu
{//结构体的相关属性——结构的成员变量
char name[20];//名字
int age;//年龄
char id[20];//学号
};
int main()
{
struct Stu s;//使用类型struct Stu创造了一个对象s
return 0;
}
我们拿类型struct Stu
创建出对象s
的时候,就相当于我们按照图纸盖起了一栋房子。房子里的一个个房间就相当于对象s中各有一部分空间存放name、age和id。
struct Stu
{
char name[20];//名字
int age;//年龄
char id[20];//学号
}s1,s2;
s1和s2也是结构体变量,因为在{}外面创建,所以s1和s2还是全局变量。如果要创建局部的结构体变量的话只能按照下面这种方法。
int main()
{//s是局部变量
struct Stu s;//对象s
return 0;
}
3.结构成员的类型:
结构成员的类型可以是标量(int age = 10;中的age
)、
数组、
指针,
甚至是其他结构体。
比如说:
struct B
{
char c;
short s;
double d;
};
struct Stu
{
struct B tea;//在这里面有一个结构体成员tea,它的类型是 struct B
char name[20];
int age;
char id[20];
};
二、结构体变量的初始化
结构体中放置的是多个成员,和数组初始化一样,结构体初始化用的也是花括号{}
。
struct B
{
char c;
short s;
double d;
};
struct Stu
{
struct B tea;//在这里面有一个结构体成员tea,它的类型是 struct B
char name[20];
int age;
char id[20];
};
int main()
{
struct Stu s = { {'w', 20 , 3.14}, "张三", 30, "20220214"};
return 0;
}
struct Point
{
int x;
int y;
};
struct Node
{
int data;
struct Point p;
struct Node* next;//指针
}n1 = { 10, {4, 5}, NULL };//结构体嵌套初始化,指针next设置为空。
三、结构体成员访问
结构体变量访问成员:结构变量的成员是通过2个操作符访问的,分别是.
和->
。
struct B
{
char c;
short s;
double d;
};
struct Stu
{
struct B tea;//在这里面有一个结构体成员tea,它的类型是 struct B
char name[20];
int age;
char id[20];
};
int main()
{
struct Stu s = { {'w', 20 , 3.14}, "张三", 30, "20220214"};
printf("%c\n", s.tea.c);//w是放在struct B里面的。我们先在s里面找到tea,然后在tea里面找到c。
printf("%s\n", s.id);//打印学号
return 0;
}
而->
操作符多应用于结构体指针。当我们需要获取s的地址时使用struct Stu* ps = &s;
,*
说明ps是指针,struct Stu
表示ps所指对象是结构体类型。
int main()
{
struct Stu s = { {'w', 20 , 3.14}, "张三", 30, "20220214"};
//printf("%c\n", s.tea.c);
//printf("%s\n", s.id);
struct Stu* ps = &s;
printf("%c\n", (*ps).tea.c);//法一:使用ps解引用找到s,即*ps就是s
printf("%c\n", ps->tea.c);//法二:先用ps找到s,然后在找到tea。
//因为tea是结构体变量,不是指针,所以tea访问成员变量c用 . 就可以。
return 0;
}
四、结构体传参
写一个函数打印s的内容:
case1:传值访问
使用print1()函数,结构体对象s作为参数。
void print1(struct Stu t)//t用来接收s
{
printf("%c %d %lf %s %d %s\n", t.tea.c, t.tea.s, t.tea.d, t.name, t.age, t.id);
}
int main()
{
struct Stu s = { {'w', 20 , 3.14}, "张三", 30, "20220214" };//s是局部变量
print1(s);
return 0;
}
我们把s传给t,那么t就获得s的全部的值,则打印t的内容就是在打印s的内容。
运行结果:
case2:传址访问
使用print2()函数,结构体对象s的地址&s作为参数。
void print2(struct Stu* ps)//传递的是s的地址,所以形参ps一定是指针形式
{
printf("%c %d %lf %s %d %s\n", ps->tea.c, ps->tea.s, ps->tea.d, ps->name, ps->age, ps->id);
}
int main()
{
struct Stu s = { {'w', 20 , 3.14}, "张三", 30, "20220214" };//s是局部变量
print1(s);
print2(&s);
return 0;
}
运行结果:
函数print1()
和print2()
两者是一模一样的结果。
如果我们采用print1()函数进行传值调用,main()函数中的s作为一个对象会在内存中开辟一段空间,t作为形参要能接住实参s传过来的内容,势必要在内存中也开辟一个和实参s一样大内存空间,用于存储来自s的数据。s有多大,t就要有多大;s有多少数据,t就要有多少数据。这在空间和时间上都有一定的浪费,会导致性能的下降。
反之如果我们采用print2()函数的方法进行传址调用,把s的地址&s传给print2()函数,则会使用指针变量接收。在32位平台上,指针变量占4个字节;在64位平台上,指针变量占8个字节。除此以外没有额外开销,且地址返回后仍然指向s。
综合比较,传址调用的传参效率更高,但是在print2()函数中修改的内容会使对象s发生相应的改变。传值调用的传参效率慢,但是对t进行的任何操作都不会修改s的值。