ZT: DELPHI講義

第一讲  delphi基础

【例】改变窗体的标题,颜色,尺寸

  delphi 程序设计特点:可视化,开发方便

【例】写代码,改变标题,颜色,尺寸

  对象成为处处用到的基本元素。对象含有属性和事件。delphipascal语法为基础。

一、delphi是什么?

    Delphi borland 公司研发的面向对象的,可视化的,快速的应用开发工具。

  ◇对象是对相对独立的客观存在或相对独立的逻辑存在的计算机表示。

  ◇面向对象是与面向过程相比较而言的。

    1  对象的三个主要特征:封装、继承和多态。

       面向对象的优点:易管理,可重用。

    2  可视化的体现:   所见即所得的界面设计;delphi组件是可视化的对象。

    3  快速的原因:  可视化;丰富的函数,类库,组件,数据库组件;开发工具和向导;编译速度最快,语法的高度最一致。

二、delphi不是什么?

1 不是专门的数据库开发工具。

  ◇研发的的背景和目的:windows初步流行,主要开发工具VCVB各有所长;数据库开发的需求急剧增加。Delphi = VC功能+VB界面+快速数据库开发。

  delphi中没有专门为数据库操作保留的关键字。

  ◇有很强数据库能力的原因是使用Delphi后续开发的数据库组件。

 【图解】delphi语言及开发包提供的资源的关系。delphi的语言基础是object pascal,在其上构建的函数库,类库,控件库,甚至是IDE本身都不是语言的固有组成部分,而是用delphi语言写成的第一批工具,这扩展了delphi的能力,也验证了delphi的强大功能。

2 不是专门的界面开发工具。IDE本身是delphi程序且提供源代码;delphi可脱离IDE环境

而独立运行。

  3 Delphi 可用于各种通用目的。通信,图形处理,数值计算,自动控制等。

三、学习基本语法前的准备

  【例】不使用IDE的控制台小程序

  program p;  {$AppType console}  begin  writeln('hello');  end.

    编译方法:  DCC32  *.pas

    ◇语句是程序的基本组成元素;语句间用分号分隔;delphi没有行的概念

    【例】稍微扩展的控制台小程序

  program p;  {$AppType console} 

    const s = 'hello';  var  a,b,c : integer;

    function MyAdd(x,y: integer): integer; begin  MyAdd := x*10 + y; end;

begin  a:=5; b:=6;c:=myadd(a,b); writeln(c); writeln(s); end.

◇函数是最常见的语法现象

◇‘:=’表示赋值操作;‘=’功能之一是表示定义;与C语言的写法有区别

 

    1 源程序与机器表示的映射关系

  【图解】源程序被翻译成机器码后,原来的结构特征消失了。在机器表示中,只有一种结构,就是:数据+操作码。通常的编译,把内存分为三个部分来使用:静态空间,栈空间和堆空间。

    ◇我们我讨论的内存是经过操作系统虚拟过的内存,称逻辑内存或虚拟内存,这是相对于硬件上提供的物理内存而言的。

    ◇函数被广泛使用,并且可以嵌套。一般通过栈的方法来实现在不同的函数执行环境间切换。栈的操作有两个,压栈和弹出。函数携带的参数也是通过栈的形式来传递的。

  ◇栈也用来实现局部变量的自动申请和释放。

  【例】windows风格的程序例子。工程是管理者,也是pas格式的代码,只是扩展名不同。单元是最重要的被管理的元素。.pas扩展名,是标准的源代码。

    2 与Dos程序不同,windows下的程序不在是从主函数开始的“一根面条”,而是与消息相联系的若干可执行的部分。

    【图解】在windows下,代码响应事件(或消息的过程)。消息是可以被传递的信息,有固定的格式,应用程序可以发送消息,也可以接受消息。

  3 接受到的消息被放在队列中,等待处理。windows下的应用程序只做两个循环往复的动作:提取消息和处理消息。

    ◇消息由标识和参数组成

  ◇具有某个标识的消息可以和某个函数相关联,叫做:消息映射。

  【例】怎样在windows下输出信息呢?在windows下,对屏幕的输出是输出图象,一般是通过画的方法,而不是写。屏幕被抽象为许多逻辑上独立的区域,供给多个程序使用。这些矩形区,叫窗口。程序中用于代表窗口的可输出能力的对象是TCanvas

  ◇可以通过canvas在自己的窗口上直接画图形,或“画字”

  ◇可以控制canvas内的penbrush等对象来实现对输出内容外观的控制。

  【例】使用控件直接输出内容。这是实现输出的更常用的方法。也体现了对象的重用性的优点。

    4 修改控件的属性,就可以直接实现输出。这与VC不同,改变属性,不是简单地改变了对应单元的数值,而是执行得了更复杂的操作,否则无法实现屏幕内容的变化。

  ◇在屏幕上看到的控件也是窗口。我们所节省的代码在编写这些控件的代码中必须出现,而且,也是通过canvas向屏幕输出的。

  ◇使用控件的优点是简单,直观。控件可在设计时修改属性。

  【例】保存工程,指定目录和修改名字

    5 在我们指定的目录中,有许多文件,他们的关系如图。.dcu.exe较大,我们拷贝源程序的一般方法是删除.dcu.exe其他的都拷贝。

.PAS

.DCU

.DFM

.RES

.DPR 工程

.DOF .CFG 配置

.EXE

 

 

 

 

 

 

 


    ◇重要原则:不要去删除自动生成的代码。我们把自己写的删除,IDE会自动把它写的删除。

  ◇不可用save as 的方法备份源程序,这样会使工程中的单元的路径改变。

    6 获得帮助的办法是把光标停在某词上按F1,这时会出现与该词相关的帮助。

  四、基础语法

  1 不可忽视注释。文档是软件的重要组成部分。delphi的注释有多种方法:{}用于注释一段文字;//表示行注释;{}也可以注释一段。{$..} 是特例,表示编译指令。

  2 有名量和匿名量。名字是量的标识符,是用于区分不同的量的手段。

  ◇标识符的定义规则是:字母开头;不含空格;最长255;一般只含字母,数字,下划线。

  ◇名字要有意义,不要用拼音缩写。可以用英语或全拼。GZ:工资?高中?规则?广州?

  ◇delphi不区分大小写,可以用字头大写或下划线区分名字中不同的字。如:gong_ziGongZi

DaYin_BaoBiaoCaiDan_BaoCun_WenJianMenu_SaveFile

  3 常量和变量。通过前缀修饰符const,var定义常量和变量。

  ◇varconst不是域的概念,只是前缀。之所以看起来象域定义,是因为如果省略前缀就默认为与前一条语句的前缀相同。

◇普通常量不分配内存地址,表现为代码中的立即数。

◇类型常量分配内存,有地址,可以通过指针使用它。

4 数据类型综述。

◇学习数据类型时要弄清楚的问题:用处?定义?占多少内存?常量怎样表示?有关的函数?

◇数据类型可从多个角度划分。我们从使用该类型的角度看,可划分为书中p20的结构。若从是否需要定义的角度看,可分为原始类型和用户定义类型。

◇要注意各个类型间是否可转换及如何实现转换。比如整数和实数。

5 整数

用处:表达可数的数目;记录循环次数

◇定义:var a: integer;  b,c: int64;

◇内存占用:见p21

◇常量表示:普通 123;十六进制 $1FE7

◇必会函数:Div, Mod, Dec, Inc, StrToInt,

6 字符

◇用处:表示单个字母或数字

◇定义:var a: char;  const b:char='x';

◇内存占用:P23

