Previous chapter | Index | Next chapter |
Although a program is simply a number of instructions contained in program lines, in many ways it is more than the sum of its parts. One of the major factors affecting the success of a program is the way that you organise the instructions in it to form the program. The organisation of the program (its structure) is the topic of this chapter.
Very short programs are usually easy to understand and follow; as long as you know what the individual instructions do, you can figure out what the whole program does - line 10 gets a value, line 20 multiplies it by 3, line 30 prints the result and so on. As soon as a program gets beyond about ten lines long or starts to use complex instructions, this is no longer the case. A program which is difficult to understand is difficult to write and doubly difficult to change or correct!
The way to make a long or complex program easier to understand is to break it down into smaller
chunks and label each chunk with what it does. To break a program into chunks, decide what
separate stages the task consists of and write instructions to complete each stage in turn,
but between each stage and the next, enter a program line consisting of just '
on its own. To indicate what a particular stage in the program does, use REM. This is a
strange keyword - it simply tells BASIC to ignore the rest of the line! This allows you to type
comments after the REM, usually to explain what is going on.
You will see both methods being used throughout the example programs in this introduction to BASIC. To give you a taste, contrast the following programs:
10 title$ = "Markup program" 20 markup = 0.5 30 PRINT title$:PRINT 40 INPUT "Product price";price 50 INPUT "Product name";name$ 60 markup.price = price*(1+markup) 70 vat = 0.15 80 INPUT "How many";number 90 value = number*markup.price*(1+vat) 100 PRINT "Your stock of ";name$; " should cost ";value 10 REM ** set up variables ** 20 ' 30 vat = 0.15 40 markup = 0.50 50 title$ = "Markup program" 60 ' 70 REM ** set up screen ** 80 ' 90 PRINT title$: PRINT 100 ' 110 REM ** get input ** 120 ' 130 INPUT "Product price";price 140 INPUT "Product name";name$ 150 INPUT "How many";number 160 ' 170 REM ** calculate value including vat and markup 180 ' 190 markup.price = price*(1+markup) 200 value = number*markup.price*(1+vat) 210 ' 220 REM ** display value ** 230 ' 240 PRINT "Your stock of ";name$; " should cost ";valueAlthough the second, neatly structured, program is somewhat longer, it is far easier to understand and check. For example, have you assigned the value to
vat
before
using it? In the first version, you have to hunt for an answer; in the second, all constants
are assigned to variables in one place, making it easy to check.
If you are going to write programs of any size or complexity, you should try to write them like the second version. Changing a program like the first will be a real nightmare!
The programs you have written so far have all been executed as a list of instructions, starting at the lowest line number and ending after executing the highest. There are however many ways to change the order in which program lines are executed.
The first of these is the GOTO command, which is followed by a line number: the target line. When this instruction is executed, BASIC continues execution from the target line specified rather than the next line in number sequence.
This may seem a little silly at first glance, but try the effect of adding the following line to the example program named "MARKUP". Load the program which you saved earlier by typing:
LOAD "markup"
Now add the new line by typing:
40 GOTO 10
The complete program is now:
10 INPUT "Type the item cost price, then press RETURN ", cost 20 profit=20/100 30 PRINT "The price to charge is ";cost+cost*profit 40 GOTO 10
Now run the program. See how much easier it is to use the revised program: if you want to calculate the price to charge for a number of items, instead of having to run the program once for each item, it is ready for you to type the next cost price as soon as it has displayed the charge price for the previous item.
This revised version of the program using GOTO is all very well, but now the program never stops! The only way to get out of the program is to press +C, which immediately stops the BASIC program.
While this approach is quite acceptable in a simple program like this, it does not help you to write programs in which you will want to leave the repeated section and carry on executing another part of the program. A far better solution is described below.
Repeating the same sequence of instructions over and over again like this is called 'looping', with the repeated instructions called a 'loop'. Looping is used so frequently that BASIC provides commands to define and execute loops.
The first type of loop starts with the command FOR and ends with the command NEXT. The simplest form of this loop is as follows:
FOR counter=start TO finish instructions NEXT
When it executes the FOR instruction, BASIC first checks whether start is less than or equal to finish. If it is not, BASIC skips the whole loop. If it is, BASIC sets the numeric variable counter to the numeric value start, then obeys the following instructions until it reaches the NEXT command. BASIC then adds 1 to the counter variable and checks whether its new value is greater than the numeric value of finish. If not, the instructions inside the loop are executed again, counter is increased by 1, then tested against finish again. Only when counter is greater than finish does BASIC finish the loop and continue with the next instruction after the NEXT command.
This type of loop is usually called a 'FOR...NEXT loop', for obvious reasons.
The FOR...NEXT loop is most useful when the number of times the loop should be executed is known at the start of the loop. For example, to print a column of asterisks down the screen:
10 INPUT "How many asterisks";asterisks 20 FOR a=1 TO asterisks 30 PRINT "*" 40 NEXT
This type of loop is also very convenient if you want to keep a count of the number of times the instructions in the loop have been executed. For example, to program a 'times table':
10 INPUT "What number times table do you want";times% 20 FOR number%=1 TO 12 30 PRINT number%*times% 40 NEXT
See how the counter variable is used not only to control the loop, but also to generate the output.
So far, we have seen how the computer can perform calculations, store and retrieve information, even repeat sequences of commands over and over. All very impessive, but not enough to program many tasks; remember the problem with leaving the loop created using GOTO, above?
The vital missing link is the ability to make decisions. Without it, the computer can only function as a powerful calculator; with it, really versatile programs can be written which adjust their behaviour to the information they receive, even (in extreme cases) appearing to be 'intelligent'.
Decision-making in BASIC is mainly provided by the commands IF and ON. IF will be described first.
IF: IF is followed by a 'logical expression' that BASIC can test, such as 'count is grater than limit'. In its simplest form described here, if BASIC finds that the expression is true, it obeys the instructions following the keyword THEN on the line. If the expression is false, (for instance if count is 4 and limit is 6), these instructions will be skipped over and execution will continue on the next program line.
This simple version of the IF instruction takes the following form:
IF |
logical-expression |
THEN |
instructions | |
expression tested | executed if true |
Note that the whole structure of the IF command, the logical expression, THEN and the instructions that depend on it must all be on the same program line. This is one of the instances when you will often need to include several instructions on the same line (by separating them with colons).
In the example program named 'MARKUP', we could decide that to leave the loop, the user will
type a cost price of zero. The logical expression to test if cost is equal to zero is
cost=0
. To leave the loop we could use GOTO to carry on execution with a program
line after the loop (line 50). Type the following additional lines:
15 IF |
cost=0 |
THEN |
GOTO 50 | |
expression | executed if true | |||
50 PRINT "And now for the rest of the program ..." |
The modified program is now:
10 INPUT "Type the item cost price, then press RETURN ", cost 15 IF cost=0 THEN GOTO 50 20 profit=20/100 30 PRINT "The price to charge is ";cost+cost*profit 40 GOTO 10 50 PRINT "And now for the rest of the program ..."
Try running the program to check that it works correctly. Try typing positive (or even negative!) cost prices and see that the looping continues. Type a cost price of 0; this should display the message produced by line 50 and end the program.
IF can also be followed by the keyword ELSE, which allows you to specify that a group of instructions will only be executed if the expression is not true:
IF |
logical-expression |
THEN |
instructions-1 |
ELSE |
instructions-2 | |
expression tested | if true | if false |
For example, consider the following program fragment:
90... 100 balance = balance + transaction 110 IF balance<0 THEN PRINT "**Overdrawn**": PRINT -balance ELSE PRINT "In credit" : PRINT balance 120 INPUT "What next?(Q-quit,T-transfer,I-invoice)";answer$ 130...
Line 110 uses IF...THEN...ELSE to print a different message depending on the state of your bank balance.
THEN PRINT "**Overdrawn**":PRINT -balance | ||
/ | ||
IF balance<0 | ||
\ | ||
ELSE PRINT "In credit":PRINT balance |
Note that, as for IF...THEN, the whole structure of IF...THEN...ELSE must be on a single program line (though this can flow onto several screen lines).
ON: The ON keyword is followed by a numeric expression, GOTO and a list of line numbers:
ON numeric-expression GOTO line1,line2,line3,
etc.
The numeric expression is evaluated when the instruction is executed to yield a number n. If n is between 1 and the number of line numbers in the list, execution continues at the nth line number in the list. If n is less than 1 or greater than the number of line numbers in the list, execution continues with the next instruction.
ON provides a neat way to select one from a number of choices. It is equivalent to a sequence of IF instructions:
IF numeric-expression = 1 THEN GOTO line1 IF numeric-expression = 2 THEN GOTO line2 IF numeric-expression = 3 THEN GOTO line3etc.
(ON is also used with a number of other commands - described below.)
As described above, the logical-expression is usually a statement of the relationship between variables and/or constants. This will usually use one of the following 'relational operators':
a < b | true if a is less than b |
a <= b | true if a is less than or equal to b |
a = b | true if a is equal to b |
a <> b | true if a is not equal to b |
a >= b | true if a is greater than or equal to b |
a > b | true if a is greater than b |
Typical logical expressions are:
count1 < count2 name$ = "" key <> pointer%*2+1 file.absent.flag
These logical expressions are evaluated by BASIC to produce either 0 (for false) or -1 (for true). You may also use a numeric constant or variable as a logical expression, which BASIC will consider as 'true' unless it has a value of 0.
Logical expressions can also be combined and modified using logical operators. The most useful are NOT, AND and OR.
NOT is used to invert the sense of a logical expression, so if a > 6
evaluates to 'true', NOT (a > 6)
evaluates to 'false'.
OR is used to combine two logical expressions; the result is 'true' if either (or both)
are 'true'. Thus the result of (1 = 2) OR (3 = 3)
is 'true' because while the
first expression is 'false', the second is 'true'.
AND is used to combine two logical expressions; the result is 'true' only if both
are 'true'. Thus the result of (1 = 2) AND (3 = 3)
is 'false' because while the
second expression is 'true', the first is 'false'.
(The logical operators actually perform what are bitwise operations on the integer values of -1 and 0 BASIC uses to represent true and false; see Section 5.1.5.)
IF causes BASIC to execute a group of instructions once if a condition is true. Another command, WHILE, is provided to make BASIC execute a loop while a condition is true.
The WHILE loop is started by the command WHILE and ended by the command WEND. It takes the form:
WHILE logical-expression instructions WEND
When it executes the WHILE instruction, BASIC evaluates the logical-expression and if it is true, executes the instructions in the loop up to the WEND command, then evaluates the logical-expression again as if entering the loop for the first time. BASIC only skips the loop and continues with the instruction after the WEND command when the logical-expression is found to be false.
The WHILE loop is ideally suited to situations where the number of times a loop should be executed is not known when it is started. As the WHILE logical expression is tested again each time the WEND is executed, all the programmer has to do is make sure that this expression evaluates to false when the looping should stop.
Left to its own devices, a BASIC program only stops when the last program line has been executed. You can also stop it at any time by pressing +C - a sort of emergency stop!
BASIC provides two commands to let the programmer make the program stop itself under different circumstances: END and STOP
END stops the program just like it stops when it runs out of program lines. END is mainly used to stop the program when it has finished its intended tasks but there are more lines in the program.
STOP stops the program and displays the number of the program line containing the stop command. STOP is mainly used when testing a program that works incorrectly, to provide an easy way to check which parts of the program are being executed. (The command CONT can be used to make BASIC resume execution from where it stopped.)
You have already met several different ways of controlling the order in which lines are executed. The first examples above executed each program line just once, in numerical order, rather like a straight road, running from start to finish:
A 'straight line' program:
Next came programs that looped: the program still ran from start to finish, but along the way parts were repeated, rather like a road with a roundabout and a driver who couldn't decide which exit to take:
A program with looping:
Then came programs that made decisions; there was a fork in the road and the program took the left or right path depending on information it had been given or calculated when run:
A program with decision-making:
These different structures suit different types of tasks: the 'straight line' for a simple 'one-off' task; the loop for fixed repetitive tasks; the 'fork' for tasks whose details depend on the particular information received when the program is run.
In designing a program, you will usually find that a combination of these elements is called for: you should choose the type of structure best suited to the particular stage in the task.
BASIC provides one other fundamental way of organising parts of a program, which is suited to tasks that require the same or a very similar sequence of steps in several parts of the program. This structure is provided by the commands GOSUB and RETURN, and is called a 'subroutine'.
Take the following program fragments:
80 FOR A=1 TO entries% 90 PRINT name$(A) 100 NEXT 110 PRINT "***Press any key to continue***" 120 WHILE INKEY$="":WEND 130 PRINT:REM Blank line ... 200 FOR A=1 TO queries 210 PRINT equation$(A),result(A) 220 NEXT 230 PRINT "***Press any key to continue***" 240 WHILE INKEY$="":WEND 250 PRINT:REM Blank line ...
You will see that lines 110 to 130 and 230 to 250 are identical, apare from their line numbers. This duplication seems a bit wasteful of typing effort and program space, even more if it occurs more than twice in the program. A better solution is to write the duplicated program lines just once, as a subroutine. A subroutine is simply a sequence of program lines that end with the command RETURN, that can be used ('called') from anywhere in the rest of the program by using the command GOSUB followed by the line number of the first line in the subroutine.
The subroutine should be kept away from the main part of the program to make sure it will only be entered when the appropriate GOSUB instruction is executed. The best place is usually at the end of the program, after an END, as in the example below.
Using subroutines, this program fragment becomes as follows:
80 FOR A=1 TO entries% 90 PRINT name$(A) 100 NEXT 110 GOSUB 1000 ... 200 FOR A=1 TO queries 210 PRINT equation$(A),result(A) 220 NEXT 230 GOSUB 1000 ... 990 END 999 REM * subroutine to pause until any key pressed * 1000 PRINT "***Press any key to continue***" 1010 WHILE INKEY$="":WEND 1020 PRINT:REM Blank line 1030 RETURN
GOSUB is rather like the GOTO command, except that when BASIC gets to the RETURN command at the end of the subroutine, it leaves the subroutine and continues execution with the next instruction, after the GOSUB instruction that called it originally. If this is not clear, enter then run each of these two programs in turn:
10 REM example demonstrating GOTO 20 PRINT "First this, "; 30 GOTO 60 40 PRINT "This instruction never executed" 50 END 60 PRINT "then this, because of the GOTO" 10 REM example demonstrating GOSUB 20 PRINT "First this, "; 30 GOSUB 60 40 PRINT "and finally this, after the subroutine has finished" 50 END 60 PRINT "then this, from the subroutine " 70 RETURN
Subroutines can also be used when the progran lines are not exact duplicates, by intelligent use of variables and condition testing. For instance, if the messages required in the above example were different, the particular message required could be assigned to a string variable before calling the subroutine, with the subroutine printing that string variable rather than a fixed message.
Subroutines don't just help you write more compact programs. They also make it easier to correct or improve your programs, because you only need to change the subroutine, rather than the many occurrences of the duplicated code throughout the program.
GOSUB may also be used with the ON keyword, to select one from a list of subroutines, depending on the value of a numeric expression. The instruction takes the form:
ON numeric-expression GOSUB line1,line2,line3,
etc.
The numeric expression is evaluated when the instruction is executed to yield a number n. If n is between 1 and the number of line numbers in the list, execution continues at the nth subroutine in the list. If n is less than 1 or greater than the number of line numbers in the list, the whole instruction is skipped.
Subroutines can also be used to write programs that don't have a great deal of duplication, providing a good way of compartmentalising the program. Writing programs this way makes the program much easier to design, develop and understand, by allowing you to break down the task into a hierarchy of subtasks. For example, consider the following task:
Each level in the hierarchy can then be translated into a subroutine. The highest level is very simple:
10 GOSUB 1000:REM prepare 20 GOSUB 2000:REM process orders 30 GOSUB 3000:REM tidy up 40 END 50 REM ************* SUBROUTINES ********************
Each of these major subroutines can now be designed and coded. For example, the main processing subroutine could be:
2000 REM *** process orders *** 2010 INPUT "To prepare an order, type Y RETURN", answer$ 2020 WHILE answer$="Y" 2030 GOSUB 2100:REM get order 2040 GOSUB 2500:REM print order 2050 INPUT "Another? type Y RETURN for yes"; answer$ 2060 WEND 2070 RETURN
Another level down the hierarchy:
2100 REM ** get order ** 2110 GOSUB 2200:REM get name 2120 GOSUB 2300:REM get amount 2130 RETURN
And another:
2200 REM * get name * 2210 INPUT "Type the client name followed by RETURN",name$ 2220 IF name$="" THEN GOTO 2210 2230 RETURN 2300 REM * get amount * 2310 INPUT "Type the amount, followed by RETURN",amount$ 2320 IF amount$="" THEN GOTO 2310 2330 amount=VAL(amount$) 2340 IF amount <=0 THEN PRINT "The amount must be positive":GOTO 2310 2350 RETURN
And so on...
By segmenting the program in this way, you can make sure the structure of the program reflects the structure of the task, which in turn makes it easier to find the part of the program that is of interest.
It also allows you to write the program a bit at a time. Starting from the top, you write only one level down at a time, checking that each level works correctly before proceeding to the greater detail of the next level. This makes it easier to spot and correct errors, because you will always know the level which they have developed on.
The flow in a program with a subroutine can be pictured as:
Subroutines provide a convenient way of 'packaging' instructions. BASIC provides another rather similar facility for packaging expressions, called 'user-defined functions'. These are functions that can be used ('called') anywhere that a BASIC function could be, once they have been defined in the program using a DEF FN instruction.
DEF FN is used in an instruction consisting of four parts:
DEF FNfunction-name(parameter-list)= function-expression
The user function call takes the form:
FNfunction-name(list-of-values)
The function name is chosen by the programmer and can be any valid variable name. The parameter list (which must be in round brackets) consists of one or more variable names ('formal parameters'), separated by commas. When the function is used, these variables are assigned the values in the corresponding positions in the function call. Note that these variables are entirely separate from any variables of the same name used anywhere else in the program.
The function expression can be any valid expression, but will usually use the values passed to it in the formal parmaeters to calculate a result. This is passed back as a result of the user-defined function.
The 'list of values' is a number of values (constants, variables, or expressions), separated by commas. There must be the same number of these values as parameters in the corresponding function definition and they must be compatible types ie. both numeric or both string.
Typical functions and function calls (using the example screen control codes) are:
10 DEF FNvat(gross) = gross-gross/1.15 20 cost = 100 30 PRINT "The vat on a vat-inclusive item costing ";cost;"is ";FNvat(cost) ... 10 DEF FNmean(n1,n2)=(n1+n2)/2 20 highest% = 42:lowest%=3 30 average = FNmean(highest%,lowest%) 40 PRINT average ... 10 esc$ = CHR$(27) 20 REM for a VT52 computer 30 DEF FNscreen$(x,y,text$) = esc$ + "Y" + CHR$(y+32) + CHR$(x+32)+text$ 40 INPUT "Message ";message$ 50 INPUT "Column ";col% 60 INPUT "Row ";row% 70 PRINT FNscreen$(col%, row%, message$) 80 GOTO 40 ... 10 log2=LOG(2) 20 DEF FNlog2(x) = LOG(x)/log2 30 INPUT "Positive number ";number 40 PRINT "Logarithm to base to is ";FNlog2(number) 50 GOTO 30
When it is executed, the user function definition does not do anything other than define the function. It must be executed before the user function can be used. It is usual to group function definitions all in one place, either at the start of the program or in an initialisation subroutine.
User-defined functions provide a convenient but very limited alternative to subroutines when the processing required can be encapsulated into a single expression.
Previous chapter | Index | Next chapter |