《【教学课件】第9章编程技巧.ppt》由会员分享,可在线阅读,更多相关《【教学课件】第9章编程技巧.ppt(26页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第第9章章 编程技巧编程技巧第一节第一节 表达式解释计算表达式解释计算第二节第二节 C与操作系统接口设计与操作系统接口设计第三节第三节 C与汇编语言的接口与汇编语言的接口第四节第四节 程序调试问题程序调试问题1/26C语言程序设计语言程序设计 龙昭华主编第一节第一节 表达式解释计算表达式解释计算(第九章(第九章 编程技巧)编程技巧)2/26一、一、表达式句法定义表达式句法定义 需要定义一个含有+、-、*、/的运算表达式。加、减、乘、除四则运算符及括号的意义完全与平常的算术运算习惯一致。程序接受一行字符串,并对它进行表达式的解释计算,这就相当于一个简单的编译系统。数:数:num()数字数字数字数
2、字.因子:因子:factor()数数)表达式表达式(项:项:term()因子因子*/因子因子表达式:表达式:expr()项项+-项项二、二、各句法对应函数各句法对应函数第一节第一节 表达式解释计算表达式解释计算3/26(第九章(第九章 编程技巧)编程技巧)为了便于识别表达式的各成份,并计算它们的值,需要定义相应的函数:num()函数:将数的字符序列转换成数值。如:9、8.12、-10.30、1230等。factor()函数:计算因子的值。如:-91.89、(90*10.8+5.8)、60等。term()函数:计算项的值。如:89、(9/2+10)、90*7/10、(10-2)/2等。exor(
3、)函数:计算表达式的值。如:9+(10/2-9)、200-90、30+0.99等。由于表达式句法的递归定义,这些函数也将通过相互递归调用求得各自对应成份的值。假设主函数控制流程是反复读入一行正文,调用表达式计算函数expr(),求出表达式的值并输出,直至输入空行结束。程序中引入下列全局变量:buf字符数组,存储一行正文信息。cpt为当前正待识别的字符指针。result为存储表达式计算结果的变量。主函数的算法主函数的算法为:读入一行存buf数组。如果输入行非空转,否则转。置cpt初值。计算表达式值result=expr();输出result的值。转。退出。在第4步中所用到的函数需要编写,其余函数
4、用标准库函数完成。1 1、expr()expr()函数算法函数算法第一节第一节 表达式解释计算表达式解释计算4/26(第九章(第九章 编程技巧)编程技巧)根据表达式的句法定义,expr()函数有以下形式:double expr()value1=term();/*调用函数term()求项值*/while(当前有效字符为+或-)保存运算符;value2=term();/*求下一个项值*/if(运算符=+)value1+=value2;else value1-=value2;return value1;函数函数term()term()的算法与函数expr()的算法类似。函数term()调用facto
5、r()完成因子计算,处理乘除法运算符*和/。对于除法需要考虑除数为0的情况。说明说明:当前有效字符是指非空白字符。2 2、factor()factor()函数算法函数算法第一节第一节 表达式解释计算表达式解释计算5/26(第九章(第九章 编程技巧)编程技巧)当前有效字符为数字符,因子是一个数。转换该数的字符序列为数值,作为因子的值。当前有效字符为左括号(,因子是一个带括号的表达式。递归调用函数expr(),以表达式的值作为因子的值。且表达式计算后,当前有效字符应为右括号),否则因括号不正确配对,是个句法错误的表达式。当前有效字符为其它字符,也是个句法错误的表达式。double factor()
6、if(当前有效字符为数字符)return num();if(当前有效字符=()移动字符指针至下一个有效字符;fvalue=expr();if(当前有效字符=)移动字符指针至下一个有效字符;else 报告表达式句法错误;return fvalue;else 报告表达式句法错误;return 1.0;注意注意:这里没有讨论有)而没有(与之匹配的问题。三、三、完整的程序完整的程序第一节第一节 表达式解释计算表达式解释计算6/26 3 3、num()num()函数算法函数算法 num()函数实际上就是字符转换成数值。先转换整数部分,整数每位数字乘以10,使用循环完成。如果还有小数点.,则小数点后面的数
7、字乘以0.1,仍使用循环完成。(第九章(第九章 编程技巧)编程技巧)例例9.1 9.1 表达式解释计算的详细程序见表达式解释计算的详细程序见li9_1.c。运行结果:9+8 (enter)he result is 17.000000(10-20)*2+5 (enter)he result is-15.0000009+9*9+1-(10/2+1.9)(enter)he result is 84.100000(enter)注:最后一个回车退出循环。第二节第二节 C与操作系统接口设计与操作系统接口设计(第九章(第九章 编程技巧)编程技巧)7/26一、一、BIOS中断表中断表 在C语言编程中,常常需要
8、利用操作系统如DOS的低级资源,而这些资源不能使用C编译程序访问,必须使用DOS中断(软中断)才能完成。中断中断是一种特殊类型的指令,它停止执行当前程序,把系统当前状态保留在堆栈中,然后转移到由中断号确定的相应的中断处理子程序上。当中断子程序执行完时,它执行中断返回,使原先运行的程序恢复执行。中断分为硬中断和软中断两种基本类型。8086CPU允许程序通过INT指令执行软中断,跟在INT指令后的数字指定所用的中断号。如:INT 21h执行21h号中断。中断号是用来找出相应的中断处理程序的。在PC-DOS中,使用软中断访问操作系统功能,每个中断命令都有其专门的访问功能类型,而且这些功能函数是由AH
9、寄存器中的值决定的。如果需要增加信息,所增加的信息传给AX、BX、CX和DX寄存器。PC-DOS操作系统分为ROM-BIOSROM-BIOS(Basic I/O System)和DOSDOS(Disk OperatingSystem)两部分。ROM-BIOS提供最低层子例程,而DOS用这些低层子例程提供进一步的高级功能。二者是交叉在一起的,用户访问它们的方法基本相同:都是通过软中断。BIOS中断表中断表(续续)第二节第二节 C与操作系统接口设计与操作系统接口设计8/26(第九章(第九章 编程技巧)编程技巧)中断号中断号11h5h10h12h13h14h屏幕打印服务程序15h16h1Ah17h1
10、8h19h打印机I/O盒带控制键盘I/O执行ROM BASIC执行引导装入程序系统时间和日期设置功功 能能功功 能能中断号中断号显示器I/O设备清单内存大小磁盘I/O串行口I/O在PC-DOS中,ROM-BIOS有12个中断,如下表所示:访问上表中断有两种方法:一是使用系统调用函数int86(),二是使用汇编语言接口来实现。大多数C都提供了int86()函数。int86()函数的一般格式如下:#include int int86(int intnum,union REGS *in,union REGS *out)二、二、利用利用int86()函数访问函数访问BIOS系统功能系统功能第二节第二节
11、 C与操作系统接口设计与操作系统接口设计9/26 在int86()中需要用到的结构体与共用体如下:(第九章(第九章 编程技巧)编程技巧)/*字符寄存器*/struct WORDREGS unsigned int ax,bx,cx;unsigned int dx,si,di unsigned int cflag,flags;struct BYTEREGS unsigned char al,ah;unsigned char bl,bh;unsigned char cl,ch;unsigned char dl,dh;/*寄存器字节*/unionREGS struct WORDREGS x;struc
12、t BYTEREGS h;例例9.2 利用10h号中断功能6,可以实现清屏。#include void cls()union REGS r;r.h.ah=6;/*屏幕转动程序*/r.h.al=0;/*清屏程序*/r.h.ch=0;/*上转起始行*/r.h.cl=0;/*列起始列起始*/r.h.dh=24;/*上转结束行上转结束行*/r.h.dl=79;/*列结束列结束*/r.h.bh=7;/*空行是黑色空行是黑色*/int86(0 x10,&r,&r);例例9.3 9.3 调用调用16h16h号中断号中断0 0号功能读取键盘扫描码。号功能读取键盘扫描码。第二节第二节 C与操作系统接口设计与操作
13、系统接口设计10/26(第九章(第九章 编程技巧)编程技巧)在为IBM-PC及其兼容机编程时,最难读到的是箭头键和功能键,以及INS、DEL、PGUP、PGDN、END、HOME等键的ASCII码值。键值存放如下:int c;char sc2;sc0sc1 当用户在IBM-PC机上按下一个键时,产生一个称为扫描码的两个字节(16位)的值。该扫描码由两部分组成:低位低位字节内含相应键的ASCII码码(若它是标准键),高位高位字节内含该键在键盘上的定位码定位码。对于标准键的定位码为0,因此低位的值就是它的ASCII码值(8位)。而对于特殊键,它的低位值为0,高位的值才是它的键值。要得到特殊键值,不
14、能使用gets()、scanf()等函数,只能用这里提供的方法。其程序参见li9_3.c。运行结果显示部分键盘扫描码为:A-65 a-97 0-48 9-57 (-40 +-43 -60 =-61左箭头左箭头-75 右箭头右箭头-77 上箭头上箭头-72 下箭头下箭头-80INS-82 DEL-83 F2-60 F3-61 F10-68 ESC-27 PGUP-73 PGDN-81 END-79 HOME-71int get_key()union REGS r;r.h.ah=0;return(int86(0 x16,&r,&r);三、三、利用利用DOS访问系统功能访问系统功能第二节第二节 C与
15、操作系统接口设计与操作系统接口设计11/26(第九章(第九章 编程技巧)编程技巧)PC-DOS中由ROM-BIOS引导装入程序装入和执行的部分叫DOS。其中包含了大部分在ROM-BIOS例程中找不到的各种各样的高级功能,利用AH寄存器传送所请求的DOS功能调用号,通过中断21h可以访问DOS的所有功能。如1 1号功能号功能为从键盘读字符,2 2号功能号功能为在屏幕上显示字符,3 3号功能号功能从异步端口读字符,4 4号功能号功能写字符到异步端口,5 5号功能号功能在打印机上打印字符,B B号功能号功能检查键盘状态,2A2A号功能号功能读取系统日期,2B2B号功能号功能设置系统日期,2C2C号功
16、能号功能读取系统时间等。虽然可以像ROM-BIOS功能一样,利用int86()函数访问DOS功能,但很多系统都有一专门函数bdos(),该函数用来执行21h号中断调用,调用操作系统中的某个高级功能。bdos()函数原型如下:int bdos(int fnum,unsigned int Reg_DX,unsigned int Reg_AL)int bdos(int fnum,unsigned int Reg_DX,unsigned int Reg_AL)其中其中:fnum是DOS功能号;Reg_DX的值赋给DX寄存器;Reg_AL的值赋给AL寄存器;返回值返回值:bdos()回送AX寄存器的值。
17、例例9.4 9.4 调用调用12h12h号中断号中断BhBh号功能检查键盘状态。号功能检查键盘状态。第二节第二节 C与操作系统接口设计与操作系统接口设计12/26(第九章(第九章 编程技巧)编程技巧)#include#include int kbhit()int kbhit()return(char)bdos(0 xB,0,0);return(char)bdos(0 xB,0,0);除了第一个参数外。其余都用0,因为不需要其它信息。把返回值强行变成char型是必要的,因为所返回的是在AL中的状态,而AL没有定义。kbhit()函数的返回值为:如果按下键,则返回“真”,否则返回“假”。kbhit
18、()函数的一个非常普通的用途就是可以让某个子例程被用户命令所中断。例例9.5 调用12h号中断的3号功能读串口,4号功能写串口。如果需要编写一个调制解调器程序,就要用到在异步串行口上进行读写字符。/*向串口写字符*/#include int put_async(char ch)bdos(0 x4,ch,0);/*从串口读字符*/#include int get_async()return(char)bdos(0 x3,0,0);这里又强行作了char转换,以保证放在AH寄存器中的任何值,都不会让任何调用例程乱了套。第三节第三节 与汇编语言的接口与汇编语言的接口(第九章(第九章 编程技巧)编程技
19、巧)13/26 在C语言编程中,需要使用汇编语言编写例程大概有三个方面的原因:为了提高速度和效率。为了实现某些C语言中不具备、但为不同的机器所特有的功能。为了利用通用的汇编语言例程。把汇编程序模块和用户的C程序结合起来,主要有两种方法:第一种,单独编写汇编例程,然后再将它同自己的程序连接起来。第二种,使用大多数C编译器所具有的内部汇编程序功能。有的C编译系统内部汇编使用#asm开头,使用#endasm结束,中间全部是汇编语句。而TurboC则不同,每行汇编语句都要以asm开头。如:两数相乘的函数。如:两数相乘的函数。int mul(int a,int b)#asm mov ax,word pt
20、r 8bp imul ax,word ptr 10bp#endasmint mul(int a,int b)asm mov ax,word ptr 8bp asm imul ax,word ptr 10bp第四节第四节 程序调试问题程序调试问题(第九章(第九章 编程技巧)编程技巧)14/26一、一、程序易出错问题程序易出错问题 1 1、忘记定义变量。、忘记定义变量。如:main()x=6;y=8;z=x+y;在使用变量之前,必须加:int x,y,z;定义变量。2 2、输入输出数据的类型与所用格式说明符不一致。、输入输出数据的类型与所用格式说明符不一致。如:int a=3;float b=4.
21、5;printf(“%f,%dn”,a,b);结果可能不是所需。3 3、未注意、未注意intint型数据的取值范围。型数据的取值范围。如:int num;num=89101;printf(“%dn”,num);造成num超界益出。4 4、输入变量时忘记使用地址符。、输入变量时忘记使用地址符。如:scanf(“%d%d”,a,b);应改为:scanf(“%d%d”,&a,&b);5 5、输入时数据的组织与要求不符。、输入时数据的组织与要求不符。如:scanf(“%d%d”,&a,&b);输入若按:3,4(Enter)则是错的,两数之间应为空格,而不是逗号。再如:scanf(“Input a&b:
22、%d,%d”,&a,&b);想在屏幕上提示Input a&b:再输入则是不正确的。可改用printf(“Input a&b:”);scanf(“%d,%d”,&a,&b);这时屏幕显示:Intput a&b:后再输入:3,4(Enter)6 6、误把、误把“=”“=”作为作为“等于等于”比较符。比较符。第四节第四节 程序调试问题程序调试问题15/26(第九章(第九章 编程技巧)编程技巧)如:if(a=b)应改为:if(a=b)应该用“=”作为“等于”比较符。前则为赋值操作。7 7、语句后面漏分号。、语句后面漏分号。如:a=4 b=5 应为:a=4;b=5;复合语句的最后一句也应该有分号。如:t
23、=a;a=b;b=t 应改为:t=a;a=b;b=t;8 8、在不该加分号的地方加分号。、在不该加分号的地方加分号。如:if(ab);printf(“a is larger than b.n”);再如:for(i=0;i10;i+);scanf(“%d”,&x);printf(“%dn”,x*x);它不能输入10个数据,而只能输入一个数据。9 9、对应该有花括弧的复合语句,忘记加花括弧。、对应该有花括弧的复合语句,忘记加花括弧。如:sum=0;i=1;while(i=100)sum=sum+i;i+;结果是在循环中i的值永远不变,循环是死循环。循环应改为:while(i100)sum=sum+
24、i;i+;1010、括号不配对。、括号不配对。如:while(c=getchar()!=#)putchar(c);while语句后面少了右括号。1111、在用标识符时,忘记了大写字母和小写字母的区别。、在用标识符时,忘记了大写字母和小写字母的区别。第四节第四节 程序调试问题程序调试问题16/26(第九章(第九章 编程技巧)编程技巧)如:int a,b,c;a=2;b=3;C=A+B;大写与小写字母在C语言中为两个不同的标识符。1212、引用数组元素时误用了圆括号。、引用数组元素时误用了圆括号。如:int i,a(10);for(i=0;i10;i+)scanf(“%d”,&a(i);应改为:i
25、nt i,a10;for(i=0;i10;i+)scanf(“%d”,&ai);1313、在定义元素时,将定义的、在定义元素时,将定义的“元素个数元素个数”误认为是误认为是“可使用的最大下标值可使用的最大下标值”。如:int a10=1,2,3,4,5,6,7,8,9,10,i;for(i=1;i=10;i+)printf(“%dt”,ai);数组下标是从0开始,到“元素个数-1”为止的。for应为:for(i=0;iy?x:y;z=xy?x:y;return(z);return(z);2222、所调用的函数在调用语句之后才定义,而又在调用之前未加声明。、所调用的函数在调用语句之后才定义,而又
26、在调用之前未加声明。第四节第四节 程序调试问题程序调试问题19/26(第九章(第九章 编程技巧)编程技巧)如:main()float max(float x,float y)float x=3.5,y=-7.6,z;return(xy?x:y);z=max(x,y);必须在main()函数中声明:printf(“%fn”,z);float max(float,float);或在main()函数之前定义max函数。23 23、误认为形参值的改变会影响实参的值。、误认为形参值的改变会影响实参的值。如:main()swap(int x,int y)int a=3,b=4;int t;swap(a,b
27、);t=x;x=y;y=t;printf(“%d,%dn”,a,b);可用指针方式定义swap函数解决传递。24 24、函数的实参和形参类型不一致。、函数的实参和形参类型不一致。如:main()fun(float x,float y)int a=3,b=4,c;c=fun(a,b);可改为:fun(int x,int y)或a,b为float。2525、不同类型的指针混用。、不同类型的指针混用。第四节第四节 程序调试问题程序调试问题20/26(第九章(第九章 编程技巧)编程技巧)如:main()int i=3,*p1;float a=1.5,*p2;p1=&i;p2=&a;p2=p1;prin
28、tf(“%d,%dn”,*p1,*p2);应改为:main()int i=3,*p1;float a=1.5,*p2;p1=&i;p2=&a;p2=(float*)p1;printf(“%d,%dn”,*p1,*p2);26、没有注意函数参数的求值顺序。、没有注意函数参数的求值顺序。如:i=3;printf(“%d,%d,%dn”,i,+i,+i);有些系统输出:3,4,5。而TurboC等有些系统则输出:5,5,4。即求函数参数表达式的值的顺序有:从左向右的,也有从右向左的,注意区分顺序。一般改用先赋值,再带入不含+或-的变量。27、混淆数组名与指针变量的区别。、混淆数组名与指针变量的区别。
29、如:int i,a5;for(i=0;i5;i+)scanf(%d”,a+);应改为:int i,a5,*p;for(p=a,i=0;i5;i+)scanf(%d”,p+);2828、混淆结构体类型与结构体变量的区别,对一个结构体类型赋值。、混淆结构体类型与结构体变量的区别,对一个结构体类型赋值。第四节第四节 程序调试问题程序调试问题21/26(第九章(第九章 编程技巧)编程技巧)如:struct worker int num;char name20;worker.num=1001;strcpy(worker.name,”Li”);29、使用文件时忘记打开,或打开方式与使用情况不匹配。、使用文
30、件时忘记打开,或打开方式与使用情况不匹配。如:对文件的读写,用只读方式打开,却企图向该文件输出数据。此外,有的程序常忘记关闭文件,虽然系统会自动关闭所用文件,但可能会丢失数据。其它还有指针引用错误造成野指针、函数重名、恶性语法错误、边界错误、函数说明的疏漏、调用参数错误、scanf()与gets()函数的区别等等。以上错误在多练习C语言编程后可以克服,并且也容易检查。在深入使用C语言后,还会出现其它一些更深入、更隐蔽的错误。应改为:struct worker int num;char name20;worker1;worker1.num=1001;strcpy(worker1.name,”Li
31、”);二、二、程序出错的三种情况程序出错的三种情况第四节第四节 程序调试问题程序调试问题22/26(第九章(第九章 编程技巧)编程技巧)1、语法错误、语法错误 程序违背C语法规定引起的错误。编译程序一般能发现错误,根据“出错信息”可以很快发现并及时纠正。2、逻辑错误、逻辑错误 程序没有语法错误,但执行结果与原意不符。如:sum=0;i=1;while(i=100)sum=sum+i;i+;最后两句应用一对花括号括起来。这种错误比较难查,要求程序员有较丰富的经验。3、运行错误、运行错误 程序既无语法错误,也无逻辑错误,但在运行时出现错误,甚至停止运行。如:int a,b,c;scanf(“%d%
32、d”,&a,&b);c=b/a;printf(“c=%dn”,c);如果输入a的值为0的话,就会出错。因此程序应经受各种数据的“考验”,应具有“健壮性”。写完一个程序只能说完成任务的一半(甚至不到一半)。调试程序往往比写程序更难,更需要精力、时间和经验。程序员需要通过大量的实践来掌握调试程序的方法和技术。三、三、程序调试程序调试第四节第四节 程序调试问题程序调试问题23/26(第九章(第九章 编程技巧)编程技巧)所谓程序调试是指对程序的查错和排错。调试程序一般经过以下几个步骤。1、人工检查,即静态检查、人工检查,即静态检查 程序写好后,首先应进行人工检查。作为一个程序员应当养成严谨的科学作风,
33、每一步都要严格把关,不要把问题留给后面的工序。为了更有效地进行人工检查,编程人员应力求做到以下几点:应当采用结构化程序方法编程,以增加可读性。尽量多加注释,以帮助理解每段程序的作用。在编写复杂程序时,不要将全部语句都写在main函数中,而要多利用函数,用一个函数来实现一个单独的功能。各函数之间除用参数传递数据这一渠道外,数据间尽量少出现偶合关系,便于分别检查和处理。编写程序时,要注意编程风格。好的编程风格也便于查错。2 2、上机调试、上机调试 通过上机发现错误称为动态检查。主要根据编译时给出的错误信息来纠错。应当注意:有时提示的出错行并不是真正的出错行,应往上查找。可能改了前面的某一个错误后,
34、后面的大片错误就消失。要分析,找出真正的错误行。3、检查运行结果的正确性、检查运行结果的正确性第四节第四节 程序调试问题程序调试问题24/26(第九章(第九章 编程技巧)编程技巧)在改正“错误”(error)和“警告”(warning)后,程序经过连接(link)就得到可执行的目标程序。运行程序,输入程序所需要的数据,就可得到运行结果。还应当分析运行结果,检查它是否符合要求。在验证程序时,应精心选择典型、苛刻而带有刁难性的几组数据进行输入,看它是否能够得出满足要求的结果。4、检查运行结果错误的方法、检查运行结果错误的方法 如果运行结果不对,大多属于逻辑错误。对这类错误往往需要仔细检查和分析才能
35、发现。可以采用以下方法:将程序与算法仔细对照。算法正确,就是程序错误,仔细对照就很快发现错误。算法不对,就修正算法,再改写程序。如果实在找不到错误,就采取“分段检查”的方法。在程序不同位置设计个printf()函数输出相关信息或变量值,逐段往下查。直到找到在某一段中数据不对为止。也可用“条件编译”命令来处理调试用的printf()函数语句,用完不必删除。如果程序没有错误,就要检查算法了。修改算法后再修定程序。有的系统还提供了debug(调试)工具,可跟踪程序运行并给出相应信息。三、三、C的存储方式的存储方式第四节第四节 程序调试问题程序调试问题25/26(第九章(第九章 编程技巧)编程技巧)在
36、使用8086系列处理机上运行C程序时,C编译系统都提供了6种存储方式:小方式、一般方式、中等方式、压缩方式、大方式、特大方式。8086用分段存储结构,有四个段:代码段、数据段、堆栈段和附加段。一个段在RAM中占64K,段的起点正好是16字节的偶数倍。1、小方式、小方式 要求程序、数据和栈都必须在同一64K段内。用它编译的程序,目标码最少,执行起来最快。可用DOS的EXE2BIN命令转换成.COM文件。2、一般方式、一般方式 这种方式是缺省方式,用得最多。程序代码单独占用64K,数据码占用另外64K。按这种方式编译的程序最大为128K。3、中等方式、中等方式 机器码超过了一般方式下的一个段的限制
37、的大程序,就要用中等方式编译。这种方式适合只使用少量数据的大程序。4、压缩方式、压缩方式 压缩方式与中等方式相反,适合使用大量数据,但本身程序不大的情况。5、大方式、大方式第四节第四节 程序调试问题程序调试问题26/26(第九章(第九章 编程技巧)编程技巧)大方式允许程序码和数据都使用多个段。但最大数据项(如数组)单项最多只能占用64K当程序和数据都很大时,要用大方式。6、特大方式、特大方式 特大方式与大方式有一点不同,数据单独可占64K以上的内存。方式的选择方式的选择:除非有特殊理由,一般情况下都应使用一般方式。当程序很大,但数据不多时,可选用中等方式。而程序不大,数据却很多时,应选用压缩方式。如果程序和数据都不小,则选用大方式。当某些数据项目单项大于64K时,用特大方式。在8086上运行C程序,还可以使用far、near、huge三个说明符来解决存储方式混用的问题,它们只能用于指针和函数。详细信息参见有关资料。