The CBasic clinic; session four. John A. Libertine.
In our last session, you were introduced to one of the most powerful attributes of CBasic, its file handling procedures. The programs were, of course, very elementary; but they contain many of the elements needed for the most complex applications.
Before we start on new material, let's go back and fill in a few holes. It would be helpful if you had a printout of the two programs from Part 3. We might start by breaking files down into their parts. I will use an analogy which is far from original but does get the idea across. Think of the entire File as a file cabinet with several drawers. Each drawer represents a Record. For example, if the whole cabinet consisted of customer records, we could call the file Customer fil. Each drawer (record) might be for a single customer. Now, look in one drawer. It will be filled with file folders. Each folder would be a Field. If you can remember this simple analogy, you should be able to understand the jargon. A file contains one or many records. Each record contains one or many fields. Simple, isn't it?
Here is a more specific breakdown. Let's go along with calling the file Customer.fil. Note that if follows the CP/M convention for a file name: up to eight characters. If the extension is used, the period is mandatory.
Okay, our file name is Customer.fil. It will contain several records, each a specific customer.
Records do not have names. Think of a record as a full line on your screen containing several
different variables. For example:
VARIABLE$, VARIABLE%, VARIABLE is a single record containing three variables (each called a field). The first is a string, the second an integer, and the third a real number variable. The length of a record can be as short as one variable or as long as necessary. The commas in the line above act as separators (delimeters is the computer term) between each field. The end of a record is indicated by a carriage return plus a linefeed. In other words, the end of a line.
The fields within each record are individual variables each of which can consists of up to 31 characters. For example, a couple of fields might be: Customer.address.one$ or ABC.Mfg.Co.Address$. Note that in these cases, we are using a string variable (the $ at the end indicates that). Some fields might be integer or number variables like Part.Number% or Total.sales.
Or we might show this more graphically as in Table 1.
You may think we have spent a great deal of time on what appears to be a simple, basic fact; but the truth is that this nomenclature is used over and over in most of the literature and documentation you will read. It is important to understand the concept. Variables In A File
Now, a word about variables in a file. Look back at the two programs we used in Part 3. You will see that in both programs we used the variables ADD1$, ADD2$, and ADD3$ for three address lines (three fields). The files do not store the variable names. They simply store a string, an integer, or a real number in the order you designate. This means that you do not have to use the same variable name to access the fields.
For example, you could re-write the PRINTER.BAS program and change the above three variables to ADDRESS1$ or LINE1$ or whatever. It is only important that you have the same type of variable (string, integer, or real) and that you have the same number of variables in each record. In the case of the PRINTER.BAS program, you must read out four string fields followed by three real number fields. Specifically, you could change the line numbered 20 to read:
20 READ #1; LINE1$, LINE2$, LINES3$, LINES4$, NUM1, NUM2, NUM3
Of course, if you do this, you also have to use the same variable names for printing out the field. For example, in the line following line 20, you would have to change PRINT NAME$ to read PRINT LINE1$. Furthermore, it is not necessary to have two separate programs to use this convention. Once a file has been written to and then closed, you can re-open it and read it with entirely different variable names.
Why go through this rigamorole? One reason might be documentation.
You write the file opening routine with long variable names which make the program easier to understand and simpler to debug. Later, when you want to print it out, you could use simple, short variables to save time and space. Or as in the case of our last session, you might use one program to access the files written with another. It might make more sense to use different variables for clarity. In any case, you should now understand that CBasic writes types of variables to a file rather than the exact variable names you give it.
We learned last session that the type of files we have used so far are sequential or stream files. This means you can read a file only in the exact order in which it was written. Next session, we will go into random access files, which allow you to go directly to any given record in the file. However, almost everything we have discussed about sequential files will be applicable to random access files as well.
Again, look at the BOWLING.BAS program. You will recall we created a file with the FILE statement. This opens an existing file or creates one if it does not exist. The other way to create a file is with (oddly enough) the CREATE statement.
Remember, if you use CREATE, it unquestioningly creates a new file even if an old one exists with the same name. It erases the old file. The correct way to open an existing file is either with FILE or, more commonly, with the OPEN statement. The second program from Part 3 (PRINTER.BAS) uses this method just above line 20.
There is one more difference between FILE and CREATE that you must watch for. You can create a new file with CREATE using either the literal file name (enclosed in quotes) or a variable:
CREATE "FILENAME . EXT" AS 1 or
CREATE FILES$ AS 1
Assuming you have previously defined FILE$ as a file name, either of these will work fine. Not so with the FILE statement. This must use a variable only as: FILE FILE$. Notice that AS 1 is not needed when you use the FILE statement. The lowest un-used file number (between 1 and 20) is automatically assigned to the file.
If you think this is so obvious that I am belaboring a point, take a look at the bottom of page
129 in the CBasic User Guide. Even the experts can goof. They show an example of the FILE
statement which uses a literal file name instead of a variable. If you copied this example, you would get a compiler error. File handling is a little tricky. Learn the rules right--the first time. It will save you untold headaches in the future.
If you wrote several different records to our BOWLING.FIL last session, you may have noticed an odd printout of the Three String Average. Unless by chance the average was a whole number, you might have gotten an average like: 189.73542973. This is an awkward percentage at best.
Wouldn't it be nice if you could specify that you want the percentage rounded off to the second decimal place? There is just such a way to control the printout so you can specify exactly how your numbers will be rounded off. You can also use the same method to make your output come out in dollars and cents formats. This can be done with the PRINT USING statement. PRINT
Let's take a look at a regular PRINT statement and then a PRINT USING statement:
PRINT "The average is:"; AVERAGE
PRINT "The average is:";
PRING USING "###.##"; AVERAGE (Note the semicolon at the end of the first line above. It will cause the next program line to print out on the same line.) Another variation would be: PRINT USING "The average is: ###.##";AVERAGE The final variation we will discuss would be:
PRINT USING FORMAT$; AVERAGE In this last example FORMAT$ will have been previously defined as: LET FORMAT$ = "The average is: ###.##"
If you use WordStar, you have already seen how a # character can be used to represent a number (in the case of WordStar, a page number). The same basic idea is used in CBasic but on a much more sophisticated and expanded level. The ###.## in our example above is a map or matrix or format which the computer will fill in with actual numbers. If this case, we are saying to the computer: Print out a three-digit number then a period, then round off to two decimal places. Be assured this is a true round off, not a truncation. For example, 123.3345 will round off to 123.33, and 123,33567 will round off to 123.34.
Even if the number has fewer than three digits, there is no problem. The computer will print out a space in place of the first (or second) digit. If the number turns out to be a fractional decimal, it will print a leading zero then a period then two places. Examples: 123.456789 will printout as 123.46; 12.3 will printout as 12.30, and .345678 will printout as 0.35. PRINT USING
The PRINT USING statement has many variations including use with strings. For now, we will just stick with the simpler numeric forms. You have already seen how to use it with regular numbers to round off decimal places. Now we are going to use it to produce dollars and cents formatted output. Look at this line:
PRINT "Total sales for the quarter were:";
PRINT USING "$$###,###.##"; TOTAL.SALES or
PRINT "Total sales for the quarter wer:";
PRINT USING FORMAT$; TOTAL.SALES
(Assuming FORMATS = "$$###,###.##")
This is very similar to our first example. Here we say the largest figure expected is 999,999.99. But notice the two dollars signs at the beginning of the format string? This is a convention which says: Float a single dollar sign just to the left of the first printed digit. Thus the dollar sign will print correctly for $999,999.99 as well as for $1,234.56 or $12.00.
The commas indicate you want comma formatting. Actually, you can put the correct number of commas (in this case two) anywhere in the format string ("$$,,######.##" or "$$###,,###.##") and they will automatically be positioned, but that leads only to confusion, and most good programmers place the commas in the position in which they are to print.
Study the sections on PRINT USING in your documentation or in CBasic User Guide for more details, but this will give you a fairly good start.
There is one further advantage to PRINT USING. The printouts can be automatically aligned on the decimal point. CBasic does this by adding spaces at the left of each formatted printout. If we represent a space with the lowercase, s, you can see the actual printout in the left column and the way your computer sees it in the right:
There is a slight catch to aligning on decimal points. The key is to be use the first printed character or space starts in the same column position for each line. If the numbers are the very first thing to print in a line, it is easy because each number starts at column 1 (or 0 on some machines). Most times, however, that will not be the case. We need a way to line up wherever you want to start on a line. Enter the TAB function. TAB
The structure of a TAB function is quite simple: TAB (number or variable). For example: TAB(23) or TAB (N%). Here 23 or N% means column 23 or column N%. It works very much the way the tab key on your typewriter works and will tab on either your screen or printer (whichever is active for printout).
Do watch for one problem. If you already have something printed out on a line, the tab position must be beyond this point or you will get a carriage return plus a linefeed, and your printout will be on the next line. Example: If you print out a string which ends at column 50, the tab must go to at least column 51. If in this case you tab to column 25, it will print at column 25 of the next line down.
Following are some typical examples of the use of the TAB function:
PRINT TAB(35);"* The asterisk marks column 35"
PRINT "The average for 3 strings is:" ;TAB(40);
PRINT TAB(10);NUMBER1%;TAB(20);NUMBER2%; TAB(30);NUMBER3% LPRINTER
Before we go into this month's program, let me ask if you have done your homework. Did you look into the LPRINTER and CONSOLE statements? Bottom line: When you start a CBasic program, it assumes (defaults) the CONSOLE statement is active. All printouts appear on your screen. If you then type LPRINTER (it must be the only statement on a line, except a REM can follow it), everying from then on will print on your printer instead. To stop printer output and go back to the screen, type CONSOLE (again as the only statement on the line) and back you go to screen printout. This back and forth switching is called a toggle--on and off. There is one important exception to all this. An INPUT statement will always print out on the screen, even if LPRINTER is active.
Well, we have reached the end of another session. Now you have your work cut out for you. Take a look at the program listing for this month. See if it makes logical sense from just reading it. Most of the material we have discussed in this session is included, so you can actually see it in action. Type it out on your word processor, store it (call it SESSION4.BAS), compile it (CBAS2 SESSION4), correct errors and compile if necessary, and run it (CRUN2 SESSION4). Be sure your printer is ready, since most printouts are directed to it.
Incidentally, the programs we have used in previous sessions were very short, and I am assuming that you have enough disk spac to hold a word processing or text editing program plus the CBasic programs (CBAS2.COM and CRUN2.COM) plus the .BAS program plus the .INT program. Until now, these last two shouldn't have occupied more than 2K to 4K. However, when you get into longer programs, you will want to put them on another disk. Depending upon your computer and its disk configuration, this listing will occupy from 5K to 9K for the .BAS program plus 3K or 4K for the .INT.
To use a second disk, just add the disk prefix (usually B:) when you create the program (B:SESSION4.BAS). Then do the same when you compile and run the program (i.e., CBAS2 B:SESSION4 or CRUN2 B:SESSION4). This also applies to any files you may include within a program. For example, in this month's program, you would change the file name to B:EXAMPLES.FIL.