You now have a pretty good start on learning Object Pascal. In this chapter, you will continue to learn about the Object Pascal language by examining more of the fundamentals of Object Pascal. Today you will learn about
There are some aspects of programming that are common to all programming languages. One such item that Object Pascal has in common with other programming languages is the if statement. The if statement is used to test for a condition and then execute sections of code based on whether that condition is True or False. Here's an example:
var X : Integer; begin X := StrToInt(Edit1.Text); if X > 10 then Label1.Caption := `You entered a number greater than 10.'; end;
This code gets the contents of an edit control and stores it in an integer variable. If the number is greater than 10, the expression x > 10 evaluates to True and the message is displayed; otherwise, nothing is displayed. Note that when the conditional expression evaluates to True, the statement immediately following the if...then expression is executed. The conditional part of an if statement is always followed by then.
New Term: The if statement is used to test for a condition and execute one or more lines of code when that condition evaluates to True.
Let's say you have multiple lines of code that should be executed when the conditional expression is True. In that case, you would need begin and end keywords to block those lines:
if X > 10 then begin Label1.Caption := `You entered a number greater than 10.'; DoSomethingWithNumber(X); end;
When the conditional expression evaluates to False, the code block associated with the if expression is ignored and program execution continues with the first statement following the code block.
NOTE: Object Pascal contains a few shortcuts. One of those shortcuts involves using just a Boolean variable's name to test for True. Look at this code:if FileGood then ReadData;
This method is shortcut for the longer form, which is illustrated with this line:
if FileGood = True then ReadData;
This shortcut only applies to Boolean variables. You can test for False by applying the not keyword to a variable name:
var FileGood : Boolean; begin FileGood := OpenSomeFile; if not FileG ood then ReportError; end;
Learning the Object Pascal shortcuts helps you write code that contains a degree of elegance. Knowing the shortcuts will also help you understand Object Pascal code that you read in examples and sample listings.
In some cases, you might want to perform an action when the conditional expression evaluates to True and perform some other action when the conditional expression evaluates to False. You accomplish this by implementing the else statement:
if X = 20 then DoSomething(X) else DoADifferentThing(X);
New Term: The else statement is used in conjunction with the if statement and identifies a section of code that is executed when the if statement fails (that is, evaluates to False).
In this example, one of the two functions will be called based on the value of X, but not both.
I want you to notice something about the preceding example. The line following the if statement does not end in a semicolon. This is because the entire if...then...else sequence is viewed as a single statement. You omit the semicolon on the first line following the if statement only if it's a single line of code (that is, you are not using begin and end following the if statement). Here are a couple of examples of legal if...then...else syntax:
if X = 20 then DoSomething(X) { no semi-colon here because } else { it's a single line of code } DoADifferentThing(X); if X = 20 then begin DoSomething(X); { semi-colon needed here because } end else begin { of the begin/end block } DoADifferentThing(X); end; if X = 20 then begin DoSomething(X); { Multiple lines, use semi-colons } X := 200; { at the end of each line. } Y := 30; end else begin DoADifferentThing(X); X := 100; Y := 15; end;
NOTE: It doesn't matter where you put the then, begin, and else keywords. The following two blocks of code are identical as far as the compiler is concerned:{ One way to do it... } if X = 20 then begin DoSomething(X); X := 200; Y := 30; end else begin DoADifferentThing(X); X := 100; Y := 15; end; { Same code, different layout... } if X = 20 then begin DoSomething(X); X := 200; Y := 30; end else begin DoADifferentThing(X); X := 100; Y := 15; end;
Ultimately it's up to you to decide on the coding style that you will use. While coding style is largely a matter of preference, be sure you settle on a style that makes your code easy to read.
NOTE: Remember that the equality operator is the equal sign (=) and that the assignment operator is colon-equal (:=). A common coding mistake is to use the assignment operator where you mean to use the equality operator. Fortunately, the compiler will issue an error when you do this.
You can nest if statements when needed. Nesting is nothing more than following an if statement with one or more additional if statements.
if X > 10 then if X < 20 then Label1.Caption := `X is between 10 and 20';
Keep in mind that these are simplified examples. In the real world, you can get lost in the maze of begin and end statements that separate one code block from the next. Take a look at this code snippet, for instance:
if X > 100 then begin Y := 20; if X > 200 then begin Y := 40; if X > 400 then begin Y := 60; DoSomething(Y); end; end;
end else if X < -100 then begin Y := -20; if X < -200 then begin Y := -40; if X < -400 then begin Y := -60; DoSomething(Y); end; end; end;
Even this is a fairly simple example, but you get the idea.
[BEGTIP: When a section of code contains more than two or three consecutive if statements testing for different values of the same variable, it might be a candidate for a case statement. The case statement is discussed later in this chapter in the section "The case Statement."
So far I have used only one conditional expression in the if examples I have given you. When you have just one conditional expression, you can use parentheses around the expression or not use parentheses as you see fit. If, however, you have more than one conditional expression, you must surround each conditional expression with parentheses. For example:
if (X = 20) and (Y = 50) then DoSomething;
If you forget the parentheses, the compiler will, of course, let you know by issuing a compiler error.
The if statement is heavily used in Object Pascal programming. It's straightforward, so you won't have any trouble with it. The main thing is keeping all the begin and end keywords straight!
if cond_expr then true_statement else false_statement;
If the conditional expression cond_expr is True, the line of code represented by true_statement is executed. If the optional else clause is specified, the line of code represented by false_statement is executed when the conditional expression cond_expr is False.
if cond_expr_1 then begin true_statements; end else begin false_statements; end;
If the conditional expression cond_expr_1 is True, the block of code represented by true_statements is executed. If it is False, the block of code represented by false_statements is executed.
The loo p is a common element in all programming languages. A loop can be used to iterate through an array, to perform an action a specific number of times, to read a file from disk...the possibilities are endless. In this section, I will discuss the for loop, the while loop, and the repeat loop. For the most part they work in very similar ways. All loops have these common elements:
A loop is an element in a programming language that is used to perform an action repeatedly until a specific condition is met.
The starting point for the loop is one of the Object Pascal loop statements (for, while, or repeat). The body contains the statements that will execute each iteration through the loop. The body can contain any valid Object Pascal code and can be a single line of code or multiple lines of code. If the body contains multiple lines of code, the code must be blocked with begin and end statements (with the exception of the repeat loop). The ending point for the loop is either the end keyword (in the case of the for loop and the while loop) or the until keyword (in the case of the repeat loop). When the body of a loop is a single line of code, the begin and end keywords are not required.
Most loops work something like this: The loop is entered and the test condition is evaluated. If the test condition evaluates to False, the body of the loop is executed. When program execution reaches the bottom of the loop, it jumps back to the top of the loop where the test condition is again evaluated. If the test condition is still False, the whole process is repeated. If the test condition is True, program execution jumps to the line of code immediately following the loop code block. The exception to th is description is the repeat loop, which tests for the condition at the bottom of the loop rather than at the top.
The test condition tells the loop when to stop executing. In effect the test condition says, for example, "Keep doing this until X is equal to 10," or "Keep reading the file until the end-of-file is reached." After the loop starts, it continues to execute the body of the loop until the test condition evaluates to True.
CAUTION: It's easy to accidentally write a loop so that the test condition never evaluates to True. This will result in a program that is locked up or hung. Your only recourse at that point is to press Ctrl+Alt+Del and kill the task. The Windows Close Program box (or the Windows NT Task Manager) will come up and display the name of your program with (Not Responding) next to it. You'll have to select your program from the list and click End Task to terminate the runaway program.
TIP: In Delphi you typically run a program using the Run button on the toolbar or by pressing F9. If you need to kill a runaway program that was run from the IDE, you can choose Run | Program Reset from the main menu or press Ctrl+F2 on the keyboard. Note, however, that Windows 95 does not like you to kill tasks with Program Reset and might crash if you reset a program several times (Windows NT is much more forgiving in this area). Always run your programs to completion if possible, especially when developing on the Windows 95 platform.
Given that general overview, let's take a look at each type of loop individually.
The for loop is probably the most commonly used type of loop. It takes two parameters: the starting value and ending value. If the loop is to count up, the to keyword is used. If the loop is to count backward, then the downto keyword is used.
The for loop repeatedly executes the block of code indicated by statements until the ending value end_value is reached. The state of the loop is initialized by the statement initial_value. The variable indicated in initial_value is incremented by one each iteration through the loop. If the body of the loop is a single statement, the begin and end statements are not required.
for initial_value downto end_value do begin statements; end;
The for loop repeatedly executes the block of code indicated by statements until the ending value end_value is reached. The state of the loop is initialized by the statement initial_value. The variable indicated in initial_value is decremented by one each iteration through the loop. If the body of the loop is a single statement, the begin and end statements are not required.
As most syntax statements are somewhat vague, some examples will probably help. First, take a look at a typical for loop that counts up:
var I : Integer; begin for I := 0 to 9 do begin Memo1.Lines.Add(`This is iteration ` + IntToStr(I)); end; end;
This code will result in the statement inside the braces being executed 10 times. The first parameter, I := 0, tells the for loop that it is starting with an initial value of 0. The second parameter, 9, tells the loop to keep running until the variable I equals 9. The to keyword specifies that the value of I should be incremented by one each time the loop executes.
NOTE: The use of the variable name I has its roots in the FORTRAN language and is traditional in for loops. Naturally, any variable name can be used, but you will often see I used in for loops.
Let's look at a variation of this code. The following code s nippet will achieve exactly the opposite effect as the first example:
var I : Integer; begin for I := 9 downto 0 do begin Memo1.Lines.Add(`This is iteration ` + IntToStr(I)); end; end;
This time I'm starting with 9 and stopping when I is equal to 0. The downto keyword specifies that the value of I should be decremented each time the loop executes. This is an example of a loop that counts backward.
NOTE: In the preceding examples, the begin and end keywords are not strictly required. If begin and end are not used, the statement immediately following the for statement is considered the body of the loop. It's up to you whether you use begin and end on single statements, although it's considered bad form to do so.
Let's write a little program that illustrates the use of the for loop. In doing so, I will explain another Delphi component, the Memo component (used earlier in this chapter). Perform these steps:
procedure TForm1.Button1Click(Sender: TObject); var I : Integer; begin Memo1.Lines.Clear; for I := 0 to 5 do Memo1.Lines.Add(`This is iteration ` + IntToStr(I));Memo1.Lines.Add(`'); for I := 5 downto 0 do Memo1.Lines.Add(`This is iteration ` + IntToStr(I)); end;
Run the program. When you click the button, lines of text are added to the memo. Figure 2.1 shows this program running.
As I said earlier, the loop variable will be incremented by one each time through the loop. Unlike other programming languages, Pascal doesn't provide a way of iterating through a for loop by a value other than one. For example, there is no way to iterate through a for loop from 0 to 100 by 10s. To accomplish this, you must make use of another variable as follows:
var I : Integer; X : Integer; begin X := 0; Memo1.Lines.Clear; for I := 0 to 9 do begin Memo1.Lines.Add(`Iteration value: ` + IntToStr(X)); Inc(X, 10); end; end;
This code will display this in the memo:
Iteration value: 0 Iteration value: 10 Iteration value: 20 Iteration value: 30 Iteration value: 40 Iteration value: 50 Iteration value: 60 Iteration value: 70 Iteration value: 80 Iteration value: 90
FIGURE 2.1. The output from the for loop exercise.
NOTE: Notice the use of the Inc function in the preceding snippet. This function increments the given variable (X in this example) by the specified value (10). If Inc is used without an increment parameter, the variable is incremented by one. For example:Inc(X); { X is incremented by one. Same as X := X + 1 }
Inc has a companion function called, predictably, Dec. Here are examples of the Dec function:
Dex(X); { X is decremented by one } Dec(X, 10); { X is decremented by 10 }
Use of Inc and Dec is preferred over the long version (X := X + 1, for example).
You will often see the Pred and Succ functions used with for loops . The Pred function returns the predecessor of the passed argument. For example, Pred(10) will return the value 9, Pred(100) will return 99, and so on. Given that information, the following three for loops are identical:
var X : Integer; begin X := 10; for I := 0 to 9 do DoSomething; for I := 0 to X - 1 do DoSomething; for I := 0 to Pred(X) do
DoSomething;
end;
When you start with an initial value of 0, it's natural to make the mistake of doing one too many iterations in a loop. Using the Pred function solves this problem and is a bit more elegant than using X - 1. The Succ function, naturally, returns the successor of the argument passed. This is useful when counting backward:
for I := 100 downto Succ(X) do DoSomething;
Now that you've seen the for loop in action, it won't be too difficult to apply the same concepts to the while and repeat loops. Let's take a look at those now.
The while loop differs from the for loop in that it contains a test condition that is checked at the start of each iteration. As long as the test condition is True, the loop keeps running.
var X : Integer; begin X := 0; while X < 1000 do begin X := DoSomeCalculation; DoSomeMoreStuff; end; { ...more code } end;
In this example, I am calling a function that I assume will eventually return a value greater than or equal to 1,000. As long as the return value from this function is less than 1,000, the while loop continues to run. When the variable X contains a value greater than or equal to 1,000, the test condition yields False and program execution jumps to the first line following the body of the while loop. A common implementation of a while loop uses a Boolean test variable. The state of the test variable can be set somewhere within the body of the loop:
var Done : Boolean; begin Done := False; while not Done do begin DoSomeSt uff; Done := SomeFunctionReturningABoolean; DoSomeMoreStuff; end; end;
At some point it is expected that the variable Done will be True, and the loop will terminate. Let's do another simple program that illustrates the use of the while loop. Start a new application and place a button and a memo on the form. Double-click the button and modify the event handler so that it looks like this:
procedure TForm1.Button1Click(Sender: TObject); var I : Integer; begin I := 5; Memo1.Lines.Clear; while I > -1 do begin Memo1.Lines.Add(`Today I have ` + IntToStr(I) + ` problems to worry about.'); Dec(I); end; Memo1.Lines.Add(`Yeah!'); end;
When you run the program and click the form's button, you will see this text in the memo:
Today I have 5 problems to worry about. Today I have 4 problems to worry about. Today I have 3 problems to worry about. Today I have 2 problems to worry about. Today I have 1 problems to worry about. Today I have 0 problems to worry about. Yeah!
This program declares a variable, I, and initializes it to a value of 5. Next, a while loop is started. Text is added to the Memo component each time through the loop, and the variable I is decremented by one. When I is equal to -1, the loop stops and a final line is added to the memo.
while cond_expr do begin statements;
end;
The while loop repeatedly executes the block of code indicated by statements as long as the conditional expression cond_expr is True. The state of the loop must be initialized prior to the while statement and modification of the state must be explicit in the block of code. When the conditional expression cond_expr evaluates to False, the loop terminates. If the body of the loop is a single statement, the begin and end statements are not required.
The repeat loop is nearly identical to the while loop. The distinction between the two is important, though. As you found out in the last exercise, the while loop checks the conditional expression at the top of the loop. In the case of the repeat loop, the conditional expression is checked at the bottom of the loop. For example, here's the previous exercise you did except that a repeat loop has been substituted for the while loop:
procedure TForm1.Button1Click(Sender: TObject); var I : Integer; begin I := 5; Memo1.Clear; repeat Memo1.Lines.Add(`Today I have ` + IntToStr(I) + ` problems to worry about.'); Dec(I); until I = -1; Memo1.Lines.Add(`Yeah!'); end;
This code will result in the same text displayed in the memo as the previous exercise. Note that it is not necessary to use begin and end because the repeat keyword marks the beginning of the code block, and the until keyword marks the end of the code block. Whether you use a while or a repeat loop depends on what the loop itself does.
repeat statements; until cond_expr;
The repeat loop repeatedly executes the block of code indicated by statements as long as the conditional expression cond_expr is False. The state of the loop must be initialized prior to the repeat statement, and modification of the state must be explicit in the block of code. When the conditional expression cond_expr evaluates to True, the loop terminates.
NOTE: Due to the way the repeat loop works, the code in the body of the loop will be executed at least once regardless of the value of the test condition (because the condition is evaluated at the bottom of the loop). In the case of the while loop, the test condition is evaluated at the top of the loop, so the body of the loop might never be executed.
I'll mention goto just so you know it exists. The goto statement enables you to jump program execution to a label that you have previously declared with the label keyword. The label itself is placed in the code followed by a colon. The following code snippet illustrates:
procedure TForm1.Button1Click(Sender: TObject); var I: Integer; label MyLabel; begin Memo1.Clear; I := 0; MyLabel: Inc(I); Memo1.Lines.Add(IntToStr(I)); if I < 5 then goto MyLabel; end;
It is not necessary to use begin and end here because all lines of code between the goto statement and the label will be executed.
NOTE: The goto statement is considered bad form in an Object Pascal program. Just about anything you can accomplish with goto you can accomplish with a while or repeat loop. Very few self-respecting Object Pascal programmers have goto in their code. If you are moving to Object Pascal from another language that uses goto statements, you will find that the basic structure of Object Pascal makes the goto statement unnecessary.
label label_name; goto label_name . . . label_name:
The goto statement unconditionally transfers the program execution sequence to the label represented by label_name.
Before we leave this discussion of loops, you need to know about two procedures that help control program execution in a loop. The Continue procedure is used to force program execution to the bottom of the loop, skipping any statements that come after the call to Continue. For example, you might have part of a loop that you don't want to execute if a particular test returns True. In that case, you would use Continue to avoid execution of any code below that point in the code:
var Done : Boolean; Error : Boolean; begin Done := False; while not Done do begin { some code } Error := SomeFun ction; if Error then Continue; { jumps to the bottom of the loop } { other code that will execute only if no error occurred } end; end;
The Break procedure is used to halt execution of a loop prior to the loop's normal test condition being met. For example, you might be searching an array of integers for a particular number. By breaking execution of your search loop when the number is found, you can obtain the array index where the number was located:
var MyArray : array [0..99] of Integer; Index : Integer; SearchNumber : Integer; I : Integer; begin FillArray; { procedure which fills the array } Index := -1; SearchNumber := 50; for I := 0 to High(MyArray) do begin if MyArray[I] = SearchNumber then begin Index := I; Break; end; end; if Index <> -1 then Label1.Caption := `Number found at index ` + IntToStr(Index) else Label1.Caption := `Number not found in array.'; end;
Continue and Break are only used within for, while, and repeat loops. If you attempt to use these procedures outside of a loop, the compiler will generate a compiler error that says BREAK or CONTINUE outside of loop.
There are many situations in which the Continue and Break procedures are useful. As with most of what I've been talking about, it will take some experience programming in Object Pascal before you discover all the possible uses for these two procedures.
The case statement can be considered a glorified if statement. It enables you to execute one of several code blocks based on the result of an expression. The expression might be a variable, the result of a function call, or any valid Object Pascal code that evaluates to an expression. Here is an example of a case statement:
case AmountOverSpeedLimit of 0 : Fine := 0; 10 : Fine := 20; 15 : Fine := 50; 20, 25, 30 : Fine := AmountOverSpeedLimit * 10; else begin Fin e := GoToCourt; JailTime := GetSentence; end; end;
There are several parts that make up a case statement. First, you can see that there is the expression, which in this example is the variable AmountOverSpeedLimit (remember, I warned you about long variable names!). Next, the case statement tests the expression for equality. If AmountOverSpeedLimit equals 0 (0 :), the value 0 is assigned to the variable Fine. If AmountOverSpeedLimit is equal to 10, a value of 20 is assigned to Fine, and so on. In each of the first three cases a value is assigned to Fine and code execution jumps out of the case statement, which means that a case matching the expression has been found and the rest of the case statement can be ignored.
Notice that cases 20 and 25 have commas following them, but no statements. If the expression AmountOverSpeedLimit evaluates to 20 or 25, those cases fall through and the next code block encountered will be executed. In this situation, values of 20, 25, or 30 will all result in the same code being executed.
Finally, you see the else statement. The code block following the else statement will be executed if no matching cases are found. Inclusion of the else statement is optional. You could write a case statement without an else:
case X of 10 : DoSomething; 20 : DoAnotherThing; 30 : TakeABreak; end;
As I said earlier, you might want to use a case statement if you find that you have several if statements back to back. The case statement is a bit clearer to others reading your program.
NOTE: The expression portion of a case statement must evaluate to an Object Pascal ordinal type (Integer, Word, Byte, and so on). The following, for example, is not allowed:
case SomeStringVariable of `One' : { code } `Two' : { code } end;
String values are not allowed, nor are floating-point values.
case expr of value_1: statements_1; value_2: statements_2; . . . value_n: statements_n; else else_statements; end;
The case statement offers a way to execute different blocks of code depending on various values of an expression expr. The block of code represented by statements_1 is executed when expr is equal to value_1, the block of code represented by statements_2 when expr is equal to value_2, and so on through the block of code represented by statements_n when expr is equal to value_n. When expr is not equal to any of the value_1 through value_n, the block of code at else_statements is executed. The else statement is optional.
The term scope refers to the visibility of variables within different parts of your program. Most variables have local scope, which means that the variable is visible only within the code block in which it is declared. Take a look at the program in Listing 2.1. (This is the first look you've had at a complete unit as generated by Delphi. There is some code here that you haven't seen before, and I'll explain it all in due time, but for the time being you can ignore the parts you aren't familiar with.)
New Term: The term scope refers to the visibility of variables within different parts of your program.
01: unit ScopeU; 02: 03: interface 04: 05: uses 06: Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, ÂDialogs, 07: StdCtrls; 08: 09: type 10: TForm1 = class(TForm) 11: Button1: TButton; 12: Memo1: TMemo; 13: procedure Button1Click(Sender: TObject); 14: procedure FormCreate(Sender: TObject); 15: private 16: { Private declarations } 17: public 18: { Public declarations } 19: end; 20: 21: var 22: Form1: TForm1; 23: 24: implementation 25: 26: v ar 27: X : Integer; 28 29: {$R *.DFM} 30: 31: procedure TForm1.Button1Click(Sender: TObject); 32: var 33: X : Integer; 34: 35: procedure Test; 36: var 37: X : Integer; 38: begin 39: X := 300; 40: { This X variable has a value of 300. } 41: Memo1.Lines.Add(`Local Function X: ` + IntToStr(X)); 42: end; 43: 44: begin 45: X := 100; 46: Memo1.Lines.Clear; 47: { Local X has a value of 100. } 48: Memo1.Lines.Add(`Local X: ` + IntToStr(X)); 49: { Unit scope X has a value of 200. } 50: Memo1.Lines.Add(`Global X: ` + IntToStr(ScopeU.X)); 51: { Call the Test procedure. } 52: Test; 53: end; 54: 55: procedure TForm1.FormCreate(Sender: TObject); 56: begin 57: { Initialize the unit variable X. } 58: X := 200; 59: end; 60: 61: end.
The first thing you might notice (if you're still awake by this time) is that the variable X is declared three times in this program. It is declared on line 27 in the implementation section, it is declared on line 33 in the Button1Click method, and it is declared on line 37 in a Local Procedure called Test. If you accidentally declare a variable more than once, the compiler spits out an error that says Identifier redeclared: `X', and the compile stops. Yet this program compiles and runs just fine. Why? Because each of the X variables in Listing 2.1 has different scope.
Take a closer look at Listing 2.1. The declaration for X on line 37 is inside the local procedure Test and is local to that block of code. (I realize I haven't talked about local functions and procedures yet so I'm getting a bit ahead of myself again. Bear with me; I explain local functions later in the section "Local Functions and Procedures.") Effectively, the X that is declared on line 37 does not exist outside the Test procedure. This variable has local scope. Likewise, the declaration for X on line 33 is local to the Button1Click method and does not exist outside the function.
Now look at the variable X declared in the implementation section. T his variable is visible anywhere in this unit. Think about that for a minute. Once inside the Button1Click procedure, there are two variables named X (the one declared in the implementation section and the one declared in the Button1Click method), and both are in scope. Which one is being used? The answer: the one in the Button1Click method, because it has the most immediate scope.
The variable X that is declared in the implementation section is said to have unit scope. What this means is that this variable X is available anywhere in the unit. As mentioned earlier, a local variable has precedence over a variable with unit scope. But what if you want to access the unit variable X from inside the Button1Click procedure and not the local variable X? You can qualify the variable. Line 50 of Listing 2.1 contains this line:
Memo1.Lines.Add(`Global X: ` + IntToStr(ScopeU.X));
As you can see, the variable X is qualified with the unit name (ScopeU) followed by the dot operator. Qualifying the variable with the unit name says, "Give me the unit variable X and not the local variable X." (The dot operator is also used with records and classes, but I'll get to that when I talk about classes later.)
As I said, when the unit variable X is declared in the implementation section, it has unit scope. If you want a variable to be available to other units in the project, you should declare the variable in the interface section (the variable Form1 in Listing 2.1 is declared in this way). A variable declared in the interface section can be accessed from other units in the project. A variable declared in this way is often referred to as a global variable. To access a variable declared in the interface section of a unit requires nothing more than adding the unit to the uses list and accessing the variable as you would any other variable. If any units in the project have variables with the same name, the variables can be qualified with the unit name as described earlier.< /P>
NOTE: I just said that a variable declared in a unit's interface section is usually referred to as a global variable. That's not entirely accurate, though, because the variable cannot be automatically used by other units in the project--you have to add the unit containing the variable to the uses list of any other unit that wants to use the variable. A true global variable is a variable that can be used by any unit in the program without the need to add the unit containing the variable to the uses list. Delphi has a few global variables set up by the compiler's startup code. You cannot declare true global variables yourself.
A record is a collection of related data rolled up into a single storage unit. For instance, let's say you want to keep a mailing list. It would be convenient to use a single data variable to hold all the fields needed in a typical mailing list. A record enables you to do that. You first declare the record and then later create an instance of that record when you want to use the record. A record is declared with the record keyword:
MailingListRecord = record FirstName : string; LastName : string; Address : string; City : string; State : string; Zip : Integer; end;
Each of the elements in a record is called a field. Notice that each of the fields must be declared just as if it were a variable in a code block. This record example has five string fields and one integer field. (My apologies to my friends around the world if this looks like a U.S.-slanted mailing-list record.) A zip code/postal code field should really be a string as well, but I want to show you a record with more than one data type.
A record is a collection of related data identified as a single storage unit. After a record is declared, an instance of that record can be created for use. Each of the elements in a record is called a field. P>
NOTE: The record in this example is fine for what I am doing here, but it is not ideal for storing records in files. When you store records in files, each record should be of the exact same size in bytes. Because the record in this example uses long strings as fields, there is no way to guarantee that the records will all be the same size. When creating records that will be stored in a file, you should use short strings or even an array of Char over long strings. I'll talk about this more tomorrow when I discuss file input and output in the section "Dealing with Binary Data."
Now that the record is declared, it can be put to use. I first need to create an instance of the record. Here's how that looks:
var MLRecord : TMailingListRecord;
This statement allocates memory for the record and assigns that memory to a variable named Record. Now that I have an instance of the record set up, I can assign values to the fields:
MLRecord.FirstName := `Bruce'; MLRecord.LastName := `Reisdorph'; MLRecord.Address := `123 Inspiration Pt.'; MLRecord.City := `Merced'; MLRecord.State := `CA'; MLRecord.Zip := 99999;
This code snippet contains some syntax you haven't seen yet (although it is very similar to earlier examples when I was discussing qualifying variables). To access the fields of a record, you need to employ the structure member selector operator, commonly called the dot operator. The dot operator is a period placed between the variable name and the field name. If you forget to add the record member operator, you will probably find the compiler complaining about undefined symbols. The record member operator enables you to access a particular member of the record--either to read the value of the field or to change the value of the field. Here's an example of placing the contents of a particular field in a record into an label on a form:
Label1.Caption := MLRecord.LastName;
name = record field_1 : data_type; field_2 : data_type; . . . field_n : data_type; end;
The record statement declares a grouping of fields (field_1, field_2, ..., field_n) and provides a name for this grouping (name).
As long as I am talking about records, let me introduce the with statement. Use of the with statement is not limited to records, but this is a good place to illustrate how the with statement is used. Earlier I gave you this example of filling in a structure:
MLRecord.FirstName := `Bruce';
MLRecord.LastName := `Reisdorph'; MLRecord.Address := `123 Inspiration Pt.'; MLRecord.City := `Merced'; MLRecord.State := `CA'; MLRecord.Zip := 99999;
The with statement can be used to help simplify this code. Here is the same code, but implementing the with statement:
with MLRecord do begin FirstName := `Bruce'; LastName := `Reisdorph'; Address := `123 Inspiration Pt.'; City := `Merced'; State := `CA'; Zip := 99999; end;
The with statement says, "With this object (MLRecord) do the following...." Notice that when the with statement is implemented, you no longer have to qualify the field names with the record identifier and dot operator. Everything within the begin and end blocks is assumed to belong to the MLRecord object, so qualifying the field names is unnecessary. The with statement can save you a lot of typing and can also make the code more readable.
Just as you can have arrays of Integers, Chars, or Words, you can also have arrays of records. Declaring and using an array of records is not terribly complicated:
var MLRecord : array [0..9] of MailingListRecord; begin MLRecord[0].FirstN ame := `Bruce'; MLRecord[0].LastName := `Reisdorph'; MLRecord[0].Address := `123 Inspiration Pt.'; MLRecord[0].City := `Merced'; MLRecord[0].State := `CA'; MLRecord[0].Zip := 99999; MLRecord[1].FirstName := `Georgia'; MLRecord[2].LastName := `Burleson'; MLRecord[3].Address := `999 Fortitude'; MLRecord[4].City := `Denver'; MLRecord[5].State := `C0'; MLRecord[6].Zip := 80888; Label1.Caption := MLRecord[0].LastName; { More code here. } end;
This is only slightly more complicated than using an array of one of the integral data types. You will notice that the subscript operator and the record member operator are used together to retrieve the value of a field from a specific position in the array.
Sometimes Pascal programmers use include files. An include file can contain any code that you don't want in your main source unit. Typically, use of include files is reserved for constants or compiler directives that are intended to be used by many other files in the project. An include file is nothing more than a text file with and extension of .INC. The INC extension is not a requirement, but it is customary. Listing 2.2 shows an example of an include file.
const DefWidth = 500; DefHeight = 300; type MailingListRecord = record FirstName : string; LastName : string; Address : string; City : string; State : string; Zip : Integer; end;
To create an include file, you simply start with a new text file and save it with an extension of INC. First, choose File | New from the main menu. Next, double-click on the Text icon in the New Items dialog. A new text file will be created and opened in the Code Editor. Enter code and then save the file by choosing File | Save As from the main menu. Be sure to give the file an INC extension or the file will be saved with a TXT extension by default.
To use an include file, y ou use the $I compiler directive in any other units that need to use the declarations in the include file. It looks like this:
unit Unit2; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
{$I Test.inc} { ... rest of unit follows }
The $I compiler directive tells the compiler to compile the contents of the include file into the unit at that point. It's as if the include file were pasted into the unit at that point. You need to be sure that any code in the include file is syntactically correct, or a compiler error will be generated. Don't be too concerned if this is a little confusing right now. It will probably take some experience writing real programs for all this to come together for you.
Functions and procedures are sections of code separate from the main program. These code sections are executed when needed to perform specific actions in a program. For example, you might have a function that takes two values, performs a complex mathematical calculation on those two values, and returns the result. You might need a function that takes a string, parses it, and returns a portion of the parsed string. You can call (use) these functions any time throughout your programs.
Functions and procedures can collectively be called subroutines. (While the term subroutine is not commonly used in Pascal, it is a convenient word to cover both functions and procedures, so I'll use it here.) Subroutines are an important part of any programming language, and Object Pascal is no exception. The simplest type of subroutine takes no parameters and returns no value. Other subroutines might take one or more parameters and might return a value. Rules for naming functions and procedures are the same as those discussed earlier for variables.
New Term: A function is a section of code separate from the main progra m that performs some action and returns a value.
New Term: A parameter is a value passed to a function or procedure that is used to alter its operation or indicate the extent of its operation.
Figure 2.2 shows the anatomy of a function.
FIGURE 2.2. The anatomy of a function.
New Term: A procedure is a section of code separate from the main program that performs some action but does not return a value.
Figure 2.3 shows the anatomy of a procedure.
New Term: A method is a function or procedure that is a member of a class.
As you can see from these descriptions, the only difference between a function and a procedure is that a function returns a value and a procedure does not return a value.
FIGURE 2.3. The anatomy of a procedure.
Let's write a program that uses a function. Once again, start with a new application. Then perform the following steps:
function Multiply(Num1, Num2 : Integer) : Integer; begin Result := Num1 * Num2; end;
FIGURE 2.4. The Code Editor showing the Multiply function.
procedure TForm1.Button1Click(Sender: TObject); var X : Integer; begin X := Multiply(10, 20); Label1.Caption := IntToStr(X); end;
Run the program and click the button. The label will change to 200 when you click the button. Here's how it works: When you click the button, the Button1Click event handler is called. This, in turn, calls the Multiply function, passing the values of 10 and 20 as parameters. The result is stored in the variable X, which is then displayed in the label.
NOTE: This example illustrates the use of a standalone function in a Delphi program. I normally would have made this function part of the main form's class, but because we haven't talked about classes yet I would be getting ahead of myself by using that technique here.
You might be thinking, "Okay, but how does the product of the two numbers get back from the function?" Take another look at the Multiply function:
function Multiply(Num1, Num2 : Integer) : Integer; begin
Result := Num1 * Num2;
end;
Every Object Pascal function has a local variable called Result. This variable is declared invisibly by the compiler, and it is used to hold the return value from the function. To return a specific value from a function, then, is a simple matter of assigning that value to the Result variable within the function.
NOTE: There is another way of specifying the return value for a function. Rather than assigning the return value to the Result variable, you assign the return value to the function name. For example:function Multiply(Num1, Num2 : Integer) : Integer; begin Multiply := Num1 * Num2; end;
You might see this method used in older Pascal programs or in programs ported to Delphi from Turbo Pascal (one of Delphi's prede cessors).
The Multiply function can be called in one of several ways. You can pass variables, literal values, or even the results of other function calls. For example:
X := Multiply(2, 5); { passing literal values } X := Multiply(A, B); { passing variables } { return value used as a parameter for another function } Label1.Caption := IntToStr(Multiply(X, Y)); Multiply(X, Y); { return value ignored }
Notice in the preceding example that the return value is not used. In this case, it doesn't make much sense to call the Multiply function and ignore the return value, but ignoring the return value is something that is done frequently in Object Pascal programming. There are many functions that perform a specific action and then return a value indicating the status of the function call. In some cases the return value is not relevant to your program, so you can just ignore it. If you don't do anything with the return value, it is simply discarded and no harm is done.
Now let's add a procedure to the program by following these steps:
procedure SayHello; begin MessageDlg(`Hello There!', mtInformation, [mbOk], 0); end;
procedure TForm1.Button1Click(Sender: TObject); var X : Integer; begin X := Multiply(10, 20); Label1.Caption := IntToStr(X); SayHello; end;
Now run the program again. This time when you run the program, the result of the Multiply function is shown in the label as before, and then a message box appears. The message box is shown as a result of calling the SayHello procedure. Calling the SayHello procedure is extremely simple because the procedure takes no parameters. It's important to understand that the code in a function or procedure is executed only if you specifically call the function or procedure from somewhere in your code.
TIP: Any time you find yourself repeating code more than a couple of times in your programs, think about moving that code to a subroutine. Then you can call the subroutine when you need to execute that code.
Subroutines can (and frequently do) call other subroutines. Subroutines can even call themselves. This is called recursion and is one way to get into trouble when programming! Recursion is best left alone until you've put in some time with the Object Pascal language.
New Term: Recursion is the process by which a procedure or function calls itself.
Functions and procedures often have a declaration and always have a definition.
New Term: A declaration is a single statement that describes a method's name and parameters, if any. In the case of a function, the declaration also indicates the function's return type.
New Term: A function or procedure's definition is the actual body of the function or procedure in the implementation section of the unit.
There are three primary cases where a declaration is necessary:
I haven't used declarations up to this point, only definitions. This is because the function definition a lways came before the place in the code where the function was actually used. Take the Multiply function, for example. If I had written a function declaration for this function, it would look like this:
function Multiply(Num1, Num2 : Integer) : Integer;
As you can see, the function declaration simply describes the function.
Function and procedure declarations are placed in the interface section. Placing a declaration in the interface section automatically makes that function or procedure available to other units (makes it public, so to speak). If you don't want the function or procedure to be visible to other units, you can't use a declaration. Instead, you will have to make sure that the function or procedure is defined near the top of the interface section so that it can be seen by all other methods in the unit that use the function. As I said, the examples of functions and procedures up to this point have used this method. I could have done it the other way and used both declaration and definition. Here's part of a unit that contains a declaration for the Multiply function, a Button1Click method that calls the Multiply function, and the definition of the Multiply function:
unit Unit1; interface { some code removed... } function Multiply(Num1, Num2 : Integer) : Integer; implementation procedure TForm1.Button1Click(Sender: TObject); var X : Integer; begin X := Multiply(10, 20); end; function Multiply(Num1, Num2 : Integer) : Integer; begin Result := Num1 * Num2; end; end.
In this case the declaration is necessary because the Multiply function is defined after the Button1Click method that calls it. The declaration tells the compiler that a function can be found later in the unit. You'll learn more about function declarations tomorrow when we talk about methods in classes.
NOTE: If you declare a function but neglect to define it, the compiler will issue an error that says, Unsatisfied forward or ex ternal declaration: `Multiply.'
Parameters to functions or procedures can be of at least three different types (more than three, actually, but I'll only discuss three types here).
First, parameters can be value parameters. All the parameters you have seen up to this point have been value parameters. The value parameter acts like a local variable in the function or procedure. You can modify the variable within the function and the original variable will remain unchanged. Let's create a new function that illustrates the point. This function will be called SquareAndMultiply. It will take two numbers, square them, multiply them together, and return the result. Here it is:
function SquareAndMultiply(Num1, Num2 : Integer) : Integer; begin Num1 := Num1 * Num1; Num2 := Num2 * Num2; Result := Num1 * Num2; end;
Now let's look at the code that will call this function:
procedure TForm1.Button1Click(Sender: TObject); var X : Integer; Y : Integer; Z : Integer; begin X := 2; Y := 3; Z := SquareAndMultiply(X, Y); Label1.Caption := IntToStr(Z); end;
If you want, you can enter this code to test it out. Two values are passed to SquareAndMultiply. The two values are modified inside the SquareAndMultiply function because the numbers need to be squared before they are multiplied together. However, the original values of X and Y in the Button1Click method do not change. When a function uses a value parameter, the compiler first makes a copy of the variable passed to the function and then sends the copy to the function. The original variable is unchanged because the copy is sent to the function and not the actual variable.
Another way to send values to functions is to use constant parameters. A constant parameter cannot be changed inside the function body. Here's an example of a procedure that takes a constant parameter:
procedure SaySomething(const S : string); begin S := S + `Test'; ShowMessage(S); end;
This is one of the few code examples in this book that contains an error (I hope!). The compiler will issue an error on the first line in this procedure. The compiler error will say, Left side cannot be assigned to. The error is generated because the const keyword stipulates that the variable S cannot be modified. Any attempts to modify the constant parameter will result in a compiler error. Write procedures and functions using constant parameters when you don't want the passed variable to be modified within the function.
A third way to send values to functions is to use reference parameters. When you use a reference parameter, the compiler does not make a copy of the object as it does when using value parameters. Rather, the actual variable is passed. This means that any changes made to the variable in the function or procedure will modify the original variable. The following is an example of a procedure that uses a reference parameter (both the procedure and the use of the procedure are shown):
procedure Square(var Number : Integer); begin Number := Number * Number; end; procedure TForm1.Button1Click(Sender: TObject); var X : Integer; begin X := 20; Square(X); Label1.Caption := IntToStr(X); end;
First look at the Square procedure. Notice that the variable parameter is designated by using the var keyword. Because the var keyword is used to declare reference parameters, those parameters are commonly called var parameters. I'll use the terms interchangeably in this section.
NOTE: Many Object Pascal keywords do double duty. In this case, the var keyword is used to declare a reference parameter. Previously you have seen the var keyword used to declare variables in a function, procedure, or unit. The compiler knows the cont ext in which the keyword is being used and therefore knows how to compile the code correctly.
Notice also that the body of the function modifies the variable Number by multiplying it times itself. Next, notice the code in the Button1Click method that calls Square. First the variable X is assigned a value. Next, that variable is passed to the Square procedure. After Square executes, the value of X will be 400. Because Square takes a variable parameter, the variable passed to the procedure (X in this case) will be modified. Use variable parameters when you want the procedure or function to make some change to a variable. The fact that an object can be modified by the function or procedure is the most important aspect of variable parameters.
Because Square uses a variable parameter you must pass a variable of the same type as the variable parameter. You cannot, for example, do this:
Square(30);
This code will generate a compiler error because you can't pass a literal value for a variable parameter. This won't compile either:
var X : Word; begin X := 20; Square(X);
In this case, X is declared as a Word, and the variable parameter of the Square procedure is declared as an Integer. The compiler will generate an error because the types don't match. The compile error generated is Types of actual and formal var parameters must be identical.
TIP: Remember that a function can return only one value. By using variable parameters, you can achieve the effect of a function returning more than one value. The function still returns only one value, but the objects passed by reference are updated, so the function effectively returns multiple values.
A local function or procedure is a subroutine that is contained within another subroutine. Here's an example:
procedure TForm1.Button1Click(Sender: TObject); var X : Integer; { A local procedure. } procedure Test; begin Memo1.Lines.Add(`Local Function, X = ` + IntToStr(X)); end; begin X := 100; Memo1.Lines.Clear; Memo1.Lines.Add(`Main Function, X = ` + IntToStr(X)); Test; end;
Note that the procedure called Test is contained within the var section of the Button1Click procedure. A procedure declared in this way is called a local procedure because it is local to the function or procedure in which it is contained. A local subroutine can be called only from the containing routine; it cannot be called from anywhere else in the program.
An important fact of local procedures and functions is that the variables of the containing procedure are available inside the local subroutine. In this example, the variable X is available in the main body of the Button1Click procedure and in the local procedure. When this code executes, the memo component will contain this text:
Main Function, X = 100 Local Function, X = 100
This illustrates that the variable X is available in the local procedure as well as in the main procedure.
Starting with Delphi 4, Object Pascal enables you to work with functions that have the same name but take different parameters.
New Term: Method overloading is having two or more procedures or functions with the same name but with different parameter lists.
Methods that share a common name are called overloaded methods.
Earlier I showed you a sample program that contained a function called Multiply. Not surprisingly, this function multiplied two values together. The function took two integers, multiplied them, and returned the result. What if you want to have the function multiply two Doubles or two Words? Previous versions of Delphi would require you to have several functions:
{ declarations for a program written in Delphi 1, 2, or 3 } function MultiplyInt(Nu m1, Num2 : Integer) : Integer; function MultiplyDouble(Num1, Num2 : Double) : Double; function MultiplyWord(Num1, Num2 : Word) : Word;
Wouldn't it be a lot easier if you could just have one function called Multiply that would be smart enough to know whether you wanted to multiply Integers, Doubles, or Words? That is now possible in Delphi thanks to function overloading. Here's what the declarations for an overloaded function look like:
{ declarations in Delphi 4 } function Multiply(Num1, Num2 : Integer) : Integer; overload; function Multiply(Num1, Num2 : Double) : Double; overload; function Multiply(Num1, Num2 : Word) : Word; overload;
You still have to write separate functions for each of these declarations, but at least you can use the same function name. The compiler takes care of calling the correct function based on the parameters you pass to the function. For example:
var X, Y, Z : Double; begin X := 1.5; Y := 10.5; Z := Multiply(X, Y); end;
The compiler sees that two Doubles are passed to the function and calls the version of the Multiply function that takes two Doubles for parameters. Likewise, if two Integers are passed, the compiler calls the version of Multiply that takes two Integers.
NOTE: It is the parameter list that makes overloaded functions work. You can vary either the type or the number of parameters a function takes (or both), but you cannot create an overloaded function by changing just the return value. For example, the following does not constitute an overloaded function:function DoSomething : Integer; overload; function DoSomething : Word; overload;
If you try to compile a program containing these lines, you will get a compiler error that says Declaration of `DoSomething' differs from previous declaration. The two functions need to vary by more than just the return value to qualify as overloaded functions.
A procedure or function can have default parameters that, as the name implies, supply a default value for a parameter if no value is specified when the procedure or function is called.
A function implementing a default parameter might look like this:
{ Procedure declaration. } { Parameter `EraseFirst' will be false by default. } procedure Redraw(EraseFirst : Boolean = False); { Procedure definition. } procedure Redraw(EraseFirst : Boolean); begin if (EraseFirst) then begin { erase code } end; { drawing code } end;
You can call this function with or without a parameter. If the parameter is supplied at the time the function is called, the function behaves as a regular function would. If the parameter is not supplied when the function is called, the default parameter is used automatically. Given this example, the following two lines of code are identical:
Redraw; Redraw(False);
As you can see, when a parameter has a default value, it can be omitted from the function call altogether.
When declaring functions and procedures, you can mix default and nondefault parameters in the same function:
{ declaration } function PlayWaveFile(Name : string;
Loop : Boolean = False; Loops : Integer = 10) : Integer;
{ calls to PlayWaveFile } R := PlayWaveFile(`chime.wav'); { does not loop sound } R := PlayWaveFile(`ding.wav', True); { plays sound 10 times } R := PlayWaveFile(`bell.wave', True, 5); { plays sound 5 times }
Default parameters are helpful for many reasons. For one thing, they make your life easier. You might have a function that you call with the same parameters 99 percent of the time. By giving it default parameters, you shorten the amount of typing required each time you make a call to the function. Whenever you want to supply parameters other than the defaults, all you have to do is plug in values for the default parameters.
NOTE: Any default parameters must come at the end of the function's parameter list. The following is not a valid function declaration:procedure MyProcedure(X : Integer; Y : Integer = 10; Z : Integer);
In order for this function declaration to compile, the default parameter must be moved to the end of the function list:
procedure MyProcedure(X : Integer; Z : Integer; Y : Integer = 10);
If you don't put the default parameters at the end of the parameter list, the compiler will generate an error message.
This chapter contains essential information on some of Object Pascal's basic operations. You need to understand what is presented here in order to program in Delphi. First, you learned about the different types of loops in Object Pascal, and then you learned about the case statement and how to use it. I talked a little about scope and what that means to your variables. Then you found out about records and how they can be used in your programs. You finished the day by learning about functions and procedures.
The Workshop contains quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you have learned. You can find answers to the quiz questions in Appendix A, "Answers to the Quiz Questions."
© Copyright, Macmillan Computer Publishing. All rights reserved.