数据类型转换_第1页
数据类型转换_第2页
数据类型转换_第3页
数据类型转换_第4页
数据类型转换_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、数据类型转换各类整数之间的转换C语言中的数分8位、16位和32位三种。属于8位数的有:带符号 字符char,无符号字符unsigned char。属于16位数的有:带符号整 数int,无符号整数unsigned int(或简写为unsigned),近指针。属于32位数的有:带符号长整数long,无符号长整数unsigned long ,远指针。旧M PC是16位机,基本运算是16位的运算,所以,当8位数和16位数进行比较或其它运算时,都是首先把8位数转换成16位数。为了便于按2的补码法则进行运算,有符号8位数在转换为16位时是在左边添加8个符号位,无符号8位数则是在左边添加8个0。当由16位转

2、换成8位时,无论什么情况一律只是简单地裁取低8位,抛掉高8位。没有char或usigned char常数。字符常数,像”C”,是转换为int以后存储 的。当字符转换为其它16位数(如近指针)时,是首先把字符转换为int,然后再进行转换。16位数与32位数之间的转换也遵守同样的规则。注意,Turbo C中的输入/输出函数对其参数中的int和unsignedint不加区分。例如,在printf函数中如果格式说明是%d则对这两种 类型的参数一律按2的补码(即按有符号数)进行解释,然后以十进制 形式输出。如果格式说明是u %o%X则对这两种类型的参数一律按二进制(即按无符号数)进行解释,然后以相应形式

3、输出。在scanf函数中,仅当输入的字符串中含有负号时,才按2的补码对输入 数进行解释。还应注意,对于常数,如果不加L,则Turbo C一般按int型处理。例如,语句printf(%081x” , -1L),则会输出ffffffff。如果省略1,则输出常数的低字,即ffff。如果省略L,则仍会去找1个双字,这个 双字的就是int常数-1 ,高字内容是不确定的,输出效果将是在4个乱七八糟的字符之后再跟ffff。在Turbo C的头文件value.h中,相应于3个带符号数的最大值,定义了3个符号常数:#define MAXSHORT 0X7FFF#define MAXINT 0X7FFF#defi

4、ne MAXLONG 0X7FFFFFFFL在Turbo C Tools中,包括3对宏,分别把8位拆成高4位 和低4位,把16位拆成高8位和低8位, 把32位拆成高16位和低16位。uthinyb(char value) utlonyb(char value) uthibyte(intvalue) utlobyte(int value) uthiword(long value) utloword(long valueu)在Turbo C Tools中,也包括相反的3个宏,它们把两个4位组成 一个8位,把两个8位组成一个16位,把两个16位组成一个32位。utnybbyt(HiNyb , LoN

5、yb) utwdlong(HiWord , Loword)utbyword(HiByte , LoByte)实数与整数之间的转换Turbo C中提供了两种实数:float和double。float由32位组成,由高到低依次是:1个尾数符号位,8个偏码表示的指数位(偏值= 127) , 23个尾数位。double由64位组成,由高到低依次是:1个尾数符号位,11个偏码表示的指数位(偏值=1023) , 52个尾数位。通过下列公式,可以由尾数和指数计算出所代表的实数值:X=土1.尾数*2(指数-偏值)下列几种情况下,此公式不成立:指数=000.0且尾数=00.0,贝U X=X寸0指数=000.0且

6、尾数!=00.0,贝U X= 0.尾数*2(1-偏值)指数=11.1且尾数=00.0,贝U X=8指数=11.1且尾数!=00.0,则X是一个无效数在Turbo C的头文件value.h中,相应于实数所能达到的最大最小值,定义了如下几个符号常数:#define MAXFLOAT 3.37E+38#define MINFLOAT 8.43E-37#define MAXDOUBLE 1.797693E+308#define MINDOUBLE 2.225074E-308实常数是按double格式存放的,如果想按float格式存放,则必须加后缀F,如:1.23E+4F。当把实数强制转换为整数时, 采

