Learn Object Pascal
Part 6 - Procedures and Functions
Procedures are subprograms.A procedure must have a name and also may have a parameter input list.
In this section we will pass parameters in our procedures only by value, there are other options which we will see them in next tutorials.
Example :
procedure PrintHelloWorld();
begin
Form1.Memo1.Lines.Add('Hello World');
end;
PrintHelloWorld is the name of our procedure. This procedure does not have any input paramenters.
procedure PrintReceipt(ProductName:string; Price:integer);
begin
Form1.Memo1.Lines.Add('Product : ' + ProductName +
', Price : ' + IntToStr(Price));
end;
PrintReceipt is the name of our procedure. The parameters are ProductName and Price.
We can call the our procedures from the Button1Click event like this :
procedure TForm1.Button1Click(Sender: TObject);
begin
PrintHelloWorld();
PrintReceipt('DesktopZoomer', 15);
end;
A global variable is a variable defined in the main program.
Any procedure can see it, use it, and modify it.
Within a procedure the variables are local.
Some global variables examples are :
var
Form1: TForm1; //Global variable
x_global: integer; //Global variable
GlobalIntegerResult: integer = 0; //Global variable initialized to 0
GlobalIntegerInput: integer = 1; //Global variable initialized to 1
implementation
...
Some local variables examples are :
procedure TForm1.Button1Click(Sender: TObject);
var x : integer; //Local variable
begin
...
procedure DoubleInLabel(x:integer);
var LocalResult:integer; //Local variable
begin
LocalResult:=2*x;
Form1.Label1.Caption := IntToStr(LocalResult);
end;
A procedure can change the global variables and it's own local variables.
Scope of a variable refers to where this variable is visible.
Procedure DoubleInLabel can see and modify it's own local variable LocalResult and the GlobalIntegerResult.
Instead Procedure Button1Click can see x and GlobalIntegerResult but can not see the local variable LocalResult (that is local variable of another procedure).
procedure DoubleInLabel(x:integer);
var LocalResult:integer; //Local variable
begin
LocalResult:=2*x;
Form1.Label1.Caption := IntToStr(LocalResult);
end;
procedure TForm1.Button1Click(Sender: TObject);
var x : integer; //local variable
begin
{Scopes
Local Scope : As you can see the LocalResult local variable
which is located internally at the DoubleInLabel Procedure
dies after the end of the procedure.}
//Memo1.Lines.Add(IntToStr(LocalResult)); // Error: Identifier not found "LocalResult"
{Global Scope :
Instead : }
//Memo1.Lines.Add(IntToStr(GlobalIntegerResult)); //is correct
{can be accesses since it is a global variable}
end;
Notice : By passing by value a variable,
the passed variable changes while the procedure is executed but once the
procedure ends the variable gets the initial value that had before entering into the
called procedure. The explanaition of why this is happenning is the following :
When you declare a variable inside a procedure, the memory required to hold the variable is allocated from the stack.
Example : "var LocalResult:integer;" is stored into the stack.
When the variable exits the procedure (goes out of scope), the memory which was taken on the stack is cleared.
So passing parameters by value: the value is copied on the stack and the procedure uses and manipulates the copy, not the original value.
Once the procedure is done the local variable is destroyed and only the original copy is remaining with it's original value of course.
See the example below :
//procedure pass by value example:
procedure Increase(x:integer);
begin
x:=x+1; //x is now 2+1=3
Form1.Memo1.Lines.Add('Local variable x=' + IntToStr(x)); //prints 3
end;
procedure TForm1.Button1Click(Sender: TObject);
var x : integer; //local variable
begin
//procedure pass by value examples:
Memo1.Lines.Add('Procedure pass by value local');
x:=2; //initialize local var at 2
Memo1.Lines.Add('Initial value x =' + IntToStr(x)); //prints 2
Increase(x); //this procedure localy changes x to 3 and prints 3
//after the Increase procedure execution x (local variable) remains = 2
Memo1.Lines.Add('after the increase procedure the local value of x =' + IntToStr(x));
Memo1.Lines.Add('--------------------------------');
Memo1.Lines.Add('Procedure pass by value global');
x_global:=5; //initialize global var at 5
Memo1.Lines.Add('Initial value x =' + IntToStr(x_global)); //prints 5
Increase(x_global); //this procedure locally changes x to 6 and prints 6
//after the Increase procedure execution x (global variable) remains = 5
Memo1.Lines.Add('after the increase procedure the global value of x =' + IntToStr(x_global));
Memo1.Lines.Add('--------------------------------');
end;
Notice that we have the same behavior for x_global (Global variable) and x (Local variable of Button1Click procedure).
Procedures can not return a value but they can manipolate global variables.
Global variables may be used as input and as output for proceduers.
This technique is not recommended but it is shown for educational purposes.
For this demo we also added on the Form a Label other than the a Memo and a Button.
//Result sent in a Form1.Label.Caption (global variable)
procedure DoubleInLabel(x:integer);
var LocalResult:integer; //Local variable
begin
LocalResult:=2*x;
Form1.Label1.Caption := IntToStr(LocalResult);
end;
//Result passed as Global Variable [not recommended]
procedure DoubleInGlobalIntegerResult(x:integer);
var LocalResult:integer; //Local variable
begin
LocalResult:=2*x;
GlobalIntegerResult := 2*x;
end;
//Input and Result passed as Global Variables [not recommended]
procedure DoubleUsingGlobals();
var LocalResult:integer; //Local variable
begin
LocalResult:=2*GlobalIntegerInput;
GlobalIntegerResult := LocalResult;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
//procedure that outputs in a Label.Caption (Global)
DoubleInLabel(5);
//procedure that outputs in GlobalIntegerResult
DoubleInGlobalIntegerResult(5); //5*2=10
//Now print the GlobalIntegerResult
Memo1.Lines.Add('Print GlobalIntegerResult=' + IntToStr(GlobalIntegerResult));
//procedure that uses global input and output
//Do not use. Demonstration only :
GlobalIntegerInput:=100;
DoubleUsingGlobals(); //100*2=200
Memo1.Lines.Add('Print GlobalIntegerResult=' + IntToStr(GlobalIntegerResult));
end;
Specially the third option is to avoid all the times since you can input variables and avoid using global variables at least as inputs.
Overloading procedures.
Overloading a procedure means defining it in multiple versions, using the same procedure name but different parameter lists.
The parameter list may have more or less parameters or the same parameters number but with different data types
Let's see an example below :
{Overloaded routines must be declared with the overload directive
OVERLOAD IN LAZARUS
Prior to version 1.9 of the compiler,the overloaded functions needed to be in the same unit.
Now the compiler will continue searching in other units if it doesn't find a matching version
of an overloaded function in one unit, and if the overload keyword is present.
If the overload keyword is not present,then all overloaded versions must reside in the
same unit,and if it concerns methods part of a class, they must be in the same class,
i.e. the compiler will not look for overloaded methods in parent classes if the overload
keyword was not specified.
***GENERALLY OVERLOAD IS USED ON THE SAME CLASS***
}
procedure TripleInMemo(x:integer); overload; //I can be overloaded ...
begin
Form1.Memo1.Lines.Add('Three times the integer ' + IntToStr(x) + '=' + IntToStr(3*x));
end;
procedure TripleInMemo(x:double); overload; //I can be overloaded ...
begin
Form1.Memo1.Lines.Add('Three times the float ' + FloatToStr(x) + '=' + FloatToStr(3*x));
end;
procedure TripleInMemo(x:string); overload; //I can be overloaded ...
begin
Form1.Memo1.Lines.Add('Three times the string : ' + x + x + x);
end;
procedure TripleInMemo(x,y:double); overload; //I can be overloaded ...
begin
Form1.Memo1.Lines.Add('Three times the float ' + FloatToStr(x) + '=' + FloatToStr(3*x));
Form1.Memo1.Lines.Add('Three times the float ' + FloatToStr(y) + '=' + FloatToStr(3*y));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
//overload procedure example:
TripleInMemo(4);
TripleInMemo(4.2);
TripleInMemo('Simple string.');
TripleInMemo(5.1, 2.4);
end;
Functions are subprograms like procedures, but they always return a single value to the main program.
So functions always have an output (called result) value.
Example :
function Sum(a,b:integer):integer; //returns an integer
begin
result:=a+b;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
//function examples :
Memo1.Lines.Add('function Sum 8+2=' + IntToStr(Sum(8,2)));
end;
Notice that Sum(8,2) returns 10 since it is a function.
Overloading functions.
Overloading functions, like the procedures, allows you to have different versions of the same named function with different arguments.
Example :
function DoubleIt(x:integer):integer; overload; //result
var LocalVariable : integer;
begin
LocalVariable:=2*x;
result:=LocalVariable;
end;
function DoubleIt(x:double):double; overload;//result
var LocalVariable : double;
begin
LocalVariable:=2*x;
result:=LocalVariable;
end;
function DoubleIt(x:string):string; overload;//result
begin
result:=x+x;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
//function overload examples :
Memo1.Lines.Add('function 5x2=' + IntToStr(DoubleIt(5)));
Memo1.Lines.Add('function 5.2x2=' + FloatToStr(DoubleIt(5.2)));
Memo1.Lines.Add('function double string:' + DoubleIt('Test string.'));
end;
A function can call another function as well. Notice that the inner function (the one that is called last) must be declared first.
In the example below we have a CapitalizeAndDoubleString function .
This calls CapitalizeString function that must be declared first.
//declaration order example :
{
//wrong !!!
function CapitalizeAndDoubleString(s:string):string;
var str:string;
begin
str:=CapitalizeString(s); //wrong it is not declared yet
result := str+str;
end;
}
//Capitalisation is the writing of a word with its first letter
//in upper-case and the remaining letters in lower-case.
function CapitalizeString(s:string):string;
var str:string; //local var
begin
if Length(s)>=1 then begin
str := copy(s, 1, 1);
str := UpperCase(str);
if Length(s)>=2 then begin
str := str + LowerCase(copy(s, 2, Length(s)));
end;
result:=str;
end else begin //length = 0
result:='';
end;
end;
//correct !!!
function CapitalizeAndDoubleString(s:string):string;
var str:string;
begin
str:=CapitalizeString(s); // declared before CapitalizeAndDoubleString
result := str+str;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
//function call another function
//ordering functions
Memo1.Lines.Add('Capitalize & double string:' + CapitalizeAndDoubleString('hello world.'));
end;
As we have seen a procedure or a function can call another procedures and functions.
If a function calls itself again this function is called recusive.
Recursion must be used with good documentation and comments since it is harder to mantain and understand.
Below i will give you two examples.
In the first example we will see how to calculate fractorials using Pascal.
Factorial definition :
n! = n*(n-1)*(n-2)*...*2*1
0! = 1 (by definition)
example : 5!=5*4*3*2*1=120
We can simply use the Factorial definition formula using a for n downto 2 loop.
function Factorial(n:integer):Int64;
var res : int64;
i : integer;
begin
if n>1 then begin
res:=1;
//n! = n*(n-1)*(n-2)*...*2 (*1 is not needed !)
for i:=n downto 2 do begin
res := res * i;
end;
result:=res;
end else begin // 1!=1 and 0!=1
result:=1;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.Add('Simple Function : 8!=' + IntToStr(Factorial(8)));
end;
If we look closer we can see that we can solve this problem using recursion because n! = n*(n-1)*(n-2)*...*2*1 = n*(n-1)!
So n! = n*(n-1)! or programatically :
FactorialRecursion(n) = n * FactorialRecursion(n-1);
We can clearly see that FactorialRecursion can call itself until n=1 in order to solve Factorials.
function FactorialRecursion(n:integer):Int64;
begin
if n>1 then begin
//n! = n*(n-1)*(n-2)*...*2*1 = n*(n-1)!
result:=n * FactorialRecursion(n-1);
end else begin // 1!=1 and 0!=1
result:=1;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.Add('Recursive Function : 8! =' + IntToStr(FactorialRecursion(8)));
end;
Another example is the calculation of the triangle number.
By definition : Sigma(n)=n+(n-1)+(n-2)+...+2+1
Sigma(n=0)=0 (by definition)
Example : Sigma(5)=5+4+3+2+1=12
Following the formula again we can calculate triangle numbers using a dor n downto 1 loop
function TriangleNumber(n:integer):Int64;
var res : int64;
i : integer;
begin
if n>0 then begin
res:=0;
//Sigma(n)=n+(n-1)+(n-2)+...+2+1
for i:=n downto 1 do begin
res := res + i;
end;
result:=res;
end else begin // Sigma(0)=0
result:=0;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.Add('Simple Function : TriangleNumber(10)=' +
IntToStr(TriangleNumber(10)));
end;
Again looking better we can solve this problem recursively since : Sigma(n) = n+(n-1)+(n-2)+...+2+1 = n+Sigma(n-1)
function TriangleNumberRecursion(n:integer):Int64;
begin
if n>0 then begin
//Sigma(n)=n+(n-1)+(n-2)+...+2+1 = n + Sigma(n-1)
result:=n + TriangleNumberRecursion(n-1);
end else begin // Sigma(0)=0
result:=0;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.Add('Recursive Function : TriangleNumber(10)=' +
IntToStr(TriangleNumber(10)));
end;
In the next tutorial we will talk about custom datatypes. You can download the program of this section from here
Part 1: Introduction - Part 2: Hello World - Part 3: Data types, constants and variables - Part 4: Operators - Part 5: Decisions and Loops - [[ Part 6: Procedures and Functions ]] - Part 7: Custom datatypes - Part 8: Enumerations, subranges and sets - Part 9: Records