(<--Previous) Back to Index (Next-->)

FB n00b: Tutorial 5

Note: This tutorial is for absolute beginners. If you aren't an absolute beginner and you dislike the tutorial, that's too bad. If you are an absolute beginner and there's something you don't understand, read through the tutorial a second time. If you STILL have difficulties, e-mail me (TheMysteriousStrangerFromMars@yahoo.com) and I'll try to help you out. I do assume you at least have a decent knowledge of how to use your computer - if not, there's not much I can do for you. By the way, you'll need FreeBasic to use this tutorial - you download that at freebasic.net, so be sure to download and install it before doing this tutorial. Now start reading!

Procedural Programming

Here I introduce to you a new paradigm of programming that is necessary to understand before we study anything more. Later you will learn of yet another paradigm, that of OOP, but right now we study the foundation of most modern programming languages: procedures.

As I said in the introduction, early forms of Basic used line numbers or line labels combined with Goto to get around. For example, this would be an infinite loop with line numbers:

  0001 Print "Hello, World!"
  0002 Goto 0001
Later, you got line labels:
    Print "Hello, World!"
    Goto loop_name
Both of these types of programming imitated assembly language, which to this day follows the same sort of methods. Basic, however, was becoming an HLL, in a world where this sort of code is looked down upon. For while it may be necessary to use line labels in assembler, they are not very easy to work with in general. You may think it's a pretty simple way to do things, and it may seem that way as you begin programming, but as you write longer and larger programs, it becomes more and more difficult to wade through a long program written this way. Think of it: you're reading through some old code you wrote, trying to remember what it did. 20 lines into the program, you see a Goto. Now what? You have to find where the Goto goes. So you scroll up and down until you find the correct label or line number, then repeat for every Goto in the program. There is nothing to tell you ahead of time what you will find at the next part of the program (for if there were, you wouldn't need to even look at it... you could just continue on your merry way). The whole thing becomes extraordinarily complicated. This situation, with control passing all over throughout the code, is graphically described as "spaghetti code," with some disgust from the more advanced programmers.

So how DO we control flow in our program? Obviously our code won't be able to run straight through all the time... we may need to go to a different part of the program sometimes. The answer is simple: procedures.

A procedure allows you to specify a block of code that you may call more than once. Each time you execute that block of code, you invoke the name of the procedure rather than typing the code itself. This is a little bit like a line label, but not very much. Here's an example:

  'Sub stands for Subprocedure
  Sub my_procedure()
    Print "Hello, World!"
  End Sub

  'The result is that "Hello, World!" is printed on the screen twice

In this very simplified example, you see that to execute the block of code in the procedure you must use the name of the procedure. This is a very simple procedure, and not very useless at all... perhaps we should write a more useful procedure, eh?

One thing you need to understand is that you can give some information to a procedure before you call it. For example, you might write a procedure that multiplies two numbers together and prints the result on the screen. Obviously you'll need to be able to tell the procedure what the two numbers are, since they could be different each time. The information you tell the procedure is called arguments, and an argument can be any kind of variable. Here's our example procedure for multiplying two numbers:

  'A fairly obvious name, eh?
  Sub multiply_two_numbers (first_number As Integer, second_number As Integer)
  Dim As Integer result
    result = first_number * second_number
    Print Str(result)
  End Sub
Notice several things here: first that the arguments to the procedure have different names inside the procedure. Also notice that you can create a special variable inside the procedure that only exists in there. It is possible for you to have a variable outside of the procedure with the name result and no problems would occur. That's because the procedure only knows about his result and outside only knows about their result. The same applies for the arguments. You may pass variables with different names, or even not variables at all - but the procedure only knows them as the names given in the argument list, first_number and second_number in this case. (Note: the name of the procedure and arguments in this case are ridiculously long. Don't name things like this in real programs!) So how do we use this procedure in the main program?
  multiply_two_numbers(10, some_variable)
  multiply_two_numbers(variable_a, variable_b)
