零. 课程要点:
- 结构体
- 联合体
结构体和联合体其实本身并不复杂,但是常常和指针一起用,比较容易引起混淆,必须掌握结构体/联合体在参数传递时的用法。
一. 结构体
我们知道数组可以存储具有相同类型的数据,比如班级每个学生的分数;但有的时候我们希望存储由多种类型构成的数据集合呢?比如每个学生的信息,包括学号,姓名,住址等等,这个时候就可以用结构体来表示。
1. 结构体的定义与声明
struct STUDENT
{
char id[8];
char name[12];
unsigned post;
char address[100];
char phone[20];
};
struct STUDENT s1, s2[20], *s3;
上面定义了一个结构体STUDENT,然后声明了三个变量,第一个很简单,s1就是结构体;第二个回想数组的定义,我们就明白这里声明了一个数组s2,每个数组成员里存储的都是结构体;第三个类似的,回想指针的定义,这里声明了一个指针s3,它指向一个结构体的首地址。
其实,在C中更常见的是结合typedef来定义一个结构体,如下:
typedef struct
{
char id[8];
char name[12];
unsigned post;
char address[100];
char phone[20];
} STUDENT;
STUDENT s1, s2[20], *s3;
注意比较二者用法上的区别。声明之后就可以对变量进行初始化或访问。
2. 结构体的初始化
类似其它数据类型,结构体也可以在定义时初始化:
struct STUDENT
{
char id[8];
char name[12];
unsigned post;
char address[100];
char phone[20];
} s1 = {"1234567", "ZhangSan", 201201, "Shanghai", "15123456789"};
STUDENT s2 = {"1234569", "WangWu", 364000, "Fujian", "13876543210"};
当然,也可以逐个成员初始化,这就要介绍结构体的访问。
3. 结构体成员的访问
结构体成员的访问可以使用运算符.
,如下所示:
struct STUDENT
{
char id[8];
char name[12];
unsigned post;
char address[100];
char phone[20];
} s1;
strcpy( s1.id, "123456");
strcpy( s1.name, "ZhangSan");
s1.post= 201201;
strcpy( s1.address, "Shanghai");
strcpy( s1.phone, "15123456789");
4. 结构体作为函数参数
结构体最常见的用途,就是作为函数的参数传入传出,这样当需要增加参数个数时,可以不用修改函数调用的地方,直接修改结构体定义处即可,使得代码灵活性更高。
作为一种数据类型,那当然可以按值传递,也可以按址传递,通常的做法都是按址传递,为什么?回忆《笔记 | 袁春风《计算机系统基础》:05-按值传递和按地址传递为什么不同?》,若采用按值传递,则结构成员都要复制到栈中参数区,这既增加时间开销又增加空间开销,且更新后的数据无法在调用过程使用。
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book;
strcpy( Book.title, "C Programming");
strcpy( Book.author, "Nuha Ali");
strcpy( Book.subject, "C Programming Tutorial");
Book.book_id = 6495407;
printBook( &Book );
return 0;
}
可以看到,在函数调用时,把结构体Book的地址传了进去,那么在函数那边是怎么样的呢?
void printBook( struct Books *book )
{
printf( "Book title : %s\n", book->title);
printf( "Book author : %s\n", book->author);
printf( "Book subject : %s\n", book->subject);
printf( "Book book_id : %d\n", book->book_id);
}
函数的入口参数定义了一个指向结构体Books的指针book,可以看到此时对结构体成员的访问就变成了用->
符号,其实也可以用.
来访问成员:如(*book).title
,它就等价于book->title
,后者比较好记,所以一般都用后者来表示。
5. 嵌套结构体
结构体中可以存储各种变量类型,那么当然也可以存储结构体喽。可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
我们先来看一下包含其他结构体的例子:
struct STUDENT
{
char id[8];
char name[12];
unsigned post;
char address[100];
char phone[20];
};
struct CLASS
{
char classname[100];
struct STUDENT student;
};
void printClass( struct CLASS *classes );
int main( )
{
struct CLASS classes;
strcpy( classes.classname, "Math");
strcpy( classes.student.id, "2008001");
strcpy( classes.student.name, "ZhangSan");
classes.student.post= 201201;
strcpy( classes.student.address, "Shanghai");
strcpy( classes.student.phone, "15123456789");
printClass(&classes);
return 0;
}
void printClass( struct CLASS *classes )
{
printf( "classname : %s\n", classes->classname);
printf( "student.id : %s\n", classes->student.id);
printf( "student.name : %s\n", classes->student.name);
printf( "student.post : %d\n", classes->student.post);
printf( "student.address : %s\n", classes->student.address);
printf( "student.phone : %s\n", classes->student.phone);
}
这里要注意的是对嵌套结构体的成员访问采用了classes->student.phone
的形式,那如果是三级的形式呢?是classes->student.birthday.year
还是classes->student->birthday.year
?感兴趣的小伙伴可以自己试一下,答案应该是前者。
对于结构体嵌套自身,更多的是在链表和树中使用,之后介绍时再深入讨论。
struct NODE
{
char string[100];
struct NODE *next_node;
};
二. 联合体(共同体)
上面介绍了结构体,允许我们储存不同类型的数据集合,那么联合体又是什么呢?联合体允许我们在相同的内存位置存储不同的数据类型,可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。如:
union Data
{
int iData;
float fData;
char cData[20];
};
对联合体成员的访问同样采用.
:
union Data data;
data.iData = 1215;
data.fData = 12.15;
strcpy( data.cData, "12月15日");
printf( "data.iData : %d\n", data.iData);
printf( "data.fData : %f\n", data.fData);
printf( "data.cData : %s\n", data.cData);
上面输出的结果是:
data.iData : -1662635471
data.fData : -0.000000
data.cData : 12月15日
可以看到,只有最后一次的赋值生效了,或者说最后一次的赋值重新占据了之前的内存空间。
联合体对内存的使用更为精细灵活,也节省了内存空间。不过随着内存越来越大,这点节省空间其实并没有什么吸引力,反而使用过程中更容易出错,因此真正编程中比较少使用联合体,只有在一些高级的应用场景,或者比较巧妙的编程中才会出现。
网友评论