易码技术论坛

 找回密码
 加入易码
搜索
12
返回列表 发新帖
楼主: FantasyDR

[教程] [连载][从Lava到MFC](一)数据类型的简单介绍 [更新至第一节]

[复制链接]
发表于 2006-11-23 19:14:45 | 显示全部楼层
先顶了~今晚没空看,周六看~
 楼主| 发表于 2006-10-30 23:49:33 | 显示全部楼层 |阅读模式
教程为一家之言难免会有不少缺漏甚至错误,如果有哪位过路者发现了可以指出,谢谢。

本帖谢绝任何广告以及与主题无关的争论。

Lava之名源自最初由lee开发于文曲星nc2k系列上的跨平台应用程序标准。后历经各种变故以及版本更新,衍生出由GGV另行开发的GVMaker系列,以及由lee继续维护更新的LavaX/Lavo系列。下文中Lava指可以编译并且能在wqx上运行的若干Lava版本,并不特指lee或者GGV的编译器。可以看作是lee的LavaX3.0标准和GVMaker1.0标准的均予以支持的公共部分。

●序言
shooting在尝试做《游戏设计与c++》的教程连载,我这边弄点简单的,就从程序设置基础开始。当然我不会从c/c++的关键字讲起,也不会讲数组和变量应该怎么定义,我这里假设的读者群是已经具有简单的程序设计常识的人,再具体一点就是Lava Coders

前段时间论坛做了个统计,可以看出熟悉Lava的人群仅次于GVBasic的用户群。这说明Lava作为一款简单易学的语言,非常受WQX Fans的欢迎。在这里我不是要界定Lava与其他语言的孰好孰坏,而是从它们的区别入手,逐步引出windows程序设计的知识。希望能让一些只接触过Lava的程序爱好者们也能体会到开发windows应用程序的乐趣。

之所以取名叫做《从Lava到MFC》,并不是要让读者在读到最后一章的时候,可以像玩转Lava一样玩转MFC。MFC是微软给windows开发的应用程序框架,并不等价于c/c++,也不是开发windows程序的唯一选择,况且我本人也不很了解MFC。可以说,这个题目完全是个噱头,最终的目的有三个层次:

第一层,让Lava Coder了解Lava和c/c++的区别,从而可以在日后的学习中能更方便的过渡到c/c++的世界中,毕竟在大学中还是很容易遇到这门课程的。
第二层,对于有经验的Lava Coder,可以在我提供的lava-sim框架下开发出即可运行于电脑又可以编译成lava字节码的程序。
第三层,对于有意了解windows应用程序开发的人群,通过了解lava-sim的运作方式学会简单的基于MFC的程序制作。

如果你对以上任何一个目的都没有兴趣,那么随便浏览一下就可以了。

关于上文提到的lava-sim,随着教程的进展,我会详细介绍这个框架的使用方法。第一讲暂时用不到,就先不细说了。
[附]教程使用的程序框架lava-sim:
http://lava-sim.googlecode.com/svn/trunk/

这是一个用MFC模拟了lava绝大多数函数功能的框架,配合教程使用。下载源码请安装TortoiseSVN,如果有人希望参与开发请跟帖并附上你的google帐号。

此程序基于loglave当年的研究,经过FantasyDR的进一步完善,以及lee的lava控制台编译器程序发布,可以做到同时进行pc应用程序以及lava程序的开发。

注意:此开源项目中并不包含lava的编译器,若需要编译成lava程序,请前往lee的Blog下载LavaX3.5版的LavaXIDE,将其中的lc.exe和lvm.exe拷贝到工程的lava目录下即可。

●数据类型
Lava将变量类型这个概念做了淡化,以“无类型”语言出现在了大家面前。这的确可以减少很多思考上的问题,以及编译器设计上的麻烦。但c++是强类型语言,c语言相对与Lava来说也可以看作强类型语言,它们对于变量类型的区分是非常明确的。因此在第一讲中,必须重新拿起变量类型这个概念,重新做一下阐述:

第一节
关键字:位(Bit) 数据(Data) 常量(Const) 变量(Variable) 内存(Memory) 地址(Address) 指令(Instruction) 数字(Number)

有些人(包括我=.=)总是习惯性地说出"变量类型"这个词。但是"变量"相对于"常量",常量也是有类型的。二者本质上都是数据,其实我要讨论的是数据类型。

