在C语言中,通常通过一种称为结构的数据构造体来简化程序设计任务。
结构是程序员根据程序设计需求设计的一种数据存储类型。
本次将介绍以下内容:
●什么是简单结构和复杂结构
●如何声明并定义结构
●如何访问结构中的数据
●如何创建包含数组的结构和包含结构的数组
●如何在结构中声明指针,如何声明指向结构的指针,如何将结构作为参数传递给函数
●如何定义、声明、使用联合
●如何对结构使用类型定义.
一.简单结构:
结构是一个或多个变量的集合,该集合有一个单独的名称,便于操作。与数组不同,结构可以储存不同类型(C语言的任意数据类型,包括数组和其他结构)的变量。结构中的变量被称为结构的成员。
我们先来学习简单的结构。注意,C语言并未区分简单结构和复杂结构,但是用这种方式来解释结构,比较容易理解。
1.1:声明和定义结构
如果编写一个图形程序,就要处理屏幕上点的坐标。屏幕坐标由表示水平位置的x值和表示垂直位置的y值组成。
可以声明一个名为coord的结构,其中包含表示屏幕位置的x和y,如下所示:
struct coord
{
int x;
int y;
}
关键字struct表明结构声明的开始。struct关键字后面必须是结构名。结构名也被称为结构的标签( tag )或类型名( type .name)。
稍后介绍如何使用标签。
结构标签后面是左花括号。花括号内是结构的成员变量列表。必须写明各成员的变量类型和名称。
上面的代码声明了一个名为coord的结构类型,其中包含了两个整型变量,x和y。
然而,虽然声明了coord,但并未创建任何coord的结构实例,也未创建x变量和y变量。
声明结构有两种方式。一种是,在结构声明后带有一个或多个变量名列表:
struct coord {
int X;
int y;
} first, second;
以上代码定义了类型为coord的结构,并声明了两个coord类型的结构变量,first 和second。
first 和second都是coord类型的实例,first包含两个整型成员,x和y ;
second也是如此。这种方法把声明与定义结合在一起。
另一种方法是,把结构变量的声明和定义放在源代码的不同区域。
下面的代码也定义了两个coord类型的实例:
struct coord {
int x;
int y;
};
/*其他代码已省略*/
struct coord first, second;
在该例中,coord类型结构的声明与结构变量的定义分离。
在这种情况下,要使用struct关键字,后面紧跟结构类型名和结构变量名。
1.2:访问结构的成员
使用结构成员,就像使用同类型变量一样。
在C语言中,使用结构成员运算符(. )来访问结构成员。结构成员运算符也称为点运算符,用于结构名和成员名之间。
因此,要通过结构名first引用屏幕的位置(x =50,y=100),可以这样写:
first.x = 50;
first.y = 100;
要将该位置储存在second结构中,并显示在屏幕上,可以这样写:
printf ("%d, %d", second.x, second.y);
那么,与单独的变量相比,结构有何优点?
一个主要的好处是,通过简单的赋值表达式语句就能在相同类型的结构间拷贝信息。
继续使用上面的例子,语句:
first = second;
与下面的语句等价:
first.x = second.x;
first.y = second.y;
当程序中使用包含许多成员的复杂结构时,这样的写法很节约时间。
等你学会一些高级技巧后,会发现结构的其他好处。
一般而言,需要将不同类型变量的信息看作一个整体时,结构非常有用一可以将不同类的信息(名字、地址、城市等)作为结构的成员。
下面程序清单将上述内容结合在程序中,虽然没什么实际的用途,但可用于解释结构。
输入:
// 使用简单结构的示例
#include <stdio.h>
int length, width;
long area;
struct coord{
int x;
int y;
} myPoint;
int main(void)
{
// 给坐标设置值
myPoint.x = 12;
myPoint.y = 14;
printf("\nThe coordinates are: (%d, %d).",
myPoint.x, myPoint.y);
return 0;
}
输出:
解析:
该程序清单定义了一个简单的结构来储存坐标中的点。
这与前面介绍的结构相同。
第8行,在结构标签coord前面使用了struct关键字。
第9^11行定义了结构的主体。
该结构中声明了两个成员x和y,都是int类型的变量。
第11行,声明了coord结构的实例: mypoint结构变量。
也可以单独声明一行:
struct coord myPoint;
第16、17行给mypoint的成员赋值。
前面提到过,使用结构变量名.成员名便可对其赋值。
第19、20行,在printf语句中使用了这两个变量。
语法:struct关键字
struct 标签 {
结构成员;
/*可在此处添加语句*/
} 实例;
在声明结构时要使用struct关键字。结构是一个或多个变量(结构成员)的集合,这些变量的数据类型可以不同。结构不仅能储存简单变量,还能储存数组、指针和其他结构。
struct关键字表明结构声明的开始,后面紧跟的是标签,即结构的名称。结构成员位于标签后面的花括号中。实例 是结构声明的一部分。 如果声明一个结构没有实例,那它仅仅描述了结构的模板,用于在后面的程序中定义结构变量。
下面是模板的格式:
struct tag {
structure_member(s);
// 可在此处添加语句
};
要使用结构模板,可以按以下格式:
struct tag instance;
示例1:
// 声明一个名为SSN的结构模板
struct SSN {
int first_three;
char dash1;
int second_two;
char sash2;
int last_four;
};
// 使用结构模板
struct SSN customer_ssn;
示例2:
// 声明一个结构和实例
struct date {
char month[2];
char day[2];
char year[4];
} current_date;
示例3:
// 声明并初始化一个结构
struct time {
int hours;
int minutes;
int seconds;
} time_of_birth = { 8, 45, 0 };
二.复杂结构:
介绍完简单结构,接下来介绍更有趣、更复杂的结构。
这些结构中包含其他结构或数组。
2.1:包含结构的结构
前面提到过,C结构可以包含任意C语言的数据类型。
例如,结构可以包含其他结构。我们以前面用过的例子来说明。
假设图形程序需要处理矩形。我们可以通过两对角的坐标定义矩形。前面的例子中,可以在结构中储存两个坐标来表示一个点。
因此,要处理矩形需要储存两个这样的结构。可以这样声明一个结构(假设前面已经声明了coord结构) :
struct rectangle {
struct coord topleft;
struct coord bottomrt;
};
该语句声明了一个rectangle类型的结构,其中包含两个coord类型的结构,分别是topleft和bottomrt。
上面的结构声明只定义了rectangle结构的类型。要定义结构的实例,必须像这样写:
struct rectangle mybox;
如果前面声明了coord类型,也可以把结构声明和实例定义结合起来,如下所示:
struct retangle {
struct coord topleft;
struct coord bottomrt;
} mybox;
要使用成员运算符(. )两次才能访问真正的数据位置(int类型的成员)。因此,表达式:
mybox.topleft.x
指的是rectangle类型的mybox结构的成员topleft的成员x。要通过坐标(0,10),(100, 200) 来定义矩形,可以这样写:
mybox.topleft.x = 0;
mybox.topleft.y = 10;
mybox.bottomrt.x = 100;
mybox.bottomrt.y = 200;
这也许有点难。参考下面图有助于理解,图中显示了rectangle类型结构、rectangle 类型结构包含的两个coord类型的结构、coord类型结构包含的两个int类型变量之间的关系。
下面程序清单中使用的结构就包含其他结构。该程序清单要求用户输入矩形的坐标,然后计算并显示矩形的面积。
注意程序开头注释中的假设情况(第3~8行)。
输入:
// 结构中包含结构的程序示例
/*
程序接收用户输入的矩形对角坐标,并计算矩形的面积。
假设左上角的x坐标小于右下角的x坐标
左上角的y坐标大于右下角的y坐标
而且,所有的坐标都为非负整数。
*/
#include <stdio.h>
int length, width;
long area;
struct coord{
int x;
int y;
};
struct rectangle{
struct coord topleft;
struct coord bottomrt;
} mybox;
int main(void)
{
// 输入坐标
printf("\nEnter the top left x coordinate: ");
scanf("%d", &mybox.topleft.x);
printf("\nEnter the top left y coordinate: ");
scanf("%d", &mybox.topleft.y);
printf("\nEnter the bottom right x coordinate: ");
scanf("%d", &mybox.bottomrt.x);
printf("\nEnter the bottom right y coordinate: ");
scanf("%d", &mybox.bottomrt.y);
//计算length和width
width = mybox.bottomrt.x - mybox.topleft.x;
length = mybox.topleft.y - mybox.bottomrt.y;
// 计算并显示面积
area = width * length;
printf("\nThe area is %ld units.\n", area);
return 0;
}
输出:
解析:
第15~18行声明了coord类型的结构,包含两个成员x和y。
第20~23行声明了rectangle类型的结构并定义了该结构的一个实例mybox,rectangle类型的结构包含的两个成员(topleft和bottomrt )都是coord类型的结构。
第29^39行提示用户输入数据,并将其储存在mybox结构的成员中。
看上去只用储存两个值,因为mybox只有两个成员。
但是,mybox的每个成员都有自己的两个成员: coord 类型的topleft和bottomrt,而它们又分别有两个成员x和y。
因此,总共为4个成员存入值。将用户输入的值存入这些成员后,便可使用结构名和成员名计算矩形的面积。
要使用x和y的值,必须包含结构实例名。由于x和y属于结构中的结构,因此在计算时必须使用结构的实例名——mybox.bottomrt.x 、mybox.bottomrt.y、
mybox. topleft.x、mybox.topleft.y
尽管C程序设计语言对嵌套的结构数量不作限制,但是ANSI标准最多只支持到63层。
只要有足够内存,便可定义包含多层结构的结构。
当然,嵌套的结构层太多并没什么好处。
通常,C程序中使用的嵌套很少超过3层。
2.2包含数组的结构
可以声明一个包含数组的结构。数组可以是任意C数据类型(int、char等)。
例如,以下声明:
struct data
{
short x[4];
char y[10];
};
定义了一个结构的类型data,该结构包含一个short类型的数组x和一个char类型的数组y。
x中包含4个short 类型的元素,y中包含10个char类型的元素。
稍后,可以声明一个data类型的结构变量record,如下所示:
struct data record;
该结构的布局如图所示。
注意,图中x数组元素占用的空间是y数组元素占用空间的两倍,因为通常short类型占2字节,而char类型占1字节(第3节介绍过)。
使用结构名成员名来访问数组中的元素,此时成员名可用数组下标表示法:
record.x[2] = 100;
record.y[1] = 'x';
应该记得,字符数组通常都用来储存字符串。而且,第9节中还介绍过,数组名(不带方括号)是指向数组第1个元素的指针。
由于,在record结构中,表达式
record.y
是指向y[]数组第1个元素的指针。因此,可以使用下面的语句在屏幕上打印y[]中的内容:
puts (record.y);
现在来看另一个例子。下面程序清单中的结构包含了一个float类型的变量和两个char类型的数组。
输入:
// 包含数组成员的结果示例
#include <stdio.h>
#define NAMESIZE 30
// 声明一个结构包含一个float类型的变量和两个char类型的数组
// 并定义了一个结构实例
struct data{
float amount;
char fname[NAMESIZE];
char lname[NAMESIZE];
} rec;
int main(void)
{
// 通过键盘输入数据
printf("Enter the donor's first and last names,\n");
printf("separated by a space: ");
scanf("%s %s", &rec.fname, rec.lname);
printf("\nEnter the donation amount: ");
scanf("%f", &rec.amount);
// 显示信息
// 注意:%.2f指定了
// 浮点值保留小数点后
// 两位有效数字
// 在屏幕上显示数据
printf("\nDonor %s %s gave $%.2f.\n", rec.fname, rec.lname,
rec.amount);
return 0;
}
解析:
该程序中的结构包含两个数组成员fname [NAMESIZE]和lname [NAMESIZE]。
这两个数组分别用于储存姓名。
通过符号常量来定义数组可容纳字符的最大数量,在以后修改数组储存更多字符的姓名时非常方便。
第8~12行声明了一个data类型的数组,其中包含两个char类型的数组fname和lname、一个float类型的变量amount。
该结构可用于储存姓名(姓和名两部分)和数值(如,此人捐助给慈善机构的数额)。
第12行声明了一个结构的实例rec。
程序的其他部分用rec储存用户输入的值(第18^23行),然后将其打印在屏幕上(第32、33行)。
三.结构数组:
既然能创建包含数组的结构,那么是否能创建包含结构的数组?
当然可以。实际上,结构数组是强大的程序设计工具。见下面详细分析。
前面介绍了如何根据程序的需要定义结构的类型。
通常,程序需要使用多个数据的实例。例如,在一个管理电话号码的程序中,可以声明一个结构储存每个人的姓名和电话号码:
struct entry
{
char fname[10];
char lname[12];
char phone[12];
};
电话号码列表中要储存许多实体(而不是一个实体),因此,要创建一个包含entry类型结构的数组。声明该结构后,可以这样写:
struct entry list[1000];
声明了一个名为list的数组,该数组包含了1000个元素。每个元素都是entry类型的结构,与其他类型的数组一样,以下标来区分。每个结构有3个元素,每个元素都是char类型的数组。如图所示:
声明结构数组后,可以通过多种方式操控数据。
例如,要把一个数组元素中的数据赋值给另一个数组的元素,可以这样写:
list[1] = list[5];
该语句将list[5]结构中的每个成员都赋值给list[1]结构相应的成员。除此之外,还可以移动结构成员的数据。
下面的语句
strcpy(list [1].phone, list[5].phone);
将list[5] .phone中的字符串拷贝给list[1] . phone (strcpy()库函数用于将一个字符串拷贝给另一个字符串,后面18节会讲)。还可以移动结构的数组成员中某个元素的数据:
list[5].phone[1] = list[2].phone[3];
该语句把list[2]的电话号码中的第4个字符拷贝给list[5]的电话号码中的第2个字符(别忘了数组下标从0开始)。
下面程序清单演示了如何使用包含数组的结构。
输入:
// 数据结构的使用示例
#include <stdio.h>
// 输入一个储存电话号码条目的结构
struct entry {
char fname[20];
char lname[20];
char phone[13];
};
// 声明一个月结构数组
struct entry list[4];
int i;
int main(void)
{
// 利用循环输入4个人的数据
for (i = 0; i < 4; i++)
{
printf("\nEnter first name: ");
scanf("%s", list[i].fname);
printf("Enter last name: ");
scanf("%s", list[i].lname);
printf("Enter phonr in 123-456-7890 format: ");
scanf("%s", list[i].phone);
}
// 打印两行空行
printf("\n\n");
// 利用循环显示数据
for (i = 0; i < 4; i++)
{
printf("Name: %s %s", list[i].fname, list[i].lname);
printf("\t\tPhone: %s\n", list[i].phone);
}
return 0;
}
输出:
解析:
该程序清单与其他程序清单类似,第1行是注释。
程序中使用了输入/输出函数,因此要包含头文件stdio.h (第3行)。
第7~11行定义了一个名为entry的结构模板,其中包含3个字符数组:fname、lname、phone。
第17行定义了一个int类型的变量,用于在程序中计数。
main()函数开始于第19行。main()中的第1个for语句执行了4次循环,用于把用户输入的数据存入结构的char类型数组中(第24 32行)。
注意,list使用下标的方式与第8课中介绍的下标使用方式相同。
第36行在获取用户输入的信息和输出数据之间打印两行空行。
第40^ 44行把之前用户输入的数据显示在屏幕上。通过带下标的数组名结构成员名打印结构数组中的值。
要熟悉程序清单中使用的技巧。许多现实中的编程任务都要用到包含数组成员的数组结构。
用已定义的结构类型声明实例时,要使用struct关键字。声明结构实例的作用域规则与其他类型变量相同(详见第12节)
使用结构成员时,不要遗漏点运算符(.)和结构实例名。
不要混淆结构标签和结构实例!结构标签用于定义结构的模板或格式:而结构实例是用结构标签声明的变量。
四:初始化结构
与C语言其他类型的变量一样,在声明结构时可以初始化它。这个过程与初始化数组类似:结构声明后面加上一个等号和一个用花括号括起来的初始化值列表( 各值用逗号分隔)。如下所示:
struct sale {
char customer[20];
char item[20];
float amount;
} mysale = {
"Acme Industries",
"Left-handed widget",
1000.00
};
执行声明时,将执行以下操作。
1.声明结构,定义一个结构类型,名为sale (第1^ 5行)。
2.声明一个sale类型结构的实例,名为mysale (第5行) 。
3.把结构成员mysale. customer初始化为字符串"AcmeIndustries" (第5、6行)。
4.把结构成员mysale. item初始化为字符串"Left-handedwidget" (第7行) 。
5.把结构成员mysale. amount初始化为1000.00 (第8行)。
对于包含结构成员的结构,应按顺序列出初始化值列表。结构成员的初始化值应该与该结构声明中的顺序一致。
下面的例子就解释了这一点:
struct customer {
char firm[20];
char contact[25];
}
struct sale {
struct customer buyer;
char item[20];
float amount;
} mysale = { { "Acme Industries", "George Adams"},
"Left-handed widget",
1000.00
};
按以下顺序初始化。
1.把结构成员mysale. buyer. firm初始化为字符串"AcmeIndustries" (第10行) 。
2.把结构成员mysale. buyer .contact初始化为字符串"Geotge Adams" (第10行)。
3.把结构成员mysale. buyer.item初始化为字符串"Left-handed widget" (第11行) 。
4.把结构成员mysale . buyer . amount初始化为1000. 00(第12行)。
初始化结构数组与此类似,提供的初始化数据被依次应用在数组的结构中。
例如,要声明一个包含sale类型结构的数组,并初始化前两个数组成员(即,前两个结构),可以这样写:
struct customer {
char firm[20];
char contact[25];
};
struct sale {
struct customer buyer;
char item[20];
float amount;
};
struct sale y1990[100] = {
{ { "Acme Industries", "George Adams" ),
"Left-handed widget",
1000.00
},
{ { "Wilson & Co.", "Ed Wilson" },
"Type 12 gizmo",
290.00
}
};
1.把结构成员y1990[0] . buyer. firm初始化为字符串"AcmeIndustries" (第14行) 。
2.把结构成员y1990[0] . buyer.contact初始化为字符串"Geotge Adams" (第14行) 。
3.把结构成员y1990[0] .buyer. item初始化为字符串"Left-handed widget" (第15行) 。
4.把结构成员y1990[0] .buyer. amount初始化为1000.00(第16行)。
5.把结构成员y1990[1] . buyer. firm初始化为"Wilson &Co." (第18行)。
6.把结构成员y1990[1] . buyer .contact初始化为字符串"Ed Wilson" (第18行)
7.把结构成员y1990[1] .buyer. item初始化为字符串"Type12 gizmo" (第19行)。
8.把结构成员y1990[1] . buyer. amount初始化为290. 00(第20行)。
五.结构和指针
指针是C语言中的重要部分,在结构中也可以使用指针。可以把指针作为结构成员,也可以声明指向结构的指针。
接下来,将详细介绍相关内容。
5.1 包含指针成员的结构
把指针作为结构成员来使用非常地灵活。声明指针成员与声明普通指针的方式相同,即用间接运算符(* )。
如下所示:
struct data
{
int *value;
int *rate;
} first;
上面的声明定义了一个data类型(包含两个指向int类型的指针)和该结构的实例first。
与所有的指针一样,不能使用未初始化的指针。
如果在声明时没有初始化,可以稍后为其赋值后再使用。
记住,要把变量的地址赋给指针。假设cost和interest 都被声明为int类型的变量,可以这样写:
first.value = &cost;
first.rate = &interest;
现在才能使用这两个指针。第9节介绍过,对前面加上间接运算符(* )的指针求值得指针所指向内容的值。
因此,对表达式*first.value求值得cost的值,对表达式*first.rate 求值得interest的值。
指向char类型的指针也许是作为结构的成员使用得最频繁的指针。
第10节中介绍过,字符串是一组以空字符结尾的字符序列,字符串储存在字符数组中,而数组名是指向该字符串第1个字符的指针。
为复习以前学过的内容,可以声明一个指向char类型的指针,然后让它指向一个字符串:
char *p_message;
P_ message = "Teach Yourself C In One Hour a Day";
对于结构中指向char类型的指针成员,可以这样做:
struct msg {
char *p1;
char *p2;
} myptrs;
myptrs.p1 = "Teach Yourself C In One Hour a Day";
myptrs.p2 = "By SAMS Publishing";
下列图解释了以上结构声明和赋值表达式语句的结果。
结构中的每个指针成员都指向字符串的第1个字节,这些字符串储存在内存中的其他地方。
上图解释了如何在内存中储存包含char类型数组成员,的结构,可将下图与上图作比较。
在可以使用指针的地方就能使用结构的指针成员。例如,要打印指针指向的字符串,可以这样写:
printf("%s %s", myptrs.p1, myptrs.p2);
char类型的数组成员和指向char类型的指针成员都能把字符串“储存”在结构中。下面的msg结构就使用了这两种方法:
struct msg
{
char p1[30];
char *p2; /*注意:未初始化*/
} myptrs;
因为数组名是指向数组第1个元素的指针,所以可以用类似的风格使用这两个结构成员(注意,在给p2拷贝值之前要先初始化它)。
strcpy (myptrs.p1, "Teach Yourself C In One Hour a Day");
strcpy (myptrs.p2, "By SAMS Publishing");
/*其他代码已省略*/
puts (myptrs.p1);
puts (myptrs.p2);
这两种方法有何区别?
如果声明一个包含char类型数组的结构,除了要指定数组的大小,在声明该类型结构的实例时,还要为数组分配存储空间。
而且,不能在结构中储存超过指定大小的字符串。下面是一个例子:
struct msg
{
char p1[10];
char p2[10];
} mypts;
...
strcpy(p1, "Minneapolis"); // 错误!字符串中的字符数超出数组指定的大小。
strcpy(p2, "MN"); // 没问题,但是浪费存储空间,but wastes space because
// 因为该字符串的字符数小于数组指定的大小
但是,如果声明一个结构包含指向char类型的指针,就没有上述限制。在声明该类型结构的实例时,只需为指针分配存储空间。实际的字符串被储存在内存的别处(暂时不用关心具体储存在何处)。用这种方法储存字符串,没有长度的限制,也不会浪费存储空间。结构中的指针可以指向任意长度的字符串。虽然实际的字符串并未储存在结构中,但是它们仍然是结构的一部分。
警告:
使用未初始化指针,会无意中擦写已使用的内存。
使用指针之前,必须先初始化指针。可以通过为其赋值另一个变量的地址,或动态地分配内存来完成。
5.2创建指 向结构的指针
在C语言中,可以声明并使用指向结构的指针,就像声明指向其他数据类型的指针一样。稍后会介绍,在需要把结构作为参数传递给函数时,通常会用到指向结构的指针。指向结构的指针还用于链表(linkedlist)中,链表将在第16节中介绍。
接下来介绍如何在程序中创建指向结构的指针,并使用它。首先声明一个结构:
struct part
{
short number;
char name[10];
};
然后,声明一个指向part类型的指针:
struct part *P_part;
记住,声明中的间接运算符(* )表明p_part 是一个指向part类型的指针,不是一个part类型的实例。
该指针在声明时并未初始化,还不能使用它。虽然上面已经声明了part类型的结构,但是并未定义该结构的实例。
记住,声明不一定是定义,在内存中为数据对象预留存储空间的声明才是定义。
由于指针要指向一个内存地址,因此必须先定义一个part类型的实例。下面便声明了该结构的实例:
struct part gizmo;
现在,将该实例的地址赋值给p_part 指针:
p_part = &gizmo;
上面的语句将gizmo的地址赋值给p_part ( 第9节中介绍过取址运算符&)。
下面图解释了结构和指向结构的指针之间的关系。
现在已经创建了一个指向gizmo结构的指针,如何使用它?通过指向结构的指针访问其成员的第1种方法是:使用间接运算符(*)
第9节中提到过, 如果ptr是一个指向数据对象的指针,那么表达式*ptr则引用该指针所指向的对象。
将其应用到当前的例子中可知,p_ part是指向part类型结构gizmo的指针,因此*p_ part 引用gizmo。
然后,在*p_ part后面加上结构成员运算符(. ),便可访问gizmo的成员。
要给gi.zmo.number赋值100,可以这样写:
(*P_ part).number = 100;
必须用圆括号把*p_ part括起来,因为结构成员运算符(. )的优先级比间接运算符(* )高。
通过指向结构的指针访问其成员的第2种方法是:使用间接成员运算符( indirect membership operator ) -> (由连字符号和大于号组成)。
注意,将-与>-起使用时,C编译器将其视为一个运算符。这个符号应放在指针名和成员名之间。
例如,要通过p_ part指针访问gizmo的成员number,可以这样写:
p_part->number
来看另一个例子,假设str是一个结构,p_ str是指向str的指针,memb是str的一个成员,要访问str.memb可以这样写:
P_str->memb
因此,有3中访问结构成员的方法:
●使用结构名;
●通过间接运算符(* )使用指向结构的指针;
●通过间接成员运算符(-> )使用指向结构的指针。
如果p_ str 是指向str结构的指针,下面3 个表达式都是等价的:
str.memb
(*p_str).memb
p_str->memb
注意:间接成员运算符也称为结构指针运算符。
5.3使用指针 和结构数组
前面介绍过,结构数组是强大的编程工具,指向结构的指针也是如此。可以将两者结合起来,使用指针访问数组的结构元素
前面的示例中,声明了一个结构:
struct part
{
short number;
char name[10];
};
以上声明定义了结构的类型part,下面的声明:
struct part data[100];
定义了一个part类型的数组。
接下来,可以声明一个指向part类型的指针,并让其指向data数组的第1个结构:
struct part *p_part;
p_part= &data[0];
由于数组名即是指向数组第1个元素的指针,因此上面代码的第2行也可以这样写:
p_part = data;
现在,已经创建了一个包含part类型结构的数组和一个指向该数组第1个元素(即,数组中的第1个结构)的指针。
因此,可以使用下面的语句来打印数组第1个元素的内容:
printf("&d &s", P_part->number, P_part->name);
那么,如何打印数组中的所有元素?这要用到for循环,每迭代一次打印一个元素。
如果使用指针表示法访问结构的成员,则必须改变p_ part指针,使其每次迭代都指向下一个数组元素(即,数组中的下一个结构)。
如何做?
这里要用到C语言的指针算术。将一元递增运算符(++)应用于指针,意味着:以指针指向对象的大小递增指针。
假设一个ptr 指针指向obj类型的数据对象,下面的语句:
ptr++;
相当于与下面语句的效果:
ptr += sizeof (obj);
指针算术特别适用于数组,因为数组元素按顺序被储存在内存中。
假设指针指向数组元素n,用++运算符递增指针,指针便指向元素n+1。
如下图所示,x[]数组包含的每个元素都占4字节(例如,结构包含两个short类型的成员)。ptr指针被初始化为x[0],每次递增ptr,它便指向数组的下一个元素。
这意味着递增指针便可遍历任意类型的结构数组(或任意类型的结构)。
在完成相同的任务时,这种表示法通常比下标表示法更易于使用,也更简洁。
输入:
// 使用指针表示法遍历结构数组
#include <stdio.h>
#define MAX 4
// 定义一个包含part类型结构的数组data
// 并初始化包含4个结构的数组
struct part {
short number;
char name[12];
} data[MAX] = { { 1, "Thomas" },
{ 2, "Christopher" },
{ 3, "Andrew" },
{ 4, "Benjiamin" },
};
// 声明一个指向part类型的指针和一个计算器变量
struct part *p_part;
int count;
int main(void)
{
// 将数组的地址赋值给p_part指针,使其指向数组的第一个元素。
p_part = data;
// 遍历数组
// 每次迭代都递增指针
for (count = 0; count < MAX; count++)
{
printf("At address %d: %d %s\n", p_part, p_part->number,
p_part->name);
p_part++;
}
return 0;
}
输出:
解析:
首先,第11^18行,程序定义并初始化了一个包含结构的数组,名为data。
然后,在第22行声明了一个指向data结构的指针。
第29行,main()函数首先设置p_part指针指向前面定义的data数组的第1个part结构(数组名是指向该数组第1个元素的指针)。
第34^39行,使用for循环来打印数组中所有的元素,每次迭代便递增p_part指针。
该程序还同时显示了每个元素的地址。
仔细查看显示的地址。你的计算机上显示的地址可能本例显示的不同,但是两相邻地址间的差值应该相同一都等于part结构的大小。
这清楚地解释了为指针递增1,指针中储存的地址便自动递增该指针所指向数据类型的大小。
5.4给函数传 递结构实参
与其他数据类型一样,可以把结构作为实参传递给函数。
下面程序清单11.6演示了如何给函数传递结构实参。该程序修改了上上程序清单,把原来在main()中直接打印,改为调用一个函数在屏幕上显示传入结构的内容。
输入:
// 给函数传递一个结构
#include <stdio.h>
// 声明一个结构储存数据
struct data {
float amount;
char fname[30];
char lname[30];
} rec;
// 函数原型
// 该函数没有返回值,接受一个data类型的结构
void print_rec(struct data diplayRec);
int main(void)
{
// 从键盘输入数据
printf("Enter the donor's first and last names,\n");
printf("separated by a space: ");
scanf("%s %s", rec.fname, rec.lname);
printf("\nEnter the donation amount: ");
scanf("%f", &rec.amount);
// 调用函数显示结构中的内容
print_rec(rec);
return 0;
}
void print_rec(struct data displayRec)
{
printf("\nDonor %s %s gave $%.2f.\n", displayRec.fname,
displayRec.lname, displayRec.amount);
}
输出:
解析:
第16行是print_rec的函数原型,它接受一个结构。
与传递其他数据类型的变量一样, 实参与形参的类型必须相匹配。在本例中,实参是data类型的结构。
第34行的函数头中也说明了这一点。当调用print_rec 函数时,只能传递结构的实例名,本例是rec ( 第30行)。
给函数传递结构与传递简单变量相同。
当然,也可以通过传递结构的地址(即,指向结构的指针)把结构传递给函数。
实际上,更早版本的C语言只能用这种方式传递数组。
虽然现在不必这样了,但以前编写的程序会使用这种方式传递数组。
如果把指向结构的指针作为参数传递给函数,在该函数中必须使用间接成员运算符(-> )或点运算符(以(*ptr). 成员名的方式)来访问结构成员。
注意:声明结构数组后,要好好利用数组名。因为数组名即是指向数组第1个结构的指针。
指向结构的指针要配合间接成员运算符(-> )来访问结构的成员。
不要混淆数组和结构。
不要忘记,为指针递增1,该指针中储存的地址便自动递增它指向数据类型的大小。
如果指针指向一个结构,则递增一个结构类型的大小。
六:联合
联合(union )与结构类似,它的声明方式与结构相同。联合与结构不同的是,同一时间内只能使用一个联合成员。原因很简单,联合的所有成员都占用相同的内存区域一它们彼此擦写 。
6.1 声明、定义并初始化联合
联合的声明和定义的方式与结构相同。唯一的区别是,声明联合用union关键字,而声明结构用struct关键字。下面声明了一个包含一个char类型变量和一个int类型变量的联合:
union shared
{
char.c;
int i;
};
上面shared类型的联合可创建包含一个字符值c或一个整型值i的联合实例。
注意,联合中的成员是“或”的关系。如果声明的是结构,则创建的结构实例中都包含这两个值。
而联合在同一时间内只能储存一个值。
下面图解释了如何在内存中储存shared联合。
在声明联合时可以同时初始化它。由于每次只能使用一个成员,因此只需初始化一个成员。
为避免混乱,只允许初始化联合的第1个成员。下面的代码声明并初始化了shared类型的联合:
union shared generic_variable = {'@'};
注意,只初始化了shared类型的联合generic_ variable的第1个成员。
6.2 访问联合成员
可以像访问结构成员一样,通过点运算符(.)访问联合的成员。
但是,每次只能访问一个联合成员。由于在联合中,每个成员都储存在同一个内存空间中,因此同一时间内只能访问一个成员。
下面程序清单是一个错误访问联合的示例。
输入:
// 同时使用多个联合成员的错误示例
#include <stdio.h>
int main(void)
{
union shared_tag {
char c;
int i;
long l;
float f;
double d;
} shared;
shared.c = '$';
printf("\nchar c = %c", shared.c);
printf("\nint i = %d", shared.i);
printf("\nlong l = %f", shared.l);
printf("\nfloat f = %f", shared.f);
printf("\ndouble d = %f", shared.d);
shared.d = 123456789.8765;
printf("\n\nchar c = %c", shared.c);
printf("\nint i = %d", shared.i);
printf("\nlong l = %ld", shared.l);
printf("\nfloat f = %f", shared.f);
printf("\ndouble d = %f\n", shared.d);
return 0;
}
输出:
解析:
程序清单中,第6~ 12行声明了shared_tag 类型的联合shared。shared 包含5个成员,每个成员的类型都不同。
第14行和第22行分别给联合的成员赋值。
然后,第16~20行和第2428行使用printf()函数输出联合的每个成员。
注意,读者在运行该程序时,输出中除了charc=$和double d = 123456789.876500 这两行,其他可能都与本例的输出不同。
因为第14行给char类型的变量c赋了初始值,所以在给其他成员赋初值之前,只应该使用该成员。
如果打印联合的其他成员(i、1、f、d),其结果是无法预知的(第16' 20行)。
第22行给double类型的变量d赋值。注意,除了d,其余各变量值都无法预知。
此时,第14行赋给c的值也丢失了,因为第22行给d赋值时己经擦写了c的值。
这是联合的成员占用同一内存空间的证明。
语法:union关键字
union标签{
联合成员;
/*可在此处添加其他语句*/
}实例;
声明联合时要使用union关键字。联合是一个或多个变量(联合成员)的集合,每个联合成员都占用相同的内存区域。
union关键字是联合声明的开始,后面的标签是联合的类型名,标签后面用花括号括起来的是联合的成员。在声明联合时可以同时声明它的实例。如果声明联合时没有声明实例,该联合便是一个模板,以供程序稍后声明联合的实例。模板的格式如下:
union tag {
union_member(s);
/*可在此处添加其他语句*/
};
按下面的格式使用模板:
union tag instance;
要使用上面的格式,必领先声明联合的标签。
示例1:
// 声明一个名为tag的联合模板
union tag {
int nbr;
char character;
}
// 使用联合模板
union tag mixed_variable;
示例2
// 声明一个联合实例
union generic_type_tag {
char c;
int i;
float f;
double d;
} generic;
示例3
// 初始化一个联合
union date_tag {
char full_data[9];
struct part_data_tag {
char month[2];
char break_valuel;
char day[2];
char break_value2;
char year[2];
} part _date;
}date = {"01/01/97"};
下面程序演示了较为实用的联合用法
输入:
#include <stdio.h>
#define CHARACTER 'C'
#define INTEGER 'I'
#define FLOAT 'F'
struct generic_tag{
char type;
union shared_tag {
char c;
int i;
float f;
} shared;
};
void print_function(struct generic_tag generic);
int main(void)
{
struct generic_tag var;
var.type = CHARACTER;
var.shared.c = '$';
print_function(var);
var.type = FLOAT;
var.shared.f = (float) 12345.67890;
print_function(var);
var.type = INTEGER;
var.shared.i = 111;
print_function(var);
return 0;
}
void print_function(struct generic_tag generic)
{
printf("\n\nThe generic value is...");
switch (generic.type)
{
case CHARACTER: printf("%c", generic.shared.c);
break;
case INTEGER: printf("%d", generic.shared.i);
break;
case FLOAT: printf("%f", generic.shared.f);
break;
default: printf("an unknown type: %c\n",
generic.type);
break;
}
}
解析:
该程序是使用联合的最简单版本。
程序演示了如何在一个存储空间中储存多个数据类型。
可以在generic_tag 类型的结构中把一个字符、一个整数或一个浮点数储存在相同的内存区域。
该区域是一个名为shared的联合,这与程序清单7相同。注意,generic_tag 类型的结构中添加了一个char类型的成员type,用于储存shared中包含的变量类型信息。
type可以防止误用shared结构变量,因此能避免像程序清单7那样的错误数据。
第5、6、7行分别定义了3个符号常量: CHARACTER 、INTEGERFLOAT。
在程序中使用这些常量能提高代码的可读性。
第9^ 16行声明了一个generic_tag 类型的结构。
第18行是print_function() 函数的函数原型,该函数没有返回值,因此返回类型是void。
第22行声明了结构实例var,
第24行和第25行分别为var的成员储存值。
第26行调用print_function() 完成打印任务。
第28~30行和第32^ 34行重复以上步骤分别存储并打印其他类型的
print_function() 函数是该程序的核心。
虽然该函数用于打印generic_tag 类型结构变量的值,但是也可以编写一个 类似的函数给该变量赋值。
print_function() 函数通过对结构变量中的type成员求值,以打印与之匹配的值。
这样能避免出现程序清单7的错误输出。
要记住正在使用联合的哪一个成员。
七:用typedef创建结构的别名
使用typedef关键字可以创建结构或联合类型的别名。
例如,下面的代码为指定的结构声明了coord别名。
typedef struct {
int x;
int y;
} coord;
稍后,可以使用coord标识符声明该结构的实例:
coord topleft, bottomr ight;
注意,typedef 与前面介绍的结构标签不同。如果编写下面的声明:
struct coord {
int x;
int y;
};
coord标识符就是该结构的标签。可以使用该标签声明结构的实例,但是与使用typedef不同,要使用结构标签,必须包含struct关键字:
struct coord topleft, bottomright;
使用typedef和使用结构标签声明结构稍有不同。
使用typedef,代码更加简洁,因为声明结构实例时不必使用struct关键字;而使用结构标签,必须显式使用struct关键字表明正在声明一个结构。
八.小结:
本次介绍了如何使用一种为满足程序需求设计的数据类型——结构。结构可以包含C语言的任意数据类型,包括指针、数据和其他结构。结构中的每个数据项都称为成员,可以通过结构名.成员名的方式来访问它们。可以单独使用结构,也可以在数组中使用结构。
联合与结构类似。它们的主要区别是,联合把所有的成员都储存在相同的内存区域。这意味着每次只能使用一个联合成员。