Structured Programming
Introduction
Structured programming appeared after the expansion of programs. Large applications become unreadable and unmaintainable when they use unstructured code in one file.
In structured programming, we can split an application's source code into smaller pieces, called procedures and functions, and a we can also combine the procedures and functions that relate to one subject in a separate code file called a unit.
Structured programming benefits:
1. Partitioning an application's code to readable modules and procedures.
2. Code reusability: procedures and functions can be called from any part of the code many times without the need to rewrite and duplicate code.
3. Programmers could share and participate in one project at the same time. Each programmer could write their own procedures and functions in a separate unit, then they can integrate these units in the project.
4. Application maintenance and enhancement becomes easy: we can find bugs easily in procedures, also it is easy to improve procedures and functions, write new ones, add new units, etc.
5. Introducing modules and layers: we can divide an application into different logical layers and modules: for example we can write a unit that contains procedures that read/write data from/into files, and another unit that represent rules and validation layer, and a third one as a user interface layer.
Procedures
We have already used some procedures in the previous chapter, like Writeln, Readln, Reset, etc, but this time we need to write our own procedures that can be used by our applications.
In the next example we have written two procedures: SayHello and SayGoodbye: program Structured;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes
{ you can add units after this };
procedure SayHello;
begin
Writeln('Hello there');
end;
procedure SayGoodbye;
begin
Writeln('Good bye');
end;
begin // Here main application starts
Writeln('This is the main application started');
SayHello;
Writeln('This is structured application');
SayGoodbye;
Write('Press enter key to close');
Readln;
end.
We see that the procedure looks like a small program, with its own begin. .end, and it can be called from the main application's code.
Parameters
In the next example, we introduce parameters, which are variables passed to a procedure when calling it:
procedure WriteSumm(x, y: Integer);
begin
Writeln('The summation of ', x, ' + ', y, ' = ', x + y)
end;
begin
WriteSumm(2, 7);
Write('Press enter key to close');
Readln;
end.
In the main application, we have called the WriteSumm procedure and passed the values 2, 7 to it, and the procedure will receive them in x, y integer variables to write the summation result of them.
In the next example, we have rewritten the restaurant application using procedures: Restaurant program using procedures
procedure Menu;
begin
Writeln('Welcome to Pascal Restaurant. Please select your order');
Writeln('1 - Chicken (10$)');
Writeln('2 - Fish (7$)');
Writeln('3 - Meat (8$)');
Writeln('4 – Salad (2$)');
Writeln('5 - Orange Juice (1$)');
Writeln('6 - Milk (1$)');
Writeln;
end;
procedure GetOrder(AName: string; Minutes: Integer);
begin
Writeln('You have ordered : ', AName, ', this will take ',
Minutes, ' minutes');
end;
// Main application
var
Meal: Byte;
begin
Menu;
Write('Please enter your selection: ');
Readln(Meal);
case Meal of
1: GetOrder('Chicken', 15);
2: GetOrder('Fish', 12);
3: GetOrder('Meat', 18);
4: GetOrder('Salad', 5);
5: GetOrder('Orange juice', 2);
6: GetOrder('Milk', 1);
else
Writeln('Wrong entry');
end;
Write('Press enter key to close');
Readln;
end.
Now the main application becomes smaller and more readable. The details of the other parts are separated in procedures, like the get order and display menu procedures.
Functions
Functions are similar to procedures, but have an additional feature, which is returning a value. We have used functions before, like UpperCase, which converts and returns text to upper case, and Abs, which returns the absolute value of a number.
In the next example, we have written the GetSumm function, which receives two integer values and returns their summation:
function GetSumm(x, y: Integer): Integer;
begin
Result:= x + y;
end;
var
Sum: Integer;
begin
Sum:= GetSumm(2, 7);
Writeln('Summation of 2 + 7 = ', Sum);
Write('Press enter key to close');
Readln;
end.
Notice that we have declared the function as Integer, and we have used the Result keyword to represent the function's return value.
In the main application, we have used the variable Sum, in which we receive the function result, but we could eliminate this intermediate variable and call this function inside the Writeln procedure. This is one difference between functions and procedures. We can call functions as input parameters in other procedures or functions, but we can not call procedures as parameters of other functions and procedures:
function GetSumm(x, y: Integer): Integer;
begin
Result:= x + y;
end;
begin
Writeln('Summation of 2 + 7 = ', GetSumm(2, 7));
Write('Press enter key to close');
Readln;
end.
In the next example, we have rewritten the Restaurant program using functions: Restaurant program using functions
procedure Menu;
begin
Writeln('Welcome to Pascal Restaurant. Please select your order');
Writeln('1 - Chicken (10$)');
Writeln('2 - Fish (7$)');
Writeln('3 - Meat (8$)');
Writeln('4 – Salad (2$)');
Writeln('5 - Orange Juice (1$)');
Writeln('6 - Milk (1$)');
Writeln('X - nothing');
Writeln;
end;
function GetOrder(AName: string; Minutes, Price: Integer): Integer; begin
Writeln('You have ordered: ', AName, ' this will take ',
Minutes, ' minutes');
Result:= Price;
end;
var
Selection: Char;
Price: Integer;
Total: Integer;
begin
Total:= 0;
repeat
Menu;
Write('Please enter your selection: ');
Readln(Selection);
case Selection of
'1': Price:= GetOrder('Checken', 15, 10);
'2': Price:= GetOrder('Fish', 12, 7);
'3': Price:= GetOrder('Meat', 18, 8);
'4': Price:= GetOrder('Salad', 5, 2);
'5': Price:= GetOrder('Orange juice', 2, 1);
'6': Price:= GetOrder('Milk', 1, 1);
'x', 'X': Writeln('Thanks');
else
begin
Writeln('Wrong entry');
Price:= 0;
end;
end;
Total:= Total + Price;
until (Selection = 'x') or (Selection = 'X');
Writeln('Total price = ', Total);
Write('Press enter key to close');
Readln;
end.
Local Variables
We can define variables locally inside a procedure or function to be used only inside its code. These variables can not be accessed from the main application's code or from other procedures and functions.
Example:
procedure Loop(Counter: Integer);
var
i: Integer;
Sum: Integer;
begin
Sum:= 0;
for i:= 1 to Counter do
Sum:= Sum + i;
Writeln('Summation of ', Counter, ' numbers is: ', Sum);
end;
begin // Main program section
Loop;
Write('Press enter key to close');
Readln;
end.
In the procedure Loop, there are two local variables Sum and I. Local variables are stored in Stack memory, which is a part of memory that allocates variables temporarily until the procedure's execution is finished. That means it will be unaccessible and can be overwritten when program execution reaches this line of code:
Write('Press enter key to close');
Global variables can be accessed from the main program and other procedures and functions. They can hold values until the application is closed, but this can break the structure of the program and make it hard to trace errors, because any procedure can change global variable values, which may result in unknown values and misbehavior when we forget to initialize them.
Defining local variables guarantees their privacy, which helps the procedures and functions to be ported or called from anywhere without worry about global variables values.
News database application
In this example we have three procedures and one function: Add News title, display all News, searching, and displaying menu to let the user select the function that he/she want to execute: program news;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes, SysUtils
{ you can add units after this };
type
TNews = record
ATime: TDateTime;
Title: string[100];
end;
procedure AddTitle;
var
F: file of TNews;
News: TNews;
begin
AssignFile(F, 'news.dat');
Write('Input current news title: ');
Readln(News.Title);
News.ATime:= Now;
if FileExists('news.dat') then
begin
FileMode:= 2; // Read/Write
Reset(F);
Seek(F, System.FileSize(F)); // Go to last record to append
end
else
Rewrite(F);
Write(F, News);
CloseFile(F);
end;
procedure ReadAllNews;
var
F: file of TNews;
News: TNews;
begin
AssignFile(F, 'news.dat');
if FileExists('news.dat') then
begin
Reset(F);
while not Eof(F) do
begin
Read(F, News);
Writeln('------------------------------');
Writeln('Title: ', News.Title);
Writeln('Time : ', DateTimeToStr(News.ATime));
end;
CloseFile(F);
end
else
Writeln('Empty database');
end;
procedure Search;
var
F: file of TNews;
News: TNews;
Keyword: string;
Found: Boolean;
begin
AssignFile(F, 'news.dat');
Write('Input keyword to search for: ');
Readln(Keyword);
Found:= False;
if FileExists('news.dat') then
begin
Reset(F);
while not Eof(F) do
begin
Read(F, News);
if Pos(LowerCase(Keyword), LowerCase(News.Title)) > 0 then
begin
Found:= True;
Writeln('------------------------------');
Writeln('Title: ', News.Title);
Writeln('Time : ', DateTimeToStr(News.ATime));
end;
end;
CloseFile(F);
if not Found then
Writeln(Keyword, ' Not found');
end
else
Writeln('Empty database');
end;
function Menu: char;
begin
Writeln;
Writeln('...........News database..........');
Writeln('1. Add news title');
Writeln('2. Display all news');
Writeln('3. Search');
Writeln('x. Exit');
Write('Please input a selection : ');
Readln(Result);
end;
// Main application
var
Selection: Char;
begin
repeat
Selection:= Menu;
case Selection of
'1': AddTitle;
'2': ReadAllNews;
'3': Search;
end;
until LowerCase(Selection) = 'x';
end.
This application is easy to read and has compact and clear code in the main section. We can also add new features to any procedure without affecting or modifying other parts.
Functions as input parameters
As we said before, we can call the function as a procedure/function input parameter, because we can treat it as a value.
See the example below:
function DoubleNumber(x: Integer): Integer;
begin
Result:= x * 2;
end;
// Main
begin
Writeln('The double of 5 is : ', DoubleNumber(5));
Readln;
end.
Note that we have called the DoubleNumber function from inside the Writeln procedure.
In the next modification of the example, we will use an intermediate variable to store the function's result, and then use it as input to the Writeln procedure:
function DoubleNumber(x: Integer): Integer;
begin
Result:= x * 2;
end;
// Main
var
MyNum: Integer;
begin
MyNum:= DoubleNumber(5);
Writeln('The double of 5 is : ', MyNum);
Readln;
end.
We can also call functions within if conditions and loop conditions:
function DoubleNumber(x: Integer): Integer;
begin
Result:= x * 2;
end;
// Main
begin
if DoubleNumber(5) > 10 then
Writeln('This number is larger than 10')
else
Writeln('This number is equal or less than 10);
Readln;
end.
Procedure and function output parameters
In the previous usage of functions, we found that we can return only one value, which is the result of the function, but how can we return more than one value in functions or procedures?
Let us do this experiment:
procedure DoSomething(x: Integer);
begin
x:= x * 2;
Writeln('From inside procedure: x = ', x);
end;
// main
var
MyNumber: Integer;
begin
MyNumber:= 5;
DoSomething(MyNumber);
Writeln('From main program, MyNumber = ', MyNumber);
Writeln('Press enter key to close');
Readln;
end.
In this example, the doSomething procedure receives x as an Integer value, then it multiplies it by two, and then finally it displays it.
In the main part of the program, we declared the variable MyNumber as an Integer, put the number 5 in it, and then passed it as a parameter to the DoSomething procedure. In this case, the MyNumber value (5) will be copied into the x variable.
After calling the function, X 'svalue will be 10, but when we display MyNumber after procedure calling we will find that it still holds the value 5. That means MyNumber and X have two different locations in memory, which is normal.
This type of parameter passing is called calling by value, which does not affect the original parameter MyNumber. We also could use constants to pass such values, e.g:
DoSomething(5);
Calling by reference
If we add the var keyword to the declaration of DoSomething's x parameter, things will be different now:
procedure DoSomething(var x: Integer);
begin
x:= x * 2;
Writeln('From inside procedure: x = ', x);
end;
// main
var
MyNumber: Integer;
begin
MyNumber:= 5;
DoSomething(MyNumber);
Writeln('From main program, MyNumber = ', MyNumber);
Writeln('Press enter key to close');
Readln;
end.
This time MyNumber's value will be changed according to x, which means they are sharing the same memory location.
We should pass a variable (not a constant) to the procedure this time, using the same type: if the parameter is declared as Byte, then MyNumber should be declared as Byte; if it is Integer, then it should be Integer.
The next example will generate an error when calling DoSomething, which requires a variable for its parameter:
DoSomething(5);
In calling by value, the previous code could be used, because it cares only about having a value passed as its parameter, and 5 is a value, but in calling by reference the program cares about having a variable passed as its parameter and then acts on its value.
In the next example, we will pass two variables, and then the procedure will swap their values: procedure SwapNumbers(var x, y: Integer);
var
Temp: Integer;
begin
Temp:= x;
x:= y;
y:= Temp;
end;
// main
var
A, B: Integer;
begin
Write('Please input value for A: ');
Readln(A);
Write('Please input value for B: ');
Readln(B);
SwapNumbers(A, B);
Writeln('A = ', A, ', and B = ', B);
Writeln('Press enter key to close');
Readln;
end.
Units
A Unit in Pascal is a library that contains procedures, functions, constants, and user defined types that can be used in many applications.
Units are used to achieve these goals:
1. Accumulate procedures and functions that are frequently used in applications in external units.
This achieves code reusability in software development.
2. Combine procedures and functions that are used to perform certain tasks in one entity. Instead of populating the main application's source code with unrelated procedures, it is better to divide the application into logical modules using units.
To create a new unit, go to File/New Unit in the Lazarus menu, and Lazarus creates this template: unit Unit1;
{$mode objfpc}{$H+}
interface