第九章:用户自己建立数据类型

本文详细介绍了C语言中如何定义和使用结构体、枚举以及typedef声明新类型名。结构体允许用户自定义组合型数据结构,如structStudent,用于存储学生信息。枚举类型enum允许定义一组特定的整数常量,例如enumWeekday。typedef则可以创建新类型名,简化复杂类型如数组、指针、结构体的定义,提高代码可读性和可移植性。
摘要由CSDN通过智能技术生成

第九章:用户自己建立数据类型

9.1 定义和使用结构体变量

9.1.1 自己建立结构体类型

C语言允许用户自己建立由不同数据类型组成的组合型的数据结构,它称为结构体。可以通过建立结构体类型表示数据结构:

struct Student
	{
	int num;		//学号为整型
	char name[20];	//姓名为字符串
	char sex;		//性别为字符型
	int age;		//年龄为整型
	float score;	//成绩为实型
	char addr[30];	//地址为字符串
	};				//注意最后有一个分号

上面由程序设计者指定了一个结构体类型struct Student( struct是声明结构体类型时必须使用的关键字,不能省略),经过上面的指定,struct Student就是一个在本程序中可以使用的合法类型名,它向编译系统声明:这是一个“结构体类型”,它包括num,name,sex,age,score,addr 等不同类型的成员。它和系统提供的标准类型(如int,char , float , double等)具有相似的作用,都可以用来定义变量,只不过int等类型是系统已声明的,而结构体类型是由用户根据需要在程序中指定的。
声明一个结构体类型的一般形式为

struct 结构体名
		{成员表列};

注意:结构体类型的名字是由一个关键字struct和结构体名组合而成的(例如struct Student)。结构体名是由用户指定的,又称“结构体标记”(structure tag),以区别于其他结构体类型。上面的结构体声明中Student 就是结构体名(结构体标记)。
花括号内是该结构体所包括的子项,称为结构体的成员( member)。上例中的num,name,sex等都是成员。对各成员都应进行类型声明,即
类型名 成员名;
“成员表列”(member list)也称为“域表”(field list),每一个成员是结构体中的一个域。成员名命名规则与变量名相同。
说明:
(1)结构体类型并非只有一种,而是可以设计出许多种结构体类型,例如除了可以建立上面的struct Student结构体类型外,还可以根据需要建立名为 struct Teacher,structWorker 和 struct Date等结构体类型,各自包含不同的成员。
(2)成员可以属于另一个结构体类型。例如:

struct Date 		//声明一个结构体类型struct Date
	{
	int month;		//月
	int day;		//日
	int year;		//年
	};
struct Student		//声明一个结构体类型struct Student
	{
	int num;
	char name[20];
	char sex;
	int age;
	struct Date birthday;	//成员 birthday属于struct Date类型
	char addr[30];
	};

在这里插入图片描述

9.1.2 定义结构体类型变量

1.先声明结构体类型,再定义该类型的变量

在这里插入图片描述
这种形式和定义其他类型的变量形式(如int a,b;)是相似的。上面定义了studentl和student2为 struct Student类型的变量,这样studentl和 student2就具有struct Student类型的结构,如图9.3所示。
在这里插入图片描述

2.在声明类型的同时定义变量

一般形式:
在这里插入图片描述
如:

struct Student
	{
	int num;
	char name[20];
	char sex;
	int age;
	float score;
	char addr[30];
	} student1, student2;

声明类型和定义变量放在一起进行,能直接看到结构体的结构,比较直观,在写小程序时用此方式比较方便,但写大程序时,往往要求对类型的声明和对变量的定义分别放在不同的地方,以使程序结构清晰,便于维护,所以一般不多用这种方式。

3.不指定类型名而直接定义结构体类型变量

一般形式:
在这里插入图片描述
指定了一个无名的结构体类型,它没有名字(不出现结构体名)。显然不能再以此结构体类型去定义其他变量。这种方式用得不多。

