The text
type represents a text file:
var f: text;
To write to a file, first call the assign
function to
assign a filename to a file, then call rewrite
to open
the file for writing. You can then write to the file using the
familiar write
and writeln
functions. When
you are done writing, call close
to close the file.
Here's a program that writes the numbers from 1 to 10 to a file:
var f: text; i: integer; begin assign(f, 'numbers.txt'); rewrite(f); for i := 1 to 10 do writeln(f, i); close(f); end.
Be warned that rewrite
will truncate a file, i.e.
erase all its existing contents, if it already exists!
Reading a file is similar, but you call reset
to open
a file for reading. This program adds a series of numbers read from a
file:
var f: text; i: integer; sum: integer = 0; begin assign(f, 'numbers.txt'); reset(f); while not seekEof(f) do begin readln(f, i); sum := sum + i; end; writeln(sum); close(f); end.
All of these file-related functions are listed in the library quick reference.
The for…in
statement loops over successive values
in an array:
var a: array[1..100] of integer; v: integer; sum: integer = 0; … for v in a do sum := sum + v;
The preceding for
loop is equivalent to
for i := low(a) to high(i) do sum := sum + a[i];
We have seen that Pascal lets us declare an array of any type: an
array of integer
, an array of boolean
, and
so on. We can even declare an array of arrays, otherwise known as a
multidimensional array.
Like a single-dimensional array, a multidimensional array may be either static or dynamic. You can declare a static multidimensional array like this:
a: array[1..10] of array[1..20] of integer;
or
a: array[1..10, 1..20] of integer;
The preceding two definitions are equivalent; you can use whichever syntax you like better.
The array we have just declared is a 2-dimensional table of dimensions 10 x 20, holding a total of 200 integer values.
You can access a particular array element using the syntax a[4][5]
or a[4, 5]
. These are completely equivalent and you can
use whichever syntax you prefer.
Given the array a
declared above, length(a)
returns 10. That's because a
actually is an array with
10 elements, each of which happens to be a 20-element subarray.
length(a[1])
returns 20. (The number 1 is arbitrary
here; you could just as easily say length(a[3])
, for
example.)
We will have many uses for multidimensional arrays. A multidimensional array can be used to store a matrix, for example, or a raster image (with each pixel in a single element).
Here's how to declare a dynamic multidimensional array:
var d: array of array of integer;
To set the dimensions of such an array, call setLength
and pass each dimension as a separate parameter:
setLength(d, 100, 20);
Here is a program that reads an integer N from standard input, followed by an N x N matrix of integers. The program writes a message indicating whether the matrix is symmetric. (Recall that an N x N matrix A is symmetric iff Aij = Aji for all 1 ≤ i, j ≤ N.)
var n, i, j: integer; a: array of array of integer; begin readln(n); setlength(a, n, n); for i := 0 to n - 1 do for j := 0 to n - 1 do read(a[i, j]); for i := 1 to n - 1 do for j := 0 to i - 1 do if a[i, j] <> a[j, i] then begin writeln('asymmetric'); exit; end; writeln('symmetric'); end.
Note carefully the form of the second double loop above. We iterate only over values (i, j) below the diagonal of the matrix:
for i := 1 to n - 1 do for j := 0 to i - 1 do
If we had instead written
for i := 0 to n - 1 do for j := 0 to n - 1 do
then our program would still work correctly, but would be less
efficient because it would check redundant pairs. For example, when i
= 3 and j = 7 it would test whether a[3, 7] <> a[7, 3]
.
Then when i = 7 and j = 3 it would test whether a[7, 3] <>
a[3, 7]
, which is redundant.
We have already learned how to call functions and procedures in Pascal's standard library. We will now learn how to write our own functions and procedures. This will allow us to write larger and more interesting programs than before.
Recall that in Pascal a function returns a value but a procedure does not.
Here is a procedure that draws a rectangle of asterisks with a given height and width:
procedure rectangle(height, width: integer); var i: integer; begin for i := 1 to height do writeln(stringOfChar('*', width)); end;
Here, height
and width
are parameters
to the procedure. i
is a local variable.
The first line containing the procedure's name and parameters is its declaration. Actually we could have written this line in either of two ways:
procedure rectangle(height, width: integer);
or
procedure rectangle(height: integer; width: integer);
These forms are equivalent.
We can call the rectangle
procedure from our main
begin/end block like this:
begin rectangle(5, 8); end.
The values 5 and 8 are the arguments that are passed to the procedure. The program's output is
******** ******** ******** ******** ********
Recall that is N is a positive integer, then N! ("N factorial") is defined as
N! = N · (N – 1) · … · 3 · 2 · 1
Here is a function that computes the factorial of a given number N:
function factorial(n: integer): integer; var i: integer; f: integer = 1; begin for i := 2 to n do f := f * i; exit(f); end;
The return type of this function is integer
, and
is written after the colon (':') in the function's declaration.
Every function must return a value. In Pascal there are two ways
to accomplish this. You may either use the exit
statement as in the function above, or you can assign to the
function's name:
factorial := f;
The exit
statement immediately exits the function, even
if you are in the middle of a loop. (Assigning to the function's name
does not immediately exit.)
In this course we will generally use exit
for
returning function values.
Consider this program:
var x: integer; function abc(a: integer): integer; var i: integer; begin i := a + x; exit(i); end; var y: integer; begin x := 3; y := 4; writeln(abc(y)); end.
The variable x
declared at the top of the program is a
global variable. It is visible everywhere in the
program. The funcction abc
and the main begin/end block
can both read and modify x
.
The variable i
is local to the function abc
.
Code outside abc
cannot see this variable.
The variable y
is local to the main begin/end block.
It is not visible inside the function abc.
In general you should avoid global variables when possible. Global variables make a program harder to understand, since it may not be obvious which functions in a program might change them. (Global constants, however, are fine.)
Consider this program:
procedure increment(i: integer); begin i := i + 1; writeln(i); end; var j: integer = 7; begin increment(j); writeln(j); end.
The program will print
8 7
Here is why. First the procedure increment
receives the
value i = 7. The statement i := i + 1
sets i
to 8, and increment
writes this value.
Now control returns to the main begin/end block. The value of j is still 7! That's because by default Pascal passes arguments by value: a function or procedure receives a local copy of the value that was passed. A function may modify its local copy, but that does not change the corresponding value in the caller, i.e. the code that called the function. And so the second number printed by this program is 7.
To change this behavior, you can
write var
before any parameter in a function
or procedure declaration. Let's modify the first line of the program
above to be
procedure increment(var i: integer);
The var
keyword indicates that an argument is to be
passed by reference. In this case, the procedure does not
receive a local copy – instead, it receives a reference to
the caller's value. In this example, i is a reference to the value of
j. If the procedure changes i, then j will also change. And so the
program will print
8 8
Let's now write a procedure swap
that exchanges the
values of two variables:
procedure swap(var i, j: integer); var t: integer; begin t := i; i := j; j := t; end;
We can call this procedure in our main begin/end block as follows:
var x: integer = 4; y: integer = 6; begin swap(x, y); writeln(x, ' ', y); end.
The program will print
6 4
Notice that in the declaration of swap
we must use the
var
keyword. Without it, the procedure would not be able
to modify the variables in the caller.
Functions are an essential tool for writing larger programs in a way that is easy to understand. Here are three general rules to follow for writing good code with the help of functions:
Don't repeat yourself. Beginning programmers often write code that looks like this:
if x > 0 then begin ... 50 lines of code ... end else begin ... 50 very similar lines of code ... end
This is bad code. It is hard to read: the differences between the 50-line blocks may be important, but hard to spot. And it is hard to maintain. Every time you change one of the parallel blocks of code, you must change the other. That is a bother, and it's also easy to forget to do that.
In this situation you should factor out the 50 lines of code into a separate function. If there are differences between the 50-line blocks, you can have function parameter(s) that allow the function to behave in either fashion.
Every function, including the main begin/end block, should fit on a single screen. Practically speaking this means that functions should generally be limited to about 50 lines. Functions that are much longer than this quickly become hard to understand.
Make variables as local as possible. In other words, avoid global variables in Pascal. (In many programming languages you can declare variables inside a loop body or other block of code inside a function, which is generally a good practice. But unfortunately Pascal does not allow this.)
Consider the following program:
var a: array[1..10] of integer; b: array[1..10] of integer; i: integer; begin for i := 1 to 10 do a[i] := i * i; b := a; a[1] := 77; writeln(b[1]); end.
In this program, a
and b
are static arrays
of integers. The assignment b := a
copies the
entire array a
into b
. The following
assignment a[1] := 77
changes the value of a[1]
but has no effect on b
, which is a separate array. The
program prints
1
Here is a similar-looking program:
var a: array of integer; b: array of integer; i: integer; begin setLength(a, 10); for i := 0 to 9 do a[i] := i * i; b := a; a[0] := 77; writeln(b[0]); end.
In this program, a
and b
are variables of
dynamic array type. Assignment between these variables has a
different behavior than in the static case above. The assignment b
:= a
does not copy the array a. Instead, it modifies the
variable b
to point to the same array as a
.
So after the assignment a[0] := 77 the value of b[0] is also 77,
since it refers to the same element of the same array. The program
prints 77.
We will not so often use array assignment, but it is good to be aware of this behavior difference. (When we study pointers and linked data structures in a few weeks, we'll see that pointer assignment works similarly to dynamic array assignment as described here.)
Here is a program that passes a dynamic array to a procedure:
procedure abc(a: array of integer); begin a[0] := a[1] + a[2]; end; var b: array of integer; i: integer; begin setLength(b, 10); for i := 0 to 9 do b[i] := i; abc(b); writeln(b[0]); end.
Because we did not specify the var
keyword, the array is
passed by value. So the procedure receives a local copy of the
array. The assignment to a[0] inside the procedure changes the local
copy, but the caller's array b is unaffected. The program prints 0.
If we change the first line to
procedure abc(var a: array of integer);
now the array will be passed by reference.
Then the parameter a
will refer to the same array as b
,
and the assignment to a[0] will affect b[0]. The program will print
3.
(The situation would be similar if we were passing a static array. But passing static arrays to functions involves some syntactic surprises, which we will discuss in the next lecture.)
Sorting is a fundamental task in computer science. Sorting an array means rearranging its elements so that they are in order from left to right.
We will study a variety of sorting algorithms in this class. Most of these algorithms will work on sequences of any ordered data type: integer, real, string and so on.
Our first sorting algorithm is bubble sort. Bubble sort is not terribly efficient, but it is simple to write. It can be a reasonable choice if you are only sorting a small number of values.
Bubble sort works by making a number of passes over the input. On each pass, it compares pairs of elements: first elements 1 and 2, then elements 2 and 3, and so on. After each comparison, it swaps the elements if they are out of order.
For example, consider bubble sorting this array:
On the first pass, the algorithm first compares 6 and 5, and swaps them because 5 < 6:
Now the algorithm compares 6 and 3, and swaps them because 3 < 6:
And so on. At the end of the first pass, the array looks like this:
Notice that the largest element (8) has moved to the last position. If input array has N elements, then the first pass of a bubble sort makes N – 1 comparisons, and always brings the largest element into the last position. So the second pass does not need to go as far: it makes only N – 2 comparisons, and brings the second-largest element into the second-to-last position. And so on. After N – 1 passes, the sort is complete and the array is in order.
Here is an animation of bubble sort in action on the above array.
Here is Pascal code implementing a bubble sort:
procedure swap(var a, b: integer); var t: integer; begin t := a; a := b; b := t; end; procedure bubbleSort(var a: array of integer); var i, j: integer; begin for i := high(a) - 1 downto 0 do for j := 0 to i do if a[j] > a[j + 1] then swap(a[j], a[j + 1]); end;