7、取的是“向零靠拢的算法”,如:float值:65432.6 -65432.6转换为long ; 65432 -65432转换为unsigned : 65432 104如果不希望“向零靠拢”,而希望“四舍五入”,则必须由程序员自己处理。一种办法是先加上(符号位/2),然后再进行类型转换。应该注意的是:如果被转换的实数值超过了目标类型的表达范围,贝U会产生错误。例如上面的float值-65432 , 6转换为unsigned int值时,由于超过了目标范围,所产生的104就是错误的。在65432。6转换为unsigned int的65432以后,可以用printf的u格式输出,如果用%d格式输出,

8、则会得到错误的结果。指针说明指针是包含另一变量的地址变量。它的一般说明形式,如int *fd ,其fd是一个指向整型变量的指针。比较复杂的指针说明,如*(*pfpi)(),可按如下几个原则来理解:以标识符为中心,一对方括号一般表示数组,一对圆括号一般表示函数或强调某一优先顺序,方括号对和圆括号对为同一优先级,方括号和圆括号比*号优先级高。以下几例解释了这些原则的应用。int *fip(),因圆括号优先级高,帮fip先与圆括号结合,说明fip是一个函数,这个函数返回一个指向整数的指针。int (*pfi)(),因两对圆括号为同一优先级,故从左到右,pfi是一个指针,这个指针指向一个函数,这个函数

9、返回一个整数。int *par,因方括号比*号优先级高,故par是一个数组,这个数组的每一个元素是指向整数的指针。int (*ptr),因方括号与圆括号为同一优先级,故ptr是一个指针,这个指针指向一个数,这个数的每一个元素是一个整数。int *(*pfpi)(), pfpi是一指针,这个指针指向一个函数,这个函数返回一个提向整数的指针。指针与地址指针在使用之前必须初始化,给指针赋地址的方法一般有如下几种:第一种很容易理解,通过取地址操作符取变量(包括结构变量)的地址,如:char_c=a” , *ptr_char;ptr_char=&c;第二种是数组,因为不带方括号的数组名等效于数组