类型用于区别不同的物体。通常情况下,越是高层的逻辑,其描述物体时使用的分类越详细。组成万物的基本粒子只有几十种,但大千世界的事物种类成千上万,无论从哪个角度划分,其类型都要远远多于基本粒子。

同理,数据类型是为了区别不同种类的数据而存在,它不是给CPU看的,而是给程序员和编译器看的。一段执行中的代码中本无类型可言。设想从电子元件的角度看去,一切都不过是电流的变化。当这些模拟量被数字化后,从CPU的角度看去有了Bit的概念,也就是"二进制的数字"。CPU在处理这些Bit的时候,将它们划分成指令和数据两种类型,同时根据指令的不同,数据可能用来表示一个"数字"或者表示一个"地址"。

即便是数字和地址,也有些许差别。有时需要表示大一些的数字,有时需要表示小一些的数字,把它们区别开对待可以更有针对性的设计指令,所以数字被分为8位、16位、32位乃至64位。地址信息会根据寻址范围的不同选择不同的位数,譬如64k内存有65536个地址,用16位的数据就可以表示。内存增大的话,就得使用用更多的位数。

进一步考虑,数字还有正负之分。用Bit表示的数字需要采用规定的方式区分正负,所以每种数字又被分为"有符号数"和"无符号数"。一般情况下,有符号数是把数据最高位当作符号位,0表示正1表示负。无符号数只能表示正整数,由于它不存在符号位,相同位数的无符号数能表示更大的数字。

假如再考虑到小数的表示,又会出现浮点数等更多的数据类型,在这里就不继续讨论了。(具体的表示方法请参考相关计算机原理书籍)

上面的讨论一直是站在CPU的角度,汇编语言是直接和CPU打交道的,因此以上内容基本囊括了汇编的所有数据类型。可以想象,程序语言的一步步的抽象将导致数据类型变得更加丰富。尝试过Windows程序设计的人一定会对其中令人眼花缭乱的数据类型印象深刻。而面向对象机制的引入,使得数据类型似乎脱离了"数"的概念。但万变不离其宗,其本质上仍旧都是数字和地址。这种观点在很大范围内都是正确的,树立这个想法,可以缓解学习新语言时的遇到的"类型恐惧症"。