◇常量表示:'a';单引号本身的表示:''''chr(39)#39

◇必会函数:chr, ord

7 布尔

◇用处:表示是或不是

◇定义:var a: boolean; 

◇内存占用:P24

◇常量表示:true,false

◇必会函数:not and or

8 枚举

◇用处:代表有限集合中的每一个元素,为每个元素指定一个有意义的名字。

◇定义:type TV_Color = (TV_red, TV_Blue, TV_Green); var a: TV_Color;

◇内存占用:相当于常量,无内存地址

◇常量表示:TV_red, TV_Blue

◇必会函数:ord取得序号;序号从零开始。

9 子域

◇用处:限定范围的字符类型或整型(序数类型),可在编译阶段检查赋值合法性。

◇定义:type Age=1..200var a: Age;

◇内存占用:与它使用的序数类型相同。

10 实数

◇用处:表示连续量

◇定义:var  x,y: double;

◇内存占用:见p26

◇常量表示:34.560.23.231.2E-3

◇必会函数:FloatToStrFormatFloatIntFracRound

 

11 记录

◇用处:把许多量组合在一个类型中

◇定义:type STU=record name: string[10]; id:integer; age: double; end;

        var a: STU;

◇内存占用:基本上等于组成的变量所占内存的和(存在整字对齐问题

◇引用:edit1.text := a.name;  a.age := 15.5;

◇注意:元素可以是任何类型,甚至是另一个记录类型;可以用packed指定不要整字对齐。

12 数组

◇用处:表示同型元素构成的列表

◇定义:type AAA = array[0..100] of char; BBB=array[5..10] of STU;

        var a: AAA;

◇内存占用:数组大小乘单个元素的占用。

◇引用:a[下标];如 a[2], a[34]

◇必会函数:Low,High

13 字符串 delphi中最重要的数据类型

delphi的串不是定义类型,是语言固有的类型,这与C串的概念不同。

delphi的串有短串和长串之分。一般短串在栈中分配空间,长串在堆中分配空间。短串最大长度是255,与C的串占用空间相同,结构有区别。在堆上分配的长串,是自动堆对象,不需要手动地申请和释放。

◇定义:var s: string; {长串}  s2: string[12]; {短串}

◇可以用下标的方法引用串中的单个字母:s := 'hello'; s[1] 表示‘h, s[3] 表示‘l’。

◇必会的内部函数:length(s)s1:=copy(s,1,2)delete(s,3,2)insert('abc',s,2)

i := Pos('abc',s); s:=LowerCase(s); upperCase; format

14 语句综述

   与其他的语言的语句形似,delphi的语句从流程方向上也分三种:顺序语句,选择语句,循环语句。从语法结构上又分为单语句和复合句。复合语句是由begin,end 包含一组语句。类似C语言的{ }。语句间的分隔符是分号。

  15 选择结构

  if 语句的格式: ①if .. then ..; ②if .. then .. else ..

  ◇if x = 5 then x := x + 5else x := 12; 是错误的写法

  ◇if x > 90 then t:=1 else if x >80 then t:=2 else if x > 70 then t:=3 else t:=4;是正确的写法。注意elseif 不能连写。

  【技巧】 减少嵌套层数的技巧  逐渐逼近条件法

  case 语句的格式:case x of 1:t:=12: t:=2;3..7: t:=3else t:=4 end

  ◇case s of 'abc': t:=1'bcd': t:=2 .... 是错误的写法。case 只能是序数类型。

  ◇case x of 1: t1:=2t2:=32: t1:=4t2:=5end;是错误的。分支情况后边只能是一条语句,如果有多条语句,要使用复合语句来处理。

    16 循环结构  for .. do ..  while .. do ..  repeat .. until ..

    一般for适用于次数可确定的循环;while先判断条件,repeat 先执行。

    break 用于从循环中跳出; continue用于跳过一次循环,执行下一次。

  17 其他较深的语法在后边分别介绍。包括多维数组,动态数组,集合,函数的参数传递方式,指针,函数指针,类引用,文件类型,变体类型,变体数组等。

 

第二讲  面向对象的程序设计

  构成delphi的语言基础,严格地说,不是pascal而是object pascal;后者不是对前者的简单扩充和升级,而是思想上,结构上的全面革新。贯穿object pascal始终的概念是对象。

一、面向对象的原理

  ◇面向对象是与面向过程相比较而言的。面向过程的结构化设计方法被广泛使用,但在代码量增大时(1万行以上)有许多困难难以克服。比如全局变量不容易控制;重用性不好等。

  ◇对象是对具有独立性质的事物的抽象。我们考察对象时,只关心对象的性质和行为,而不关心对象的内部实现机制。对象的最大优点是可重用性。

  ◇对象间可以通过消息的传递来相互影响,协作完成某个特定的任务。

  ◇与对象有关的三个概念:封装,继承与多态

  1 封装不是简单地装起来,比如记录结构,比如子目录结构。封装的真正含义是隐藏,就是隐藏代表对象状态的内部数据。

  ◇函数象一部加工机器,而对象保存自己的状态。对象不能用函数加全局变量实现。因为对象的数据是私有的,外界不能直接访问,并且多个同类型的对象可同时存在,且状态不同。

  【模型】现实世界中的收音机。设计良好的收音机,外界的输入错误并不会破坏内部的元件。

  2 封装的内容是私有的数据和操作。但如果私有的东西完全不能被访问则没有意义。不能被外界感知的东西对外界来说就是不存在的。所以要对外界提供访问接口:属性和方法。属性和方法是对被封装的内容的曲折的反映和间接的操纵。

  3 定义对象的性质的程序文档,称为类。类并不存在于内存中。通过类可以生成或说制造出多个同属于一个类型的对象。这个过程称为实例化。

  4 用于描述多种对象的文档可能有许多相似的地方。我们可以通过继承的方法减少这种重复描述的文档。继承就是指出某个文档包含其他文档中描述的特征。

  ◇继承是文档的特征,因而只是对类而言的。对象是内存中实例,对对象而言,不存在继承的概念。当观察一个对象实例的时候,我们并不知道它是否是通过继承的方式被构造的。

  ◇对从其他文档中继承的内容,我们可以通过重新描述而替代原来的内容,或者干脆通过隐藏的方法而不向外界公布某些内容。可见继承并不一定必须是上下级的关系。只要有相似的地方,就可以使用继承,继承的目的之一是为了省写代码,即代码重用。

  ◇在定义类的时候,继承提供了极大的方便。这启发我们定义一些描述多种对象性质的抽象的类。这些类不能生成实例对象,它们的唯一作用就是为其他的类描述提供继承。

  5 多态是面向对象的精髓。一般来说,多态指对同样的消息,不同的响应。静态多态通过函数重载来实现;狭义的多态指在编译时无法确定要调用的函数的地址的现象。

.劳动

   【模型】从劳动者类继承的农民,工人和其他劳动者,从农民类继承的青年农民中,有些定义了劳动这个动作。见图。

.劳动

.劳动

劳动者

农民

工人

其他

青年农民

 

 

 

 

 

 

 

 

 

 

 

 


◇形成多态的前提:抽象地看待一个对象。把农民,青年农民,工人都抽象地当作劳动者来处理。这种处理方法叫做泛化。

◇在调用一个被泛化的对象的方法时,一般我们并不是希望调用抽象类的方法,而是希望执行对该对象而言,最具体的方法。因为在编译时无法确定被泛化的对象的真正类型,因而就无法确定要执行的函数的地址,从而产生多态的现象。

◇多态的实施是通过在运行时保存类型信息的办法来实现的。类型信息是关于类的全体的公共信息,不是专属于某一个特定的对象。类型信息间有继承关系,就是对类的继承关系的计算机表示。

◇多态的方法使我们站在概括的高度来处理对象,抽象思维是人类的特点和优点,多态的使用充分发挥了这种优势。我们在抽象设计的时候,可以把注意力放在对象间的本质联系上,可以不考虑任何具体特征,这种设计方法称为蓝图设计。

◇继承的目的之二就是要产生多态的能力。这是继承的重要目标。

6 面向过程的设计方法=数据结构+算法;面向过程的设计方法=对象+消息

【模型】奴隶社会和现代社会的对比。在生产资料少之又少的时候,集中管理更有效,在生产极大发展的时候,分布式的对象模型更有潜力和稳定性。

◇设计原则:

①对象要有独立的特点,就是不依赖或较少地依赖于外界。用术语表达,就是对象要有确定的边界。边界把对象的内部和外界环境严格地区分开。换句话说,我们必须能够明确地指出哪些属于对象,哪些不属于对象。

②对象要能完成相对独立的功能。对象的定义一般从功能的定义入手。

③对象要保护自己的数据,使外界无法干涉对象的独立性。同时,对象的方法应该操作自己的数据,而不是去他人的数据。

④对象对外界的影响是通过向其他对象发送消息来实现的。尽量避免直接操作外部的数据结构。

7 面向对象的设计并不完全排斥面向过程结构化方法。体现在:

①一般大型工程项目都采用了自顶向下与自底向上相结合的方法。好比盖楼房。

②在每个对象实现过程中仍然要使用结构化的设计方法。

8 构建类的时候,注意考虑对未来重用性能。尽量能构造成树型的类群,以便将来从合适的节点继承形成新类。

9 面向对象的思想的实现要兼顾到运行效率的问题。就效率而言:

c++ > delhi > Java > smallTalk。就面向对象的彻底性而言,则刚好相反。

二、面向对象的delphi实现

  1 类的定义  type 类名=class(父类) ... end

  ◇不写父类,表示从TObject 继承,这时,括号也不写

  ◇不支持多重继承。可以通过支持接口的方法实现多重继承的类似功能。

  2 保护方式  关键字 private protected public published

  可以把类的用户分成三组:①自己:就是类的成员函数 ②子孙:就是从本类直接或间接继承的类的成员函数 ③他人:就是除此以外的其他函数。

 

 

private

protected

public

Published

自己

允许

允许

允许

允许

子孙

禁止

允许

允许

允许

他人

禁止

禁止

允许

允许

  

  ◇publishedpublic有相同的保护效果。但publised修饰的成员可以在设计时出现在对象浏览器中。注意,这不是delphi的内部功能,因为对象浏览器也是用delphi写的。

  ◇有一个特例:处于同一单元的定义,可以自由访问,忽略保护方式描述字

  ◇保护方式也是一个前缀,不是域的概念,后边无冒号,注意与C的区别。

  3 类操作符――isas

    is的用法:表达式“对象is类”返回一个布尔类型。如,猫是猫类;猫是动物类。

  ②as的用法:表达式“对象as类”返回该对象,但类型已经强制为后边的类所指定的类型。这种表达方法叫特化。

  ③二者经常联合使用。if a is TA then (a as TA).caption := ...

    【例】多个控件连接到同一个事件,怎样在事件中区分是哪个控件发出的事件?不同类型的判断要用is,相同类型的判断要用as配合tag属性。

  4 对象存储结构

  在内存中,只存储对象的私有数据,而方法只有一个拷贝,与其他函数一起存于静态空间。对象在定义的时候,只是一个指针(4字节)并无对象的实体。必须调用类的构造方法才能生成对象。该对象存于堆空间中。对象在使用完毕后,需要调用它的析构函数来完成对内存的释放。在delphi中,一般不直接调用析构函数,而是调用free函数,这是更安全的方法。

  5 类引用

  类引用是指向类的公共信息的类型。类引用包含了类的名字,父类引用,对象占用多少内存等信息。类引用的使用是为了在运行的时候,获得关于对象的类的特征的描述。

  定义:type 标识符 = class of 类名;

  ◇在早期的面向对象语言中无类型信息的概念。这些信息只有编译器知道,而无法被运行中的代码获得。运行时类型信息,就是资料中常提到的RTTI

  【例】取得对象的RTTI  .instanceSize返回整数;.classparent返回父类型信息。

  6 构造函数

  构造函数是特殊的函数,严格地说它不是成员函数,构造函数完成内存申请和初始化成员变量的作用。构造顺序是先构造祖先,再构造自己。

  ◇定义: constructor create;或 constructor create(参数列表)

  ◇构造函数的实现中一般多要先调用祖先的构造函数。方法是:inherited create..

定义两个构造函数时,要用overload关键字表示重载,这是静态多态的例子。

7 析构函数

析构函数是成员函数。析构函数是动态多态的典型例子。当一个对象被抽象地处理时,一定要调用属于这个对象的类所定义的析构函数。而不是抽象类的析构函数。析构的顺序是,先析构自己,再析构祖先,和构造正相反。

定义:destructor destroyoverride

在实现的时候,不写override这个关键字指示着该名字的函数在运行中要做动态多态处理。

  ◇一般来说,析构函数的末尾总是调用祖先的析构函数。inherited;多态特征的函数不需要指明要调用的祖先函数名。

  ◇与C++不同,delphi的构造函数和析构函数都不会被编译器自动调用,必须显式地调用。

  8 替换与覆盖

  替换是根据局部优先的原则,用同名的成员代替祖先定义的成员。这是为了屏蔽祖先定义的某个方法或数据。覆盖则是把祖先定义的方法具体化,或说特化。这是实现多态的基础。

  实现覆盖的方法:①父类声明中加 virtual;②子类声明中加override;③子类实现中不加。

◇用dynamic virtual 具有同样的逻辑含义,只是实现的方式不同。

◇有时,覆盖的方法也称虚方法或动态方法。

9 抽象类型

只定义不实现的虚方法称抽象方法。至少含有一个抽象方法的类称为抽象类。抽象类型不能实例化,只能供给其他类做继承之用。

10 属性

属性就是对象的性质的外部表示或外部视图。属性的使用贯彻了面向对象中的封装的思想,是delphiC++相区别的地方。理解了属性的概念,就理解了封装。

◇定义:property 变量:类型;read 读方法 write 写方法;

◇这样的定义使外界看起来,好象该对象有一个成员变量。实际上,这个变量是虚拟的,在内存中并不存在,当涉及到它的读取时,实际上调用了读方法;当涉及到它的赋值时,实际上调用了写方法。

◇属性就象对象的私有数据的一个影子。它提供了对私有数据的访问的同时提供了保护。

  【例】 type TA=class   //类的定义开始

private fx: integer;   // 私有数据

          private function getx: integer;  //读方法

                 procedure setx(n: integer);  //写方法

published property x:integer; read getx write setx;  //属性的定义

end;    //类的定义结束

var a: TA;  ....

a.x := 5;等价于 a.setx(5);   c:=a.x;等价于 c:=a.getx

    ◇属性可以直接与私有变量关联。

   published property x:integer; read fx write setx;  //属性的定义

  ◇有read write 时,表明该属性是只读属性。

  ◇所谓事件也是属性,只不过它的值是指针,是方法指针,指向用户定义的某个方法。

三、类开发方法提示

  1 分析需要的功能,从delphi类库中找到最合适的节点来继承。

  2 可以从可视化的构件继承。不一定要注册,不注册的控件可以正常在程序中使用。

  【例】自己定义一个中国Edit控件。特点是回车键当Tab键来使用。

  type TMyEdit=class(TEdit) 

private procedure WndProc(var msg: TMessaeg); override; 

end;  //类的定义部分。在其中覆盖了父类的虚拟方法 WndProc

    procedure TMyEdit.WndProc(var msg: TMessage); //实现的时候,不要写 override

    begin  if (msg.msg = wm_keyDown) and (msg.wParam = vk_Return) then

parent.perform(wm_nextDlgCtl,0,0)

           else inherited; 

  end;  //当按了回车键的时候,给父窗口发消息,移动焦点到下一个控件

 3 类的生成还有一种方法,就是嵌入另一个控件作为自己的成员数据。用这种办法生成的类叫做包装类。使用包装类,一般是把被包装的控件当作工具来使用,而不是特化的过程。

 【练习】包装一个时钟类,显示日期和时间。从TPanel继承,把TTimer作为成员数据

 

第三讲  编程进阶

【思考】假如我们要把一个地图数据装入数组,然后显示它,却不知道地图数据有多大,怎么办呢?开一个足够大的数组吗?windows的消息结构只能容纳8个字节,我怎么通过这么少的字节传递长达500k的数据呢?

一、语法进阶

1 动态数组。动态数组是指尺寸在运行时可以改变的数组。

 定义:var a: array of integer;   //其中没有说明尺寸

  

◇动态数组在使用的时候,要先设定尺寸。setLength(a,20)

  ◇下标的引用从0开始。

 ◇动态数组可以当作函数的参数来传递,这种参数称为开发数组参数。对开放数组参数赋给常量的时候,使用方括号。

 【例】 使用开放数组求和

   function MySum(a: array of integer): integer

var i: integer;

    begin  result := 0;

      for i:=low(a) to high(a) do result := result + a[i];

    end;

    ....  edit1.text := intToStr(MySum([1,2,3,4]));

    ....  var s: array of integer; ....  s[0]:=1; s[1]:=2; ..... edit1.text := intToStr(MySum(s));

  2 多维数组。多维数组只不过是关于数组的数组,就是说数组的元素仍然是另一个数组。

 定义:type 类型标识: array [下标范围] of array [下标范围] of 某类型;

 例如:type AAA: array [0..9] of array [5..10] of integer;

  访问方法:var a: AAA; ... a[1][5] := 10

 ◇还有另一种定义和引用的方法,但不能体现多维数组的本质。

  type AAA: array[0..9,5..10] of integer... a[1,5]:=12

 3 函数,过程和参数

 delphi中的子程序有函数和过程的区分。带有返回值的叫函数,没有返回值的叫过程。它们的定义采用了不同关键字,这是与C语言不同的地方。

  定义:function 函数名(函数参数):返回值类型;

     例如:function myAdd(x,y: integer): integer;

          procedure 过程名(过程参数)

     例如:procedure MyProc(s: string);

  ◇当没有参数的时候,一对圆括号要省略。这与C不同。

 ◇调用子程序的那个程序叫主调方;被调用的子程序叫被调方。主调方传入给子程序的作为子程序的参数的量称为实际参数,简称实参;在子程序中定义的参数,称为形式参数,简称形参。

 ◇delphi中子程序的参数有两中传递方式:按数值传递和按地址传递。按数值传递就是把主调函数传入的值(即实参)拷贝一份给子程序。这样子程序对这个拷贝进行修改的时候不会影响实参。按地址传送,是把实参所在的内存的地址传给子程序,子程序通过地址对数据进行的修改实际上就是在修改实参的值。

 ◇缺省的方式按值传递。

 ◇要求传地址时,使用var修饰形参。

 ◇有两个按地址传送的变种,限定了数值的传送方向。const 修饰的参数按地址传送,但在子程序中不能修改。out修饰的参数表明只是用于传送返回值的。

【例】 使用开放数组求和,通过参数返回和

   procedure MySum(const a: array of integer; b: integer);

var i: integer;

    begin  b := 0;

      for i:=low(a) to high(a) do  b := b + a[i];

    end;

◇注意,这里的const并不是说要传入常量参数,而是说传入的数据不能被修改。

  4 指针

  ◇指针就是一个数值。这个数值是内存的地址标号。在该地址中存着某种类型的量。

  定义:type 类型名=^类型名;

例子:type MP=^integer; //定义了指向整型量的指针。

    使用:var p: MP; a: integer; ...  p := @a; p^=5;

    @是取得变量地址的操作。^在前定义指针;^在后取某个指针所指向的值。

    运算:Inc(p);指针移动,使它指向下一个数据元素;Dec(p);指针向前移动。

    ◇指针具有灵活性,常配合数组一起使用。

    5  过程类型

    一般来说,在编译后,子程序的地址都是一个固定的数字。但有时候我们需要浮动地调用同一类子程序中的某一个,这就需要一个指向子程序所在地址而不是变量所在地址的指针。在delphi中,把这种类型的变量独立出来,叫做过程类型。

    定义: type 标识符 = function(参数):返回; 或

           type标识符 = procedure(参数)   //注意,这种定义,没有写子程序的名字

    更常用的是,指向类的方法的指针。起定义类似:

           type标识符 = procedure(参数) of object

    【例】用程序的方法生成一个按钮,把这个按钮的点击事件连接到自己预先定义的成员方法中。

    ◇方法指针不一定要指向自己定义的类中的方法。这种设计使得我们可以不生成新的类,就改变对象对事件的响应行为。

二、熟练使用代码编辑器

    代码编辑器为我们提供了许多方便的功能,能够大大地提高我们的编程效率。许多功能都对应着快键的操作,通过熟练地使用键盘,可以事半功倍地完成许多常见的代码编辑任务。

    1  追踪一个标识符的定义。鼠标右键|find declaration

    2  在当前工作的地方快速地设置或取消书签。 Ctrl + Shift + 数字

       移动到某个书签。  Ctrl + 数字

    3  选中某一个块。   shift + 方向键

    4  把一个块向前移动或向后缩进。Ctrl + K,I;  Ctrl + K,U

    5  在函数的定义和实现间进行跳转。 Ctrl + Shift + 上下箭头

    6  从摸板中粘帖代码。 Ctrl + J

    7  向摸板中增加代码。 tools|editor options|code insight|add

    8  代码补全功能。我们定义好了类的声明部分,如果语法正确,可以delphi替我们把实现部分的代码框架写好。  Ctrl + Shift + C

    9  查看活动窗口列表。  Alt + 0

    10  快速弹出对象监视器。 F11

    11  在代码和窗体间切换。  F12

    12  删除当前行。  Ctrl + Y

    13  立即存盘。   Ctrl + S

    14  程序复位。 Ctrl + F2

    15  运行程序。  F9

    16  恢复一步操作。  Ctrl + Z

 

三、TStrings对象

    TStrnigs代表着字符串的列表,在delphi中有着十分广泛的用途。

    1  TStrings是抽象类型的对象,不能生成实例。

    2  我们经常用到的该类的派生是TMemo.lines属性。我们通过对Tmemo.lines的操作,就可以改变memo中的内容。

    【例】用一个时钟触发事件,向memo1中添加数据。当memo1中字符串数量很多时,把第一行删除,如此往复。

    memo1.lines.add(timeToStr(now));

    if memo1.lines.count > 10 then memo1.lines.delete(0);

    ◇我们把第一行删除后,总的行数就减少了,并且其后的每一行的行号都发生了变化。

3  TStrings类的其它的常用方法。

.clear  //清除字符串列表

.insert(位置,字符串)  //在某位置插入一个串

    .loadFromFile(文件名)  //从文件中读入

    .saveToFile(文件名)  //向文件写出

    【例】TMemo实现的简单的文本文件的编辑功能。

    4  重要属性

    .count   //所包含的字符串的数目

    .values[name]  //返回具有该name value

    ◇我们在写配置文件的时候,常常用到一种叫做键-值结构的表达方法,就是name=value的形式。例如:path=c:/gyhang/t1  sendname=PA

    ◇相关联的方法有:indexofName(name) 返回该‘键’所在行号。

    5  常用的不可见的字符串列表类型 TStringList

    ◇自定义的对象使用前一定要先创建该对象。使用后把它释放。

    TStringList除了具有TStrings属性外,还能自动排序。

    【练习】使用TStringList制作小配置文件,读出一个键的值,改变它,并存盘

   

四、异常和保护

    传统的程序员经常是花30%的精力来完成业务流程,却要花70%的精力去防止用户的误操作或处理其它的意外情况。delphi提供了异常和保护的方法,使程序员可以从更加概括的角度用更加灵活的手段来处理错误和保护系统的资源。

    1  异常的使用

    try  正常操作 except 错误处理 end;   // try except 是关键字,与end一起构成了一个结构,这个结构中两个块,就是try块和except块。在try块中发生的任何异常都会在except块中被捕获。

    except块中,可以对不同的异常种类去执行不同的操作

    ◇在被捕获后,异常就消失了。如果捕获块认为自己没有完全处理好这个异常,需要外层继续处理,可以再次提起异常。方法是: except ... raise; end;

    ◇异常与if判断不同,它可以穿越所有的函数嵌套层次,直到被捕获为止。如果我们不定义捕获块,则delphi在最外层提供的缺剩捕获块会捕获它。这最后一关是:Application.onException,我们可以提供自己的方法来代替系统缺省动作,从而避免弹出难看的出错对话框。

    ◇我们可以定义自己的异常类(从Exception派生),并可以在没有发生系统异常的情况下,主动提出异常。这时,可以把自己的异常对象发送出去。在自己的异常对象中可以携带大量的数据,这就比判断简单的返回值有更多的优越性。提出异常的语法是: raise 某个异常对象;

    ◇我们也可以使用哑异常,来终止一次执行过程,并不显示任何错误信息。

    【比较】abort exit break terminate

    2  保护的使用

    申请资源;try 正常操作 finally 释放资源 end

    无论在try块中发生了什么事情,finally块中的代码都会被执行的。这避免使程序中忘记了释放资源,或多次释放同一资源。

    ◇异常和保护经常被嵌套使用。

    lst := TStringList.create;

    try

      try  lst.loadFromFile('c:/a.txt'); ... except showMessage('文件不存在'); end;

    finally  lst.free; end;

    ◇ 也可以把finally块放在里边。这时,如发生异常,会先执行了finally块,再跳到异常处理块。一般也提倡这种方法,就是,如果发生错误,尽量早地释放资源,以免夜长梦多。

try

lst := TStringList.create;

    try  lst.loadFromFile('c:/a.txt');  其它操作;

    finally  lst.free; end;

except showMessage('文件不存在'); end;

 

第四讲  数据库编程(一)

一、基础补充

    1  开域语句

    在面向对象的程序代码中,嵌套对象的现象十分普遍,我们经常会去引用很深层的对象。如:

form1.canvas.font。当对一个深层对象设多个属性的时候,要写很多重复的符号,麻烦且容易出错。为此,delphi设计了开域语句。

    格式:  width 对象 do ...  一般情况下都是跟随多条语句,所有几乎总是用复合语句,即

            width 对象 do begin ...  end

    例如:  with form1.canvas.font do

            begin  color := clRed; size := 15; name:='宋体' end;

    2  集合的使用

    delphi中的集合是对数学中集合的概念的简单实现。要求是集合中的元素必须同类型,且必须是序数类型,且集合中可能的元素个数不能大于255

    定义: type 集合类型名 = set of 元素类型

    例如: type MySet = set of char; //注意 char 的个数本来就不超过255

           type MyItem = (sun, moon, star); MyItem2 = 1..100;

           type MySet2 = set of MyItme; MySet3 = set of MyItem2;

    ◇我们可以测试给定的元素是否在某个集合中。  var A: MySet;  'a' in A ?

    ◇集合的常量表达法: ['a','x','c']  [1,2,4..10] [sun, moon]

    ◇空集的表示法: []

    ◇集合间可以直接赋值。 A := [];  A := [1,2,6];  A := B;

    ◇集合间可以进行标准的运算:  A+B 并集; A-B 差集; A*B 交集

    我们经常使用集合来表达用户对多选控件的选择情况。因为delphi定义了标准的集合运算,就使得我们处理类似:“两个用户公共选择了哪些项?”“从选择中去掉固定的一组选则”这样问题变得十分简单容易。delphi的控件属性中,有很多的是集合类型的例子。比如

    with form1.canvas.font do style := style + [fsItalic] 就是增加了斜体特征。

 

二、数据库的知识

    1  数据库是按照一定的结构组织起来的数据。组织的目的是为了方便操作。操作包括查找,增添,修改,删除等。不同的组织方法形成了不同的数据库模型。流行最广的是关系模型。

    2  关系模型中,数据库由二维表构成。它的行叫记录。列叫域或字段。关系模型的数学基础是集合论。严格地说,表是记录的集合。也就是说,不能有每个字段都相同的记录;仅仅是记录排列的位置不同的表被看做是相同的表。

    3  常听说的E-R模型是实体-联系模型的缩写。它是关系型数据库设计的理想状态。就是说:一张表,要么表达的是实体,要么表达的是联系。我们在创建表的时候就首先要明确这个表的E-R属性。实体必须的要素是标识和属性。标识能把不同的实体分开,叫主键。联系的要素是被关联的实体和联系的属性。被关联的实体的标识联合决定了唯一的联系,可做主键。每一个实体标识代表了外部的一个实体,叫外键。

    【比较】表的设计的例子。学生,课程,成绩。典型的实体关系模型。

     药品出库表:药物号,药物名,单价,数量,出库量,库存剩余,出库日期,经办人,用途,发货单号,备注。是错误的方案。可以用词性分析的方法初步分析出实体和联系。一般名词表示实体,动词表示联系。 药品出库:o1 + v + o2。‘药品’和‘仓库’是两个实体,‘出’是联系。

    4  完整性规则。实体完整性,参照完整性。

    5  桌面库与大型库的区别。

    ◇区别的标志是DBMS在哪台机器上执行,而不是操作台离数据库文件的远近。

    DBMS是管理数据关系的系统。

    C/S模型的优势是:减小了网络负担;支持并发访问。

    ◇发展方向是B/S模型。就是浏览器做客户端的方式。

三、delphi的数据库访问原理

    1  BDEborland公司提供的数据库引擎。目的是为了实现不同数据库的统一访问方式。BDE中内置了本地的DBMS,支持dbase,paradox,foxpro的直接访问。

    如图,BDE可以通过ODBC接口访问几乎所有数据库。另外,borland公司也提供了对大型数据库比如oracle,sybase,db2,sql-server等的SQL-Link连接访问方式。这中连接方式比ODBC速度快。

    ◇使用SQL-Link仍然需要安装大型库的客户端程序。

    ADO是微软继ODBC之后推出的新的数据库访问标准,其技术基础是OLE DB,支持更丰富的数据类型,访问速度更快,尤其是考虑了internet应用的需要。使用ADO连接不需要通过BDE,delphi提供了一组ADO控件,它们的功能和用法与对应的BDE控件很相似。

 

应用程序

BDE别名

BDE引擎

ADO

 

 

 

 

 

 

SQL-Link

interbase,oracle

sybase,sql-server等大型库

各种支持

ADO的数据库

本地库

dbase,paradox...

ODBC

Access,foxpro,

oracle,sybase,

sql-server db2..

 

 

 

 

 

 

 

 

 


【例】连接一个已经存在的dbase表。在设计阶段需要修改控件的若干属性,完成连接工作。

 

 

所在页

控件类

属性

说明

BDE

TTable

  代表了数据库中的一张表。是对数据库进行操作的核心控件

 

TTable

dbaseName

c:/mydbase/ttt

指定dbase表所在的目录

 

TTable

TableName

student.dbf

在该目录中选择一个表名

DataAccess

TDataSource

  作为数据表与可视化控件间的连接控件

 

TDataSource

dataSet

table1

把数据源连接在数据表上

Data Controls

TDBGrid

  用于以网格的方式显示数据

 

TDBgrid

dataSource

dataSource1

把网格控件连接到数据源

 

属性设置结束后,可以在设计阶段把Table1Active属性设为True,则表格中显示数据,也可以通过一个按钮的触发事件,写:Table1.active:=true Table1.open来完成。

 

B

D

E

table1

table2

dataSource1

dataSource21

dbgrid1

dbgrid2

其它可视控件

 

 

 

 

 

 

 


    当数据表被激活以后,我们就可以在网格中浏览数据,并且可以修改数据。有的时候,不希望用户改变数据。怎么实现只读设置呢?上图启发我们有三种方法。①table1.readOnly属性 为true

dataSource1.autoEdit属性为false dbgrid1.options中的dgEditing:=false

    ◇凡是我们在设计阶段所做的选择,都可以用程序的方法实现,而且我们也鼓励这样做。因为我们读别人的程序的时候,从一大堆属性中要区分哪些是系统默认的,哪些是后来设定的是一件十分耗时且枯燥的工作。

    ◇如果我们的工作并不需要显示数据,则只要一个TTable控件就可以对表进行操作了。

    【例】通过TTable控件创建dbase数据表。

     table1.databaseName := 'c:/mydabse/tt';  table1.tableName := 'test.dbf';

     with table1.fieldDefs.addFieldDef do

begin name:='K'; dataType := ftString; size := 10; end;

     with table1.fieldDefs.addFieldDef do

begin name:='V'; dataType := ftInteger; end;

     table1.createTable;

    ◇我们甚至可以不同窗体上的table1控件,可以直接创建一个TTable对象,使用后,把它释放。var t: TTable ... t := TTable.creaet(self)try ... finally t.free end

 

TTable是从TDataSet(数据集)类继承的。有许多对数据库操作的控件类都是从TDataSet继承的。TDataset从概括的高度,定义了对记录的导航,增,删,改等操作,它是数据操纵的核心。

四、核心对象TDataSet

    TDataSet代表一张二维表。表是记录的集合。记录是字段的数组。

    ◇只是记录的顺序不同的表被看做相同的表。字段的定位可以使用数组下标,而记录的定位是通过主键来实现的。

    怎么样访问表中某个记录的某个字段呢?指定行和列的号码么?不能!TDataSet提供了对一条记录的访问,它是记录的数组,就是:TFields对象。TFields好比是一个小窗口,当我们要访问某个数据时,先要把记录移动到相应的位置,再通过这个窗口来访问各个字段。

 

【例】把表中所有的V>100K值写到memo1中。

table1.dababaseName := 'c:/mydbase/tt';  table1.tableName := 'test.dbf';

table1.open;  table1.first;

while not table1.eof do

begin if table1.fieldByName('V').asInteger>100 then

memo1.lines.add(table1.fields[0].asStrig);  table1.next;

end;

    ◇数据集控制当前记录移动的方法:first,last,next,prior,moveby(相对偏移)

    ◇字段可用下标引用。也可以通过名字引用,通过名字是良好的书写风格。

    ◇除了用eof判断结尾的方法,也可以用recordCount总记录数来控制循环次数。

    ◇为了防止在移动记录时,视图跟随而耽误时间,可以暂时把视图断开,操作结束后在把视图连接起来。 table1.disableControls; ...  table1.enableControls;

    【例】修改当前记录的V值为edit1中指定的数

 table1.edit;   table1.fieldByName('V').asInteger := strToInt(edit1.text); table1.post;

TDataSet有多种状态,dsBrowse(浏览)  dsEdit(编辑)  dsInactive(未打开)等。

    ◇修改记录前必须把数据集状态设定为edit状态。这时,TFields小窗口指向了要改动的记录。结束edit状态有很多方法。①调用post方法把数据写到数据库  ②调用cancel方法,放弃修改,数据恢复到原来的值 ③移动记录,这时相当与自动调用了post方法。edit状态结束后,数据集恢复到浏览状态。

    TDataSet执行的很多任务都是通过TFields完成,一般的方法就是先把数据集设为某种模式,然后修改TFields值,作为操作的参数。再调用相应的方法完成操作。比如append方法用于添加数据,写法与edit极为类似。删除数据相对简单,只要调用delete就会删除当前的记录。

    查找是数据库操作中十分常见的任务。TdataSet提供了两种查找的方法。locate方法可以没有索引,setkey的方法则需要先建立并使用索引。

    【例】使用locate方法进行查找

     table1.locate('k',['abcd'],[loCaeInsensitive]);   //查找单个字段

     table1.locate('k;v',varArrayof(['abcd',125]),[]);  //查找多个字段

    【例】用setkey方法进行查找

    先建立索引:table1.close; table1.Addindex('IDX_1', 'K', [ixUnique]);

    table1.indexName := 'IDX_1'; table1.open;

    table1.setKey;  table1.fieldByName('K').asString := 'abcd';

    table1.gotokey;

    ◇索引一经建立,就一直存在,直到被删除;对同一表,可建立多个索引;每一个索引可以指定多个字段;索引不起作用,直到填写了表的索引属性;一个表同时只能打开一个索引。

    setkey方法是数据集处于设置查找值状态,这时的TFields小窗口用来接受给定的被查数值。

    ◇调用gotokey方法会结束查找状态,把光标停留在匹配的记录上,数据集恢复到浏览模式。

    ◇只能查找索引中包含的字段。

    【思考】应当养成一个习惯,就是每进行一步操作的时候,都想一想,怎样回到原来的状态。比如,知道了建立表和索引,很自然地会想到怎样删除表,删除索引。

    可以调用方法: deleteTable;  deleteIndex来实现。

五、TField对象

    这是一个抽象的数据类型,不能直接实例化。它代表字段。所有的字段类型都从它派生。如,TStringField, TFloatField。我们来看看它的常用属性。

    【例】改变列标题,列宽度,小数的显示格式

    table1.fields[0].displayLabel := '主键';

    table1.fields[0].displayWidth := 20;

    (table1.fields[1] as TFloatField).displayFormat := '###.00';

    displayLabel是要显示的字段的标题。diaplayText是要显示的字段的内容,勿混淆。

    ◇只有TNumericField类型的字段才有displayformat一说,故需特化处理。

    【例】显示一个表的所有字段的名字。

    for i:=0 to table1.fieldCount-1 do    //fieldCount表示一共有多少字段

        memo1.lines.add(table1.fields[i].FieldName);

 

第五讲  数据库编程(二)

一、基础补充

    1  文件类型

    我们使用TStringList虽然能完成简单的文本文件操作,但很不灵活,比如文件很大,比如我们只想读文件前两行,并不需要所有的数据。这就需要使用文件类型。

    【例】打开文本文件,读前两行到memo中。

    var f: TextFile; s:string; ... AssignFile(f,'c:/a.txt'); reset(f);

    readln(s);  memo1.lines.add(s);  readln(s); memo1.lines.add(s);

    closefile(f);

    TextFiledelphi中预定义的文件类型。

    ◇写到文件中,与上面的方法类似。只不过是 reset改为rewrite,readln改为writeln

    AssignFile只是把文件变量和某个名字联系起来,如果文件不存在,并不出错。

    2  二进制文件的读写。

    二进制文件的操作有很多方法,比如使用delphi提供文件管理函数;使用windows提供的API函数。有一种与操作系统无关的方法,就是使用流的方法,即TFileSteam类。

    TFileStream是从TStream继承的。TStream是抽象的基类,表示着公共的流的特征。

    流是为了在线性地址编码的设备中存储对象而设计的。当对象的数据模型被确定以后,就需要一种表示方法,把这种结构在线性的地址空间中表达出来,比如内存,比如硬盘。这个过程叫流化,串行化或序列化。一个不针对任何介质的抽象流类应具备的特征是:总长度;提供线性地址中的读写能力。因而应该有当前位置指针;读方法和写方法。

    TStream的设计提供这些属性和方法。size:总长度。 position:当前指针位置。read,write

    TFileStream代表文件流。允许我们向文件的指定位置写入或读出对象。这些对象包括简单的类型,如整数,字符等,也包括用户定义的复杂的记录或自定义对象。

    TFileStream对象也象其他任何对象一样,在使用前要申请,在使用后要释放。

    【例】创建二进制数据,向其中写如一个整数,一个字符串,一个表示学生的记录。

    var  t:  TFileStream;  ... t := TFileStream.create('c:/a.dat', ...);

    try  t.write(12,4); t.write(buf, sizeof(s)+1);  t.write(stu1, sizeof(STU));

    finally  t.free;  end;

    string类型是长串类型,在堆中分配内存。不能直接写到文件中,要转换成字符数组。

    C语言串与pascal格式串的区别。为什么在sizeof()后边要加1

    ◇使用TStream对象的时候,并不需要打开文件和关闭文件。create的时候,可以选择许多参数,来决定以什么方式打开一个文件。只读?只写?如果存在怎么办?与其他人共享吗?

       

使用TTable控件虽然能对表进行自由地操作,但当需要从多个表中取数据的时候,就显得无能为力了。delphi提供了TQuery控件,封装了应用十分广泛的SQL查询方法。

二、SQL介绍

    1  SQL是结构化查询的英文缩写。SQL方法几乎被所有的数据库所支持,尤其是大型数据库。SQL是函数式的语言,它的特点是:只提出要求,不描述执行的具体过程。

    回忆 foxpro的方法,use xx go top  skip 1 ...怎么用索引  怎么找  相当复杂。

    2  SQL的数学基础是集合论。它实际上就是关系运算的描述。目前的国际标准是SQL92SQL99

将是新的标准。SQL虽然叫结构化查询语言,但实际上它可以执行许多对数据库的操作,不仅仅是查询功能。当然查询是它的主要功能,也是内容最丰富的部分。

    3  SQL提供了对不同数据库的统一访问方法。使数据库应用程序在不同的数据库之间进行移植变得容易。大型库一般都在SQL92的基础上进行了扩充。如果对可移植性要求很高,则尽量使用SQL92的标准。少用存贮过程和触发器。

    3  SQL的简明语法:

    DDL (Data Defination Language)  数据定义语言

    create table 表名(字段名 类型,字段名 类型,... //创建表

    例:create table "stu.dbf"(s_name char(10), s_age numeric(10,2), s_addr char(40))

    ◇当表名包含SQL语法关键字时,要用引号引起来。

    SQL语句可以写在一行,也可以分成多行。

    drop table 表名  //删除表

    alter table 表名 add 字段名 类型,...  //修改表

    alter table 表名 drop 字段名,...

    例:alter table "stu.dbf" add s_class char(5)

    create index 索引名 on 表名(字段1...)   //创建索引

    例:create index sid on "stu.dbf"(s_name)

    drop index 表名.索引名

    DML (Data Manipulation Language)  数据操纵语言

    insert into 表名(字段1,字段2...) values (1,值2...)   //添加数据

    例:insert into "stu.dbf"(s_name, s_age, s_addr) values('wang',12,'bei jing')

    update 表名 set 字段名=值,... where 条件   //修改记录

    例:update "stu.dbf" set s_age=15, s_addr='tian jin' where s_name = 'wang'

    delete from 表名 where 条件  //删除记录

    select 字段名或表达式1... from 表名1 [as 别名1], ... [where 条件]

[order by 字段1...] [group by 字段 ...]  //查询语句

例:①select * from "stu.dbf"

    select s_name s_age from "stu.dbf"

where s_age > 20 and s_addr like "beijing%"

        order by s_name

    select A.s_name B.score from "stu.dbf" as A, "score.dbf" as B

      where A.s_name = B.s_name

    select count(*), sum(score) from "score.dbf" group by s_name

like表示模糊匹配,只要符合串中提供的样式即认为匹配。

◇从多个表中查询的时候,实际上是先执行表的笛卡尔积,然后进行行选和列选。

SQL92只支持很少的运算函数:sum求和, avg求平均,max最大值,min最小值,count求记录的个数。大型数据库中,扩展了很多自己的函数,尤其是对字符串和日期类型的操作,需要时参考相应数据库的有关文档。

    group by主要是为了分组进行某种统计

 

二、使用TQuery对象

    TTable相同,TQuery也是从TDataSet继承来的,因而在其中可以使用TDataSet提供的导航和数据操纵方法。与TTable不同的是,TQuery不是去指定一个表名,而是代表了通过SQL语句返回的数据集,可以把它看成一张虚拟的表。此外,TQuery还可以用于去执行不返回结果集的操作命令,就是除了select 以外的SQL语句。

【例】使用TQuery创建学生表,设定索引,并添加记录。

三、关于多用户访问

    1  大型库中都提供了事务的方法,来保证对数据操作的完整性。对一次操作分两个阶段提交给服务器。先提交操作的内容,然后只用一个指令,表明上面的操作是确认,还是回滚。

2  访问同一数据造成的冲突。

    准备修改数据项A的过程中,A已经被其他用户所改变。这样造成了对先前修改的覆盖。

    类似地,要修改的数据项A可能已经被其他用户删除。

    ③读取和修改某用户正在修改,但尚未确认的数据,可能被该用户回滚所淹没。

    3  大型库中都提供了叫做隔离级别的机制,来控制怎样对待共享的数据。delphi中可以设置为dirtyRead, readCommittedrepeatableRead。许多数据库并不支持所有这些隔离级别。

    4  为控制多用户的访问,锁的方法被广泛使用。从范围上看,可分为表锁,页锁和记录锁。从兼容关系看,可分为读锁(兼容锁)和写锁(排斥锁)。

    5  如果有多个用户需要访问多个表,就有可能产生锁的循环等待,叫死锁。很多数据库设计死锁检测的功能。在用户端解决死锁的有效方法是:①需要访问多个表的时候,按照字母顺序进行访问。②当对同一个表既读又写的时候,先实施写的操作。

    ◇怎么写?在没读的时候不知道写什么。可以进行“假写”:update xxx set f1=f1 where ...目的只是为了先获得写锁。

    6  delphi中自动游标的使用有潜在的危险,缺省方式会把未读的记录全部锁住。解决的办法是:①调用TDataSetfetchAll方法,把所有的记录取回到本地。②当数据量十分大的时候,一般大型库都提供了额外的SQL语句,可以控制结果集返回的最大记录数。③也可以启用脏读方式,需要注意的是,许多库并不真正支持脏读模式。④使用缓冲模式,其效果相当于使用fetchAll

    7  怎样实现数据变更时通知?一般的方法是主动查询。为避免对多表的频繁查询,可以在关心的表产生变化时,通过编写触发器在一个监视表中记录变化及时间戳。

四、DBGrid的定制

    1  设定DBGridoptions属性,可以限定很多界面效果。常用的:

       dgEditing  是否可以通过DBGrid修改数据集。

       dgColumnResize  用户是否可以用鼠标拖动列来改变宽度。

       dgConfirmDelete 在删除数据的时候,是否要弹出一个确认对话框。

    2  如果使用永久字段,可以在设计时编辑许多显示属性。方法是双击DBGrid|Add All Fields把所有的列都选入。然后选择某一列,在对象浏览器中改变它的属性。常用的有:

        color   设定列的显示底色      readOnly  是否为只读列

        title.caption 标题内容        title.color, title.font.color  标题底色,字色

        visible  是否可见(要隐藏吗)

width  显示宽度,单位是象素,注意与TField.displayWidth的区别。

pickList  TStrings类型对象。可以构造一个列表,运行时能通过下拉列表提供数据。

    ◇所有设计阶段的设置都可以通过写程序的方法,在运行阶段实现。

    【例】通过程序的方法设定列的颜色,标题,宽度,并提供下拉列表。

    3  我们以上看到的是改变某一列的所有数据的显示特征。如果要求根据记录的值来确定显示特征,就无法使用上边的方法了。比如使用不同的颜色来显示数据。我们可以通过为DBGrid的事件的

onDrawColumnCell编写代码来控制输出的内容。

【例】定制DBGrid使大于20的数值用红色显示。

    ◇为了实现自定义输出,一定要先设定DBGriddefaultDrawing属性为false

    ◇通常情况下,总是要调用原来的输出方法:DefaultDrawColumnCell。该方法的参数与事件对应的方法只有微小区别,就是它不含sender参数。

五、截住TField的事件,实现动态控制

    如果有这样的要求:把数字列表示成汉语的大写数字,该如何处理?当然可以通过写onDrawColumnCell事件来实现。但当这种显示方式被用在多个控件显示同一数据的时候,最好能让TField提供用于显示的文本。这可以通过拦截TFieldonGetText事件来实现。

    【例】为onGetText写代码,把V值输出为大写表示。

     TForm的定义中添加如下过程: procedure myGet(Sender: TField; var Text: String; DisplayText: Boolean),并实现它:

    if sender.asInteger = 1 then text := ''

    if sender.asInteger = 2 then text := '' .......

    然后,通过一个事件,把myGet连接到TFieldonGetText事件上(事件其实就是函数指针)

    table1.fileds[2].onGetText := myGet

    onGetText在显示控件每次需要数值的时候被调用。布尔量表示当前是不是处在编辑的状态。

 

第六讲  数据库编程(三)

一、基础补充

    1  变体类型 variant

    变体类型就是其数据类型在运行期间允许发生变化的类型。

    定义: var  v: variant;  //定义后其值为:unassigned

    unassignedNULL是有区别的。NULL表示该值未知,unassigned表示未赋值。

    variant类型在赋值和运算的时候,系统自动实施类型转换。

    【例】赋值和判断类型

    2  变体数组 可以包含不同的数据类型元素的数组。

    定义:var a: variant;  a := VarArrayCreate([0,9],varVariant);

          a := VarArrayof([1,'myname',12.4]);

    ◇数组类型仍然是variant

    ◇可变数组允许它的元素是不同的类型。因为实际上它们都被转换成统一的variant类型。

二、使用别名

    1  使用别名的好处是方便数据库的移植。

    2  别名是一个名字,一个字符串。它代表了一组与数据库连接有关的设置。比如驱动程序名,数据库名,连接参数等等。

    3  可通过delphi附带的配置工具来配置别名。配置的目的就是生成一个配置文件。

    4  ODBC数据源中添加的ODBC配置会自动反映在BDE别名配置中

    【例】配置一个dbase表的别名,并通过它打开表。

     开始|程序|borland6|BDE Administrator 启动配置程序。选databases页,在databases上鼠标右键|new... driver name中选STANDARD,按OK。在左边起一个名字作为别名,在右边,default driver dbasepath中填写希望dbase表所在的目录。存盘,退出。

    ◇一般来说,别名代表一个数据库。在dbase系统中,数据库就是一个目录;对Access,数据库则是一个独立的文件,所有的表都存在这个文件中;对有些大型库,数据库表现为硬盘分区,无法直接看到;甚至有的数据库是按自己定义的方式存在于整个硬盘上。

三、连接多种类型的数据库

    1  通过ODBC连接Access

    在控制面板中找到“数据源(ODBC)”,双击打开;选系统DSN页;选添加;选microsoft Access driver (*.mdb);按完成;给数据源起个名字,就是要生成的别名;选“选择”或“创建”,来指定一个Access数据库;按确定。

◇在ODBC中配置的数据源会自动地作为别名添加在BDE Administrator中。

◇有时,在delphi程序打开的时候,刚刚配置的ODBC无法立刻反映在控件中,只要先关闭,在重新启动delphi即可看到新添的别名。

    2  通过ODBC连接SQL-Server

    基本步骤与连接Access相同。只是driver要选SQL Server,服务器如在本机可以选(local),登陆方式可以是选NT登陆(不需要用户名和密码)或用户登陆(必须知道用户名和密码),下一步,选中“更改默认数据库”,通过下拉列表选一个数据库,以下都确认即可。

    ◇有时,在服务器下拉列表中没有反映(沙漏光标),可以直接手填服务器的名字(单击任务栏上的服务器图标会看到本机服务器的名字)。

    ◇建议在最后确认前,使用“测试数据源”检查一下是否配置正确。

    ◇有时,使用用户登陆方式,密码和用户名都正确,但就是连接不上。这可能是因为在SQL Server中设置了只允许NT登陆方式造成的。改变的方法是:启动SQL Server企业管理器,选要操作的服务器,鼠标右键|属性,选Security页,把Authentication项选在“Sql server and windows”上。

    3  通过ADO连接Access

    ADO页上,把ADOConnection控件放在Form上,双击打开|build,选microsoft Jet 4.0 OLE DB ...,下一步,选数据库名字,连接测试,确认。放ADOTableForm上,connection属性指向ADOConnection,选择表名等。其他操作与Table类似。

    ADOConnection的配置过程实质上是形成一个串,赋给它的connectionString属性。

    ◇如果不希望每次都弹出一个密码对话框,可以在配置过程中指定密码,然后把它的loginPromt属性改为false

    ◇实际上,我们完全可以不通过配置过程,直接在connectString中填写适当内容来完成上述的工作。也可以把配置存在一个文件里,启动时程序时从那个文件中读入。

    4  通过SQL-Link连接SQL-Server

    SQL-Linkborland公司提供的连接大型数据库的方法。这种方法比使用ODBC要快。

    启动BDE Administrator,新建一个别名,选MSSQL,确认。左边起名字;右边需要修改三项:databaseNameSql server 上已经存在的数据库的名字;host name:服务器所在的主机的名字(如果是本机,此项可空);server name:服务器的名字。

    ◇虽然使用SQL-Link,在本机上仍然要安装大型库的客护端软件。

    ◇如果要屏蔽密码输入框,需要在form上放TDataBase控件。把它的loginPrompt设为false,填写它的params属性:user name=XXXX, password=XXXX

四、使用TDataBase

    1  TDatabase代表了一个数据库。

    多个TTable, TQuery等代表数据集的控件可以指向数据库控件。我们在没有使用TDatabase控件的时候也完成了数据库的操作是因为,系统替我们使用了一个默认的TDatabase对象。

    ◇注意,指向TDatabase的方法与在数据敏感控件中使用的方法不一样。先为TDatabase控件选择一个别名,再为它的DatabaseName起一个名字。其他数据集控件中的DataName一项要填写刚刚起的那个DatabaseName名字,而不是TDatabase控件的名字(一般为database1)。

    2  可以使用它自动传递用户名和密码,而不弹出登录提示框。

    database1.loginPrompt:=false

    database1.params.clear

    database1.params.add('user name=sa')

    database1.params.add('password=abcd')

    ◇注意user name的拼写,中间有一个空格。名字和密码都不使用引号。

    3  通过TDataBase使用显式定义的事务功能。

    database1.StartTransaction

    try  进行多次数据库的操作; database1.Commit

    except  database1.Rollbackend

    ◇在没有显式地使用事务操作的时候,相当于每一个SQL语句是一个独立的事务。

    ◇一般来说,一定要使用try..except..end的结构来防止一个事务开始后,没有后续的动作。很多大型库并不会自动地取消一个事务,即便它在一周前就开始了。悬浮的事务会一直锁住某些表,使其他用户的操作无法进行。

    4  通过它,可以在不打开表的情况下,取得库中所有的表名,以及指定表的所有字段的名字。

    database1.GetTableNames(memo1.lines)

    这一命令会把当前库中的所有表的名字写在一个memo中。

    database1.GetFieldNames用来取得指定的表中的所有字段名,用法与上边类似。

    5  通过它,可以直接执行SQL语句。

    database1.Execute('update stu set name="aaa" where ...')

五、再谈多表联合查询

    1  多表联合的实质是集合的笛卡儿积运算。

    2  随着联合的表的数目的增加,积的记录数目呈指数式增长。虽然大型数据库在这方面作了大量的优化工作,多表联合仍然十分耗费服务器的资源(时间和空间)。

    3  “代码-描述名”的定义方式有很多优点,因而是数据库设计时广泛采用的方案。但在查询的时候,不可避免地用到多表联合。在多表查询的时候,只要其中一个表的记录数目很大,很可能会影响查询的性能。

    4   解决的方法:①当发现查询性能下降的时候,就要建立临时表,通过多步进行查询。仅仅通过建立视图的方法不能解决性能的问题。因为视图不是真的表,数据库中只保存了视图定义。

②当“代码-描述”表较小的时候,用多表联合十分不和算,我们可以把这些表先读到本地,再通过动态的翻译的方法实现最终的显示效果。

    【例】使用动态翻译的例子。对性别和班级进行动态翻译。

     实际上就是前边onGetText技巧的翻版。我们先打开代码对照表,读到本地一个TStringList中,然后在myGet过程里,通过查找TStringList来动态地提供text的值。

     5  多表联合是数学基础是集合论,它的功能十分强大。实际上,只要是表中存在的数据,无论要求怎样的输出结果,总能通过SQL语句来实现。有的时候,我们需要用到一个表和它自己的联合来解决一些特殊的问题。

    【例】求出一个表中某列数据有重复值的所有项(下表为横向排列)

    K 1  2  3  4  5  6 8 9  34

    V 2  2  3  5  6  6 7 9  10

    结果应为: k 1 2 5 6

    分析这类问题的方法是先写出笛卡儿积的所有行,再选择需要的行,看它们有什么特征。

    再看类似的例子:

    【例】求上面表中K值不连续的项(只找向上不连续即可)

     结果: K 1 8 34

六、报表的使用

    1  使用QuickReport粗略地打印报表

    QuickReport并非borland公司的产品。其设计很粗糙,功能很难适应中国报表。

    先在form中放QuickRep控件,代表一张纸。放入若干Band代表数据区。可以改变bandtype来指定band的类型。要与数据相连需放入数据集控件,把QuickRepdataset属性指向该控件。

可以用鼠标右键|preview来预览报表。

    ◇表格线需要放图形控件实现。band的边框线控制不准确。

    ◇用鼠标拖动的方式几乎很难完成对齐工作。需要直接调节属性,或在程序中设置。

    2  使用F1Book控件完成复杂报表输出

    F1book功能十分强大,几乎就是电子表格的翻版。如果用过Excel则极易上手。

    ActiveX页中找到F1book,放在form上。鼠标右键|workbook Designer 可以可视化地设计表格的属性(和Excel的功能很类似)。

    单元格的表示法:A1表示一个单元,A1:C1表示一行单元格,A1:C10表示一片单元格。

    当前的活动单元:.col  .row

    当输入字符串的时候,自动左对齐;当输入数字的时候,自动右对齐。怎样输入一个数字,并把它看作是字符串呢?输入时,用'123就可以了。

    可以指定某个单元为计算值。方法是:=sum(A1:C10)以等号开始一个函数

    所有在设计阶段的功能都能用代码的方式完成。

    F1book提供数百个方法可共调用,滋举其要:

    给一个单元格赋字符串值  f.textRC[row,col]   := 'abcd'

    给一个单元格赋数字值    f.numberRC[row,col] := 123

    设置需要打印的区域:    f.printArea := 'A2:F15'

    重新计算所有计算字段:  f.Recalc

    f.printHCenter := true  水平居中   f.printVCenter .. 垂直

    f.printLandscape := true  横向打印

    f.prinLeftMarginf.printfRightMargin  左右边距

    f.setPrintScale(nScale, bFittopage, nVPages, nHPages)

        nScale: 10-400 放大率  100 表示原样大小

        bFitToPage: true 满纸打印(不一定满一张纸,而是可以满一组纸)

        nVPages:  垂直纸页数    nHPages:  水平...

    f.FilePrint(true)  打印输出。显示对话框否?

    ◇缺少打印预览的功能。

    3  完全自己控制打印

    TCanvas对象封装了windows的绘图环境,可以在抽象的设备上进行绘图。

    在打印机上绘图:

        printer是全局对象,代表当前的打印机。 printer.canvasTCanvas对象,代表打印机的绘图环境。

        printer.pageHeightprinter.pageWidth 代表纸高和宽,单位是象素。

    TCanvas 内含了许多有用的对象

      pen: TPen  笔对象,用来画线或其他图形

      brush: TBrush  用来填充颜色

      font: TFont  用来设置字体的特征

      【例】在formcanvas上输出的例子

       canvas.pen.color := clRed;  canvas.pen.width := 2;

       canvas.moveTo(10,10);  canvas.lineTo(100,100);

       canvas.brush.color := RGB(10,20,100);

       canvas.rectangle(20,20,250,100)

    ◇在默认的情况下,canvas输出的计量单位是象素。使用这种方式存在一个问题,就是打印机的分辨率一般比屏幕要高出许多倍,同样的语句,在打印机上可能就变成了“缩微图象”。怎样实现自由缩放?这涉及到windows下的设备映射方式问题。Windows提供多种设备映射方式,缺省的方式是:MM_TEXT,即象素映射方式。

    【例】改变缺省的映射方式,实现设图形的整体放缩。

    setMapMode(canvas.handle,MM_ANISOTROPICD);  //不等轴映射

    setWindowExtex(canvas.handle,1000,1000,nil)

    setViewportExtex(canvas.handle,200,200,nil);

    .... // 绘图过程

    setMapMode(canvas.handle, MM_TEXT); //恢复原来的模式

    canvas是对windows绘图场境的封装,设备场境是易失资源,所以对canvas的设定和操作要在一个紧凑的代码中完成,而不能期望其设置的持久性。

    我们在form中输出时,为了把输出限制在一个矩形区域内,可以使用控件来完成(比如system 页中的TPaintBox),但对打印机的输出却不能使用控件,怎样把输出限定在一个区域中呢?可以利用windows中提供的剪切区的功能来实现。

    【例】使用剪切区控制输出的范围

    var myR: HRgn;  //剪切区对象句柄

    myR := creaetRectRgn(50,50,300,300);  // 创建剪切区

    selectClipRgn(printer.canvas.handle, myR);

    ....  // 绘图操作

    selectClipRgn(printer.canvas.handle, 0);  //使用原来的剪切区

    deleteObject(myR);  // 删除对象

    ◇与绘图有关的很多对象都是系统的敏感资源,我们应当遵循如下规则:

    ①使用的时候才申请,而不是储备待用。

    ②不用的时候立即释放。

 

第七讲  线程和DLL

一、基础补充

    1  常用控件的使用提示

    label中的多行

    label1.caption := 'abcd' + #10#13 + '1234567'

    label的右对齐输出

    label1.Alignment := taRightJustify label1.autosize := false

    memo中设置滚动条

    memo1.scrollbars := ssBoth

    radiogroup的用法

    不能把radio控件放在radioGroup里。radioGroup本身就是“全能”的。

    with radioGroup.items do begin

add("yellow") add("blue")add("green")

add("white")

    end   radioGroup.Columns := 2

    radioGroup.itemIndex表示当前哪个按钮被选中。

    TStringGrid的用法

    mouseToCell(x,y,aCol,aRow);用来把一个x,y坐标转换成单元格的行和列号。

    ◇主菜单和弹出菜单

    两种菜单都可以在设计阶段进行定制。caption中的短横线表示分割线。鼠标右键|new submenu 用来创建自菜单。使用弹出式菜单时,popup(x,y)方法需要的参数是关于屏幕的坐标,而我们一般取得的都是关于客户区的坐标,需要一个转换,使用 clientToScreen可也。

    ◇使用TAction

    我们经常遇到同一个动作有多个操作途径的情况。比如使用菜单,快捷按钮,快捷键都是对应了同一个动作。对这个动作可能有许多种状态,比如:禁止,隐藏等。我们希望只在一处写代码,就会使所有使用的地方都跟随这个特性。这实际上是提醒我们使用一种文档-视图的结构。

    delphi为我们提供了TAction来实现把多个对象连接在一个动作上。

    使用多页

    控件很多的时候,无法在一个form上放置,我们可以使用多页控件,把众多的控件放在不同的页上。

    2  多窗体的关系

    主窗体

    第一个创建的窗体是主窗体。主窗体的关闭导致整个程序的关闭。每个窗体在创建的时候,可以指定一个owner,它的任务是负责在自己释放的时候,把这个窗体也一起释放。

    菜单 project|options|forms|main form选项可以指定哪个窗体是主窗体。

    动态创建与预创建

    在缺省的情况下,我们所选择的多个窗体都是预先创建好的。我们还可以在使用的时候,才去创建窗体。这需要在project|options|forms中把auto create form移到右边。自己创建窗体的语法是:  aForm := TForm4.create(self)  self,即释放管理者,也可以直接填application

    ◇即便指定了owner,我们仍然可以主动地释放窗体:  aForm.free

    关闭和隐藏

    一般情况下,我们关闭一个窗体的时候,实际的动作是隐藏,窗体并没有真的被从内存中清除。如果我们希望把窗体释放掉,就要显式地调用 aFrom.free;基于这个原因,如果我们把窗体的属性visible设置为false,或调用窗体的方法hide 可以获得同样的效果。

    典型的登陆窗口

    登陆窗口一般是先要求输入密码,正确才进入主应用。很多人把登陆窗口做成主窗口,这样很不舒服,因为登陆窗口无论从生存期,作用上都很难担当主窗口的“重任”。比较通顺的做法是把主窗口隐藏起来,弹出登陆窗,正确,再把登陆窗释放,主窗显示出来。

    看似简单,但我们直接隐藏主窗体是办不到的。这需要在project中填写代码:

    Application.CreateForm(TForm1, Form1);

    Application.CreateForm(TForm2, Form2);

    form1.ShowPasswordWindow;   //  添加这一行代码,弹出密码窗口。

    Application.Run;

    ...  form1.showPasswordWindow中的代码: 

    hide;  if form2.showModal = password_ok then show else application.terminate;

二、多进程和多线程

进程从用户的观点看是独立的运行实体,拥有独立的内存空间,处理机资源等。不同的进程之间不能直接访问对方的内存。从操作系统的角度看,进程是轮流执行的独立任务。在切换任务时,要保存与该任务有关的资源信息。

从用户的角度看,线程与进程很类似。区别只是线程之间共享同一虚拟内存空间。在操作系统看来,线程的切换不需要保存内存的信息,线程就是轻型的进程。

每一个进程都拥有至少一个线程,这个线程就是主线程。在delphi中,正是主线程在处理日常的事物。在windows操作系统中,在缺省的情况下,主线程的任务就是:循环往复地执行取消息和执行消息的动作。这实际上就是Application.Run的核心内容。在同一时刻,一个程序不可能既去取消息又去执行消息。因而在执行复杂的耗时的操作的时候,窗口会暂时失去反映,给用户一种好象“死机”的感觉。

三、使用delphi的线程对象

    通过继承TThread可以很方便地生成自己的线程类,从而创建自己的线程对象。

    delphi的线程类是对windows API中线程服务的封装。缺省的线程类创建对象的时候,需要一个参数,就是创建后是否挂起。挂起的意思是创建后并不马上运行,而要等待调用resume才开始运行。对于正在运行的对象也可以指定它挂起,调用suspend即可。如果调用suspend两次,则需要两次调用resume才能解除挂起状态。

    可以使用挂起的这个特性来让一个线程等待其他多个线程执行完毕才开始工作。

    delphi的控件并非线程安全控件。也就是说,多个线程在操作一个控件时候,无法保证控件数据的逻辑完整性。一般说来,在主线程中操作控件。如果其他线程也需要操作,则需要一种同步的机制,就是调用 synchronize()方法,把自己操作包含在其中。synchronize的实质是把对控件的访问自动进行串行化。

    【例】构造线程,完成自动向主窗体控件中连续写入数据的操作。为了看到效果,使用了延时操作。可以观察到,在写操作的过程中,主窗体仍然可以被拖动,改变大小等等。

    ◇创建线程对象可以使用delphi提供的向导来完成更方便。new|other ... Thread Object

    synchronize中的过程不能有参数,因而需要通过线程的私有变量来传递信息。

四、线程的排斥和同步

排斥和同步是多个线程间的常见关系。排斥可以通过TcriticalSection来控制,它相当于临界区对象。在同一时间,只能有一个线程获得该对象。当申请这个对象时,如果对象已经被占用,则申请的线程会进入挂起状态;当释放这个对象时,如果有其他线程是挂起的,则第一个挂起的线程会被唤醒。

【例】TcriticalSection.create;   a.Acquire;  a.release;

TcriticalSection实现了线程间的完全排斥。但有些情况下,我们并不希望所有的线程都是排斥的关系,比如最常见的读写相同的数据区,则读的线程间就不排斥。Delphi为我们提供了处理这类问题的专门的类:TmultiReadExclusiveWriteSyncronizer。与TCriticalSection稍有区别的是,这个类的方法是:beginRead,endReadbeginWrite,endWrite

最简单的同步是等待某一个进程的结束。A.waitfor;就可以了。

TEvent类可以构造一个信号灯对象。

T := TEvent.create(安全描述,手动复位,创建后有信号,对象名字);

安全描述可以填nil,手动复位如果为true,则线程获得信号灯的时候,信号等并不熄灭,要等待一个显式的reset调用才复位。创建后有信号为true,则创建后,该信号灯是打开的。对象的名字只在进程间通信有用,如果是线程,则可以为空串。

五、windows DLL原理

进程间的内存是独立的。若多个进程用了相同的代码,怎样节省空间呢?DLL是为了共享代码而设计的。当第一次被调用时,DLL被装入物理内存,并映射到逻辑内存中。当第二次被调用时,DLL不会被再次装入,只是再次映射到新的逻辑内存。当最后一个使用DLL的进程退出运行,则DLL被自动从物理内存卸载。

Delphi提供了一个wizard来帮助我们生成DLL类型应用的基本框架。

【例】用Wizard生成一个DLL工程。其中提供一个加法函数,供调用。

再生成一个调用的例子。声明外部的DLL函数:

    function MyAdd(x,y: integer):integer; external 'c:/gyhang/a.dll';

◇注意exports export的区别。

exports用来因出一个或多个函数。在DLL中,我们可能定义了许多函数,但并不是每一个函数都要引出的。有些函数仅仅是被其他的引出函数来调用的。如果DLL中的函数很多,一般不放在一个单元中,如果某个需要引出的函数不在主单元中定义(以library开头的单元),必须在该函数的声明的后边加export进行修饰。

◇注意external的使用。在使用DLL的时候,必须用external指出DLL所在的位置。在delphi中,很多Windows DLL都被预先进行了定义,因此,我们使用的时候可以直接调用,不必再去声明位置信息。

◇注意stdcall的含义。如果DLL只是在自己的程序之间调用,不会有什么问题,但当一个DLL函数在跨语言调用的时候,会存在一个“调用约定”问题,即:以怎样的顺序把参数压栈?由谁来恢复栈指针?使不使用寄存器等等。stdcall修饰的函数使用的是与WinAPI相同的处理方式,因而被广泛地采用。

◇注意使用c语言串指针的使用。在调用WinAPI函数的时候,经常遇到参数类型为c格式的串指针。delphi的串格式与c语言不同,所以必须进行转换。

    strPCopy用来把pascal串拷贝到一个c格式存储的字符串数组中

    strPas从一个c格式串返回等价的pascal串。

六、使用DLL

使用DLL之前必须明确第描述函数的定义,以及存在哪个DLL中。

DLL与我们的应用程序公用栈空间,因此我们要充分估计栈的使用,使不至于溢出。

DLL的使用中,最关键是传递的参数必须是在内存中具有相同表示。比如整数,布尔型。DLL的调用错误经常是由于双方的数据表示不一致造成的。

                     第八讲  消息和进程通信

一、基础补充

    1  image的使用

    把图象存为jpeg格式

    2  treeView的简单使用。

    treeView可以带来很好的视觉效果,但生成过程较烦琐。如果是简单应用,我们可以使用一种简单的方法。从文件中一次性读入整个树形结构。

    treeView1.loadFromFile('c:/tree.txt')

    treeView1.selected 返回被选中的节点。

    文本和绝对索引号

二、windows消息机制

    windows系统是靠消息的传递来工作的,消息无处不在。其核心结构:ID+wParam+lParam(消息号,参数1,参数2)。消息可以在不同的进程间传递。

    【例】发送消息和接受消息

    我们可以发送系统预先定义的消息,也可以发送自定义的消息。自定义的消息从WM_User开始。

    接收消息就是把一个消息号和一个函数连接起来,使得当那个消息发生的时候,相应的函数就被调用了。

    postmessagesendMessage是有区别的。postMessage是把消息仍在消息队列后就返回,而sendMessage要一直等到消息被处理才返回。也就是说,用sendMessage实现的是类似与同步处理的操作。

    ◇如果在祖先类中也映射了同一个消息,则本类中的定义优先。如果希望在本类中调用祖先类曾经映射过的函数,方法是: inherited

    通过消息操作,我们可以模拟鼠标,键盘的动作,用程序来支配一个已知的程序去完成某些动作。比如菜单操作,点击按钮等等。为了监视某一个程序中的消息流,可以使用消息监视工具,如微软公司提供的spy++

    【例】监视扑克游戏的消息流。通过程序“指挥”该游戏发牌,翻牌等等。

    ◇监视消息时,可以选择消息的输出格式,如果是为了原样照发,可以选择原始格式,否则可以使用解码过的消息格式。

三、delphi的消息处理流程

首先是调用Wndproc虚方法。然后依次调用每一个类中的消息映射。

这两种方法都可以调用inherited来重复调用祖先的方法。

四、WM_copyData方法实现进程通信

    进程间的内存空间是独立的,这使得不同的进程可以独立运行,互不干扰。但同时也有一个缺点,就是怎样在不同进程间传递数据信息呢?当然可以使用文件,但怎样把这个文件的名字通知对方呢?进程间的通信有多种方法,比如命名管道,内存映射文件等。但最简单的方法是使用一个windows消息:WM_copyData

 

五、TCP/IP实现进程通信

TCP/IP是一个协议族,而不是一个协议。IP是网络层协议,传递的数据单位是包,目的是在两个主机间透明地传递包。IP的下层是链路层。UDP是用户报文协议的简称。它传递的单位是报文。

UDP是不可靠的协议,在传输中可能会丢包,或重包。但UDP协议占用资源少,机制简单。TCP是可靠的报文传输协议。它可以建立在UDP基础上,也可以建立在IP的基础上。

delphi提供了TNMUDP来支持UDP协议。

delphi提供了TClientSocketTServerSocket来支持TCP协议的使用

六、internet

   delphi不是专门开发静态和动态网页的工具,但使用它提供的组件可以完成这些工作。

第九讲  杂项

一、程序构造提示

1。文档-视图结构在构造程序过程中被广泛地使用。

我们应当严格地区分数据和数据的表达这两件事情。在窗台上放花或不放花来充当联络信号是表达,它的文档只有一个比特,是0或是1。在构造程序的时候,首先要实现对文档的操作,只要完成了对内部数据结构的控制,就完成了工程的大部分。至于如何显示这个文档,则是个相对容易的问题。

例子:自定义的表格   菜单,按钮的同步表现

2。构造程序时的常量替换法。

鸡兔问题的启发

二、使用Windows资源

资源就是一个二进制的文件。其中提供了命名的数据。它们可以是字符串或图片或声音等。

delphi中一直在使用资源  {$R *.res}

使用资源的优点是:可以在不重新编译的情况下,只要改写资源后连接就可以更新程序。这在不同语言文字间翻译上很有用。

例如:调用一副扑克牌中某一张的图案。

三、使用delphi全局对象

     application

applicatoin TApplication类生成的对象,它代表着应用程序本身。其中比较重要的属性

如下:

    application.exename  代表当前运行的可执行文件的带路径全名

    从中解析出纯文件名的方法是: extractFileName   extractFileDir

    icon  代表当前应用的图标。 它出现在主窗口的左上角,也出现在

screen

printer

四、包和控件的注册

 

五、帮助的制作

 

六、使用windows图元文件

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页