C#程序设计第11章结构体联合体与位运算.docx
- 文档编号:9242859
- 上传时间:2023-05-17
- 格式:DOCX
- 页数:42
- 大小:132.49KB
C#程序设计第11章结构体联合体与位运算.docx
《C#程序设计第11章结构体联合体与位运算.docx》由会员分享,可在线阅读,更多相关《C#程序设计第11章结构体联合体与位运算.docx(42页珍藏版)》请在冰点文库上搜索。
C#程序设计第11章结构体联合体与位运算
第11章结构体、联合体与位运算
本章介绍结构体、联合体及枚举类型等三种新的构造型数据类型以及位运算的基本方法,包括结构体的含义;结构体类型变量的定义、引用及初始化方法;结构体数组的定义和数组元素的引用;结构体类型指针的概念及链表的基本操作方法;联合体的含义;联合体类型变量的定义方法;枚举类型的定义;TYPEDEF的作用和位运算的基本方法等。
11.1结构体类型
通过前面有关章节的学习,我们认识了整型、实型、字符型等C语言的基本数据类型,也了解了数组这样一种构造型的数据结构,它可以包含一组同一类型的元素。
但仅有这些数据类型是不够的。
在实际问题中,有时需要将不同类型的数据组合成一个有机的整体,以便于引用。
例如,在新生入学登记表中,一个学生的学号、姓名、性别、年龄、总分等,它们属于同一个处理对象,却又具有不同的数据类型。
如图11-1。
每增加、删减或查阅一个学生记录,都需要处理这个学生的学号、姓名、性别、年龄、总分等数据,因此,有必要把一个学生的这些数据定义成一个整体。
学号
(整型)
姓名
(字符型)
性别
(字符型)
年龄
(整型)
总分
(实型)
11301
ZhangPing
F
19
496.5
图11-1
虽然数组作为一个整体可用来处理一组相关的数据,但不足的是,一个数组只能按序组织一批相同类型的数据。
对于一组不同类型的数据,显然不能用一个数组来存放,因为数组中各元素的类型和长度都必须一致。
为了解决这个问题,C语言中给出了另一种构造数据类型——“结构体”。
11.1.1结构体类型与结构体变量
结构体是一种构造类型,它由若干“成员”组成。
每一个成员可以是一个基本数据类型或者又是一个构造类型。
结构体既然是一种构造而成的数据类型,那么在使用之前必须先定义它,如同在调用函数之前要先定义或声明一样。
定义一个结构体类型的一般形式为:
struct结构体名
{成员1类型1;
成员2类型2;
...
成员n类型n;
};
“结构体”这个词是根据英文单词structure译出的。
结构体中的每个成员均须作类型说明,成员名的命名应符合标识符的书写规定,成员名可以与程序中的变量名同名,二者不代表同一对象,互不干扰。
例如:
structstudent
{intnum;/*学号*/
charname[20];/*姓名*/
charsex;/*性别*/
intage;/*年龄*/
floatscore;/*成绩*/
};
在上述定义中,structstudent是结构体类型名,其中struct是关键字,在定义和使用中都不能省略。
该结构体由5个成员组成。
第一个成员为num,整型变量,当然,在实际应用中我们也常常把学号定义为字符型;第二个成员为name,字符数组;第三个成员为sex,字符型;第四个成员为age,整型;第五个成员为score,实型变量。
应注意末尾的分号是必不可少的。
数据类型和变量是两个不同的概念。
有了一种结构体类型之后,就可用它去定义变量,就象用int去定义一个整型变量那样。
定义结构体类型的变量有以下三种方法。
1.先定义结构体类型,再定义变量。
例如:
structstudent
{intnum;
charname[20];
charsex;
intage;
floatscore;
};
structstudentstudent1,student2;
本例中,在定义了structstudent这个结构体类型之后,再用这个类型标识符去定义了两个结构体变量student1与student2。
为了使用方便,也可以在程序开头定义一个符号常量来表示一个结构体类型。
例如上例可改写成:
#defineSTUstructstudent
...
STU
{intnum;
charname[20];
charsex;
intage;
floatscore;
};
STUstudent1,student2;
2.在定义结构类型的同时定义结构体变量。
例如:
structstudent
{intnum;
charname[20];
charsex;
intage;
floatscore;
}student1,student2;
这是一种紧凑形式,既定义了类型,同时又定义了变量。
如果需要,下文还可再用structstudent定义其它同类型变量。
它的一般形式为:
struct结构体名
{
成员1类型1;
成员2类型2;
...
成员n类型n;
}变量名表列;
3.直接定义结构体变量。
例如:
struct
{intnum;
charname[20];
charsex;
intage;
floatscore;
}student1,student2;
直接定义了两个结构体变量student1与student2。
这种方法省去了结构体名,缺点是若下文再想定义同类型的变量就不便了。
上述三种方法中定义的变量student1与student2都具有下图所示的结构,其所有的成员都是基本数据类型或数组类型。
num
name
sex
age
score
图11-2
若想将其中的age换成出生日期birthday,定义成含有年份、月份、日期三个子成员的类型,如图11-3所示,则需先定义一个structdate日期类型,再用它去定义birthday。
这就形成了嵌套的结构体。
num
name
sex
birthday
Score
year
month
day
图11-3
按图可给出以下结构定义:
structdate
{intyear;
intmonth;
intday;
};
structstudent
{intnum;
charname[20];
charsex;
structdatebirthday;
floatscore;
}student1,student2;
首先定义一个结构体类型structdate,由month(月)、day(日)、year(年)三个成员组成。
再将它用到structstudent类型的定义中,使其中的成员birthday被定义为structdata类型。
类型与变量是不同的概念,不要混同。
对结构体变量来说,在定义时一般先定义一个结构体类型,然后定义变量为该类型。
只能对变量赋值、存取或运算,而不能对一个类型赋值、存取或运算。
在编译时,对类型是不分配空间的,只对变量分配空间。
11.1.2结构体变量的引用
1.引用结构体变量中的一个成员
由于一个结构体变量包含多个成员,要访问其中的一个成员,必须同时给出这个成员所属的变量名以及其中要访问的成员名本身,引用方式为:
结构体变量名.成员名
其中的圆点符号称为成员运算符。
对成员变量可以象普通变量一样进行各种操作。
例如,将学号11301赋给student1中的num,应写成以下形式:
student1.num=11301;
将姓名“ZhangPing”通过键盘赋给student1中的name,应写成:
scanf("%s",&student1.name);
将student2中的score加1,然后输出该值,应写成:
student2.score=student2.score+1;或student2.score++;
printf("%f",student2.score);
成员运算符的运算级别最高,例如:
student.num+100,在num两侧有二个运算符,由于成员运算符的运算优先于加号运算符,故相当于(student.num)+100
2.成员本身又是结构体类型时的子成员的访问
如果成员本身又是一种结构体类型时,那么对其下级子成员再通过成员运算符去访问,一级一级地直到最后一级成员为止。
例如上文提到的birthday,可以这样去访问:
student1.num
student1.birthday.year
student1.birthday.month
student1.birthday.day
student1.score
这里,student1.birthday本身相当于一个结构体变量。
注意下述用法是错误的:
year/*少了上两级所属主体*/
birthday.year/*少了结构体变量主体*/
student1.year/*不能跨级访问*/
year.birthday.student1/*不能颠倒次序*/
3.同一种类型的结构体变量之间可直接赋值
一般地,可以将一个结构体变量作为一个整体赋给另一个具有相同类型的结构体变量。
例如:
student2=student1;
student1与student2两者类型相同,上述赋值语句相当于将student1中各个成员的值逐个依次赋给student2中的相应成员。
若两者的类型不一致时,则不能直接赋值。
通常,也可以把一个结构体变量中的内嵌结构体类型成员赋给同种类型的另一个结构体变量的相应部分。
如下列语句是合法的:
student2.birthday=student1.birthday;
4.不允许将一个结构体变量作为一个整体进行输入或输出
下述用法是错误的:
scanf("%d,%s,%c,%d,%f",&student1);/*错*/
printf("%d",student1);/*错*/
printf("%d,%s,%c,%d,%f",student1);/*错*/
5.一个结构体变量所占用的存储空间就是其所有成员所占空间之和。
11.1.3 结构体变量的初始化
与其他类型变量一样,对结构体变量也可以在定义时进行初始化赋值,但附在变量后面的一组数据须用花括号括起来,其顺序应与结构体中的成员顺序保持一致。
【例11-1】对结构体变量初始化。
main()
{structstudent
{intnum;
charname[20];
charsex;
intage;
floatscore;
}student1={11301,"ZhangPing",'F',19,496.5};
printf("Number=%d\nName=%s\n",student1.num,student1.name);
printf("Score=%f\n",student1.score);
}
运行结果如下:
Number=11301
Name=ZhangPing
Score=496.
本例中,student1在被定义的同时,其各成员也按顺序被赋予了相应的一组数据。
11.2结构体数组
一个结构体变量只能存放一个对象(如一个学生、一个职工)的一组数据。
如果要存放一个班(30人)学生的有关数据就要设30个结构体变量,例如student1,student2,…,student30,显然是不方便的。
人们自然想到使用数组。
C语言允许使用结构体数组,即数组中每一个元素都是一个结构体变量。
11.2.1结构体数组的定义
定义结构体数组的方法与定义结构体变量方法相似,只是要多用一个方括弧以说明它是个数组。
在上一节中定义结构体变量的三种方法可以作为定义结构体数组的参考。
如:
structstudent
{intnum;
charname[20];
charsex;
intage;
floatscore;
}student1,stu[30];
以上定义了一个结构体变量student1和一个结构体数组stu,这个数组有30个元素,每一个元素都是structstudent类型的,如图11-4所示。
数组各元素在内存中占用连续的一段存储单元。
num
name
sex
age
score
stu[0]
11301
ZhangPing
F
19
496.5
stu[1]
11302
WangLi
F
20
483
…
…
…
…
…
…
stu[29]
11330
MaoQiang
M
18
502
图11-4
结构体数组定义之后,要引用某一元素中的一个成员,可采用以下形式:
stu[i].score
式中i为数组元素的下标。
11.2.2结构体数组的初始化
只有对定义为外部的或静态的数组才能初始化。
在对结构体变量初始化时,要将每个元素的数据分别用花括弧括起来。
【例11-2】设有四位同学的有关数据,试统计出他们的平均年龄和平均成绩。
structstudent
{intnum;
charname[20];
charsex;
intage;
floatscore;
};
structstudentstu[4]={{11301,"ZhangPing",'F',19,496.5},
{11302,"WangLi",'F',20,483},{11303,"LiuHong",'M',19,503},
{11304,"SongRui",'M',19,471.5}};
main()
{inti;
floata,s;
for(i=0;i<4;i++)
{a=a+stu[i].age;
s=s+stu[i].score;
}
printf("Theaverageageis%6.2f\n",a/4);
printf("Theaveragescoreis%6.2f\n",s/4);
}
运行结果如下:
Theaverageageis19.25
Theaveragescoreis488.50
11.3结构体指针变量
通常,可以定义一个指针变量用来指向一个结构体变量,这就是结构体指针变量。
结构体指针变量的值就是所指结构体变量在内存单元中的起始地址。
指针变量也可用来指向结构体数组中的元素。
11.3.1结构体指针变量
定义结构体指针变量的一般形式如下:
structstudent*p;
上述语句定义了一个指针变量p,它可以指向任何一个属于structstudent类型的数据。
通过指针去访问所指结构体变量的某个成员时,有如下两种方法:
(*p).score
或者
p->score
后者是常见的一种使用方式,其中->称为指向运算符。
【例11-3】用指针访问结构体变量及结构体数组
structstudent
{intnum;
charname[20];
charsex;
intage;
floatscore;
};
structstudentstu[3]={{11302,"Wang",'F',20,483},{11303,"Liu",'M',19,503},
{11304,"Song",'M',19,471.5}};
main()
{structstudentstudent1={11301,"ZhangPing",'F',19,496.5},*p,*q;
inti;
p=&student1;/*让指针p指向结构体变量student1,如图11-5所示*/
printf("%s,%c,%5.1f\n",student1.name,(*p).sex,p->score);
q=stu;/*让指针p指向数组stu,即指向数组中的第一个元素stu[0],如图11-6所示*/
for(i=0;i<3;i++,q++)
printf("%s,%c,%5.1f\n",q->name,q->sex,q->score);
}
运行结果如下:
ZhangPing,F,496.5
Wang,F,483.0
Liu,M,503.0
Song,M,471.5
指针符号“->”的使用比较常见。
请分析以下几种运算:
p->age得到p指向的结构体变量中的成员age的值;
p->age++先引用p所指成员age的值,用完后再使该成员值加1;
++p->age先使p所指成员age的值加1,然后再引用这个新值;
(p++)->age先引用p->age的值,用完后再使指针p加1;
(++p)->age先使指针p加1,然后再引用p->age这个值;
例如,【例11-3】中的for语句:
for(i=0;i<3;i++,q++)
printf("%s,%c,%5.1f\n",q->name,q->sex,q->score);
可改写为:
for(i=0;i<3;i++)
printf("%s,%c,%5.1f\n",q->name,q->sex,(q++)->score);
11.3.2用结构体变量和结构体指针变量作函数参数
结构体变量以及结构体指针变量均可以象int类型那样作为函数的参数,甚至可以把一个函数定义成结构体型或结构体指针型。
【例11-4】对年龄在19岁以下(含19岁)同学的成绩增加10分。
structstudent
{intnum;
charname[20];
charsex;
intage;
floatscore;
};
structstudentstu[3]={{11302,"Wang",'F',20,483},{11303,"Liu",'M',19,503},
{11304,"Song",'M',19,471.5}};
voidprint(structstudents)
{printf("%s,%d,%5.1f\n",s.name,s.age,s.score);
}
voidadd10(structstudent*q)
{if(q->age<=19)
q->score=q->score+10;
}
main()
{structstudent*p;
inti;
for(i=0;i<3;i++)
print(stu[i]);/*调用print函数*/
for(i=0,p=stu;i<3;i++,p++)
add10(p);/*调用add10函数*/
printf("\n");
for(i=0,p=stu;i<3;i++,p++)
print(*p);
}
运行结果如下:
Wang,20,483.0
Liu,19,503.0
Song,19,471.5
Wang,20,483.0
Liu,19,513.0
Song,19,481.5
本例中,函数print中的形参s,属于structstudent结构体类型,与调用语句中的实参stu[i]或*p的类型一致;函数add10中的形参q,属于structstudent结构体指针类型,与调用语句中实参p指针的类型一致。
【例11-5】将上例中的函数add10改写成一个返回结构体类型值的函数
structstudentadd10(structstudent*q)
{if(q->age<=19)
q->score=q->score+10;
return*q;
}
相应地,在主函数main中的调用语句也须改为:
for(i=0,p=stu;i<3;i++,p++)
*p=add10(p);
11.4链表
11.4.1链表概述
通过前面有关章节的学习,我们知道,用数组存放数据时,必须事先定义固定的长度。
比如a[100]最多可以存放100个数组元素,多一个都不行。
如果待处理的数据个数较多,事先难以确定数组的大小时,我们只能把这个数组定义得足够大,以存放任何可能具有的数据。
由于数组的大小与其占用的内存空间成正比,因此,在这种情况下内存浪费现象将比较严重。
为此,我们需要一种新的数据结构:
当数据每增加一个时,可以向系统申请空间从中增加一个元素;当数据每减少一个时,可以删除一个元素,释放其所占内存空间。
也就是能够进行动态存储分配,这就是链表。
链表是一种常见的重要的数据结构。
图11-7
链表中的每个元素称为结点,一个链表由若干结点组成。
图11-7是一种简单的单向链表。
链表中的每个结点都应包括两部分:
其一是数据区,存放本结点要存储的数据,其二是指针区,存放下一个结点的首地址。
因此,链表中的每个结点在内存中可以是不连续的。
访问链表时,通过第一个结点,可以找到第二个结点;通过第二个结点,又可以找到第三个结点;…;直到最后一个结点为止。
链表的第一个结点称为头结点,最后一个结点称为尾结点。
链表的这种数据结构只能利用指针变量才能实现。
指向头结点的指针变量称为链表的头指针,图11-7中以head表示。
尾结点因无需指向其它结点,通常将其指针区赋值为空值NULL,以便判断。
11.4.2处理链表的函数
在对链表进行动态管理时,需要用到如下的几个函数。
:
1.分配内存空间函数malloc
调用形式:
(类型说明符*)malloc(size)
功能:
在内存的动态存储区中分配一块长度为size个字节的连续区域。
函数的返回值为该区域的首地址。
“类型说明符”表示指定该区域用于何种数据类型,“(类型说明符*)”表示把返回值强制转换为该类型指针,“size”用于指定空间大小。
例如:
pc=(char*)malloc(100);
表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的首地址,上述语句把该地址赋予指针变量pc。
2.分配内存空间函数calloc
calloc也用于分配内存空间。
调用形式:
(类型说明符*)calloc(n,size)
功能:
在内存动态存储区中分配n块长度为“size”字节的连续区域。
函数的返回值为该区域的首地址。
calloc函数与malloc函数的区别仅在于一次可以分配n块区域。
3. 释放内存空间函数free
调用形式:
free(指针变量ptr);
功能:
释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,它指向被释放区域的首地址。
被释放区应是由malloc或calloc函数所分配的区域。
除了上述三个函数外,另有一个虽与链表没有直接关系,但在创建结点中经常用到的函数sizeof,它用于测试某种数据类型的宽度,也就是这种类型的数据在内存中所占用的字节数。
例如:
floata;
printf("%d",sizeof(char));
printf("%d",sizeof(a));
结果为1和4,因为一个字符型数据只需占用一个字节,一个实型变量占四个字节。
ANSIC标准要求在使用动态分配函数时要用#include命令将stdlib.h文件包含进来。
11.4.3链表的建立
要建立链表,必须先定义结点的数据类型。
前面介绍的结构体变量,包含若干成员。
这些成员可
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C#程序设计 第11章 结构体联合体与位运算 C# 程序设计 11 结构 联合体 运算
![提示](https://static.bingdoc.com/images/bang_tan.gif)