Comparing Object-Oriented Features of Delphi, C++, C# and Java

本文详细对比了Delphi、C++、C#和Java四种编程语言的面向对象特性,包括类和对象、封装、继承、虚拟方法等核心概念,并通过具体示例展示了不同语言中这些特性的实现方式。

Contents


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: Borland® Delphi™ 7 Enterprise Edition
  • C++: Borland® C++Builder Compiler and Command Line Tools free download (bcc32 v5.5)
  • C#: Microsoft® .NET Framework v1.1 SDK
  • Java: Java™ 2 SDK, Standard Edition 1.4.2_04

Objects and Classes

A class is a structure that contains data (also known as fields or attributes) and instructions for manipulating that data (also referred to as methods or behavior). Taken as a whole, a class should model a real world physical or conceptual object. An object is a dynamic instance of a class. That is, a class provides a blueprint for an object.

Here are examples for how classes are defined:

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() {
//
}

public void Method1() {
//
}
}


Java:

    
Test1.java:
public class Test1 {
public Test1() {
//
}

public void method1() {
//
}
}


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.

Here is a Delphi code example:

    
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:

  • friend: a function or class explicitly granted access to members of a class by that class.
  • friend function: a function declared as friend in a class so that it has the same access as the class' members without having to be within the scope of the class.

Here is a C++ code example:

     
  
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.

Here is a C# code example:

    
test1.cs:
public class Test1
{
public Test1() {
//
}

private int _privateField;

protected void ProtectedMethod() {
//
}

internal void InternalMethod() {
//
}

protected internal void ProtectedInternalMethod() {
//
}
}


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.

Here is a Java code example:

    
Test1.java:
public class Test1 {
public Test1() {
//
}

private int privateField;

protected void protectedMethod() {
//
}

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.

Here are some code examples:

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;

TDerivedClass = class(TBaseClass, IFooInterface, IBarInterface)
procedure Foo;
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;

function TDerivedClass.Bar: Integer;
begin
Result := 0;
end;

end.


C++:

     
  
test1.h:
#ifndef Test1_H
#define Test1_H

class Base1 {
public:
void foo(void);
};


class Base2 {
public:
int bar(void);
};


class DerivedClass : public Base1, public Base2 {
public:
DerivedClass(void);
~DerivedClass(void);
};
#endif // Test1_H


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() {
//
}
}


public interface IFooInterface
{
void Foo();
}


public interface IBarInterface
{
int Bar();
}


public class DerivedClass : BaseClass, IFooInterface, IBarInterface
{
public void Foo() {
//
}

public int Bar() {
return 0;
}
}


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() {
//
}

public int bar() {
return 0;
}
}


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.

Here is some example code showing the use of virtual methods:

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 }

function TBase.NonVirtualMethod: string;
begin
Result := 'TBase.NonVirtualMethod';
end;

function TBase.VirtualMethod: string;
begin
Result := 'TBase.VirtualMethod';
end;

{ TDerived }

function TDerived.NonVirtualMethod: string;
begin
Result := 'TDerived.NonVirtualMethod';
end;

function TDerived.VirtualMethod: string;
begin
Result := 'TDerived.VirtualMethod';
end;

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>

using namespace std;

class Base {
public:
string nonVirtualMethod(void);
virtual string virtualMethod(void);
};


class Derived : public Base {
public:
string nonVirtualMethod(void);
virtual string virtualMethod(void);
};
#endif // Test1_H


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>

using namespace std;

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) ___FCKpd___14lt;

# 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
Base::nonVirtualMethod
Base::virtualMethod
Base::nonVirtualMethod
Derived::virtualMethod

[c:/borland/bcc55/projects]

C#:

    
test1.cs:
using System;

public class BaseClass
{
public string NonVirtualMethod() {
return "BaseClass.NonVirtualMethod";
}

public virtual string VirtualMethod() {
return "BaseClass.VirtualMethod";
}
}


public class DerivedClass : BaseClass
{
new public string NonVirtualMethod() {
return "DerivedClass.NonVirtualMethod";
}

public override string VirtualMethod() {
return "DerivedClass.VirtualMethod";
}
}


public class MainClass
{
public static void Main() {
BaseClass foo = new BaseClass();
BaseClass bar = new DerivedClass();
Console.WriteLine(foo.NonVirtualMethod());
Console.WriteLine(foo.VirtualMethod());
Console.WriteLine(bar.NonVirtualMethod());
Console.WriteLine(bar.VirtualMethod());
}
}

This should produce the following output:

[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";
}

public String virtualMethod() {
return "BaseClass.virtualMethod";
}
}


class DerivedClass extends BaseClass {
public String virtualMethod() {
return "DerivedClass.virtualMethod";
}
}


public class Test1 {
public static void main(String args[]) {
BaseClass foo = new BaseClass();
BaseClass bar = new DerivedClass();
System.out.println(foo.finalMethod());
System.out.println(foo.virtualMethod());
System.out.println(bar.finalMethod());
System.out.println(bar.virtualMethod());
}
}

This should produce the following output:

[d:/source/java/code]java.exe Test1
BaseClass.finalMethod
BaseClass.virtualMethod
BaseClass.finalMethod
DerivedClass.virtualMethod

[d:/source/java/code]

Constructors And Destructors / Finalizers

Constructors are special methods used to construct an instance of a class. They normally contain initialization code e.g. code for setting default values for data members, allocating resources, etc.. The objective of constructors is to allocate memory for the object and to initialize the object to a valid state before any processing is done.

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.

This should produce the following output:

[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);
};


class Derived : public Base1, public Base2 {
public:
Derived(void);
~Derived(void);
};
#endif // Test1_H


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) ___FCKpd___22lt;

# 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
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");
}
}


