发信人: Mars (FangQ), 信区: Programming
标 题: [HTML]Delphi for C++ Programmers
发信站: The Big Green (Sat Jul 27 01:36:29 2002) , 转信
Delphi
for C++ Programmers
Introduction
There are a lot of Delphi books out there, but 99% of them deal only with the
more "glamorous" visual aspects. While the visual interface is the main
attraction of development systems like Delphi and Visual Basic, their greatest
strength is that they free the programmer from the drudgery of coding the user
interface, and allow him or her to work on the actual program.
It doesn't matter how visual your tools are (or how fantastic the
manufacturer's claims), you won't accomplish much without actually coding the
logic behind the interface.
This text is meant to aid C++ programmers in the transition to Object Pascal.
Although the main features of both languages are relatively similar, there are
certain small differences that can be confusing, especially when they lead the
programmer into making incorrect assumptions.
The Major
Differences
There are three main C++ features missing from Object Pascal, these are:
multiple inheritance, templates and overloading . I
can hear the groans now, but don't despair. Object Pascal has enough great
features to more than compensate for these omissions. There are also many Object
Pascal features that are missing from C++ (and they have nothing to do with the
"visual thing").
Other major differences include:
1. Common base class - In Delphi, all objects descend from TObject.
2.
3. No stack objects - In C++ you can create objects on the stack or on the
4. heap. All Delphi objects are created on the heap, and referenced via pointers.
5.
6. Full RTTI - Delphi's Run Time Type Information is more complete than C++'s.
7.
8. Class references - In C++ you always work on objects, but in Delphi, you can
9. also work on uninstantiated classes.
10.
11. Properties - A very user-friendly language extension. It's like designating
12. methods to be executed upon reading from, or writing to, a data member. (This
13. can be implemented in C++, but it's not as simple. Check out the C++ section for more details.)
14.
Pointers
Pointers give C a lot of it's power and flexibility, but they are also the
cause of most major bugs. There are pointers in Pascal too, but the compiler is
a little more protective (or restrictive, depending on your point of view).
Declaring &
De-referencing
The syntax for declaring pointers in Pascal is similar to the one used in C.
The main difference being that the caret ("^") is used instead of the
asterisk ("*
"):
{ Declare
a pointer to a character
}
CharPtr: ^char;
To de-reference
a pointer, the caret is placed on the right hand side of the
variable:
{ Set
MyChar to what CharPtr points to
}
MyChar :=
CharPtr^;
Whatever follows the caret must be a defined
type. You can't directly create a pointer to an array,
structure or class. You must first define these as types
before you can create pointers to them.
While in C you can do this:
// Pointer to a pointer to // an
array of 5 characters char
**CharArrPtr[5];
>
In Pascal you are required to do it in steps:
type
{ define type: array of 5 characters
} TChArr = Array [0..4] of char; { define type: pointer to TChArr
} TPChArr = ^TChArr; var { declare
variable as pointer to type TPChArr }
CharArrPtr:
^TPChArr;
Indexing
This may come as a shock to most C programmers, but in Pascal, as a general
rule, pointers can't be indexed. A Pascal pointer lets you reference one, and
only one object in memory. This rule is not as limiting as it may seem at first,
since the object that you reference through the pointer can be an array, a
structure or a class.
When you need a pointer to a list of integers, you use a pointer to an array
of integers instead of a pointer to an integer. You still can't index a pointer
to an array, but you can index the array itself. The way to accomplish this is
by de-referencing the pointer before using the index.
For these declarations:
type<
/tt> {
declare type: array of 101 char
} TCharArr = Array
><>color="#808080">[0..100] of
char;
< br> { declare type: pointer
to an array of 101 char
} TCharArrPtr
=
^TCharArr;
var< br> { declare a pointer to an array of 101 char
}
MyPtr:
TCharArrPtr;>
The following facts apply:
{ This
is wrong, can't index a pointer }
X
:= MyPtr[10];
{ This is fine.
De-referencing the pointer }
{ gives you an array, which can be indexed
}
X :=
MyPtr^[10];
Allocating &
Deallocating
Just like in C, the Delphi programmer is responsible for the allocation and
deallocation of pointer space. There are several ways of allocating memory for
pointers. The simplest one is through the procedure "New".
The procedure "New" allocates the exact amount of memory needed for the
specified pointer. Using the previous example:
New(<
/tt>MyPtr);
Allocates exactly 101 bytes to "MyPtr" (since it points to an array of 101
characters).
There is also another form of "New", a function, which may be used for the
same purpose. In this version, the parameter is a type, not a variable. The
return value is a pointer to the allocated space.
MyPtr :=
New(>TCharArrPtr<>color="#808080">);
This is a tad more complicated, but would be useful if you wanted to allocate
memory for one type of data, but wanted to point to it with a different type
pointer (like an untyped pointer).
To deallocate the memory allocated with "New", you must use
"Dispose":
Dispose(MyPtr><>color="#808080">);
If you want to be able to specify the exact amount of memory to allocate, you
can use the "GetMem" procedure. To allocate the 101 bytes
needed for "MyPtr", you would do the following:
GetMem(MyPtr><>color="#808080">,
101);
Allocating more than the space required is a waste of memory, since Delphi
won't let you index it anyway (See the section "Range Checking and Pointers").
Allocating less could cause serious problems if you write to the unallocated
part of the variable.
To deallocate this memory, you must use "FreeMem", and you
must explicitly state the size of the allocated memory:
FreeMem(MyPtr, 101);
Range Checking and
Pointers
When you declare an array, you must specify an upper and lower value for it's
index. The Pascal compiler, unlike C's, performs range checking to ensure that
you don't use an out of bounds index . This offers additional protection against
memory overwrites, but has an unexpected side-effect (unexpected for C
programmers that is): pointers to arrays are also range checked.
Since you are responsible for allocating memory for the array pointer, and
you can allocate as much or as little memory as you want, you may be tempted to
assume that you can use as high an index as you need. This is not the case.
Remember that you can't really index the pointer. You must de-reference the
pointer and then index the array. Since the array has specific index boundaries,
you are constrained by them.
Dynamic
Arrays
>
So, what happens if you need an array of variable size? Well, Pascal was
never meant to support variable size arrays. However, there is an easy, if
rather inelegant, workaround (e.g. a hack). What you must do is declare a
pointer to an array as big as it can possibly be. In 16 bit Delphi, this size
limit happens to be 32,767 bytes (32k), but in Delphi 2.0, you have a little
more space; about 2,147,483,647 bytes (yes, that's 2gb!). The upper bound of the
array would be this number divided by the length of the array's type:
const
MAX_SIZE = 2147483647;
type<
/tt>
TMyArr = Array
><>color="#808080">[0..(MAX_SIZE
div
SizeOf(AType))]
< font
color="#800000">of
AType;
TMyPtr =
^TMyArr;
After this, you can declare variables of type TMyPtr, and
allocate as much or as little memory as you need. The only thing to keep in
mind, is that for all practical purposes, you won't have range checking in
effect for this type of array.
By the way, don't be deceived into thinking that you can use any mathematical
expression in the declaration of an array. Only constant expressions, those that
can be resolved at compile time, can be used here.
I must point out that you will rarely need to perform any of these steps.
Delphi comes with a very useful class called TList which
implements all of the functionality of a dynamic array of pointers. Since all
objects are pointers, you can use TList with them. If you need
an array of strings, you can use TStringList.
I only bothered with the above explanation, because I think it will give C
programmers an insight into how things work in Pascal.
Strings
In Pascal, you do not generally need character pointers for text. There's a
string data type that simplifies string handling immensely.
Pascal strings are particularly easy to use because the assignment and addition
operators can be used to set and concatenate them.
Strings in 16 bit
Delphi
In Delphi 1.0, when you declare a variable of type string,
you can specify a size from 1 to 255 characters; if you omit the size, it
defaults to 255. The first byte of the string (byte number zero) contains the
length of the text that's stored in the string. You do not include the length
byte when declaring the size.
MyVar1:
string[3];
{ 3 chars + length byte
}
MyVar2: string[255]; { 255 chars + length byte
}
MyVar3: string;
{ 255 chars + length byte
}
MyVar4: string[300]; { Syntax error }
Because of the length byte, Pascal strings don't need a null-terminator.
Although it is possible to directly read and set byte #0, it is better not to
do this. The "correct" way of getting the length of a string is through the
Length function. The length is set automatically when you
assign or add a value to the variable.
Changing the length byte won't change the allocated size, only the size of
the text. (much like moving the null terminator in a C string). For example, a 5
character string would initially look like this:
[0][0][0][0][0][0]
; <-if you
print this, it will say: "".
^-Length Byte
If you assigned it the word "HELLO", it would look like this:
[5][H][E][L][L][O]
; <-if you
print this, it will say: "HELLO".
^->Length
Byte
If you assign it the word "GOODBYE", it would look like this:
[5][G][O][O][D][B]
; <-if you
print this, it will say: "GOODB".
^->Length
Byte
As you can see, the word gets truncated at the fifth
character, because the variable is only 5 bytes long (plus byte #0). If
you now assign it the word "HI", it would look like this:
[2][H][I][O][D][B]
; <-if you
print this, it will say: "HI".
^->Length
Byte
Since "HI" is a two letter word, the length byte now contains a "2". The
positions after the "I" have not been changed.
Strings in 32 bit
Delphi
In Delphi 2.0, strings have improved dramatically. The maximum size for
strings is now 2 gigabytes, and they are dynamically allocated. This means that
you now declare them without an initial size, and the strings grow and shrink as
necessary depending on what you assign to them.
The strings described in the previous section are still available on Delphi
2.0, but the type is now called "ShortString". Also, if you
use the old style declaration with a size between 1 and 255, you'll get a
ShortString:
var
newstr1:
string;
{ new type of string
}
oldstr1:
string[255]; { old type of string
}< tt>
oldstr2:
ShortString;>
<>color="#408080">{ old type of string
}< tt>
newstr2:
AnsiString;
{ new type of string
}
There is also a new string related function:
SetLength. This function forces a string to reallocate itself
to a specific size. This can be useful for truncating values, and for allocating
space for character by character operations:
msg := 'Hello'; { Allocs 5 bytes and sets to 'Hello'
}
SetLength(msg, 3); { Reallocs to 3 bytes (& truncates)
}
ShowMessage(< /tt>msg); { Displays the message
'Hel' }>
SetLength< font
color="#808080">(msg, 4); { Reallocs to 4 bytes, adding a char
}
msg[4] := 'p'; < font
color="#408080">{ Assigns 'p' to the last
char }
ShowMessage(< /tt>msg); { Displays the message
'Help' }
In General
Individual characters in a string can be accessed by indexing, just like
character arrays in C, with the exception that the first character is #1 instead
of #0.
Unlike C's strings, Delphi's aren't pointers. If you assign one string to
another, the value is copied from the source to the destination. Here is
(roughly) how a Delphi string assignment would look in C++.
16 bits | 32 bits |
char str1[256]; | char < /strong>*str1 = NULL; |
The same applies when passing strings as parameters (unless they're
"var" parameters).
C Style
Strings
There are times, like when making API calls, when you absolutely must use a
C-style null-terminated string. For this reason, Delphi lets you use C-style
strings too. Their type is known as PChar, and as you may have
already guessed, they are character pointers (but of a special kind). Unlike
strings, PChars must be explicitly allocated and deallocated.
The functions used to allocate and deallocate PChars in Delphi are
StrAlloc and StrDispose. There is also a group
of functions used to manipulate these strings. A pleasant surprise is that their
names are almost the same as their C counterparts. Some of these are:
StrCopy, StrComp, StrCat and
StrLen. There is also a function called StrFmt that works just like "sprintf".
Why have
PChars
A PChar is not exactly the same as a pointer to a char. As you already know,
in Pascal, you can't index a character pointer. To be able to access the
elements via indexing you would normally need a pointer to an array of
characters. Even then, you would need to de-reference the pointer before
indexing it. The PChar is a new type that is treated in a special way by the
compiler so that it works exactly like a char pointer in C.
Objects
In C++, there are two ways to instantiate an object: on the stack or on the
heap. In Delphi, all objects are created on the heap. When you declare a
variable as an object type, you are actually declaring a pointer. In this
example:
var
MyList:> TList<>color="#808080">;
MyButton:
TButton;
>
MyList is actually a pointer to a TList object in the heap. Of course, until
you assign something to MyList, it doesn't point to a valid object. You must
create an object an assign it to MyList before you can access any of it's
methods or properties.
In Delphi, unlike C++, an object's constructor allocates it's own memory, and
you must explicitly call the constructor:
MyList :=
TList.Create;
MyButton
:= TButton.Create(Self);
This is equivalent to the following C++ statements:
MyList = new
TList;
MyButton = new
TButton(this);
When you finish using the object, you must explicitly deallocate it's memory
by calling the object's "Free" method. "Free"
is not the object's destructor; that one is typically called
"Destroy"; but "Free" calls
"Destroy" after performing certain checks. A programmer would
normally redefine "Destroy", but call "Free".
MyList.Free;
MyButton.Free;
This is the equivalent of using "delete" on objects in
C++:
delet e
MyList;
delete
MyButton;
But wait! Isn't MyList a pointer? Shouldn't it have to be dereferenced before
calling Free or any of it's methods? Yes, it should, but object pointers are
different from regular pointers. They don't need dereferencing (in fact, they
don't allow it). This makes for a simpler syntax, and code that's a lot easier
to read and maintain.
Virtual and
Override
Just like C++, Object Pascal has virtual methods (i
n fact it has several kinds of virtual methods). A method is defined as such
with the keyword "virtual". The difference here is that when
you redefine a virtual method you have to use the "override"
directive. If you don't, the new method will just hide the old one, as if it was
static. For example:
For the base class TMyBase:
TMyBase
=
class
{ declare a virtual method
}
procedur e
MyMethod(Arg1
:
Integer); virtual; procedur e
MyCaller; end;
procedure TMyBase.MyMethod(Arg1: Integer); begin
{ write the name of the class plus Arg1
}
writeln('TMyBase [' +
IntToStr(Arg1)
+ ']');
end;
proce
dure TMyBase.MyCaller;
begin<
/font>
MyMethod(1);
end;
When you call TMyBase.MyCaller, you get: 'TMyBase
[1]'.
In a class derived from TMyBase, the method "MyMethod&q
uot; can be overridden like this:
TMyChild = class(TMyBase)
{ override the virtual method
}
procedur e
MyMethod(Arg1: Integer); override;
end;
procedure TMyChild.MyMethod(Arg1: Integer);
begin
{ write the name of the class plus Arg1
}
<
/tt>writeln('TMyChild [' +
IntToStr(Arg1)
+ ']');
end;
Now, when you call TMyChild.MyCaller, you'll get:
'TMyChild [1]'
A hassle you say? Maybe. But it gives you the flexibility of deciding whether
or not to override the virtual method. If you do this instead:
TMyChild = class(TMyBase)
{ don't override the virtual method
}
procedur e
MyMethod(Arg1: Integer);
end;
procedure TMyChild.MyMethod(Arg1: Integer);
begin
{ write the name of the class plus Arg1
}
<
/tt>writeln('TMyChild [' +
IntToStr(Arg1)
+ ']');
end;
Then "MyMethod" will be treated (for polymorphic purposes)
as if the original definition was static. When you call TMyChild.MyCaller,
you'll get: 'TMyBase [1]'.
You can not, by the way, override a static method.
Invoking Redefined
Methods
You can use "inherited" to call the most recent version of
an inherited method, even if it is being redefined or overridden in the current
class. However, if you need to call an older version of the method, you are out
of luck.
procedure<
strong> TMyClass.MyMethod(Arg1: Integer);
begin<
/font>
{
call the inherited virtual method
}
inherite d
MyMethod(Arg1);
{ call a different inherited method
}
inherite d
OtherMethod;>
end;
Inside an event handler, you are allowed to call the ancestor handler without
knowing it's name. You do this by calling "inherited" all by itself.
{ Event handler.
You only need the keyword
}
procedure
WMPaint(var
Msg:> TMessage);<
strong>
begin<
/font>
...
inherite d;
...
end;
Inherited Constructors and Destructors
One of the greatest sources of bugs for C++ programmers moving to Delphi is
forgetting to call inherited constructors and destructors explicitly.
Derived class constructors in C++ can explicitly indicate which base class
constructor to call, but the compiler will automatically call the base class'
default constructor if none is specified. For this reason, it is very common to
do this in C++:
TMyClass::TMyClass
{
// Your Code Here
};
instead of:
TMyClass::TMyClass : TMyBase
{
// Your Code Here
};
However in Object Pascal, if you redefine a constructor, you must
explicitly call the inherited constructor. All classes have at least one
inherited constructor and destructor (the ones inherited from TObject).
const ructor
TMyClass.Create;
begin<
/font>
{
Call the base class' }
{ constructor explicitly
}< tt>
inherite d
Create;
{ Your code here
}< br> end;
This would be the same as the following C++ construct:
TMyClass::TMyClass;
{
/ / Call the constructor of this
//
object's base class explicitly
T
MyBase::TMyBase;
/ / Your code here
};
Do not attempt to use a similar syntax with Object Pascal. While the program
would compile, the logic would be wrong:
const ructor
TMyClass.Create;
begin<
/font>
{
This is not what you expect
}
TMyBase.Create;
{ Your code here
}
end;
This constructor is indeed calling the constructor for the base class, but it
is just creating an independent object of type TMyBase. This does not allocate
memory for the current object, nor does it initialize it.
The call to the inherited constructor is generally the first statement in a
constructor. This is not a syntax requirement; you can call class methods and
non-member functions and you can access local, global or modular variables
first; but if you try to access any of the class' data elements or methods
before calling the inherited constructor, you'll get a General Protection
Fault.
As sneaky as this error can be, it's nothing compared to what happens when
you forget to call the inherited destructor. With the constructor, you at least
get a GPF that tells you where the error occurred. With the destructor however,
your problem is more difficult to detect: you just start running out of memory.
Depending on your program, you may notice performance degradations, inability to
allocate memory, running out of Windows resources, locked devices or files,
etc.
To prevent this from happening, always call the inherited destructor. This is
usually done at the end of the new destructor (but again, this is not a
syntactical requirement):
destr
uctor TMyClass.Destroy;
begin<
/font>
{
Your code here
}
inherite d
Destroy;
end;
One other thing about destructors: remember to
"override>" them. The "Free" method won't find your
new destructor if you don't. Neglecting this is just as bad as forgetting to
call the inherited destructor.
Multiple
Constructors
Since there is no overloading in Object Pascal, you might be wondering how
you can have multiple constructors. The answer to this is very simple: The old
fashioned way!
You can have as many constructors as you want, provided they each have a
different name. While in C++ all constructors have the same name as the class,
in Object Pascal they are generally called "Create". This is not a requirement,
just a standard. If you need extra constructors, you generally use the word
"Create" with something else. For example:
constructor>
CreateCopy( iOriginal: TObject);
constructor
CreateAsChild(iParent: TObject);
constructor
CreateFromFile(iFileName: string);
You can only override a constructor with another that has the same name and
parameter list (even the argument names are important). If you only match the
constructor's name, you'll only be "hiding" the inherited constructor.
If you are developing a Visual Control, keep in mind that all controls
must have a constructor declared like this:
const ructor
Create(AOwner: TComponent); override;
If you omit this declaration, Delphi won't be able to instantiate your
control.
Common Base
Class
Every object created in Delphi is a descendant of TObject.
If you don't specify a base class when declaring your class, Delphi derives it
from TObject anyway. All components are derived from
TComponent (which of course, derives from
TObject), and all controls are derived from
TControl (which is in turn derived from
TComponent), etc.
The importance of this fact is that it gives the Visual Component Library a
great advantage over C++ class libraries like OWL and MFC. Since all objects
have a common ancestor, you can create container classes that can take any kind
of object, (or components only, or controls only, etc.) neatly doing away with
one of the main reasons for requiring templates.
The Self
Pointer
The "self" pointer is Object Pascal's equivalent to C++'s
"this" pointer. They are exactly the same except in one case.
When used within a class method, self refers to the
class and not to the object (the instance). This hardly ever
matters, since class methods are very rare, and you can't do a great deal in
them anyway.
Class Methods
Class methods are member functions or procedures that act on a class instead
of an instance of a class.
You can call a class method in the regular manner, but you can also call it
without having an object instance. This is done by prepending the class name
instead of the name of an instance. A class method's "self" pointer refers to a
class instead of an object.
If you want to turn a method into a class method, you just prepend the word
"class" to it's declaration (and definition). The compiler will
not let you access any data elements or regular methods from within a class
method.You can declare local variables though. An example of a class method is
the GetClassName function:
class function
TMyClass.GetClassName<
/strong>:
string;
begin
Result := 'TMyClass';
end;
Constructors are a special kind of class method. The constructor's "self"
pointer is a regular object pointer, not a class reference; and you can access
data elements from within it (just make sure you call that inherited constructor
first).
Member
Visibility and "Friends"
In C++ we are used to the keywords: "public",
"private" and "protected". These are present
in Object Pascal as well, but they work a little different.
"Public" works as expected, but "private" and
"protected" have no effect on other classes declared within
the same unit. In other words, all the classes declared in the same unit are
friends with each other. This is important, since there is no
"friend" keyword in Delphi.
Delphi also has a "published" keyword which behaves almost
exactly like "public", but you won't need to use it unless you
want to create your own controls. Since there is nothing similar in C++, the
"published" keyword is beyond the scope of this text.
The Scope
Resolution Operator
C++ has the double-colon ("::"), but Object Pascal, has no separate scope resolution operator.
Instead, the dot (".")serves multiple duty.
We already know that the dot is used in method declarations, and for
accessing methods, properties and data members. But when there's a name conflict
between a stand-alone function or procedure and a class method, you can precede
the function call with the name of the unit that contains the function and a
dot:
Language | Syntax |
C++ | <var> = ::<function>(<arg list>); |
Object Pascal | <var> := <unit>.<function>(<arg list>); |
As discussed earlier, you can not use the dot notation to call an inherited
method, for this you would use the keyword "inherited<
/strong>".
Other Syntax
Elements
Here are some relatively trivial items that can be annoying to C++
programmers.
The Assignment
Operator
Remember "=" is for
comparison and ":=" is for
assignment. You will often find yourself using C++'s operators. No big deal, the
compiler will catch the error, because Pascal doesn't allow the use of the
assignment operator in evaluation expressions, nor does it permit chained
assignments.
The Else>
Clause
The statement preceding an "else" must never end in a
semicolon. You will probably always forget this. Even seasoned Pascal
programmers forget it. The compiler will catch it, but it's rather annoying.
Incrementing and Decrementing Values
In C++, the "++&q
uot;, "--", "+=" and "-=" operators are a very useful
(and fast) shorthand. In Delphi, there are two useful, if not as elegant,
equivalents: "Inc " and "Dec".
C++ | Pascal Code> | Shortcuts< /td> |
++><> color="#000000">A; A++; | A := A + 1; | Inc(A); |
--><> color="#000000">A; A--; | A := A - 1; | Dec(A); |
A +=2; | A := A + 2; | Inc(A,2); |
A -=2; | A := A - 2; | Dec(A,2); |
Although Inc and Dec look like regular procedures, the fact that they can
have one or two parameters gives us a hint that they are not (there's no
overloading in Pascal). In fact, the compiler generates more efficient code for
them than for the regular assignment-and-addition or assignment-and-subtraction.
Inc and Dec are not exact equivalents for
the "++" and "--" operators. Since they are
procedures, not functions, they don't return a value, so they can't be used in
expressions.
-- 你佛慈悲 ※ 来源:.The Big GreenWWW BBS.Dartmouth.Edu.
[FROM: 129.170.66.196] ※ 修改:.Mars 于 Jul 27
01:38:43 修改本文.[FROM: thayer66-bp-196.]