Any of these are valid ways to use the sub - just as long as you pass integers (because the arguments are integers - you can't pass strings or floating point numbers). Also, you don't have to include the parentheses when you call a Sub. The following is also a valid way to call a Sub:
  multiply_two_numbers 3, 6
Another kind of procedure which we'll be discussing shortly, functions, do always require the parentheses, but for Subs they're optional (most people prefer to use them anyways).

One thing before we move on to functions: you may wish to set the arguments to default values. This way, if no arguments are passed your sub can still be used. For example,

Sub multiply (a As Integer = 1, b As Integer = 1)
  Print Str(a*b)
End Sub
If you call multiply without any arguments this way, your code will still be compiled since the default values will be passed. In this case, the result is that 1*1 or "1" is printed on the screen. Of course, you may also make it so only some of the arguments have default values - this way, you'll still be required to pass values to the arguments without default values. Most of the things that are possible with Subs are also possible with... Functions.

Functions are like Sub with one key difference: they return a value to the caller. This makes our multiplication function much more useful, since now it can tell another part of the program what the result of the multiplication is, instead of merely printing it on the screen:

  Function multiply (a As Integer = 1, b As Integer = 1) As Integer
    Return a*b
  End Function
Once again there are some new things to understand here. As you see, the function itself has a type - that's because it returns something. A function may return a string, or a floating point number, or any other type of variable (including UDTs, but not arrays). Thus it has to know what kind of function it is. In this case, it's an integer function. The arguments, as you see, are also integers. And we return something - that's important. There are actually several ways to return something in a function. This is because sometimes you'll have a long function, and you might want to set the return value but continue doing things. In that case you would use "Function = some_number" where some_number is a number or variable (yes, you can return the value of a variable). But if you want to leave the function immediately, you would use Return to do that. If the "End Function" occurs before either of these happen, then some default value will be returned. You may also "Exit Function" if you need to get out of the function prematurely.

So how do you use a function? Well it's pretty much the same as a Sub, except that you must always use parentheses around the arguments (unless there are no arguments - if default arguments are used, this is a possibility, or for functions that don't need any arguments). Also, you must set a variable equal to the function, like this:

  some_variable = some_function(arguments_here)
There is another way you can use a function - you can use it anywhere you would use a variable, even in a condition for a loop or conditional block. For example, the code in this block:
  If multiply(1,1) <> 1 Then
    'code here
  End If
will never run, since multiply(1,1) never returns anything but 1.

Now we have all the essential ingredients we need to rewrite our multiplication program from the first tutorial:

  Function multiply (a As Integer = 1, b As Integer = 1) As Integer
    Return a*b
  End Function

  Dim As Integer num1, num2

  Print "Type two numbers, one after the other.  Press Enter after each one."
  Input num1
  Input num2

  Print Str(multiply(num1,num2))


One interesting quirk of Subs and Functions is that they can be called anywhere - from inside other Subs or Functions, or even within themselves! If you think about calling a function from inside itself, you see that this can become a loop that never ends - actually, at some point it will crash the program, since calling a function or sub requires some information to be stored in memory about where the function was called from, so when the function is done running it can return to where it was before - only since it never ends, it never returns, so you eventually run out of memory and crash. The point is that there are useful things that can be done with this programming technique (known as recursion).

Once example of a recursive function comes from math; the most famous example is the Fibonacci sequence. The Fibonacci sequence starts with 1 and 1, and every number after that is calculated by adding the previous two numbers in the series. The function to calculate the nth member of the sequence is, obviously, recursive, since each element is calculated from the previous two. How do we figure out when to stop recursing? Easily enough: when the function is called for 1 or 2, the result is always 1, so recursion stops. Thus, the function follows:

  Function fibonacci (term As Integer) As Integer
    If term < 1 Then Return 0
    If term > 2 Then Return fibonacci(term-1) + fibonacci(term-2)
    Return 1
  End Function
See how we recurse? We only do it if the term is greater than allowed. And each recursion passes a value less than before, eventually the term will be 1 and 2 and recursion will stop entirely. The entire result will be summed, and then returned.

An important thing to remember about procedures in general is that they act as a "black box." That is, if you write a procedure carefully enough, the name of the procedure and its arguments will make it obvious what the procedure does without even reading the code. A black box is an old term that evokes the image of a box painted black (duh? No, keep reading.) It does something magnificent, and you know what it does, but you don't know (or care) how it works. Of course, in most cases there are different ways to do the same thing, though hopefully the person who writes the function chooses the most efficient (that is, fastest and least memory-consuming) way to do it. But in any case, it doesn't matter much so long as the function works. This is the fibonacci function: even if you don't understand it (though that shouldn't be hard), it still works and can be used. This is the basis of procedural programming, which is the foundation of most modern programming languages (even those that support OOP). In a procedural programming language, the programs you write will generally be composed of many procedures like this, rather than one huge chunk of code. Breaking up your code this way makes it much easier to understand, since each part of the program is clearly marked as to what it does and how to use it (if not how it works, as previously noted). To use a function, you only need to know how to call it, and not how it works. This means you may also re-use functions in other programs. The concept of using a useful function in more than one program leads to the concept of libraries, which will be covered later.

Now that you've seen functions, you might wonder if there's a way to return more than one thing from a function. Well, there are a couple of ways to do so: first, you can use UDTs. If you want to return twenty integers then you can simply return a UDT with an array of twenty integers - or if you want to return a couple of integers, some doubles, and a string, you may do that too. You can't return an array from a function, but you can return a UDT with an array in it (of course we have the limitation that the array must have a static size - all these problems will leave us when we get to pointers, which can be variable-sized and returned by a function with or without a function). But there is another way to return several values from a function - or even from a Sub!

