How To Think Like A Computer Scientist - C Version
How To Think Like A Computer Scientist - C Version
C Version
Allen B. Downey
C-Version by Thomas Scheffler
Version 1.09
January 5th, 2018
2
2.10 Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.11 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.12 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3 Function 25
3.1 Floating-point . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2 Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3 Converting from double to int . . . . . . . . . . . . . . . . . . . 27
3.4 Math functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.5 Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.6 Adding new functions . . . . . . . . . . . . . . . . . . . . . . . . 29
3.7 Definitions and uses . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.8 Programs with multiple functions . . . . . . . . . . . . . . . . . . 31
3.9 Parameters and arguments . . . . . . . . . . . . . . . . . . . . . 32
3.10 Parameters and variables are local . . . . . . . . . . . . . . . . . 33
3.11 Functions with multiple parameters . . . . . . . . . . . . . . . . . 34
3.12 Functions with results . . . . . . . . . . . . . . . . . . . . . . . . 34
3.13 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.14 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5 Fruitful functions 49
5.1 Return values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.2 Program development . . . . . . . . . . . . . . . . . . . . . . . . 51
5.3 Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.4 Boolean values . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.5 Boolean variables . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.6 Logical operators . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.7 Bool functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.8 Returning from main() . . . . . . . . . . . . . . . . . . . . . . . 57
5.9 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
5.10 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
6 Iteration 63
6.1 Multiple assignment . . . . . . . . . . . . . . . . . . . . . . . . . 63
6.2 Iteration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.3 The while statement . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.4 Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.5 Two-dimensional tables . . . . . . . . . . . . . . . . . . . . . . . 68
6.6 Encapsulation and generalization . . . . . . . . . . . . . . . . . . 68
6.7 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
6.8 More encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . 70
6.9 Local variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
6.10 More generalization . . . . . . . . . . . . . . . . . . . . . . . . . 71
6.11 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
6.12 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
7 Arrays 77
7.1 Increment and decrement operators . . . . . . . . . . . . . . . . . 78
7.2 Accessing elements . . . . . . . . . . . . . . . . . . . . . . . . . . 78
7.3 Copying arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
7.4 for loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
iv Contents
7.7 Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7.10 Counting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
7.12 A histogram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
7.15 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
7.16 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
8.4 Length . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
8.5 Traversal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
9 Structures 103
9.1 Compound values . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
9.2 Point objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
9.3 Accessing member variables . . . . . . . . . . . . . . . . . . . . . 104
9.4 Operations on structures . . . . . . . . . . . . . . . . . . . . . . . 105
9.5 Structures as parameters . . . . . . . . . . . . . . . . . . . . . . . 105
9.6 Call by value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
9.7 Call by reference . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
9.8 Rectangles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
9.9 Structures as return types . . . . . . . . . . . . . . . . . . . . . . 109
9.10 Passing other types by reference . . . . . . . . . . . . . . . . . . 110
9.11 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
9.12 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
B ASCII-Table 117
vi Contents
Chapter 1
The goal of this book, and this class, is to teach you to think like a computer
scientist. I like the way computer scientists think because they combine some of
the best features of Mathematics, Engineering, and Natural Science. Like math-
ematicians, computer scientists use formal languages to denote ideas (specifi-
cally computations). Like engineers, they design things, assembling components
into systems and evaluating tradeoffs among alternatives. Like scientists, they
observe the behavior of complex systems, form hypotheses, and test predictions.
The single most important skill for a computer scientist is problem-solving.
By that I mean the ability to formulate problems, think creatively about solu-
tions, and express a solution clearly and accurately. As it turns out, the process
of learning to program is an excellent opportunity to practice problem-solving
skills. That’s why this chapter is called “The way of the program.”
On one level, you will be learning to program, which is a useful skill by itself.
On another level you will use programming as a means to an end. As we go
along, that end will become clearer.
But the advantages are enormous. First, it is much easier to program in a high-
level language; by “easier” I mean that the program takes less time to write, it’s
shorter and easier to read, and it’s more likely to be correct. Secondly, high-
level languages are portable, meaning that they can run on different kinds of
computers with few or no modifications. Low-level programs can only run on
one kind of computer, and have to be rewritten to run on another.
Due to these advantages, almost all programs are written in high-level languages.
Low-level languages are only used for a few special applications.
There are two ways to translate a program; interpreting or compiling. An
interpreter is a program that reads a high-level program and does what it says.
In effect, it translates the program line-by-line, alternately reading lines and
carrying out commands.
source
code interpreter
source object
code compiler code executor
The compiler ... and generates You execute the ... and the result
reads the object code. program (one way appears on
source code... or another)... the screen.
The next step is to run the program, which requires some kind of executor. The
1.2 What is a program? 3
role of the executor is to load the program (copy it from disk into memory) and
make the computer start executing the program.
Although this process may seem complicated, in most programming environ-
ments (sometimes called development environments), these steps are automated
for you. Usually you will only have to write a program and press a button or
type a single command to compile and run it. On the other hand, it is useful
to know what the steps are that are happening in the background, so that if
something goes wrong you can figure out what it is.
input: Get data from the keyboard, or a file, or some other device.
output: Display data on the screen or send data to a file or other device.
testing: Check for certain conditions and execute the appropriate sequence of
statements.
That’s pretty much all there is to it. Every program you’ve ever used, no matter
how complicated, is made up of statements that perform these operations. Thus,
one way to describe programming is the process of breaking a large, complex task
up into smaller and smaller subtasks until eventually the subtasks are simple
enough to be performed with one of these basic operations.
For example, in English, a sentence must begin with a capital letter and end
with a period. this sentence contains a syntax error. So does this one
For most readers, a few syntax errors are not a significant problem, which is
why we can read the poetry of e e cummings without spewing error messages.
To make matters worse, there are more syntax rules in C than there are in
English, and the error messages you get from the compiler are often not very
helpful. During the first few weeks of your programming career, you will prob-
ably spend a lot of time tracking down syntax errors. As you gain experience,
though, you will make fewer errors and find them faster.
C is not a safe language, such as Java, where run-time errors are rare. Program-
ming in C allows you to get very close to the actual computing hardware. Most
run-time errors C occur because the language provides no protection against
the accessing or overwriting of data in memory.
For the simple sorts of programs we will be writing for the next few weeks,
run-time errors are rare, so it might be a little while before you encounter one.
The problem is that the program you wrote is not the program you wanted to
write. The meaning of the program (its semantics) is wrong. Identifying logical
errors can be tricky, since it requires you to work backwards by looking at the
output of the program and trying to figure out what it is doing.
1.4 Formal and natural languages 5
As I mentioned before, formal languages tend to have strict rules about syntax.
For example, 3 + 3 = 6 is a syntactically correct mathematical statement, but
6 The way of the program
ambiguity: Natural languages are full of ambiguity, which people deal with
by using contextual clues and other information. Formal languages are
designed to be nearly or completely unambiguous, which means that any
statement has exactly one meaning, regardless of context.
redundancy: In order to make up for ambiguity and reduce misunderstand-
ings, natural languages employ lots of redundancy. As a result, they are
often verbose. Formal languages are less redundant and more concise.
literalness: Natural languages are full of idiom and metaphor. If I say, “The
other shoe fell,” there is probably no shoe and nothing falling. Formal
languages mean exactly what they say.
People who grow up speaking a natural language (everyone) often have a hard
time adjusting to formal languages. In some ways the difference between formal
and natural language is like the difference between poetry and prose, but more
so:
Poetry: Words are used for their sounds as well as for their meaning, and the
whole poem together creates an effect or emotional response. Ambiguity
is not only common but often deliberate.
Prose: The literal meaning of words is more important and the structure con-
tributes more meaning. Prose is more amenable to analysis than poetry,
but still often ambiguous.
1.5 The first program 7
Here are some suggestions for reading programs (and other formal languages).
First, remember that formal languages are much more dense than natural lan-
guages, so it takes longer to read them. Also, the structure is very important, so
it is usually not a good idea to read from top to bottom, left to right. Instead,
learn to parse the program in your head, identifying the tokens and interpret-
ing the structure. Finally, remember that the details matter. Little things like
spelling errors and bad punctuation, which you can get away with in natural
languages, can make a big difference in a formal language.
int main(void)
{
printf("Hello, World.\n");
return(EXIT_SUCCESS);
}
The third line begins with /* and ends with */, which indicates that it is a
comment. A comment is a bit of English text that you can put in the middle
of a program, usually to explain what the program does. When the compiler
sees a /*, it ignores everything from there until it finds the corresponding */.
In the forth line, you notice the word main. main is a special name that indicates
the place in the program where execution begins. When the program runs, it
starts by executing the first statement in main() and it continues, in order,
until it gets to the last statement, and then it quits.
There is no limit to the number of statements that can be in main(), but the
example contains only two. The first is an output statement, meaning that
8 The way of the program
it displays or prints a message on the screen. The second statement tells the
operating system that our program executed successfully.
The statement that prints things on the screen is printf(), and the characters
between the quotation marks will get printed. Notice the \n after the last
character. This is a special character called newline that is appended at the
end of a line of text and causes the cursor to move to the next line of the display.
The next time you output something, the new text appears on the next line. At
the end of the statement there is a semicolon (;), which is required at the end
of every statement.
There are a few other things you should notice about the syntax of this program.
First, C uses curly-brackets ({ and }) to group things together. In this case, the
output statement is enclosed in curly-brackets, indicating that it is inside the
definition of main(). Also, notice that the statement is indented, which helps
to show visually which lines are inside the definition.
At this point it would be a good idea to sit down in front of a computer and
compile and run this program. The details of how to do that depend on your
programming environment, this book assumes that you know how to do it.
As I mentioned, the C compiler is very pedantic with syntax. If you make
any errors when you type in the program, chances are that it will not compile
successfully. For example, if you misspell stdio.h, you might get an error
message like the following:
hello_world.c:1:19: error: sdtio.h: No such file or directory
There is a lot of information on this line, but it is presented in a dense format
that is not easy to interpret. A more friendly compiler might say something
like:
“On line 1 of the source code file named hello_world.c, you tried to
include a header file named sdtio.h. I didn’t find anything with that
name, but I did find something named stdio.h. Is that what you
meant, by any chance?”
Unfortunately, few compilers are so accomodating. The compiler is not really
very smart, and in most cases the error message you get will be only a hint
about what is wrong. It will take some time for you to learn to interpret
different compiler messages.
Nevertheless, the compiler can be a useful tool for learning the syntax rules
of a language. Starting with a working program (like hello_world.c), modify
it in various ways and see what happens. If you get an error message, try to
remember what the message says and what caused it, so if you see it again in
the future you will know what it means.
1.6 Glossary
problem-solving: The process of formulating a problem, finding a solution,
and expressing the solution.
1.6 Glossary 9
formal language: Any of the languages people have designed for specific pur-
poses, like representing mathematical ideas or computer programs. All
programming languages are formal languages.
natural language: Any of the languages people speak that have evolved nat-
urally.
portability: A property of a program that can run on more than one kind of
computer.
object code: The output of the compiler, after translating the program.
debugging: The process of finding and removing any of the three kinds of
errors.
10 The way of the program
1.7 Exercises
Exercise 1.1
Computer scientists have the annoying habit of using common English words to mean
something different from their common English meaning. For example, in English, a
statement and a comment are pretty much the same thing, but when we are talking
about a program, they are very different.
The glossary at the end of each chapter is intended to highlight words and phrases
that have special meanings in computer science. When you see familiar words, don’t
assume that you know what they mean!
Exercise 1.2
Before you do anything else, find out how to compile and run a C program in your
environment. Some environments provide sample programs similar to the example in
Section 1.5.
a. Type in the “Hello World” program, then compile and run it.
b. Add a second print statement that prints a second message after the “Hello
World.”. Something witty like, “How are you?” Compile and run the program
again.
c. Add a comment line to the program (anywhere) and recompile it. Run the pro-
gram again. The new comment should not affect the execution of the program.
This exercise may seem trivial, but it is the starting place for many of the programs
we will work with. In order to debug with confidence, you have to have confidence
in your programming environment. In some environments, it is easy to lose track of
which program is executing, and you might find yourself trying to debug one program
while you are accidentally executing another. Adding (and changing) print statements
is a simple way to establish the connection between the program you are looking at
and the output when the program runs.
Exercise 1.3
It is a good idea to commit as many errors as you can think of, so that you see what
error messages the compiler produces. Sometimes the compiler will tell you exactly
what is wrong, and all you have to do is fix it. Sometimes, though, the compiler will
produce wildly misleading messages. You will develop a sense for when you can trust
the compiler and when you have to figure things out yourself.
#include <stdio.h>
#include <stdlib.h>
As you can see, it is legal to put comments at the end of a line, as well as on a
line by themselves.
The phrases that appear in quotation marks are called strings, because they
are made up of a sequence (string) of letters. Actually, strings can contain any
combination of letters, numbers, punctuation marks, and other special charac-
ters.
Often it is useful to display the output from multiple output statements all on
one line. You can do this by leaving out the \n from the first printf:
int main (void)
{
printf ("Goodbye, ");
printf ("cruel world!\n");
14 Variables and types
return (EXIT_SUCCESS);
}
In this case the output appears on a single line as Goodbye, cruel world!.
Notice that there is a space between the word “Goodbye,” and the second quo-
tation mark. This space appears in the output, so it affects the behavior of the
program.
Spaces that appear outside of quotation marks generally do not affect the be-
havior of the program. For example, I could have written:
int main(void)
{
printf("Goodbye, ");
printf("cruel world!\n");
return(EXIT_SUCCESS);
}
This program would compile and run just as well as the original. The breaks
at the ends of lines (newlines) do not affect the program’s behavior either, so I
could have written:
int main(void){printf("Goodbye, ");printf("cruel world!\n");
return(EXIT_SUCCESS);}
That would work, too, although you have probably noticed that the program is
getting harder and harder to read. Newlines and spaces are useful for organizing
your program visually, making it easier to read the program and locate syntax
errors.
2.2 Values
Computer programs operate on values stored in computer memory. A value
—like a letter or a number— is one of the fundamental things that a program
manipulates. The only values we have manipulated so far are the strings we have
been outputting, like "Hello, world.". You (and the compiler) can identify
these string values because they are enclosed in quotation marks.
There are different kinds of values, including integers and characters. It is im-
portant for the program to know exactly what kind of value is manipulated
because not all manipulations will make sense on all values. We therefore dis-
tinguish between different types of values.
An integer is a whole number like 1 or 17. You can output integer values in a
similar way as you output strings:
printf("%i\n", 17);
When we look at the printf() statement more closely, we notice that the
value we are outputting no longer appears inside the quotes, but behind them
separated by comma. The string is still there, but now contains a %i instead
of any text. The %i a placeholder that tells the printf() command to print
2.3 Variables 15
an integer value. Several such placeholders exist for different data types and
formatting options of the output. We will see the next one just now.
A character value is a letter or digit or punctuation mark enclosed in single
quotes, like ’a’ or ’5’. You can output character values in a similar way:
printf("%c\n", ’}’);
This example outputs a single closing curly-bracket on a line by itself. It uses
the %c placeholder to signify the output of a character value.
It is easy to confuse different types of values, like "5", ’5’ and 5, but if you pay
attention to the punctuation, it should be clear that the first is a string, the
second is a character and the third is an integer. The reason this distinction is
important should become clear soon.
2.3 Variables
One of the most powerful features of a programming language is the ability to
manipulate values through the use of variables. So far the values that we have
used in our statements where fixed to what was written in the statement. Now
we will use a variable as a named location that stores a value.
Just as there are different types of values (integer, character, etc.), there are
different types of variables. When you create a new variable, you have to declare
what type it is. For example, the character type in C is called char. The
following statement creates a new variable named fred that has type char.
char fred;
This kind of statement is called a declaration.
The type of a variable determines what kind of values it can store. A char
variable can contain characters, and it should come as no surprise that int
variables can store integers.
Contrary to other programming languages, C does not have a dedicated variable
type for the storage of string values. We will see in a later chapter how string
values are stored in C.
To create an integer variable, the syntax is
int bob;
where bob is the arbitrary name you choose to identify the variable. In general,
you will want to make up variable names that indicate what you plan to do with
the variable. For example, if you saw these variable declarations:
char first_letter;
char last_letter;
int hour, minute;
you could probably make a good guess at what values would be stored in them.
This example also demonstrates the syntax for declaring multiple variables with
the same type: hour and minute are both integers (int type).
16 Variables and types
ATTENTION: The older C89 standard allows variable declarations only at the
beginning of a block of code. It is therefore necessary to put variable declarations
before any other statements, even if the variable itself is only needed much later
in your program.
2.4 Assignment
Now that we have created some variables, we would like to store values in them.
We do that with an assignment statement.
first_letter = ’a’; /* give first_letter the value ’a’ */
hour = 11; /* assign the value 11 to hour */
minute = 59; /* set minute to 59 */
This example shows three assignments, and the comments show three different
ways people sometimes talk about assignment statements. The vocabulary can
be confusing here, but the idea is straightforward:
A common way to represent variables on paper is to draw a box with the name of
the variable on the outside and the value of the variable on the inside. This kind
of figure is called a state diagram because is shows what state each variable
is in (you can think of it as the variable’s “state of mind”). This diagram shows
the effect of the three assignment statements:
When we assign values to variables, we have to make sure that the assigned
value correspondents to the type of the variable. In C a variable has to have
the same type as the value you assign. For example, you cannot store a string
in an int variable. The following statement generates a compiler warning:
int hour;
hour = "Hello."; /* WRONG !! */
This rule is sometimes a source of confusion, because there are many ways that
you can convert values from one type to another, and C sometimes converts
things automatically. But for now you should remember that as a general rule
variables and values have the same type, and we’ll talk about special cases later.
Another source of confusion is that some strings look like integers, but they are
not. For example, the string "123", which is made up of the characters 1, 2 and
3, is not the same thing as the number 123. This assignment is illegal:
minute = "59"; /* WRONG!! */
2.5 Outputting variables 17
hour = 11;
minute = 59;
colon = ’:’;
This program creates two integer variables named hour and minute, and a
character variable named colon. It assigns appropriate values to each of the
variables and then uses a series of output statements to generate the following:
The current time is 11:59
When we talk about “outputting a variable,” we mean outputting the value of
the variable. The name of a variable only has significance for the programmer.
The compiled program no longer contains a human readable reference to the
variable name in your program.
The printf() command is capable of outputting several variables in a single
statement. To do this, we need to put placeholders in the so called format
string, that indicate the position where the variable value will be put. The
variables will be inserted in the order of their appearance in the statement. It
is important to observe the right order and type for the variables.
By using a single output statement, we can make the previous program more
concise:
hour = 11;
minute = 59;
colon = ’:’;
On one line, this program outputs a string, two integers and a character. Very
impressive!
18 Variables and types
2.6 Keywords
A few sections ago, I said that you can make up any name you want for your
variables, but that’s not quite true. There are certain words that are reserved in
C because they are used by the compiler to parse the structure of your program,
and if you use them as variable names, it will get confused. These words, called
keywords, include int, char, void and many more.
The complete list of keywords is included in the C Standard, which is the official
language definition adopted by the the International Organization for Standard-
ization (ISO) on September 1, 1998.
Rather than memorize the list, I would suggest that you take advantage of a
feature provided in many development environments: code highlighting. As
you type, different parts of your program should appear in different colors. For
example, keywords might be blue, strings red, and other code black. If you
type a variable name and it turns blue, watch out! You might get some strange
behavior from the compiler.
2.7 Operators
Operators are special symbols that are used to represent simple computations
like addition and multiplication. Most of the operators in C do exactly what
you would expect them to do, because they are common mathematical symbols.
For example, the operator for adding two integers is +.
The following are all legal C expressions whose meaning is more or less obvious:
1+1 hour-1 hour*60+minute minute/60
Expressions can contain both variables names and values. In each case the
name of the variable is replaced with its value before the computation is per-
formed.
Addition, subtraction and multiplication all do what you expect, but you might
be surprised by division. For example, the following program:
2.8 Order of operations 19
Earlier I said that you can only assign integer values to integer variables and
character values to character variables, but that is not completely true. In some
cases, C converts automatically between types. For example, the following is
legal.
int number;
number = ’a’;
printf ("%i\n", number);
The result is 97, which is the number that is used internally by C to represent the
letter ’a’. However, it is generally a good idea to treat characters as characters,
and integers as integers, and only convert from one to the other if there is a
good reason.
More often than not, convenience wins, which is usually good for expert pro-
grammers, who are spared from rigorous but unwieldy formalism, but bad for
beginning programmers, who are often baffled by the complexity of the rules
and the number of exceptions. In this book I have tried to simplify things by
emphasizing the rules and omitting many of the exceptions.
2.10 Composition
So far we have looked at the elements of a programming language—variables,
expressions, and statements—in isolation, without talking about how to combine
them.
Actually, I shouldn’t say “at the same time,” since in reality the multiplication
has to happen before the output, but the point is that any expression, involving
numbers, characters, and variables, can be used inside an output statement.
We’ve already seen one example:
printf ("%i\n", hour * 60 + minute);
You can also put arbitrary expressions on the right-hand side of an assignment
statement:
int percentage;
percentage = (minute * 100) / 60;
This ability may not seem so impressive now, but we will see other examples
where composition makes it possible to express complex computations neatly
and concisely.
WARNING: There are limits on where you can use certain expressions; most
notably, the left-hand side of an assignment statement has to be a variable name,
not an expression. That’s because the left side indicates the storage location
where the result will go. Expressions do not represent storage locations, only
values. So the following is illegal: minute + 1 = hour;.
2.11 Glossary
variable: A named storage location for values. All variables have a type, which
determines which values it can store.
type: The meaning of values. The types we have seen so far are integers (int
in C) and characters (char in C).
declaration: A statement that creates a new variable and determines its type.
2.12 Exercises
Exercise 2.1
a. Create a new program named MyDate.c. Copy or type in something like the
"Hello, World" program and make sure you can compile and run it.
b. Following the example in Section 2.5, write a program that creates variables
named day, month and year What type is each variable?
Assign values to those variables that represent today’s date.
c. Print the value of each variable on a line by itself. This is an intermediate step
that is useful for checking that everything is working so far.
d. Modify the program so that it prints the date in standard American form:
mm/dd/yyyy.
e. Modify the program again so that the total output is:
American format:
3/18/2009
European format:
18.3.2009
The point of this exercise is to use the output function printf to display values
with different types, and to practice developing programs gradually by adding a few
statements at a time.
Exercise 2.2
a. Create a new program called MyTime.c. From now on, I won’t remind you to
start with a small, working program, but you should.
b. Following the example in Section 2.7, create variables named hour, minute and
second, and assign them values that are roughly the current time. Use a 24-hour
clock, so that at 2pm the value of hour is 14.
c. Make the program calculate and print the number of seconds since midnight.
d. Make the program calculate and print the number of seconds remaining in the
day.
e. Make the program calculate and print the percentage of the day that has passed.
f. Change the values of hour, minute and second to reflect the current time (I
assume that some time has elapsed), and check to make sure that the program
works correctly with different values.
2.12 Exercises 23
The point of this exercise is to use some of the arithmetic operations, and to start
thinking about compound entities like the time of day that are represented with mul-
tiple values. Also, you might run into problems computing percentages with ints,
which is the motivation for floating point numbers in the next chapter.
HINT: you may want to use additional variables to hold values temporarily during the
computation. Variables like this, that are used in a computation but never printed,
are sometimes called intermediate or temporary variables.
24 Variables and types
Chapter 3
Function
3.1 Floating-point
In the last chapter we had some problems dealing with numbers that were not
integers. We worked around the problem by measuring percentages instead of
fractions, but a more general solution is to use floating-point numbers, which
can represent fractions as well as integers. In C, there are two floating-point
types, called float and double. In this book we will use doubles exclusively.
You can create floating-point variables and assign values to them using the same
syntax we used for the other types. For example:
double pi;
pi = 3.14159;
It is also legal to declare a variable and assign a value to it at the same time:
int x = 1;
char first_char = "a";
double pi = 3.14159;
In fact, this syntax is quite common. A combined declaration and assignment
is sometimes called an initialization.
Although floating-point numbers are useful, they are often a source of confusion
because there seems to be an overlap between integers and floating-point num-
bers. For example, if you have the value 1, is that an integer, a floating-point
number, or both?
Because the variable on the left is an int and the value on the right is a double.
But it is easy to forget this rule, especially because there are places where C
automatically converts from one type to another. For example,
double y = 1;
should technically not be legal, but C allows it by converting the int to a
double automatically. This is convenient for the programmer, but it can cause
problems; for example:
double y = 1 / 3;
You might expect the variable y to be given the value 0.333333, which is a legal
floating-point value, but in fact it will get the value 0.0. The reason is that
the expression on the right appears to be the ratio of two integers, so C does
integer division, which yields the integer value 0. Converted to floating-point,
the result is 0.0.
One way to solve this problem (once you figure out what it is) is to make the
right-hand side a floating-point expression:
double y = 1.0 / 3.0;
This sets y to 0.333333, as expected.
3.2 Constants
In the previous section we have assigned the value 3.14159 to a floating point
variable. An important thing to remember about variables is, that they can hold
– as their name implies – different values at different points in your program.
For example, we could assign the value 3.14159 to the variable pi now and
assign some other value to it later on:
double pi = 3.14159;
...
pi = 10.999; /* probably a logical error in your program */
The second value is probably not what you intended when you first created the
named storage location pi . The value for π is constant and does not change
over time. Using the storage location pi to hold arbitrary other values can cause
some very hard to find bugs in your program.
C allows you to specify the static nature of storage locations through the use of
the keyword const. It must be used in conjunction with the required type of
the constant. A value will be assigned at initialisation but can never be changed
again during the runtime of the program.
3.3 Converting from double to int 27
3.5 Composition
Just as with mathematical functions, C functions can be composed, meaning
that you use one expression as part of another. For example, you can use any
expression as an argument to a function:
double x = cos (angle + PI/2);
This statement takes the value of PI, divides it by two and adds the result to
the value of angle. The sum is then passed as an argument to the cos function.
You can also take the result of one function and pass it as an argument to
another:
double x = exp (log (10.0));
This statement finds the log base e of 10 and then raises e to that power. The
result gets assigned to x; I hope you know what it is.
3.6 Adding new functions 29
Second line.
Notice the extra space between the two lines. What if we wanted more space
between the lines? We could call the same function repeatedly:
int main (void)
{
printf ("First Line.\n");
30 Function
NewLine ();
NewLine ();
NewLine ();
printf ("Second Line.\n");
}
Or we could write a new function, named PrintThreeLines(), that prints three
new lines:
void PrintThreeLines (void)
{
PrintNewLine (); PrintNewLine (); PrintNewLine ();
}
• You can call the same procedure repeatedly. In fact, it is quite common
and useful to do so.
• You can have one function call another function. In this case, main()
calls PrintThreeLines() and PrintThreeLines() calls PrintNewLine().
Again, this is common and useful.
So far, it may not be clear why it is worth the trouble to create all these new
functions. Actually, there are a lot of reasons, but this example only demon-
strates two:
first line of the called function, execute all the statements there, and then come
back and pick up again where you left off.
That sounds simple enough, except that you have to remember that one function
can call another. Thus, while we are in the middle of main(), we might have
to go off and execute the statements in PrintThreeLines(). But while we are
executing PrintThreeLines(), we get interrupted three times to go off and
execute PrintNewLine().
Fortunately, C is adept at keeping track of where it is, so each time
PrintNewLine() completes, the program picks up where it left off in
PrintThreeLine(), and eventually gets back to main() so the program can
terminate.
What’s the moral of this sordid tale? When you read a program, don’t read
from top to bottom. Instead, follow the flow of execution.
The char value you provide is called an argument, and we say that the ar-
gument is passed to the function. In this case the value ’a’ is passed as an
argument to PrintTwice() where it will get printed twice.
Alternatively, if we had a char variable, we could use it as an argument instead:
int main ()
{
char argument = ’b’;
PrintTwice (argument);
return EXIT_SUCCESS;
}
Notice something very important here: the name of the variable we pass as an
argument (argument) has nothing to do with the name of the parameter (phil).
Let me say that again:
They can be the same or they can be different, but it is important to realize
that they are not the same thing, except that they happen to have the same
value (in this case the character ’b’).
The value you provide as an argument must have the same type as the pa-
rameter of the function you call. This rule is important, but it is sometimes
confusing because C sometimes converts arguments from one type to another
automatically. For now you should learn the general rule, and we will deal with
exceptions later.
• What happens if you call a function and you don’t do anything with the
result (i.e. you don’t assign it to a variable or use it as part of a larger
expression)?
• What happens if you use a function without a result as part of an expres-
sion, like PrintNewLine() + 7?
• Can we write functions that yield results, or are we stuck with things like
PrintNewLine() and PrintTwice()?
3.13 Glossary 35
The answer to the third question is “yes, you can write functions that return
values,” and we’ll do it in a couple of chapters. I will leave it up to you to answer
the other two questions by trying them out. Any time you have a question about
what is legal or illegal in C, a good way to find out is to ask the compiler.
3.13 Glossary
constant: A named storage location similar to a variable, that can not be
changed once it has been initialised.
floating-point: A type of variable (or value) that can contain fractions as well
as integers. There are a few floating-point types in C; the one we use in
this book is double.
argument: A value that you provide when you call a function. This value must
have the same type as the corresponding parameter.
3.14 Exercises
Exercise 3.1
The point of this exercise is to practice reading code and to make sure that you
understand the flow of execution through a program with multiple functions.
a. What is the output of the following program? Be precise about where there are
spaces and where there are newlines.
HINT: Start by describing in words what Ping() and Baffle() do when they
are invoked.
#include <stdio.h>
#include <stdlib.h>
void Ping ()
{
printf (".\n");
}
36 Function
void Baffle ()
{
printf ("wug");
Ping ();
}
void Zoop ()
{
Baffle ();
printf ("You wugga ");
Baffle ();
}
Exercise 3.2 The point of this exercise is to make sure you understand how to
write and invoke functions that take parameters.
a. Write the first line of a function named Zool() that takes three parameters: an
int and two char.
b. Write a line of code that invokes Zool(), passing as arguments the value 11, the
letter a, and the letter z.
Exercise 3.3
The purpose of this exercise is to take code from a previous exercise and encapsulate
it in a function that takes parameters. You should start with a working solution to
exercise
a. Write a function called PrintDateAmerican() that takes the day, month and
year as parameters and that prints them in American format.
b. Test your function by invoking it from main() and passing appropriate argu-
ments. The output should look something like this (except that the date might
be different):
3/29/2009
Exercise 3.4
Many computations can be expressed concisely using the “multadd” operation, which
takes three operands and computes a*b + c. Some processors even provide a hardware
implementation of this operation for floating-point numbers.
e. Write a function called Yikes() that takes a double as a parameter and that
uses Multadd() to calculate and print
p
xe−x + 1 − e−x
HINT: the Math function for raising e to a power is double exp(double x);.
In the last part, you get a chance to write a function that invokes a function you
wrote. Whenever you do that, it is a good idea to test the first function carefully
before you start working on the second. Otherwise, you might find yourself debugging
two functions at the same time, which can be very difficult.
else
{
printf ("x is odd\n");
}
}
Now you have a function named PrintParity() that will display an appropriate
message for any integer you care to provide. In main() you would call this
function as follows:
PrintParity (17);
Always remember that when you call a function, you do not have to declare the
types of the arguments you provide. C can figure out what type they are. You
should resist the temptation to write things like:
int number = 17;
PrintParity (int number); /* WRONG!!! */
the function. The flow of execution immediately returns to the caller and the
remaining lines of the function are not executed.
I used a floating-point value on the right side of the condition because there is
a floating-point variable on the left.
Remember that any time you want to use one a function from the math library,
you have to include the header file math.h.
4.7 Recursion
I mentioned in the last chapter that it is legal for one function to call another,
and we have seen several examples of that. I neglected to mention that it is
also legal for a function to call itself. It may not be obvious why that is a good
thing, but it turns out to be one of the most magical and interesting things a
program can do.
And then you’re back in main() (what a trip). So the total output looks like:
3
2
1
Blastoff!
As a second example, let’s look again at the functions PrintNewLine() and
PrintThreeLines().
void PrintNewLine ()
{
printf ("\n");
}
void PrintThreeLines ()
{
PrintNewLine (); PrintNewLine (); PrintNewLine ();
}
Although these work, they would not be much help if I wanted to output 2
newlines, or 106. A better alternative would be
void PrintLines (int n)
{
if (n > 0)
{
printf ("\n");
PrintLines (n-1);
}
}
This program is similar to Countdown; as long as n is greater than zero, it
outputs one newline, and then calls itself to output n-1 additional newlines.
Thus, the total number of newlines is 1 + (n-1), which usually comes out to
roughly n.
The process of a function calling itself is called recursion, and such functions
are said to be recursive.
4.8 Infinite recursion 45
You should write a small program that recurses forever and run it to see what
happens.
Remember that every time a function gets called it creates a new instance that
contains the function’s local variables and parameters.
Countdown() n 0
Countdown() n 1
Countdown() n 2
Countdown() n 3
main()
There is one instance of main() and four instances of Countdown(), each with
a different value for the parameter n. The bottom of the stack, Countdown()
46 Conditionals and recursion
with n=0 is the base case. It does not make a recursive call, so there are no
more instances of Countdown().
The instance of main() is empty because main() does not have any parameters
or local variables. As an exercise, draw a stack diagram for PrintLines(),
invoked with the parameter n=4.
4.10 Glossary
modulus: An operator that works on integers and yields the remainder when
one number is divided by another. In C it is denoted with a percent sign
(%).
conditional: A block of statements that may or may not be executed depend-
ing on some condition.
chaining: A way of joining several conditional statements in sequence.
nesting: Putting a conditional statement inside one or both branches of an-
other conditional statement.
recursion: The process of calling the same function you are currently execut-
ing.
infinite recursion: A function that calls itself recursively without every reach-
ing the base case. Eventually an infinite recursion will cause a run-time
error.
4.11 Exercises
Exercise 4.1 This exercise reviews the flow of execution through a program with
multiple methods. Read the following code and answer the questions below.
#include <stdio.h>
#include <stdlib.h>
}
else
{
printf ("rattle ");
Baffle (quince);
printf ("boo-wa-ha-ha\n");
}
}
a. Write the number 1 next to the first statement of this program that will be
executed. Be careful to distinguish things that are statements from things that
are not.
b. Write the number 2 next to the second statement, and so on until the end of
the program. If a statement is executed more than once, it might end up with
more than one number next to it.
c. What is the value of the parameter blimp when baffle() gets invoked?
d. What is the output of this program?
Exercise 4.2 The first verse of the song “99 Bottles of Beer” is:
99 bottles of beer on the wall, 99 bottles of beer, ya’ take one down, ya’
pass it around, 98 bottles of beer on the wall.
Subsequent verses are identical except that the number of bottles gets smaller by one
in each verse, until the last verse:
No bottles of beer on the wall, no bottles of beer, ya’ can’t take one down,
ya’ can’t pass it around, ’cause there are no more bottles of beer on the
wall!
And then the song (finally) ends.
Write a program that prints the entire lyrics of “99 Bottles of Beer.” Your program
should include a recursive method that does the hard part, but you also might want
to write additional methods to separate the major functions of the program.
As you are developing your code, you will probably want to test it with a small number
of verses, like “3 Bottles of Beer.”
The purpose of this exercise is to take a problem and break it into smaller problems,
and to solve the smaller problems by writing simple, easily-debugged methods.
Exercise 4.3 You can use the getchar() function in C to get character input from
the user through the keyboard. This function stops the execution of the program and
waits for the input from the user.
48 Conditionals and recursion
The getchar() function has the type int and does not require an argument. It returns
the ASCII-Code (cf. Appendix B) of the key that has been pressed on the keyboard.
a. Write a program, that asks the user to input a digit between 0 and 9.
b. Test the input from the user and display an error message if the returned value
is not a digit. The program should then be terminated. If the test is successful,
the program should print the input value on the computer screen.
Exercise 4.4 Fermat’s Last Theorem says that there are no integers a, b, and c
such that
an + bn = cn
You should assume that there is a function named raiseToPow() that takes two inte-
gers as arguments and that raises the first argument to the power of the second. For
example:
int x = raiseToPow (2, 3);
would assign the value 8 to x, because 23 = 8.
Chapter 5
Fruitful functions
function and use the following expression as a return value.” The expression you
provide can be arbitrarily complicated, so we could have written this function
more concisely:
double Area (double radius)
{
return acos(-1.0) * radius * radius;
}
On the other hand, temporary variables like area often make debugging easier.
In either case, the type of the expression in the return statement must match
the return type of the function. In other words, when you declare that the return
type is double, you are making a promise that this function will eventually
produce a double. If you try to return with no expression, or an expression
with the wrong type, the compiler will take you to task.
Sometimes it is useful to have multiple return statements, one in each branch
of a conditional:
double AbsoluteValue (double x)
{
if (x < 0)
{
return -x;
}
else
{
return x;
}
}
Since these returns statements are in an alternative conditional, only one will
be executed. Although it is legal to have more than one return statement in a
function, you should keep in mind that as soon as one is executed, the function
terminates without executing any subsequent statements.
Code that appears after a return statement, or any place else where it can
never be executed, is called dead code. Some compilers warn you if part of
your code is dead.
If you put return statements inside a conditional, then you have to guarantee
that every possible path through the program hits a return statement. For
example:
double AbsoluteValue (double x)
{
if (x < 0)
{
return -x;
}
else if (x > 0)
{
5.2 Program development 51
return x;
} /* WRONG!! */
}
This program is not correct because if x happens to be 0, then neither condition
will be true and the function will end without hitting a return statement. Un-
fortunately, many C compilers do not catch this error. As a result, the program
may compile and run, but the return value when x==0 could be anything, and
will probably be different in different environments.
By now you are probably sick of seeing compiler errors, but as you gain more
experience, you will realize that the only thing worse than getting a compiler
error is not getting a compiler error when your program is wrong.
Here’s the kind of thing that’s likely to happen: you test AbsoluteValue() with
several values of x and it seems to work correctly. Then you give your program to
someone else and they run it in another environment. It fails in some mysterious
way, and it takes days of debugging to discover that the problem is an incorrect
implementation of AbsoluteValue(). If only the compiler had warned you!
From now on, if the compiler points out an error in your program, you should
not blame the compiler. Rather, you should thank the compiler for finding your
error and sparing you days of debugging. Some compilers have an option that
tells them to be extra strict and report all the errors they can find. You should
turn this option on all the time.
As an aside, you should know that there is a function in the math library called
fabs() that calculates the absolute value of a double – correctly.
As an example, imagine you want to find the distance between two points, given
by the coordinates (x1 , y1 ) and (x2 , y2 ). By the usual definition,
p
distance = (x2 − x1 )2 + (y2 − y1 )2 (5.1)
The first step is to consider what a Distance function should look like in C. In
other words, what are the inputs (parameters) and what is the output (return
value).
In this case, the two points are the parameters, and it is natural to represent
them using four doubles. The return value is the distance, which will have type
double.
double Distance (double x1, double y1, double x2, double y2)
{
return 0.0;
}
The return statement is a placekeeper so that the function will compile and
return something, even though it is not the right answer. At this stage the
function doesn’t do anything useful, but it is worthwhile to try compiling it so
we can identify any syntax errors before we make it more complicated.
In order to test the new function, we have to call it with sample values. Some-
where in main() I would add:
double dist = Distance (1.0, 2.0, 4.0, 6.0);
printf ("%f\n" dist);
I chose these values so that the horizontal distance is 3 and the vertical distance
is 4; that way, the result will be 5 (the hypotenuse of a 3-4-5 triangle). When
you are testing a function, it is useful to know the right answer.
Once we have checked the syntax of the function definition, we can start adding
lines of code one at a time. After each incremental change, we recompile and
run the program. That way, at any point we know exactly where the error must
be—in the last line we added.
The next step in the computation is to find the differences x2 − x1 and y2 − y1 .
I will store those values in temporary variables named dx and dy.
double Distance (double x1, double y1, double x2, double y2)
{
double dx = x2 - x1;
double dy = y2 - y1;
printf ("dx is %f\n", dx);
printf ("dy is %f\n", dy;
return 0.0;
}
I added output statements that will let me check the intermediate values before
proceeding. As I mentioned, I already know that they should be 3.0 and 4.0.
When the function is finished I will remove the output statements. Code like
that is called scaffolding, because it is helpful for building the program, but
it is not part of the final product. Sometimes it is a good idea to keep the
scaffolding around, but comment it out, just in case you need it later.
The next step in the development is to square dx and dy. We could use the
pow() function, but it is simpler and faster to just multiply each term by itself.
double Distance (double x1, double y1, double x2, double y2)
{
double dx = x2 - x1;
double dy = y2 - y1;
double dsquared = dx*dx + dy*dy;
printf ("d_squared is %f\n", dsquared);
5.3 Composition 53
return 0.0;
}
Again, I would compile and run the program at this stage and check the inter-
mediate value (which should be 25.0).
Finally, we can use the sqrt() function to compute and return the result.
double Distance (double x1, double y1, double x2, double y2)
{
double dx = x2 - x1;
double dy = y2 - y1;
double dsquared = dx*dx + dy*dy;
double result = sqrt (dsquared);
return result;
}
Then in main(), we should output and check the value of the result.
As you gain more experience programming, you might find yourself writing
and debugging more than one line at a time. Nevertheless, this incremental
development process can save you a lot of debugging time.
The key aspects of the process are:
• Once the program is working, you might want to remove some of the
scaffolding or consolidate multiple statements into compound expressions,
but only if it does not make the program difficult to read.
5.3 Composition
As you should expect by now, once you define a new function, you can use it as
part of an expression, and you can build new functions using existing functions.
For example, what if someone gave you two points, the center of the circle and
a point on the perimeter, and asked for the area of the circle?
Let’s say the center point is stored in the variables xc and yc, and the perimeter
point is in xp and yp. The first step is to find the radius of the circle, which is the
distance between the two points. Fortunately, we have a function, Distance(),
that does that.
double radius = Distance (xc, yc, xp, yp);
The second step is to find the area of a circle with that radius, and return it.
double result = Area (radius);
return result;
54 Fruitful functions
The code itself is straightforward, although it is a bit longer than it needs to be.
Remember that the expression x >= 0 && x < 10 is evaluated to a boolean
value, so there is nothing wrong with returning it directly, and avoiding the if
statement altogether:
int IsSingleDigit (int x)
{
return (x >= 0 && x < 10);
}
In main() you can call this function in the usual ways:
printf("%i\n", IsSingleDigit (2));
short bigFlag = !IsSingleDigit (17);
5.8 Returning from main() 57
The first line outputs the value true because 2 is a single-digit number. Unfor-
tunately, when C outputs boolean values, it does not display the words TRUE
and FALSE, but rather the integers 1 and 0.
The second line assigns the value true to bigFlag only if 17 is not a positive
single-digit number.
The most common use of boolean functions is inside conditional statements
if (IsSingleDigit (x))
{
printf("x is little\n");
}
else
{
printf("x is big\n");
}
5.9 Glossary
return type: The type of value a function returns.
dead code: Part of a program that can never be executed, often because it
appears after a return statement.
scaffolding: Code that is used during program development but is not part of
the final version.
void: A special return type that indicates a void function; that is, one that
does not return a value.
boolean: A value or variable that can take on one of two states, often called
true and f alse. In C, boolean values are mainly stored in variables of
type short and preprocessor statements are used to define the states.
5.10 Exercises
Exercise 5.1 If you are given three sticks, you may or may not be able to arrange
them in a triangle. For example, if one of the sticks is 12 inches long and the other
two are one inch long, it is clear that you will not be able to get the short sticks to
meet in the middle. For any three lengths, there is a simple test to see if it is possible
to form a triangle:
“If any of the three lengths is greater than the sum of the other two,
then you cannot form a triangle. Otherwise, you can.”
Write a function named IsTriangle() that it takes three integers as arguments, and
that returns either TRUE or FALSE, depending on whether you can or cannot form a
triangle from sticks with the given lengths.
The point of this exercise is to use conditional statements to write a function that
returns a value.
The purpose of this exercise is to make sure you understand logical operators and the
flow of execution through fruitful methods.
#define TRUE 1
#define FALSE 0
hoopyFlag = TRUE;
}
else
{
hoopyFlag = FALSE;
}
return hoopyFlag;
}
Exercise 5.3
a. Create a new program called Sum.c, and type in the following two functions.
int FunctionOne (int m, int n)
{
if (m == n)
{
return n;
}
60 Fruitful functions
else
{
return m + FunctionOne (m+1, n);
}
}
Exercise 5.4 Write a recursive function called Power() that takes a double x and
an integer n and that returns xn .
Hint: a recursive definition of this operation is Power (x, n) = x * Power (x, n-1).
Also, remember that anything raised to the zeroeth power is 1.
Exercise 5.5
(This exercise is based on page 44 of Ableson and Sussman’s Structure and Interpre-
tation of Computer Programs.)
The algorithm is based on the observation that, if r is the remainder when a is divided
by b, then the common divisors of a and b are the same as the common divisors of b
and r. Thus we can use the equation
gcd(a, b) = gcd(b, r)
implies that the GCD of 36 and 20 is 4. It can be shown that for any two starting
numbers, this repeated reduction eventually produces a pair where the second number
is 0. Then the GCD is the other number in the pair.
Write a function called gcd that takes two integer parameters and that uses Euclid’s
algorithm to compute and return the greatest common divisor of the two numbers.
Exercise 5.6 The distance between two points (x1 , y1 ) and (x2 , y2 ) is
p
Distance = (x2 − x1 )2 + (y2 − y1 )2
Please write a function named Distance() that takes four doubles as parameters—x1,
y1, x2 and y2—and that prints the distance between the points.
You should assume that there is a function named SumSquares() that calculates and
returns the sum of the squares of its arguments. For example:
double x = SumSquares (3.0, 4.0);
would assign the value 25.0 to x.
The point of this exercise is to write a new function that uses an existing one. You
should write only one function: Distance(). You should not write SumSquares() or
main() and you should not invoke Distance().
Exercise 5.7 The point of this exercise is to practice the syntax of fruitful functions.
a. Use your existing solution to Exercise 3.4 and make sure you can still compile
and run it.
b. Transform Multadd() into a fruitful function, so that instead of printing a result,
it returns it.
c. Everywhere in the program that Multadd() gets invoked, change the invocation
so that it stores the result in a variable and/or prints the result.
d. Transform Yikes() in the same way.
62 Fruitful functions
Chapter 6
Iteration
fred = 7; fred 5 7
6.2 Iteration
One of the things computers are often used for is the automation of repetitive
tasks. Repeating identical or similar tasks without making errors is something
that computers do well and people do poorly.
In section 4.7 we have seen programs that use recursion to perform repetition,
such as PrintLines() and Countdown(). I now want to introduce a new type
of repetition, that is called iteration, and C provides several language features
that make it easier to write repetetive programs.
The two features we are going to look at are the while statement and the for
statement.
2. If the condition is false, exit the while statement and continue execution
at the next statement.
3. If the condition is true, execute each of the statements between the curly-
brackets, and then go back to step 1.
This type of flow is called a loop because the third step loops back around to
the top. Notice that if the condition is false the first time through the loop, the
statements inside the loop are never executed. The statements inside the loop
are called the body of the loop.
The body of the loop should change the value of one or more variables so that,
eventually, the condition becomes false and the loop terminates. Otherwise the
loop will repeat forever, which is called an infinite loop. An endless source
of amusement for computer scientists is the observation that the directions on
shampoo, “Lather, rinse, repeat,” are an infinite loop.
In the case of Countdown(), we can prove that the loop will terminate because
we know that the value of n is finite, and we can see that the value of n gets
smaller each time through the loop (each iteration), so eventually we have to
get to zero. In other cases it is not so easy to tell:
void Sequence (int n)
{
while (n != 1)
{
printf ("%i\n", n);
if (n%2 == 0) /* n is even */
{
n = n / 2;
}
else /* n is odd */
{
n = n*3 + 1;
}
}
}
The condition for this loop is n != 1, so the loop will continue until n is 1,
which will make the condition false.
At each iteration, the program outputs the value of n and then checks whether
it is even or odd. If it is even, the value of n is divided by two. If it is odd, the
value is replaced by 3n + 1. For example, if the starting value (the argument
passed to Sequence) is 3, the resulting sequence is 3, 10, 5, 16, 8, 4, 2, 1.
Since n sometimes increases and sometimes decreases, there is no obvious proof
that n will ever reach 1, or that the program will terminate. For some particular
values of n, we can prove termination. For example, if the starting value is a
66 Iteration
power of two, then the value of n will be even every time through the loop, until
we get to 1. The previous example ends with such a sequence, starting with 16.
Particular values aside, the interesting question is whether we can prove that
this program terminates for all values of n. So far, no one has been able to
prove it or disprove it!
6.4 Tables
One of the things loops are good for is generating tabular data. For example,
before computers were readily available, people had to calculate logarithms,
sines and cosines, and other common mathematical functions by hand. To
make that easier, there were books containing long tables where you could find
the values of various functions. Creating these tables was slow and boring, and
the result tended to be full of errors.
When computers appeared on the scene, one of the initial reactions was, “This
is great! We can use the computers to generate the tables, so there will be no
errors.” That turned out to be true (mostly), but shortsighted. Soon thereafter
computers and calculators were so pervasive that the tables became obsolete.
Well, almost. It turns out that for some operations, computers use tables of
values to get an approximate answer, and then perform computations to improve
the approximation. In some cases, there have been errors in the underlying
tables, most famously in the table the original Intel Pentium used to perform
floating-point division.
Although a “log table” is not as useful as it once was, it still makes a good
example of iteration. The following program outputs a sequence of values in the
left column and their logarithms in the right column:
double x = 1.0;
while (x < 10.0)
{
printf ("%.0f\t%f\n", x ,log(x));
x = x + 1.0;
}
The sequence \t represents a tab character. The sequence \n represents a
newline character. They are so called escape sequences which are used to encode
non-printable ASCII-characters. Escape sequences can be included anywhere in
a string, although in these examples the sequence is the whole string.
A tab character causes the cursor to shift to the right until it reaches one of
the tab stops, which are normally every eight characters. As we will see in a
minute, tabs are useful for making columns of text line up. A newline character
causes the cursor to move on to the next line.
1 0.000000
2 0.693147
3 1.098612
4 1.386294
5 1.609438
6 1.791759
7 1.945910
8 2.079442
9 2.197225
If these values seem odd, remember that the log() function uses base e. Since
powers of two are so important in computer science, we often want to find
logarithms with respect to base 2. To do that, we can use the following formula:
loge x
log2 x =
loge 2
Changing the output statement to
printf ("%.0f\t%f\n", x, log(x) / log(2.0));
yields:
1 0.000000
2 1.000000
3 1.584963
4 2.000000
5 2.321928
6 2.584963
7 2.807355
8 3.000000
9 3.169925
We can see that 1, 2, 4 and 8 are powers of two, because their logarithms base
2 are round numbers. If we wanted to find the logarithms of other powers of
two, we could modify the program like this:
double x = 1.0;
while (x < 100.0)
{
printf ("%.0f\t%.0f\n", x, log(x) / log(2.0));
x = x * 2.0;
}
Now instead of adding something to x each time through the loop, which yields
an arithmetic sequence, we multiply x by something, yielding a geometric
sequence. The result is:
1 0
2 1
4 2
8 3
16 4
32 5
64 6
68 Iteration
Because we are using tab characters between the columns, the position of the
second column does not depend on the number of digits in the first column.
Log tables may not be useful any more, but for computer scientists, knowing
the powers of two is! As an exercise, modify this program so that it outputs the
powers of two up to 65536 (that’s 216 ). Print it out and memorize it.
A good way to start is to write a simple loop that prints the multiples of 2, all
on one line.
int i = 1;
while (i <= 6)
{
printf("%i ", i*2);
i = i + 1;
}
printf("\n");
The first line initializes a variable named i, which is going to act as a counter,
or loop variable. As the loop executes, the value of i increases from 1 to 6,
and then when i is 7, the loop terminates. Each time through the loop, we
print the value 2*i followed by three spaces. By omitting the \n from the first
output statement, we get all the output on a single line.
Here’s a function that encapsulates the loop from the previous section and
generalizes it to print multiples of n.
6.6 Encapsulation and generalization 69
If we call this function with the argument 2, we get the same output as before.
With argument 3, the output is:
3 6 9 12 15 18
and with argument 4, the output is
4 8 12 16 20 24
By now you can probably guess how we are going to print a multiplication table:
we’ll call PrintMultiples() repeatedly with different arguments. In fact, we
are going to use another loop to iterate through the rows.
int i = 1;
while (i <= 6)
{
PrintMultiples (i);
i = i + 1;
}
First of all, notice how similar this loop is to the one inside PrintMultiples().
I only replaced the call of the printf() function with the call of the
PrintMultiples() function.
6.7 Functions
In the last section I mentioned “all the things functions are good for.” About
this time, you might be wondering what exactly those things are. Here are some
of the reasons functions are useful:
name, but they do not refer to the same storage location, and changing the
value of one of them has no effect on the other.
Remember that variables that are declared inside a function definition are local.
You cannot access a local variable from outside its “home” function, and you
are free to have multiple variables with the same name, as long as they are not
in the same function.
The stack diagram for this program shows clearly that the two variables named
i are not in the same storage location. They can have different values, and
changing one does not affect the other.
PrintMultiples() n 1 i 3
PrintMultTable() i 1
main()
Just to be annoying, I will also call this parameter high, demonstrating that
different functions can have parameters with the same name (just like local
variables):
void PrintMultiples (int n, int high)
{
int i = 1;
while (i <= high)
{
printf ("%i ", n*i);
i = i + 1;
}
printf ("\n");
}
When you generalize a function appropriately, you often find that the resulting
program has capabilities you did not intend. For example, you might notice
that the multiplication table is symmetric, because ab = ba, so all the entries in
the table appear twice. You could save ink by printing only half the table. To
do that, you only have to change one line of PrintMultTable(). Change
PrintMultiples (i, high);
to
PrintMultiples (i, i);
and you get:
1
2 4
3 6 9
4 8 12 16
5 10 15 20 25
6 12 18 24 30 36
7 14 21 28 35 42 49
I’ll leave it up to you to figure out how it works.
6.11 Glossary
loop: A statement that executes repeatedly while a condition is true or until
some condition is satisfied.
infinite loop: A loop whose condition is always true.
body: The statements inside the loop.
iteration: One pass through (execution of) the body of the loop, including the
evaluation of the condition.
tab: A special character, written as \t in C, that causes the cursor to move to
the next tab stop on the current line.
encapsulate: To divide a large complex program into components (like func-
tions) and isolate the components from each other (for example, by using
local variables).
local variable: A variable that is declared inside a function and that exists
only within that function. Local variables cannot be accessed from outside
their home function, and do not interfere with any other functions.
generalize: To replace something unnecessarily specific (like a constant value)
with something appropriately general (like a variable or parameter). Gen-
eralization makes code more versatile, more likely to be reused, and some-
times even easier to write.
development plan: A process for developing a program. In this chapter, I
demonstrated a style of development based on developing code to do sim-
ple, specific things, and then encapsulating and generalizing.
74 Iteration
6.12 Exercises
Exercise 6.1
void Loop(int n)
{
int i = n;
while (i > 1)
{
printf ("%i\n",i);
if (i%2 == 0)
{
i = i/2;
}
else
{
i = i+1;
}
}
}
Exercise 6.2 In Exercise 5.4 we wrote a recursive version of Power(), which takes
a double x and an integer n and returns xn . Now write an iterative function to perform
the same calculation.
Exercise 6.3 Let’s say you are given a number, a, and you want to find its square
root. One way to do that is to start with a very rough guess about the answer, x0 ,
and then improve the guess using the following formula:
For example, if we want to find the square root of 9, and we start with x0 = 6, then
x1 = (6 + 9/6)/2 = 15/4 = 3.75, which is closer.
We can repeat the procedure, using x1 to calculate x2 , and so on. In this case,
x2 = 3.075 and x3 = 3.00091. So that is converging very quickly on the right answer
(which is 3).
Write a function called SquareRoot that takes a double as a parameter and that
returns an approximation of the square root of the parameter, using this algorithm.
You may not use the sqrt() function from the math.h library.
6.12 Exercises 75
As your initial guess, you should use a/2. Your function should iterate until it gets two
consecutive estimates that differ by less than 0.0001; in other words, until the absolute
value of xn − xn−1 is less than 0.0001. You can use the built-in fabs() function from
the math.h library to calculate the absolute value.
2
Exercise 6.4 One way to evaluate e−x is to use the infinite series expansion
2
e−x = 1 − 2x + 3x2 /2! − 4x3 /3! + 5x4 /4! − ...
In other words, we need to add up a series of terms where the ith term is equal to
(−1)i (i+1)xi /i!. Write a function named Gauss() that takes x and n as arguments and
that returns the sum of the first n terms of the series. You should not use factorial()
or pow().
76 Iteration
Chapter 7
Arrays
c 0 0 0 0
c[0] c[1] c[2] c[3]
The large numbers inside the boxes are the values of the elements in the
array. The small numbers outside the boxes are the indices used to identify each
78 Arrays
box. When you allocate a new array, without initializing, the arrays elements
typically contain arbitrary values and you must initialise them to a meaningful
value before using them.
c[0] = 7;
c[1] = c[0] * 2;
c[2]++;
c[3] -= 60;
All of these are legal assignment statements. Here is the effect of this code
fragment:
c 7 14 1 -60
c[0] c[1] c[2] c[3]
By now you should have noticed that the four elements of this array are num-
bered from 0 to 3, which means that there is no element with the index 4.
Nevertheless, it is a common error to go beyond the bounds of an array. In safer
languages such as Java, this will cause an error and most likely the program
quits. C does not check array boundaries, so your program can go on accessing
memory locations beyond the array itself, as if they where part of the array.
This is most likely wrong and can cause very severe bugs in your program.
You can use any expression as an index, as long as it has type int. One of the
most common ways to index an array is with a loop variable. For example:
int i = 0;
while (i < 4)
{
printf ("%i\n", c[i]);
i++;
}
This is a standard while loop that counts from 0 up to 4, and when the loop
variable i is 4, the condition fails and the loop terminates. Thus, the body of
the loop is only executed when i is 0, 1, 2 and 3.
Each time through the loop we use i as an index into the array, printing the
ith element. This type of array traversal is very common. Arrays and loops go
together like fava beans and a nice Chianti.
a = 0.0; /* Wrong! */
b = a; /* Wrong! */
In order to set all of the elements of an array to some value, you must do so
element by element. To copy the contents of one array to another, you must
again do so, by copying each element from one array to the other.
int i = 0;
while (i < 3)
{
b[i] = a[i];
i++;
}
This type of loop is so common that there is an alternate loop statement, called
for, that expresses it more concisely. The general syntax looks like this:
for (INITIALIZER; CONDITION; INCREMENTOR)
{
BODY
}
This statement is exactly equivalent to
INITIALIZER;
while (CONDITION)
{
BODY
INCREMENTOR
}
except that it is more concise and, since it puts all the loop-related statements
in one place, it is easier to read. For example:
int i;
for (i = 0; i < 4; i++)
{
printf("%i\n", c[i]);
}
is equivalent to
7.5 Array length 81
int i = 0;
while (i < 4)
{
printf("%i\n", c[i]);
i++;
}
generate pseudorandom numbers and use them to determine the outcome of the
program. Pseudorandom numbers are not truly random in the mathematical
sense, but for our purposes, they will do.
The return value from rand() is an integer between 0 and RAND_MAX, where
RAND_MAX is a large number (about 2 billion on my computer) also defined in the
header file. Each time you call rand() you get a different randomly-generated
number. To see a sample, run this loop:
1804289383
846930886
1681692777
1714636915
Of course, we don’t always want to work with gigantic integers. More often we
want to generate integers between 0 and some upper bound. A simple way to
do that is with the modulus operator. For example:
This code sets y to a random value between 0.0 and 1.0, including both end
points. As an exercise, you might want to think about how to generate a
random floating-point value in a given range; for example, between 100.0 and
200.0.
7.7 Statistics 83
7.7 Statistics
The numbers generated by rand() are supposed to be distributed uniformly.
That means that each value in the range should be equally likely. If we count
the number of times each value appears, it should be roughly the same for all
values, provided that we generate a large number of values.
In the next few sections, we will write programs that generate a sequence of
random numbers and check whether this property holds true.
7.10 Counting
A good approach to problems like this is to think of simple functions that are
easy to write, and that might turn out to be useful. Then you can combine
them into a solution. This approach is sometimes called bottom-up design.
7.11 Checking the other values 85
Of course, it is not easy to know ahead of time which functions are likely to be
useful, but as you gain experience you will have a better idea. Also, it is not
always obvious what sort of things are easy to write, but a good approach is to
look for subproblems that fit a pattern you have seen before.
In our current example we want to examine a potentially large set of elements
and count the number of times a certain value appears. You can think of this
program as an example of a pattern called “traverse and count.” The elements
of this pattern are:
• A counter that keeps track of how many elements pass the test.
In this case, I have a function in mind called HowMany() that counts the number
of elements in a array that are equal to a given value. The parameters are the
array, the length of the array and the integer value we are looking for. The
return value is the number of times the value appears.
int HowMany (int array[], int length, int value)
{
int i;
int count = 0;
printf ("value\tHowMany\n");
for (i = 0; i < upperBound; i++)
{
86 Arrays
7.12 A histogram
It is often useful to take the data from the previous tables and store them for
later access, rather than just print them. What we need is a way to store 10
integers. We could create 10 integer variables with names like howManyOnes,
howManyTwos, etc. But that would require a lot of typing, and it would be a
real pain later if we decided to change the range of values.
A better solution is to use a array with length 10. That way we can create all
ten storage locations at once and we can access them using indices, rather than
ten different names. Here’s how:
int i;
int upperBound = 10;
int r_array[100000];
7.13 A single-pass solution 87
int histogram[upperBound];
int r_array_length = sizeof(r_array) / sizeof(r_array[0]);
The tricky thing here is that I am using the loop variable in two different ways.
First, it is an argument to HowMany(), specifying which value I am interested
in. Second, it is an index into the histogram, specifying which location I should
store the result in.
It would be better to make a single pass through the array. For each value in
the array we could find the corresponding counter and increment it. In other
words, we can use the value from the array as an index into the histogram.
Here’s what that looks like:
int upperBound = 10;
int histogram[upperBound] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
7.15 Glossary
array: A named collection of values, where all the values have the same type,
and each value is identified by an index.
element: One of the values in a array. The [] operator selects elements of a
array.
index: An integer variable or value used to indicate an element of a array.
increment: Increase the value of a variable by one. The increment operator in
C is ++.
decrement: Decrease the value of a variable by one. The decrement operator
in C is --.
deterministic: A program that does the same thing every time it is run.
pseudorandom: A sequence of numbers that appear to be random, but which
are actually the product of a deterministic computation.
seed: A value used to initialize a random number sequence. Using the same
seed should yield the same sequence of values.
bottom-up design: A method of program development that starts by writing
small, useful functions and then assembling them into larger solutions.
histogram: A array of integers where each integer counts the number of values
that fall into a certain range.
7.16 Exercises 89
7.16 Exercises
Exercise 7.1
A friend of yours shows you the following method and explains that if number is any
two-digit number, the program will output the number backwards. He claims that if
number is 17, the method will output 71.
Is he right? If not, explain what the program actually does and modify it so that it
does the right thing.
#include <stdio.h>
#include<stdlib.h>
Exercise 7.2 Write a function that takes an array of integers, the length of the
array and an integer named target as arguments.
The function should search through the provided array and should return the first
index where target appears in the array, if it does. If target is not in the array
the function should return an invalid index value to indicate an error condition (e.g.
-1).
Exercise 7.3
One not-very-efficient way to sort the elements of an array is to find the largest element
and swap it with the first element, then find the second-largest element and swap it
with the second, and so on.
This chapter is going to rectify this situation and I can now tell you that strings
in C are stored as an array of characters terminated by the character \0.
By now this explanation should make sense to you and you probably understand
why we had to learn quite a bit about the working of the language before we
could turn our attention towards string variables.
In the previous chapter we have seen that operations on arrays have only mini-
mal support from the C language itself and we had to program extra functions
by ourselves. Fortunately things are a little bit easier when we manipulate these
special types of arrays - called strings. There exist a number of library func-
tions in string.h that make string handling a bit easier than operations on
pure arrays.
Nevertheless string operations in C are still a lot more cumbersome than their
equivalence in other programing languages and can be a potential source of
errors in your programs, if not handled carefully.
The first line creates an string and assigns it the string value "Hello." In
the second line we declare a second string variable. Remember, the combined
declaration and assignment is called initialization.
Initialisation time is the only time you can assign a value to a string directly
(just as with arrays in general). The initialisation parameters are passed in the
form of a string constant enclosed in quotation marks (". . . ").
Notice the difference in syntax for the initialisation of arrays and strings. If you
like you can also initialize the string in the normal array syntax, although this
looks a little odd and is not very convenient to type.
char first[] = {’H’,’e’,’l’,’l’,’o’,’,’,’ ’,’\0’};
There is no need to supply an array size when you are initialising the string
variable at declaration time. The compiler compute the necessary array size to
store the supplied string.
Remember what we said about the nature of a string variable. It is an array
of characters plus a marker that shows where our string ends: the termination
character \0.
Normally you do not have to supply this termination character. The compiler
understands our code and insertes it automatically. However, in the example
above, we treated our string exactly like an array and in this case we have to
insert the termination character ourselves.
When we are using a string variable to store different sting values during the
lifetime of our program we have to declare a size big enough for the largest
sequence of characters that we are going to store. We also have to make our
string variable exactly one character longer than the text we are going to store,
because of the necessary termination character.
We can output strings in the usual way using the printf() function:
printf("%s", first);
a is not the first letter of "banana". Unless you are a computer scientist. For
perverse reasons, computer scientists always start counting from zero. The 0th
letter (“zeroeth”) of "banana" is b. The 1th letter (“oneth”) is a and the 2th
(“twoeth”) letter is n.
If you want the zereoth letter of a string, you have to put zero in the square
brackets:
char letter = fruit[0];
8.4 Length
To find the length of a string (the number of characters this string contains), we
can use the strlen() function. The function is called using the string variable
as an argument:
#include <string.h>
int main(void)
{
int length;
char fruit[] = "banana";
length = strlen(fruit);
return EXIT_SUCCESS;
}
The return value of strlen() in this case is 6. We assign this value to the
integer length for further use.
In order to compile this code, you need to include the header file for the
string.h library. This library provides a number of useful functions for oper-
ations on strings. You should familiarize yourself with these functions because
they can help you to solve your programming problems faster.
To find the last letter of a string, you might be tempted to try something like
int length = strlen(fruit);
char last = fruit[length]; /* WRONG!! */
That won’t work. The reason is that fruit is still an array and there is no
letter at the array index fruit[6] in "banana". Since we started counting at
0, the 6 letters are numbered from 0 to 5. To get the last character, you have
to subtract 1 from length.
int length = strlen(fruit);
char last = fruit[length-1];
8.5 Traversal
A common thing to do with a string is start at the beginning, select each char-
acter in turn, do something to it, and continue until the end. This pattern of
94 Strings and things
int index = 0;
while (index < strlen(fruit))
{
char letter = fruit[index];
printf("%c\n" , letter);
index = index + 1;
}
This loop traverses the string and outputs each letter on a line by itself. Notice
that the condition is index < strlen(fruit), which means that when index
is equal to the length of the string, the condition is false and the body of the
loop is not executed. The last character we access is the one with the index
strlen(fruit)-1.
The name of the loop variable is index. An index is a variable or value used
to specify one member of an ordered set, in this case the set of characters in the
string. The index indicates (hence the name) which one you want. The set has
to be ordered so that each letter has an index and each index refers to a single
character.
If we are looking for a letter in a string, we have to search through the string
and detect the position where this letter occurs in the string. Here is an imple-
mentation of this function:
We have to pass the string as the first argument, the other argument is the
character we are looking for. Our function returns the index of the first occur-
rence of the letter, or -1 if the letter is not contained in the string.
8.7 Pointers and Addresses 95
C is one of the very few high-level programming languages that let you directly
manipulate objects in the computer memory. In order to do this direct manip-
ulation, we need to know the location of the object in memory: it’s address.
Adresses can be stored in variables of a special type. These variables that point
to other objects in memory (such as variables, arrays and strings) are therefore
called pointer variables.
A pointer references the memory location of an object and can be defined like
this:
int *i_p;
This declaration looks similar to our earlier declarations, with one difference:
the asterisk in front of the name. We have given this pointer the type int. The
type specification has nothing to do with the pointer itself, but rather defines
which object this pointer is supposed to reference (in this case an integer).
This allows the compiler to do some type checking on, what would otherwise
be, an anonymous reference.
A pointer all by itself is rather meaningless, we also need an object that this
pointer is referencing:
int number = 5;
int *i_p;
This code-fragment defines an int variable and a pointer. We can use the
"address-of" operator & to assign the memory location or address of our variable
to the pointer.
i_p = &number;
Pointer i_p now references integer variable number. We can verify this using
the "content-of" operator *.
printf("%i\n", *i_p);
This prints 5, which happens to be the content of the memory location at our
pointer reference.
Pointers are widely used in many C programs and we have only touched the
surface of the topic. They can be immensely useful and efficient, however they
can also be a potential source of problems when not used appropriately. For this
reason not many programming languages support direct memory manipulation.
char greeting[15];
strncpy (greeting, "Hello, world!", 13);
copies 13 characters from the of the second argument string to the first argument
string.
This works, but not quite as expected. The strncpy() function copies exactly
13 characters from the second argument string into the first argument string.
And what happens to our string termination character \0?
Attention! In the last two sections we have used the strncpy() and the
strncat() function that require you to explicitly supply the number of charac-
ters that will get copied or attached to the first argument string.
The string.h library also defines the strcpy() and the strcat() functions
that have no explicit bound on the number of characters that are copied.
The usage of these functions is strongly discouraged! Their use has lead to a vast
number of security problems with C programs. Remember, C does not check
array boundaries and will continue copying characters into computer memory
even past the length of the variable.
You have to use the strcmp() function to compare two strings with each other.
The function returns 0 if the two strings are identical, a negative value if the
first string is ’alphabetically less’ than the second (would be listed first in a
dictionary) or a positive value if the second string is ’greater’.
Please notice, this return value is not the standard true/false result, where the
return value 0 is interpreted as ’false’.
Finally, there are two functions that convert letters from one case to the other,
called toupper() and tolower(). Both take a single character as an argument
and return a (possibly converted) character.
8.12 Getting user input 99
/* get input */
100 Strings and things
We need to empty the input buffer, before we can attempt to read the next input
from the user. Since there is no standard way to do this, we will introduce our
own code that reads and empties the buffer using the getchar() function. It
run through a while-loop until there are no more characters left in the buffer
(notice the construction of this loop, where all the operations are executed in
the test condition):
8.13 Glossary
index: A variable or value used to select one of the members of an ordered set,
like a character from a string.
8.14 Exercises
Exercise 8.1 A word is said to be “abecedarian” if the letters in the word appear
in alphabetical order. For example, the following are all 6-letter English abecedarian
words.
Exercise 8.3 A word is said to be a “doubloon” if every letter that appears in the
word appears exactly twice. For example, the following are all the doubloons I found
in my dictionary.
Write a function called IsDoubloon() that returns TRUE if the given word is a doubloon
and FALSE otherwise.
Exercise 8.4
The Captain Crunch decoder ring works by taking each letter in a string and adding
13 to it. For example, ’a’ becomes ’n’ and ’b’ becomes ’o’. The letters “wrap around”
at the end, so ’z’ becomes ’m’.
a. Write a function that takes a String and that returns a new String containing the
encoded version. You should assume that the String contains upper and lower
case letters, and spaces, but no other punctuation. Lower case letters should
be tranformed into other lower case letters; upper into upper. You should not
encode the spaces.
b. Generalize the Captain Crunch method so that instead of adding 13 to the
letters, it adds any given amount. Now you should be able to encode things by
adding 13 and decode them by adding -13. Try it.
Exercise 8.5 In Scrabble each player has a set of tiles with letters on them, and
the object of the game is to use those letters to spell words. The scoring system is
complicated, but as a rough guide longer words are often worth more than shorter
words.
Imagine you are given your set of tiles as a String, like "qijibo" and you are given
another String to test, like "jib". Write a function called TestWord() that takes these
two Strings and returns true if the set of tiles can be used to spell the word. You might
have more than one tile with the same letter, but you can only use each tile once.
Exercise 8.6 In real Scrabble, there are some blank tiles that can be used as wild
cards; that is, a blank tile can be used to represent any letter.
Think of an algorithm for TestWord() that deals with wild cards. Don’t get bogged
down in details of implementation like how to represent wild cards. Just describe the
algorithm, using English, pseudocode, or C.
Chapter 9
Structures
struct definitions appear outside of any function definition, usually at the be-
ginning of the program (after the include statements).
This definition indicates that there are two elements in this structure, named x
and y. These elements are called the members or fields of a structure.
It is a common error to leave off the semi-colon at the end of a structure defini-
tion. It might seem odd to put a semi-colon after curly-brackets, but you’ll get
used to it.
Once you have defined the new structure, you can create variables with that
type:
Point_t blank;
blank.x = 3.0;
blank.y = 4.0;
The first line is a conventional variable declaration: blank has type Point_t.
The next two lines initialize the fields of the structure. The “dot notation” used
here is called the field selection operator and allows to access the structure
fields.
The result of these assignments is shown in the following state diagram:
x: 3
blank
y: 4
As usual, the name of the variable blank appears outside the box and its value
appears inside the box. In this case, that value is a compound object with two
named member variables.
x: 3
PrintPoint() point
y: 4
x: 3
main() blank
y: 4
point.x = point.y;
point.y = temp;
}
This won’t work, because the changes we make in ReflectPoint() will have no
effect on the caller.
Instead, we have to specify that we want to pass the parameter by reference.
Our function now has a struct pointer argument Point_t *ptr.
void ReflectPoint (Point_t *ptr)
{
double temp = ptr->x;
ptr->x = ptr->y;
ptr->y = temp;
}
When we are accessing the struct member variables through a pointer we can
no longer use the "field-selection-operator" (.). Instead we need to use the
"pointing-to" operator (->).
We pass a reference of our struct parameter by adding the "address-of" operator
(&) to the structure variable when we call the function:
PrintPoint (blank);
ReflectPoint (&blank);
PrintPoint (blank);
The output of this program is as expected:
(3.0, 4.0)
(4.0, 3.0)
Here’s how we would draw a stack diagram for this program:
ReflectPoint() ptr
x: 3 4
main() blank
y: 4 3
The parameter ptr is a reference to the structure named blank. The usual
representation for a reference is a dot with an arrow that points to whatever
the reference refers to.
The important thing to see in this diagram is that any changes that
ReflectPoint() makes through ptr will also affect blank.
Passing structures by reference is more versatile than passing by value, because
the callee can modify the structure. It is also faster, because the system does not
108 Structures
have to copy the whole structure. On the other hand, it is less safe, since it is
harder to keep track of what gets modified where. Nevertheless, in C programs,
almost all structures are passed by reference almost all the time. In this book
I will follow that convention.
9.8 Rectangles
Now let’s say that we want to create a structure to represent a rectangle. The
question is, what information do I have to provide in order to specify a rectangle?
To keep things simple let’s assume that the rectangle will be oriented vertically
or horizontally, never at an angle.
There are a few possibilities: I could specify the center of the rectangle (two
coordinates) and its size (width and height), or I could specify one of the corners
and the size, or I could specify two opposing corners.
The most common choice in existing programs is to specify the upper left corner
of the rectangle and the size. To do that in C, we will define a structure that
contains a Point_t and two doubles.
typedef struct
{
Point_t corner;
double width, height;
} Rectangle_t;
Notice that one structure can contain another. In fact, this sort of thing is quite
common. Of course, this means that in order to create a Rectangle_t, we have
to create a Point_t first:
Point_t corner = { 0.0, 0.0 };
Rectangle_t box = { corner, 100.0, 200.0 };
This code creates a new Rectangle_t structure and initializes the member
variables. The figure shows the effect of this assignment.
x: 3
point
y: 4
box
width 100
height 200
Notice, how we had to change the access to the members of the structure,
since box is now a pointer. We would also have to change the function call for
FindCenter():
Point_t center = FindCenter (&box);
9.11 Glossary
structure: A collection of data grouped together and treated as a single object.
member variable: One of the named pieces of data that make up a structure.
9.12 Exercises 111
9.12 Exercises
Exercise 9.1
Section 9.5 defines the function PrintPoint(). The argument of this function is passed
along as a value (call-by-value).
a. Change the definition of this function, so that it only passes a reference of the
structure to the function for printing (call-by-reference).
b. Test the new function with different values and document the results.
Exercise 9.2
Most computer games can capture our interest only when their actions are non-
predictable, otherwise they become boring quickly. Section 7.6 tells us how to generate
random numbers in in C.
Write a simple game, where the computer chooses an arbitrary number in the range
between 1 and 20. You will then be asked to guess the number chosen by the Computer.
To give you a hint the computer should answer your guess in the following way: in
case your guess was lower than the number of the computer, the output should be:
My number is larger!
If you guess was higher than the number of the computer, the output should read:
My number is smaller!
It is necessary to seed the random number generator when you start your program (cf.
Section 7.14. You could use the time() function for this. It returns the actual time
measured in seconds since 1971.
srand(time(NULL)); /*Initialisation of the random number generator*/
When you found the right answer, the computer should congratulate you. The program
should also display the number of tries that where needed in order to guess the number.
The program should also keep the a ‘High-Score’, that gets updated once our number of
trials is lower than any previous try. The High-Score (the number of minimal guesses)
should be stored in a struct, together with the name of the player.
The High-Score() function should ask for your name and store it, when your current
number of tries is lower than the previous High-Score value.
112 Structures
The program then gives you the chance to play again or stop the game by pressing ’q’
on the keyboard.
Appendix A
Coding Style
In these cases, it is to your advantage to be familiar with convention and use it,
since it will make your programs easier for others to understand. At the same
time, it is important to distinguish between (at least) three kinds of rules:
Divine law: This is my phrase to indicate a rule that is true because of some
underlying principle of logic or mathematics, and that is true in any pro-
gramming language (or other formal system). For example, there is no
way to specify the location and size of a bounding box using fewer than
four pieces of information. Another example is that adding integers is
commutative. That’s part of the definition of addition and has nothing to
do with C.
Rules of C: These are the syntactic and semantic rules of C that you cannot
violate, because the resulting program will not compile or run. Some are
arbitrary; for example, the fact that the = symbol represents assignment
and not equality. Others reflect underlying limitations of the compila-
tion or execution process. For example, you have to specify the types of
parameters, but not arguments.
Style and convention: There are a lot of rules that are not enforced by the
compiler, but that are essential for writing programs that are correct, that
you can debug and modify, and that others can read. Examples include
indentation and the placement of squiggly braces, as well as conventions
for naming variables, functions and types.
114 Coding Style
In this section I will briefly summarize the coding style used within this book. It
follows loosely the "Nasa C Style Guide" 1 and its main intent is on readability
rather than saving space or typing effort.
Since C has such a long history of usage, many different coding styles have been
developed and used. It is important that you can read them and follow one
particular scheme in all your code. This makes it much more accessible should
you find yourself in a position where you have to share your work with other
people or have to access code written by your younger self - many years ago...
/*GNU Style*/
if (condition)
{
statement1;
statement2;
}
Indents are always four spaces per level, with the braces halfway between the
outer and inner indent levels.
/*K&R/Kernel Style*/
if (condition) {
statement1;
statement2;
}
This style is named after the programming examples in the book The C Program-
ming Language by Brian W. Kernighan and Dennis Ritchie (the C inventors).
The K&R style is the style that is hardest to read. The opening brace happens
to be at the far right side of the control statement and can be hard to find.
The braces therefore have different indentation levels. Nevertheless, many C
programs use this style. So you should be able to read it.
/*BSD/Allman Style*/
if (condition)
116 Coding Style
{
statement1;
statement2;
}
This style is used for all the examples in this book.
A.4 Layout
Block comments should be used at the top of your file, before all function decla-
rations, to explain the purpose of the program and give additional information.
You should also use a similar documentation style before every relevant function
in your program.
/*
* File: test.c
* Author: Peter Programmer
* Date: May, 29th, 2009
*
* Purpose: to demonstrate good programming
* practise
* /
#include <stdlib.h>
/*
* main function, does not use arguments
*/
ASCII-Table
Dec Hex Oct Character Dec Hex Oct Character
0 0x00 000 NUL 32 0x20 040 SP
1 0x01 001 SOH 33 0x21 041 !
2 0x02 002 STX 34 0x22 042 "’
3 0x03 003 ETX 35 0x23 043 #
4 0x04 004 EOT 36 0x24 044 $
5 0x05 005 ENQ 37 0x25 045 %
6 0x06 006 ACK 38 0x26 046 &
7 0x07 007 BEL 39 0x27 047 ’
8 0x08 010 BS 40 0x28 050 (
9 0x09 011 TAB 41 0x29 051 )
10 0x0A 012 LF 42 0x2A 052 *
11 0x0B 013 VT 43 0x2B 053 +
12 0x0C 014 FF 44 0x2C 054 ,
13 0x0D 015 CR 45 0x2D 055 -
14 0x0E 016 SO 46 0x2E 056 .
15 0x0F 017 SI 47 0x2F 057 /
16 0x10 020 DLE 48 0x30 060 0
17 0x11 021 DC1 49 0x31 061 1
18 0x12 022 DC2 50 0x32 062 2
19 0x13 023 DC3 51 0x33 063 3
20 0x14 024 DC4 52 0x34 064 4
21 0x15 025 NAK 53 0x35 065 5
22 0x16 026 SYN 54 0x36 066 6
23 0x17 027 ETB 55 0x37 067 7
24 0x18 030 CAN 56 0x38 070 8
25 0x19 031 EM 57 0x39 071 9
26 0x1A 032 SUB 58 0x3A 072 :
27 0x1B 033 ESC 59 0x3B 073 ;
28 0x1C 034 FS 60 0x3C 074 "<
29 0x1D 035 GS 61 0x3D 075 =
30 0x1E 036 RS 62 0x3E 076 ">
31 0x1F 037 US 63 0x3F 077 ?
118 ASCII-Table
<ctype.h>, 98 string, 97
<math.h>, 28 comparison operator, 54
<stdio.h>, 28 compile, 2, 9
<stdlib.h>, 82 compile-time error, 4, 51
<string.h>, 91, 93 composition, 20, 22, 28, 53, 109
concatenate, 101
absolute value, 51 conditional, 39, 46
address, 95, 101 alternative, 40
ambiguity, 6 chained, 41, 46
argument, 27, 32, 35 nested, 41, 46
arithmetic constant values, 26
floating-point, 26 constants, 26
integer, 19 counter, 84, 101
array, 88
copying, 79 dead code, 50, 58
element, 78 debugging, 3, 9, 51
length, 81 declaration, 15, 104
array parameters, 84 decrement, 78, 101
arrays, 77 deterministic, 81, 88
assigning diagram
string, 96 stack, 45, 71
assignment, 16, 22, 63 state, 45
distribution, 83
body, 73 division
loop, 65 floating-point, 66
bool, 56, 58 integer, 19
boolean, 54 double (floating-point), 25
bottom-up design, 85 Doyle, Arthur Conan, 5
bug, 3
element, 78, 88
call, 35 encapsulation, 68, 70, 73
call by reference, 84, 106, 110 error, 9
call by value, 84, 106 compile-time, 4, 51
character operator, 20 logic, 4
Chianti, 79 run-time, 4
coding style, 113 expression, 18, 20, 22, 27, 28, 79
comment, 7, 9
comparison fava beans, 79
operator, 39 flag, 55
120 Index
floating-point, 35 natural, 5
floating-point number, 25 programming, 1
for, 80 safe, 4
formal language, 5, 9 length
fruitful function, 34, 49 array, 81
function, 35, 70 string, 93
bool, 56 Linux, 5
definition, 29 literalness, 6
fruitful, 34, 49 local variable, 71, 73
main, 29 logarithm, 66
math, 27 logic error, 4
multiple parameter, 34 logical operator, 55
void, 49 loop, 65, 73, 79
body, 65
generalization, 68, 71, 73 counting, 84
for, 80
header file, 28 infinite, 65, 73
ctype.h, 98 loop variable, 68, 71, 79, 94
math.h, 28 low-level language, 1, 9
stdio.h, 28
stdlib.h, 82 main, 29
string.h, 91, 93 math function, 27
hello world, 7 acos(), 49
high-level language, 1, 9 exp(), 49
histogram, 86–88 fabs(), 51
Holmes, Sherlock, 5 sin(), 49
mean, 83
increment, 78, 101 member variable, 111
incremental development, 51 modulus, 40, 46
index, 79, 88, 94, 101 multiple assignment, 63
infinite loop, 65, 73
infinite recursion, 45, 46 natural language, 5, 9
initialization, 25, 35, 55 nested structure, 42, 55, 109
input newline, 13, 44
flushing the buffer, 100 nondeterministic, 81
keyboard, 99
input buffer operand, 19, 22
flushing the buffer, 100 operator, 18, 22
integer division, 19 character, 20
interpret, 2, 9 comparison, 39, 54
iteration, 64, 73 conditional, 58
decrement, 78
keyword, 18, 22 increment, 78
logical, 55, 58
language modulus, 40
formal, 5 sizeof, 81
high-level, 1 order of operations, 19
low-level, 1 output, 13
Index 121
while statement, 64