1.delphi中的多行注释{}和(**)
2.delphi中的常量可以不指定数据类型而直接赋初值,编译器会自动识别并确定正确的类型。
const
Pi=3.14;
I=5;
MyMessage="kkk";
也可以指定数据类型:
const
Pi:Double=3.1415926
如果常量类型是纪录类型,数组类型,shortString类型,接口类型时应做上类型标记,提高编译器产生代码的效率。
3.函数中的参数修饰符var,const,out,无修改符
var是传递的参数地址,外部的变量原值会传进来,经过函数处理后,地址不变,会把地址传出去,经过函数处理后的新值也会传出去。
const是常量参数,即只读参数,在函数内部参数不可以被修改,只允许读取。
out输出参数,传递的也是地址,只是在进入函数内后先初始化了这个地址的内容。即不接受外部的传值,只是在函数处理后把新产生的值传出去。
无任何修饰符:只是把外部送进来的值传给了在函数内部新创建的变量,函数结束后新创建的变量被释放,不影响原来的变量的值。
4.delphi局部变量不可以指定默认值,而全局变量可以。
5.delphi的关键字都是小写
6.数组是由一些具有相同类型的元素按一定顺序组成的序列,它是代表一定数量的具有相同类型变量的一种数据类型。
使用数组时要先定义数组类型,再定义数组变量。例如:
type
MyArray=array[1..100] of real;//定义数组类型,用等号
var
MyArray1,MyArray2:MyArray;//定义数组变量,用冒号
当然也可以var MyArray1:array[1..100] of real;把定义类型和变量合为一
数组类型=array[下标下限..下标上限] of 基类型;
下标的类型可以是整数,字符类型,枚举类型,布尔类型。等。
数组有上边界和下边界,使用时一定不要越界。最好使用编译器内置函数High(MyArray1)和Low(MyArray1)来取数组的上边界和下边界。
7.常量
type
TDirection=(Left,Right,Up,Down);//定义枚举类型
PNode=^TNode;//用^加基类型定义指针类型PInteger=^Integer;
TNode=record
Next:PNode;
Symbol:String;
Value:TDirecction;
end;
TStatus=(Action,Passive,Waiting);//定义一个枚举类型,用作下标
TStatusMap=array[TStatus] of string;//定义一个下标为枚举Tstatus的基类型为字符串的数组
TCharArray=array[1..6] of char;//定义字符类型数组
Number= set of 0..9;//定义数字集合
Letter= set of 'A'..'Z';//大写字母集合,集合的基类型必须为有序类型,像整型,布尔,字符型,枚举,子界。不能是实型等其他非有序类型。
//过程类型
TProcedure=procedure(A:integer);
procedure DefaultProcedure(A:integer);
const
N1:TNode=(Next:nil;Symbol:'Up';Value:Up);//纪录类型常量用小括号,成员用分号隔开
P:PNode=@N1;//指向N1的指针常量
defProc:TProcedure=DefaultProcedure;//过程类型常量。过程类型是具有相同参数和返回值的一种过程的类型。
StatusStr:TStatusMap=('Action','Passive','Waiting'); //数组类型常量,数组常量用小括号,元素用逗号隔开
Str:TCharArray='ni hao';//字符型数组常量可以为一个字符串,切记长度要等,不管是字符串还是字符数组。
StatusStr2:TStatusMap1=('T','h','y','T','h','y'); //基类型为字符类型的数组类型常量,切记长度要等
EvenDigits:Digits=[0,2,4,6,8];//偶数集合类型的常量,集合用中括号
Vowels:Letters=['A','E','I','O','U'];//元音字母的常量集合
implementation
procedure DefaultProcedure(A:Integer);
begin
//处理A的过程。
end;
8.指针
指针是特殊的数据类型,结构不复杂,且比较灵活。但难以理解。
变量模式:声明变量——程序帮我们找到变量地址——我们通过变量地址操作变量。
指针模式:声明指针类型变量——我们把地址赋给指针类型变量——直接找到该地址对变量操作。
定义指针类型以^开始,后跟基类型。如type 指针类型标识符=^基类型。
使用指针前要先定义指针的类型,要不它不知道指向什么类型。
type PBypte=^Byte;PInteger=^Integer;一般来说指针类型以P开头加基类型命名,基类型跟在^后,这样才指向基类型的数据。
类型定义要用type,如枚举类型,集合类型,数组类型,纪录类型,过程类型,类类型。变量定义才用Var。变量名:类型。
不要求基类型定义一定要在指针类型的前面,只要他们在一个块中即可。像
PStudent=^TStudent;
TStudent=record
Name:string;
Age:Integer;
Sex:(Male,Female);
end;
PStudent^表示Student类型的动态变量。
指针操作常用的函数
New过程。procedure New(var p:Pointer);中的参数P是指针变量,New(P)后通过P^对P所指的变量进行操作。
var
P1,P2:PInteger;
begin
New(P1);
New(P2);
P1^ := 100;
P2^ := 200;
showmessage(IntToStr(P1^+P2^));
Dispose(P2);
Dispose(P1);
end;
使用Test2^:=Test1^;就可以了,这个时候变量Test1和变量Test2指向的是两个不同的地址,当一方的内容发生变化的时候另外一方不会受到影响。
堆中为动态变量分配区域,并把区域地址赋给指针变量。(不够,会报outofmemory).
@运算符与Addr函数的作用一样,用于获得操作数的地址。@后面的操作数可以是变量、过程、函数或类中的方法等。也就是说@运算符构造了一个指向其后面操作数的指针。
Object Pascal中有一个特殊的保留字nil,这是个空指针常量,常量。当指针的值为nil时,表示指针当前没有指向任何动态变量,所以不能使用值为nil的指针变量去访问动态变量。
GetMem(var p:Pointer;size:Integer);
function AllocMem(Size:Cardinal):Pointer;//从堆中给一个指针分配一个指定内存大小的内存块,并返回这个指针。并初始化缓存区为0,使用procedure FreeMem(var P:Pointer[;Size:Integer]);释放分配的内存。
FreeMem销毁P指向的变量,把内存返回给堆。如果P没有指向任何堆中内存,会报运行时错误。如果P指针指向包括长字符串,变体,或接口,在释放内存前先finalize它。参数size如果指定,必须是先前通过GetMem分配的内存大小。
procedure Dispose(var P:Pointer);//释放指针空间。释放完后就不能再引用这个指针。
SizeOf(指针类型)求指针类型的大小。
无类型指针:指针变量声明时没有给出基类型的指针类型。var p:Pointer;可指向任何类型,需要先类型转换为另一个有类型指针变量后才能使用。
type
PInteger=^Integer;
var s:single;I:integer;P:Pointer;PI:PInteger;
begin
P:= @S;
PI := PInteger(P);
P:= P+1;//指针后移一位
end;
特殊指针PChar
字符指针类型PChar指向Null字符结尾的字符串的指针。Null即char(0);StrPas();返回PChar所指的字符串。
指针数组:指针指向数组的第一个元素的位置。字符串即字符的一维数组,以Null结尾。ShortString加手工加上Null标志字符串结束的位置。ShortString类型的字符串中长度不得超过255个字符。它不是以Null结尾的。索引0处存放字符串的长度。
String在Delphi7中默认AnsiString,无限大,最后自动加Null,索引从1开始。元素是AnsiChar。
SetLength(var s;NewLength:Integer);用来动态设定数组或字符串的长度,超过将截断。
Object Pascal中允许使用常量表达式,在常量表达式中也可以使用编译期间的函数。如下面的函数
Abs(X)求X的绝对值
Addr(X)定义一个指向对象的指针
Chr(X)返回值为X的字符
Length(X)求字符串的长度
Odd(X)判断X是否奇数
ord(X)返回序数类型变量X的序数值
Pred(X)返回序数类型变量X的前一个值
Succ(X)返回序数类型变量X的后续值
SizeOf(X)返回X在内存中占用的字节数
Round(X)把X四舍五入取整
Swap(X)交换16位整数的高低位
Trunc(X)把实数X截断为整数
9 整型
整型有通用的Integer(有符号的32位,范围从-2的31次方到2的31次方减1,占4个字节) ,Cardinal(无符号32位,从0到2的32次方)。
基本的
Shortint(有符号的8位,范围从-2的7次方到2的7次方减1,占1个字节) ,Byte(无符号8位,从0到2的8次方)
Smallint(有符号的16位,范围从-2的15次方到2的15次方减1,占2个字节) ,Word(无符号16位,从0到2的16次方)
Longint(有符号的32位,范围从-2的31次方到2的31次方减1,占4个字节) ,LongWord(无符号32位,从0到2的32次方)
Int64有符号的64位,从-2的63次方到2的63次方,占8个字节。
short=smallint;
在定义整数类型时,应该根据该整数的大小范围来确定它的类型,以免造成内存的浪费。
10. 有序类型
包括整型,字符型,布尔型,枚举型,子界型。有Ord(),Pred(),Succ(),High(),Low()
11.字符类型
通用Char一个字节,8位,可以存储256个不同的字符,对应整数的范围0~255.等价于AnsiChar。而WideChar占两个字节16位,用来支持Unicode字符集中的字符。
12.布尔类型
通用Boolean.相当于ByteBool.占据一个字节的空间。WordBool类型占据2个字节的空间。LongBool占据4个字节的空间。ByteBool,WordBool,LongBool主要是为了和其它语言和windows环境兼容。如果不是0则表示true.
13.枚举类型的数目不得超过255
14.子界类型
由整型,字符型,枚举型或布尔型中的两个常量指定的该类型的值域区间,这两个常量叫做子界类型的上界和下界。要求下界<=上界。注意上界等于下界也是可以的啊,子界类型的上下界可以用表达式。
15.浮点类型
Real类型是通用浮点类型,等同于Double。real,double,currency,comp都是8个字节,而single占4个字节,Extended占10个字节,real48占6个字节。
浮点类型可以用科学计数法表示。如7E-2给7乘以10的负2次方。7为尾数,-2为指数。12.56E6。注意指数必须为整数。
常用函数
Frac(X)返回X的小数部分值,Sqr(X),返回X的平方。Sqrt(X)返回X的平方根。Int(X)取整数部分,仍然是浮点数。Trunc(X)返回X的整数部分,返回值为整数。
16.动态数组
动态数组是在编译时不知道长度,在运行时动态分配的数组。在声明动态数组时不需要指定元素的个数,只需要声明数组的类型信息(包括数组的维数和数组元素的类型)
var
ai:array of integer;//动态整型数组
声明语句并没有分配用来存储数组的内存空间,在使用动态数组前需要使用SetLength(ai,12)过程来为动态数组分配内存。这里分配了12个整型元素的空间。动态数组的标号总是整数类型。从0开始。使用Length()取数组元素的个数,0长度的数组High返回-1,Low()返回0.用完用ai := nil;释放内存。
动态数组的赋值是使用地址赋值。如果想拷贝可使用A2 := Copy(A1);复制A1给A2.A2 := Copy(A1,1,2);从元素1开始,复制2个元素。
17.halt函数
halt()函数将导致程序的非正常结束,并返回操作系统。使用halt时应特别小心,只有当遇到非常严重的错误时才使用halt语句。halt(ErrorCode)可以跟一个整数代码,以指定错误的原因。Exit语句如果在嵌套程序结构内,则结束当前过程,跳到外一层嵌套继续执行。
18.RunError()
如果不想等Delphi指出错误,可以自己创建代码来查找错误,可以在程序执行的任何地方使用RunError语句,该语句将终止程序的执行,并显示使程序失败的错误代码。最大错误代码是255.
19.String与String[4],array[0..3] of char;
String变量占4个字节,它只是一个指针,指向一个数组,下标从1开始。
String[]表示一个ShortString类型的数组,Ord(下标0元素)表示字符串的长度。所以String[4]它占5个字节。
array[0..3]占用4个字节。
a. string是Delphi编译器内在支持的(predefined or built-in),是Delphi的一个基本数据类型,而PChar只是一个指向零终止字符串的指针;
b. String 所存字符串是在堆分配内存的,String变量实际上是指向零终止字符串的指针,与此同时它还具有引用计数(reference count)功能,并且自身保存字符串长度,当引用计数为零时,自动释放所占用的空间。
c.将string赋值给另一个string,只是一个简单的指针赋值,不产生copy动作,只是增加string的引用计数;
d.将一个PChar变量类型赋值给一个string 变量类型会产生真正的Copy动作,即将PChar所指向的字符串整个copy到为string分配的内存中;
e.将string赋值给一个PChar变量类型,只是简单地将string的指针值赋值给PChar变量类型,而string的引用计数并不因此操作而发生变化,因为这种情况PChar会对string产生依赖,当string的引用计数为零自动释放内存空间后,PChar很可能指向一个无效的内存地址,在你的程序你必须小心对付这种情况。
f.对PChar的操作速度要远远高于对string操作的速度,但PChar是一种落后的管理字符串的方式,而string则以高效的管理而胜出,PChar它的存在只是为了兼容早期的类型和操作系统(调用Windows API时会经常用到),建议平常使用string。
20.packed record
delphi中的record的数据各个字节都是对齐的,数据格式比较完整,比如win32系统下每4个字节是组,不够4个字节的补成4个字节。所以这种格式占内存比较大,但是由于格式比较整齐,电脑读取这样数据类型的数据时速度就比较快。而packed record则对数据进行了压缩处理,这样节省了内存空间,但读取速度也变的慢了。不过对于现在的操作系统,packed record节省的那些空间已经不用考虑了,但是做dll和硬件编程时必须用到packed record.因为dll不用packed record容易造成内在混乱。
type
Tinteger=packed record
a:integer;//4
end;
Tstring=packed record
s:string; //4
end;
Tboolean=packed record
a:boolean;//1
end;
Tchar=packed record
s:char; //1
end;
Tshort=packed record
a:short;//2
end;
Tsmallint=packed record
s:smallint; //2
end;
Tbyte=packed record
a:byte;//1
end;
Tdouble=packed record
s:double; //8
end;
Tsingle=packed record
s:single;//4
end;
TString1=packed record
a:string[1];//2
end;
Tcharshort=packed record//3
s:char; //1
c:short;//2
end;
TString4=packed record
a:string[4];//5
end;
Tcharshortint=packed record//7
s:char;
c:short;
i:integer;//4
end;
//定义一个常规记录类型
TDefaultRecord=Record
name1:string[4];//8
floater:single;//4
name2:char;//4
int:Integer;//4
end;
//定义一个压缩记录类型
TPackedRecord=Packed Record //14
name1:string[4];//5//因为它是代表一个Char数组,后面自动加一个char(0);
floater:single;//4
name2:char;//1
int:Integer;//4
end;
begin
listbox1.Items.Append('Tinteger占用内存:'+IntToStr(SizeOf(Tinteger)));
listbox1.Items.Append('Tstring占用内存:'+IntToStr(SizeOf(Tstring)));
listbox1.Items.Append('Tboolean占用内存:'+IntToStr(SizeOf(Tboolean)));
listbox1.Items.Append('Tchar占用内存:'+IntToStr(SizeOf(Tchar)));
listbox1.Items.Append('Tshort占用内存:'+IntToStr(SizeOf(Tshort)));
listbox1.Items.Append('Tsmallint占用内存:'+IntToStr(SizeOf(Tsmallint)));
listbox1.Items.Append('Tbyte占用内存:'+IntToStr(SizeOf(Tbyte)));
listbox1.Items.Append('Tdouble占用内存:'+IntToStr(SizeOf(Tdouble)));
listbox1.Items.Append('Tsingle占用内存:'+IntToStr(SizeOf(Tsingle)));
listbox1.Items.Append('TString1占用内存:'+IntToStr(SizeOf(TString1)));
listbox1.Items.Append('TString4占用内存:'+IntToStr(SizeOf(TString4)));
listbox1.Items.Append('Tcharshort占用内存:'+IntToStr(SizeOf(Tcharshort)));
listbox1.Items.Append('Tcharshortint占用内存:'+IntToStr(SizeOf(Tcharshortint)));
listbox1.Items.Append('TAI占用内存:'+IntToStr(SizeOf(TAI)));
listbox1.Items.Append('TPackedRecord占用内存:'+IntToStr(SizeOf(TPackedRecord)));
listbox1.Items.Append('TDefaultRecord占用内存:'+IntToStr(SizeOf(TDefaultRecord)));
end;
21.对象的大小
对象所占的内存空间大小取决于这个对象中的数据成员,再加一个4字节的VMT,成品函数不占空间。如果没有数据成员,那就是4字节。
注意:对于无法合并的小于4字节的数据域填充到4字节,以加快存取速度,数据对齐,跟record类似。可以通过对象名.instanceSize取对象的实际大小。sizeof(对象)只是显示对象指针的大小,不是实际对象的大小。
TAI=class//16,b4,i4,j4,vmt4
//a:char;
b:char;
i:integer;
j:char;
//e:word;
//c:single;
//si:integer;
function aa:boolean;
procedure bb;virtual;
end;
TAI=class//12,ab4,i4,vmt4
a:char;
b:char;
i:integer;
function aa:boolean;
procedure bb;virtual;
end;
TAI=class//4,vmt4
function aa:boolean;
procedure bb;virtual;
end;
22.类的虚拟方法和动态方法
虚拟方法占用内存空间比较大,每个子类都有一个方法VMT。但是访问速度比较快。因为自己的VMT中有方法地址。
动态方法占用内存空间比较小,DMT中只是存一个指向方法的指针。访问速度比较慢。
虚拟方法和动态方法在语义上是相同的,不同之处在于运行时对方法调用分发的实现上。虚拟方法速度优先,而动态方法代码空间优先。一般地说,虚拟方法是最有效的方式来实现多态行为。当一个基类声明了许多可重写的方法需要被许多继承类继承,但是偶尔被重写时,动态方法是有用的。
==============================VMT===================================
在创建一个类的实例之后,编译器在该对象的内存空间的首4个字节安插一个指针,该指针指向的地址称为VMT(Virtual Method Table,虚方法表),这个表中放了该类的所有虚方法的入口地址。
在Object Pascal中,所有类实例都会有这么一个指向VMT的指针。如果没有在类中声明虚方法,则该指针为nil。
没有被派生类赋给的方法,编译器会将基类的该方法实现的入口地址填入派生类的VMT中。
其实“指向VMT的指针”所指向的VMT,其实只是真正VMT的一部分,也就是用户定义的第一个虚方法的位置。如果以这个位置为原点,向正方向即刚才所说的VMT,而向负方向,则是语言定义的另一些信息所在地址,析构函数地址就被放在了负方向上了。所以VMT中也有析构函数,但没有出现在我们所能见到的VMT中。这样做的目的,是为了与C++以及COM的VMT相兼容。
据观察:负方向包括7个TObject的虚函数(包括析构函数,位置-4),以及指向其它表格的指针(虚方法表, 接口表, Automation information table, 实例初始化表, 类型信息表,属性表, 方法表, DMT(动态方法表), 类名, 实例大小,指向祖先类指针,安全异常指针)
==============================DMT====================================
VMT的-48处是一个指向DMT的指针,它与VMT有什么关系?
派生类的虚方法表完全继承了基类的虚方法表,只是将被覆盖的虚方法地址变了。基类和每个派生类都有一份自己的虚方法表。可以想象,随着类层次的扩展,虚方法将耗费非常大的内存空间。为了防止这种情况,Object Pascal引入了“dynamic”的概念。dynamic和virtual方法实现相同的功能,只有声明的关键字不同。被声明为dynamic的方法,其入口地址将被放在DMT中。
VMT和DMT的区别在于:对于派生类没有覆盖的方法,这些方法的入口地址不会出现在DMT中,编译器要通过基类的信息来寻找它们的入口地址。
DMT中不会出现没有被派生类覆盖的基类dynamic方法,因此DMT会比VMT节省空间,但是DMT中对基类的动态方法的寻址不是直接进行的,因此dynamic要比virtual要慢一些。当基类有许多虚方法,而派生类只覆盖很少几个时,区别尤其明显。当派生层次越来越深,派生数量越来越多时,DMT就能节省更多的内存空间。virtual与dynamic的区别仅在于编译器采用不同的晚绑定策略而已,对于程序员而言,它们的功能相同。
几乎每个派生类都要覆盖的方法,将它声明为virtual;
如果层次很深,或者派生类很多,但某个方法只被很少的派生类赋给,则声明为dynamic。
另外需要注意的是,只有VMT才与C++、COM的vtable兼容。如果需要这样的兼容性时,只能使用virtual。
动态方法使用在继承深度达到一定数目的类中,或是想节省VMT空间的类中使用。
23.
条件编译指令
$DEFINE 用于定义一个条件符号,一旦定义,条件符号就为真。
$ELSE 与$IFDEF配合使用,如果$IFDEF条件为假,则只对源文件$ELSE后一小部分进行编译。
$ENDIF 结束一个以$IF开始的条件段
$IFDEF 对条件符号进行判断,为真则编译
$IFNDEF 对条件符号进行判断,为假则编译源文件
$IFOPT 根据编译开关状态,对源文件编译
$UNDEF 撤消以前的条件符号定义
24.创建组件,举例说明
创建组件前先明确组件的作用,根据用途选择它的父类。
TObject类提供基本的对象管理服务
对象的创建和初始化
对象方法的分配
对象的销毁
对象识别服务
对象信息服务
对象消息分派服务
TPersistent
持久化服务
TComponent
核心组件基础类提供下面基础服务以便让派生类能够不再需要重复撰写这些服务程序代码。
作为基础的根组件类
可同时扮演Container组件和单一组件的功能
基础组件管理功能
基础组件互动通知功能
同时提供可视化和非可视化组件架构基础
TControl
控件最基本的特性是像多数windows控件一样可以响应鼠标事件,控制光标以及能够分派事件消息的服务。实现了这些基本的控件服务,那么所有从它派生的类便自动拥有这些功能,派生类只需要实现特定功能即可。
基本资源服务
处理鼠标的服务
处理消息和事件的服务
控件重绘服务
TWinControl
实现封装Windows控件的类,自然加入了封装Windows控件的Handle,处理窗口事件的方法,经由Windows API进行控件图形用户界面控制的工作以及窗口消息的机制。封装Windows控件,封装Windows创建功能,封装Windows消息,Windows控件重绘服务。处理Window消息服务。
如果想要创建自定义用户界面组件,可以从TGraphicControl继承下来,改写Paint方法,再于其中实现绘制组件的程序代码就可以了。VCLFramework经由TGraphicControl类提供了另一种图形用户界面组件类,那就是不封装Windows控件的组件类。
自定义控件类TCustomoControl
如果想封装Windows控件而且又想加入自定义绘制的能力,那么就可以选择从TCustomControl类继承下来。
TCanvas是通用封装画布区域的类,而TControlCanvas则是VCL控件拥有的画布区域。
如果想要属性显示在对象查看器中,要声明成Property并写在Published部分。
定义消息方法
procedure CMMouseEnter(var Message:TMessage);message CM_MOUSEENTER;
procedrue CMMouseLeave(var Message:TMessage);message CM_MOUSELEAVE;
不要忘记注册,这样才能装到组件面板上。
注册函数用Register这是一个保留字。注册都要用这个方法。
RegisterComponent函数有(页面串,组件数组);
组件add到package中。通过package中的install安装到组件面板上。
25.parent,owner,
owner是TComponent类就加入父代对象的连接以及储存所有它管理的子组件的引用值为了提供管理子组件的服务,连接上层父对象的是Owner,连接所有子组件的是Components串行。
parent是在TControl类加入的TWinControl的类型。其目的是为了维持一个统一的显示风格在你的程序里,你可以设定你的所有控件像它的容器---parent的风格。通过设置parent相关属性为True。ParentColor,ParentFont,ParentShowHint.
Parent属性是在类TControl中定义的,指定一个控件在可视化编程方面所处的容器组件。
一个控件只能在其Parent组件中移动和显示。一般在运行时创建一个新的控件的时候,要为新控件指定Parent属性。
Owner属性是在类TComponent中定义的,指定一个组件的所有者,对该组件的创建和删除负责。
26.self,sender
在一个事件处理过程中,这个Sender参数指示了哪个组件接收这个事件,调用这个句柄。有些时候,几个组件共享一个事件句柄的时候,它区分是哪个组件在调用它。
在方法实现过程中,这个Self标识符指向方法调用的对象
。
Button:=TButton.Create(nil)表示传递一个空对象做为容器组件,它的Owner属性是空。必须由编码手动释放。
Button:=TButton.Create(Self)当前方法的调用的对象是新对象button的容器组件。self负责该控件的释放
Button:=TButton.Create(Application)容器组件为application,程序退出时释放
27.消息 1 and 49151(BFFFF)
WM_开头的标准Windows消息
CM_开头的VCL内部消息
通知消息
用户自定义消息(WM_USER = $0400;1024)WM_USER+100到$7FFF(32767)
perform();