第二节
关键字:基本数据类型 指针 结构体
题外话:为了不引入错误的观念,我特地去查了一下c语言标准,却发现了一个诡异的事情。标准中竟没有规定这些基本数据类型的长度,只给了很模糊的定义:“编译器可能使用不同的数据位长和范围。这取决于使用的编译器。请参考具体的参考手册。”为了避免让这个朴素的教程充满诡异的争论,我所指的都以Microsoft Visual Studio 2005中的c++编译器为准。这个编译器还是比较符合一般习惯的。
接下来切入正题,开始讨论lava和c的数据类型。c语言的数据类型比较复杂,但是分类很详细,因此先从这里入手,介绍几种常见的数据类型,稍后会和lava做对比讨论。首先是最基本的整数类型:
char:8位有符号数据
short:16位有符号数据
long:32位有符号数据
long long:64位有符号数据
这三个类型用来表示有符号的整数,它们的最高位是符号位。如果需要表示无符号数怎么办呢?c语言提供了unsigned关键字,只要这样写就可以:
unsigned char:8位无符号数据
unsigned short:16位无符号数据
unsigned long:32位无符号数据
unsigned long long:64位无符号数据
它们只能表示正数,但由于最高位也用来表示数据,所以它们可表示的数字绝对值是前面对应的类型的2倍。
题外话:看到这里可能有人奇怪,为何少了一个常见的类型int?这是因为int比较特殊。以前听说过一种说法,int类型的长度和编译器所处的环境的CPU位数有关,16位系统下的编译器会把int做成16位的,而32位CPU下的编译器则会生成32位的int数据。16位的时代已经过去,我们可以不考虑。现在绝大多数环境都是32位的CPU,那么int的实际效果和long是一样的。有人可能会发出抗议,64位的CPU也比较常见了,在64位环境下int会变成64位的数据么?根据MSDN里面的说法,int的长度只能在short和long之间,所以32位就到头了。事实上,long和short并不是数据类型,只是用来修饰基本类型的修饰符,就像unsigned的作用一样。因为默认的修饰对象是int,因此书写时可以省略掉。可以说int已经没有存在的必要=.=
关于int,如果是在MS的VS中编译我们的程序,可以肯定的说:
int等价于long,表示32位有符号整数。
unsigned int等价于unsigned long,表示32位无符号整数。
如果需要表示小数,可以用下面的类型:
float:单精度浮点数,占4字节
double:双精度浮点数,占用8字节
可以看出,上面所说的数据类型都很好理解,因为它们只代表一个“数字”,并没有超越第一节中对类型的概述。那么我们尝试对上述类型做个小小的修改,加"*"到类型名称的后面,这时它们就不再表示数字,而是表示一个存放了该类型数字的地址。这就是指针。举个例子先:
unsigned long * pn;
pn表示一个指向32位无符号数的指针,虽然本质上它也是一个数,但pn的意义并不能单用这个数的值来表示,pn指得是在内存地址为pn的地方存放着一个32位的整数。pn的值没有绝对意义,因为它究竟在内存的哪里并不重要,我们只要使用(*pn)就可以获取存放在这里的数值,具体的内存地址是编译器和操作系统决定的。就好像我们不需要知道北京市的经度和纬度,只要乘坐开往北京的列车就可以到达,具体的地点是列车长的事情。但要注意,pn的值有相对意义,就好像你得知道北京是乘多少站才可以到达。指针常见的加减运算可以表现这一点:
pn ++;
这个运算表示pn增加了"1"。我这里之所以加引号,是因为这个1并不表示1个字节或者数值上的1,而是表示1个单位长度。pn的类型是32位无符号数指针,所以1个单位长度是32位,也就是4个字节,因此pn的实际数值被增加了4。我们可以类比,假如pn的定义是这样的:
short * pn;
这说明pn指向了16位的有符号整数,所以pn++会让pn的值变大2个字节,也就是16位整数的单位长度。这一点尤其重要,后面和Lava做对比的时候就会发现,Lava并没有实现这一点,因为它没有指针数据类型,只有存放了地址的数值。
题外话:指针有什么用呢?如果有数组的概念,那么稍微好理解一些,一维数组就是该类型数据的指针,二位数组就是该类型数据的指针的指针,依次类推。指针可以看作是数据的门牌号码,通过门牌号码可以快速的找到需要的数据。调用函数的时候,有时候需要将大量的数据作为参数传入,这个时候可以仅仅传入这堆数据头部的指针,其他数据通过这个指针和偏移量就能访问。(关于高效的参数传递,还可以使用引用,关于引用请参考shooting发的帖子。)同时,指针可以更加灵活的操作数据。譬如动态分配出来的空间可以被赋给一个指针,然后程序通过指针和偏移量来操作空间中的各个数据。假如某份数据需要被多个其他数据所包含(譬如下面将要提到的结构体,就可以包含各种数据),但是这份数据只有一份本体,那么其他数据种可以仅仅包含它的指针。(引用也可以,参考shooting的帖子)

下面继续介绍一个稍微复杂些的数据类型:
struct:结构体,用于定义一组数据,它的大小由它的成员决定。
c++里面结构体不仅可以包含数据,还可以包含方法。这里只说c的概念,举个例子:
struct Time
{
  short hour;
  short minute;
  short second;
};
struct Time now;
上面的代码先定义了一个结构叫Time,包括“时,分,秒”三个成员。每个成员都是short类型,所以一共2*3=6字节。然后声明了一个Time类型的变量,叫做now,假设我们准备在某些地方用now来储存现在的时间。注意声明结构类型变量,需要在前面加上struct关键字。

结构体的概念就稍微抽象了,它不可以再用第一节中的方式概括。虽然我们仍旧知道,它被编译后的意义不是数字就是地址,但显然在编译之前它在c语言中的意义不是这样的。now就是一个完整的数据结构,包括了三个成员。我们可以通过now.hour来得到小时分量,用now.hour++给小时增加1。但是如果我们做“now++”这个运算,就没有意义了。因为now不是指针,它加1不能表示任何事情。
题外话:Lava中也有相同的东西,但是Lava可以直接用结构名来表示这个结构储存的地址,c语言中是不可以的。c语言中只能用&来获取结构变量的地址,譬如"&now"表示获得now的实际储存地址,可以将这个值赋给"struct Time*"。

下面说一说Lava中的数据类型
(未完待续吧……)

第三节
关键字:隐式类型转换  强制类型转换  typedef  #define
(未完待续吧……)
您需要登录后才可以回帖 登录 | 加入易码

本版积分规则

Archiver|手机版|小黑屋|EMAX Studio

GMT+8, 2024-4-20 17:47 , Processed in 0.013410 second(s), 17 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表