class DerivedClass: BaseClass
{
public DerivedClass() {
Console.WriteLine("In DerivedClass constructor");
}

~DerivedClass() {
Console.WriteLine("In DerivedClass destructor");
}
}


class MoreDerivedClass: DerivedClass
{
public MoreDerivedClass() {
Console.WriteLine("In MoreDerivedClass constructor");
}

~MoreDerivedClass() {
Console.WriteLine("In MoreDerivedClass destructor");
}
}


public class MainClass
{
public static void Main() {
BaseClass foo = new BaseClass();
Console.WriteLine();

foo = new DerivedClass();
Console.WriteLine();

foo = new MoreDerivedClass();
Console.WriteLine();
}
}

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");
}

protected void finalize() throws Throwable {
System.out.println("In BaseClass finalize()");
super.finalize();
}
}


class DerivedClass extends BaseClass {
public DerivedClass() {
System.out.println("In DerivedClass constructor");
}

protected void finalize() throws Throwable {
System.out.println("In DerivedClass finalize()");
super.finalize();
}
}


class MoreDerivedClass extends DerivedClass {
public MoreDerivedClass() {
System.out.println("In MoreDerivedClass constructor");
}

protected void finalize() throws Throwable {
System.out.println("In MoreDerivedClass finalize()");
super.finalize();
}
}


public class Test1 {
public static void main(String args[]) {
BaseClass foo = new BaseClass();
System.out.println();

foo = new DerivedClass();
System.out.println();

foo = new MoreDerivedClass();
System.out.println();
}
}

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]

Abstract Methods and Classes

An abstract method is a method which the class does not implement. In C++ terminology, such a method is termed a pure virtual function. A class which contains one or more abstract methods is often called an abstract class although in some cases the term abstract class is reserved for classes that contains only abstract methods. Abstract classes cannot be instantiated. Classes which derive from an abstract class should override and provide implementations for the abstract methods, or should themselves be marked also as abstract. Delphi uses the abstract keyword to mark a method as abstract. C# and Java use the abstract keyword to mark a method or class as abstract. A class marked as abstract is permitted but not required to have abstract members. C++ uses the =0 notation to indicate that a virtual function is a pure virtual function.

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;
};


class AbstractIntermediateClass : public AbstractBaseClass {
public:
virtual void method1(void)=0;
virtual void method2(void);
};


class ConcreteClass : public AbstractIntermediateClass {
public:
virtual void method1(void);
};
#endif // Test1_H


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();
}


public abstract class AbstractIntermediateClass : AbstractBaseClass
{
public override abstract void Method1();

public virtual void Method2() {
//
}
}


public class ConcreteClass : AbstractBaseClass
{
public override void Method1() {
//
}
}


Java:

    
Test1.java:
abstract class AbstractBaseClass {
public abstract void method1();
}


abstract class AbstractIntermediateClass extends AbstractBaseClass {
public abstract void method1();

public void method2() {
//
}
}


class ConcreteClass extends AbstractIntermediateClass {
public 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.

(See the earlier section on inheritance for code examples of interfaces.)


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 }

procedure TBar.MethodB(const I: Integer);
begin
//
end;

procedure TBar.MethodB(const I: Integer; const S: string);
begin
//
end;

procedure TBar.MethodA(const I: Integer);
begin
//
end;

{ TFooBar }

procedure TFooBar.MethodA(const S: string);
begin
//
end;

function TFooBar.MethodC(const I: Integer): Integer;
begin
Result := 0;
end;

function TFooBar.MethodC(const S: string): Integer;
begin
Result := 0;
end;

end.


C++:

     
  
test1.h:
#ifndef Test1_H
#define Test1_H

#include <string>

using namespace std;

class Foo {
public:
void methodA(void);
};


class Bar : public Foo {
public:
void methodA(int);
void methodB(int);
void methodB(int, string);
};


class FooBar : public Bar {
public:
void methodA(string);
int methodC(int);
int methodC(string);
};
#endif // Test1_H


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)
{
//
}

void Bar::methodB(int i, string s)
{
//
}


void FooBar::methodA(string s)
{
//
}

int FooBar::methodC(int i)
{
return 0;
}

int FooBar::methodC(string s)
{
return 0;
}


C#:

    
test1.cs:
public class Foo
{
public void MethodA() {
//
}
}


public class Bar : Foo
{
public void MethodA(int i) {
//
}

public void MethodB(int i) {
//
}

public void MethodB(int i, string s) {
//
}
}


public class FooBar : Bar
{
public void MethodA(string s) {
//
}

public int MethodC(int i) {
return 0;
}

public int MethodC(string s) {
return 0;
}
}


Java:

    
Test1.java:
class Foo {
public void methodA() {
//
}
}


class Bar extends Foo {
public void methodA(int i) {
//
}

public void methodB(int i) {
//
}

public void methodB(int i, String s) {
//
}
}


class FooBar extends Bar {
public void methodA(String s) {
//
}

public int methodC(int i) {
return 0;
}

public int methodC(String s) {
return 0;
}
}


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 }

constructor TComplexNumber.Create(const Real, Imaginary: Integer);
begin
inherited Create;
Self.Real := Real;
Self.Imaginary := Imaginary;
end;

class operator TComplexNumber.Add(const X,
Y: TComplexNumber): TComplexNumber;
begin
Result := TComplexNumber.Create(X.Real + Y.Real, X.Imaginary + Y.Imaginary);
end;

class operator TComplexNumber.Subtract(const X,
Y: TComplexNumber): TComplexNumber;
begin
Result := TComplexNumber.Create(X.Real - Y.Real, X.Imaginary - Y.Imaginary);
end;

