TP C Programming
TP C Programming
For additional courses, please visit the Embedded Learning Center on the Freescale Semiconductor
website.
Freescale™ and the Freescale logo are trademarks of Freescale Semiconductor, Inc.
© Freescale Semiconductor, Inc. 2005
Working Programmers
Working programmers are those who program as part of their daily job. They may be
contractors who write software to fulfill a specific need for a company, or they could be
developers at a well-known (or maybe not so well-known) software development
company.
Of course, just because programming is part of the job does not mean the programmer
doesn't love or enjoy programming. However, these programmers have specific tasks in
mind, which are often driven by hard deadlines, structure, and goals.
Identity
The example above shows one situation and the accompanying solution. However, the
entire computer industry is based around the principle and practice of problem solving.
Solving problems requires that you first identify the problem (too much time and
manpower for inventory). Once you've found the problem, you need to analyze the
problem to find out where things can be simplified or automated. Finally, you write code
to solve the problem.
Problem solving is a repetitive process that we'll discuss further in the next session.
Document
Documentation is actually a twofold process. A good programmer needs to document the
code he or she writes so that it can be understood many years later when you or someone
else tries to modify the software. I strongly believe in documenting your code. This
usually means using your language's comment features to describe what is happening in a
particular section of code or describe the use of a variable or the like.
The second part of documenting comes near the end. This is the step where you (and/or a
technical writer) create the manual on how to use the program you've just written. The
technical writer needs to work closely with the programmer (more commonly called the
developer) to ensure that all aspects of the program are well documented and can be
easily understood by users.
Algo What?
If you have never heard the term before, you may get tongue-tied just trying to say
algorithm (pronounced Algo-rith-im).
Simply put, an algorithm is an outline of the steps your program needs to take in order to
solve a specific problem. You can think of an algorithm as a recipe, but instead of
cooking you are writing source code. Algorithms are written in pseudocode, a mix of
English sentence structure with code notation. Pseudocode is completely independent of
any programming language.
For example, let's say you need to create a payroll accounting program. Part of that
program would be calculating the gross salary of each employee in the company, taking
into account overtime (any work above and beyond 40 hours a week), as well as regular
work hours. The pseudocode for that program might look like:
If Hours greater than 40
Set Gross Pay to Rate times 40 plus 1.5 times the
hours above 40.
Else
(%TAB%)Set gross pay to Rate times Hours
Using nothing more than this simple algorithm for calculating an employee's gross pay,
we can quickly come up with the source code to handle that part of the problem. Now
that you have the basic algorithm, you can replace some statements with their
mathematical equivalents, giving you:
If Hours > 40
Set Gross Pay to Rate X 40 + 1.5 X Rate X (Hours-
40).
Else
(%TAB%)Set gross pay to Rate X Hours
Algorithms should never be used to solve a large problem, but they can be used to show
where a problem breaks down into smaller bits; different algorithms can be applied to
solve those bits. In the example above, computing gross salary is only one small portion
of the overall problem of payroll accounting. There's no mention of tax deductions,
employee benefits, and so on. These tasks can be computed elsewhere using different
algorithms.
Canned Algorithms
One of the benefits of many years of computing history is that people have already solved
many problems and written the algorithms down to share with colleagues and friends.
Programmers face the same problems today and the same algorithms work just as well
now as they did when computers first appeared.
There are a lot of good books on algorithms, particularly if you have access to a college
or university bookstore. You can think of these books as recipe books; they make great
reference material. Don't think that you have to memorize all the algorithms you
encounter. While there may be a few "favorites" you will be able to recite on the spot,
you generally look up what you need when you need it.
Plethora of Algorithms
Just as there are many recipes for carrot cake, each slightly different from the next, you
will find many algorithms that solve the same problem in different ways. Sorting
algorithms are a good example. There are many different sorting algorithms: quick sort,
selection sort, and bubble sort, to name a few. Each of these algorithms approaches the
same problem -- sorting data -- in different ways.
To choose a sorting algorithm (something you'll do in the final project in this course) you
need to look at the amount of data to be sorted. While all of the sort algorithms will work
regardless of the quantity of data that you're sorting, some are faster than others on large
amounts of data. Conversely, some are better at sorting smaller amounts of data (e.g.,
quick sort).
Efficiency
There exists a whole area of computer science devoted to the study of algorithm
efficiency. Efficiency is beyond the scope of this course. However, if you pursue your
programming skills further, you can delve into the wonderful world of algorithm analysis.
Flowcharting a Program
Flowcharts are decision-based. Let's go back to our example of calculating an employee's
gross pay. What decisions are involved? The first one is, what is the pay rate? Second,
did the employee work less or more than forty hours? You can now use that information
to create a flowchart for the problem. The flowchart might look like Figure 2-1 below.
Data-Flow Diagrams
A data-flow diagram is another visual tool to help you design good software. Data-flow
diagrams, or DFDs, actually look very similar to flowcharts. The difference is that a data-
flow diagram shows what is being done, whereas a flowchart shows how it is done. A
DFD for our pay example might look like Figure 2-2, assuming a pay rate of 10 dollars
per hour.
Other Visuals
Creating good algorithms and programs does not rest solely on flowcharts and DFDs. A
good programmer will use any kind of visual to help him or her understand the problem.
You might create tables representing data changing in response to other processes. Or
you might create graphs and charts similar to those commonly found in mathematics and
physics to help illustrate the full problem and understand all the data involved. If
necessary, pull out that old math or physics textbook and reread the chapter on creating
charts and graphs.
BASIC
BASIC (Beginner's All-Purpose Symbolic Instruction Code) was developed by John
Kemeny at Dartmouth College around 1967. It is a simple programming language,
designed to be easy to learn and use. Here's how our algorithm would look in BASIC:
10 REM SIMPLE PAY IN BASIC
20 REM H IS THE NUMBER OF HOURS
30 REM P IS THE GROSS PAY
40 INPUT H
50 IF H > 40 THEN 80
60 LET P = 10 * H
70 GOTO 90
80 LET P = 10 * 40 + 1.5 * (H-40)
90 PRINT "GROSS PAY IS ", P
100 END
In BASIC, the numbers at the beginning of each line are step references for the program.
The keyword REM is used as a "remark," a comment to tell you and others what is
happening and to help in understanding the program. Each programming language has
different ways of marking comments. Some versions of BASIC limit the names of
variables to a single letter. Here we use H for hours and P for pay. Also notice that the
asterisk (*) is used as a symbol for multiplication instead of the X from mathematics.
This use of the asterisk is common to most programming languages.
Pascal
Pascal, created by Niklaus Wirth in the 1970s, was based on an international language
called ALGOL 60. It was designed as a teaching language for beginning students.
Comments in Pascal are enclosed in braces.
PROGRAM SimplePay( INPUT, OUTPUT);
{ simple pay in pascal }
VAR
hours, pay: INTEGER
C
C is a programming language created at the Bell Laboratories in the early 1970s when
low-level access to the machine was considered important. Comments in C start with /*
and end with */.
/* Simple pay in C */
#include <stdio.h>
main()
{
int hours, pay;
scanf("%d", &hours);
if (hours <= 40)
pay = 10 * hours;
else
pay = 10 * 40 + 1.5 * (hours-40);
printf("gross pay is %d", pay);
}
Again, don't worry if you don't understand everything that is happening in these code
examples. The point here is to recognize the similarities between our algorithm and the
resulting code. You will start writing real code in Lesson 5.
What Is a Variable?
A variable is an "information holder" whose contents can be used or changed by the
program. In programming, a variable is usually represented by an alphanumeric "name"
like a single letter (H for hours in lesson 2) or an even longer name that is more
representative of what the variable is used for. For example, "count" would be used for
counting. If you've taken math or physics in school, you should be familiar with variables
in equations such as v = dt (velocity = distance x time).
Variables in computer programming are no different. You can also think of a variable in
programming as a box. Maybe you've moved recently. To move, you put things in boxes,
label them so you know what's in them, move them to the new location, and then spend
triple the time unpacking and arranging everything as you did packing. Programming is
similar; variables are like boxes with labels, such as the one shown in Figure 3-1.
Naming Conventions
Our box has a label of "varName." When writing code, you refer to that variable by its
label or name. Figure 3-1 also illustrates one of a few conventions for naming variables
(at least in C). A variable is generally in lower-case text. If the variable name consists of
more than one word, then the first letter of each successive word is capitalized.
With a few notable exceptions I will explain shortly, you should always use complete
words to describe variables. The payroll examples from the previous lesson showed
variables for hours and pay as both a single character (P and H) and as proper names (Pay
and Hours). It's much easier to understand what the code does when variables have
proper names.
The only time you should use single-character variables is for loops and counting, which
we cover in more detail in Lesson 9. It's not wrong to use a variable that is only a single
What Is a Constant?
Constants and variables share some similarities; nevertheless, they are used differently.
Simply put, a constant is a variable whose value cannot be changed during the course of
the program. Constants are usually given a value at the time they are declared (initially
created). Familiar examples of constants include the mathematical value pi, the speed of
light in a vacuum, the speed of sound, and the gravitational constant.
Like variables, constants have a label and can be referred to in calculations and equations
in your program. The constant name is substituted for the actual value of the constant
when doing calculations. Let's look at an example. The mathematical value for pi is
generally considered to be approximately 3.14. This value can actually have more
numbers after the decimal point, depending on how accurate you need to be in your
equation. Now say you need to figure out the area of a circle. In mathematics, the
equation is area = pi * r2, as shown in Figure 3-3.
Integer Types
Integer types are numbers that do not contain a decimal point and can have positive or
negative values. -50; 200; -1,023; 0; 10; and 256 are all examples of integers. C and Java
offer a variety of integer types. They vary in the range of values they can hold and in
whether negative numbers can be used. The common integer data types are:
• int
• short
• long
All of these data types are signed data types, meaning they can have a positive or
negative value. If you specifically need a number to only be positive, you add the
keyword "unsigned" to the data type:
• unsigned int
• unsigned short
-32,767 to -2,147,483,648
Int 32,767 to
(2 byte int) 2,147,483,647(4 byte int)
-32,768
Short to -32,768 to 32,678
32,768
- -
2,147,483,647 9,223,372,036,854,775,808
Long
to to
2,147,483,647 9,223,372,036,854,775,807
unsigned
0 to 65,535 n/a
int
unsigned
0 to 65,535 n/a
short
0
unsigned
to n/a
long
4,294,967,295
Unlike C and C++, Java does not have any "unsigned" data types: you cannot combine
data types as in C and C++. We will look at these data types in more detail in Lesson 5.
Character Type
Variables that are defined as a character type can hold letters as well as numbers and even
punctuation marks. In this case, numbers are not treated like integer and decimal values
but are treated as characters, the same as the letters A or Z. In C and Java, character types
are defined with the keyword char. Variables of the char type can only hold a single
letter or number. However, the Java implementation of the char data type is 16 bits in size
(larger box), which allows it to handle foreign (also called double-byte) characters. The C
implementation of char is only 8 bits and thus limited to handling ASCII values
(described below).
That said, char is actually an integer data type. This has to do with the way in which the
computer stores data. Computers use a numerical code to store characters in which
certain integers represent certain characters. The numerical code is called the American
Standard Code for Information Interchange code, or ASCII (pronounced "ask-key").
There are a few others, but ASCII is the most common and is what we will use
throughout this course. Using the ASCII code, the integer value 65 represents the letter
'A' (uppercase A). The standard ASCII code runs numerically from 0 to 127. When the
range of values is small enough to fit into 7-bits and the char data type has a size of 8 bits
(or 1 byte), there is enough space to handle standard ASCII code.
The first 31 characters are considered control characters, represented by the ^ symbol.
These are also called non-printing characters, as they cannot be printed out on a printer.
Some of these control characters -- ^L, for example -- are used to send commands to a
device such as a line printer or modem. ^L represents the command FF, or Form Feed,
which causes the printer to advance one page before continuing to print.
Strings
Strings are another character-based data type. You can think of a string as a series of
characters. Strings are usually longer than a single character, but do not have to be. A
string can be a single word such as "text" or a small phrase such as "type a number" or
even a combination of characters and numbers such as "#14 -- 1519 Nowhere Lane".
Some languages, such as Pascal and Java, have an official string data type. C and C++ do
not have a formal string type. In these languages, strings are represented as an array, a
series of values of the same data type (in this case, char). You will learn more about
arrays in Lesson 6.
Like numbers and characters, you can assign a string to a variable. For example:
char fullName[26] = "Joe Programmer";
The [26] after the variable name identifies fullName as an array. In this case, the array is
a character array (type char) that is a maximum of 26 characters long. A space in a string
is treated as one character (ASCII 32).
While fullName can hold a maximum of 26 characters, "Joe Programmer" is not 26
characters long. How then does the computer know where the string stops? How does it
know not to print out everything past the last character in the name? A special character,
called the null terminator, is automatically added by the compiler to the end of the string.
The null terminator is represented by \0. Strings in C are always stored with this
terminating null character. The presence of the null character means that the array must
have at least one more cell than the number of characters to be stored. For example, if
your program accepts a name that is 25 characters long, your variable must be declared as
an array of 26 characters so the last one can be used for the null terminator.
char fullName[26];
In memory, the variable looks like Figure 4-1.
True or False?
The last main data type to look at is called a logic, or Boolean, type. A Boolean type can
only be one of two values, true or false. That value is usually represented by a 1 (true) or
a 0 (false). These values are actually carryovers from the early computer days where
switches were used to turn a bit on or off. A switch in the "on" position was 1 and a
switch in the "off" position had the value of 0. Thus you can think of Boolean as being
true or false, 1 or 0, on or off.
Some languages such as Pascal and Java have a native Boolean data type. In C, however,
such a type does not exist. In fact, the Boolean data type in C is actually defined in a
series of constants:
#define BOOLEAN int /* define BOOLEAN to be the
same data type as int */
#define TRUE 1
#define FALSE 0
Here we see that Boolean is defined to be an integer type, int. So wherever you declare a
Boolean in your code, it's the same as declaring an int data type. For example, look over
the following code example.
BOOLEAN result;
int anotherResult;
result = FALSE;
anotherResult = result;
Because BOOLEAN is the same as int, we can assign them the same values. Both have a
value of 0 in the end.
Non-Zero Value
Technically, any non-zero value is the same as TRUE. A few of the modern languages
such as Java and C++ handle the Boolean data type properly.
Table 4-2: Data types are sized differently, depending on the processor and operating
system.
In early computing, the size of the data was extremely important. Memory was limited
and expensive. In the table above, take special note of the int data type. The size of an int
is dependent on the type of processor and the operating system used. This size issue
frequently causes problems when porting (or translating) a program from one type of
computer to a different one. This is also why many programmers use short and long
instead of int, because the size of the type is known and defined by a standards committee
known as the American National Standards Institute (ANSI).
Counting Systems
Computers support four primary number systems for programmers to use: decimal,
binary, hexadecimal, and octal (also called base 8). In the end, however, computers see
numbers as a series of ones and zeros, a system known as binary.
Depending on your needs, you can write code that uses any or all of these number
systems. For example, when trying to access a specific area in a computer's memory, it's
easiest to use the hexadecimal system. Similarly, when writing a lot of mathematical
code, it's easiest to use the decimal system.
The octal number system isn't used much any more and thus won't be discussed in this
lesson.
Let's take a look at each number system in more detail.
Decimal Numbers
You should already be familiar with the decimal number system. Decimal numbers
encompass all numbers from negative infinity to positive infinity. The decimal number
system includes 5, -26, 8.34, and so on. This is the number system you use most often in
calculations when writing programs.
The decimal number system is also called the base 10 system. For example, 3,421 has a 3
in the thousands place, a 4 in the hundreds place, a 2 in the tens place, and a 1 in the ones
place. This means you can think of 3,421 like this:
3 x 1000 + 4 x 100 + 2 x 10 + 1 x 1
Remember, though, that the number 1000 is really 10 cubed, 100 is 10 squared, 10 is 10
to the first power, and by convention 1 is 10 to the zero power. We can then write 3,421
like this:
3 x 103 + 4 x 102 + 2 x 101 + 1 x 100
We say that 3,421 is written in base 10 because our system of writing numbers is based
on powers of ten.
Binary Numbers
The binary number system is the lowest level system and ultimately what everything gets
converted to. The binary number system is often referred to as base 2.
Hexadecimal Numbers
Hexadecimal (referred to simply as hex) is a base 16 number system. Can you figure out
what this means? If you said, "because it uses powers of 16," you are correct. In addition,
hex uses 16 digits ranging from 0 to 15. There are no single digits to represent numbers
10 through 15, so letters A through F are used instead. Thus, the hex number A4D
(written as 0xA4D in C) converts to:
10 x 162 + 4 x 161 + 13 x 16= = 2637 in base 10
In C, you can use upper or lowercase letters for the hex values. So you can write 2637 as
0xa4d.
Decimal to Binary
Converting a decimal number to binary is a simple matter of continual division. Let's
look at the algorithm first.
Set N to the number to convert
While N > 0
Divide N by 2 yielding Quotient and Remainder
Output Remainder
Set N to Quotient
Reverse the order of outputs
Now let's look at a practical example. Figure 5-2 demonstrates what happens when we
apply this algorithm to a number like 13.
Decimal to Hexadecimal
To convert decimal numbers into hex, you can use the same algorithm as the binary
conversion, but instead of dividing by 2, you divide by 16. You then substitute each
output for the appropriate hex value. Let's use our earlier example and convert 2637 to
hex.
Binary to Hexadecimal
One of the easiest conversions is going from binary to hex and back again. Working in
binary is difficult as it's easy to misread ones and zeros. It is much easier to work with the
hex equivalents, which translate to binary (and back again) easily when you need to
examine a specific bit.
Each set of four binary digits corresponds to a single hex digit. The largest set of four
binary digits, 1111, or 15 in decimal, is F in hex. There are two hex digits in a byte (8
bits): the first digit represents the upper 4 bits, the last digit represents the lower 4 bits.
The following table shows decimal numbers up to 15 and their binary and hexadecimal
equivalents.
Decimal Binary Hexadecimal
Value Value Value
0 0000 0
1 0001 1
2 0010 2
3 0011 3
4 0100 4
5 0101 5
6 0110 6
7 0111 7
8 1000 8
Table 5-1: Decimal numbers up to 15 and their binary and hexadecimal equivalent
Arrays
We've already seen an example of arrays in Lesson 4 as a string data type. You can create
arrays of any data type, including any custom data type you choose. This makes arrays
one of the most flexible methods of handling large amounts of data. There are two main
types of arrays, linear and tabular.
Linear Arrays
Linear arrays consist of one data type written in sequential fashion. Strings are a good
example of a linear array. The sequence of characters continues "along a line" and stops
at the end. You can think of this type of array as having one dimension.
Linear arrays are declared as follows:
dataType variableName[number];
dataType can be any of the data types we've looked at up to this point, or any custom data
type you create. (More on this later). The name of the variable and number representing
the size of the array is the variableName. So, if you needed an array to hold 100 numbers,
you would declare it as follows:
int numArray[100];
The 100 is called the subscript. In C, arrays are 0-based which means that the array
actually starts at position 0 instead of position 1. In our example above, we would have a
range of numArray[0] to numArrray[99].
Tabular Arrays
Tabular arrays have more than one subscript, but can still only hold one kind of data.
Tabular arrays are most commonly called multi-dimensional arrays. The easiest to
visualize is a two-dimensional array, which looks like a table (thus the name). Consider
the following example:
Boolean hotDays[7][52];
In this example, meetings is the name of an array that allows you to keep track of how
many hot days you had in the year by storing "true" or "false" in the appropriate location.
The 7 is the number of rows (days) and the 52 is the number of columns (weeks).
Conceptually, the array looks like this:
hotDays hotDays hotDays hotDays
[0][0] [0][1] [0][2] [0][3]
And so on. By changing the first subscript, you move from day to day. Changing the
second subscript moves you from week to week. So, for your average daily temperature
for the second day of the 8th week of the year, you would perform the following
assignment:
meetings[1][7] = true;
Remember that arrays are 0-based in C, so the second day is 1 instead of 2 and the 8th
week is 7 instead of 8.
More Dimensions
You are not limited to two-dimensional arrays. You can have as many dimensions as you
require. You are limited by memory space, however (remember the size of your data
types). To create a three-dimensional array, the above example can be further changed to
include months of the year as follows:
Boolean hotDays [7][5][12];
You can think of a thee-dimensional array as having width, height, and depth. While it's
more difficult to visualize an array with more than 3 dimensions, you can definitely have
as many dimensions as you need.
You may have noticed that the above example isn't a real world situation as days and
weeks in a month vary by month and year. The array shown here has too many places in
it. However, you could still use this as a base array and then write code to handle a
specific situation. Additionally, you may change the data type to a numerical type such as
short and store the actual average temperature. You could then store an unreasonable
value in one array position (-999, for example) that would signal your code to switch
months.
Array Limitations
Arrays do have limitations in their use. When you declare an array, the program allocates
exactly enough space for the number of items of the type declared. If you need to store
more data than you initially declared in your array, you have to change the program
accordingly. You can't just add another place in the array while the program is running.
As mentioned previously, you can still only store one type of data in an array. You cannot
create an array that can store both numbers and characters, for example, at least not
without creating a custom data type. (More on that below.)
Arrays also take up a large amount of memory. When you declare an array to hold 100 int
values for example, all the memory needed to store those int values is allocated
immediately in memory, even though you are not using them yet. That equates to 100 *
Array Initialization
Like other variables, you can initialize an array to specific values on creation instead of
filling the array with values later on. To initialize an array when it's declared, you use the
braces ({ and }) that contain the values for each array position separated by a comma.
This is similar to set notation in mathematics. For example:
short anArray[3] = {12, 5, 150};
is the same as
short anArray[3];
anArray[0] = 12;
anArray[1] = 5;
anArray[2] = 150;
Initializing an array like this allows for a little shorthand in code and is great for setting
up tabular data that will be referenced in another part of your program.
Initializing arrays is not limited to single-dimensional arrays. To initialize a multi-
dimensional array, you do the same task but with additional sets, also separated by
commas. Consider the following example:
short junk[2][4] = {
{2, 4, 6, 7},
{3, 8, 10, 6}
};
Here, junk is an array consisting of two rows of four values each. You define a "set" for
each row and then enclose that set in braces to mark the beginning and ending of the data.
Notice that the last set did not include a comma (,) after it.
Structures
One of the easiest methods of creating custom data types, particularly for more complex
data, is to use structures (called records in Pascal). Not all programming languages have
the capability to create structures, but most modern ones do.
Simply put, a structure consists of one or more elements of data (variables) wrapped in a
single container variable for easy access and retrieval. Each data element in the structure
can be a different data type. You can think of structures as a mini-database. Let's look at a
practical example.
Using typedef
While we have created this new data type to handle our addresses, creating variables that
use the new data type is a little cumbersome. You must declare variables as follows:
struct address someVar;
It seems like it would be simpler if you could avoid the extra typing by using something
like:
ADDRESS addressList;
And indeed it would. Enter typedef. C's typedef function allows you to name your data
types. Using addresses as an example, you'd do the following:
typedef struct address
{
char name[40];
char street[100];
char apt[5];
char city[25];
char state[5];
char zip[9];
char country[3];
} address;
Pointer Variables
Probably one of the most powerful features of most modern programming languages is
the use of pointers. Pointers are a very powerful feature and allow you to perform tasks
using less code than would otherwise be required. Not only that, but pointers allow you to
create new custom types, some of which you'll see shortly.
Simply put, a pointer "points" to data. Specifically, pointers are memory locations that
store addresses. Whenever you see the word "pointer" your mind should immediately say,
"store the address of." For example, if you see:
aPtr is a long pointer."
Your mind says, Aha! What is really meant is that . . .
aPtr stores the address of a long."
One thing to remember is that pointers are not a data type. You can retrieve the value of
pointer variable holds in a manner similar to retrieving information from other data types,
but pointers themselves are not a data type. The main differences between a data type and
a pointer are their purposes. The purpose of a variable with a char data type is to store a
character (or string). The purpose of a character pointer is to store the address of the
pointer value as well as to point to the data type.
The basic format for declaring pointers is to place an asterisk (*) in front of the variable
name. For example:
char(%TAB%)*charPtr;
int(%TAB%)*intPtr;
When assigning a value to a pointer, the golden rule is that you only assign addresses.
This matches nicely with the fact that pointers store addresses. To assign an address to a
pointer variable, you use the assignment operator (=) with the & character.
Pointer Uses
While pointers look cool, you may be wondering, "Why use them? What is their
purpose?" You can perform some operations using pointers that are more difficult using
conventional means. For example, you can use pointers to step through an array you may
not know the exact size of. Also, you can design some complex data types that would not
be possible without the use of pointers.
One classification of custom data types is called Abstract Data Types. These data types
are designed to mimic real-world objects or concepts. For example, a long line of people
waiting to get into a movie is called a queue and it operates on the principle of first come,
first served. A pile of dishes at the local cafeteria is called a stack. There are many others.
One such popular one is called a linked list. A linked list consists of a structure of one or
more data types, plus a pointer to a similar structure, and it looks like this:
struct aList
{
Using Functions
I'll describe functions first, as it will be easier to understand how procedures are written
in C, C++, and Java if you know how functions are written.
Functions are most commonly used when you need to know the result of an operation or
series of calculations before the program continues. Some languages, notably Pascal, use
the keyword "function" to define a function. C, C++, and Java do not have this feature.
Instead, everything in these languages is written as a function. In C, functions are written
as follows:
ReturnType FunctionName(parameters)
ReturnType is the data type the function returns. This could be a native data type such as
long or char, or it can be a custom data type like our address structure from the previous
lesson.
FunctionName is the name you give the function. Common practice is to name functions
in all lowercase with the first letter of each word in uppercase. Function names should be
named according to the task they are to perform. For example:
IsFinished();
GotAddress();
FinalDeductions();
The parameters are a list of one or more variables a function needs to complete the task.
Giving a function a parameter list is called "passing parameters" and helps eliminate the
use of global variables (variables that can be accessed from any point in the program).
We'll cover parameters in more detail a little later in this lesson.
Often, functions are used for calculations or Boolean (true or false) statements. You can
then use functions in a test condition as follows:
if (ItIsTimeToLeave())
...
In this example, we're making the call to the function directly in the test, so we are
relying on the result of the function to continue. If the function returns true we do one
thing, and if false we do another. The function is called and the result returned before the
"if" statement completes.
Additionally, we can assign the result of a function to a variable as follows:
float result;
result = CalculateTaxes();
Using Procedures
Procedures are called when you want to perform a task or series of tasks that doesn't
require the result to be known before the program continues. Some languages, notably
Pascal, use the keyword "procedure" to define a procedure. C, C++, and Java do not have
this distinction. In C, procedures are written as follows:
ReturnType ProcedureName(parameters)
At first glance, you may ask, "but isn't that a function?" Yes it is. Remember, every
routine you write in C is a function. The difference between the two becomes clearer
when we use a real example.
void AddDatabaseItem( itemType theItem )
The use of the keyword "void" as the return type tells us that this routine doesn't return a
value, therefore it's considered to be a procedure, not a function.
Procedures are not directly used in conditional expressions such as the if statement from
the previous section. Generally speaking, procedures are called linearly like this:
procedure1();
procedure2();
...
And also after the result of a condition is met (more on that in the next lesson).
Let's look at a real example of a procedure:
Using Parameters
An important part of good program design is using parameters to give a routine data to
work on instead of using a global variable. You should try to minimize the use of global
variables as much as possible. You can use parameters with procedures and functions. A
procedure or function that does not use parameters is said to be "void" of parameters. In
C, the keyword "void" is used when defining the routine, but not used when calling the
routine. For example, remember the CalculateTaxes routine from above:
float CalculateTaxes( void )
{
float taxes;
...
return taxes;
}
There is nothing wrong with this routine; it's perfectly functional. However, this routine
would need to rely on reading information that can be used when calculating taxes either
from global variables, or from a data file, or from some other means. To make the routine
a little more efficient, it's best to pass in the data the function needs as parameters, like
this:
float CalculateTaxes( float income, float taxRange )
{
float taxes;
...
return taxes;
}
Then, you would call the function like this:
float grossIncome, taxPercentage;
...
Conditional Statements
You are probably already aware of some the conditional statements you make when
talking or making decisions. You've seen some examples of conditions in the previous
lessons using an "if statement" to perform a test. However, the "if" is not the end of the
condition. There are two main types of conditional statements used in C, C++, and Java.
The first is the "if . . . then . . . else" condition. The second is the "switch . . . case"
condition. Let's look at each one in more detail.
If . . . then . . . else
The most common conditional statement used in programming is called the if . . . then . . .
else statement. It looks like this:
if (some condition is met) then
do something
else
do something else
The first line is the test condition. If the condition inside the parentheses evaluates as
TRUE, then the line below is executed. The "else" part of the statement handles the case
if the test condition is false. There can be any number of lines for the respective "do
something" lines in our pseudocode. However, general practice is that if there are more
than two or three lines, that code is put into a separate routine and called explicitly on its
own (a process known as branching).
Some languages, such as Pascal, use "then" as a keyword and part of the statement. Other
languages such as C, C++, and Java assume "then," thus you never see the word in this
type of conditional statement. Let's use the example of calculating overtime pay from an
earlier lesson. If Tom, an employee, worked over 40 hours in one week, his salary is 1.5
times the regular salary of $10 per hour for each hour over 40 he worked and $10 for
every hour up to 40 hours. That statement written in C might look like this:
#define kNormalPay 10
#define kNormalWeek 40
float overtimePay;
...
if (hours > 40)
overtimePay = (hours - kNormalWeek) * 1.5;
"Case" Statements
The other main condition statement is the "case" statement. "Case" statements are most
often used when there are more than two possible answers for a condition, and each
answer requires a separate action. The "case" statement is used in conjunction with a
"switch" statement to tell the compiler what variable you want to test. Let's look at an
example.
What Is Branching?
There are a couple of different techniques that are commonly referred to as branching.
Strictly speaking, branching is using a test to decide between alternative sequences.
Those sequences may be calling a different procedure or function, or setting variables to
different values (like in the examples you've seen above). In fact, you've seen examples
of branching all through this course. Let's look again at the flowchart for payroll from
Lesson 2.
What Is a Loop?
There's an old joke about a programmer who couldn't get out of the shower. The reason?
He took the instructions on the shampoo bottle literally. They read, "lather, rinse, repeat."
The programmer effectively got stuck in an "infinite loop." He read each line, performing
the action described, and then hit repeat -- at which point he began the whole process
again. This joke illustrates the concept of simple looping.
A loop is created when you repeat the same set of actions more than once in a single
section of code. To exit a loop, you require a test condition that evaluates to false. The
test condition is usually part of the loop structure and can be either at the beginning or the
end of the loop. A loop with the test at the beginning is called an entry-condition loop. A
loop with the test at the end is called an exit-condition loop. Some programmers consider
an entry-condition loop superior to the exit-condition variety because it's easier to test
before you loop. In some cases, you may need to skip the loop entirely if the test
condition fails, which cannot be done with an exit-condition loop.
There are many kinds of loops. You'll find that some types of loops are common between
programming languages, although they may have a slightly different structure. Other
loops may be unique to your programming language of choice. If a particular loop is not
covered here, you should research the kinds of loops your programming language offers
and how to use them.
In C, there are three fundamental kinds of loops. They are the "for" loop, while . . . do,
and do . . . while. Let's look at each one in more detail.
"For" Loop
The "for" loop is an incremental loop, meaning that the loop is performed a fixed number
of times incrementally. All the parameters (conditions) for the loop are given at the
beginning. The parameters are starting value, ending value, and increment value. The
parameters for the "for" loop perform the following three tasks:
• Initialize the counter variable
• Compare the counter with the stopping value
• Increment the counter each time the loop iterates
Let's look at a practical example. Suppose you want to print the numbers 1 through 10 on
the screen. You could write ten lines of code, or you can write just a few lines of code
like this:
int main(void)
{
short count;
for (count = 1; count <= 10; count++)
printf("%d\n", count);
}
The first part of the loop sets the variable "count" to 1. The semicolons separate each
section of the loop. The next section is an expression of the test condition. If this
expression becomes false, we exit the loop. In this case, we continue looping as long as
our count variable is less than or equal to 10. Finally, count++ is C shorthand for saying
count = count + 1. This last section incrementally increases the variable count by 1.
Lastly, the "printf" statement writes out the value of the variable "count" on a new line
for each iteration of the loop.
With all this in mind, what would happen if the test condition was "count < 10" instead of
"count <= 10"? The program would write out "count" until 10 but would not write 10 on
the last loop because "count = 10" and the expression would be false.
Like "if" and "case" statements, you can write a loop that requires more than one line of
code to execute. Consider the following pseudo example:
short count;
for (count = 0; count <= 500; count+=2)
{
DoSomething();
DoSomethingElse();
BringItOn();
}
Whenever you use multiple statements in a loop, you need to group them together using
braces as shown above. There are many varied opinions on placement of the braces and
on whether or not using braces for even a single line in the "for" loop makes the loop
more readable or not. After you read other people's source code for a while, you'll
develop your own opinion and adopt a style you feel comfortable using.
One of the most common uses of a "for" loop is to step through an array variable to read
or write information to that array position. Consider the following example:
Boolean vote[50]; /* remember, Boolean is really an
int */
"While" Loop
"While" loops are another form of loop common in many modern programming
languages. Some languages call "while" loops "while-do" loops because they have the
following structure:
while (something is true)
do something
The test condition for a "while" loop is at the beginning, similar to the "for" loop. Thus, if
the test condition fails or evaluates to false, then the loop is skipped. Let's change our
"for" loop from above to a "while" loop.
Boolean vote[50]; /* remember, Boolean is really an
int */
/* code to fill vote array not shown */
short count, numVotes;
Do . . . While Loop
The "do . . . while" loop is an exit condition loop. This means that the loop is executed at
least one time regardless if the test condition fails or not. This quality gives a "do . . .
while" loop certain advantages and disadvantages. On the disadvantage side, if the test
condition is met before going into the loop, the loop still executes at least one time until it
tests at the end of the loop. On the advantage side, if you know for sure that you need to
perform a series of actions at least one time (regardless of any prior condition being met),
and then may need to execute that same series of actions again after that, then a "do . . .
while" loop fits the bill quite nicely.
Let's use our vote counting example once again:
Boolean vote[50]; /* remember, Boolean is really an
int */
/* code to fill vote array not shown */
Problem Description
The problem you will solve over the next three lessons is keeping track of a bookstore
inventory. The program you write does not need to be elaborate; in fact, we'll keep the
program requirements manageable to ensure simplicity. The bookstore in question is a
secondhand bookstore.
Database
Once we are sure that the ISBN for the book entered is correct, we can allow the user to
add other important data to the store's database. The program must allow the following
actions to be performed on the database:
• Add new book
• Delete a book
• Show all books in the database
For our purposes, we don't really want anything that is overly complex. We'll keep the
database small and simple to use and access. More information on the database will be
provided in the next lesson.
You can find out more information about the ISBN system and how it works from the
International ISBN Agency Web site at:
http://www.isbn.spk-berlin.de
Let's look at an example that has an error in it. We'll use both methods for demonstration
purposes.
Group Check
Publisher Prefix Title ID
ID Digit
ISBN 5 7 6 2 6 1 1 6 3 2
Weight 10 9 8 7 6 5 4 3 2 1
50 +49 +48 +14 +36 +5 +4 +15 +6 +2
Checksum: 229 / 11 = 20 with a remainder
Total 229
of 9. So the ISBN is not valid.
Design Hints
You do not need to read this section if you already have ideas on how to accomplish the
above goals. If you are unsure, or just need a little pointer to head you off in the right
direction, read on.
The easiest way to accomplish all the goals listed above is to use a combination array and
structure for the book inventory. Since we're dealing with a finite amount of information,
an array will work perfectly. Using an array makes it easy to step through the inventory
to print it and easy to add items if you keep track of the last array position. It can also be
easily changed later on without significantly affecting the rest of the program.
The ISBN should be entered as an array with one digit of the ISBN occupying one
position in the array. The size of this array then would be 10 (0 to 9). One way is to
accomplish this is to try and separate out each number as it is entered and place the value
into the array. You need to test for errors during input and convert the X to a 10 for the
check digit where appropriate. For a further hint on how you might do this, look over
Chapter 7 in your book, especially pages 211-215 and 232-236.
For convenience purposes, and because it's easier to code, use global variables for the
book structure, the book array, the ISBN array, and an index variable into the book array
so you know how many books you have just by looking at that variable. As a bonus, once
you've validated an ISBN number, store it as a string variable in the book information
structure. This is easier to do than it sounds. Remember: arrays and strings are essentially
equal except for a null terminator "\0" at the end of a string, which means your string
variable will need to be one character larger than your ISBN array.
Program Documentation
Let's talk a bit about documentation. Documenting a program occurs on two levels, the
source level and the user level. User-level documentation is the written manual or
document that you give to the people who will use your program. A dedicated technical
Bad Comments
Like many other good things, comments can be abused or overused when there is no
need. Consider the following:
short count; /* a count variable /*
...
test = 0; /* set test to 0 */
Debugging Methods
There are many different methods programmers use to "debug" their code. Debugging is
the process of going through your program to find errors and problems, and then
eliminating them. Let's look at a couple different methods.
Strategic Statements
One of the easiest techniques to use in debugging a program is the use of strategically
placed printf statements. Using a printf call to print where you are in the source code, or
to print the value of a variable at a given point in time, allows you to actively track the
progress of your program.
For example, let's say I write a routine that calls a particular function. However, the
results from that function are either erroneous or the program "locks up" and doesn't
return from the function call. I can place a printf statement just before I call the function,
and somewhere else in the function to know my program actually got to a certain point.
void main( void )
{
long num = 0;
/* some other code may go here */
printf("Calling function CalculateSomething()\n");
num = CalculateSomething();
printf("The value of num after
CalculateSomething() is %d",num);
}
In this example, if you don't see the text from the second printf call on the screen, you
know something messed up during the call to CalculateSomething() and you need to dig
further.
You can do other strategic things with printf as well. For example, you can print the value
of parameters before calling a routine and print what the values are once you arrive in
that routine.
Some languages and operating systems allow other types of strategic statements. For
example, calling the operating system to make a beep sound or pause the program to wait
for input from the user. It depends on your choice of languages and the OS-specific
Hard-Copy Debugging
Hard-copy debugging is the second most common form of debugging. In fact, the process
of hard-copy debugging is what led to the source-level debugger that we'll discuss in the
next section.
Simply put, hard-copy debugging involves printing out the source code to your program
and, using a pencil and maybe some extra paper, tracing through your program as though
you were the computer. You look at each line of code carefully to try to figure out what it
is doing. If there are any mathematical calculations involved, you perform them and write
down the result. If a variable changes value, you write down the new value. When you
step through a loop, you write down the result of each pass through the loop so you can
track what you think is happening and compare it to what may really be happening.
Obviously, hard-copy debugging can be a time-consuming process. However, it's not
always necessary to print out the source for an entire program. Often, all you need to
print is just a few key files (in the case of a multi-file project) or routines and just trace
through those.
While debugging source code this way can be extremely tedious, it can also be very
rewarding. You learn a little more about your program by thinking like the computer.
You can often find mistakes much easier this way, especially mistakes in logic. You may
also want to ask someone to help you with the process. Often a fresh perspective on your
code finds bugs much quicker. I don't know how many times I've looked at a line of
source code trying to find the error and someone (often my wife) looks at it and says, "I
think you forgot a comma here." Needless to say, she's usually right.
Source-Level Debuggers
Now that we've covered a few of the traditional debugging techniques, let's talk about
how changes in compilers and the introduction of Integrated Development Environments
(IDEs) make your life that much easier. Since this course uses the Metrowerks
CodeWarrior IDE as the basis, I'll focus on it. However, this discussion can apply to any
IDE and source level debugging tool or IDE.
Integrated Development Environments are so called because they combine a compiler,
editor, project management tool, and debugger into a single package. The project window
in CodeWarrior, for example, allows you to see all the files and libraries associated with
your project and even keep them in groups.