You see, normally when you pass arguments to a Sub or Function, you don't actually give the sub the actual variable. For example, when we call our multiply sub with the variables just Inputted from the user as arguments, they are actually copied. The Sub doesn't get to use the real variables - it just gets to use the copies. This way, it can change the variables passed to it without messing up the real ones. However, in some cases you might want it to be able to change the values of them, in which case you need to pass variables by reference, meaning the location they are stored at. The way they are normally passed is called by value; strings and UDTs are passed by reference by default, but number variables are not. So if you wish to modify a number variable from within a Sub, then in the list of arguments you must declare it as ByRef instead of ByVal:

  Sub some_sub (ByRef somevar As Integer)
    somevar = something_new
  End Sub
As you see, by making it ByRef you can modify it. This allows you to return more information than one variable to the caller (whatever part of the program that calls the Sub or Function), without using UDTs or a regular Return. Of course, remember that you don't need to do this with UDTs or strings (since they are passed ByRef by default - in fact, you would need to put the ByVal in if you DON'T want them passed that way) - only to number variables.

One final way your functions and subs can modify variables from outside is if you declare the variables to be shared variables. As I said before, normally subs and functions can't change variables outside of themselves, and outside code can't change variables inside subs and functions. It is preferable to do things this way always, but occasionally you may have no other choice - or it might just be easier to use a variable that is accessible everywhere under the same name. Therefore, you would declare such variables using "Dim Shared" instead of "Dim." This allows you to use a variable with the same name inside all procedures. However, you must remember that by doing so you make it impossible for yourself to create another variable with that name inside any other function, since it becomes a global variable. If it seems confusing now, don't worry - later we will discuss namespaces, or "which variables are accessible where." This becomes even more important when we discuss OOP.

That's pretty much the majority of the things you can do to Subs and Functions. Of course, there's no limit to what you can do WITH Subs and Functions - you just have to know how to do it.

Before we close, there's one more thing we have to look at - modular programming and libraries. These are not precisely the same thing, but they are related, and they relate to procedural programming as mentioned before. Modular programming is a consequence of procedural programming: eventually you'll want to move related procedures into separate files, instead of keeping them all in one huge source file. Each of these files is called a module, and your project may have many of them. FreeBasic supports modular programming; to use another module in your main program, use this:

  #Include "somefile.bi"
Where "somefile.bi" is the name of the module (which usually ends with .bas or .bi). Remember that a module may include other modules in it, if some of the functions in it call functions from those other modules. If there is possibility that two modules will both include a third module, they should use this syntax:
  #Include Once "somefile.bi"
This way, if the other module has already been included somewhere else, it won't be included in the output twice (which could cause your program not to compile).

The concept of modules filled with common and related Subs and Functions leads to one other idea: that of the library. Libraries are like modules, except that they are usually generalized so they can be used in any program, by anyone. Some people write libraries for useful things such as math functions, graphics, or other things. In fact, sometimes if a library is useful enough someone might sell it, or at least not show the source to other people. Of course, it's always preferable to keep a library free and open source, so everyone can use it.

FreeBasic comes with two libraries you can use - the common routines, which allow you to work with strings, files, the keyboard, and many other things, and the graphics library, which allows you to use graphics, the mouse, and many other things for games. If you use Windows, there is also the Win32 API which allows you to create programs with windows instead of writing programs with grey text on a black background. There are many libraries written by users, as well - fbmath, fbsound, fbpng, and others.

Some libraries become used by so many different programs that it is better to make them dynamic. Your regular library is static, meaning that any program that uses it has the entire library code added to itself when it is compiled. If the library is large, this will use lots of memory and disk space. A dynamic library, which in Windows is known as a DLL and in linux is known as a shared library, does not have this limitation. A dynamic library is not compiled into the main program, but instead is held in a separate file. This way, if two programs use the same library, it takes as much memory or disk space as that library needs, rather than twice as much. Here's how it works: if your program uses a dynamic library then when you run it, the OS checks if the dynamic library is already being used by another program. If so, there is no need to load it into memory - and if not, it is loaded into memory. This saves memory, disk space, and time (since it only needs to be loaded from the disk once).

I won't go into how dynamic libraries are made with FreeBasic; it's much to early for you to learn about that, but if you want to study it you may look in the manual. In order to use a dynamic library, you need a header which is a kind of module that allows you to compile your program without including the actual procedures with it (since the procedures are instead included with the dynamic library). FreeBasic comes with headers for many major libraries, including Allegro, FreeImage, wx-c, and others. To use these libraries, you will still need to download the library itself (DLL or linux shared library) and you will need the manual for the library. There are also many examples.

Closing up

And so we close with the fifth tutorial in what is to be a long series. You've already learned a huge amount in just these five tutorials - but if you think you know a lot now, let me tell you that you have just begun to learn! The tutorials will continue, and you will keep learning!

We rewrote the multiplication program from tutorial 1 - your assignment now is to rewrite the basic arithmetic program from tutorial 2, using Functions to add, subtract, multiply, and divide floating point numbers entered by the user. This assignment should be relatively easy compared to the last one, so this time I will not be giving you the answer... you get to do it yourself :) Or not, if you choose.

Until next time... cheers! (The topic of the next tutorial is a secret... meaning I haven't decided what to talk about next :D)

The sixth tutorial is here

(<--Previous) Back to Index (Next-->)