function TComplexNumber.ToString: string;
begin
if (Self.Imaginary > 0) then
Result := Format('%d + %di', [Self.Real, Self.Imaginary])
else
Result := Format('%d - %di', [Self.Real, Abs(Self.Imaginary)]);
end;

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.

This should produce the following output:

[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>

using namespace std;

class ComplexNumber {
public:
int real;
int imaginary;
ComplexNumber operator+(const ComplexNumber&) const;
ComplexNumber operator-(const ComplexNumber&) const;
virtual string toString(void) const;
ComplexNumber(int, int);
};
#endif // Test1_H


test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include <string>
#include <sstream>
#include <cmath>

using namespace std;

string itos(int i)
{
stringstream s;
s << i;
return s.str();
}

ComplexNumber::ComplexNumber(int real = 0, int imaginary = 0)
{
this->real = real;
this->imaginary = imaginary;
}

ComplexNumber ComplexNumber::operator+(const ComplexNumber& param) const
{
ComplexNumber temp;
temp.real = this->real + param.real;
temp.imaginary = this->imaginary + param.imaginary;
return temp;
}

ComplexNumber ComplexNumber::operator-(const ComplexNumber& param) const
{
ComplexNumber temp;
temp.real = this->real - param.real;
temp.imaginary = this->imaginary - param.imaginary;
return temp;
}

string ComplexNumber::toString() const
{
if (this->imaginary > 0)
return itos(real) + " + " + itos(imaginary) + "i";
else
return itos(real) + " - " + itos(abs(imaginary)) + "i";
}


main.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include <iostream>

using namespace std;

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) ___FCKpd___38lt;

# 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;

public ComplexNumber(int real, int imaginary) {
this.Real = real;
this.Imaginary = imaginary;
}

public static ComplexNumber operator +(ComplexNumber a, ComplexNumber b) {
return new ComplexNumber(a.Real + b.Real, a.Imaginary + b.Imaginary);
}

public static ComplexNumber operator -(ComplexNumber a, ComplexNumber b) {
return new ComplexNumber(a.Real - b.Real, a.Imaginary - b.Imaginary);
}

public override string ToString() {
if (this.Imaginary > 0)
return (string.Format("{0} + {1}i", this.Real, this.Imaginary));
else
return (string.Format("{0} - {1}i", this.Real, Math.Abs(this.Imaginary)));
}
}


public class MainClass
{
public static void Main() {
ComplexNumber a = new ComplexNumber(2, 5);
ComplexNumber b = new ComplexNumber(4, -3);
Console.WriteLine(string.Format("a = ({0}), b = ({1})", a, b));
Console.WriteLine(string.Format("a + b = ({0})", a + b));
Console.WriteLine(string.Format("a - b = ({0})", a - b));
}
}

This should produce the following output:

[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 }

constructor TRectangle.Create(const Width, Height: Cardinal);
begin
inherited Create;
FHeight := Height;
FWidth := Width;
end;

function TRectangle.GetArea: Cardinal;
begin
Result := FWidth * FHeight;
end;

function TRectangle.ToString: string;
begin
Result := Format('Height: %d, Width: %d, Area: %d', [Height, Width, Area]);
end;

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.

This should produce the following output:

[c:/borland/delphi7/projects]Project1.exe
Height: 3, Width: 2, Area: 6
Height: 4, Width: 3, Area: 12

[c:/borland/delphi7/projects]

C#:

    
test1.cs:
using System;

class Rectangle
{
private uint _height;
private uint _width;

public Rectangle(uint width, uint height) {
_width = width;
_height = height;
}

public uint Area {
get { return _width * _height; }
}

public uint Height {
get { return _height; }
set { _height = value; }
}

public uint Width {
get { return _width; }
set { _width = value; }
}

public override string ToString() {
return string.Format("Height: {0}, Width: {1}, Area: {2}", Height, Width, Area);
}
}


public class MainClass
{
public static void Main() {
Rectangle MyRectangle = new Rectangle(2, 3);
Console.WriteLine(MyRectangle);
MyRectangle.Height = 4;
MyRectangle.Width = 3;
Console.WriteLine(MyRectangle);
}
}

This should produce the following output:

[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.

This should produce the following output:

[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;
}

string MyException::getMessage() const
{
return message;
}


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) ___FCKpd___48lt;

# 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
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;

class MyException: Exception { };

class MyClass
{
public MyClass() {
Console.WriteLine("In MyClass.MyClass()");
}

~MyClass() {
Console.WriteLine("In MyClass.~MyClass()");
}

public void MyMethod() {
Console.WriteLine("Entering MyClass.MyMethod()");
throw new MyException();
Console.WriteLine("Leaving MyClass.MyMethod()"); // this should never get executed
}
}

public class MainClass
{
public static void Main() {
MyClass myClass = new MyClass();
try {
myClass.MyMethod();
}
catch (MyException) {
Console.Error.WriteLine("Caught a MyException!");
}
finally {
Console.WriteLine("In finally block"); // will always get executed
}
}
}

This should produce similar output:

[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();
}
}

public class Test1 {
public static void main(String args[]) {
MyClass myClass = new MyClass();
try {
myClass.myMethod();
}
catch(MyException e) {
System.err.println("Caught a MyException!");
}
finally {
System.out.println("In finally block"); // will always get executed
}
}
}

This should produce similar output:

[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;

Stack<float> floatStack (5);
float f = 2.1;
while(floatStack.push(f)) {
cout << "pushing " << f << " onto floatStack" << endl;
f += 2.1;
}
while(floatStack.pop(f)) {
cout << "popping " << f << " from floatStack" << endl;
}
}

This should produce the following output:

