第五章 构造数据类型.docx
《第五章 构造数据类型.docx》由会员分享,可在线阅读,更多相关《第五章 构造数据类型.docx(21页珍藏版)》请在冰豆网上搜索。
![第五章 构造数据类型.docx](https://file1.bdocx.com/fileroot1/2022-12/29/1cd663db-8c33-4143-b43d-0f4bd0817a58/1cd663db-8c33-4143-b43d-0f4bd0817a581.gif)
第五章构造数据类型
第五章构造数据类型
Cx51的构造数据类型主要有数组、指针和结构等。
在单片机系统中,数组的应用比较广泛,指针则次之,结构用的相对较少,这和单片机系统的要求以及用户的程序设计习惯有一定的关系。
一、数组
数组是同类型数据的一个有序集合。
数组用一个名字来标识,称为数组名。
数组中各元素的顺序用下标表示,下标为n的元素可以表示为数组名[n]。
改变[]中的下标就可以访问数组中所有的元素。
1、一维数组
由具有一个下标的数组元素组成的数组称为一维数组,定义一维数组的一般形式如下:
类型说明符[存储器类型]数组名[元素个数][={初值1,……初值n}];
其中,数组名是一个标识符,元素个数是一个常量表达式,不能是含有变量的表达式。
和标准C比较,多了一个存储器类型。
例1:
unsignedintidatademo1[10];
定义一个数组名为demo1的数组,数组包含10个无符号整型的元素,片内间接寻址。
在定义数组时可以对数组进行整体初始化,若定义后想对数组赋值,则只能对每个元素分别赋值。
例2:
chardataa[5]={1,2,3,4,5};//给全部元素赋值,a[0]=1,a[1]=2,a[2]=3,a[3]=4,a[4]=5
intb[6]={1,2,6};//给部分元素赋值,b[0]=1,b[1]=2,b[2]=6,b[3]=b[4]=b[5]=0
2、二维数组和多维数组
数组的下标具有两个,则称为二维数组。
定义二维数组的一般形式如下:
类型说明符存储器类型数组名[行数][列数][={{初值表1},……,{初值表n}}];
其中,数组名是一个标识符,行数和列数都是常量表达式。
例1:
floatxdatademo2[3][4];//demo2数组有3行4列共12个实型元素,存储在片外RAM中。
二维数组也可以在定义时进行整体初始化,也可以在定义后单个地进行赋值。
例2:
charpdataa[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};//全部初始化
intb[3][4]={{1,2,3,4},{5,6,7,8},{}};//部分初始化,未初始化的个元素为0
说明:
1)多维数组是二维数组的延伸,定义格式基本相同。
但由于单片机中内存较小,多维数组用的很少。
2)如果不赋初值,计算机自动置0。
3、字符数组
若一个数组的元素是字符型的,则该数组就是一个字符数组。
例如:
chara[20]={“ShanxiLigongxueyuan”};//字符数组
charadd[3][6]={"weight","height","width"};//字符串数组
数组名
地址
数据
a[1]
0x34
0x12
a[2]
0x35
0x34
a[3]
0x36
0x56
4、数组的存储
1)一维数组的存储
一维数组在存储空间连续存储,数组名就是数组存储的首地址。
注意不同的数据类型占用的字节长度不一样。
数组名
地址
x[0][0]
+0
x[0][1]
+1
x[0][2]
+2
x[1][0]
+3
x[1][1]
+4
x[1][2]
+5
x[2][0]
+5
x[2][1]
+7
x[2][2]
+8
chardataa[3]={0x12,0x34,0x56};
2)二维数组的存储
对二维数组,同样数组名就是首地址,存储时,将每列的数据存完后,再存储下一列的数据,如图:
表中地址+1表示加一个数据单位,根据数据类型不同一个数据单位所占字节不同,如插入为1字节;int为2字节;long为4字节等。
5、数组的应用
数组在编程时应用很多,如LED显示的字形表,在数据进行滤波等处理中多用到数组。
下面举一个将摄氏温度转换成华氏温度的例子,在出口产品中经常要求转换。
例1:
将摄氏温度0-5℃转换成华氏温度。
转换关系:
转换时,可以用公式在程序中进行计算转换,也可以先将转换关系转换成表格,用查表法进行转换,一般查表法占用内存少,速度快,对于表格数量不大的情况下,经常用数组或查表法。
源程序如下:
#defineucharunsignedchar
ucharcodetempt[]={32,34,36,37,39,41};/*数组元素可以为空,根据实际数据多少在编译时设置,初值为转换成华氏温度的值,按顺序排列*/
ucharftoc(uchardegc)//转换函数
{returntempt[degc];}
main()
{x=ftoc(5);}
例2:
用冒泡法对一组数据进行排序。
排序是一种十分有用的数据处理方法,例如在对测量值进行中值滤被之前,必须先对测量数据进行排序。
#include
#include
#defineucharunsignedchar
voidmain()
{uchardataa[]={0x3f,0x44,0x32,0x54,0x66,0x56,0x99,0x88,0x77,0x11,0x34};//11个数
uchari,j,t;
for(j=0;j<=8;j++)
for(i=0;i<=9-j;i++)//第1次进行10次,每次减1
if(a[i]>a[i+1])
{t=a[i];
a[i]=a[i+1];
a[i+1]=t;}//交换数据,将最大的排到最后
}运行结果,将数组内的数从小到大排列。
(冒泡法排序)
例3:
用8155扩展的LED显示。
共显示4个数据,每个数据小于999,用8155的A口作为字位控制,地址为ff21,共阳极,点亮时输出高电平;用8155的B口输出字型码,地址为ff22,低电平亮。
参考程序如下:
#include
#include
#defineucharunsignedchar
#defineuintunsignedint
#defineCOM8155PBYTE[0xff20]//控制字地址
#definePA8155PBYTE[0xff21]//字位地址
#definePB8155PBYTE[0xff22]//字形码地址
ucharcodedispcode[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,
0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,
0xff,0x0c,0x89,0xbf};//字形代码表,共阳
uintdispbuf[3],getdata[4]={123,343,564,679};
/*显示缓存和4通道数据缓存,全局变量*/
//uchardd,m=0;
disp2()//显示字符,显示3位,
{
uchari1=2,j;
for(j=0x04;j!
=0;j=j>>1)//循环3次
{
PA8155=j;
PB8155=dispcode[dispbuf[i1]];
I1=i1--;
}
}
disp1(uchark)//显示拆分字符程序
{uintgetd;
getd=getdata[k];
dispbuf[2]=getd/100;
getd=getd%100;
dispbuf[1]=getd/10;
dispbuf[0]=getd%10;
disp2();
}
voidmain(void)
{
uchari=0;
COM8155=0x01;//PA,PB为基本输出方式
SP=0x53;
PA8155=0x00;//关显示
while
(1)
{
disp1(i);
i=i++;
if(i==4)i=0;
}}
(实验显示)
二、结构
1、基本概念
结构是一种构造类型的数据,它是将若干个不同类型的数据变量有序地组合在一起而形成的一种数据的集合体。
组成该集合体的各个数据变量称为结构成员,整个集合体使用一个单独的结构变量名。
一般来说结构中的各个变量之间是存在某些关联的,例如时间数据中的时、分、秒,日期数据中的年、月、日等。
由于结构是将一组相关联的数据变量作为一个整体来进行处理,因此在程序中使用结构将有利于对一些复杂且具有内在联系的数据进行有序的管理。
2、结构变量的定义
有二种定义结构变量的方法,分述如下。
1)先定义结构类型再定义结构变量名
定义结构类型的一般格式为:
struct结构名
{结构成员表};
其中,“结构成员表”为该结构中的各个成员,由于结构可以由不同类型的数据组成,因此对结构中的各个成员都要进行类型说明。
例如定义一个日期结构类型date,它由三个结构元素year、month、day组成,定义格式如下:
structdate
{intyear;
charmonth;
charday;};
结构类型是对该结构中都含那些元素,各元素的数据类型进行说明,编译时给变量分配存储空间。
(上例中1个变量留4个存储单元)
定义好结构类型之后,就可以用它来定义结构变量。
一般格式为:
存储器类型struct结构名结构变量名l,结构变量名2,…,结构变量名n;
注意:
先定义结构类型,然后定义结构变量。
例如可以用结构date来定义两个结构变量dl和d1:
structdatedl,d2;
结构变量dl和d2都具有structdate类型的结构,即它们都是由1个整型数据和2个字符型数据所组成的。
2)在定义结构类型的同时定义结构变量名
这种方法是将方法l的两个步骤合在一起,一般格式为:
struct结构名
{结构成员表}存储器类型结构变量名l,结构变量名2…结构变量名n;
例如对于上述日期结构变量也可按以下格式定义:
structdate
{intyear;
charmonth,day;
}xdatad1,d2;
关于结构变量的应用有几点需要注意的地方:
1)结构类型与结构变量是两个不同的概念。
定义一个结构类型时只是给出了该结构的组织形式,并没有给出具体的组织成员。
因此结构名不占用任何存储空间,也不能对一个结构名进行赋值、存取和运算。
结构变量是一个结构中的具体组织成员,编译器会给具体的结构变量名分配确定的存储空间,因此可以对结构变量名进行赋值、存取和运算。
2)一个结构中的结构成员还可以是另外一个结构,即可以形成结构的嵌套。
3)一个结构中的结构成员的名字可以与程序中其它地方使用的变量同名。
在使用中不会互相影响。
4)结构体成员不能是位变量。
3、结构变量的引用
在定义了一个结构变量之后,就可以对它进行引用,即可以进行赋值、存取和运算。
一般情况下,结构变量的引用是通过对其结构成员的引用来实现的。
引用结构成员的一般格式为:
结构变量名·结构成员
其中“·”是存取结构成员运算符。
例如:
d1·year=2006;表示给结构变量dl中的成员year赋值。
如果一个结构变量中的结构成员又是另外一个结构变量,即出现结构的嵌套时,则需要采用若干个成员运算符,一级级地找到最低级的结构成员,而且只能对这个最低级的结构成员进行访问。
例如:
clerk1·birthday·year=2006
4、例子
1)#include
voidmain()
{
structdate//说明第一个结构
{
intmonth;
charday;
intyear;//说明3个结构成员
}datadate1;//第一个结构变量的1种说明方法
pdatastructdatedate2;//第二种说明方法
structclerk//说明第二个结构
{
intnum;
structdatebirthday;//第一个结构是第二个结构的成员
};
structclerkteacher;//第二个结构的第一个变量说明
xdatastructclerkclassl[30];
xdatastructclerkclass2[30];//第二个结构的其他变量
date1·month=9;
datel·day=13;
date1·year=1989;
date2·month=8;//结构的引用
date2·day=22;
date2·year=2005;
class1[23]·birthday·month=1O;
class2[3]·birthday·month=11;
}
(运行“结构”)
5、结构数据的存储
成员1
变
量
1
成员2
……
成员k
成员1
变
成员2
量
……
2
成员k
成员1
……
按照结构变量为大循环,结构成员为小循环依次存放。
即按定义的顺序,每一个结构变量的成员是在连续空间,存完一个变量的成员后,接着存放下一个成员。
与数组相同,也是按数据单位存放。
三、指针
指针是C语言的一个难点,也是C语言的精华。
C51的指针内容与标准C基本相同,有一些小的区别。
1、指针变量的概念
1)指针的概念
地址
变量
代号
35H
0x12
a
36H
0x34
b
……
……
50H
0x35
p1
51H
0x36
p2
一个程序的指令、常量和变量等都要存放在机器的内存单元中,该单元有一个地址,这就是存储单元的地址。
通过地址可以找到所需要的需要的存储内容。
一个变量的地址就称为该变量的指针。
例如,有个整型变量a=0x12存放在内存单元35H中,则该内存单元地址35H就是变量a的指针。
可以这样理解:
变量就是存储在单元中可变的内容,而该单元的地址就是指针。
2)取地址运算符&
使用格式:
&变量
作用:
取出&符号右侧变量的地址(指针)。
地址
变量
代号
35H
0x12
a
36H
0x34
b
……
……
50H
0x35
p1
51H
0x36
p2
例:
ap=&a;//将地址35H送入ap。
3)指针变量
因为变量就是存储在单元中的内容,如果存储的内容为地址(指针),则该变量就是指针变量。
就和汇编语言中@R1的含义完全一样。
如在上图中,将地址35H放到地址为50H的单元中,则35H就是指针变量。
4)指针变量的定义
指针变量的定义与一般变量的定义类似,其一般形式如下:
地址
变量
代号
35H
0x12
a
36H
0x34
b
……
……
50H
0x35
p1
51H
0x36
p2
数据类型[存储器类型1]*[存储器类型2]标识符[=初值];
其中:
标识符:
是所定义的指针变量名,可以赋初值,赋初值时用取址符“&”,或者直接赋地址。
数据类型:
存储到指针(地址)中数据的类型。
(图中0X12)
特别注意:
指针定义中的数据类型必须和存储的数据类型完全一致。
如果给指针中送数据,送的数据类型必须与此处定义的类型相同,否则编译出错。
存储器类型1:
指针对象存储类型;
存储器类型2:
指针存储类型。
(1)普通指针:
省略存储器类型1时,为普通指针。
普通指针在存储时占用3字节。
三个字节的内容见下表:
地址
+0
+1
+2
内容
存储类型代码
高位地址偏移量
低位地址偏移量
存储类型代码如下:
存储类型
idata/data/bdata
xdata
pdata
code
编码值
0x00
0x01
0xfe
0xff
其中存储类型代码是由指针变量中所存储数据的存储类型确定。
(按存位置,主要是地址区分)
(2)基于存储器的指针
如果在定义中给出指针变量存储类型,就是基于存储器的指针。
当存储器类型1为data,idata,pdata,指针长度占1个字节;
存储器类型1为xdata,指针长度占2个字节。
当为16位地址时要用xdata。
要根据地址的长度确定存储类型。
注意:
因为该项是指针对象(即地址中存的内容)的存储类型,存储对象本身已定义存储类型,在此必须两者一致,否则出错。
最好的办法是该项省略,用普通指针。
存储器类型2-指针变量的存储器类型。
在上图中,指0x35的存储器类型。
一般可以不指明,由编译器确定。
如果用xdata类型,就把指针变量的地址存储到片外RAM中。
可以这样理解,盖一个房子存放地址,存储类型1确定房子的大小,也就是存地址时占几个字节;存储类型2确定将房子盖在什么地方,即地址存到片内还是片外。
charxdata*px;//px本身在默认的存储器中,指针占2字节
“*”是指针定义符,表示右边的标识符代表一个指针。
但要注意的是:
如果将“*”都理解成指针定义符,则不好解释,所以后面又将其称作指针定义或间接访问运算符。
#include
main()
{
intxdata*datap;
intdataa=0x1234,b;//注意:
定义的数据a存在片内,//则指针定义时存储器类型1也要求是“data”存储类型,//否则出错。
同时要求变量与指针变量的数据类型必须一致,如上面的“int”。
上面定义是不对的。
p=&a;
b=*p;//注意观察
}(指针类型)
例如:
Intdata*p1;//基于存储器指针,1字节,地址存片内
float*xdatap2;//普通指针,3字节,指针存片外。
5)常数指针
常数指针就是将一个常数定义为指针,常用来定义地址,如:
(P318)
#include
#definePA8155XBYTE[0X00F1]
main()
{
int*a1;
a1=&PA8155;
}(见指针常量)
注意看教材P318页,如果按页访问,要用PBYTE,在实验中就是这样。
6)间接访问运算符*
同汇编语言中的间接寻址(@)的作用相同,该*作为运算符,表示将*右边地址中的内容取出。
有没有必要如此区分是指针定义符还是间接访问运算符,怎么区分是定义符还是运算符?
下面看一个例子:
地址
变量
代号
35H
0x23
a
36H
0x23
b
……
……
50H
35H
p
inta=0x23,b;
int*p=&a;//定义了一个指针变量并将变量a的地址送入
b=*p;//将a送入b
如果直观从形式看,b中存储的是a变量的地址呢?
指针定义符与间接访问运算符的区别:
(1)指针定义符的位置有下列特点:
无等号(int*p);或者在等号的左侧(int*p=&a);或者表达式中有多个“*”时,出现在“*”的右侧(b=**p)。
要注意多维数组中的指针数组规律与此不同。
(2)间接访问运算符一般在等号右边(b=*(p+i););表达式中有多个“*”时,出现在“*”的最左边(b=**p)。
例:
int*p;//定义符
int*p=&a;//定义符
b=*(p+i);//间接访问运算符
b=*(*(ap+i)+j);//左边的是间接访问运算符,右边的是定义符
b=**p;//左边的是间接访问运算符,右边的是定义符
7)指针使用中的注意事项:
(1)指针变量中存放的只能地址。
指针变量定义后,可以给变量赋值,赋值时变量前面不要”*”,但必须是地址。
如:
intdatax[3]={1,2,3};
………
uchar*ad_adr;
ad_adr=&x;
ad_adr=x;//正确的,数组也可以不加“&”符号
如:
int*p;//表示指向的变量为整型
p=0x09;
(2)指针(地址)中所存对象的数据类型不同,指针给数据留的空间不同,如指针指向char数据类型,留一个字节;若是long数据类型,则留4个字节。
(3)一个指针变量只能指向同一类型的变量,因为指针定义了指向变量的类型。
而定义变量时也定义了变量类型,该部分不注意容易出错。
(见“指针类型”)
(4)指针加1表示加了1个存储单位,一个存储单位占几个字节要根据数据类型确定,char型为1个字节;int型为两个字节等。
二、指针变量使用中的要点
指针变量既然是存储地址的变量,虽然应用很灵活,但万变不离其宗,所以使用时要紧扣以下几点:
1、存的是数据还是函数的地址,数据类型是什么,数据的数量及存储方式。
2、指针变量所指数据类型存储的方式,这一点很重要,如一维数组,按次序存放,数组名也就是第一个数组元素的地址,定义一个数组指针变量,则该变量里面存的就是该数组的首地址。
对于函数,在ROM中占连续一片空间进行存储,函数的指针变量就是该函数存储的首地址。
3、c语言语法中对不同数据类型和函数的指针定义是怎么规定的,如何引用。
最好通过例子进行理解。
该内容很重要,因为指针变量的概念很简单,就是存放地址的变量,所以必须清楚:
1)指针变量中存的是那一类数据的地址,数组、结构、函数还是一般变量;
2)如何通过该地址将数据传输到该变量中;
3)如何取出该数据。
三、指针与数组
由前面已经知道,一个数组是由连续的一块内存单元组成的。
数组名就是这块连续内存单元的首地址。
一个数组也是由各个数组元素组成的。
按照不同类型,每个数组元素占有几个连续的内存单元(存储单位)。
一个数组元素的首地址也是指它所占有的几个内存单元的首地址。
数组的指针-数组的起始地址;
数组的指针变量-存储数组起始地址的变量。
1、一维数组与指针
定义一个指针,将数组名或第一个元素的地址赋予它。
就可以用指针访问该数组的任意一个元素。
例:
chara[5];
char*pa;
pa=a;//数组名与&a[0]都是指该数组的首地址
地址
数组名
变量
指针
35H
a[0]
0x23
pa
36H
a[1]
0x32
pa+1
37H
a[2]
0x98
pa+2
38H
a[3]
0x46
pa+3
39H
a[4]
0x51
pa+4
pa,a,&a[0]均指向同一单元,它们是数组a的首地址,也是0号元素a[0]的首地址。
pa+1,a+1,&a[1]均指向1号元素a[1]。
类推可知pa+i,a+i,&a[i]都指向i号元素a[i]。
应该说明的是pa是变量,而a,&a[i]都是常量。
引用:
b=*(pa+3);//数组的第4个元素
则引用的是a[3]=0x46。
注意,如果定义成整型(int)变量,数组的每一个元素占两个字节,指针变量加1时,表示加一个存储单元,即2个字节。
其他类型也一样。
2、二维数组指针变量的定义及引用
1)二维数组查找的基本思路
在前面已经介绍过二维数组的存储规律,C语言允许把一个二维数组按行分解为多个一维数组来处理。
则一行为一个一维数组,如图所示。
第1列