当前位置 : 主页 > 编程语言 > c语言 >

第九节(结构、联合typedef)

来源:互联网 收集:自由互联 发布时间:2023-09-06
在C语言中,通常通过一种称为结构的数据构造体来简化程序设计任务。 结构是程序员根据程序设计需求设计的一种数据存储类型。 本次将介绍以下内容: ●什么是简单结构和复杂结构

在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;
}

输出:

第九节(结构、联合typedef)_数据

解析:

该程序清单定义了一个简单的结构来储存坐标中的点。
这与前面介绍的结构相同。
第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类型变量之间的关系。

第九节(结构、联合typedef)_数组_02

下面程序清单中使用的结构就包含其他结构。该程序清单要求用户输入矩形的坐标,然后计算并显示矩形的面积。

注意程序开头注释中的假设情况(第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;
}

输出:

第九节(结构、联合typedef)_初始化_03

解析:

第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节介绍过)。

第九节(结构、联合typedef)_初始化_04

使用结构名成员名来访问数组中的元素,此时成员名可用数组下标表示法:

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类型的数组。如图所示:

第九节(结构、联合typedef)_数据_05

声明结构数组后,可以通过多种方式操控数据。

例如,要把一个数组元素中的数据赋值给另一个数组的元素,可以这样写:

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;
}

输出:

第九节(结构、联合typedef)_数组_06

解析:

该程序清单与其他程序清单类似,第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类型数组成员,的结构,可将下图与上图作比较。

第九节(结构、联合typedef)_数组_07

在可以使用指针的地方就能使用结构的指针成员。例如,要打印指针指向的字符串,可以这样写:

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节中介绍过取址运算符&)。

下面图解释了结构和指向结构的指针之间的关系。

第九节(结构、联合typedef)_数组_08

现在已经创建了一个指向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,它便指向数组的下一个元素。

第九节(结构、联合typedef)_数据_09

这意味着递增指针便可遍历任意类型的结构数组(或任意类型的结构)。

在完成相同的任务时,这种表示法通常比下标表示法更易于使用,也更简洁。

输入:

// 使用指针表示法遍历结构数组


#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;
  }

输出:

第九节(结构、联合typedef)_数组_10

解析:

首先,第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);
}

输出:

第九节(结构、联合typedef)_数组_11

解析:

第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联合。

第九节(结构、联合typedef)_数组_12

在声明联合时可以同时初始化它。由于每次只能使用一个成员,因此只需初始化一个成员。

为避免混乱,只允许初始化联合的第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;
}

输出:

第九节(结构、联合typedef)_初始化_13

解析:

程序清单中,第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;
  }
}

第九节(结构、联合typedef)_数组_14

解析:

该程序是使用联合的最简单版本。
程序演示了如何在一个存储空间中储存多个数据类型。
可以在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语言的任意数据类型,包括指针、数据和其他结构。结构中的每个数据项都称为成员,可以通过结构名.成员名的方式来访问它们。可以单独使用结构,也可以在数组中使用结构。

联合与结构类似。它们的主要区别是,联合把所有的成员都储存在相同的内存区域。这意味着每次只能使用一个联合成员。

上一篇:计算二叉树深度
下一篇:没有了
网友评论