[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

pushing 2.1 onto floatStack
pushing 4.2 onto floatStack
pushing 6.3 onto floatStack
pushing 8.4 onto floatStack
pushing 10.5 onto floatStack
popping 10.5 from floatStack
popping 8.4 from floatStack
popping 6.3 from floatStack
popping 4.2 from floatStack
popping 2.1 from floatStack

[c:/borland/bcc55/projects]

Further Reading

For more information, refer to the following links:

(Last updated on 2005-09-10.)

Valid XHTML 1.0 Strict

 
<think> We are being asked to show the LQ (Linear Quadratic) model dose-response curves comparing hyperfractionation and hypofractionation. The Linear Quadratic model is commonly used in radiobiology to describe the effect of radiation on cell survival. The basic equation is: S = exp(-αD - βG D²) where: S is the survival fraction of cells, D is the total dose, α and β are parameters for the linear and quadratic components of cell killing, and G is the dose protraction factor that accounts for the repair of sublethal damage between fractions. However, when we fractionate the dose into n equal fractions of dose d (so total dose D = n * d), we have: S = exp(-α * n * d - β * n * d²) But note: the standard LQ model for fractionated radiation without repopulation or time effects (for simplicity) is: S = exp(-αD - βD d) because the quadratic term is β times the total dose times the dose per fraction (d). Alternatively, we can write: S = exp(-αD - βD d) [since n*d = D and then the quadratic term is β * n * d² = β * D * d] To compare hyperfractionation and hypofractionation, we note: Hyperfractionation: more fractions (n) with smaller dose per fraction (d), total dose D may be similar or adjusted. Hypofractionation: fewer fractions (n) with larger dose per fraction (d), total dose D may be the same or different. The key point is that for the same total dose, a higher dose per fraction (hypofractionation) leads to more cell kill (lower survival) because the quadratic term (βDd) becomes more dominant as d increases. However, in clinical radiotherapy, we are often interested in the tumor control probability (TCP) or normal tissue complication probability (NTCP). The dose-response curves for these endpoints can be derived from the LQ model by assuming a Poisson distribution of clonogenic cell survival. Steps to generate the curves: 1. Calculate the survival fraction for given fractionation schemes. 2. Convert the survival fraction to a tumor control probability (TCP) using: TCP = exp(-λ * S * N0) where N0 is the initial number of clonogens, λ is a proportionality constant (often set to 1 for relative comparisons), and S is the survival fraction. However, for relative comparison of two fractionation schemes, we can fix N0 and λ and just compare S. 3. Alternatively, we can use the concept of biologically effective dose (BED) to compare fractionation schemes. The BED is given by: BED = D * [1 + d / (α/β)] Then, we can assume that the TCP is a sigmoid function of BED (e.g., logistic function). But note: the original question asks for dose-response curves. We can plot TCP vs total dose (D) for two fractionation schemes: one hyperfractionated and one hypofractionated. However, to keep the comparison fair, we should adjust the total dose so that we are comparing the same level of normal tissue damage? But the question does not specify. Therefore, we might simply fix the total dose and vary it to see the response. Alternatively, we can fix the BED to normal tissues (with a given α/β ratio) and then vary the total dose for the tumor (with a different α/β ratio) for each fractionation. But this becomes complex. Given the complexity, let's consider a simpler approach: Assume we are comparing two fractionation schemes at the same total dose D, but with different fraction sizes: Hyperfractionation: dose per fraction d_hypo = D / n_hyper, where n_hyper is large (e.g., 60 fractions) Hypofractionation: dose per fraction d_hypo = D / n_hypo, where n_hypo is small (e.g., 5 fractions) Then the survival fraction for tumor cells (with parameters α_tumor and β_tumor) would be: S_hyper = exp(-α_tumor * D - β_tumor * D * d_hyper) S_hypo = exp(-α_tumor * D - β_tumor * D * d_hypo) Since d_hyper < d_hypo, then β_tumor * D * d_hyper < β_tumor * D * d_hypo, so the exponent is less negative for hyperfractionation. Therefore, survival is higher for hyperfractionation at the same total dose. This means that hypofractionation is more effective in killing tumor cells at the same total dose. However, we must also consider normal tissues. Normal tissues typically have a lower α/β ratio than tumors. The α/β ratio is defined as the dose at which the linear and quadratic components of cell killing are equal. For a given total dose, hypofractionation (large d) will cause more damage to normal tissues with low α/β (because the quadratic term becomes more important). Therefore, in practice, when plotting dose-response curves (e.g., TCP vs total dose) we must consider: For tumor: BED_tumor = D * [1 + d / (α/β)_tumor] For normal tissue (with α/β_nt): BED_nt = D * [1 + d / (α/β)_nt] Now, to compare hyperfractionation and hypofractionation, we can plot: TCP for tumor as a function of total dose for both fractionation schemes, but we must note that normal tissue constraints will limit the total dose we can deliver. However, the question does not specify the context. Therefore, we can generate two curves for TCP vs total dose for the two fractionation schemes (ignoring normal tissue constraints for the moment) and then discuss. Alternatively, we can plot the TCP for the tumor for the two fractionation schemes when the normal tissue BED is fixed (to the same level). This would be more clinically relevant. Steps: 1. Fix the BED for normal tissue (BED_nt) to a constant value (say 100 Gy for example). 2. For a given fractionation scheme (hyper or hypo), we can express the total dose D from BED_nt: BED_nt = D * [1 + d / (α/β)_nt] => D = BED_nt / [1 + d / (α/β)_nt] 3. Then, for the tumor, the BED_tumor for that fractionation scheme would be: BED_tumor = D * [1 + d / (α/β)_tumor] = [BED_nt / (1 + d / (α/β)_nt)] * [1 + d / (α/β)_tumor] 4. Now, we can express TCP as a function of BED_tumor. A common model is: TCP = 1 / [1 + (BED50 / BED_tumor)^k] [logistic model] or TCP = exp(-exp(eγ * (BED50 - BED_tumor))) [Poisson model] 5. We can plot TCP vs d (dose per fraction) for fixed BED_nt? But the question asks for dose-response curves, which typically have TCP on the y-axis and dose on the x-axis. However, here we have two fractionation schemes. Alternatively, we can plot two curves: one for hyperfractionation and one for hypofractionation, with the x-axis being the total dose D, but note that for each D and each scheme, we have a different d. But then the normal tissue BED would be different. After careful thought, the most common approach in the literature is to compare the two fractionation schemes by fixing the normal tissue complication (i.e., fixing BED_nt) and then plot TCP vs total dose. However, total dose D is not the same for the two schemes when we fix BED_nt. Therefore, we can instead plot TCP vs BED_nt? Or we can plot TCP for the two schemes as a function of dose per fraction? Alternatively, we can plot two curves on the same graph: Curve 1 (hyperfractionation): For each dose per fraction (d_hyper), we compute D_hyper from BED_nt constraint, then compute BED_tumor_hyper, then TCP_hyper. Curve 2 (hypofractionation): Similarly, for each dose per fraction (d_hypo), we compute D_hypo, then BED_tumor_hypo, then TCP_hypo. But note: hyperfractionation typically uses two fractions per day and small dose per fraction (e.g., 1.1-1.2 Gy) and hypofractionation uses large doses per fraction (e.g., 2.5-20 Gy). So we can vary d from low (hyper) to high (hypo). However, the x-axis would be dose per fraction. However, the question asks for "dose–response curves", which typically have total dose on the x-axis. So let's consider: Option A: Fix the normal tissue BED and plot TCP vs total dose for each fractionation scheme. But note: for a fixed BED_nt, the total dose D is determined by d: D = BED_nt / [1 + d/(α/β_nt)]. So for each fractionation scheme (which gives a fixed d), we have one point. We cannot vary D arbitrarily for a fixed d? Actually, we can think: if we change the total dose, we break the constraint. So we cannot arbitrarily vary D and d independently while keeping BED_nt fixed. Option B: Plot two separate dose-response curves (TCP vs total dose) for hyperfractionation and hypofractionation without normal tissue constraints. Then we see that hypofractionation leads to higher TCP at the same total dose. But then we ignore the normal tissue, which is not realistic. Option C: Plot the TCP for the tumor as a function of the normal tissue BED (i.e., for a given BED_nt, we calculate the TCP for the two fractionation schemes). Then we can vary BED_nt and plot two curves: TCP_hyper(BED_nt) and TCP_hypo(BED_nt). This is a way to compare the two schemes under the same normal tissue burden. Given the complexity and the fact that the question does not specify the exact context, we will provide a general explanation and then show the typical behavior: - When normal tissue constraints are not considered, hypofractionation yields a higher TCP than hyperfractionation at the same total dose because of the higher dose per fraction (larger quadratic term). - However, when normal tissue constraints are considered (fixed BED_nt), hyperfractionation allows a higher total dose to be delivered (because for the same BED_nt, D is larger when d is smaller) and this higher total dose may lead to a higher TCP even though the dose per fraction is lower. Therefore, the curves: Without normal tissue constraint: Hypofractionation curve: TCP starts higher at the same total dose. Hyperfractionation curve: TCP is lower at the same total dose. With normal tissue constraint (fixed BED_nt): We may find that at low levels of BED_nt, hypofractionation yields a higher TCP, but at high levels of BED_nt, hyperfractionation yields a higher TCP because we can deliver a much higher total dose. Since we cannot produce actual plots (without graphical capabilities), we can describe the curves mathematically and provide example calculations. However, the user asked for "curves", but as an AI we can only describe and provide equations. We can also generate data points and suggest plotting. Let's choose parameters: For tumor: (α/β)_tumor = 10 Gy For normal tissue: (α/β)_nt = 3 Gy Fix BED_nt = 100 Gy. Then: D = BED_nt / [1 + d/(α/β_nt)] = 100 / (1 + d/3) Then BED_tumor = D * (1 + d/10) = [100 / (1 + d/3)] * (1 + d/10) Now, we can use a Poisson TCP model: TCP = exp( - K * exp(-α_tumor * BED_tumor) ) But note: the Poisson model for TCP is: TCP = exp( - clonogen_number * survival_fraction ) and survival_fraction = exp(-α_tumor * BED_tumor) [if we neglect the constant, but note: ln(S) = -α * BED_tumor? Actually, BED_tumor is defined such that the survival fraction S = exp(-α * BED_tumor).] Therefore, let N be the initial number of clonogens. Then: TCP = exp( - N * exp(-α_tumor * BED_tumor) ) We set α_tumor to 0.3 Gy⁻¹ (typical value) and N = 10^9. Then we can compute TCP as a function of d. For hyperfractionation: d = 1.2 Gy For hypofractionation: d = 2.5 Gy (moderate) and d=5 Gy (extreme) But note: we are comparing two schemes, so we might pick two representative d values: d=1.2 Gy for hyper and d=5 Gy for hypo. Then compute for each d: D = 100 / (1 + d/3) BED_tumor = D * (1 + d/10) TCP = exp( - 1e9 * exp(-0.3 * BED_tumor) ) Let's calculate: For d=1.2 Gy (hyper): D = 100 / (1 + 1.2/3) = 100 / (1+0.4) = 100/1.4 ≈ 71.43 Gy BED_tumor = 71.43 * (1 + 1.2/10) = 71.43 * (1.12) ≈ 80.00 Gy Then, TCP = exp(-1e9 * exp(-0.3*80)) = exp(-1e9 * exp(-24)) = exp(-1e9 * 3.8e-11) = exp(-0.038) ≈ 0.96 For d=5 Gy (hypo): D = 100 / (1 + 5/3) = 100 / (8/3) = 100 * 3/8 = 37.5 Gy BED_tumor = 37.5 * (1 + 5/10) = 37.5 * 1.5 = 56.25 Gy TCP = exp(-1e9 * exp(-0.3*56.25)) = exp(-1e9 * exp(-16.875)) = exp(-1e9 * 4.8e-8) = exp(-48) ≈ 0 Therefore, at the same normal tissue BED (100 Gy), hyperfractionation achieves a TCP of 96% while hypofractionation only 0%. But note: this extreme hypofractionation (d=5 Gy) with total dose 37.5 Gy is not enough to kill the tumor because the BED_tumor is only 56.25 Gy, which is too low. However, if we take a moderate hypofractionation, say d=2.5 Gy: D = 100 / (1 + 2.5/3) = 100 / (1 + 5/6) = 100 / (11/6) = 600/11 ≈ 54.55 Gy BED_tumor = 54.55 * (1 + 2.5/10) = 54.55 * 1.25 ≈ 68.18 Gy TCP = exp(-1e9 * exp(-0.3*68.18)) = exp(-1e9 * exp(-20.454)) = exp(-1e9 * 1.3e-9) = exp(-1.3) ≈ 0.27 So hyperfractionation (d=1.2) gives TCP=96%, moderate hypofractionation (d=2.5) gives TCP=27%, and extreme hypo (d=5) gives TCP=0. This shows that hyperfractionation is superior when normal tissue BED is fixed. But note: if the tumor has a low α/β (like prostate cancer, α/β~1.5 or 3), then the picture might be reversed. Therefore, the curves: We can plot TCP (y-axis) vs dose per fraction (x-axis) for fixed BED_nt. The curve would show TCP decreasing as d increases (because D decreases and the tumor BED also decreases for typical tumors with α/β=10). For tumors with low α/β, the tumor BED might increase with d? Let's check: Tumor with α/β_tumor = 3 Gy (same as normal tissue?): BED_tumor = D * (1 + d/3) = [BED_nt / (1+d/3)] * (1+d/3) = BED_nt So TCP would be the same regardless of fractionation? But if α/β_tumor is lower than α/β_nt (say α/β_tumor=2 Gy, α/β_nt=3 Gy), then: BED_tumor = D * (1 + d/2) = [BED_nt / (1+d/3)] * (1+d/2) Let's compute for d=1.2 and d=5: d=1.2: D=71.43, BED_tumor = 71.43 * (1+1.2/2)=71.43*1.6≈114.29 Gy d=5: D=37.5, BED_tumor=37.5*(1+5/2)=37.5*3.5=131.25 Gy -> higher. So for a tumor with low α/β (less than normal tissue), hypofractionation leads to higher tumor BED. Therefore, the dose-response curves (TCP vs dose per fraction) under fixed normal tissue BED will show: For high α/β tumors: TCP decreases as d increases (hyperfractionation better). For low α/β tumors: TCP increases as d increases (hypofractionation better). Given the above, we can describe the curves. However, the original question asks for a comparison between hyper and hypo fractionation. We can provide two curves on one plot for a given tumor type (either high or low α/β). But note that typical tumors (most carcinomas) have high α/β (around 10), whereas prostate cancer and some others have low. Therefore, we might generate two curves: Curve 1: Tumor with high α/β (e.g., 10 Gy): Plot TCP (for fixed BED_nt) as a function of dose per fraction (from 1 Gy to 5 Gy). It will be a decreasing curve. Curve 2: Tumor with low α/β (e.g., 2 Gy): Plot TCP as a function of dose per fraction (same range). It will be an increasing curve. And then label hyperfractionation at low d (e.g., 1-1.5 Gy) and hypofractionation at high d (e.g., 2.5-5 Gy). But note: the question asks for "dose–response curves" and typically these are plotted as TCP vs dose. So we could also plot: For a tumor with high α/β: Hyperfractionation: TCP vs total dose (with fixed d=1.2 Gy): D can vary? But then we break the normal tissue constraint. Alternatively, we can plot without constraint: TCP vs D for d=1.2 and for d=5. Without constraint: For d=1.2: S = exp(-αD - β D d) = exp(-αD - β D*1.2) For d=5: S = exp(-αD - β D*5) Since the exponent is more negative for d=5, the TCP will be higher for d=5 at the same D. So without constraint: the hypofractionation curve (d=5) will be shifted to the left (i.e., reaches high TCP at lower total dose) compared to hyperfractionation (d=1.2). But with normal tissue constraint, we cannot independently vary D. Hence, the common practice is to plot the iso-effect curves (fixed normal tissue damage) and then TCP vs fraction size. Due to the complexity and the multiple factors, it is common in radiobiology textbooks to show: - Cell survival curves: log survival vs dose for single fraction (large slope) vs fractionated (shallower slope) - But for dose-response curves (TCP) in clinical contexts, the above described approach is used. Since we are to generate an answer without plots, we describe the curves and provide the underlying equations. Answer Structure: The Linear Quadratic (LQ) model describes the biological effect of radiation fractionation. The dose-response curves for tumor control probability (TCP) comparing hyperfractionation and hypofractionation depend on the α/β ratios of the tumor and nearby normal tissues. Key considerations: 1. Without normal tissue constraints, hypofractionation (fewer fractions, higher dose per fraction) leads to greater tumor cell kill per unit total dose due to enhanced quadratic cell killing. Hence, the TCP curve for hypofractionation lies above that of hyperfractionation when plotted against total dose. Example formula for TCP (Poisson model): $$ TCP = \exp\left( - N_0 \cdot \exp\left(-\alpha D - \beta D d\right) \right) $$ where $d$ is the dose per fraction, $D$ is the total dose, and $N_0$ is the initial number of clonogens. For hypofractionation, $d$ is larger so the exponent $-\alpha D - \beta D d$ is more negative, leading to lower survival and higher TCP. 2. With normal tissue constraints (fixed normal tissue biologically effective dose, BED_nt), the comparison changes. The total dose that can be delivered is constrained by: $$ BED_{nt} = D \cdot \left(1 + \frac{d}{\alpha/\beta_{nt}}\right) $$ Rearranged: $$ D = \frac{BED_{nt}}{1 + \frac{d}{\alpha/\beta_{nt}}} $$ Then, the tumor BED is: $$ BED_{tumor} = D \cdot \left(1 + \frac{d}{\alpha/\beta_{tumor}}\right) = BED_{nt} \cdot \frac{1 + \frac{d}{\alpha/\beta_{tumor}}}{1 + \frac{d}{\alpha/\beta_{nt}}} $$ TCP can be modeled as: $$ TCP = \exp\left( - N_0 \cdot \exp\left(-\alpha \cdot BED_{tumor}\right) \right) $$ The curve of TCP versus dose per fraction (d) under fixed BED_nt shows: - For tumors with high α/β (e.g., 10 Gy) relative to normal tissue (e.g., 3 Gy), TCP decreases as d increases. Thus, hyperfractionation (small d) yields higher TCP. - For tumors with low α/β (e.g., 2-3 Gy), TCP may increase with d, favoring hypofractionation. Example comparison at fixed BED_nt (100 Gy) and α/β_nt=3 Gy: Tumor with α/β_tumor=10 Gy: d=1.2 Gy (hyperfractionation): D ≈ 71.43 Gy, BED_tumor ≈ 80.00 Gy, TCP ≈ 96% (assuming α=0.3 Gy⁻¹, N0=10^9) d=2.5 Gy (moderate hypofractionation): D ≈ 54.55 Gy, BED_tumor ≈ 68.18 Gy, TCP ≈ 27% d=5 Gy (extreme hypofractionation): D ≈ 37.5 Gy, BED_tumor ≈ 56.25 Gy, TCP ≈ 0% Tumor with α/β_tumor=2 Gy: d=1.2 Gy: BED_tumor = 100 * (1+1.2/2) / (1+1.2/3) = 100 * (1.6) / (1.4) ≈ 114.29 Gy d=5 Gy: BED_tumor = 100 * (1+5/2) / (1+5/3) = 100 * (3.5) / (8/3) = 100 * 3.5 * 3/8 = 131.25 Gy TCP increases with d. Therefore, the dose-response curves (TCP vs total dose) under no constraints show hypofractionation yielding higher TCP, but under normal tissue constraints they cross depending on the tumor's α/β. Note: The curves are typically sigmoidal when plotting TCP vs BED_tumor or vs total dose. To visualize, one might plot: Without constraints: Hypofractionation curve: TCP(D) for fixed d_hypo (e.g., 5 Gy) - rises steeply at lower D. Hyperfractionation curve: TCP(D) for fixed d_hyper (e.g., 1.2 Gy) - rises at higher D. With fixed normal tissue BED: For a high α/β tumor: Hyperfractionation yields higher TCP for the same BED_nt. For a low α/β tumor: Hypofractionation yields higher TCP. In clinical practice, the choice of fractionation depends on the α/β ratios of the tumor and critical normal tissues. We cannot generate an actual plot, but the following code snippet in Python can be used to generate the curves: Example code for plotting TCP vs dose per fraction under fixed normal tissue BED, for two tumor types: ```python import numpy as np import matplotlib.pyplot as plt # Parameters BED_nt = 100 # Gy alpha_b_nt = 3 # Gy alpha = 0.3 # Gy^{-1} N0 = 1e9 # Tumor types: [high_alpha_beta, low_alpha_beta] tumor_types = [ {'name': 'High α/β Tumor (10 Gy)', 'alpha_b_tumor': 10, 'color': 'blue'}, {'name': 'Low α/β Tumor (2 Gy)', 'alpha_b_tumor': 2, 'color': 'red'} ] # Dose per fraction range: from 1 Gy to 5 Gy d = np.linspace(1, 5, 100) plt.figure(figsize=(10,6)) for tumor in tumor_types: ab_t = tumor['alpha_b_tumor'] # Calculate total dose D from BED_nt constraint D = BED_nt / (1 + d/alpha_b_nt) # Calculate tumor BED BED_tumor = D * (1 + d/ab_t) # Calculate survival fraction S = np.exp(-alpha * BED_tumor) # Calculate TCP TCP = np.exp(-N0 * S) plt.plot(d, TCP, label=tumor['name'], color=tumor['color']) plt.axvline(x=1.2, color='gray', linestyle='--', alpha=0.5) plt.axvline(x=2.5, color='gray', linestyle='--', alpha=0.5) plt.axvline(x=5, color='gray', linestyle='--', alpha=0.5) plt.text(1.2, 0.1, 'Typical Hyperfractionation', rotation=90, verticalalignment='center') plt.text(2.5, 0.1, 'Moderate Hypofractionation', rotation=90, verticalalignment='center') plt.text(5, 0.1, 'Extreme Hypofractionation', rotation=90, verticalalignment='center') plt.xlabel('Dose per fraction (Gy)') plt.ylabel('Tumor Control Probability (TCP)') plt.title('TCP vs Dose per Fraction at Fixed Normal Tissue BED (100 Gy)') plt.legend() plt.grid(True) plt.show() ``` This code produces a plot of TCP versus dose per fraction for two tumor types under the same normal tissue constraint. The high α/β tumor shows decreasing TCP with increasing d, while the low α/β tumor shows increasing TCP. Note: The parameters (α, N0) are chosen arbitrarily for illustration. Actual values vary. Therefore, the comparison of hyperfractionation versus hypofractionation in the LQ model is not absolute but depends on the α/β ratios. </think> The Linear Quadratic (LQ) model describes radiation-induced cell kill using the equation: $$ S = e^{-\alpha D - \beta G D^2} $$ where $S$ = survival fraction, $D$ = total dose, $\alpha$/$\beta$ = tissue-specific radiosensitivity ratio, and $G$ = repair factor. Fractionation regimens alter biological effectiveness by modifying dose per fraction ($d$) and number of fractions ($n$), where $D = n \cdot d$. ### Comparison of Dose–Response Curves **1. Hyperfractionation (e.g., 1.1–1.2 Gy/fraction)** - **Lower dose per fraction**: Reduces late tissue toxicity (low $\alpha$/$\beta$ tissues, e.g., spinal cord). - **Increased total dose**: Enables higher tumor BED (Biologically Effective Dose): $$ BED_{\text{tumor}} = D \left(1 + \frac{d}{\alpha/\beta_{\text{tumor}}}\right) $$ For $\alpha/\beta\!=\!10$ Gy, BED increases with smaller $d$. - **Curve shift**: TCP (Tumor Control Probability) curve shifts leftward when plotted against total dose, improving therapeutic ratio for high $\alpha/\beta$ tumors. **2. Hypofractionation (e.g., 2.5–20 Gy/fraction)** - **Higher dose per fraction**: Amplifies quadratic cell kill, advantageous for tumors with low $\alpha/\beta$ (e.g., prostate cancer). - **Lower total dose**: Limits exposure but increases late toxicity risk in normal tissues: $$ BED_{\text{normal}} = D \left(1 + \frac{d}{\alpha/\beta_{\text{normal}}}\right) $$ For $\alpha/\beta\!=\!3$ Gy, BED rises steeply with larger $d$. - **Curve shift**: TCP curve shifts rightward against total dose for high $\alpha/\beta$ tumors but leftward for low $\alpha/\beta$ tumors. ### Key Differences | Parameter | Hyperfractionation | Hypofractionation | |--------------------|--------------------------|--------------------------| | **TCP for high $\alpha/\beta$ tumors** | Higher at fixed $D$ | Lower at fixed $D$ | | **Normal tissue sparing** | Better for late-responding tissues | Riskier for late effects | | **Clinical utility** | Head/neck cancers | Prostate/stereotactic radiotherapy | ### Example Calculation Fixed normal tissue BED ($BED_{\text{nt}}\!=\!100$ Gy, $\alpha/\beta_{\text{nt}}\!=\!3$ Gy): - **Hyperfractionation** ($d\!=\!1.2$ Gy): $$ D = \frac{100}{1 + 1.2/3} \approx 71.4 \text{ Gy}, \quad BED_{\text{tum}} = 71.4 \left(1 + \frac{1.2}{10}\right) \approx 80 \text{ Gy} $$ TCP ≈ 96% (assuming $\alpha\!=\!0.3$ Gy⁻¹, $N_0\!=\!10^9$). - **Hypofractionation** ($d\!=\!5$ Gy): $$ D = \frac{100}{1 + 5/3} = 37.5 \text{ Gy}, \quad BED_{\text{tum}} = 37.5 \left(1 + \frac{5}{10}\right) = 56.25 \text{ Gy} $$ TCP ≈ 0%. ### Visualization Code ```python import numpy as np import matplotlib.pyplot as plt # Parameters BED_normal = 100 # Gy (fixed) alpha_beta_normal = 3 # Gy alpha_beta_tumor_high = 10 # Gy alpha_beta_tumor_low = 2 # Gy alpha = 0.3 # Gy⁻¹ N0 = 1e9 # Clonogen count # Dose per fraction range (Gy) d_hyper = np.linspace(1.0, 1.5, 100) # Hyperfractionation d_hypo = np.linspace(2.5, 10, 100) # Hypofractionation def calculate_tcp(d, alpha_beta_tumor): D = BED_normal / (1 + d / alpha_beta_normal) BED_tumor = D * (1 + d / alpha_beta_tumor) survival = np.exp(-alpha * BED_tumor) return np.exp(-N0 * survival) # Calculate TCP tcp_hyper_high = calculate_tcp(d_hyper, alpha_beta_tumor_high) tcp_hyper_low = calculate_tcp(d_hyper, alpha_beta_tumor_low) tcp_hypo_high = calculate_tcp(d_hypo, alpha_beta_tumor_high) tcp_hypo_low = calculate_tcp(d_hypo, alpha_beta_tumor_low) # Plot plt.figure(figsize=(12, 6)) plt.plot(d_hyper, tcp_hyper_high, 'b-', label='Hyperfractionation (α/β=10)') plt.plot(d_hyper, tcp_hyper_low, 'b--', label='Hyperfractionation (α/β=2)') plt.plot(d_hypo, tcp_hypo_high, 'r-', label='Hypofractionation (α/β=10)') plt.plot(d_hypo, tcp_hypo_low, 'r--', label='Hypofractionation (α/β=2)') plt.xlabel('Dose per Fraction (Gy)') plt.ylabel('Tumor Control Probability') plt.legend() plt.grid(True) plt.title('LQ Model: Fractionation Comparison at Fixed Normal Tissue BED (100 Gy)') plt.show() ``` This code generates TCP curves showing: - **High $\alpha/\beta$ tumors**: Hyperfractionation > Hypofractionation - **Low $\alpha/\beta$ tumors**: Hypofractionation > Hyperfractionation
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值