10、中第一个元素的地址,即数组名也可看作是一个指针,所以有两种办法。 如:char myname31, *ptr;ptr=myname;或ptr=&myname0;第三种是动态分配的一块内存,这时往往带有类型强制转换,但应注意当内存不够时,可能返回一个空(NULL)指针。如:struect complex (double real,image; ;struct complex *ptr;ptr=( struct complex *)malloc(sizeof(struct complex);第四种是函数,与数组名一样,函数名也可以当作一个地址,于是可以把函数名赋给一个指向函数的指针。函数名

11、后面跟一个带圆括号的参数表意味着计算函数的值,但仅有一个函数名则意味着指向函数的指针。如:double (*fx)();double quad_poly(double);fx=quad_poly;指针运算常见的指针运算有:指针加或减一个数,指针增量,指针减量,指针比较等等。假设P是某数组第1个元素的指针,则P+N就是这个数组中第n个元素的地址,每个元素占多少存储单元则由指针所指数组的类型来确定。但有两点要注意:第一,上面这段话是C语言对指针运算的普遍规律,但具体到种C编译则有所不同,尤其是在80X86类型的机器上。Turbo C和Microsoft C 6.0以前的版本把指针分为near、fa

12、r、huge,Microsoft C 6.0又增加了based。在这几种指针中,只有huge严格遵守上面的指针运算规则,详见下一节。第二,当指针应用于数组尤其是多维数组时,有时容易弄错,下表说明了数组法与指针法的区别。1维| 2维| 3维数组说明int x int y int z指针说明int *xptr int *yptr int *zptr数组法表示某元素地址&xi&yij&zijk指针法表示某元素地址Iptr+i *(yptr+i)+j *(*(zptr+i)+j)+k数组法存取某元素xi yijzijk指针法存取某元素*(ptr+i) *(*(yptr+i)+j

13、)*(*(*(zptr+i)+j)+k)指针分类在C语言教科书上,指针就是指针,不存在分类的问题。我们经 常说“指向整数的指针”,“指向结构的指针”,“指向函数的指针” 等等,只是说指针指向不同的目标,而不是说指针本身有什么区别。但是,在以80X86为基础的微机上实现C语言时,由于80X86的物理地址空间不是线性连续的而是分段的,为了提高效率,就必须对指针加 以分类。各类指针的运算法则也不一样。Turbo C 2.0及以前的版本,Microsoft C 6.0以前的版本,指针都是分类三类,近(near),远(far),巨(huge)。Microsoft C 6.0版本中,出现了一种新的指外类型

14、,这就是基(based)指针。基指针综合实现了近和远指针的优点,它像近指针那么小那么快,又像远指针那样可以寻址缺省数据段以外 的目标。基指针这个名字就反映了这类指针上的实现方法:它是以程 序员指定的某一地址为段基址。如果在C源程序中使用了基指针,编译程序一般先把所指定的段基址装在ES寄存器内。在缺省的数据段内,程序员一般不会使用基指针。但若同时要使用多个数据段,基指则有 其明显的优点。一、近(near)指针近指针是用于不超过64K字节的单个数据段或码段。对于数据指 针,在微、小和中编译模式下产生的数据指针是近指针,因为此时只 有一个不超过64K字节的数据段。对于码(即函数指针)指针,在微、 小

15、和紧凑编译模式下产生的码指针是近指针,因为此时只一个不超过64K字节的码段。本章将只讨论数据指针。近指针是16位指针,它只含有地址的偏移量部分。为了形成32位的完整地址,编译程序一般是反近指针与程序的数据段的段地址组合起来。因为在大部分情况下程序的数据段的段地址是装在DS寄存器内,因此一般没有必要装载这个寄存器。此外,当用汇编语言和C语言混合编程时,汇编语言总是假设DS含有数据目标的地址。虽然近指针占用空间最小,执行速度最快,但它有一个严格的限制,即只能64K字节以内的数据,且只能存取程序的数据段内的数据。 如果在小模式下编译一个程序,而这个程序企图增量一个近指针使之 超过第65536个字节,

16、则这个近的指针就会复位到0。 下面就是这样一个例子:char _near *p=(char _near *)0 xffff;p+;由于近指针的这个严重限制,所有在比较大或比较复杂的程序中,都无法使用。二、远(far)指针远指针不是让编译程序把程序数据段地址作为指针的段地址部分, 而是把指针的段地址与指针的偏移量直接存放在指针内。因此,远指 针是由4个字节构成。它可以指向内存中的任一目标,可以用于任一 编译模式,尽管仅在紧凑、大和巨模式下远指针才是缺省的数据指针。因为远指针的段地址在指针内,熟悉80X86汇编语言的人都知道,这意味着每次使用远指针时都需要重新装载段寄存器,这显然会降低速 度。应该

17、注意:尽管远指针可以寻址内存中的任一单元,但它所寻址 的目标也不能超过64K字节。这是因为,远指针在增量或减量之类的 算术运算时,也只是偏移量部分参与运算,而段地址保持不变。因此,当远指针增量或减量到超过64K字节段边界时就出错。例如:char far *fp=(char far*)0 xb800ffff;fp+;在指针加1以后,fp将指向B800: 0000,而不是所希望的C800:0000。此外,在进行指针比较时,far指针还会引起另外一些问题。far指针是由偏移量和段地址这样一对16位数来表示的,对于某一实际内存地址,far指针不是唯一的,例如,far指针1234:0005、1230:0

18、045、1200:0345、1000:2345、0900:9345等都是代表实际地址12345,这样会引起许多麻烦。第一,为了便于与“空”(NULL)指针(0000: 0000)进行比较,当关系操作符=和!=用于对far指针进行比较时,比较的是全部32位。否则,如果只比较16位偏移量,那么任何偏移量为0的指针都将是“空”(NULL)指针,这显然不符合一般使用要求。但在进行这32位比较时,不是按20位实际地址来比较,而是把段地址和偏移量当作一个32位无符号长整数来比较。对于上面这个例子,假设这些指针分别叫作a、b、c、d、e,尽管这5个far指针指向的都是同一内存单元,但下列表达式运算的结果却都

19、为“假”,从而得出错误的结论:if(a=b).if(b=c).if(c=d).if(d=e).if(a=c).if(a=d).第二,当用 ”、=”,顼和“d).if(dc).if(cb).if(ba).if(ea).三、巨(huge)指针只有巨指针才是一般C语言教科书上所说的指针,它像远指针也占4个字节。与远指针的显著差别是:当增量或减量超过64K字节段边界时,巨指针会自动修正段基址的值。因此,巨指针不但可以寻址内存中的任一区域,而且所寻址的数据目标可以超过64K字节。例如:char huge *hp=(char huge *)0 xb800ffff;hp+;在指针加1后,hp将指向C800:

20、0000。但是,巨指针总是比较慢的,因为编译必须生成一小段程序对指针进行32位而不是16位的加减运算。此外,由于huge指针是规则化指针,每一个实际内存地址只一个huge指针,所有在指针比较时不会产生错误。四、基(based)指针前面已经说过,巨指针综合了近指针和远指针的优点。像近指针一样,基指针只占两个字节,这两个字节是地址的偏移量。像远指针一样,基指针可以寻址内存中的任一区域。近指针的段地址隐含地取自程序的数据段,远指针的段地址取自指针本身,基指针的段地址取法以及基指针的许多技术和应用问题,请见第11章。五、各类指针之间的转换far指针可以强制转换为near指针,做法很简单,抛掉段地址只保

21、留偏移量。near指针也可以转换为far指针,Turbo C的做法是从相应的段寄存器中取得段地址。far指针有时也需要转换为huge指针,以便对指针进行比较或做其它操作。一种方法是通过下面这样一个规则化函数:void normalize(void far *p) *p=(void far*)(long)*pA0 xffff000f)+ (long)*pA0 x0000fff0)0)(for(i=0;i result.stri=str1.stri; result.strlen=str1.strlen;if(str2.strlen0) (j=stre1.strlen;for(i=0;i resul

22、t.stri+j=str2.stri;result.strlen+=str1.strlen;return result;无返回值的函数无返回值的函数与PASCAL其它结构化语言中的过程很相似,它们既不返回结果,又不修改参数,而只是执行某一特定的任务。例如, 下面的清屏函数就是这样一个函数。void clrscr(void)(printf(xlb2J);既然不返回值,则调用的办法也不一样,不是把函数名放在某一表达式内调用,而是把函数名连同其调用参数单独人微言轻一个语句。修改参数的函数 由于C语言是按传值方式把参数传递给函数的,因此,被调用的 函数不能直接改变调用函数中的变量。但有时确实需要修改调

23、用函数的参数,尤其在返回值多于一个的函数中必须再借用参数来返回结果。在这些情况下,必须利用指针来从函数的参数,典型的例子是交换两个变量的值的函数。如下所示:void swap int(int *i,int *j)(int temp;temp=*i;*i=*j;*j=temp;递归函数C语言是支持递归调用的。显然,当一个问题蕴含递归关系且结构比较复杂时,采用递归调用技巧将使程序变得简洁,并增加程序的可读性。但递归调用技巧的使用是在牺牲存储空间的基础上得到的,因为它必须在某处维护一个要处理的值的栈。同时,递归也不能提高执行速度,只是其代码比较紧凑易读。对于像树和链表这样的递归定义的数据结构,递归函

24、数尤为适用。下面是用递归计算阶乘的例子。double factorial(intn)(if(n1) return factorial(n-1)*(double)n;else return 1.0L;参数个数不定的函数C语言中的某些函数,如vfprintf数之后再带一些不定数目的可变参数。不但如此, 自定义的函数也这样做。为了便于用户编程,va开头的4个定义va_list数据类型,3个宏(函数)。这些定义都在头文件stdarg.h中。一步一步地通过整修参数表,尽管被调用函数事先不知道有多少个参数,也不知道这些参数的类型。为了编写具有不定数目的可变参数函数,应遵守如下几点:第1,在C源中包含std

25、arg.h文件。第2,如果函数的返回值不是int型,则在调用函数中应做如下形式的函数说明:类型 函数名 ( 固定参数表 ,.);这个调用形式表明,参数表中至少必须有一个参数是固定的。第3,函数应按如下形式定义:类型函数名(固定参数表,.);第4,定义一个表指针,其类型应是va_list,以表明它指向可变参数表。如下所示:va_list 可变参数表指针第5,调用va_start,初始化表指针:va_start(可变参数表指针,最后一个固定参数的名字)这样初始化后,表指针就指向了 调用函数传来的可变参数中的第1个参数。第6,调用va_arg,取可变参数:变量=va_arg(可变参数表指针,参数的数

26、据类型)第1次调用va_arg时,它返回可变参数表中的和第1个参数。随后每一次调用,它返回表中的下一个参数。每次调用之后自动修正表指针的值,使它指向随后的一个参数。为了正确地停止读可变参数表,应该在调用函数可变参数表的最后放一个表结束符(例如-1或0),在被调用函数中再去检查这个表结束符。While循环很适合做这件事情,如和vprintf,允许在一些固定参C语言还允许用户Turbo C中提供了以va_stat , va_arg和va_end借助于这些宏可以下面的例子所示。第7,调用va_end,返回到调用函数;va_end(可变参数表指针)它帮助被调用函数正常返 回到调用函数。应在va_arg

27、读完所有参数之后,才调用va_end返回,否则可能会引起意想不到的结果。下面这个例子利用一个具有可变参数表的函数,从一个数字表中挑选值最大的那个数。#include#include#define EOL -1main()(int big;void vmax(int *,char *,.);vmax(&big,The largest of 55, 67, 41 and 28 is”,55,67,41,28,EOL);printf(%dn,big);void vmax(int *large,char *message,.)(int num;va_list num_ptr;va_start(

28、num_ptr,message);printf(%s,message);*large=-1;while(num=va_arg(num_ptr,int)!=EOL)if(num*(large) *(large)=num;va_end(num_ptr);函数指针及其应用函数名后面跟一对圆括号(兴许括号内还有参数),将导致去计算 这个函数。仅仅一个函数名则意味着是一个指针,是指向这个函数的 指针。函数指针有两个特殊用途,不太熟练的程序员可能很少使用函 数指针,但在某些场合下若借助于函数指针,则会使程序显得非常精 练。第1种用途是把函数名赋给一个指针,然后用这个指针去间接引用函数。请看下面这个例子:#

29、includemain()(double x;const double delta=1.0;const double first=0.0;const double last=10.0;double (*fx)();double quad_poly(double);fx=quad_poly;x=first;while(x=last)printf(f(%1f)=%1fn,x,fx(x);x+=delta;double quad_poly(double x)(double a=1.0,b=-3.0,c=5.0;return (x*a)*x+b)*x+c;在这个例子里,语句double(*fx)()说

30、明fx是一个函数指针,该 函数返回一个double型数。然后把函数名quad_poly赋给这个指针。通过fx(x)引用这个函数,取得函数的返回值。有时候,程序中要用到多个函数,这些函数有相同的参数要求和相同的返回值类型。但这些函数不是同时都要用到,而是根据不同的情况每次仅调用其中的一个。比较笨拙的办法就是用switch语句去实现,虽然也还清楚,但程序显得冗长。用函数指针则显得精练多了, 如下面的例子所示:#include#include#include#define MAX 3main()(double x;const double delta=1.0;const double first=0

31、.0;const double last=1.0;double (*fxMAX)();int i;char ch;double quad_poly(double);fx0=quad_poly;fx1=sqrt;fx2=log;for(i=0;i x=first;while(x%1f,first,last);printf(is %1fn,find_largest(first,last,delta,fx);double quad_poly(double x)double a=1.0,b=-3.0,c=5.0;return (x*a)*x+b)*x+c;double find_largest(dou

32、ble a,double b,double step,double (*fx)()double x=a,big=(*fx)(a);while(x=b)if(big(*fx)()big=(*fx)(x);x+=step;return big;在这个例子里,函数quad_poly计算(a*x3+b*x+c)的值,参数x和返回值教师double型数。函数find_largest根据某一计算法则求出在某一范围内(某一步长)的最大值。不但范围和步长是由调用参数指定, 计算法则也是由调用参数指定,所有的参数返回值都是double型数。主函数main调用find_largest求最大值,通过函数指针把函数q

33、uad_poly人微言轻参数传给find_largest。段与偏移量8086 和 80286 的寄存器都是 8 位或 16 位的,而 8086 以及工作于实地 址方式下的 80286/80386 的地址空间却是 20 位的。这样在寻找下一条将执行的指令时以及当用寄存器间接寻址内存中某一数据时,16位的指针寄存器和地址寄存器却不足以存下20位地址。为了解决这个问题, 便把20位地址分为两部分,分别称为段地址和偏移量。段地址可以是 任一16字节边界处,即段地址的末4位一定是0,不需要保存,只把高16位存入段寄存器中。偏移量也是16位的,一方面便于寄存器间接寻址,但另一方面也限制了每段不能超过64K

34、字节。考虑到程序和数据 的寻址一般都是连续的,故这种设计还是合理的。在80X86微机中,总是有4个16位段寄存器:C D SS, ES。在Turbo C编译产生的目标码中,一般只用到了其中的3个寄存器,CS用来存放码的段地址,DS用来存放全局变量和静态变量所在段的段地址,SS用来存放局部变量,参数(以及其它属于某一个函数的信息)所在段 的段地址。在Microsoft C 6.0中,ES用来存放基指针的段地址。如果需要,在Turbo C中,程序员可以通过伪变量_DS _CS _SS _ES取得这些段寄存器的值。如果程序的码、数据和堆栈分别都不超过连续64K字节,则在程序的整修执行过程中,CS D

35、S和SS寄存器的值可以不变,仅仅通过对单字偏移量的操作就可以寻址到所有的码和变量,这样速度比较快。如果码或数据不能在边续64K字节内放下,则必须用双字的段:偏移量来寻址,但速度也就变慢。在所有程序中,堆栈的操作都较频繁, 所以都禁止使用双字的寻址方法,并限制堆栈不能超过64K字节,即不超过一段,尽管有时各个程序模块使用自己独立的堆栈。有时,我们知道了某一个存储单元的段地址,也知道它的偏移量, 也知道它的偏移量,但这个段地址和偏移量并没有构成一个远指针。Turbo C中提供了如下4个宏,使得可以从这个内存单元中读(或往这个内存单元中写)1个字节(或1个整数)。char peekb(unsigne

36、d segment,unsigned offset)int peek(unsigned segment,unsigned offset)void pokeb(unsigned segment,unsigned offset,char value)void poke(unsigned segment,unsigned offset,int value)下面是从ROM勺地址FFFF: 000E中读1个字节的例子,这个字节实际上就是微机类型的标志字节。/* #include*/#includevoid main()printf(PC model=hex%x”,( char ) peekb(0 xff

37、ff,0 x000e);这4个宏以及下面将介绍的3个宏的定义都在头文件dos.h中,但在头文件general.h中包含有#include这样一行,所以在上面 这个例子中只写了#include。因为下面经常要用到general.h,故单独把它列出来。/* general.h */#include#include#include#include#include#include#define boolean int#define TRUE 1#define FALSE 0#define BLANK #define CR 13#define beep putchar(a)#define newline

38、 putchar(n)#define EMPTYSTR #define FARNULL (void far *)NULL#define INTA00 0 x20#define EOI 0 x20#define strempty(s) (*(s)=0)boolean strequalsf(char *s,char *t,char *ps,char *pt);boolean strequalsb(char *s,char *t,char *pm,char *pn);#define strchrf strchrchar *strchrb(char *s,char *p,char c);#define strpbrkf strpbrkchar *strstrf(char *s,char *t);char *strstrb(char *s,char *t);char *stralloc(char ch,unsigned N);char *strsubst(char *s,char *a,char *b);char

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论