说明:
(1)结构体类型与结构体变量是不同的概念,不要混淆。只能对变量赋值、存取或运算,而不能对一个类型赋值,存取或运算。在编译时,对类型是不分配空间的,只对变量分配空间。
(2)结构体类型中的成员名可以与程序中的变量名相同,但二者不代表同一对象。例如,程序中可以另定义一个变量num,它与struct Student中的num是两回事,互不干扰。
(3)对结构体变量中的成员(即“域”),可以单独使用,它的作用与地位相当于普通变量。关于对成员的引用方法见下节。

9.1.3 结构体变量的初始化和引用

(1)在定义结构体变量时可以对它的成员初始化。初始化列表是用花括号括起来的一些常量,这些常量依次赋给结构体变量中的各成员。注意:是对结构体变量初始化,而不是对结构体类型初始化。
C99标准允许对某一成员初始化,如 :

struct Student b={.name="Zhang Fang"!};//在成员名前有成员运算符"."

".name”隐含代表结构体变量b中的成员b.name。其他未被指定初始化的数值型成员被系统初始化为0,字符型成员被系统初始化为’\0’,指针型成员被系统初始化为NULL。
(2)可以引用结构体变量中成员的值,引用方式为
结构体变量名.成员名
(3)如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级的成员。只能对最低级的成员进行赋值或存取以及运算。如果在结构体 struct Student类型的成员中包含另一个结构体struct date类型的成员 birthday(见 9.1,1节最后介绍的结构体),则引用成员的方式为

student1.num
//(结构体变量studentl中的成员num)
student1.birthday.month
//(结构体变量studentl中的成员birthday中的成员 month)

不能用student1.birthday来访问student1变量中的成员 birthday,因为 birthday本身是一个结构体成员。
(4)对结构体变量的成员可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)。例如:

student2.score=studentl.score;
//(赋值运算)
sum=student1.score+student2.score;
//(加法运算)
student1.age++;
//(自加运算)

由于“.”运算符的优先级最高,因此 student1.age++是对(student1.age)进行自加运算,而不是先对age进行自加运算。
(5)同类的结构体变量可以互相赋值,如:

student1 = student2;
//假设student1和student2已定义为同类型的结构体变量

(6)可以引用结构体变量成员的地址,也可以引用结构体变量的地址。例如:

scanf("%d",&student1.num);
//(输入student1.num的值)
print("%o",&student1) ;
//(输出结构体变量student1的起始地址)

但不能用以下语句整体读入结构体变量,例如:

scanf("%d,%s.%c%d,%f,%s\n",&student1) ;//错误的

结构体变量的地址主要用作函数参数,传递结构体变量的地址。

9.2 使用结构体数组

9.2.1 定义结构体数组

例:有3个候选人,每个选民只能投票选一人,要求编一个统计选票的程序,先后输入被选人的名字,最后输出各人得票结果。

#include<string.h>
#include<stdio.h>

struct Person
	{char name[20];
	 int count;
	}leader[3]={"Li",0,"Zhang",0,"Sun",0};

