Lesson 6: Subroutines and Functions
To program efficiently you need the ability to reuse code. This not only saves RAM but time coding as well. Furthermore the ability to save these reused snippets of code in a library allows the programmer to use them in multiple projects. This is where subroutines and functions come into play allowing you to create code that can be reused as many times as you wish.
A subroutine is a chunk of code within a larger program that does something specific and can be called upon from anywhere in the source code. A function is identical to a subroutine except for one important difference; a function returns a value after it has been called upon. Once called upon functions and subroutines perform the code contained within them and then return back to the point in the source code where they were called upon. Functions and subroutines can be thought of as mini-programs that can be accessed by jumping out of the main code, performing the subroutine or function, and then jumping back into the main code.
Subroutines - GOSUB and SUB Statements
Subroutines were created for the need to reuse code. In early forms of BASIC this was accomplished with the GOSUB statement. Many programmers however avoid the use of the GOSUB statement for the same reason as the GOTO statement, it creates hard to read code. However, just like the GOTO statement we need to discuss the GOSUB statement a bit because you may run across them in older source code. Type in the following program to see the GOSUB statement in action.
( This code can be found at .\tutorial\Lesson6\Gosub.bas )
'--------------------------------
'- Variable declaration section -
'--------------------------------
CONST WHITE = _RGB32(255, 255, 255) ' define color white
DIM x%, y% ' x,y coordinate pair of star to be drawn
'----------------
'- Main program -
'----------------
SCREEN _NEWIMAGE(640, 480, 32) ' initiate graphics screen
GOSUB DIRECTIONS ' print directions
DO ' begin main program loop
_LIMIT 60 ' limit to 60 frames per second
WHILE _MOUSEINPUT: WEND ' get latest mouse information
IF _MOUSEBUTTON(1) THEN GOSUB DRAWSTAR ' if left mouse button then draw a star
IF _MOUSEBUTTON(2) THEN GOSUB DIRECTIONS ' if right mouse button then clear the screen
_DISPLAY ' update the screen with changes
LOOP UNTIL _KEYDOWN(27) ' leave main loop when ESC pressed
SYSTEM ' return control to OS
'---------------------------------------------------------------------------------------------------------------
'- Clear the screen then print directions
'---------------------------------------------------------------------------------------------------------------
DIRECTIONS:
CLS ' clear the screen
PRINT ' print directions
PRINT " Left click anywhere on the screen to draw a star."
PRINT " Right click to clear the screen."
PRINT " Press the ESC key to exit the program."
RETURN ' return back to main code
'---------------------------------------------------------------------------------------------------------------
'- Draw a star at the current mouse pointer location
'---------------------------------------------------------------------------------------------------------------
DRAWSTAR:
x% = _MOUSEX ' get current x location of mouse pointer
y% = _MOUSEY ' get current y location of mouse pointer
LINE (x% - 10, y% - 3)-(x% + 10, y% - 3), WHITE ' draw a solid star
LINE -(x% - 7, y% + 7), WHITE
LINE -(x%, y% - 10), WHITE
LINE -(x% + 7, y% + 7), WHITE
LINE -(x% - 10, y% - 3), WHITE
PAINT (x%, y%), WHITE, WHITE
PAINT (x% - 5, y% - 1), WHITE, WHITE
PAINT (x% + 5, y% - 1), WHITE, WHITE
PAINT (x%, y% - 4), WHITE, WHITE
PAINT (x% - 3, y% + 3), WHITE, WHITE
PAINT (x% + 3, y% + 3), WHITE, WHITE
RETURN ' return back to main code
Figure 1: My gosh, it's full of stars!
Just like GOTO, GOSUB requires a label to jump to. Unlike GOTO however, when GOSUB encounters the RETURN statement it returns to the line of code that called the label and continues processing. This may seem a bit cleaner and produce less spaghetti code in the long run but it still makes code difficult to follow if used too much. Here is the code rewritten to use SUB statements instead. Type the following code into your IDE.
( This code can be found at .\tutorial\Lesson6\Subs.bas )
'--------------------------------
'- Variable declaration section -
'--------------------------------
CONST WHITE = _RGB32(255, 255, 255) ' define color white
'----------------
'- Main program -
'----------------
SCREEN _NEWIMAGE(640, 480, 32) ' initiate graphics screen
Directions ' print directions
DO ' begin main program loop
_LIMIT 60 ' limit to 60 frames per second
WHILE _MOUSEINPUT: WEND ' get latest mouse information
IF _MOUSEBUTTON(1) THEN DrawStar _MOUSEX, _MOUSEY ' if left button then draw star at pointer location
IF _MOUSEBUTTON(2) THEN Directions ' if right button then clear the screen
_DISPLAY ' update the screen with changes
LOOP UNTIL _KEYDOWN(27) ' leave main loop when ESC pressed
SYSTEM ' return control to OS
'---------------------------------------------------------------------------------------------------------------
SUB Directions ()
'-----------------------------------------------------------------------------------------------------------
'- Clear the screen then print directions -
'------------------------------------------
CLS ' clear the screen
PRINT ' print directions
PRINT " Left click anywhere on the screen to draw a star."
PRINT " Right click to clear the screen."
PRINT " Press the ESC key to exit the program."
END SUB
'---------------------------------------------------------------------------------------------------------------
SUB DrawStar (x%, y%)
'-----------------------------------------------------------------------------------------------------------
'- Draw a star at coordinate provided -
'- -
'- x% - x location to draw star -
'- y% - y location to draw star -
'--------------------------------------
LINE (x% - 10, y% - 3)-(x% + 10, y% - 3), WHITE ' draw a solid star
LINE -(x% - 7, y% + 7), WHITE
LINE -(x%, y% - 10), WHITE
LINE -(x% + 7, y% + 7), WHITE
LINE -(x% - 10, y% - 3), WHITE
PAINT (x%, y%), WHITE, WHITE
PAINT (x% - 5, y% - 1), WHITE, WHITE
PAINT (x% + 5, y% - 1), WHITE, WHITE
PAINT (x%, y% - 4), WHITE, WHITE
PAINT (x% - 3, y% + 3), WHITE, WHITE
PAINT (x% + 3, y% + 3), WHITE, WHITE
END SUB
The code may not look that different at first glance but there are a few key things that need pointing out. First, QB64 recognizes Directions() and DrawStar() as subroutines and color codes them accordingly in the source code, in our case green. You have basically made two new commands that can be called upon by their name anywhere in your source code. Subroutines are defined by using the SUB statement and then the END SUB statement to mark where they end.
Secondly, information can be passed into a subroutine through the use of local variables. Again, think of a subroutine as a mini-program within your program. The subroutine DrawStar() has been set up in such a way that two pieces of information must be sent along when the subroutine is called.
SUB DrawStar (x%, y%)
When you call upon DrawStar() it expects two parameters to be sent along with it. x% and y% become the local variables that hold that information. x% and y% can only be seen by the DrawStar() subroutine, meaning that they are local to that subroutine only. In line 16 of the example code you can see where _MOUSEX and _MOUSEY are passed in with the subroutine.
_MOUSEX and _MOUSEY are two QB64 commands that report the current location of the mouse pointer within the program's window. _MOUSEX returns the x location of the mouse pointer. That number is then passed on to x% in the DrawStar() subroutine. Likewise, _MOUSEY returns the y location of the mouse pointer and that number is passed on to y% in the DrawStar() subroutine. x% and y% can now be used as standard integer variables locally within the DrawStar() subroutine.
Thirdly, the QB64 IDE keeps track of subroutines and functions allowing for quick and easy navigation to and from them. Select the "View" menu and then select "SUBs..." in the drop down menu. Figure 2 below shows the screen that will appear.
Figure 2: Viewing subroutines and functions in the QB64 IDE
From this screen you can navigate quickly to any subroutine or function that is contained within your source code. This screen will become your friend when you start developing code with many subroutines and functions to manage. I personally have written games and utilities that contain well into the 100s of functions and subroutines.
Functions - FUNCTION Statement
Functions behave exactly like subroutines with a few key differences. First, functions are created with the use of the FUNCTION statement and end with the END FUNCTION statement. Second, functions are used to return a value to the code that called them. To get a better idea of this let's take the Fahrenheit to Celsius converter we wrote in lesson 2 as an example.
( This code can be found at .\tutorial\Lesson2\FtoCelsius.bas )
'--------------------------------
'- Variable Declaration Section -
'--------------------------------
DIM Fahrenheit! ' holds the user's input temperature in Fahrenheit
DIM Celsius! ' contains the formula's computation into Celsius
'----------------------------
'- Main program begins here -
'----------------------------
PRINT "Fahrenheit to Celsius Converter"
PRINT "-------------------------------"
PRINT
INPUT "Enter temperature in Fahrenheit: ", Fahrenheit!
Celsius! = 5 * (Fahrenheit! - 32) / 9
PRINT
PRINT Fahrenheit!; "degrees F ="; Celsius!; "degrees C"
That math equation in line 16 of the code is just begging to be converted to a function.
( This code can be found at .\tutorial\Lesson6\FtoCelsius_function.bas )
'--------------------------------
'- Variable Declaration Section -
'--------------------------------
DIM Fahrenheit! ' holds the user's input temperature in Fahrenheit
'----------------------------
'- Main program begins here -
'----------------------------
PRINT "Fahrenheit to Celsius Converter" ' display instructions
PRINT "-------------------------------"
PRINT
INPUT "Enter temperature in Fahrenheit: ", Fahrenheit! ' get temperature from user
PRINT Fahrenheit!; "degrees F ="; Celsius!(Fahrenheit!); "degrees C" ' convert and display results
'--------------------------------------------------------------------------------------------------
FUNCTION Celsius! (Temperature!)
'----------------------------------------------------------------------------------------------
'- Converts the temperature passed in to Celsius and returns the result -
'- -
'- Temperature! - the Fahrenheit temperature passed in -
'------------------------------------------------------------------------
Celsius! = 5 * (Temperature! - 32) / 9 ' calculate conversion and return value
END FUNCTION
The Celsius!() function was written to take in a single variable type as a parameter, Temperature!, and then return a single type value within the name of the function itself. The function Celsius!() was declared using an exclamation point ( ! ) signifying that the returned value should be of the single data type. Think of functions as super variables that can change their value based on a parameter(s) passed in.
Notice also that the only variable we needed to declare with DIM is Fahrenheit!. The variable Temperature! is only used locally within the function and is automatically declared in the FUNCTION line of code. This statement in line 11:
Celsius!(Fahrenheit!)
is passing the value contained in Fahrenheit! to the Temperature! variable contained within the function. This is known as passing by reference.
You may be thinking that the new code we created using a function is actually more complicated than the original code. Well, here's where the super variable part comes in. Type in the following code and execute it.
( This code can be found at .\tutorial\Lesson6\FtoCelsius_function2.bas )
'--------------------------------
'- Variable Declaration Section -
'--------------------------------
DIM StartTemp% ' temperature to begin conversion
DIM StopTemp% ' temperature to end conversion
DIM Temperature% ' current temperature to convert
'----------------------------
'- Main program begins here -
'----------------------------
PRINT "Convert a range of Fahrenheit temperatures to Celsius" ' display instructions
PRINT "-----------------------------------------------------"
PRINT
INPUT "Enter Fahrenheit temperature to start with > "; StartTemp% ' get first temperature
INPUT "Enter Fahrenheit temperature to finish with > "; StopTemp% ' get last temperature
PRINT
FOR Temperature% = StartTemp% TO StopTemp% ' cycle temperatures
PRINT Temperature%; "degrees F ="; INT(Celsius!(Temperature%)); "degrees C" ' convert and display
NEXT Temperature%
'--------------------------------------------------------------------------------------------------
FUNCTION Celsius! (Temperature!)
'----------------------------------------------------------------------------------------------
'- Converts the temperature passed in to Celsius and returns the result -
'- -
'- Temperature! - the Fahrenheit temperature passed in -
'------------------------------------------------------------------------
Celsius! = 5 * (Temperature! - 32) / 9 ' calculate conversion and return value
END FUNCTION
The example code above illustrates how a function can be called over and over and the result returned based on the information passed to it. Here is another sample program that uses two functions to solve for the Pythagorean Theorem. I highly encourage you to type the code in. The more code you type the more natural it will feel.
( This code can be found at .\tutorial\Lesson6\Pythagoras.bas )
'-------------------------------------------------------
'Description: Solves graphically the Pythagorean Theorem
'Author : Terry Ritchie
'Date : 11/02/13
'Update : 04/12/20
'Version : 1.1
'Rights : Open source, free to modify and distribute
'Terms : Original author's name must remain intact
'-------------------------------------------------------
'--------------------------------
'- Variable Declaration Section -
'--------------------------------
CONST WHITE = _RGB32(255, 255, 255)
CONST GRAY = _RGB32(127, 127, 127)
CONST YELLOW = _RGB32(255, 255, 0)
CONST BRIGHTRED = _RGB32(255, 0, 0)
CONST BRIGHTGREEN = _RGB32(0, 255, 0)
DIM a!, b!, c! ' the lengths of the sides of the triangle
DIM Answer! ' the answer to the problem presented by the user
DIM KeyPress$ ' any key that the user presses when asked to execute program again
'----------------------------
'- Main Program Begins Here -
'----------------------------
_TITLE "Solve Pythagorean Theorem" ' give window a title
SCREEN _NEWIMAGE(640, 480, 32) ' enter a graphics screen
DO ' begin main program loop here
CLS ' clear the screen
LOCATE 2, 9 ' position text cursor
COLOR GRAY ' set text color to gray
PRINT "This program will solve the Pythagorean Theorem "; ' inform user of program purpose
PRINT "(A"; CHR$(253); " + B"; CHR$(253); " = C"; CHR$(253); ")" ' character 253 is power of 2 symbol
COLOR WHITE ' set text color to bright white
LOCATE 9, 28 ' position text cursor
PRINT "c =" ' print text
COLOR YELLOW ' set text color to yellow
LOCATE 10, 55 ' position text cursor
PRINT "b =" ' print text
COLOR BRIGHTRED ' set text color to bright red
LOCATE 17, 37 ' position text cursor
PRINT "a =" ' print text
LINE (219, 250)-(419, 50), WHITE ' draw triangle's bright white hypotenuse
LINE -(419, 250), YELLOW ' draw triangle's yellow leg
LINE -(219, 250), BRIGHTRED ' draw trianlge's bright red leg
LINE (409, 250)-(409, 240), BRIGHTRED ' draw 90 degree indicator
LINE -(419, 240), YELLOW ' draw 90 degree indicator
COLOR GRAY ' set text color to gray
LOCATE 19, 24 ' position text cursor
PRINT "Enter the values for a, b and c." ' inform user of what to do
LOCATE 20, 24 ' position text cursor
PRINT "Enter 0 to solve for that length." ' print more information for user
a! = AskFor!(22, 27, "a", BRIGHTRED, 17, 40) ' ask user for value of side a leg
b! = AskFor!(23, 27, "b", YELLOW, 10, 58) ' ask user for value of side b leg
c! = AskFor!(24, 27, "c", WHITE, 9, 31) ' ask user for value of side c hypotenuse
Answer! = SolvePythagoras!(a!, b!, c!) ' solve for Pathagorean Theorem
IF Answer! = -1 THEN ' is the solution -1?
LOCATE 26, 16 ' yes, unsolvable, position text cursor
COLOR WHITE ' set text color to bright white
PRINT "ERROR: Triangle not possible with values provided" 'inform user that problem can't be solved
ELSEIF Answer! = 0 THEN ' is the solution 0?
LOCATE 26, 22 ' yes, unsolvable, position text cursor
COLOR WHITE ' set text color to bright white
PRINT "ERROR: Unsolvable! Too many unkowns!" ' inform user that problem can't be solved
ELSEIF a! = 0 THEN ' was side a to be solved for?
LOCATE 17, 40 ' yes, position text cursor
COLOR BRIGHTRED ' set text color to bright red
PRINT Answer! ' print value of side a
ELSEIF b! = 0 THEN ' no, was side b to be solved for?
LOCATE 10, 58 ' yes, position text cursor
COLOR YELLOW ' set text color to yellow
PRINT Answer! ' print value of side b
ELSE ' no, side c is to be solved for
LOCATE 9, 31 ' position text cursor
COLOR WHITE ' set text color to bright white
PRINT Answer! ' print value of side c
END IF
COLOR BRIGHTGREEN ' set text color to bright green
LOCATE 28, 16 ' position text cursor
PRINT "Press ESC key to exit or any key to solve again." ' give user instructions
DO ' begin keyboard loop here
KeyPress$ = INKEY$ ' save any keystroke pressed
_LIMIT 5 ' limit to 5 loops per second
LOOP UNTIL KeyPress$ <> "" ' stop loop when a key was pressed
LOOP UNTIL KeyPress$ = CHR$(27) ' stop main loop if ESC key pressed
SYSTEM ' return to Windows
'-----------------------------------
'- Function and Subroutine section -
'-----------------------------------
'--------------------------------------------------------------------------------------------------
FUNCTION AskFor! (x%, y%, L$, c~&, px%, py%)
'
'* asks for a value and returns the result *
'* updates screen with answers given *
'
'x%, y% = text coordinate to place question at
'L$ = the name of the letter to ask the value of
'c% = the color of the letter (line on triangle)
'px%, py% = text coordinate to place value at
DIM Value! ' the value the user supplies
LOCATE x%, y% ' position text cursor on screen
COLOR GRAY ' set text color to gray
PRINT "Enter the value for "; ' place text message on screen
COLOR c~& ' set text color to color passed in
PRINT L$; ' place letter on screen passed in
COLOR GRAY ' set text color back to gray
INPUT " > ", Value! ' ask user for a value
LOCATE px%, py% ' position text cursor on screen
COLOR c~& ' set text color to color passed in
IF Value! = 0 THEN PRINT " ?" ELSE PRINT Value! ' print value or ? depending on user input
AskFor! = Value! ' return to calling statement value user typed in
END FUNCTION
'--------------------------------------------------------------------------------------------------
FUNCTION SolvePythagoras! (SideA!, SideB!, SideC!)
'
'* Solves the Pythagorean Theorem given two of three triangle sides. *
'* This function will return a value of 0 if more than one side of the *
'* triangle is asked to be solved, which is impossible with Pythagoras. *
'* A value of -1 will be returned if the problem is unsolvable due to *
'* impossible number pairs given for sides a&c or b&c. *
'
'SideA! = leg length of triangle (value of 0 to solve)
'SideB! = leg length of trinagle (value of 0 to solve)
'SideC! = hypotenuse length of triangle (value of 0 to solve)
'
IF SideA! = 0 THEN ' is side a to be solved?
IF (SideB! <> 0) AND (SideC! <> 0) THEN ' yes, is side a only side asked to solve?
IF SideC! <= SideB! THEN ' are numbers possible for right triangle?
SolvePythagoras! = -1 ' no, return the error
ELSE ' no, these numbers are valid
SolvePythagoras! = SQR(SideC! ^ 2 - SideB! ^ 2) ' return solution for side a
END IF
END IF
ELSEIF SideB! = 0 THEN ' no, is side b to be solved?
IF (SideA! <> 0) AND (SideC! <> 0) THEN ' yes, is side b only side asked to solve?
IF SideC! <= SideA! THEN ' are numbers possible for a right triangle?
SolvePythagoras! = -1 ' no, return the error
ELSE ' no, these numbers are valid
SolvePythagoras! = SQR(SideC! ^ 2 - SideA! ^ 2) ' return solution for side b
END IF
END IF
ELSEIF SideC! = 0 THEN ' no, is side c to be solved?
IF (SideA! <> 0) AND (SideB! <> 0) THEN ' yes, is side c only side asked to solve?
SolvePythagoras! = SQR(SideA! ^ 2 + SideB! ^ 2) ' yes, return solution for side c
END IF
ELSE ' no, user entered 0 for more than one side!
SolvePythagoras! = 0 ' return 0 due to too many unknowns
END IF
END FUNCTION
'--------------------------------------------------------------------------------------------------
Figure 3: QB64 can do your homework!
Don't let the length of this example program scare you. Take each line one at a time and you'll see that most of the code we have already covered. Also notice the generous use of remarks throughout the code explaining exactly how the program operates. Make sure to get into the habit of remarking your code very well for both your benefit and other programmers reading your source code. Having to deal with uncommented code, even your own a few months later, is a huge waste of time.
In the function SolvePythagoras!() there is a new mathematical statement called SQR(). SQR() is a BASIC function that returns the square root of a number. There is an upcoming lesson devoted to the various mathematical functions contained in QB64.
The Pythagorean Theorem is used quite often in game programming because it offers a simple method of calculating the distance between two objects and is used as a basis for collision detection. Sides A and B of the triangle can easily be calculated from the x,y coordinates of each object. From there the hypotenuse (side C) can be calculated which is the distance between the two objects. Very handy!
Local vs Global Variables
Variables created in the main body of source code are known as Local variables. Local variables can be used anywhere in the main body of the source code. Local variables however can't be seen within subroutines and functions. Type the following example program into your IDE that highlights this behavior.
( This code can be found at .\tutorial\Lesson6\Local.bas )
'---------------------------------
'- Local variable demonstration -
'---------------------------------
DIM LocalVariable% ' a local integer variable
'---------------------
'- Main body of code -
'---------------------
LocalVariable% = 10 ' give variable a value
PRINT "In the main body of code." ' display variable's value in main body
PRINT "-------------------------"
PRINT "The value of LocalVariable% is"; LocalVariable%
PRINT
MySubroutine ' execute subroutine
SLEEP ' wait for a key press
SYSTEM ' return to operating system
'--------------------------------------------------------------------------------------------------
SUB MySubroutine ()
'----------------------------------------------------------------------------------------------
PRINT "In the subroutine MySubroutine()" ' display variable's value in subroutine
PRINT "--------------------------------"
PRINT "The value of LocalVariable% is"; localvariable%
END SUB
Figure 4: Two different values
As you can see after executing the code LocalVariable% does not have any value when inside of MySubrotuine(). Notice also how the IDE did not capitalize LocalVariable% inside of MySubroutine(). When LocalVariable% was declared on line 5 it was made local because it was declared in the main body of code and is only recognized there. Inside of the MySubroutine() subroutine LocalVariable% is not recognized as a declared variable. In fact, LocalVariable% inside of MySubroutine() is a completely different variable all together. Even though they share the same name they are in fact seen as two different variables by the IDE. Any variable used inside of MySubroutine() is considered local to that subroutine. Just like LocalVariable% can't be seen by MySubroutine(), localvariable% (in all lower case) can't be seen by the main body of code. This allows the programmer to use variables with the same name inside and outside of subroutines and functions. Handled correctly this can actually be beneficial.
Shared Global Variables
You've already seen how you can pass a value by reference to a subroutine or function but what if you need both the main body of code and the subroutine/function to see GlobalVariable%? You can use the SHARED statement to do this. Make the changes in the example code as seen below.
( This code can be found at .\tutorial\Lesson6\Global_shared.bas )
'---------------------------------
'- Global variable demonstration -
'---------------------------------
DIM SHARED GlobalVariable% ' a shared global integer variable
'---------------------
'- Main body of code -
'---------------------
GlobalVariable% = 10 ' give variable a value
PRINT "In the main body of code." ' display variable's value in main body
PRINT "-------------------------"
PRINT "The value of GlobalVariable% is"; GlobalVariable%
PRINT
MySubroutine ' execute subroutine
SLEEP ' wait for a key press
SYSTEM ' return to operating system
'--------------------------------------------------------------------------------------------------
SUB MySubroutine ()
'----------------------------------------------------------------------------------------------
PRINT "In the subroutine MySubroutine()" ' display variable's value in subroutine
PRINT "--------------------------------"
PRINT "The value of GlobalVariable% is"; GlobalVariable%
END SUB
Figure 5: The variable and its value is now shared
Did you notice that GlobalVariable% within MySubroutine() capitalized after making the change? By adding the SHARED statement to DIM in line 5 you told the IDE that GlobalVariable% can be seen everywhere including inside subroutines and functions. This time when you execute the code the values are the same.
Using SHARED in conjunction with DIM allows a variable to be seen everywhere, in ALL subroutines and functions throughout the entire program. Constant variables declared with CONST are also seen everywhere throughout the source code without the need for the SHARED statement.
There is another SHARED method that allows only the subroutines and functions you choose to share a local variable's value.
( This code can be found at .\tutorial\Lesson6\Local_shared.bas )
'---------------------------------------
'- Local shared variable demonstration -
'---------------------------------------
DIM LocalVariable% ' a local integer variable
'---------------------
'- Main body of code -
'---------------------
LocalVariable% = 10 ' give variable a value
PRINT "In the main body of code." ' display variable's value in main body
PRINT "-------------------------"
PRINT "The value of LocalVariable% is"; LocalVariable%
PRINT
MySubroutine ' execute subroutine
SLEEP ' wait for a key press
SYSTEM ' return to operating system
'--------------------------------------------------------------------------------------------------
SUB MySubroutine ()
'----------------------------------------------------------------------------------------------
SHARED LocalVariable% ' this variable is now shared locally here
PRINT "In the subroutine MySubroutine()" ' display variable's value in subroutine
PRINT "--------------------------------"
PRINT "The value of LocalVariable% is"; LocalVariable%
END SUB
Executing the program yields the same results as before. LocalVariable% is still shared within the subroutine however no other subroutines or functions within your program will be able to see LocalVariable% as a SHARED variable, only MySubroutine() can. Sharing a local variable within subroutines and functions that need them makes tracking down variable value bugs easier and streamlines source code for easier reading later on.
You may be thinking at this point, "Why not just DIM all variables as SHARED?" Well in truth with BASIC you can do just that. However, sharing all variables actually slows down execution speed of your program due to the fact that the program has to keep track of all variable values at all times. Also, in most other programming languages it is required to share variables only where needed or not even possible to share all variables all the time. Try to get into the habit of using the SHARED statement when writing code to distinguish between your program's local and global variables.
Passing Values and Unintended Results
As you've seen functions return values back to the calling code through the use of their name. It's also possible to change the value passed in by reference to a subroutine or function and have that change affect the original variable that was referenced. To see what is meant by this type the following code in and execute it.
( This code can be found at .\tutorial\Lesson6\VariablePassing.bas )
'--------------------------------
'- Local variable demonstration -
'--------------------------------
DIM SomeVariable% ' an integer variable
'---------------------
'- Main body of code -
'---------------------
SomeVariable% = 10 ' give variable a value
PRINT "In the main body of code." ' display variable's value in main body
PRINT "-------------------------"
PRINT "The value of SomeVariable% is"; SomeVariable%
PRINT
MySubroutine SomeVariable% ' pass variable into the subroutine
PRINT
PRINT "Back in the main body of code." ' display variable in main body again
PRINT "------------------------------"
PRINT "The value of SomeVariable% is"; SomeVariable%
SLEEP ' wait for a key press
SYSTEM ' return to operating system
'--------------------------------------------------------------------------------------------------
SUB MySubroutine (Value%)
'----------------------------------------------------------------------------------------------
PRINT "In the subroutine MySubroutine()" '
PRINT "--------------------------------"
PRINT "Changed Value% to 20"
Value% = 20 ' change the passed variable's value
END SUB
Figure 6: Value changed by reference
Even though SomeVariable% was never referenced in the MySubroutine() subroutine it's value was changed by changing the local variable Value% within the subroutine. When you pass a value in by reference to a subroutine or function that value gets taken on by the variable parameter name defined in the subroutine or function line.
SUB MySubroutine (Value%)
Here, Value% took on the value stored in SomeVariable% but a link between Value% and SomeVariable% still remains. If you change the value in Value% within the subroutine or function that change is linked back to the original variable which is SomeVariable% in this case. You need to be aware of this behavior when dealing with subroutines and functions and their passed values. Most programmers will tell you that you should not use this behavior as a method to change variable values because it causes an unexpected outcome. Other programmers will tell you that it keeps in the spirit of functional or procedural programming practices to use it. Either way, if you don't pay attention crazy things can happen within your code that are difficult to track down.
One other thing to note about local variables is that they do not hold their value when a subroutine or function has been exited. In the example above Value% was set to 20 in the subroutine. However, when the program exits the subroutine, all local variables are reset to zero or null in the case of string variables. If you need a local variable to hold its value between procedure calls you need to use the STATIC statement like so:
STATIC VariableName%
Instead of using DIM to declare the local variable inside of a function or subroutine you would need to use the statement STATIC instead. This directs the code to retain the values of all local variables between calls to the subroutine or function. Static local variables are a common occurrence in subroutines and functions that call themselves, usually many times over, otherwise known as recursion.
If there are values that I need to pass and I want to avoid the unexpected change the first thing I do is assign the passed values to locally declared variables like the next example program demonstrates.
( This code can be found at .\tutorial\Lesson6\VariablePassing2.bas )
'--------------------------
'- Handling passed values -
'--------------------------
DIM Number1, Number2! ' two single type numbers supplied by the user
'---------------------
'- Main body of code -
'---------------------
PRINT "Multiplies only the integer portion of the two numbers provided." ' display instructions
PRINT "----------------------------------------------------------------"
PRINT
INPUT "Number 1 > ", Number1! ' get first value
INPUT "Number 2 > ", Number2! ' get second value
PRINT
PRINT "Whole number portions multiplied together ="; Multiply%(Number1!, Number2!) ' display result
PRINT
PRINT "Original numbers left unaffected:" ' display info
PRINT Number1!; "+"; Number2!; "="; Number1! + Number2! ' add original values
SLEEP ' wait for key press
SYSTEM ' return to OS
'--------------------------------------------------------------------------------------------------
FUNCTION Multiply% (n1!, n2!)
'----------------------------------------------------------------------------------------------
' Declare local variables
DIM Num1%, Num2% ' these will hold the integer portion of the variables passed in
Num1% = INT(n1!) ' get the integer value of the first variable
Num2% = INT(n2!) ' get the integer value of the second variable
Multiply% = Num1% * Num2% ' return the calculated result
'The original values passed in were not altered
END FUNCTION
Figure 7: All looks good here
By declaring the local variables Num1% and Num2% in the Multiply%() function and then using those variables to convert n1! and n2! to integers, the original variables Number1! and Number2! never become affected. However, if this was not done the outcome would look very different as shown in the example code below.
( This code can be found at .\tutorial\Lesson6\VariablePassing3.bas )
'--------------------------
'- Handling passed values -
'--------------------------
DIM Number1, Number2! ' two single type numbers supplied by the user
'---------------------
'- Main body of code -
'---------------------
PRINT "Multiplies only the integer portion of the two numbers provided." ' display instructions
PRINT "----------------------------------------------------------------"
PRINT
INPUT "Number 1 > ", Number1! ' get first value
INPUT "Number 2 > ", Number2! ' get second value
PRINT
PRINT "Whole number portions multiplied together ="; Multiply%(Number1!, Number2!) ' display result
PRINT
PRINT "Original numbers have changed:" ' display info
PRINT Number1!; "+"; Number2!; "="; Number1! + Number2! ' add original values
SLEEP ' wait for key press
SYSTEM ' return to OS
'--------------------------------------------------------------------------------------------------
FUNCTION Multiply% (n1!, n2!)
'----------------------------------------------------------------------------------------------
n1! = INT(n1!) ' get the integer value of the first variable
n2! = INT(n2!) ' get the integer value of the second variable
Multiply% = n1! * n2! ' return the calculated result
END FUNCTION
Figure 8: Unexpected results
In this example Number1! and Number2! have become affected by what happened in the Multiply%() function. If Number1! and Number2! would be needed later on with their original values intact that would not be possible. Again, be aware of how passing values around can affect the outcomes of the variables involved in these situations.
Your Turn
Create a subroutine that draws a triangle when passed the height and base leg length of the triangle. The screen output should look like Figure 9 below.
Figure 9: A drawn triangle
- The graphics screen is 800 pixels wide by 600 pixels in height.
- A red line should be drawn vertically in the center of the triangle indicating the height provided.
- A red line should be drawn indicating the base length provided.
- Two lines in yellow should complete the overall triangle.
- Name the subroutine DrawTriangle() and pass the supplied height and leg length to it.
- Only simple addition, subtraction, and division are needed to complete this assignment.
- Save the program as Triangle.BAS when finished.
Commands and Concepts Learned
New commands introduced in this lesson:
SUB
END SUB
FUNCTION
END FUNCTION
SHARED
STATIC
New concepts introduced in this lesson:
subroutine
function
local variables
global variables
Pythagorean Theorem