06.结构和联合
结构基础
什么是结构
聚合数据类型,能够存储一些元素的集合,例如数组,但数组是相同类型的元素的集合。
结构也是一些元素的集合,但一个结构的各个成员可以具有 不同的类型
结构的声明
在声明结构时,必须列出它包含的所有成员.
struct tag {member list}variable list;
结构变量
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
struct {
int age;
char name[20];
}student,students[3],*pstudent;
// 声明了结构变量student 结构数组students 结构指针pstudent三个变量
student.age = 1;
strcpy(student.name,"xyc");
students[0].age = 2;
strcpy(students[0].name,"yqt");
pstudent = malloc(sizeof(student));
pstudent->age = 3;
strcpy(pstudent->name,"jj");
printf("name:%s,age:%d\n",student.name,student.age);
printf("name:%s,age:%d\n",students[0].name,students[0].age);
printf("name:%s,age:%d\n",(*pstudent).name,(*pstudent).age);
}
使用上述方式声明的结构变量,不能使用直接量的方式初始化 或者在声明的时候就进行初始化.
在结构内部声明字符数组时,其内存已经分配好,因此不能对其直接赋值,而要使用strcpy
对于结构指针变量,要先使用malloc分配内存在进行初始化.
结构标签
标签(tag)字段允许为成员列表提供一个名字,这样就可以在后续的声明中使用。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
struct Student {
int age;
char name[20];
};
int i;
struct Student s1 = {
1,"a"
};
struct Student s2[2] = {
{
2,"b"
},
{
3,"c"
}
};
struct Student *pstu;
pstu = (struct Student *)malloc(sizeof(struct Student));
pstu->age = 4;
strcpy(pstu->name,"d");
printf("name:%s,age:%d\n",s1.name,s1.age);
for (i=0;i<2;i++) {
printf("name:%s,age:%d\n",s2[i].name,s2[i].age);
}
printf("name:%s,age:%d\n",pstu->name,pstu->age);
}
在声明结构时使用了标签tag,可以使用字面量的方式初始化值
但对于指针,在初始化时还是要对其进行内存分配
typedef
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct {
int a;
char b;
}Simple;
int main() {
Simple s1 = {1,'a'};
Simple *s2;
s2 = (Simple *)malloc(sizeof(Simple));
s2->a = 2;
s2->b = 'b';
printf("%d,%c\n",s1.a,s1.b);
printf("%d,%c\n",s2->a,s2->b);
return 0;
}
这种声明方式几乎和标签的方式相同,区别在于对于后续的结构声明时,可以省略struct,因为typedef把它定义为了一个专门的类型.
结构成员
结构成员可以是标量,数组,指针,其他结构,也就是说在结构外部定义的任何变量都可以作为结构的成员
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct {
int a;
char b;
}Simple;
typedef struct COMPLEX {
int x;
float y;
Simple s[2];
Simple *psim;
}Complex;
int main() {
Complex c1 = {
1,0.1,{ {1,'c'},{2,'b'}}
};
int i;
printf("x:%d\n",c1.x);
printf("y:%f\n",c1.y);
for (i=0;i<2;i++) {
printf("simple a:%d,simple b:%c\n",c1.s[i].a,c1.s[i].b);
}
c1.psim = (Simple *)malloc(sizeof(Simple));
c1.psim->a = 3;
c1.psim->b = 'd';
printf("pointer simple a:%d,pointer simple b:%c\n",c1.psim->a,c1.psim->b);
return 0;
}
结构的直接访问
结构COMPLEX的成员s是一个Simple结构的数组,要访问Simple中的元素,需要使用.操作符
((c1.s)[1]).a
下标和点操作符具有相同的优先级,它们的结合性都是从左往右,所以此处可以省略掉括号
c1.s[1].a
访问结构complex的成员s结构数组的下标为1的元素a的值
结构的间接访问
假定一个函数的参数是指向一个结构的指针
void func(struct Complex *c);
函数可以使用下面的表达式来访问成员x
(*c).x
对指针执行间接(*)访问将访问结构,然后用.操作符访问成员
c语言还有另外一种方式将这两步结合为一步的操作符 ->
箭头操作符
c->x
c->y
c->s[1].a
结构自引用
错误的自引用
struct Self{
int a;
Self b;
};
这个声明错误的原因是,成员b也是一个完整的结构,其内部也包含成员b,这样就会无终止的重复下去,有点类似递归.
解决方法:
struct Self{
int a;
struct Self *s;
};
这样声明和前面的声明区别在于b现在是一个指针而不是结构,编译器在结构程度确定之前就已经知道指针的长度,所以这种自引用是合法的
如果你觉得一个内部结构包含一个指向本身的指针有些奇怪,请记住它事实上是同一类型不同结构。更加高级的数据结构,如链表和树,都是用这种技巧实现的:每个结构指向链表的下一个元素或树的下一个分支.
typedef struct {
int a;
Self *s;
}Self;
这个声明的目的是为了在结构内部创建类型名Self,但是它失败了,类型名直到结构末尾才定义
解决方法:
typedef struct Self_Tag{
int a;
Self_Tag *s;
}Self;
增加一个标签即可。
不完整声明
偶尔,必须声明一些相互之间存在依赖的结构。也就是说,其中一个结构包含了另一个结构一个或多个成员。和自引用一个,至少有一个结构必须在另一个结构内部以指针的形式存在。问题在于声明部分:如果每个结构都引用了其他结构的标签,哪个结构应该首先声明呢?
这个问题的解决方案就是不完整声明,它声明一个作为结构标签的标识符。然后我们可以把这个标识符用在不需要知道这个结构长度的声明中,如声明指向这个结构的指针。接下来的声明把这个标签与成员列表联系在一起。
struct B;
struct A{
int a;
strunct B *pb;
};
struct B{
int b;
struct A *pa;
};
在声明标签A中需要标签B不完整的声明,一旦标签A被声明之后,B的成员列表也可以被声明.
结构、指针和成员
typedef struct {
int a;
short b[2];
}Ex2;
typedef struct Ex{
int a;
char b[3];
Ex2 c;
struct EX *d;
}Ex;
Ex x = {10,{'a','b','c'},{1,{-1,25}},0};
Ex *px = &x;
访问指针
px是一个指针变量,px右值是指向Ex结构的指针,px的左值是Ex结构
现在考虑px+1这个表达式,这个表达式不是一个合法的左值,因为他的值并不存储任何可标识的内存位置。
它的右值,如果px指向一个结构数组,那么这个表达式应该指向该结构数组的下一个结构,但就算如此,这个表达式依然是非法的,因为没办法分辨内存下一个位置所存储的是这些结构元素之一还是其他东西。编译器无法检测到这类的错误,所以必须自己判断指针运算是否有意义。
访问结构
可以使用操作符对指针执行间接访问,px的右值是px所指向的整个结构,可以把这个表达式赋值给相同的结构,也可以使用.操作符访问其成员,也可以把它传递给函数,也可以把它作为函数的返回值返回
访问结构成员
int *pi;
创建一个int型的指针。能不能让pi指向整型成员a呢,最直接的做法
pi = (int *)px->a
这样虽然能绕过编译器的检查,但是危险的。
正确的写法是 pi = &px->a
取地址符的优先级小于箭头符号
访问嵌套的结构
px->c
的左值是整个结构,这个表达式可以用.访问c的特定成员 如 px->c.a
访问指针成员
px->d
的右值是0,左值是它本身的内存位置。表达式 *px->d
这里间接访问操作符得到整个结构再使用箭头操作符作用于d所存储的指针值,但d包含了一个NULL指针,所以它不指向任何东西。
作为函数参数的结构
结构变量是一个标量,它可以用于其他标量的任何使用场合,因此把结构传递给函数是合法的。但这种做法往往并不适宜。
#include <stdio.h>
#include <string.h>
typedef struct {
char product [20];
float total_amount;
}Transaction;
void print_transaction_struct(Transaction transaction);
void print_transaction_struct_pointer(Transaction *ptransaction);
int main() {
Transaction transaction;
strcpy(transaction.product,"饮料");
transaction.total_amount = 2.5;
print_transaction_struct(transaction);
print_transaction_struct_pointer(&transaction);
return 0;
}
void print_transaction_struct(Transaction transaction) {
printf("struct size:%ld\n",sizeof(transaction));
printf("%s\n",transaction.product);
printf("%.2f\n",transaction.total_amount);
}
void print_transaction_struct_pointer(Transaction *ptransaction) {
printf("struct pointer size:%ld\n",sizeof(ptransaction));
printf("%s\n",ptransaction->product);
printf("%.2f\n",ptransaction->total_amount);
}
上面的代码中,分别由两个函数,作用都是打印商品和商品价格,但是第一个函数接收一个结构,通过.操作符访问,第二个函数接受一个指向结构的指针,使用->访问。
指针要比结构小的多,第一个传递结构的参数大小是24,第二个则是8.因此传递的结构越大,使用指针的效率就越高。
在许多机器中,可以把参数声明为寄存器变量 如
void print_transaction_struct_pointer(register Transaction *ptransaction);
向函数传递指针的缺陷在于函数现在可以对调用程序的结构变量进行修改,如果不希望修改,可以在函数中使用 const
关键字来防止这类修改。 如
void print_transaction_struct_pointer(register const Transaction *ptransaction);
位段
位段的声明和结构类似,但它的成员是一个或多个位的字段。这些不同长度的字段实际上存储于一个或多个整型变量中。
位段的声明和任何普通的结构成员声明相同,但是有两个例外。首先,位段成员必须声明为int、signed int 、unsigned int类型。其次,在成员名的后面是一个冒号或者整数,这个整数指定该位段所占用的位的数目
struct CHAR {
unsigned ch : 7;
unsigned font :6;
unsigned size :19;
}
联合
联合的声明和结构类似,但它的行为方式却和结构不同,联合所有成员引用的位置是内存中相同的位置。当想在同一位置存储不同的类型时,可以使用联合.
联合的声明方式
// 第一种声明:将联合声明为一个变量,但是不能对后续同类型的进行使用.
union {
int i;
float f;
}fi;
fi.i = 1;
fi.f = 3.1415926;
// 第二种声明:使用标签
union Fi{
int i;
float f;
};
Fi fi,*pfi;
// 第三种:使用typdef
typedef union {
int i;
float f;
}Fi;
联合的使用
#include <stdio.h>
#include <stdlib.h>
typedef union FI{
int i;
float f;
}FI;
int main() {
FI fi,*pfi;
fi.f = 3.1415926;
pfi = (union FI *)malloc(sizeof(union FI));
pfi->i = 1;
printf("fi.f:%f\n",fi.f);
printf("pfi->i:%d\n",pfi->i);
}
在结中使用联合
BASIC解释器的任务之一就是记录程序所用的变量的值。BASIC提供了几种不同类型的变量,所以每个变量的类型必须和它的值一起存储。
struct VARIABLE {
enum { INT,FLOAT,STRING } type;
int int_value;
float float_value;
char *string_value;
};
当BASIC程序中的一本变量被创建时,解释器就创建一个这样的结构并记录变量的类型,然后,根据变量的类型,把变量的值存储在这三个字段之中。
这个结构的低效之处在于它所占用的内存-每个VARIABLE结构存在两个未使用的值字段。联合可以减少这种浪费
struct VARIABLE {
enum { INT,FLOAT,STRING} type;
union {
int i;
float f;
char *s;
}value;
};
总结
在结构中,不同类型的值可以存储在一起,结构的值称为成员,通过名字访问,结构变量是一个标量,可以出现在任何普通标量出现的场合.
结构标签是一个名字,使用结构标签可以在不同的声明中创建相同的结构变量。
结构的成员可以是标量、指针、数组,但不能也是这个结构。
如果有一个指向结构的指针,可以使用箭头操作符访问这个结构
结构作为参数传递给函数时,使用指的效率更高
联合变量也可以初始化,但初始值必须与联合第一个元素匹配