今天更新结构体、枚举和联合,这次的内容很多,概念也很多,请做好心理准备(但会讲得很细,什么?我一直都讲得很细,哦,那也是),但这一章是C内容的最后一章了,以后的内容就属于C++的范畴了。
===============华丽的分割线===============
学习内容:
·了解结构体的概念;
·掌握结构体的声明和使用;
·掌握结构体数组和指针;
·了解枚举的概念;
·掌握枚举的声明和使用;
·了解联合的概念;
·掌握联合的声明和使用。
===============华丽的分割线===============
结构体的概念:
在说概念之前先说一个例子,某班上有一些学生,要求储存这些学生的个人信息,包括姓名、性别、出生年月、家庭地址和电话号码,按照之前的概念,如果要声明储存这些信息的变量,应如下写:
char name[10];
int sex;
int brithyear;
int brithmonth;
char address[50];
char phone[12];
这还仅仅是一名学生的,如果是多名学生,就需要升级成数组了,字符串就要升级成为二维数组,而且这种声明在结构上也显紊乱,如果和其它变量放在一起则很容易弄混,与其这样分散地分布着,将其整合为一个整体不失为一种解决方案,因此计算机语言史上的一次革命性的发明诞生了,它就是结构体(Structure),结构体可以说是最早涉及到数据归纳与统筹的数据形式,结构体条理清晰,易于使用,这也为计算机语言向面向对象发展奠定了基础,80年代的本杰明博士也是依据这些为基础,开发了世界上第一个广泛应用的面向对象语言(第一个面向对象的语言被认为是Smalltalk),也是世界上最流行的语言,C++。
下面就体验一下结构体的神奇之处,虽然他对于现在的我们来说过于简陋,但作为一个轻量级的数据结构,还很多领域还是有很广泛的作用范围,此外,结构体还是C与C++程序之间数据交互的桥梁。
struct student
{
char *name;
int sex;
int brithyear;
int brithmonth;
char *address;
char *phone;
};
这乍一看,无非就是把刚刚的声明放入了一个代码段中,但真正神奇的地方还在后面,你可以在函数中写下如下语句:
student jimmy,sunny;
jimmy.name="Jimmy";
jimmy.birthyear=1985;
jimmy.birthmonth=9;
jimmy.address="Jimmy's home";
jimmy.phone="1234567";
sunny.name="Sunny";
sunny.birthyear=1989;
sunny.birthmonth=12;
sunny.address="Sunny's home";
sunny.phone="7654321";
但如果使用数组则会是这种结果:
name[0]="Jimmy";
birthyear[0]=1985;
birthmonth[0]=9;
address[0]="Jimmy's home";
phone[0]="1234567";
name[1]="Sunny";
birthyear[1]=1989;
birthmonth[1]=12;
address[1]="Sunny's home";
phone[1]="7654321";
从大体上可以看出,结构体的条理比使用多维数组要清晰许多,至少烦人的下标已经不见了,此外,你还可以使用更加容易记忆的jimmy和sunny来记忆这些数据的分类,而不用再去记忆0下标的是Jimmy,1下标的是Sunny,而且你还会发现编译器会自动弹出成员以供直接选择,至于什么是成员请看下文。
===============华丽的分割线===============
结构体的声明:
下面详细讲解结构体,首先是结构体的声明,声明一个结构体语法如下:
struct 名称
{
成员
};
我们把结构体中包含的变量称为成员,这些成员可以是任意类型和任意数量的,你可以把相关需要包含的变量统统加入到里面来。此外末尾还有一个分号,不要忘记了。如刚才的学生结构体,拥有如下成员。
struct student
{
char *name;
int sex;
int brithyear;
int brithmonth;
char *address;
char *phone;
};
此外,结构体还可以包含结构体,如声明一个表示日期的结构体:
struct date
{
int year;
int month;
int day;
};
然后student结构体修改为如下:
struct student
{
char *name;
int sex;
date brith;
char *address;
char *phone;
};
这样,其中生日就是date的数据类型,其中包含了年月日成员。
===============华丽的分割线===============
结构体的使用:
在声明完毕后,就可以使用结构体了,不过首先需要注意的是,结构体与函数一样,使用必须在声明之后,我是指代码的位置上!但结构体不可能像函数那样支持结构声明,所以,结构体的声明通常尽量靠前,而且出现结构体包含结构体的情况下,子级必须放在父级之前声明。
因为我们所只的结构体只是一个抽象的概念,如学生,只是一个泛指,他并没有具体到哪个学生头上,我们不可能说“学生的姓名是XXX”或“学生的出生年月是多少”,必须要把这个概念具体到个体上,如“这个学生的名字是XXX”或“那个学生的出生年月是多少”等,这个具体的过程被称为实例化(Instantiation),而被具体出来的个体被称为实例(Instance)或对象(Object)。
实例化结构体有两种方法,第一种是在结构体声明后追加实例名,如:
struct student
{
char *name;
int sex;
int brithyear;
int brithmonth;
char *address;
char *phone;
}jimmy,sunny;
这样在结构体的末尾,分号的前面,就声明了jimmy与sunny两个实例,在以后的代码中,jimmy与sunny就可以直接使用了。
此外,结构体还有另外一种实例化的方法,如:
struct student
{
char *name;
int sex;
int brithyear;
int brithmonth;
char *address;
char *phone;
};
int main()
{
student jimmy,sunny;
}
这种声明方法就是把结构体实例当作变量一样声明,实际上结构体在某种意义上也算是变量,应该使用哪种方法声明,就看使用时的情况而定了。
在实例化完成后就可以使用实例了。通过成员分隔符“.”来进行成员的引用,如:
printf("%s",jimmy.name);
这里就使用了jimmy这个实例的name成员,输出的就是其姓名,此外也可以通过这种方法给成员赋值,如:
jimmy.name="Jimmy Yang";
jimmy.birthyear=1896;
如果结构体中使用了其它的结构体,想引用更加深层的结构体该怎么办呢?如:
struct date
{
int year;
int month;
int day;
};
struct student
{
char name[10];
int sex;
date brith;
char address[50];
char phone[12];
};
int main()
{
student jimmy;
printf("the birthday is %d-%d-%d",jimmy.birth.month,jimmy.birth.day,jimmy.birthyear);
}
这里输出了出生的年月日,首先,通过jimmy.birth来引用jimmy中的birth成员,birth属于date类型,再在后面追加一个.month就可以用到birth中的month成员,这是一个多重的引用。在这里你可能会奇怪,代码中并没有声明date的实例,如何来引用date的成员呢?其实在声明student时,系统会自动将其子结构体自动实例化,而且无论你的结构体如何嵌套,所有的结构体都会被自动实例化,所以这个不用你担心。
小提示:结构体可以直接赋值,如在上面的例子中:
jimmy=sunny;
那么jimmy的所有成员的值将会变成sunny的值,又如
date newdate;
newdate.year=1989;
newdate.month=10;
newdate.day=13;
jimmy.birth=newdate;
sunny.birth=newdate;
那么jimmy与sunny的生日都会变成1989年10月13日了。
===============华丽的分割线===============
结构体数组和指针:
如果一个班上有50名同学,使用jimmy、sunny等方式来命名的话,要输出班上所有人的姓名将是一项艰苦的工作,因此,结构体也可以向普通变量那样倚靠数组来完成这一工作,声明结构体数组和普通变量的数组方法是一样的,如st:
student stu[50];
这样就声明了一个长度为50的student数组,通过诸如stu[0].name或stu[5].sex来引用其中的成员。
结构体同样也支持数组,如:
student *sturef=jimmy;
这样sturef便指向了jimmy,此外需要注意的是,结构体指针的引用成员符不再是“.”,而是“->”,如sturef->name是引用的是jimmy.name,因为sturef是一个指针,所以使用“->”符号来引用它指向结构体的成员,如以下范例:
#include "afxwin.h"
struct date
{
int year;
int month;
int day;
};
struct student
{
char *name;
int sex;
date brith;
char *address;
char *phone;
};
int main()
{
student jimmy,sunny;
jimmy.name="Jimmy";
jimmy.brith.year=1985;
jimmy.brith.month=5;
jimmy.brith.day=16;
jimmy.address="Jimmy's home";
jimmy.phone="1234567";
sunny.name="Sunny";
sunny.brith.year=1989;
sunny.brith.month=12;
sunny.brith.day=6;
sunny.address="Sunny's home";
sunny.phone="7654321";
jimmy=sunny;
student *ref=&jimmy;
printf("%d",ref->brith.year);
}
输出结果将是1989,首先初始化jimmy与sunny两个结构体,然后让jimmy等于sunny,此时jimmy与sunny的内容相同,然后指派ref指针指向jimmy,最后输出其生日中的年部分,也就是jimmy生日的年部分,因为事先赋过值,因此值为来自sunnny的1989。你可能会注意到ref->birth.year中前部分用了->,这毋庸置疑,但后部分为什么依然使用“.”来引用成员?因为在ref指向的jimmy中,birth依然是个具体的结构体,而并非指针,因此引用birth中的成员依然使用“.”。
PS:在如下代码中:
student stu[50];
stu[0].name="Jimmy";
stu[1].name=“Sunny";
student *ref=&stu[0];
ref++;
printf("%s",ref->name);
输出结果将是什么?如果将student *ref=&stu[0];换成student *ref=stu;,可以成功运行吗?如果可以,运行结果又将是什么?
===============华丽的分割线===============
枚举的概念和声明:
在讲解枚举之前,首先在student这个结构体中添加一个新的成员:学科,表示如下:
int subject;
其中,为0时代表汉语,1代表英语,2代表数学,3代表计算机。额,或者说还有更多学科,要记忆这些学科确实是一件很麻烦的事情,太多了可能就要用笔去记了,在计算机上用笔记东西确实比较难为情,当然,你可能会想到一个解决方案,使用常量或宏替换?那也确实是个不错的方法,不过今天不说这个,如果能把这些数字用一些单词或者助记符来代替,会方便许多,而且可以提高代码的可读性,因此枚举(Enumeration)的概念便产生了。首先看枚举的声明方法:
enum 名称
{
成员
};
如:
enum subjectenum
{
chinese=0,
english=1,
math=2,
computer=3
};
这样就声明好了一个枚举subjectenum,可以看到成员的名称与其代表的值通过“=”来一一对应起来,其中需要注意的是枚举的成员之间使用逗号分隔,而不再是分号,末尾也必须打上分号代表结束。此外,成员中数字部分并非强制性的,如果不存在数字部分则默认从0开始,每次增量为1,如:
enum subjectenum
{
chinese,
english,
math,
computer
};
其中成员所代表的值依旧依次是0、1、2、3,又如果:
enum subjectenum
{
math=3,
computer
};
那么,computer将代表4,因为之前的math已经指定为3,而增量为1,那么后面的computer自然就是4了。
===============华丽的分割线===============
枚举的使用:
在声明好枚举之后,就可以使用它了。枚举也同结构体一样,需要实例化后才能使用,实例化的手段也和结构体一样:
enum subjectenum
{
chinese,
english,
math,
computer
}subj1,subj2;
或者
enum subjectenum
{
chinese,
english,
math,
computer
}subj1,subj2;
int main()
{
subjectenum subj1,subj2;
}
实例化之后,就可以正式使用枚举了。首先展示一下改良后的student结构体,加入枚举成员subject,顺便去掉了birth这个麻烦的成员:
struct student
{
char *name;
int sex;
char *address;
char *phone;
subjectenum subject;
}jimmy;
然后给这个结构体进行赋值:
jimmy.name="Jimmy";
jimmy.sex=1;
jimmy.address="Jimmy's home";
jimmy.phone="1234567";
jimmy.subject=computer;
这样就赋值好了,注意看到最后一个枚举的赋值,天哪,枚举就是这样赋值的啊!直接将枚举的成员赋值给它,连引号都不用打啊,使得,jimmy想学计算机,恩,就让他直接等于computer这个成员,想学数学,就等于math,想来北京看奥运,就选chinese。不过需要注意的是枚举只能等于它的成员,你想让jimmy.subject=japanese;,门都没有。
小提示:枚举的成员与其对应的数值是一一对应的,数值是其具体的数据形式,如
jimmy.subject=computer;
printf("%d",jimmy.subject);
结果就是3,因为computer=3,3才是其真正的值,输入枚举的时候却不能直接赋值数值,如:
jimmy.subject=2;
这是错误的,因为类型不符,因此需要做一个类型转换,如:
jimmy.subject=(subjectenum)2;
这样就可以实现赋值了,如果需要通过整型变量赋值,请记住,枚举的值的类型是unsigned short。
PS:你能让student的性别sex也变成枚举吗?
===============华丽的分割线===============
联合的概念和声明:
国际惯例,依然是先说明情况和问题,再介绍概念,如果某学校的专业设置太多了,以至于枚举已经不能跟上脚步,如Jimmy不再学想计算机,因为他搞不懂联合是什么意思,反而喜欢上了宇宙飞船的花纹设计,也就是说有些专业不被包含在枚举当中,但它有必须被储存在变量中,这就造成了一个矛盾,如果能让这些罕见的潮流专业也能够被储存的好了,如使用字符串来储存就是个不错的想法,但是又想保留原先专业的枚举格式,因为枚举实在太好用了。怎么能够一举两得呢?联合(Union)完美地解决了这个问题。
首先,我们要做的肯定是想某种办法把枚举subjectenum和某个字符串放在一起,在不同的情况下使用不同的类型,看下把它们放在联合中会有什么效果,首先了解下联合的声明格式:
union 名称
{
成员
};
放入成员变量:
union subjectsuper
{
subjectenum subjecte;
char *subjectc;
};
下面是实例化联合了,也是国际惯例的事情了,放在声明后面或别处代码中都可以,这里就不罗嗦了。假设已经实例化了一个新联合subjectsuper subject。
下面了解下联合究竟如何工作来解决这个棘手的问题。联合中的成员与结构体中的成员不同,结构体中的成员都有独立的内存空间而联合是共享内存空间,为什么呢?因为联合一次只能储存其中的一个成员,如果这个联合subjectsuper储存的需要信息是一个枚举中已知的科目,那么就给其中的枚举赋值,如果是个潮流科目,那么就给其中的字符串赋值,然后根据情况来取就是了,你可能会问,就像Jimmy之前做的那样,原先是计算机,赋值了是个枚举,而后又选择了宇宙飞船花纹设计,重新赋值为字符串,那么之前的枚举到哪里去了呢?当然是被字符串覆盖掉了,因为联合的成员是共享内存的,需要是枚举时,这个内存就储存枚举的内容,需要是字符串时,就储存字符串的内容,依情况而储存,原先的值当然就不会再存在了。如果说结构体占用的内存长度为所有成员之和,那么联合所占用的内存长度就是其所有成员中长度最大的那个长度了。但有个疑问是如果把student结构体作如下声明也可以达到同样效果:
struct student
{
char *name;
int sex;
char *address;
char *phone;
subjectenum subjecte;
char *subjectc;
}jimmy;
这里通过使用两个变量也可以达到同样效果,但是你需要注意的是,subjecte与subjectc的长度之和肯定要大于其中之一的长度,不要问如果subjecte的长度是负数怎么办之类的蠢问题,总值使用联合可以为你节省内存空间。
下面把student 3.0及其附属的完整版展示一下:
enum subjectenum
{
chinese,
english,
math,
computer
};
union subjectunion
{
subjectenum subjecte;
char *subjectc;
};
struct student
{
char *name;
int sex;
date birth;
char *address;
char *phone;
subjectunion subject;
};
===============华丽的分割线===============
联合的使用:
使用联合与使用结构体类似,如针对上面的结构体的jimmy实例:
jimmy.subject.subjecte=computer;
jimmy.subject.subjectc="Wave Designer for Airship";
现在jimmy是一名Wave Designer for Airship,宇宙飞船花纹设计者了,这个computer的枚举值已经灰飞湮灭了。
最后替你们问个问题是,怎样知道联合中的那个成员是有效的呢?或者说是联合当前的值是什么呢?我的做法是用一个变量来储存其类型,如bool isenum,这样就可以根据情况来选择成员了,如果拥有更多成员,就需要char甚至是int来储存其类型了。下面是student 4.0:
enum subjectenum
{
chinese,
english,
math,
computer
};
union subjectunion
{
subjectenum subjecte;
char *subjectc;
};
struct student
{
char *name;
int sex;
date birth;
char *address;
char *phone;
bool isenum;
subjectunion subject;
};
===============华丽的分割线===============
本章的范例是:使用student 4.0的结构体数组来储存2名学生的信息档案,然后输出它们。代码如下:
#include "afxwin.h"
struct date
{
int year;
int month;
int day;
};
enum subjectenum
{
chinese,
english,
math,
computer
};
union subjectunion
{
subjectenum subjecte;
char subjectc[20];
};
struct student
{
char name[20];
int sex;
date birth;
char address[50];
char phone[20];
bool isenum;
subjectunion subject;
};
int main()
{
student stu[2];
//输入
for (int i=0;i<2;i++)
{
char subtemp[20];
printf("input the name of student%d:",i+1);
scanf("%s",stu[i].name);
printf("input the sex of student%d:",i+1);
scanf("%d",&stu[i].sex);
printf("input the year of birth of student%d:",i+1);
scanf("%d",&stu[i].birth.year);
printf("input the month of birth of student%d:",i+1);
scanf("%d",&stu[i].birth.month);
printf("input the day of birth of student%d:",i+1);
scanf("%d",&stu[i].birth.day);
printf("input the address of student%d:",i+1);
scanf("%s",stu[i].address);
printf("input the phone of student%d:",i+1);
scanf("%s",stu[i].phone);
printf("input the subject of student%d:",i+1);
scanf("%s",subtemp);
//判断类型,如果以0 1 2 3开头则是数字,否则为字符串
if (subtemp[0]>='0' && subtemp[0]<='3')
{
//是数字,采用枚举
stu[i].isenum=true;
stu[i].subject.subjecte=(subjectenum)(subtemp[0]-48);
}
else
{
//不是数字,采用字符串
stu[i].isenum=false;
strcpy(stu[i].subject.subjectc,subtemp);
}
}
//输出
for (int i=0;i<2;i++)
{
printf("Student%d\n",i+1);
printf("Name:%s\n",stu[i].name);
printf("Sex:%d\n",stu[i].sex);
printf("Year of Birth:%d\n",stu[i].birth.year);
printf("Month of Birth:%d\n",stu[i].birth.month);
printf("Day of Birth:%d\n",stu[i].birth.day);
printf("Address:%s\n",stu[i].address);
printf("Phone:%s\n",stu[i].phone);
if (stu[i].isenum)
{
printf("Subject:%d\n",stu[i].subject.subjecte);
}
else
{
printf("Subject:%s\n",stu[i].subject.subjectc);
}
}
system("pause");
}
===============华丽的分割线===============
下面是运行效果图:
===============华丽的分割线===============