This chapter shows how to efficiently work with large numbers of variables, how to manipulate strings using standard Delphi procedures and functions, and how to create your own procedures and functions that work with strings.
Arrays are very useful for working with a large number of variables of the same type that are logically related. For instance, imagine that you have to create an application for your local cinema that shows which seats are occupied and which are vacant. Let's say that the entire cinema has 350 seats. To remember the state of every seat in the cinema, you would need 350 uniquely named Boolean variables:
var seat1: Boolean; seat2: Boolean; seat3: Boolean; seat350: Boolean;
Not only is this large number of declarations unacceptable, it leads to even more problems. What if you wanted to update the state of a seat based on what the user enters? There is no way other than to write a huge if-then or case statement that would check the value the user entered and change the corresponding variable. This means that you would have at least 350 lines of code to simply change the state of one seat:
var seat1: Boolean; seat2: Boolean; seat3: Boolean; seat350: Boolean; userSeat: Integer; begin ReadLn(userSeat); case userSeat of 1: seat1 := True; 2: seat2 := True; 350: seat350 := True; end; end.
Obviously, there has to be a better way of dealing with large collections of variables. The best way of doing so is to use an array.
An array is simply a collection of variables. All variables in the array share the same name and have the same data type. But unlike individual variables, all variables in an array have an additional property that differentiates them from one another — the index.
The syntax for array declaration is:
ArrayName: array[Range] of DataType; Cinema: array[1..350] of Boolean;
To access a variable in the array, you have to specify the name of the array followed by the variable's index. The variable's index must be enclosed in brackets. The syntax for accessing a single element of the array is:
ArrayName[Index]
If you are using the Cinema array and you need to change the state of seat 265 to vacant, you can write this:
Cinema[265] := False;
The index of an element doesn't need to be a constant value. It can be a variable or a function result. The important thing is that the index value has to be inside the declared range.
Listing 6-1: Array element access
const MAX_SEATS = 350; var Cinema: array[1..MAX_SEATS] of Boolean; userSeat: Integer; begin ReadLn(userSeat); if (userSeat >= 1) and (userSeat <= MAX_SEATS) then Cinema[userSeat] := True; end.
The best thing about arrays is that you can access array elements in a loop, which can easily solve a lot of problems. In this case, you can easily set all seats to vacant if the cinema is empty or calculate the percentage of seats occupied. If you take a closer look at the GetPercent function, you will notice that arrays support the new for-in loop.
Listing 6-2: Accessing array elements in a loop
program Project1; {$APPTYPE CONSOLE} uses SysUtils; const MAX_SEATS = 350; var Cinema: array[1..MAX_SEATS] of Boolean; i: Integer; procedure ClearCinema; var i: Integer; begin for i := 1 to MAX_SEATS do Cinema[i] := False; end; function GetPercent: Double; var seat: Boolean; occupiedCnt: Integer; begin occupiedCnt := 0; for seat in Cinema do if seat then Inc(occupiedCnt); Result := occupiedCnt * 100 / MAX_SEATS; end; begin { 67 seats occupied } for i := 1 to 67 do Cinema[i] := True; WriteLn(GetPercent:0:2, '% seats occupied.'); ReadLn; end.
Arrays in Delphi are much more flexible than arrays in other programming languages. For instance, array indexes in C++, C#, and VB.NET are always zero based. In Delphi, you can use index values that best suit your needs:
negativeArray: array[-100..100] of Integer; bigIndex: array[1000..1020] of Integer; alphabet: array[Ord('a')..Ord('z')] of Char
Since arrays in Delphi can have different indexes, it can be difficult to remember the starting and ending indexes of every array you use in an application. Delphi has two functions that get the first and last index of an array: Low and High. If you're not using the for-in loop, the best way to read index values is by using the High and Low functions.
Listing 6-3: Low and High functions
program Project1; {$APPTYPE CONSOLE} uses SysUtils; var alphabet: array[Ord('a')..Ord('z')] of Char; i: Integer; begin for i := Low(alphabet) to High(alphabet) do begin alphabet[i] := Chr(i); WriteLn(alphabet[i]); end; ReadLn; end.
The Low and High functions remove the burden of remembering the exact indexes of an array, are easy to read, and don't slow down the application because the functions aren't called at run time. The compiler replaces the calls to both functions with appropriate values from the array declaration at compile time.
The syntax for array constant declaration is:
const ArrayName: array[Range] of DataType = (value_1, value_2, value_n);
There are times when array constants can drastically reduce the amount of code. One such situation is when using a function that returns the name of the month. The function for returning the month name is pretty long when implemented without an array constant.
function GetMonthName(AMonth: Integer): string; begin case AMonth of 1: Result := 'January'; 2: Result := 'February'; 3: Result := 'March'; 4: Result := 'April'; 5: Result := 'May'; 6: Result := 'June'; 7: Result := 'July'; 8: Result := 'August'; 9: Result := 'September'; 10: Result := 'October'; 11: Result := 'November'; 12: Result := 'December'; else Result := ''; end; end;
A much better solution to this problem is to declare a local array constant. The function that uses the array constant is much smaller and looks more professional.
Listing 6-4: Using array constants
function GetMonthNameEx(AMonthName: Integer): string; const MONTH: array[1..12] of string = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'); begin if (AMonthName < Low(MONTH)) or (AMonthName > High(MONTH)) then Result := '' else Result := MONTH[AMonthName]; end;
The GetMonthNameEx function returns an empty string if the AMonth parameter is out of valid range. You should always check user values to make sure they are valid. If you blindly accept user values, even simple functions like this one can cause an error and produce invalid results. In this case, if the user passes the number 13 as the AMonth parameter, the function returns an empty string.
WriteLn(GetMonthNameEx(13));
If you remove the range check, the application will try to read a value that is located outside of the array. If that memory location is empty, the call will probably execute without problems, but if the memory location isn't empty, the call will cause an error.
Array constants also give us the ability to use the case statement with string values. Although we can't use string values directly in the case statement, we can get the index of the value in the array and use that index in the case statement.
Listing 6-5: Using strings with the case statement
program Project1; {$APPTYPE CONSOLE} uses SysUtils; function MonthIndex(const AMonthName: string): Integer; const MONTH: array[1..12] of string = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'); var i: Integer; begin Result := -1; for i := Low(MONTH) to High(MONTH) do begin if AMonthName = MONTH[i] then begin Result := i; Exit; end; // if AMonthName end; // for i end; var userEntry: string; begin Write('Enter a month: '); ReadLn(userEntry); if MonthIndex(userEntry) = -1 then WriteLn('Invalid month name') else begin case MonthIndex(userEntry) of 4: WriteLn('Easter'); 12: WriteLn('Christmas'); end; // case MonthIndex end; ReadLn; end.
The MonthIndex function returns –1 if the AMonthName parameter isn't a valid month name and the proper index if AMonthName is a valid month name. You should remember this value because a large majority of functions (not only in Delphi) return –1 if something is wrong.
Another thing to note in the MonthIndex function is the use of the Exit procedure. The Exit procedure is used when you want to terminate the procedure or function and immediately return control to the caller. In this case, the use of the Exit procedure ensures optimal execution. If the user passes "January" as the AMonthName parameter, the Exit procedure terminates the loop after the single iteration. You can omit the call to the Exit procedure, but the function will be slower because it will needlessly iterate 11 more times and give the same result.