Comparing OO Features of Delphi C++ C Java
Comparing OO Features of Delphi C++ C Java
Introduction
This document attempts to compare some of the object oriented features available in the
Delphi, C++, C# and Java programming languages. Introductory paragraphs to each
section to explain the concept being compared in the section are available, but are
designed to be brief, introductory discussions only, not comprehensive definitions. The
primary purpose of these introductory paragraphs is to highlight the differences between
the implementations of the concept in the languages being compared, not to serve as
tutorials explaining the concept being compared.
Unless otherwise noted, all source samples listed below were tested and successfully
compiled with the following tools:
Delphi:
Unit1.pas:
unit Unit1;
interface
type
TTest1 = class
public
constructor Create;
destructor Destroy; override;
procedure Method1;
end;
implementation
{ TTest1 }
constructor TTest1.Create;
begin
inherited Create;
//
end;
destructor TTest1.Destroy;
begin
//
inherited Destroy;
end;
procedure TTest1.Method1;
begin
//
end;
end.
C++:
test1.h:
#ifndef Test1_H
#define Test1_H
class Test1 {
public:
Test1(void);
~Test1(void);
void method1(void);
};
#endif // Test1_H
test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
Test1::Test1(void)
{
//
}
Test1::~Test1(void)
{
//
}
void Test1::method1(void)
{
//
}
C#:
test1.cs:
public class Test1
{
public Test1() {
//
}
Java:
Test1.java:
public class Test1 {
public Test1() {
//
}
Encapsulation
Encapsulation refers to the hiding of implementation details such that the only way to
interact with an object is through a well-defined interface. This is commonly done by
defining access levels.
Delphi has 4 commonly used access levels for members: private, protected, public and
published. Members marked as private are accessible to the members of the class or any
method defined in the same unit's implementation section. Members marked as protected
are accessible by any method of the class or its descendant units; the descendant units can
be defined in different units. Public members are accessible to all. Published members
behave similar to public members, except the compiler generates extra RunTime Type
Information (RTTI). The default access level is public.
The access to a class is determined by which section of the unit the class is defined in:
classes defined in the interface section are accessible to all, classes defined in the
implementation section are only accessible by methods defined in the same unit.
In addition to these, Delphi.NET introduced the access levels of "strict private" and
"strict protected" which essentially behave the same as the classic private and protected
access levels except unit scope has been removed.
Unit1.pas:
unit Unit1;
interface
type
TTest1 = class
private
FPrivateField: Integer;
protected
procedure ProtectedMethod;
public
constructor Create;
destructor Destroy; override;
procedure PublicMethod;
published
procedure PublishedMethod;
end;
implementation
{ TTest1 }
constructor TTest1.Create;
begin
inherited Create;
//
end;
destructor TTest1.Destroy;
begin
//
inherited Destroy;
end;
procedure TTest1.ProtectedMethod;
begin
//
end;
procedure TTest1.PublicMethod;
begin
//
end;
procedure TTest1.PublishedMethod;
begin
//
end;
end.
C++ defines 3 access levels: private, protected and public. The following definitions are
taken from Bjarne Stroustrup's C++ Glossary:
• private base: a base class declared private in a derived class, so that the base's
public members are accessible only from that derived class.
• private member: a member accessible only from its own class.
• protected base: a base class declared protected in a derived class, so that the base's
public and protected members are accessible only in that derived class and classes
derived from that.
• protected member: a member accessible only from classes derived from its class.
• public base: a base class declared public in a derived class, so that the base's
public members are accessible to the users of that derived class.
• public member: a member accessible to all users of a class.
In addition to these access levels, C++ also has the notion of a friend class and friend
functions:
test1.h:
#ifndef Test1_H
#define Test1_H
class Test1 {
private:
int privateField;
protected:
void protectedMethod(void);
public:
Test1(void);
~Test1(void);
void publicMethod(void);
};
#endif // Test1_H
test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
Test1::Test1(void)
{
//
}
Test1::~Test1(void)
{
//
}
void Test1::protectedMethod(void)
{
//
}
void Test1::publicMethod(void)
{
//
}
C# defines 5 access modifiers for class members: private, protected, internal, protected
internal and public. Of these access modifiers, private is the default and members marked
as such are accessible only to the class in which it is defined. A member declared as
protected is accessible to the class wherein it is declared and also in any classes which
derive from that class. The internal access modifier specifies that the member is
accessible to any classes defined in the same assembly. A member declared as protected
internal will be accessible by any derived class as well as any classes defined in the same
assembly. A public member is accessible to all.
The same access modifiers are available for use on classes as well, though private,
protected and protected internal are only applicable to nested classes. The default access
level for a non-nested class if no access modifier is specified is internal. The default
access level for a nested class if no access modifier is specified is private.
test1.cs:
public class Test1
{
public Test1() {
//
}
Java defines 4 access levels for class members. The public modifier specifies that the
member is visible to all. The protected modifier specifies that the member is visible to
subclasses and to code in the same package. The private modifier specifies that the
member is visible only to code in the same class. If none of these modifiers are used, then
the default access level is assumed; that is, the member is visible to all code in the
package.
Java also defines the following access modifiers for classes: public, protected and private.
Of these, protected and private are only applicable for nested classes. Declaring a class as
public makes the class accessible to all code. If no access modifier is specified, the
default access level of package level access is assumed.
Test1.java:
public class Test1 {
public Test1() {
//
}
void packageMethod() {
//
}
}
Inheritance
Inheritance refers to the ability to reuse a class to create a new class with added
functionality. The new class, also referred to as the descendant class, derived class or
subclass, is said to inherit the functionality of the ancestor class, base class or superclass.
C++ supports multiple implementation inheritance; that is, a derived class has more than
one immediate base class to inherit its implementation from. In contrast to this, Delphi,
C# and Java support single implementation inheritance, wherein the descendant class or
subclass can only have one direct ancestor class or superclass to inherit its
implementation from. They do, however, support using multiple interface inheritance, an
interface being an abstract type that defines method signatures but do not have an
implementation.
Delphi:
Unit1.pas:
unit Unit1;
interface
type
TBaseClass = class(TInterfacedObject)
public
constructor Create;
destructor Destroy; override;
end;
IFooInterface = interface
procedure Foo;
end;
IBarInterface = interface
function Bar: Integer;
end;
implementation
{ TBaseClass }
constructor TBaseClass.Create;
begin
inherited Create;
//
end;
destructor TBaseClass.Destroy;
begin
//
inherited Destroy;
end;
{ TDerivedClass }
procedure TDerivedClass.Foo;
begin
//
end;
end.
C++:
test1.h:
#ifndef Test1_H
#define Test1_H
class Base1 {
public:
void foo(void);
};
class Base2 {
public:
int bar(void);
};
test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
void Base1::foo(void)
{
//
}
int Base2::bar(void)
{
return 0;
}
DerivedClass::DerivedClass(void)
{
//
}
DerivedClass::~DerivedClass(void)
{
//
}
C#:
test1.cs:
public class BaseClass
{
public BaseClass() {
//
}
}
Java:
Test1.java:
class BaseClass {
public BaseClass() {
//
}
}
interface FooInterface {
void foo();
}
interface BarInterface {
int bar();
}
class DerivedClass extends BaseClass implements FooInterface,
BarInterface {
public void foo() {
//
}
Virtual Methods
Virtual methods allow a method call, at run-time, to be directed to the appropriate code,
appropriate for the type of the object instance used to make the call. In essence, the
method is bound at run-time instead of at compile-time. The method is declared as virtual
in the base class and then overridden in the derived class. Virtual methods are an
important part of polymorphism since the same method call can produce results
appropriate to the object instance used to make the call.
Both Delphi and C# require the overriding code to be explicitly declared as an override.
In C#, the new keyword must be used in derived classes to define a method in the derived
class that hides the base class method. In Java, all methods are virtual by default unless
the method is marked as final. Also, final methods in Java cannot be hidden.
Delphi:
Unit1.pas:
unit Unit1;
interface
type
TBase = class
public
function NonVirtualMethod: string;
function VirtualMethod: string; virtual;
end;
TDerived = class(TBase)
public
function NonVirtualMethod: string;
function VirtualMethod: string; override;
end;
implementation
{ TBase }
{ TDerived }
end.
Project1.dpr:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils,
Unit1 in 'Unit1.pas';
var
Foo, Bar: TBase;
begin
Foo := TBase.Create;
Bar := TDerived.Create;
try
WriteLn(Foo.NonVirtualMethod);
WriteLn(Foo.VirtualMethod);
WriteLn(Bar.NonVirtualMethod);
WriteLn(Bar.VirtualMethod);
finally
Bar.Free;
Foo.Free;
end;
end.
This should produce the following output:
[c:\borland\delphi7\projects]Project1.exe
TBase.NonVirtualMethod
TBase.VirtualMethod
TBase.NonVirtualMethod
TDerived.VirtualMethod
[c:\borland\delphi7\projects]
C++:
test1.h:
#ifndef Test1_H
#define Test1_H
#include <string>
class Base {
public:
string nonVirtualMethod(void);
virtual string virtualMethod(void);
};
test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include <string>
string Base::nonVirtualMethod(void)
{
string temp = "Base::nonVirtualMethod";
return temp;
}
string Base::virtualMethod(void)
{
string temp = "Base::virtualMethod";
return temp;
}
string Derived::nonVirtualMethod(void)
{
string temp = "Derived::nonVirtualMethod";
return temp;
}
string Derived::virtualMethod(void)
{
string temp = "Derived::virtualMethod";
return temp;
}
main.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include <string>
#include <iostream>
int main()
{
auto_ptr<Base> foo(new Base);
auto_ptr<Base> bar(new Derived);
cout << foo->nonVirtualMethod() << endl;
cout << foo->virtualMethod() << endl;
cout << bar->nonVirtualMethod() << endl;
cout << bar->virtualMethod() << endl;
}
makefile:
# Macros
TOOLSROOT=C:\Borland\BCC55
INCLUDEDIR=$(TOOLSROOT)\Include
LIBDIR=$(TOOLSROOT)\Lib;$(TOOLSROOT)\Lib\PSDK
COMPILER=$(TOOLSROOT)\bin\bcc32.exe
COMPILERSWTS=-tWC -c -I$(INCLUDEDIR)
LINKER=$(TOOLSROOT)\bin\ilink32.exe
LINKERSWTS=-ap -Tpe -x -Gn -L$(LIBDIR)
OBJFILES=test1.obj main.obj
LIBFILES=cw32.lib import32.lib
BASEOUTPUTFILE=main
EXEFILE=$(BASEOUTPUTFILE).exe
#implicit rules
.cpp.obj:
$(COMPILER) $(COMPILERSWTS) $<
# Explicit rules
default: $(EXEFILE)
$(EXEFILE): $(OBJFILES)
$(LINKER) $(LINKERSWTS) c0x32.obj $(OBJFILES),$(EXEFILE),,$
(LIBFILES),,
clean:
del $(OBJFILES)
del $(EXEFILE)
del $(BASEOUTPUTFILE).tds
[c:\borland\bcc55\projects]main.exe
Base::nonVirtualMethod
Base::virtualMethod
Base::nonVirtualMethod
Derived::virtualMethod
[c:\borland\bcc55\projects]
C#:
test1.cs:
using System;
[d:\source\csharp\code]test1.exe
BaseClass.NonVirtualMethod
BaseClass.VirtualMethod
BaseClass.NonVirtualMethod
DerivedClass.VirtualMethod
[d:\source\csharp\code]
Java:
Test1.java:
class BaseClass {
public final String finalMethod() {
return "BaseClass.finalMethod";
}
[d:\source\java\code]java.exe Test1
BaseClass.finalMethod
BaseClass.virtualMethod
BaseClass.finalMethod
DerivedClass.virtualMethod
[d:\source\java\code]
Destructors serve the opposite purpose of constructors. While constructors take care of
allocating memory and other resources that the object requires during its lifetime, the
destructor's purpose is to ensure that the resources are properly de-allocated as the object
is being destroyed, and the memory being used by the object is freed. In Delphi and C++
(i.e. unmanaged environments) the programmer is responsible for ensuring that an
object's destructor is called once the object is no longer needed.
Finalizers are somewhat similar to destructors but (1) they are non-deterministic, (2) they
are there to release unmanaged resources only. That is, in a managed environment (e.g.
Java Virtual Machine, .NET Common Language Runtime) finalizers are called by the
managed environment, not by the programmer. Hence, there is no way to determine at
which point in time or in which order a finalizer will be called. Also, memory, which is
considered a managed resource, is not freed by the finalizer, but by the garbage collector.
Since most classes written will only be manipulating objects in memory, most objects in
a managed environment will not need a finalizer.
In Delphi, constructors are defined using the constructor keyword and destructors are
defined using the destructor keyword. Delphi, by convention, names its constructor
Create and its destructor Destroy, but these names are not requirements. Also, Delphi
constructors can be made virtual and the default destructor Destroy is virtual. To invoke a
constructor in Delphi, the convention ClassName.ConstructorName is used e.g. if a class
named TMyClass has a constructor Create then constructing an instance is done using
"<variable> := TMyClass.Create;" To invoke the destructor, call the Free method, not the
Destroy method e.g. "<variable>.Free".
In C++, C# and Java, a constructor is defined by creating a method with the same name
as the class. C# and Java require the use of the 'new' keyword to invoke the constructor.
For example, constructing an instance of MyClass is done using "<variable> = new
MyClass()". C++ requires the use of the 'new' keyword if the object is to be allocated on
the heap, otherwise simply declaring a variable of the same type as the class with invoke
the constructor and create the object on the stack. In C++, C# and Java constructors must
be named the same as the name of the class.
Constructors in Delphi do not implicitly call the contructors for the base class. Instead,
the programmer must make an explicit call. In C++, C# and Java, a constructor will
implicitly call the constructors for its base class(es).
Destructors in C# follow the same convention as C++, that is, the destructor must be
named the same as the name of the class, but preceded by a tilde (~). (This syntax often
misleads developers with a C++ background to expect a C# destructor to behave like a
C++ destructor (i.e. it can be called in a deterministic fashion) when in fact it behaves
like a finalizer.) To implement a finalizer in Java, the developer needs only to override
the finalize() method of java.lang.Object.
C++ objects allocated on the stack will automatically have their destructor called when
the object goes out of scope; objects explicitly allocated in the heap using the 'new'
keyword must have its destructor invoked using the 'delete' keyword. While both C#
destructors and Java finalizers are non-deterministic C# destructors are guaranteed to be
called by the .NET CLR (except in cases of an ill-behaved application e.g. one that
crashes, forces the finalizer thread into an infinite loop, etc.) and when they are called,
will in turn implicitly call the other destructors in the inheritance chain, from most
derived class to least derived class. Such is not the case with Java finalizers; finalizers
must explicitly invoke the finalize() method of its superclass and they are not guaranteed
to be invoked.
Delphi, C++, C# and Java all provide default constructors if none are defined in the
current class. All support overloading of constructors.
Delphi:
Unit1.pas:
unit Unit1;
interface
type
TBase = class
constructor Create;
destructor Destroy; override;
end;
TDerived = class(TBase)
constructor Create;
destructor Destroy; override;
end;
TMoreDerived = class(TDerived)
constructor Create;
destructor Destroy; override;
end;
implementation
{ TBase }
constructor TBase.Create;
begin
WriteLn('In TBase.Create');
inherited;
end;
destructor TBase.Destroy;
begin
WriteLn('In TBase.Destroy');
inherited;
end;
{ TDerived }
constructor TDerived.Create;
begin
WriteLn('In TDerived.Create');
inherited;
end;
destructor TDerived.Destroy;
begin
WriteLn('In TDerived.Destroy');
inherited;
end;
{ TMoreDerived }
constructor TMoreDerived.Create;
begin
WriteLn('In TMoreDerived.Create');
inherited;
end;
destructor TMoreDerived.Destroy;
begin
WriteLn('In TMoreDerived.Destroy');
inherited;
end;
end.
Project1.dpr:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils,
Unit1 in 'Unit1.pas';
var
Foo: TBase;
begin
Foo := TBase.Create;
Foo.Free;
WriteLn;
Foo := TDerived.Create;
Foo.Free;
WriteLn;
Foo := TMoreDerived.Create;
Foo.Free;
end.
[c:\borland\delphi7\projects]Project1.exe
In TBase.Create
In TBase.Destroy
In TDerived.Create
In TBase.Create
In TDerived.Destroy
In TBase.Destroy
In TMoreDerived.Create
In TDerived.Create
In TBase.Create
In TMoreDerived.Destroy
In TDerived.Destroy
In TBase.Destroy
[c:\borland\delphi7\projects]
C++:
test1.h:
#ifndef Test1_H
#define Test1_H
class Base1 {
public:
Base1(void);
~Base1(void);
};
class Base2 {
public:
Base2(void);
~Base2(void);
};
test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include <iostream>
using std::cout;
using std::endl;
Base1::Base1(void)
{
cout << "In Base1::Base1(void)" << endl;
}
Base1::~Base1(void)
{
cout << "In Base1::~Base1(void)" << endl;
}
Base2::Base2(void)
{
cout << "In Base2::Base2(void)" << endl;
}
Base2::~Base2(void)
{
cout << "In Base2::~Base2(void)" << endl;
}
Derived::Derived(void)
{
cout << "In Derived::Derived(void)" << endl;
}
Derived::~Derived(void)
{
cout << "In Derived::~Derived(void)" << endl;
}
main.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include <iostream>
using std::cout;
using std::endl;
int main()
{
Base1 a;
cout << endl;
Base2 b;
cout << endl;
Derived c;
cout << endl;
}
makefile:
# Macros
TOOLSROOT=C:\Borland\BCC55
INCLUDEDIR=$(TOOLSROOT)\Include
LIBDIR=$(TOOLSROOT)\Lib;$(TOOLSROOT)\Lib\PSDK
COMPILER=$(TOOLSROOT)\bin\bcc32.exe
COMPILERSWTS=-tWC -c -I$(INCLUDEDIR)
LINKER=$(TOOLSROOT)\bin\ilink32.exe
LINKERSWTS=-ap -Tpe -x -Gn -L$(LIBDIR)
OBJFILES=test1.obj main.obj
LIBFILES=cw32.lib import32.lib
BASEOUTPUTFILE=main
EXEFILE=$(BASEOUTPUTFILE).exe
#implicit rules
.cpp.obj:
$(COMPILER) $(COMPILERSWTS) $<
# Explicit rules
default: $(EXEFILE)
$(EXEFILE): $(OBJFILES)
$(LINKER) $(LINKERSWTS) c0x32.obj $(OBJFILES),$(EXEFILE),,$
(LIBFILES),,
clean:
del $(OBJFILES)
del $(EXEFILE)
del $(BASEOUTPUTFILE).tds
[c:\borland\bcc55\projects]main.exe
In Base1::Base1(void)
In Base2::Base2(void)
In Base1::Base1(void)
In Base2::Base2(void)
In Derived::Derived(void)
In Derived::~Derived(void)
In Base2::~Base2(void)
In Base1::~Base1(void)
In Base2::~Base2(void)
In Base1::~Base1(void)
[c:\borland\bcc55\projects]
C#:
test1.cs:
using System;
class BaseClass
{
public BaseClass() {
Console.WriteLine("In BaseClass constructor");
}
~BaseClass() {
Console.WriteLine("In BaseClass destructor");
}
}
~DerivedClass() {
Console.WriteLine("In DerivedClass destructor");
}
}
class MoreDerivedClass: DerivedClass
{
public MoreDerivedClass() {
Console.WriteLine("In MoreDerivedClass constructor");
}
~MoreDerivedClass() {
Console.WriteLine("In MoreDerivedClass destructor");
}
}
This should produce similar output (results may vary due to the non-deterministic nature
of finalization):
[d:\source\csharp\code]test1.exe
In BaseClass constructor
In BaseClass constructor
In DerivedClass constructor
In BaseClass constructor
In DerivedClass constructor
In MoreDerivedClass constructor
In MoreDerivedClass destructor
In DerivedClass destructor
In BaseClass destructor
In DerivedClass destructor
In BaseClass destructor
In BaseClass destructor
[d:\source\csharp\code]
Java:
Test1.java:
class BaseClass {
public BaseClass() {
System.out.println("In BaseClass constructor");
}
This should produce similar output (results may vary due to the non-deterministic nature
of finalization):
[d:\source\java\code]java.exe Test1
In BaseClass constructor
In BaseClass constructor
In DerivedClass constructor
In BaseClass constructor
In DerivedClass constructor
In MoreDerivedClass constructor
[d:\source\java\code]
Delphi:
Unit1.pas:
unit Unit1;
interface
type
TAbstractBaseClass = class
public
procedure Method1; virtual; abstract;
end;
TAbstractIntermediateClass = class(TAbstractBaseClass)
public
procedure Method1; override; abstract;
procedure Method2; virtual;
end;
TConcreteClass = class(TAbstractIntermediateClass)
public
procedure Method1; override;
end;
implementation
{ TAbstractIntermediateClass }
procedure TAbstractIntermediateClass.Method2;
begin
//
end;
{ TConcreteClass }
procedure TConcreteClass.Method1;
begin
inherited Method1;
//
end;
end.
C++:
test1.h:
#ifndef Test1_H
#define Test1_H
class AbstractBaseClass {
public:
virtual void method1(void)=0;
};
test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
void AbstractIntermediateClass::method2(void)
{
//
}
void ConcreteClass::method1(void)
{
//
}
C#:
test1.cs:
public abstract class AbstractBaseClass
{
public abstract void Method1();
}
Java:
Test1.java:
abstract class AbstractBaseClass {
public abstract void method1();
}
Interfaces
An interface is an abstract type that defines members but does not provide an
implementation. Interfaces are used to define a contract; a class that implements an
interface is expected to adhere to the contract provided by the interface and provide
implementations for all the members defined in the interface. Delphi, C# and Java
support interfaces. C++ does not though an abstract class composed purely of public pure
virtual methods could serve a similar purpose.
Method Overloading
Method overloading refers to the ability to define several methods with the same name
but with different signatures (parameter types, number of parameters). Delphi requires
that overloaded methods be marked with the overload keyword.
Delphi:
Unit1.pas:
unit Unit1;
interface
type
TFoo = class
public
procedure MethodA;
end;
TBar = class(TFoo)
public
procedure MethodA(const I: Integer); overload;
procedure MethodB(const I: Integer); overload;
procedure MethodB(const I: Integer; const S: string); overload;
end;
TFooBar = class(TBar)
public
procedure MethodA(const S: string); overload;
function MethodC(const I: Integer): Integer; overload;
function MethodC(const S: string): Integer; overload;
end;
implementation
{ TFoo }
procedure TFoo.MethodA;
begin
//
end;
{ TBar }
{ TFooBar }
end.
C++:
test1.h:
#ifndef Test1_H
#define Test1_H
#include <string>
class Foo {
public:
void methodA(void);
};
test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include <string>
void Foo::methodA(void)
{
//
}
void Bar::methodA(int i)
{
//
}
void Bar::methodB(int i)
{
//
}
int FooBar::methodC(int i)
{
return 0;
}
int FooBar::methodC(string s)
{
return 0;
}
C#:
test1.cs:
public class Foo
{
public void MethodA() {
//
}
}
Java:
Test1.java:
class Foo {
public void methodA() {
//
}
}
Operator Overloading
Operator overloading is the process of providing new implementations for built-in
operators (such as '+' and '-' for example) when the operands are user-defined types such
as classes. This can simplify the usage of a class and make it more intuitive.
Operator overloading is available in C++ and C# and has recently been added into
Delphi.NET. It is not available in the current Win32 implementation of Delphi, nor is it
available in Java.
The following examples create a simple class for manipulating complex numbers. It
overloads the '+' and '-' operators.
Delphi.NET:
Unit1.pas:
unit Unit1;
interface
type
TComplexNumber = class
public
Real: Integer;
Imaginary: Integer;
class operator Add(const X, Y: TComplexNumber): TComplexNumber;
class operator Subtract(const X, Y: TComplexNumber): TComplexNumber;
function ToString: string; override;
constructor Create(const Real, Imaginary: Integer);
end;
implementation
uses
SysUtils;
{ TComplexNumber }
end.
Project1.dpr:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils,
Unit1 in 'Unit1.pas';
var
A, B: TComplexNumber;
begin
A := TComplexNumber.Create(2, 5);
B := TComplexNumber.Create(4, -3);
WriteLn(Format('A = (%s), B = (%s)', [A, B]));
WriteLn(Format('A + B = (%s)', [A + B]));
WriteLn(Format('A - B = (%s)', [A - B]));
end.
[d:\source\delphi.net\code]Project1.exe
A = (2 + 5i), B = (4 - 3i)
A + B = (6 + 2i)
A - B = (-2 + 8i)
[d:\source\delphi.net\code]
C++:
test1.h:
#ifndef Test1_H
#define Test1_H
#include <string>
test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include <string>
#include <sstream>
#include <cmath>
string itos(int i)
{
stringstream s;
s << i;
return s.str();
}
main.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include <iostream>
int main()
{
ComplexNumber a (2, 5);
ComplexNumber b (4, -3);
cout << "a = (" << a.toString() << "), b = (" << b.toString() << ")"
<< endl;
cout << "a + b = (" << (a + b).toString() << ")" << endl;
cout << "a - b = (" << (a - b).toString() << ")" << endl;
}
makefile:
# Macros
TOOLSROOT=C:\Borland\BCC55
INCLUDEDIR=$(TOOLSROOT)\Include
LIBDIR=$(TOOLSROOT)\Lib;$(TOOLSROOT)\Lib\PSDK
COMPILER=$(TOOLSROOT)\bin\bcc32.exe
COMPILERSWTS=-tWC -c -I$(INCLUDEDIR)
LINKER=$(TOOLSROOT)\bin\ilink32.exe
LINKERSWTS=-ap -Tpe -x -Gn -L$(LIBDIR)
OBJFILES=test1.obj main.obj
LIBFILES=cw32.lib import32.lib
BASEOUTPUTFILE=main
EXEFILE=$(BASEOUTPUTFILE).exe
#implicit rules
.cpp.obj:
$(COMPILER) $(COMPILERSWTS) $<
# Explicit rules
default: $(EXEFILE)
$(EXEFILE): $(OBJFILES)
$(LINKER) $(LINKERSWTS) c0x32.obj $(OBJFILES),$(EXEFILE),,$
(LIBFILES),,
clean:
del $(OBJFILES)
del $(EXEFILE)
del $(BASEOUTPUTFILE).tds
This should produce the following output:
[c:\borland\bcc55\projects]main.exe
a = (2 + 5i), b = (4 - 3i)
a + b = (6 + 2i)
a - b = (-2 + 8i)
[c:\borland\bcc55\projects]
C#:
test1.cs:
using System;
class ComplexNumber
{
public int Real;
public int Imaginary;
[d:\source\csharp\code]test1.exe
a = (2 + 5i), b = (4 - 3i)
a + b = (6 + 2i)
a - b = (-2 + 8i)
[d:\source\csharp\code]
Properties
Properties can be described as object-oriented data members. External to the class,
properties look just like data members. Internally, however, properties can be
implemented as methods. Properties promote encapsulation by allowing the class to hide
the internal representation of its data. Also, by implementing only a getter (also known as
an accessor) method or a setter (also known as a mutator) method, a property can be
made read-only or write-only respectively, thus allowing the class to provide access
control.
Delphi and C# have support for properties built into the language. C++ and Java do not
have support for properties built in but can loosely implement this behavior using a
get/set convention.
Delphi:
Unit1.pas:
unit Unit1;
interface
type
TRectangle = class
private
FHeight: Cardinal;
FWidth: Cardinal;
protected
function GetArea: Cardinal;
public
function ToString: string; virtual;
constructor Create(const Width, Height: Cardinal);
property Area: Cardinal read GetArea;
property Height: Cardinal read FHeight write FHeight;
property Width: Cardinal read FWidth write FWidth;
end;
implementation
uses
SysUtils;
{ TRectangle }
end.
Project1.dpr:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils,
Unit1 in 'Unit1.pas';
begin
with TRectangle.Create(2, 3) do
try
WriteLn(ToString);
Height := 4;
Width := 3;
WriteLn(ToString);
finally
Free;
end;
end.
[c:\borland\delphi7\projects]
C#:
test1.cs:
using System;
class Rectangle
{
private uint _height;
private uint _width;
[d:\source\csharp\code]test1.exe
Height: 3, Width: 2, Area: 6
Height: 4, Width: 3, Area: 12
[d:\source\csharp\code]
Exceptions
Exceptions provide a uniform mechanism for handling errors. Exceptions are created and
raised or thrown when the code enters a condition that it cannot or should not handle by
itself. Once the exception is raised or thrown, the exception handling mechanism breaks
out of the normal flow of code execution and searches for an appropriate exception
handler. If an appropriate exception handler is found, then the error condition is handled
in an appropriate manner and normal code execution resumes. Otherwise, the application
is assumed to be in an unstable / unpredictable state and exits with an error.
Delphi provides the raise keyword to create an exception and a try...except and
try...finally construct (but no unified try...except...finally construct though a try...finally
block can be nested within a try...except block to emulate this behavior) for exception
handling. Code written in a try block is executed and if an exception occurs, the
exception handling mechanism searches for an appropriate except block to handle the
exception. The on keyword is used to specify which exceptions an except block can
handle. A single except block can contain multiple on keywords. If the on keyword is not
used, then the except block will handle all exceptions. Code written in a finally block is
guaranteed to execute regardless of whether or not an exception is raised.
Unit1.pas:
unit Unit1;
interface
uses
SysUtils;
type
EMyException = class(Exception);
TMyClass = class
public
procedure MyMethod;
constructor Create;
destructor Destroy; override;
end;
implementation
{ TMyClass }
procedure TMyClass.MyMethod;
begin
WriteLn('Entering TMyClass.MyMethod');
raise EMyException.Create('Exception raised in TMyClass.MyMethod!');
WriteLn('Leaving TMyClass.MyMethod'); // this should never get
executed
end;
constructor TMyClass.Create;
begin
inherited Create;
WriteLn('In TMyClass.Create');
end;
destructor TMyClass.Destroy;
begin
WriteLn('In TMyClass.Destroy');
inherited Destroy;
end;
end.
Project1.dpr:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils,
Unit1 in 'Unit1.pas';
var
MyClass: TMyClass;
begin
MyClass := TMyClass.Create;
try
try
MyClass.MyMethod;
except
on EMyException do
WriteLn(ErrOutput, 'Caught an EMyException!');
end;
finally
MyClass.Free;
end;
end.
[c:\borland\delphi7\projects]Project1.exe
In TMyClass.Create
Entering TMyClass.MyMethod
Caught an EMyException!
In TMyClass.Destroy
[c:\borland\delphi7\projects]
C++ provides the throw keyword for creating an exception and provides a try...catch
construct for exception handling. Code written in the try block is executed and if an
exception is thrown, the exception handling mechanism looks for an appropriate catch
block to deal with the exception. A single try block can have multiple catch blocks
associated with it. Catch blocks have a required exception declaration to specify what
type of exception it can handle. An exception declaration of (...) means that the catch
block will catch all exceptions.
A function can optionally specify what exceptions it can throw via an exception
specification in the function declaration. The throw keyword is also used for this purpose.
If no exception specification is present then the function can throw any exception.
test1.h:
#ifndef Test1_H
#define Test1_H
#include <string>
using std::string;
class MyException {
public:
MyException(string);
string getMessage() const;
private:
string message;
};
class MyClass {
public:
MyClass();
~MyClass();
void myMethod();
};
#endif // Test1_H
test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include <iostream>
using std::cout;
using std::endl;
MyException::MyException(string errorMessage)
{
message = errorMessage;
}
MyClass::MyClass()
{
cout << "In MyClass::MyClass()" << endl;
}
MyClass::~MyClass()
{
cout << "In MyClass::~MyClass()" << endl;
}
void MyClass::myMethod()
{
cout << "Entering MyClass::myMethod()" << endl;
throw MyException("Exception thrown in MyClass::myMethod()!");
cout << "Leaving MyClass::myMethod()" << endl; // this should never
get executed
}
main.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include <iostream>
using std::cerr;
using std::endl;
int main()
{
MyClass myClass;
try {
myClass.myMethod();
}
catch(MyException) {
cerr << "Caught a MyException!" << endl;
}
}
makefile:
# Macros
TOOLSROOT=C:\Borland\BCC55
INCLUDEDIR=$(TOOLSROOT)\Include
LIBDIR=$(TOOLSROOT)\Lib;$(TOOLSROOT)\Lib\PSDK
COMPILER=$(TOOLSROOT)\bin\bcc32.exe
COMPILERSWTS=-tWC -c -I$(INCLUDEDIR)
LINKER=$(TOOLSROOT)\bin\ilink32.exe
LINKERSWTS=-ap -Tpe -x -Gn -L$(LIBDIR)
OBJFILES=test1.obj main.obj
LIBFILES=cw32.lib import32.lib
BASEOUTPUTFILE=main
EXEFILE=$(BASEOUTPUTFILE).exe
#implicit rules
.cpp.obj:
$(COMPILER) $(COMPILERSWTS) $<
# Explicit rules
default: $(EXEFILE)
$(EXEFILE): $(OBJFILES)
$(LINKER) $(LINKERSWTS) c0x32.obj $(OBJFILES),$(EXEFILE),,$
(LIBFILES),,
clean:
del $(OBJFILES)
del $(EXEFILE)
del $(BASEOUTPUTFILE).tds
[c:\borland\bcc55\projects]main.exe
In MyClass::MyClass()
Entering MyClass::myMethod()
Caught a MyException!
In MyClass::~MyClass()
[c:\borland\bcc55\projects]
C# uses the throw keyword to throw an exception and provides try...catch, try...finally
and try...catch...finally constructs for exception handling. Code in the try block is
executed and if an exception is thrown, the exception handling mechanism searches for
an appropriate catch block. A single try block can have multiple catch blocks associated
with it. A catch block can declare the exception type that it handles. If a catch clause does
not specify the exception type it will catch all exceptions thrown. Code in a finally block
will be executed regardless of whether or not an exception is thrown.
using System;
~MyClass() {
Console.WriteLine("In MyClass.~MyClass()");
}
[d:\source\csharp\code]test1.exe
In MyClass.MyClass()
Entering MyClass.MyMethod()
Caught a MyException!
In finally block
In MyClass.~MyClass()
[d:\source\csharp\code]
Java uses the throw keyword to throw an exception and provides try...catch, try...finally
and try...catch...finally constructs for exception handling. Code inside a try block is
executed and if an exception is thrown, the exception handling mechanism searches for
an appropriate catch block. A single try block can have multiple catch blocks associated
with it. Catch blocks are required to specify the exception type that they handle. Code in
a finally block will be executed regardless of whether or not an exception is thrown.
Java also requires that methods either catch or specify all checked exceptions that can be
thrown within the scope of the method. This is done using the throws clause of the
method declaration.
Test1.java:
class MyException extends Exception { };
class MyClass {
public MyClass() {
System.out.println("In MyClass.MyClass()");
}
protected void finalize() throws Throwable {
System.out.println("In MyClass.finalize()");
}
public void myMethod() throws MyException {
System.out.println("Entering myClass.myMethod()");
throw new MyException();
}
}
[d:\source\java\code]java.exe Test1
In MyClass.MyClass()
Entering myClass.myMethod()
Caught a MyException!
In finally block
[d:\source\java\code]
Generics and Templates
Templates and Generics are constructs for parametric polymorphism. That is, they make
creating parameterized types for creating type safe collections. However, they do not
refer to the same thing i.e. templates are not generics and generics are not templates. For
more information on what the differences are, refer to the following external links:
Currently, only the C++ language supports templates. Generic support is planned for
version 2.0 of the C# language and for version 1.5 of the Java language.
C++:
stacktemplate.h:
#ifndef StackTemplate_H
#define StackTemplate_H
#define EMPTYSTACK -1
template<class T>
class Stack {
public:
Stack(int);
~Stack();
bool push(const T&);
bool pop(T&);
private:
bool isEmpty() const;
bool isFull() const;
int stackSize;
int stackTop;
T* stackPtr;
};
template<class T>
Stack<T>::Stack(int maxStackSize)
{
stackSize = maxStackSize;
stackTop = EMPTYSTACK;
stackPtr = new T[stackSize];
}
template<class T>
Stack<T>::~Stack()
{
delete [] stackPtr;
}
template<class T>
bool Stack<T>::push(const T& param)
{
if (!isFull()) {
stackPtr[++stackTop] = param;
return true;
}
return false;
}
template<class T>
bool Stack<T>::pop(T& param)
{
if (!isEmpty()) {
param = stackPtr[stackTop--];
return true;
}
return false;
}
template<class T>
bool Stack<T>::isEmpty() const
{
return stackTop == EMPTYSTACK;
}
template<class T>
bool Stack<T>::isFull() const
{
return stackTop == stackSize - 1;
}
#endif
main.cpp:
#ifndef StackTemplate_H
#include "stacktemplate.h"
#endif
#include <iostream>
using std::cout;
using std::endl;
int main()
{
Stack<int> intStack (5);
int i = 2;
while(intStack.push(i)) {
cout << "pushing " << i << " onto intStack" << endl;
i += 2;
}
while(intStack.pop(i)) {
cout << "popped " << i << " from intStack" << endl;
}
cout << endl;
[c:\borland\bcc55\projects]main.exe
pushing 2 onto intStack
pushing 4 onto intStack
pushing 6 onto intStack
pushing 8 onto intStack
pushing 10 onto intStack
popped 10 from intStack
popped 8 from intStack
popped 6 from intStack
popped 4 from intStack
popped 2 from intStack
[c:\borland\bcc55\projects]
Further Reading
For more information, refer to the following links:
• Delphi:
o Delphi Basics : http://www.delphibasics.co.uk/
o Object Pascal Style Guide:
http://community.borland.com/soapbox/techvoyage/article/1,1795,10280,0
0.html
• C++:
o Bjarne Stroustrup's homepage - The C++ Programming Language:
http://www.research.att.com/~bs/C++.html
• C#:
o MSDN - C# Language Specification:
http://msdn.microsoft.com/library/default.asp?url=/library/en-
us/csspec/html/CSharpSpecStart.asp
o Standard ECMA-334 - C# Language Specification 2nd edition (December
2002): http://www.ecma-international.org/publications/standards/Ecma-
334.htm
• Java:
o Code Conventions for the Java™ Programming Language:
http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html
o The Java Language Specification: http://java.sun.com/docs/books/jls/