int main()
	{int i,j;
	 char leader_name[20];
	 for (i=1;i<=10;i++)
	 	{scanf("%s",leader_name);
	 	 for (j=0;j<3;j++)
	 	 	if(strcmp(leader_name,leader[j].name)==0)
	 	 		leader[j].count++;
	 	}
	 printf("\nResult:\n");
	 for(i=0;i<3;i++)
	 	printf("%5s:%d\n",leader[i].name,leader[i].count);
	 return 0;

(1)定义结构休数组一般形式是
在这里插入图片描述
②先声明一个结构体类型(如 struct Person),然后再用此类型定义结构体数组;
结构体类型 数组名[数组长度];
如:

struct Person leader[3];//leader是结构体数组名

②对结构体数组初始化的形式是在定义数组的后面加上:
={初值表列};
如:

struct Person leader[3]={"Li",0,"Zhang",0,"Sun",0};

9.3 结构体指针

9.3.1指向结构体变量的指针

例:通过指向结构体变量的指针变量输出结构体变量中成员的信息

#include<stdio.h>
#include<string.h>
int main()
	{struct Student
		{long num;
		 char name[20];
		 char sex;
		 float score;
		};
	 struct Student stu_1;
	 struct Student *p;
	 p=&stu_1;
	 stu_1.num=10101;
	 strcpy(stu_1.name,"Li Lin");
	 stu_1.sex='M';
	 stu_1.score=89.5;
	 printf("No.: %ld\nname: %s\nsex:%c\nscore:%5.1f\n",stu_1.num,stu_1.name,stu_1. sex,stu_1.score);
	 printf("\nNo.:%ld\nname:%s\nsex:%c\nscore:%5.1f\n",(*p).num,(*p).name,(*p).scx,(*p).score);
	 return 0;
	}

在这里插入图片描述

说明:为了使用方便和直观,C语言允许把(*p).nump->num代替,“->”代表一个箭头,p->num表示p所指向的结构体变量中的num成员。同样,(*p).name等价于p->name。“->”称为指向运算符。
如果p指向一个结构体变量stu,以下3种用法等价:
stu.成员名(如stu.num);
(*p).成员名(如(*p).num);
p->成员名(如 p->num)

9.3.2 指向结构体数组的指针

例:有3个学生的信息,放在结构体数组中,要求输出全部学生的信息

#include<stdio.h>
struct Student
	{int num;
	 char name[20];
	 char sex;
	 int age;
	};
struct Student stu[3]={{10101,"Li Lin",'M',18},
					   {10102,"Zhang Fang",'M',19},
					   {10104,"Wang Min",'F',20}};
int main()
	{struct Student *p;
	 printf("No.	Name			sex age\n");
	 for(p=stu;p<stu+3;p++)
	 	printf("%5d %20s %2c %4d\n",p->num,p->name,p->sex,p->age);
	 return 0;
	}

在这里插入图片描述
注意:
(1)如果p的初值为stu,即指向stu的序号为0的元素,p加1后,p就指向下一个元素。例如:

(++p)->num //先使p自加1然后得到p指向的元素中的num成员值
(p++)->num //先求得p->num的值,然后再使p自加1。

请注意以上二者的不同。
(2)程序定义了p是一个指向struct Student类型对象的指针变量,它用来指向一个struct Student类型的对象(在例中的p的值是stu数组的一个元素(如 stu[0]或stu[1])的起始地址),不应用来指向stu数组元素中的某一成员。例如,下面的用法是不对的:

p=stu[1].name;//stu[1].name是stu[1]元素中的成员name的首字符的地址

编译时将给出“警告”信息,表示地址的类型不匹配。不要认为反正p是存放地址的,可以将任何地址赋给它。如果一定要将某一成员的地址赋给p,可以用强制类型转换,先将成员的地址转换成p的类型。例如:

p=(struct Student *)stu[0].name;

此时,p的值是stu[0]元素的name成员的起始地址。可以用“printf("%s",p);”输出stu[0]中成员name 的值。但是,p仍保持原来的类型。如果执行“printf("%s",p+1);”,则会输出 stu[1]中name 的值。执行p++时,p的值的增量是结构体struct Student的长度。

9.3.3 用结构体变量和结构体变量的指针作函数参数

将一个结构体变量的值传递给另-一个函数,有3个方法:
(1)用结构体变量的成员作参数。例如,用stu[1].num或stu[2].name作函数实参,将实参值传给形参。用法和用普通变量作实参是一样的,属于“值传递”方式。应当注意实参与形参的类型保持一致。
(2)用结构体变量作实参。用结构体变量作实参时,采取的也是“值传递”的方式,将结构体变量所占的内存单元的内容全部按顺序传递给形参,形参也必须是同类型的结构体变量。在函数调用期间形参也要占用内存单元。这种传递方式在空间和时间上开销较大,如果结构体的规模很大时,开销是很可观的。此外,由于采用值传递方式,如果在执行被调用函数期间改变了形参(也是结构体变量)的值,该值不能返回主调函数,这往往造成使用上的不便。因此一般较少用这种方法。
(3)用指向结构体变量(或数组元素)的指针作实参,将结构体变量(或数组元素)的地址传给形参。

9.4 用指针处理链表

9.4.1 什么是链表

链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。由前面的介绍中已知:用数组存放数据时,必须事先定义固定的数组长度(即元素个数)。如果一时间难以确定长度,则会浪费内存。链表则没有这种缺点,它根据需要开辟内存单元。
在这里插入图片描述
链表有一个“头指针”变量,图中以 head表示,它存放一个地址,该地址指向一个元素。链表中每一个元素称为“结点”,每个结点都应包括两个部分:(1)用户需要用的实际数据;(2)下一个结点的地址。可以看出,head 指向第1个元素,第1个元素又指向第2个元素……直到最后一个元素,该元素不再指向其他元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。
可以看到链表中各元素在内存中的地址可以是不连续的。要找某一元素,必须先找到上一个元素,根据它提供的下一元素地址才能找到下一个元素。如果不提供“头指针”(head) ,则整个链表都无法访问。链表如同一条铁链一样,一环扣一环,中间是不能断开的。
显然,链表这种数据结构,必须利用指针变量才能实现,即一个结点中应包含一个指针变量,用它存放下一结点的地址。
前面介绍了结构体变量,用它去建立链表是最合适的。一个结构体变量包含若干成员,这些成员可以是数值类型、字符类型、数组类型,也可以是指针类型。用指针类型成员来存放下一个结点的地址。例如,可以设计这样一个结构体类型:

struct Student
	{int num;
	 float score;
	 struct Student *next ;// next是指针变量,指向结构体变量
	};

注意上面只是定义了一个 struct Student类型,并未实际分配存储空间,只有定义了变量才分配存储单元。

9.4.2 建立简单的静态链表

例:建立一个如图9.9所示的简单链表,它由3个学生数据的结点组成,要求输出各结点中的数据。

在这里插入图片描述

#include<stdio.h>
struct Student
	{int num;
	 float score;
	 struct Student *next;
	};
int main()
	{struct Student a,b,c, *head,*p;
	 a.num=10101;a.score=89.5;
	 b.num=10103;b.score=90;
	 c.num=100107,b.score=85;
	 head=&a;
	 a.next=&b;
	 b.next=&c;
	 c.next=NULL;
	 p=head;
	 do
	 	{printf("%ld %5.1f\n",p->num,p->score0; //输出p指向的结点的数据
	 	 p=p->next; //使p指向下一结点
	 	}while(p!=NULL);
	 return 0;

在这里插入图片描述

9.4.3 建立动态链表

所谓建立动态链表是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟结点和输入各结点数据,并建立起前后相链的关系。

例:写一函数建立一个有3名学生数据的单向动态链接

#include<stdio.h>
#include<stdlib.h>
#define LEN sizeof(struct Student)

struct Student
	{long num;
	 float score;
	 struct Student *next;
	};
int n; //n为全局变量,本文件模块中各函数均可以使用它
struct Student *creat(void) //定义函数。此函数返回一个指向链表头的指针
	{struct Student *head;
	 struct Student *p1,*p2;
	 n=0;
	 p1=p2=(struct Student *)malloc(LEN) //开辟一个新单元
	 scanf("%ld,%f",&p1->num,&p1->score) //输入第一个学生的学号和成绩
	 head=NULL;
	 while(p1->num!=0)
	 	{n=n+1;
	 	 if(n==1)head=p1;
	 	 	else p2->next=p1;
	 	 p2=p1;
	 	 p1=(struct Student *)malloc(LEN); //开辟动态存储区,把起始地址赋给p1
	 	 scanf("%ld,%f",&p1->num,&p1->score); //输入其他学生的学号和成绩
	 	}
	 p2->next=NULL;
	 return(head);
	}
	
int main()
	{struct Student *pt;
	 pt=creat(); //函数返回链表第一个结点的地址
	 printf("\nnum:%ld\nscore:%5.1f\n",,pt->num,pt->score); //输出第一个结点的成员值
	 return 0;
	};

在这里插入图片描述(1)调用creat函数后,先后输入所有学生的数据,若输入“0,0”,表示结束。函数的返回值是所建立的链表的第1个结点的地址(请查看return语句),在主函数中把它赋给指针变量pt。为了验证各结点中的数据,在main函数中输出了第1个结点中的信息。
(2)第3行令LEN代表struct Student类型数据的长度,sizeof是“求字节数运算符”。
(3)第10行定义一个creat函数,它是指针类型,即此函数带回一个指针值,它指向一个struct Student类型数据。实际上此 creat函数带回一个链表起始地址。
(4)第14行malloc(LEN)的作用是开辟一个长度为LEN的内存区,LEN已定义为sizeof(struct Student),即结构体 struct Student的长度。malloc带回的是不指向任何类型数据的指针(void *类型)。而 p1,p2是指向struct Student类型数据的指针变量,可以用强制类型转换的方法使指针的基类型改变为struct Student类型,在malloc(LEN)之前加了“( struct Student * )”,它的作用是使malloc返回的指针转换为struct Student类型数据的指针。注意括号中的“*”号不可省略,否则变成转换成struct Student类型了,而不是指针类型了。
由于编译系统能实现隐式的类型转换,因此14行也可以直接写为p1=malloc(LEN);
(5)creat函数最后一行return后面的参数是head(head已定义为指针变量,指向structStudent类型数据)。因此函数返回的是 head 的值,也就是链表中第1个结点的起始地址。
(6)n是结点个数。
(7)这个算法的思路是让p1指向新开辟的结点,p2指向链表中最后一个结点,把p1所指的结点连接在p2所指的结点后面,用“p2->next=p1”来实现。
以上对建立链表过程做了比较详细的介绍,读者如果对建立链表的过程比较清楚的话,对链表的其他操作过程(如链表的输出、结点的删除和结点的插入等)也就比较容易理解了。

9.4.4 输出链表

例:编写一个输出链表的函数print

#include<stdio.h>
#include<stdlib.h>
#define LEN sizeof(struct Student)

struct Student
	{long num;
	 float score;
	 struct Student *next;
	};
int n;
void print(struct Student *head)
	{struct Student *p;
	 printf("\nNow,These %d records are :\n",n);
	 p=head;
	 if(head!=NULL)
	 	do
	 		{pirntf("%ld %5.1f\n",p->num,p->score);
	 		 p=p->next;
	 		}while(p!=NULL);
	}

9.5 共用体类型

9.5.1什么是共用体类型

有时想用同一段内存单元存放不同类型的变量。例如,把一个短整型变量、一个字符型变量和一个实型变量放在同一个地址开始的内存单元中。以上3个变量在内存中占的字节数不同,但都从同一地址开始(图中设地址为1000)存放,也就是使用覆盖技术,后一个数据覆盖了前面的数据。这种使几个不同的变量共享同一段内存的结构,称为“共用体”类型的结构。
定义共用体类型变量的一般形式为
在这里插入图片描述
例如:

union Data
	{int i;			//表示不同类型的变量i,ch,f可以存放到同一段存储单元中
	 char ch;
	 float f;
	}a,b,c

可以看到,“共用体”与“结构体”的定义形式相似。但它们的含义是不同的。
结构体变量所占内存长度是各成员占的内存长度之和。每个成员分别占有其自己的内存单元。而共用体变量所占的内存长度等于最长的成员的长度。例如,上面定义的“共用体”变量a,b,c各占4个字节(因为一个float型变量占4个字节),而不是各占4+1+4=9个字节。
国内有些C语言的书把 union直译为“联合”。作者认为,译为“共用体”更能反映这种结构的特点,即几个变量共用一个内存区。而“联合”这一名词,在一般意义上容易被理解为“将两个或若干个变量联结在一起”,难以表达这种结构的特点。但是读者应当知道“共用体”在一些书中也被称为“联合”。在阅读其他书籍时如遇“联合”一词,应理解为“共用体”。

9.5.2 引用共用体变量的方式

只有先定义了共用体变量才能引用它,但应注意,不能引用共用体变量,而只能引用共用体变量中的成员。例如,前面定义了a,b,c为共用体变量,下面的引用方式是正确的:

a. i//(引用共用体变量中的整型变量i
a.ch//(引用共用体变量中的字符变量ch)
a. f//(引用共用体变量中的实型变量f

不能只引用共用体变量,例如下面的引用是错误的:

printf("%d",a);

因为a的存储区可以按不同的类型存放数据,有不同的长度,仅写共用体变量名a,系统无法知道究竟应输出哪一个成员的值。应该写成

printf("%d",a.i);

printf(" %c",a.ch);

9.5.3 共用体类型数据的特点

(1)同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一个成员,而不是同时存放几个。其道理是显然的,因为在每一个瞬时,存储单元只能有唯一的内容,也就是说,在共用体变量中只能存放一个值。如果有以下程序段:

union Date
	{int i;
	 char ch;
	 float f;
	}a;
a.i=97

表示将整数97存放在共用体变量中,可以用以下的输出语句:

printf("%d",a.i);	//输出整数97
printf("%c",a.ch);	//输出字符'a'
printf("%f",a.f);	//输出实数0.000000

其执行情况是:由于97是赋给a.i 的,因此按整数形式存储在变量单元中,最后一个字节是“01100001”。如果用“%d"格式符输出a.i,就会输出整数97。如果想用“%c”格式符输出a.ch,系统会把存储单元中的信息按字符输出’a’。如果想用“%f”格式符输出 a.f,系统会将存储单元中的信息按浮点数形式来处理,其数值部分为0,故输出0.000000。
(2)可以对共用体变量初始化,但初始化表中只能有一个常量。下面用法不对:

union Data
	{int i;
	 char ch;float f;
	}a={1,'a',1.5};	//不能初始化3个成员,它们占用同一段存储单元
union Data a={16};	//正确,对第1个成员初始化
union Data a={.ch='j };	//C99允许对指定的一个成员初始化

(3)共用体变量中起作用的成员是最后一次被赋值的成员,在对共用体变量中的一个成员赋值后,原有变量存储单元中的值就取代。如果执行以下赋值语句:

a.ch='a';
a.f=1.5;
a.i=40;

在完成以上3个赋值运算以后,变量存储单元存放的是最后存入的40,原来的’a’和1.5都被覆盖了。此时如用“printf("%d",a.i);”输出a.i的值是40。而用“printf("%c",a.ch);”,输出的不是字符’a’ ,而是字符’(‘。因为在共用的存储单元中,按整数形式存放了40,现在要按%c格式输出a.ch,系统就到共用的存储单元去读数据,将存储单元中的内容按存储字符数据的规则解释,40是字符’(‘的ASCII 码,因此输出字符’('。
因此在引用共用体变量时应十分注意当前存放在共用体变量中的究竟是哪个成员的值。
(4)共用体变量的地址和它的各成员的地址都是同一地址。例如,&a.i,&a.c,&a.f都是同一值,其原因是显然的。
(5)不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。例如,下面这些都是不对的:

a=l;//不能对共用体变量赋值,赋给谁?
m=a;//企图引用共用体变量名以得到一个值赋给整型变量m

C99允许同类型的共用体变量互相赋值。如:

b=a;//a和 b是同类型的共用体变量,合法

(6)以前的C规定不能把共用体变量作为函数参数,但可以使用指向共用体变量的指针作函数参数。C99允许用共用体变量作为函数参数。
(7)共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。

9.6 使用枚举类型

如果一个变量只有几种可能的值,则可以定义为枚举(enumeration)类型,所谓“枚举”就是指把可能的值一一列举出来,变量的值只限于列举出来的值的范围内。
声明枚举类型用enum开头。例如:

enum Weekday{sun,mon,tue,wed,thu,fri,sat} ;

以上声明了一个枚举类型enum Weekday。然后可以用此类型来定义变量。例如:
在这里插入图片描述
workday和 weekend被定义为枚举变量,花括号中的sun,mon,… ,sat称为枚举元素枚举常量。它们是用户指定的名字。枚举变量和其他数值型量不同,它们的值只限于花括号中指定的值之一。例如枚举变量workday和 weekend的值只能是sun到 sat之一。

workday= mon;//正确, mon是指定的枚举常量之一
weekend=sun;//正确,sunon是指定的枚举常量之一
weekday= monday ;//不正确, monday不是指定的枚举常量之一

枚举常量是由程序设计者命名的,用什么名字代表什么含义,完全由程序员根据自己的需要而定,并在程序中作相应处理。
也可以不声明有名字的枚举类型,而直接定义枚举变量,例如:

enum{sun, mon,tue,wed,thu,fri,sat}workday,weekend;

声明枚举类型的一般形式为
enum[枚举名]{枚举元素列表};
其中,枚举名应遵循标识符的命名规则,上面的Weekday就是合法的枚举名。
注意:
(1)C编译对枚举类型的枚举元素按常量处理,故称枚举常量。不要因为它们是标识符(有名字)而把它们看作变量,不能对它们赋值。例如:

sun=0 ; mon=1;//错误,不能对枚举元素赋值

(2)每一个枚举元素都代表一个整数,C语言编译按定义时的顺序默认它们的值为0,1,2,3,4,5…。在上面的定义中, sun的值自动设为0,mon 的值为1,…, sat的值为6。如果有赋值语句:

workday= mon;
//相当于
workday=1;

枚举常量是可以引用和输出的。例如:

printf(" %d" , workday);

将输出整数1。
也可以人为地指定枚举元素的数值,在定义枚举类型时显式地指定,例如:

enum Weckday{sun=7,mon=1,tue,wed,thu,fri,sat}workday, weck_end;

指定枚举常量sun的值为7,mon为1,以后顺序加1,sat 为6。
由于枚举型变量的值是整数,因此C99把枚举类型也作为整型数据中的一种,即用户自行定义的整数类型。
(3)枚举元素可以用来作判断比较。例如:

if( workday==mon)if( workday>sun)

枚举元素的比较规则是按其在初始化时指定的整数来进行比较的。如果定义时未人为指定,则按上面的默认规则处理,即第1个枚举元素的值为0,故 mon>sun,sat>fri。

9.7 用typedef声明新类型名

1.简单地用一个新的类型名代替原有的类型名

例如:

typedef int Integer;	//指定用Integer为类型名,作用与int相同
typedef float Real;		//指定用Real为类型名,作用与float相同

指定以后,以下两行等价:

int i,j;float a,b;
Integer i,j;Real a,b;
2.命名一个简单的类型名代替复杂的类型表示方法

从前面已知,除了简单的类型(如int,float等)、C程序中还会用到许多看起来比较复杂的类型,包括结构体类型、共用体类型、枚举类型、指针类型、数组类型等,如:
在这里插入图片描述
有些类型形式复杂,难以理解,容易写错。C允许程序设计者用一个简单的名字代替复杂的类型形式。例如:
(1)命名一个新的类型名代表结构体类型:

typedef struct
	{int month;
	 int day;
	 int year;
	}Date;

以上声明了一个新类型名Date,代表上面的一个结构体类型。然后可以用新的类型名Date去定义变量,如:

Date birthday;//定义结构体类型变量 birthday,不要写成struct Date birthday ;
Date *p;//定义结构体指针变量p,指向此结构体类型数据

(2)命名一个新的类型名代表数组类型

typedef int Num[100];//声明Num为整型数组类型名
Num a;//定义a为整型数组名,它有100个元素

(3)命名一个新的类型名代表指针类型

typedef char *String;//声明String为字符指针类型
String p,s[10];//定义p为字符指针变量,s为字符指针数组

(4)命名一个新的类型名代表指向函数的指针类型

typedef int ( *Pointer)();//声明Pointer为指向函数的指针类型,该函数返回整型值
Pointer pl,p2;//pl,p2为Pointer类型的指针变量

归纳起来,声明一个新的类型名的方法是:
①先按定义变量的方法写出定义体(如: int i;)。
②将变量名换成新类型名(例如:将i换成Count)。
③在最前面加typedef(例如:typedef int Count)。
④然后可以用新类型名去定义变量。
简单地说,就是按定义变量的方式,把变量名换上新类型名,并且在最前面加typedef就声明了新类型名代表原来的类型。
以定义上述的数组类型为例来说明;
①先按定义数组变量形式书写:int a[100]。
②将变量名a换成自己命名的类型名:int Num[100]。
③在前面加上 typedef,得到typedef int Num[100]。
④用来定义变量:

Num a;

相当于定义了:

int a[100];

同样,对字符指针类型,也是:

char * p;//定义变量p的方式
char * String;//用新类型名String取代变量名p
typedef char * String;//加typedef
String p;//用新类型名String定义变量,相当char * p;

习惯上,常把用typedef声明的类型名的第1个字母用大写表示,以便与系统提供的标准类型标识符相区别。
(1)以上的方法实际上是为特定的类型指定了一个同义字(synonyms)。例如:

typedef int Num[100];
Num a;//(Num是 int[100]的同义词,代表有100个元素的整型数组)

typedef int ( * Pointer)();
Pointer p1; //(Pointer是int( * )())的同义词。代表指向函数的指针类型,函数值为整型)

用typedef声明的新类型称为原有类型的typedef名称。
(2)用typedef 只是对已经存在的类型指定一个新的类型名,而没有创造新的类型。例如,前面声明的整型类型Count,它无非是对int型另给一个新名字。又如:

typedef int Num[10];

无非是把原来用int a[10];定义的数组类型用一个新的名字Num表示。无论用哪种方式定义变量,效果都是一样的。
(3)用typedef声明数组类型、指针类型,结构体类型、共用体类型,枚举类型等,使得编程更加方便。例如定义数组,原来是用

int a[10],b[10],c[10],d[10];

由于都是一维数组,大小也相同,可以先将此数组类型命名为一个新的名字Arr,即:

typedef int Arr[10];

然后用Arr去定义数组变量:

Arr a,b.c.d;//定义5个一维整型数组,各含10个元素

Arr 为数组类型,它包含10个元素。因此,a,b,c,d都被定义为一维数组,各含10个元素。
可以看到,用typedef可以将数组类型和数组变量分离开来,利用数组类型可以定义多个数组变量。同样可以定义字符串类型、指针类型等。
(4)typedef 与#define表面上有相似之处,例如:

typedef int Count;

#define Count int;

从表面看它们的作用都是用Count代表int。但事实上,它们二者是不同的。#define是在预编译时处理的,它只能作简单的字符串替换,而 typedef是在编译阶段处理的。实际上它并不是作简单的字符串替换,例如:

typedef int Num[10];
Num a;

并不是用“Num[10]”去代替“int”,而是采用如同定义变量的方法那样先生成一个类型名(就是前面介绍过的将原来的变量名换成类型名),然后用它去定义变量。
(5)当不同源文件中用到同一类型数据(尤其是像数组、指针、结构体、共用体等类型数据)时,常用typedef声明一些数据类型。可以把所有的typedef 名称声明单独放在一个头文件中,然后在需要用到它们的文件中用#include指令把它们包含到文件中。这样编程者就不需要在各文件中自己定义typedef名称了。
(6)使用typedef名称有利于程序的通用与移植。有时程序会依赖于硬件特性,用typedef类型就便于移植。例如,有的计算机系统int型数据占用两个字节,数值范围为一32 768~32 767,而另外一些机器则以4个字节存放一个整数,数值范围为±21亿。如果把一个C程序从一个以4个字节存放整数的计算机系统移植到以2个字节存放整数的系统,按一般办法需要将定义变量中的每个int改为 long,将“int a,b,c;”改为“long a,b,c;”,如果程序中有多处用int定义变量,则要改动多处。现可以用一个Integer来代替int :

typedef int Integer;

在程序中所有整型变量都用Integer定义。在移植时只须改动typedef定义体即可;

typedef long Integer;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值