You are on page 1of 362

1

2
3
4
The module consists of 15 credits:
• 1.2 credits (12 hours) of lectures in the 1st semester complemented with tutorials and
live programming examples in the sessions of the 2nd semester.
• 5.6 credits (56 hours) of practical lab work. 12 sessions of 3 hours/week in the 1st
semester and 10 sessions of 2 hours/week in the 2nd semester.
• 8.2 credits (82 hours) of individual/private study. You will need to work a lot on your
own. The only way of learning computer programming is by actually doing it!

There are no written exams in this module. The final mark will be based 100% on the
assessment of course work. There are 3 assignments in the 1st semester and 2 more
assignments in the 2nd semester (5 assignments in total). The last assignment is a group
project. The weight of each assignment on the final mark can be found in the module
specification. The deadline for each assignment will be announced when the assignment
is released. Late submission will incur in a penalty on the assignment mark and
therefore on the final mark.

All assignments must be submitted through VITAL before the corresponding deadline
expires. Assignments sent to the instructor’s email address will not be processed. Please
observe that the university applies very strict rules for Collusion and Plagiarism. VITAL is
equipped with a powerful tool for crosschecking similarities with previous VITAL
submissions and documents available online in Internet.

5
6
Lab sessions are an important part of the course as they offer you the opportunity to put
in practice the concepts explained in lectures. Remember: the only way to learn how to
program a computer is by actually doing it!

A lab work booklet is made available at the beginning of the course. This booklet
contains some programming examples designed to help you to understand the main
concepts of the topics explained throughout the course. You will need to enter the
examples included in the booklet, compile and run them in order to familiarise with the
syntax of the C programming language as well as the way a computer program (i.e.,
software) is produced. In addition to the guided examples, the booklet also contains
some exercises and problems for you to solve in order to further improve your
understanding of programming concepts and C syntax.

In your first lab session, chose any free computer, switch it on and log in with your
username and password. The booklet is self-contained and will guide you step-by-step
from here. If you have any particular issues or difficulties while working through the lab
booklet, just ask any of the demonstrators for help. They are well prepared to help you
and will be pleased to assist.

NOTE: If you cannot log in with the username and password you have been given by the
university, please ask for help in the Harold Cohen Library (the building next to the
Department of Electrical Engineering and Electronics). They will issue a temporary
credential for you.

7
VITAL is the University of Liverpool’s Virtual Learning Environment (VLE) – based on the
Blackboard Learn commercial software.

You can login with your student username and password. VITAL will be used as the main
means for exchanging information between the instructor and the students (lecture
notes, lab work booklet, coursework assignments, announcements and other relevant
materials) and vice versa (returning completed coursework for assessment and
feedback).

8
The books by Prata and Hanly & Koffman are two excellent texts designed to help you
learn C programming from the scratch. These books as specifically written as textbooks
for students with little or no previous experience with computer programming. With a
significant number of examples explained step-by-step, these books provide an
accessible introduction to the C programming language. The book by Prata is supported
with additional electronic materials (the code of the examples explained in the book as
well as the solution to selected programming problems) that can be downloaded from
the publisher’s website. Your self-study should be based on these two books mainly.

The book by Kernigham and Ritchie is a classical, well-known book in the literature on
computer programming. The book was central to the development and popularisation of
the C programming language and is still widely read and used today despite being
published more than two decades ago. Because the book was co-authored by the
original designer of the C programming language (Dennis M. Ritchie), and because the
first edition of the book served for many years as the de facto standard for the language,
the book is still regarded by many to be the authoritative reference on C. This book was
not written as a textbook for novel students and assumes from the reader some
previous knowledge of computer programming. The book’s errata can be downloaded
from the link provided in the slide (some recent re-prints have already fixed the errata).

These books are available in the University of Liverpool’s Harold Cohen Library. Just
clicking on the bibliographic reference numbers in the slides or scan the QR codes from
a mobile phone, tablet, PDA or similar electronic devices to access the corresponding
entry in the library’s online catalogue.

9
10
11
12
A computer is a general purpose device that can be programmed to carry out a set of
arithmetic or logical operations. Nowadays, different types of computers are present in
many of the electronic devices we use in our daily life: automobiles, planes, TVs,
microwave ovens, mobile phones and many, many other types of electronic devices.

The elements of a general purpose computer can be classified into two big groups:
hardware and software.

The computer hardware is the equipment (set of physical devices that can be “touched”)
used to perform the necessary computations (for example, the keyboard, the mouse,
the monitor, the “black box”, etc.). The computer hardware, however, is unable to
perform computations for a specific purpose unless a set of instructions are clearly
specified.

The instructions to be followed and executed by the hardware constitutes a computer


program, and the set of computer programs required to solve a particular problem
receives the name of software. Software consists of the programs that enable us to
solve problems with a computer (lists of instructions to perform). The computer
software allows us to use a general purpose machine (i.e., the computer) to solve a
particular, specific-purpose problem.

13
A little about how computers work will help to understand the connection between
writing a program in C and what actually takes place when the program is run.

The hardware of modern computers can be classified into five big groups:

• Central processing unit (CPU) or processor: This element does most of the computing
work. Here is where arithmetic and logical operations take place.
• Random access memory (RAM) or simply memory: The memory of a computer serves
as a workspace to hold programs and files while in use by the CPU. The RAM is
volatile, meaning that it can be used for temporary storage. When we switch off the
computer, the contents of the RAM are lost.
• Permanent memory: Permanent memory is any storage device that can be used for
storing programs and files permanently. This includes, for example, the computer’s
internal hard drive or Solid State Devices (SSDs) in the case of modern computers, but
also other forms of data storage such as external USB memory sticks, memory cards
(SD, MMC, etc.), CDs, DVDs, external hard drives, etc. Permanent storage devices
allow us to keep programs and files between computing sessions (the data will be
there when we switch the computer on again).
• Input devices/peripherals: Devices used to introduce information or data for its
processing by the computer. Some examples include keyboards, mice, microphones,
webcams, etc.
• Output devices/peripherals: Devices used to show to the computer’s user the results
of a particular computing task. For example monitors, printers, speakers,
headphones, etc.

14
The Operating System (OS) is a collection of computer programs in charge of managing
the hardware resources of a computer (CPU, RAM, hard drives, input/output devices,
etc.). The operating system is an essential component of the software system and
controls the interaction between the user’s applications and the computer hardware.
Some examples of operating systems include Windows, UNIX/Linux, Mac OS, FreeBSD,
MS-DOS, VMS... (you may be too young to have heard about some of these examples!).

The application software is the set of application programs developed to assist a


computer user in accomplishing a specific computing task. For example, word processing
applications are used to write letters, reports, books or similar documents. Therefore, a
word processor is an example of an application program developed for a particular
computing task (writing documents). Other examples of application programs include
web browsers (used to navigate through Internet websites), audio and video players
(used to play music and/or videos), etc.

15
Developing new software requires writing lists of instructions for a computer to execute.
• The computer program's role in this technology is essential: without a list of
instructions to follow, the computer is virtually useless.
• Programming languages allow us to write those programs and thus make computers
do something useful (i.e., solve specific-purpose problems).

A computer (and more specifically the processor) understands a very limited number of
simple operations such as e.g. addition, subtraction, multiplication, division, loading a
number in a certain memory location or moving numbers from one memory location to
other, etc. This set of instructions that a computer can understand is called machine
language (or machine code). Computer language instructions are numbers (codes)
associated with the operations to be performed by the processor. A numerical code tells
the processor to add two numbers, another numerical code tells the process to multiply
two numbers, etc. Each operation that a processor can perform has an associated
numerical code (machine instruction).

Machine language/code does have some advantages, mainly its fast execution and its
low memory requirements. Programs written in machine language make use of the
processor’s native language and this allows for very compact (small) programs that can
be executed very fast by the processor.

16
However, in practice, software developers rarely use machine languages because:
• Machine languages are not standardised – depend on the CPU: The set of instructions
for Intel Pentium processors is different from the set of instructions of AMD
processors and Motorola PowerPC processors. This means that a program written in
machine language has to be specific for a particular family of processors (i.e.,
processors with similar characteristics) and cannot be run in any other family of
processors. In practice, this means that solving the same problem with different
computers would require writing an individual program for each computer. Nobody
likes having to solve the same problem several times!
• Machine languages are difficult to use. A CPU can only understand numbers.
Numbers, letters/characters and instructions are all them represented by numbers.
Computer programs ultimately have to be expressed in the corresponding numeric
instruction codes, meaning that writing a computer program in machine language
involves writing a long, long list of numbers! Writing computer programs in this
manner sounds like the last thing you would like to do…
• Machine languages involve a long software development time. A CPU has a very
limited repertoire of instructions (called instruction set) that perform simple
arithmetic and logical operations over pairs of numbers like addition, subtraction,
multiplication, division, comparison, or moving data from a memory location to
another etc. Even a simple computing operation such as printing a text message on
the computer’s screen requires a long list of instructions of machine language.
Writing a complex program in machine language requires a long, long, long time.
Software companies need to develop products in a faster and more efficient way.

17
In practice, computer programs/software are developed using another type of
programming languages referred to as high-level programming languages.

High-level programming languages are made to ease the task of programming


computers to human beings. Instead of using numerical instruction codes to denote
each of the operations that the CPU can perform, high-level languages use keywords to
denote more complex and powerful operations. In high-level languages, programming is
not based on writing ‘numbers’ but ‘keywords’. Keywords used in high-level languages
resemble the words used in human languages (English, in particular) so that high-level
languages are easy to read and write for human beings.

High-level languages are closer to the natural way human beings read and write and are
therefore a more convenient way for humans to write computer programs. However,
computers can only understand machine language, which is their native language. This
means that computer programs written in high-level languages need to be translated
into a set of instruction codes in machine language in order to be run in a computer.
Another reason why such translation is needed is because high-level languages use more
complex instructions than the CPU can follow (for example, print a message in screen,
write a file in a hard disk, etc.). The execution of one instruction in a high-level language
usually involves lots of machine language instructions (this is indeed another reason why
programming is easier this way). Therefore, high-level languages must be translated to
the machine language/code for the CPU to execute the program. The translation is
performed by a program called compiler, with the assistance of another program called
linker.

18
19
20
21
22
23
High-level languages bring many benefits into programmers’ life.

• High-level languages are much easier to use for human beings. We do the high-level
thinking and write a program using a human-understandable language. The compiler
will take care of the tedious details and translate our program to a machine-
understandable language, thus doing the hard work for us.

• High-level languages are independent on the particular computer/processor on which


they are executed. This means that the same program written in a high-level language
can be run (after compilation) in (virtually) any computer. Instead of having to write
an individual program for each targeted processor using difficult-to-use numerical
instruction codes, the programmer can solve the problem (i.e., write the program)
only once using a human-understandable language. The compiler will take care of the
particular details and characteristics of the processor where the program will be run.
Compilers translate our solution to a variety of machine languages (Intel Pentium,
AMD, Motorola PowerPC…). There are C compilers for every computer/processor.

24
When we face a new programming problem, a natural trend is the desire of start writing
code right away. This is a common mistake among novel programmers. The
development of software is a more complex process where writing the code is actually
just one of the steps to be followed (and not the first one!). Developing software in an
effective and efficient way is a more complex process that involves a number of steps,
each of which has to be considered carefully in order to solve successfully and
efficiently. The Software Development Method (SDM) is composed of 6 main steps: 1)
problem specification, 2) analysis, 3) design, 4) implementation, 5) test and verification,
and 6) maintenance and updating.

25
Naturally enough, you should start with a clear idea of what you want the program to
do. This requires stating the problem to be solved and its objectives in a clear and
unambiguous way (in other words, the purpose the program is intended to serve). An
adequate specification of the problem and its objectives will allow you to gain a clear
understanding of what is required for its solution and will also eliminate unimportant
aspects that may lead you to confusion or waste your time.

To achieve this goal, you may find that sometimes you need more information from the
person who posed the problem. The problem you have to solve may have been
described in a rather general and ambiguous way. If that is the case, ask for more details
about what the program is should to do in some specific situations that may have not
been considered in the original definition/specification of the problem. This will allow
you to ensure that your program will meet the expectations and your client will be
happy with your work!

26
Once the problem and its objectives have been specified in a clear and unambiguous
way, the next step is to identify the following aspects:

• Input data: The data or information that your program will take as input in order to be
processed.
• Output data: The data or information that your program will have to produce as a
result.
• Any other additional requirements, constraints or aspects that are relevant to the
problem to be solved.

27
An algorithm is a list of steps that need to be followed for the problem to be solved. Designing the
algorithm is often the most difficult part of the problem-solving process and it is a good idea to spend as
much time as required in order to design an adequate algorithm. Writing a computer program is relatively
simple and fast once you have a clear definition of the underlying algorithm. However, an incorrect
algorithm may lead to loss of many hours of work. You should spend as much time as needed in this stage
of the SDM.

Solving complex problems usually involves a long list of aspects and details to be taken into account. Do
not attempt to solve every detail of the problem at the beginning – this is out of the capabilities of any
human being. Instead, you should try to identify the most relevant steps of the problem to be solved and
consider each of these steps as a sub-problem. Try to divide “the big problem” into “several sub-
problems”, each of which should be easier to solve (divide and rule!). These sub-problems might also be
further divided into smaller sub-problems. Solve each sub-problem/step individually and then put all the
sub-solutions together in order to obtain the final solution. This procedure is referred to as top-down
design approach and is essential to solve complex problems.

For example, the design of an aircraft involves the design of the engines, the aerodynamic elements, the
navigation system, the radio communication system, the auto-pilot system, etc. The design of each of
these elements constitutes a sub-problem, which should be solved individually before being assembled
(along with the other elements) into the final design of the aircraft. Computer programs are designed
following a similar principle.

28
Once the design of the algorithm has been completed, you can start writing the code for
your program. This step of the process is called implementation and involves the
conversion of each step of the algorithm into one or more instructions in a programming
language. If your design is complete and correct, converting the algorithm to code is
relatively simple and fast. Here is where you really have to put your knowledge of the
programming language into practice.

29
The step following the implementation of your program is the testing and verification.
You need to ensure that the program works as expected, serves the purpose for which it
was designed and therefore solves the target problem.

To this end, you will need to compile and run the completed program and verify that it
actually works as desired (i.e., the result/output is correct). Don’t rely on just one test
case. Run the program several times using different sets of data. You will need to run
and test the program under all the possible situations that may arise during its
execution, and if this is not feasible, then you should at least run a sufficiently high
number of tests in order to have some guarantee that it works well. Make sure that it
works correctly for every situation provided in the algorithm.

There may be cases or situations that were not considered and taken into account
during the initial design of the program (algorithm). In those cases, programs may
behave in an unexpected (and usually wrong) way. These errors called bugs in computer
jargon and the process of finding these errors and fixing the problem is called
debugging. The process of debugging a program (i.e., identifying and fixing errors/bugs)
may take as much time as the implementation (or even more!).

30
After completing and releasing our program, we may need to perform additional
modifications in the future for a wide number of reasons. The slide illustrates some
examples.

31
It is worth mentioning that steps 1-3 are independent of the programming language
used to write the program while steps 4-6 depend on the considered program language.

The steps of the SDM follow a “chronological” order. However, the real development of
software may not be as linear as shown in the previous slides. In most cases we actually
need to go back and forth between steps until we finally obtain the desired result. After
testing a program, we may realise that the implementation of the algorithm is incorrect
and therefore we need to do some re-implementation work and test again the new
program. The testing phase may reveal that the program does not work due to an
incorrect design of the algorithm or, even worse, due to an incorrect analysis or
specification of the problem. In such a case, it is necessary to go back in the steps of the
SDM and repeat one or more steps as may times as needed until we obtain the correct
result.

Remember: start writing code right away may work for small, simple programming
problems, but that is not a good programming approach. One might think that the
planning steps are unnecessary and a waste of time, which could be used to write the
computer program otherwise. This is a common mistake in novel programmers.
Experience tell us that neglecting the planning steps may save some time in the short
term, but will actually lead to an unnecessary waste of time in the long term trying to
find out why our program does not work. Don’t neglect the planning steps: “Those who
neglect the planning steps are condemned to hours of lost time, confusion and
frustration.” Be warned!

32
33
The C programming language was created and designed by computer scientist Dennis
Ritchie in Bell Labs. The language derived from other programming languages and was
designed with the purpose of creating and maintaining the UNIX operating system.

The original design of the C programming language dates from the late 60s and early 70s
and its first documented version was published in a book by Ritchie and Kernigham in
1978. For many years, this book (known as K&R) constituted the de facto standard of C
(K&R C). The first official standard version of the language was released by ANSI in 1989,
which is usually known as ANSI C or C89. This version was adopted by ISO in 1990, which
sometimes leads to the use of C90 as a version of the standard. However, C89 and C90
are in practice the same version of the C programming language. The standard was
amended in 1995 (C95) and revised in 1999 (C99). The latest revision of the standard
took place in 2011 (C11).

Most of the aspects of C that we will study in this course were already included in the
ANSI C (C89) version of the standard. The new aspects introduced by C90, C99 and C11
are not covered in this course (these are a few specific aspects that fall out of the scope
of an introductory course).

If you have not realised it yet, the C programming language is more than 40 years old.
One may wonder how it is possible that something that was born more than 4 decades
ago is still so important in the computing world, a world that changes and evolves at an
extremely fast pace. The answer is in the next slide…

34
C is an efficient language. Its design takes advantage of the capabilities of current
computers. C programs are compact (small require a little amount of memory) and
fast (run quickly). In those cases where efficiency is a must, C still constitutes an
adequate candidate.

C is a portable language, which means that C programs written in one system can run on
other systems with little of no modifications. There are C compilers for tens of systems
with the most diverse characteristics. A program written in C can virtually be run in any
computer.

C is a powerful and flexible programming language. C is a versatile programming


language that provides all the means and flexibility that a programmer needs to solve
computing problems effectively and efficiently. Virtually, there is no computing problem
that cannot be solved with a program in C.

35
36
37
38
39
40
41
42
43
44
45
46
This slides shows an example of a computer program written in C. This is indeed one of
the example programs contained in your lab work booklet. The program asks the user to
introduce a distance in miles, converts the distance to kilometres and shows the result
of the conversion in the computer’s screen.

The slide contains many labels pointing out the names of some relevant
elements/entities of a C program (standard header file, preprocessor directive, variable,
constant, comment, reserved word, etc.). At this point you may not understand all of
them, and maybe any of them. Don’t worry, you will at the end of this chapter.

This program has two big parts, highlighted with different colours in the slide. The first
part (blue colour) is a set of lines containing instructions known as preprocessor
directives. The second part (green colour) is the main part of the program, where all the
computations performed by the computer actually take place. This main part contains a
list of instructions for the computer to execute (statements) along with some comments
describing what the program does at every statement.

Let’s start by having a more detailed look at these elements.

47
The first part of a C program is in most cases a set of instructions known as preprocessor
directives. Preprocessor directives are commands that give instructions to the C
preprocessor on how to modify the text of a C program before it is compiled. When we
run a C compiler in order to compile our program, the C compiler automatically runs in
the first place (before compilation actually takes place) another program called
preprocessor. The purpose of the preprocessor is to make some changes to the source
code in order to prepare it for the compiler (preprocessing). Preprocessor directives tell
the C preprocessor what changes should be done to the source code and how they
should be done. Preprocessor directives are easily identifiable because the begin with a
hash/number symbol (#).

There are quite a few preprocessor directives in C and some of them are used in
advanced computer programs. Fortunately, the two most common directives of the C
preprocessor (#include and #define) are very easy to understand and use.

48
The #include directive tells the preprocessor where to find definitions of certain
elements (called identifiers) that are used (but not declared) in the program. C requires
that all identifiers in our program are declared in the code before they are used for the
first time in the program. When we use routines that are part of the C standard library
(e.g., routines for printing a message in the screen), the declarations for such elements
are contained in special files called header files. The compiler needs these declarations
in order to know how to process and compile the code. The #include directive tells the
preprocessor the file where the declarations can be found. The preprocessor will add
these declarations before compilation.

Each library file (containing pre-compiled code for common routines) has a
corresponding header file (containing the declaration of the routines implemented in
the library). For example, the header file stdio.h contains declarations of common
routines that provide support for standard (std) input (i) and output (o) functions such as
printing messages on a computer screen with printf() or introducing data through a
keyboard with scanf(). Such routines are actually implemented in a library file called
stdio.lib. If we want to make use of these routines in our program, we need to add
#include <stdio.h> at the beginning of our program.

Some libraries are part of the C standard and are provided along with any C compiler.
We can also create our own libraries and/or header files and use them in our programs.
If we want to use our own header files then we need to use the syntax #include
“filename.h”. The quotation marks tell the compiler that the header file is not standard
(the compiler will look for the header file in the directory where the source file is).

49
The #define directive instructs the preprocessor that every occurrence of NAME in the
source code has to be replaced with “value”. The preprocessor finds all the occurrences
of NAME in our source code and replaces them with the desired “value”.

Note that the use of #define directives is not essential as we can just write the desired
“value” instead of NAME every time it is needed in the program. Doing this is known as
hardcoding, which is not a good programming approach, in particular when we use
constant numerical values (for example the number π). Just imagine having to write
3.141592653589793 in the source code of your program every time you need to use the
number π. Having to copy and paste this does not seem like a productive way of
working, and if we write the number digit by digit we may miss one or more digits,
thus leading to erroneous values. The #define directive can be very useful in this
example. We just need to include the line #define PI 3.141592653589793 at the
beginning of our program. Every time we need to use the number π in our program
we just write PI in the source code. The preprocessor will replace each occurrence of
PI with the right value (3.141592653589793) before compilation.

Another example: Imagine you use in your program a constant numerical value that
you need to change some time later (for example, you have a program to convert
currencies and the currency conversion rate changes). If the conversion rate were
hardcoded in the source code, you would need to go through the code and change
manually all the lines where you have to use the conversion rate (and cross your
fingers if you don not want to miss or forget any line). With the use of a #define
directive, you just need to change one line of code at the beginning of your program.

50
C programs are composed or one or more modules called functions (this will be
explained in chapter 4). Every C program must have at least one function called “main” –
this is where the program execution begins. Elements of the main function:

• Parameters accepted from the command line. Our program, once compiled, will be an
executable file that can be run from the operating system’s command line. When
running the program from the command line, it is possible to pass some parameters
to the program. The list of parameters that can be passed is declared between the
brackets in the main function. In this example, the keyword void indicates that our
program does not accept any parameters from the command line. There are other
declarations that can be used to receive parameters from the command line.

• Value returned to the operating system. Once the execution of our program finishes,
the control is returned to the operating system, along with a number indicating the
result of the program execution. In this example, the keyword int indicates that our
program returns an integer value (with the keyword return) to the operating system.
As a general rule, zero (0) is passed to indicate normal termination (no problems)
while non-zero values are returned to indicate abnormal termination (e.g., some
errors occurred). This is not essential and we may write a program that does not
return any value by just replacing int with void in the example shown in this slide.

• Function body. The function of the body is enclosed between braces { } and contains
the list of instructions that the computer will execute when our program is run.
Compute instructions are called statements.

51
There are two types of statements: declarations and executable statements.

Declaration statements are used to let the compiler know that we will use certain
elements (e.g., variables, functions, etc.) in our program. Declarations are mandatory in
C and are used to specify the complete list of variables and functions used in our
program.

Executable statements are the instructions/commands we ask the computer to execute.

Statements are executed linearly, from top to bottom, one by one. When the execution
of our program starts, the first statement within the main function is executed. Once the
execution of the first statement finishes, the statement in the next line of the program is
executed, then the next one, etc.

52
Comments are pieces of text that we insert into the program’s source code in order to
explain or document the program (e.g., explain what the program does or clarifications
in certain parts of the code that might be difficult to understand).

Comments are ignored by the compiler, they are just intended for programmers.
Including comments in a program is not essential but it is highly recommended (and a
good programming habit).

Imagine that you are asked to modify a program that you created some months ago
(e.g., to introduce a new functionality). Then you open your source code and find out
that you cannot remember what a certain part of the code was intended for, or why you
did certain operations in that particular way and not in any other one. At this point you
realise you will need to spend some time to study and understand you own program!
Comments are very helpful in these cases and can save you a lot of your valuable time.
Writing comments in a program while you are developing it takes just a few moments of
your time, and can save you a lot of time if you have to come back and introduce
changes in the future. When writing large complex programs, comments are essential.
Comments will help you to remember details of your program and will help other
programmers to understand how your program works.

53
C is quite flexible regarding the format of the comments. Comments can be single line
and multiple line. However, comments cannot be nested.

The C++ single-line comment style was introduce in the revision of the C standard in
1999 (C99). A double slash is used to denote the beginning of the comment. The rest of
the line is assumed by the compiler to be a comment (i.e., it is the end of the line what
determines the end of the comment).

54
When writing a program in C, we need to follow some syntax rules. Syntax rules is
computer programming languages are equivalent to grammar rules in human languages.
If we do not follow the syntax rules, the compiler will not be able to understand (and
translate) our program.

The way C compilers work internally is by identifying certain elements of our source
code that are used to divide the whole code into smaller parts called tokens. A token is a
sequence of one or more characters from our source code that is significant as a group
and is treated by compilers as a separate entity. Each token is processed individually.

Syntax errors prevent compilers from identifying tokens and make compilation fail.

55
There are 6 main groups of tokens (shown in the slide), which are described in more
detail in the following.

56
Reserved words (also known as keywords) are words used to express the programming
language. The set of keywords of a programming language is equivalent to the set of
words (vocabulary) of a human language: it is the set of words we can use to express the
language.

Keywords are defined by the C standard and have a specific, unique well-defined
meaning. As such, keywords cannot be used for any other purposes in a C program (like
for example as the name of a variable or a function).

The slide shows the list of keywords of the C programming language. Boldface is used to
indicate keywords added by the C90 version of the standard while italics indicate new
keywords added by the C99 standard. The C11 version of the standard added further
keywords (_Alignas, _Alignof, _Atomic, _Generic, _Noreturn, _Static_assert,
_Thread_local ), which are not shown in the table.

57
We need to select names for the variables and functions we use in our programs. These
names are called identifiers.

58
In C, we have freedom when it comes to selecting names (identifiers) for the variables
and functions of our program. However, there are some rules and restrictions that we
must follow.

Note: While we can use underscores as the first character of our identifiers, this is not
recommended because the code of C libraries and certain modules of the Operating
System (OS) often use identifiers beginning with one or two underscore characters (so it
is better to avoid that usage yourself in order to avoid confusion and/or conflicts).

59
A variable is a name (identifier) associated with a memory cell whose value can change.

Computer programs take some data as input information, perform some processing on
this information and provide some new data as output information (result). All data
processed by the computer needs to be in the memory (RAM) while in use by the
processor (this ensures a fast and easy access to data). The use of variables provides a
simple way to access the computer’s memory. The programmer does not need to worry
about the particular location where the data are stored (the operating system handles
memory addresses for us). We only need to remember the name of the variable and the
access to memory is then very simple: every time we assign a value to a variable in our
program (e.g., a = 5), the value is stored in certain location of the computer’s memory;
similarly, every time we use the value of a variable in our program, the value is read
from the same location of the computer’s memory.

All variables in a C program must be declared before use.

60
In C, all variables must be declared before they are used for the first time (this is
mandatory). The syntax for declaring a variable is shown in the slide. It is possible to
declare more than one variable of the same data type in one single declaration
statement by separating the variable names with commas. For example, the declaration
statement:

int a, b, c;

is equivalent to

int a;
int b;
int c;

Variables of different data types must be declared in separate declaration statements.

Variable declarations indicate WHAT information will be stored in memory (variable_list)


and HOW the information will be stored in memory (data_type). When a program is run,
a variable declaration statement causes the program to request the operating system to
reserve a certain amount of memory (depending on the data type) to store data and
provide a memory address to access (read and write) in the reserved memory location.
This reservation operation is necessary before the program can use the memory to store
the program’s data.

61
C provides three fundamental data types: characters, integer numbers and real numbers.
For each data type, a keyword is reserved (char, int and float, respectively).

The amount of memory required to store a variable for each type of data varies by
implementation and depends on the compiler used to compile the program and the
target processor (CPU). The standard only specifies the minimum memory size for each
data type, which provides a guarantee of the minimum range of numerical values that
can be stored in the variables.

For example, the C standard specifies that the size of an integer variable (int) must be at
least 2 bytes. With 2 bytes (16 bits) of memory, it is possible to store integer values from
-32,767 to +32,767. However, for most 32-bit computers, an integer is usually stored as
4 bytes rather than the 2-byte minimum storage. In this case, the range of integer values
ranges from -2,147,483,647 to 2,147,483,647.

The C header files limits.h and float.h supply detailed information about the size limits of
integer types and floating types, respectively.

62
A computer processor (CPU) can only understand numbers, meaning that all the
information processed by a CPU needs ultimately to be represented in numerical form.

When we work with characters, the processor is actually working with numbers! This is
possible thanks to an association between characters and numbers, where each
character is represented by means of predefined number. Such association is known as
character code.

There are many character codes. The Morse code is actually a character code. In the
computing world, some character codes include ASCII, EBCDIC, UTF and their variants.
The (probably) most common character code is ASCII (American Standard Code for
Information Interchange)

63
C provides some control over the amount of memory used to store integer numbers. To
this end, the keywords short and long are provided by C. With these keywords, we can
specify 3 additional integer types (in addition to the basic integer type) with different
minimum data sizes.

The actual size of integer types varies by implementation. The standard only requires
size relations between the data types in terms of minimum sizes for each data type:

The relation requirements are that the long long is not smaller than long, which is not
smaller than int, which is not smaller than short.

The C header file limits.h supplies detailed information about the size limits of integer
types.

Note: A short integer x can be declared either as “short int x” or simply “short x” (both
forms are equivalent). The same applies to “long int”/“long” and “long long int”/“long
long”.

64
Two other keywords are provided to further adjust the range of values of an integer
variable, depending on whether the variable will be used to store both negative and
positive integer values, or positive integer values only.

Notice that the basic integer type int (or signed int) requires at least 2 bytes of memory
and can store integer numbers from -32,767 to +32,767. The unsigned version (unsigned
int) requires the same minimum amount of memory but can store numerical values
from 0 to 65,535. If we wanted to store positive values larger than 32,767 with a signed
integer, we would need to use a long int variable, which requires double amount of
memory (4 bytes). Therefore, if we are going to use positive integers only, unsigned
type is more convenient as the range of values we can handle is larger, using the same
amount of memory.

65
C also provides data types for storing real numbers. In addition to the basic real data
type, C also provides double and long double, for larger numerical ranges and/or higher
precision (higher number of significant decimal digits).

The actual size of real data types varies by implementation. The standard only requires
size relations between the data types in terms of minimum sizes for each data type:

The relation requirements are that the long double is not smaller than double, which is
not smaller than float.

The C header file float.h supplies detailed information about the size limits of floating
types.

66
Why have more than one numeric type (int / float)? One may think that one
numerical data type is enough, in particular real numbers, since integer numbers can
also be expressed as real numbers. While this is true, the reason for having a specific
data type for integer values is that operations with integers require less memory and are
faster, and therefore more efficient. On the other hand, integer variables cannot store
the fractional part of a number (i.e., decimal digits). In such case, we must use
float/double variables, which require more memory.

Why have more than one size (short / long, signed / unsigned, float / double)? We
can store higher number in memory (i.e., higher integer number, higher real numbers or
real numbers with more precision) at the expense of using more memory space. Having
different options in terms of memory size allows us to optimise memory usage
according to needs of our program. Choosing the best (most efficient) option is our
responsibility. We should chose for each variable in our program the data type that
requires the minimum amount of memory while being able to store the required range
of numerical values.

For example, for an integer variable x that will take integer values between 0 and 10, it
does not make sense to declare it as “long long int x” (8 bytes) since “short x” (2 bytes)
requires less memory and will work as well. Similarly, for another integer variable x that
will take integer values between 0 and 50,000, the basic integer type (2 bytes) may not
be enough (depending on the computer). However, declaring it as “long int” (4 bytes) is
not the best option since “unsigned int” (2 bytes) requires less memory and will work as
well.

67
Ideally the expressions and operations in your program should involve variables and
constants of just one type (do not mix apples and oranges!). However, in some cases it
may be necessary to mix different data types in the same expression or operation. If you
mix types, be aware that C compilers have rules to make data type conversions
automatically. If such automatic conversion rules are not known or ignored, this may
lead to unexpected (and usually wrong) results.

In Example 1: The results of an arithmetic operation involving integers is another integer.


Therefore, dividing 24/10 (where both 24 and 10 are integer values) gives as a result the
number 2 (an integer number), instead of the correct result (2.4). Therefore, the wrong
result 2 is stored in answer as a float (2.000000).

In Example 2: In an assignment operation, the result (right hand side of the assignment)
is converted to the type of variable being assigned the value (left hand side of the
assignment). The result of dividing 24/10 (where both 24 and 10 are floats in this
example) is another float (e.g., 2.4, which is the correct result), but the correct result 2.4
is converted to an integer value in order to be stored in answer (decimal digits are lost).
Therefore, the result 2.4 is stored in answer as an integer (2).

68
There are conversion rules not only for assignments (as in the example of the previous
slide) but also for other operations (addition, subtraction, multiplication, division…) and
combinations of data types between integers and reals. Some examples are shown in
the slide. However, it is not necessary to memorise all these rules (see next slide).

69
Relying in automatic data conversion rules is not a good programming approach as this
may lead to unexpected and wrong results. A good advise is: do not rely on C automatic
conversion rules. It is possible in C to demand specific type conversions to the compiler
by means of cast operators (this is called typecasting). If you need to mix different data
types in an expression or operation, do use cast operators to make sure that the
conversion will take place in the way you wish.

It is worth noting that cast operators affect only to the numerical value used in a
particular expression or operation, but the original variable where the number is stored.
A cast operator tells the compiler to convert the value of a variable to a different data
type and use that converted value in the expression or operation where it is used.
However, the original value will still be stored in memory according to the original data
type.

70
In some cases we may want to force certain variables to take constant/fixed values (e.g.,
to ensure that a certain value is not modified by mistake). This can be accomplished by
using the const keyword. Any attempt to modify the value of a variable declared with
const will result in error.

Remember (from the beginning of this chapter) that the #define directive can also be
used to include constant values in our programs. While there are some conceptual
differences between both methods, they can be considered to be equivalent from a
practical point of view.

A common programing habit to avoid confusion between variables and constants is to


use CAPITAL LETTERS for the identifiers of constants and lowercase letters for the
identifiers of variables. This allows us to recognise immediately if an identifier refers to a
variable or a constant (without having to find its declaration in the source code).

71
72
73
C includes operators for basic arithmetic operations such as addition, subtraction,
multiplication and division. For example, a / b uses the division operator / to indicate “a
divided by b”.

The modulus (integer remainder) operator returns the remainder of a division operation.
For example, 17 % 5 (read “17 modulo 5”) is 2, since 17 divided by 5 is equal to 3, and
the remainder of this division is 2 (5*3+2=17). This operator may look like an esoteric
tool for mathematicians but it is actually rather practical and useful. NOTE: The modulus
operator only works with integer values.

The unary minus operator is used to indicate negative numbers (e.g., a = -5). The unary
plus operator is its counterpart for positive numbers. It does not have any effect, but C
allows you to use it if you wish (a = 5 and a = +5 are completely equivalent).

74
The increment and decrement operators increase/decrease the value of the operand by
1. Both can be used in prefix and postfix forms.

In prefix form, the operator increases/decreases the value of the operand before using
it in an expression.

In the example q = 2*++a, the value of a is increased by the prefix increment operator ++
before multiplying it by 2 and storing the result in q.
In the example q = 2*--a, the value of a is decreased by the prefix decrement operator --
before multiplying it by 2 and storing the result in q.

If a=2, then:

• The result of q = 2*++a would be q = 6 (i.e., 2*3) and the value of a after executing
this statement would be a = 3 (because it has been incremented by the operator ++).
• The result of q = 2*--a would be q = 2 (i.e., 2*1) and the value of a after executing this
statement would be a = 1 (because it has been decremented by the operator --).

75
In postfix form, the operator increases/decreases the value of the operand after using it
in an expression.

In the example q = 2*a++, the value of a is increased by the prefix increment operator ++
after multiplying it by 2 and storing the result in q.
In the example q = 2*a--, the value of a is decreased by the prefix decrement operator --
after multiplying it by 2 and storing the result in q.

If a=2, then:

• The result of q = 2*a++ would be q = 4 (i.e., 2*2) and the value of a after executing
this statement would be a = 3 (because it has been incremented by the operator ++).
• The result of q = 2*a++ would be q = 4 (i.e., 2*2) and the value of a after executing
this statement would be a = 1 (because it has been decremented by the operator --).

As shown in the examples on this and the previous slide, increment and decrement
operators allow combining two separate statements into a single one. The use of this
operator may seem unnecessary since the same results can be achieved by using two
statements. However, the use of the increment and decrement operators in a program
leads to a more efficient executable file when the program is compiled into machine
language. Therefore, you should use the increment/decrement operators when
applicable.

76
The simple assignment operator = assigns the value (or result of the expression) in the
right hand side to the variable in the left hand side.

Compound assignment operators perform an arithmetic operation between two


variables (a and b) or a variable a and an expression b, and stores the result in the (first)
variable a.

The use of compound assignment operators may seem unnecessary since the same
results can be achieved by using the corresponding forms shown in the right-most
column of the table. However, the use of these operators in a program leads to a more
efficient executable file when the program is compiled into machine language.
Therefore, you should use these operators when applicable.

77
Operators have different priorities when evaluated by the compiler. Some operators are
applied before others. If you are in doubt on how an expression in your program will be
evaluated by the compiler, simply use parentheses. Parentheses (round brackets) have
the highest priorities. Whatever is included between parentheses is evaluated first.
Parentheses can be nested (a + b*(c + d))

78
Some examples of syntax errors include:

• Omitting the colon ; at the end of an statement.

• Omitting the beginning parenthesis ( or ending parenthesis ) in an expression – the


same applies to square brackets [], curly brackets/braces {} and angle
brackets/chevrons <>.

• Omitting commas in the declaration of a list of variables.

• Misspelling an identifier (the name of a variable or a function).

79
Some examples of run-time errors include:

• Dividing by zero: A statement in a program attempts to divide two variables a / b.


However, in a particular case that can happen during execution but was not foreseen
in advance, the variable b takes the zero value before the division. This operation is
not defined and the processor returns an error message, which results in the abortion
of the program execution.

• Accessing inadequate memory locations: A program attempts to access (read or


write) a memory location that does not exist or is out of the memory space that the
operating system has reserved for the program. The operating system reserves a
certain region of the computer’s memory for every program and will not allow that
any program accesses any memory location out of its allocation. If a program tries to
do so, the operating system will abort the execution of that program immediately.
This type of run-time errors often occur due to improper use of arrays or pointers
(these will be explained later on in the course, Chapters 3, 4, 7 and 8).

80
81
82
83
84
85
86
87
88
89
90
91
92
93
The next few slides show an illustrative example on how to perform the steps involved in
the Software Development Method (SDM) explained in Chapter 1. After having
introduced the fundamentals and basic elements of the C programming language in
Chapter 2, this example illustrates the SDM with a simple program that should be
familiar to you (it is one of the sample programs included in you lab work booklet). The
problem selected for this example is quite simple but enough to illustrate the SDM. The
steps to be performed are essentially the same regardless of the size and complexity of
the problem.

94
95
96
97
98
99
The 6th and last step of the SDM is the maintenance and updating of the program after it
has been released.

100
Chapter 2 presented the three basic data types provided by C (characters, integers and
real numbers). In addition to these three basic data types, C also provides other derived
data types: arrays, pointers, structures and unions. Strictly speaking, derived data types
are not real data types but rather some particularly convenient ways of grouping the
already existing basic data types. The concepts behind these names will be clearer in
future chapters as we progress throughout the course. This chapter introduces the
concept of array, which will be extended in Chapters 7 and 8.

101
Sometimes we may need to process in our program a large number of values of the
same type, like for example the grades of the students in a class, the prices of the books
in a bookshop, etc. Using a variable for each value does not seem to be an efficient way
to proceed in this case – it would be much easier to have a kind of unified variable that
handles all the values of interest as a single entity. To this end, C provides the concept of
“arrays”.

102
An array is an ordered sequence of data elements of the same type (either characters,
integers or real numbers) that are stored in a contiguous region of the computer’s
memory, using consecutive memory cells for consecutive elements of the array, and
whose elements can be accessed (read and written) individually just like any
conventional variable.

103
An array can store as many elements as the memory of the computer allows (tens,
hundreds even thousands…). However, we do not need to include in our programs tens,
hundreds or thousands of declaration statements for each element (however, that
would be necessary if we wanted to use individual variables – this is an advantage of
arrays). Instead, a single declaration statement is enough for an array of any size. The
slide shows the syntax of array declarations.

The syntax for the declaration of an array is essentially the same as for a regular
variable, with the addition of the array size, which is specified between square brackets
next to the array name/identifier. Besides this, the declaration of an array follows the
same rules for regular variables (can use the same data types, the name chosen for the
identifier has to follow the same set of rules, etc.).

104
Each array element has an individual and unique index, which is assigned based on its
position within the array. The first element of an array has index 0, the 2nd element has
index 1, the 3rd element has index 2, and so forth. For an array of size N, the last element
has index N-1.

The elements of an array can be accessed individually by means of their corresponding


index. For the example shown in the slide, the 4th element of the array “prices” can be
accessed with the index number 3 prices[3]. An array element can be used as a
regular variable. For example, a value can be assigned to any array element with a
conventional assignment statement (e.g., prices[3] = 1307.84), and the value stored in
an array element can be used in a program just like a regular variable (e.g., a = b +
prices[3], or prices[1] = prices[0] + prices[4]).

The bounds of an array must not be exceeded. Remember that for an array of size N, the
last element has index N-1. There is no element with index N. If you try to access and
array element whose index exceeds the bounds of the array (i.e., index N or greater), the
compiler will not stop you from doing so (the source code will be compiled with no
errors, maybe just a warning if warnings are activated in the compiler). However, you
may obtain unexpected results in your program and in some cases even run-time errors.
It is the programmer’s responsibility to not exceed the bounds of the array!

105
Array elements can be initialised individually after declaration (treating each element
just like a regular variable).

Array elements can also be initialised within the declaration statement. In this case, the
values for each element are specified between curly brackets (braces) and separated by
comas. We may initialise all the elements of the array or just some selected elements.

When initialising the elements of an array within the declaration statement, the size of
the array does not need to be indicated explicitly (it is optional). In such a case, the array
size (i.e., number of elements) is inferred form the size of the list of initial values (e.g., if
17 values are specified between braces, then the size of the array is assumed to be 17
elements).

106
Very often we may want to use text in our program (for example, to show some
message to the user or to ask the user to introduce some particular data). To this end, C
defines the concept of string. A string is just a sequence of one or more characters (for
example, the sentence “Please introduce your name and press the Enter key” is a string).
C does not provide a specific data type for strings. However, strings can be stored using
arrays of type char, where each character of the string is stored as one element of the
array. The end of a string is marked with a NULL character ‘\0’. The NULL character is not
the digit zero; it is the nonprinting character whose ASCII code value (or equivalent) is 0.
Strings in C are always stored with this terminating null character. Notice that an array of
characters that does not end with a null character is not a string in C (it is just an array of
characters). A string is an array of characters ending with a NULL character (this is
important because string functions provided in the C standard library use this character
to identify the end of strings). The presence of the null character means that the array
must have at least one more cell than the number of characters to be stored.

107
Strings can be declared in various ways.

One option is to use the #define directive explained in Chapter 2. Strings declared in this
way are constant and cannot be changed (in this case, they are also called string literals).

Strings can also be declared as an array of char elements. In this case, the size of the
array has to be at least one unit higher that the number of characters to be stored
(because of the NULL character). For example, for the string “hello” (5 characters), the
array size needs to be at least of 6 elements. We may declare char arrays bigger than the
length of the string (as in the example for my_word[40]). In this case, the rest of
elements are not initialised (they contain whatever was written in that region of the
memory by other previous programs).

Remember that simple quotation marks are used to denote characters while double
quotation marks are used to denote strings. Therefore, ‘x’ and “x” are not the same.
While ‘x’ is a character that requires one memory cell, “x” is a string (of one element)
that requires two memory cells (one memory cell for storing the character ‘x’ and
another memory cell for storing the NULL character ‘\0’).

108
The C standard library provides useful string-related functions that can be used for
manipulating strings.

This slide shows as an example the use of the strlen() function, which accepts as input
parameter a string and returns its length (expressed in number of characters). The result
returned by the strlen() function excludes the NULL character (i.e., ‘\0’ is not counted)
and all the empty elements of the char array. For example, for the variable char
my_word[40] = “hello”, the char array “my_word” has a size of 40 elements but only 6 of
them are used (5 elements for storing the characters of the word “hello” and another
element for the NULL character ‘\0’). The strlen() function would return the value 5 as
the length of my_word.

Notice that the sizeof() operator cannot be used (in general) to know the length of a
string, because it counts the NULL character and all empty elements. In the example
above, sizeof(my_word) would return the value 40 (i.e., the number of bytes of the
array, since a character requires one byte of memory for its storage).

109
The C standard library provides many string-related functions, whose prototypes are
declared in string.h. This slide shows a few selected functions (the details are not
explained but can be found in any C reference document such as the textbooks
recommended for this course).

Note: C also provides character-related functions in ctype.h. The ctype.h header file
contains the prototypes for functions that can be used to analyse and handle individual
characters, instead of strings. These functions take a character as an argument and
return nonzero (true) if the character belongs to a particular category and zero (false)
otherwise. For example, the isalpha() function returns a nonzero value if its argument is
a letter. The list of functions can be found in any C reference (for example in Chapter 7,
pp. 229-230 of the book by Stephen Prata, “C Primer Plus”).

110
From a very general point of view, a computer program can be thought as a “black box”
that receives some input information, performs some processing on the input
information and generates new results as output information. The processing of the
information (computing) is mostly done in the computer’s processor (CPU), which takes
the input information from the computer’s memory (RAM) and stores the result of the
computation in the memory (RAM) as well.

Input operations are instructions that copy data from an input device (e.g., keyboard)
into the computer’s memory.

Output operations are instructions that display information stored in the computer’s
memory to an output device (e.g., monitor).

Input/output operations allow users to introduce information to the computer and


obtain the computation results.

111
The C standard library provides many functions for performing I/O operations such as
obtaining data from the user through a keyboard, showing results in the screen, reading
and writing data from files in the computer’s hard disk or other permanent storage
devices, etc.

This chapter introduces the basic (and also most commonly used) functions for I/O.
These functions are declared in stdio.h, which contains the declarations for all the
functions for standard (std) input (i) and output (o).

Standard input functions read data from the standard input device of the computer,
which usually is the keyboard.
Standard output function display data in the standard output device of the computer,
which usually is the monitor/screen.

112
The printf() function is used when information needs to be displayed following a
particular format.

The printf() function returns the number of characters printed. If there is an output
error, printf() returns a negative value. The return value is intended to check for possible
output errors but in practice is rarely used.

113
The format of the information displayed by the printf() function is controlled by means
of conversion specifications. Conversion specifications are composed of the percentage
character (%) followed by a letter. There are conversion specifications for the three basic
data types of c (characters, integers and real numbers), which allow for several
representations (with different formats) for each data type. The printf() function uses
these conversion specifications to know how the programmer wants the data to be
displayed to the user.

114
Conversion specifications can defined to a higher level of detail by means of conversion
specification modifiers. Conversion specification modifiers are characters that, when
introduced between the percentage symbol (%) and letter of the conversion
specification, modify the way the data is displayed to the user (for example, the number
of relevant and/or decimal digits, the plus/minus sign, etc.)

The printf() function accepts a wide range of conversion specification modifiers, whose
description is not covered here but can be found in many C references (e.g., the
textbooks recommended for this course).

115
Escape sequences allow us to print or reproduce sequences that are difficult (or
impossible) to type. For example, double quotation marks “” are used to identify the
beginning and end of a string. If we want to print double quotation marks in the screen
with the printf() function, we need to use the escape sequence \”. Other symbols that
have special meanings also have a corresponding escape sequence for printing (e.g.,
simple quotation marks ‘, inverted slash \, etc.). Escape sequences can be used to obtain
other effects such as a backspace (\b), a tab (\t) or a beep sound (\a).

116
The puts() function is used to display a string literal (string constant) – this operation
does not require any particular formatting.

Notice that, unlike printf(), puts() automatically appends a new line when it displays a
string. With printf(), a new line must be indicated with the scape sequence \n when the
next string is to be printed in a new line.

Any statement with the puts() function can be replaced with an equivalent printf()
statement as illustrated in the slide. The reason for having the puts() function in addition
to the printf() function is that puts() is more efficient than printf() because the library
does not have to parse the format string and the linker does not need to link in to the
final executable file the large block of code that is printf(). This means that the displaying
of string literals should make use of puts() instead of printf(), when appropriate for the
purposes of the program, since this leads to a more efficient program.

117
The putchar() function is used to display a single character.

As in the previous slide, this function can be replaced with an equivalent printf()
statement. However, putchar() is more efficient than printf(). When single characters
need to be displayed, the putchar() function should be used instead of printf() as this
leads to more efficient programs.

118
The scanf() function is used to read information following a particular format.

The scanf() function returns the number of items that it successfully reads. If it reads no
items, which happens if you type a nonnumeric string when it expects a number, scanf()
returns the value 0. It returns EOF when it detects the condition known as “end of file”.
EOF is a special value defined in the stdio.h file. The value returned by scanf() can be
used to detect and handle mismatched inputs (you will learn how after learning about
the “if” and “while” statements).

119
The scanf() function uses pointers to variables (i.e., the memory addresses of the
variables rather than the variables themselves). You don’t need to know anything about
pointers to use the function (pointers will be explained later on in the course, Chapters
4, 7 and 8). Just remember these two simple rules: 1) If you use scanf() to read a value
for one of the basic variable types, then precede the variable name with an ampersand
(&); 2) If you use scanf() to read a string into a character array, then do not use an
ampersand (&).

The scanf() function uses pretty much the same set of conversion-specification
characters as printf() does. The main difference is that printf() uses %f, %e, %,. %g, and
%G for both type float and type double/long double, whereas scanf() uses them just for
type float, requiring the l (lowercase L) modifier for double and L (uppercase L) for long
double.

Like the printf() function, scanf() accepts a wide range of conversion specification
modifiers, whose description is not covered here but can be found in many C references
(e.g., the textbooks recommended for this course).

120
The gets() function is used when a whole string/line needs to be read.

Notice that gets() does not have a totally equivalent scanf() statement. The scanf()
function stops reading input at the whitespaces (blank spaces, tabs or new lines) it
encounters. Therefore, scanf() is more a “read one word” function. In general, scanf() is
used with %s to read only a single word (not a whole phrase) as a string. On the other
hand, the gets() function stops reading input when a return character (‘\n’) is received,
which happens when the user presses the Enter key in the keyboard. Spaces or tabs do
not stop the gets() function. Therefore, gets() is used to handle general strings (including
spaces) as inputs.

121
The getchar() function is used to read a single character.

Notice that getchar() has an equivalent scanf() statement. However, getchar() is more
efficient than scanf() and therefore it should be used when single characters are to be
read.

Strictly speaking, the value returned by getchar() is not of char type. This means that, in
principle, a char variable should not be used to store the result returned by getchar().
Instead, an int variable should be used since the range of values that getchar() returns is
a value in the range of unsigned char plus the single negative value EOF (End Of File). A
char variable does not have sufficient range for this, which can mean that one may
confuse a completely valid character return with EOF. You do not need to worry about
the reasons behind it, but although using a char variable should work in most cases, if
you want to make sure that getchar() works perfectly fine in all cases, the result
returned by getchar() should be stored in an int variable instead of a char variable.

When reading printable ASCII characters, the range of possible values that getchar() may
return is completely covered with a char variable. In that case, a char variable can be
used safely to store the value returned by getchar().

NOTE: EOF is a constant defined in stdio.h. Most systems have a way to simulate an end-
of-file condition from the keyboard, such as Ctrl+Z (or Ctrl+D on most UNIX systems).

122
123
124
Try to use the functions getch() and getche() instead of getchar() and see what happens.

The getchar() function is a buffered function, which means that the characters
introduced through the keyboard are temporarily stored, one after other, in a buffer. The
characters are made available to the program when the user presses the Enter key in the
keyboard. This means that, when using the getchar() function, the user needs to
introduce the character and then press the Enter key.

The getch() and getche() functions are non-buffered functions, which means that the
characters introduced through the key board are made available to the program
immediately. This means that, when using these functions, the user does not need to
press the Enter key – pressing the key for the desired character is enough. The difference
between getch() and getche() is that the former is “unechoed” (there is no echo of the
key pressed by the user, i.e., the character introduced by the user never appears in the
screen) while the latter is “echoed” (there is a echo of the key pressed by the user, i.e.,
the character introduced by the user is shown in the screen). The use of one function or
another is up to the programmer – the most convenient option depends on how the
user interface of the program is designed.

125
126
127
128
129
130
A function is a self-contained unit of program code designed to accomplish a particular
task, such as performing an action (e.g., the printf() function causes data to be printed in
the computer’s screen), providing a result (e.g., the sqrt() function provides the square
root of any positive real number), or both.

Functions are the basic building blocks of a program. Every program must have at least
one function (the main function) and can (and should) have more functions. Instead of
writing the whole program as a single (long) list of statements, we can divide the
program into smaller units (called functions), each of which performs a specific task, and
then write a program in a more compact and understandable way as a sequence of
functions.

Suppose for example that we need to write a program that reads a list of numbers, sorts
the list of numbers, finds the median (middle-point) value and shows the result. This
program can divided into several functions: a function that reads the list of numbers
(“readlist” in the example shown in the slide), a function that sorts the list of numbers
(“sort” in the example) and a function that finds the median (middle-point) value
(“middle” in the example). Obviously we have to write the code for each function (i.e.,
we have to write the code anyway). Thus, you may wonder why use functions. There are
important reasons to use functions when writing a program (see next slide).

131
The use of functions saves you from repetitious programming. If you have to perform a
certain task several times in a program, you do not need to write the corresponding
code several times, you only need to write an appropriate function once and the
program can then use that function wherever needed. Suppose in the example of the
slide that the program needs to sort several lists of numbers. Without functions, the
code for sorting a list of numbers should be written several times in source file.
However, writing once a function sort() that contains the code for sorting a list of
numbers and calling the function sort() wherever needed avoids repetitious
programming.

The same function can also be used in other programs without having to write the
corresponding code again in the new program, thus enabling an easier reuse of code.

132
The use of functions allows specific tasks of the program to be abstracted at a high level.
This means that we can think of a function as a “black box” with input and output
parameters, in terms of the information goes in (its input) and the value or action it
produces (its output) without having to care about the details of the procedure (what
goes inside the box is not your concern, unless you are actually writing the code for the
function). In the example of the slide, we can think of sort() as a black box that takes as
input a unordered list of numbers and produces as output an ordered version of the
same list of numbers (the way this is actually done is not our concern when using the
function).

High level abstraction allows programmers to use functions without having to bear in
mind the internal details of each function. This way, programmers can focus on the
overall design of the program rather than the details. In the example of the slide, the
steps that need to be performed by the program are more clear when using functions
(as shown in the slide) than in the case where the code for each function is written as a
single list of statements (in this case, it would be more difficult to infer the steps of the
program from a single long block of code).

133
Because the use of functions allows programmers to abstract blocks of code at a high
level, this also enables a modular way of working. Each function constitutes an
independent module, so the programmer can work independently in each
function/module until it does its job right. This way of working is of utmost importance
when addressing big/complex programs. Writing programs in a modular way (i.e.,
dividing the code into blocks that perform specific tasks) makes programs easier to read,
test, fix and maintain.

134
The use of functions in a program involves 3 steps:

- Function declaration: Tells the compiler that we are going to use a function in our
program along with some details of the function (in particular, input/output
parameters, if any).

- Function definition: Specifies/defines the block of code associated with the function
(i.e., the code that performs the specific task for which the function is intended).

- Function call: Makes the function’s block of code to be executed/run.

We have to declare and define a function only once in a program. The function can then
be used/called many times (as many as needed in the program).

135
A function declaration tells the compiler that we are using a particular function in our
program. It also specifies properties the function, in particular: the identifier/name
chosen for that function, the list of parameters that the function takes as an input (if
any), and the parameter returned by the function as an output (if any).

Function declarations are also called function prototypes.

Function declarations/prototypes should go before the main() function. Function


declarations/prototypes can alternatively be included within the main() function, just
like any other variable declaration. However, this is not the usual practice. The common
practice is to include function declarations/prototypes before the main() function.

For C library functions (e.g., printf, scanf, etc.),


function declarations/prototypes are included in C standard
header files (.h). In this case, function
declarations/prototypes can be made available to out
program by means of an #include directive.

136
When writing function declarations/prototypes, we only need to specify the data types
for the function’s arguments/parameters. The names/identifiers of the function’s
arguments/parameters are not necessary and, in fact, they are ignored by the compiler
(in the function declaration only, not in the function definition!). Therefore, the function
declaration/prototype “void function(char initial, int age, float salary);” is equivalent to
“void function(char, int, float);” since the identifiers/names “initial”, “age” and “salary”
are ignored by the compiler (in the function declaration only, not in the function
definition!). The names used in the function declaration are dummy names and don’t
have to match the names used in the function definition.

Even though including the identifiers/names of the arguments/parameters in the


function declaration/prototype is not necessary, it is a convenient practice in order to
make the program clear and more readable.

137
The function definition is the block where the code of the function is implemented (i.e.,
the part of the program containing the set of statements that perform the task for which
the function is intended).

Function definitions are composed of two parts:

- Function header: First line of the function definition. Identical to the function
declaration/prototype, except for two aspects: a) there is no semicolon at the end of the
line; b) the identifiers/names of the arguments/parameters are mandatory (remember
that they are optional in the function declaration/prototype).

- Function body: Provides the code (list of statements) of the function, enclosed
between curly brackets (braces) “{ … }”.

138
The definition of a function indicates (in its header) the formal parameters (i.e., what the
function accepts as input). These formal parameters represent variables inside the
function and are assigned values when the function is called (these values are the actual
arguments) and therefore they do not need declaration nor initialisation (they can just
used inside the function like an already declared and initialised variable). However, any
other variables used inside the function need declaration and initialisation. Like variables
defined inside the function, formal parameters are local variables, private to the
function. That means you do not need to worry if the names duplicate variable names
used in other functions. These variables are independent and will be assigned values
each time the function is called.

Note: Formal parameters are variables in the called function and actual arguments are
the particular values assigned to the function variables (i.e., the formal parameters) by
the calling function.

139
Functions can return a single parameter/value, which must match the type specified in
the function declaration (otherwise, an automatic data type conversion occurs). The
output of the function is returned to the calling function with the “return” keyword (this
keyword causes the following expression to be the return value of the function).

Note that return can be used with expressions, not only with variables. Therefore, the
body of the function in the example could be shortened to return x * y;

Some programmers prefer, for clarity or style, to enclose the return value in parentheses
(for example, “return(z);” in the example of the slide), although parentheses are not
required.

Using the keyword return has one other effect: it terminates the function and returns
control to the next statement in the calling function. This occurs even if the return
statement is not the last one in the function. For functions that do not return any value
(i.e., type void functions), the statement “return;” can be used with no return value,
which causes the function to terminate and return control to the calling function with no
return value (because no expression follows return). However, for functions that do not
return any value (i.e., type void functions) the use of the return; statement at the end of
the function is optional – for functions that return a value, the use of a return statement
is necessary (otherwise, a warning will be shown when trying to compile the program, if
warnings are activated in the compiler).

140
A function call is a statement where the function is called/invoked, which causes the
code of the function to be executed/run. The values/variables/expressions used in the
function call are the actual values passed to the function for its execution and they are
called actual arguments. The function call may or may not return a value. If the function
returns a value, this can be assigned to a variable or alternatively the function call can be
included as a part of an expression, for example: result = a/b + multiply(a,b);

When calling a function, the function can be though of as a “black box” whose internal
implementation and details are not our concern (i.e., the details are abstracted at a high
level), but whose input and output are the relevant aspects of the function.

Note: Formal parameters are variables in the called function and actual arguments are
the particular values assigned to the function variables (i.e., the formal parameters) by
the calling function.

141
142
143
First one is copied to the first one, second to second, and so on. Data types must match,
otherwise compiler gives a warning (if warnings are activated in the compiler).
Automatic conversion rules apply (use of type cast operators is recommended in order
to avoid unexpected/undesired results).

144
145
146
147
148
Any function can call any other function. This means that a function can call another
function, which in turn can call another function, and so on, thus leading to nested
function calls. A function can also call itself, thus leading to recursive functions.
Recursive functions can be useful in some cases, for example to compute factorial
numbers.

Recursion should be used carefully since it may be tricky to get recursion to an end,
because a function that calls itself tends to do so indefinitely unless the programming
includes a conditional test to terminate recursion. In many cases, recursive solutions
have an equivalent (and more efficient) equivalent solution with loops (chapter 6).

Header files include function prototypes (declarations) along with declarations of


constant values etc.

For the C standard library, functions are grouped into families, each having its own
header file: These header files contain, among other things, the declarations for the
functions in the family. For example:

math.h contains math function prototypes


stdio.h contains input/output function prototypes
string.h contains string function prototypes

If you write your own header files (.h), you need to provide at compile time the source
files (.c) where the functions are defined.

149
Arguments can be passed to a function following two different (but necessarily
exclusive) methods: by value and by reference.

An argument is passed by value when we call the function using variable names, which
results in the values/contents of the variables being passed to the function. A copy of
the values contained in the variables passed as arguments is made and used inside the
called function. In this case the function works with an individual and independent copy
of the same original value and therefore it cannot change the original value of the
variables in the calling function. This is the argument passing method used in the
example used to illustrate the execution flow when using functions (actual arguments
are copied to formal parameters).

An argument is passed by reference when we call the function using memory addresses
for variables (instead of variable names), which results in the passing of the memory
addresses where the values/contents of variables are stored (instead of the actual
values themselves). The function does not make a copy of the original values; instead,
the function can access the original values through their memory addresses. In this case
the function has access to the original values used in the calling function and therefore it
can change the original value of the variables in the calling function. This argument
passing method can be used to make a function return more than one value (i.e., we
pass the address of two or more variables, the function makes changes on them and the
variables are updated when the function execution finishes, which is equivalent to the
function returning two or more results).

150
Memory addresses can be used in a C program by means of a particular data type:
pointers. Next slides provide an introduction to pointers, which will be explained in more
detail in Chapters 7 and 8.

151
Each variable used in a program needs to be stored in the memory of the computer. The
region of the memory where each variable is stored can be identified by means of a
unique number called memory address. A memory address is an identifier for a location
in the computer’s RAM. Memory addresses enable a computer to access (read and
write) the contents of the variables used in our program.

So far we have not been concerned with the memory addresses of the variables used in
a program (only the contents/values of the variables). However, in a C program we can
handle not only the variables’ contents but also their addresses. This is accomplished by
means of pointers.

A pointer is a variable used to store a memory address (i.e., a pointer is a variable whose
value is a memory address). Internally, a pointer is stored as an unsigned integer.
However, pointers cannot be used in a program as we use integers (for example, we
cannot multiply two integers – that would not make sense indeed!). Pointers are
identified by the data type stored in the memory address they point to. Thus, we can
have pointers to integer values, pointers to float values or pointers to characters, and
their variations (signed/unsigned, short/long/long long, double/long double).

152
The way pointers are declared is very similar to the way regular variables are declared.
The only difference is the use of an asterisk, which is written right in front of the
pointer’s name/identifier. That asterisk indicates the variable itself is a pointer. Notice
that we need not only to indicate that a variable is a pointer (which is done with *), but
also specify the kind of variable to which the pointer points (char, int, float, etc.). The
reason is that different variables take up different amounts of storage in the memory of
the computer and some pointer operations require knowledge of that storage size. In
the examples shown in the slide, “pAge” is a pointer to an integer value while “pHeight”
and “pWidth” are pointers to float values.

153
The use of pointers involves two operators: the indirection/dereferencing operator (*)
and the address operator (&).

The indirection operator * can be used to find the content/value stored at the address
pointed by a pointer. In the example of the slide, a pointer to an integer is declared as
“int *pAge;” In this example, “pAge” is the pointer that stores the memory address,
while “*pAge” (with the indirection operator *) provides the value stored at that address
(which is an integer value). Therefore, if “pAge” points to an integer, then “*pAge” is the
integer value pointed at by pAge (the same applies to other data types).

The address operator & can be used to find the address of a variable. In the example of
the slide, an integer variable is declared as “int age;” In this example, “age” is a variable
that contains an integer value, which can be accessed by just using the name of the
variable, and “&age” (with the address operator &) provides the address where that
variable/integer value is stored. Therefore, if “age” is a variable, then “&age” is the
address where the variable is stored.

NOTE: Do not confuse the indirection/dereferencing operator * with the binary operator
* used to denote multiplication (they both are represented by an asterisk, but have very
different meanings).

154
Pointers have many uses in C. Regarding functions, pointers can be used as function
arguments (i.e., a function can be called passing the address of some variables instead
of just the value). Passing pointers as arguments can be interesting when we want the
called function to have access to the variables used in the calling function. This is useful
when we need more than one “return value” By passing the memory addresses, the
function can modify as many variables as needed and these changes will remain after
the function execution, thus leading to the same result as having several (more than
one) return value.

To better understand this, lets consider the example of a function that interchanges the
values of two variables. This function should accept two input variables (A and B) and
return the same variables with their values interchanged (i.e., A should store the value
previously stored in B, while B should store the value previously stored in A). Notice that
this function needs to provide more than one return value and therefore we cannot do
that with a return statement. However, we can accomplish this with pointers (passing
arguments by reference).

155
By passing arguments by reference (i.e., memory addresses) instead of by value (i.e., just
the values/contents of the variables), we allow the called function to have access to the
variables used in the calling function (though their memory addresses). The arguments
passed by reference can then be modified by the called function and any changes
performed in the variables will remain after the execution of the function is finished.

156
157
158
159
160
Do not worry about the implementation of the function starbar(). You will understand
this code after explaining loops in Chapter 6.

161
162
163
164
165
A computer program is a list of instructions/commands for a computer to execute/run.
The order in which individual statements are executed is what is called the program’s
flow. A programming language should provide three forms of flow:

Sequential execution: In a sequential execution flow the instructions are executed


sequentially. The program begins with the execution of the first instruction in the
program, when the execution of the first instruction finishes then the second instruction
is executed next, and so on. This is the natural form of executing a list of commands.
Unless a specific flow control statement is included in the program, instructions are
executed sequentially.

Conditional execution: Sometimes the task to be performed by a program depends on a


certain condition, leading to the conditional execution of instructions. In a conditional
execution flow, two or more (mutually exclusive) sequences of execution are possible.
The program has to chose one of them, which is done based on the result of evaluating
a test/condition. This type of execution flow is also called branching (the program
chooses one “branch”) and is covered in this chapter.

Iterative execution: Sometimes a program needs to perform a set of operations several


times. A set of instructions is said to be executed iteratively when they are repeated a
certain number of times or until/while a specific condition is met. This type of execution
flow is also called looping and the set of instructions repeated is called a loop. Each
repetition of the set of instructions (loop) is called iteration. This type of execution will
be covered in Chapter 6.

166
C defines several types of conditional statements in order to enable conditional
execution. A conditional statement is a statement where a test is performed in order to
decide between alternative sequences (branches) of execution for the program. The test
involves evaluating an expression and then, based on the result of such evaluation, a
decision on the sequence to be executed is made.

Decisions are made based on the value/result produced after the evaluation of an
expression. An expression in a programming language is a combination of explicit values,
constants, variables, operators and/or functions that are interpreted according to the
particular rules of precedence and of association for a particular programming language,
which computes and then produces another value. An expression in C always has a
value. The process of determining the value corresponding to an expression, like for
mathematical expressions, is called evaluation. For example, a + b*(c-2) is an arithmetic
expression that, when evaluated, produces a single numerical value that depends on the
values of the involved variables (a, b and c in this example).

Expressions can be categorised into two main groups: arithmetic and Boolean.
Arithmetic expressions are expressions that produce a numerical value (integer or real),
and may involve arithmetic operators. A second important group of expressions is called
Boolean expressions. Boolean expressions produce a Boolean value, which can be either
true/yes or false/no (as we will see, Boolean values are actually handled internally as
numerical values with some particular considerations). Boolean expressions may involve
two new types of operators: relational operators and logical operators.

167
Test decisions in conditional statements are based on Boolean expressions, which
evaluate to either true or false. This true/false result can be used to choose between
two possible sequences of execution in a program. This type of expression is named
Boolean after George Boole, an English mathematician who developed a system of
algebra to represent and solve problems in logic. In programming, variables representing
true or false have come to be known as Boolean variables. Traditionally, Boolean
variables have been represented with numerical integer values under the convention
that a numerical zero value represents the Boolean “false” value, while a numerical non-
zero value (both positive and negative) represents the Boolean “true” value. Therefore,
the test expression “x != 0” is equivalent to “x”, because both expressions become 0, or
false, only when x has the value 0. The form “x != 0” probably is clearer to those just
learning the C programming language, but the second form is the idiom most often used
by C programmers. You should try to become sufficiently familiar with the second form.

The C99 version of the standard introduced a new reserved word (keyword) to denote
Boolean variables: _Bool. A _Bool variable can only have a value of 1 (true) or 0 (false). If
a non-zero numeric value is assigned to a _Bool variable, the variable automatically
takes the value of 1, reflecting the fact that C considers any non-zero value to be true.
Notice that _Bool is not really a new data type since _Bool variables are internally
handled as integers (with the restriction that they can only take the values 0 or 1).

Notice that Microsoft’s C compiler (the one included in Visual C++) does not support the
C99 version of the C standard (it is a C89 compiler) so you will not be able to use _Bool
variables in your program. However, you can always use integer variables.

168
Boolean expressions may involve two new types of operators (and in most cases they
do): relational operators and logical operators.

169
Relational/comparison operators are used to compare expressions. They can be used
with any basic data type, not only with numerical data types (int/float), but also with
characters. When used with floating-point numbers you should limit yourself to using
the “greater than” (>) and “less than” (<) operators. The reason is that round-off errors
can prevent two numbers from being equal, even though logically they should be. For
example, the product of 3 and 1/3 is 1.0. However, if you express 1/3 as a six-place
decimal fraction, the product in the computer is 0.999999 as a result of round-off errors.
If you compare the equality of 1.0 and 0.999999 with the “equal to” operator (==), the
result will be false even though it should obviously be true. For this reason, it is not
advisable to use any operator that involves the possibility of being equal (>=, =<, == and
!=) with floating-point numbers. When comparing characters, the numerical ASCII code
is used for comparison. Comparing characters can be useful to determine if two
characters are the same (==) or not (!=), or to determine if a character precedes another
one in alphabetical order (letters are ordered alphabetically in the ASCII code). For
example, ‘a’ < ‘b’ is true because the ASCII character code for ‘a’ is less than ‘b’ (the ‘a’
character comes first in the ASCII code table).

Relational/comparison operators cannot be used with derived data types such arrays
(strings), pointers, structures or unions. They can be used with basic data types only.

Be aware that == is the “equal to” operator while = is the assignment operator! (see
chapter 2). The former (==) checks whether the expressions on the left and right sides
are equal, while the latter (=) assigns the value of the expression on the right to the
variable on the left. The compiler will let you use the wrong form - be careful with this!

170
The precedence of relational operators is less than that of the arithmetic operators and
greater than that of assignment operators. This means that the operands of arithmetic
operators are processed before the operands of relational operators, and the operands
of relational operators are processed before the operands of assignment operators.

The slide illustrates some examples of how expressions involving different types of
operators are evaluated based on the precedence rules of C.

171
Logical operators are used to combine two or more relational expressions. This can be
useful for example when it is necessary to check whether two or more conditions are
met simultaneously (logical AND operator), at least one of them is met (logical OR
operator) or a condition is not met (logical NOT operator).

Logical operators are named logical AND, logical OR and logical NOT to distinguish them
from the bitwise operators: bitwise AND, bitwise OR and bitwise NOT (the bitwise
operators are not covered in this course).

The logical AND and logical OR operators can be applied to any number of operands (not
only two). When applying these operators to many operands (more than two) it is easier
to think of the operators in this way:
- Logical AND operation: True if ALL the operands (all of them) are true (false
otherwise).
- Logical OR operation: True if ANY of the operands (at least one) are true (false
otherwise).

The logical NOT operator can be applied to a single operator only.

172
Logical operators are used to combine two or more relational expressions. This can be
useful for example when it is necessary to check whether two or more conditions are
met simultaneously (logical AND operator), at least one of them is met (logical OR
operator) or a condition is not met (logical NOT operator).

The logical operators are written using the symbols: &, | and !. Most keyboards have
these symbols. However, the keyboards used in some countries do not have some of
these symbols, which means that programmers of these countries cannot write
programs in C easily using their native keyboards. To solve this problem, the C99 revision
of the standard introduced alternate spellings based on letters only. In particular, C99
introduced the use of “and” for the logical AND operator (&&), “or” for the logical OR
operator (||), and “not” for the logical NOT operator (!). These alternate spellings are
defined in the iso656.h header file and can be used in any C program provided that the
appropriate #include directive is included.

173
The && operator has higher precedence than ||, and both rank below the relational
operators and above the assignment operators in precedence.

The ! operator has a very high precedence – higher than multiplication and division, the
same as the increment/decrement operators, and just below that of parentheses (recall
that parentheses have the highest precedence).

The slide illustrates an example of how an expression involving different types of


operators is evaluated based on the precedence rules of C.

174
Try to infer by yourself the Boolean value produced by these test expressions. If you
have doubts, you may find useful to write a short program with these lines and print the
values of each part involved in the whole test expressions in order to see individually the
values taken by each part. You can print the values as integer values – just remember
that zero is equivalent to false and non-zero (both positive and negative) is equivalent to
true.

175
The “if” statement is used to decide between executing or not a statement. It is also
called a branching statement or selection statement because it provides a junction
where the program has to select which of two paths to follow. The syntax of the general
form is shown in the slide. It the Boolean expression “test” evaluates to true (i.e., non-
zero value), then “statement B” is executed. Otherwise, it is skipped.

Normally, the test expression is based on relational and/or logical expressions and
operators. For example, it compares the magnitude of two quantities, as in the
relational expressions x>y and c==6, or sometimes it compares more than two
quantities simultaneously by means of a logical expression that combines relational
expressions, as in the expression x>y && c==6. If the expression is true (x is greater than
y, c is equal to 6, x is greater than y and c is equal to 6, respectively), then the statement
is executed. Otherwise, it is skipped/ignored.

If the “if” statement were not used, then the program would follow a sequential
execution (i.e., statement A statement B statement C). Thanks to the conditional
statement “if”, we have the possibility to introduce the conditional execution of
statement B depending on the result/value of the test expression.

176
The previous slide showed the use of the “if” conditional statement to decide the
conditional execution of a single statement. However, the “if” conditional statement
can also be used to decide the conditional execution of a group of statements, which is
called compound statement. A compound statement is a group of (two or more)
statements, enclosed between braces, that is executed as a group. The syntax of the
general form is shown in the slide. It the “test” evaluates to true (i.e., non-zero value),
then the compound statement/group of statements B1, B2, …, BN is executed (all of
them) in a sequential order. Otherwise, the whole group is skipped.

177
When using “if” conditional statements, the use of braces to distinguish between single
and compound statements is important.

If no braces are used, the “if” statement is applied to decide the conditional execution
of the very next statement only. When we want to decide the conditional execution of a
block of two or more statements, then we need to use braces in order to let the “if”
statement know that the conditional execution applies to the whole block of
statements.

As shown in the example of the slide, forgetting to write the braces leads to very
different execution flows. Note that the use of indentation is ignored by the compiler
(indentation is used only for better readability of the code for programmers).

178
The simple form of an “if” statement gives you the choice of executing a statement
(single or compound) or skipping it. C also enables you to choose between two
statements by using the “if-else” form. The general syntax is shown in the slide. If the
test expression is true, then the single/compound statement B is executed. However, if
the test expression is false, then the single/compound statement C is executed instead.
Notice that compound statements require the use of braces in order to clearly indicate
to the compiler which is the group of statements that belongs the block/compound
statements. When using single statements, the use of braces is optional. In general,
braces are not used when we have single statements.

C does not require the use of indentation, but it is the standard style and it is also very
convenient for readability reasons since using indentation in “if” and “if-else”
statements shows at a glance the statements that depend on a test for execution.

The “if” statement enables you to choose whether to do one action. The “if-else”
statement enables you to choose between two (mutually exclusive) actions.

179
Sometimes we may need to choose between more than just two choices. When this is
the case, we can make use of the flexibility provided by the C programming language,
which allows us to nest “if-else” statements. A nested “if-else” statement is simply an
“if-else” statement inside another one, as shown in the example of the slide. For the
outer “if-else” statement, the “else” part is another (inner) “if-else” statement. The
second “if-else” statement is said to be nested inside the first one.

In this example, the test expression “test1” is evaluated. If it “test1” is true, then
statement B is executed and then the execution continues with statement E. However, if
“test1” is false, then another test expression (“test2”) is evaluated. If “test2” is true, the
program executes statement C and continues with statement E. However, if “test2” is
false, then the program executes statement D and continues with statement E.

We can nest as many “if-else” statements as needed (within compiler limits, of course),
which enables us to write flexible programs that can decide between multiple options of
execution (branches).

When implementing algorithms with multiple choices, the most difficult part is the
design of the algorithm itself. In this stage, using flow charts/diagrams as the one shown
in the slide can be very helpful. Once we have designed the algorithm, with all the
possible tests and executions alternatives, and with its corresponding flow
chart/diagram, the implementation is straightforward (i.e., converting a flow
chart/diagram as the one shown in the slide to a sequence of nested “if-else”
statements is easy).

180
We can nest together as many “if-else” statements as needed (within compiler limits, of
course). In terms of compiler limits, the C99 standard requires C compilers to support a
minimum of 127 levels of nesting, which should be more than enough for any practical
problem.

When we write many nested “if-else” statements, it may be difficult on a first sight to
determine which “else” statements are paired with which “if” statements. This can be
solved with a simple rule:

An “else” goes with the most recent “if”, unless braces indicate otherwise.

The examples shown in the slide illustrate this rule.

We can help human readers of our program match “else” and “if” pairs (by just visual
inspection of the code) if we use appropriate indentation levels. However, if a program is
written with a wrong/incorrect indentation, then the visual inspection of the code can
be very confusing and lead to an incorrect interpretation. For this reason, it is very
important to use appropriate indentation when writing code – it makes the program
more readable. Most programming editors (e.g., text editors designed to write source
code rather than just plain text files, as well as text editors embedded in IDEs) indent the
source code automatically. Remember that compilers ignore blank spaces, blank lines
and therefore indentation. Indentation is intended to help programmers in reading and
understanding a program.

181
C offers a shorthand way to express one form of the “if-else” statement, which is called
conditional expression, and uses the ?: operator, which is called conditional operator
(sometimes ternary operator). This is a two-part operator that has three operands. The
syntax is illustrated in the slide. The test expression expression1 is evaluated. If
expression1 is true (non-zero), then the whole conditional expression has the same
value as expression2. However, if expression1 is false (zero), then the whole conditional
expression has the same value as expression3.

The examples shown in the slide illustrate the use of this operator to compute the
absolute value of a number and the maximum value of two numbers. The absolute value
of a number is the same number if it is positive, or the number with opposite sign if it is
negative. This can be computed with the conditional operator. We first test the condition
x < 0. If it is true (the number is negative), then the whole conditional expression takes
the value of the expression after the ? symbol, meaning that the value –x is assigned to
the variable abs_val. However, if the test x<0 is false (the number is zero or positive), the
whole conditional expression takes the value of the expression right after the : symbol,
meaning that the value x is assigned to the variable abs_val. The same operator can also
be employed to return the maximum value of a pair: we first test a > b, if true (a > b) the
conditional expression returns a (the maximum), if false (a < b) it returns b (the
maximum).

Any statement making use of the conditional ?: operator can be replaced with an
equivalent “if-else” statement. However, the conditional operator leads to a more
compact source code (1 line with “?:” vs. 3 lines with “if-else”).

182
Try to infer by yourself the output produced by this program for the different
combinations of values of variables a and b. If you have doubts, you may find useful to
write a short program with these lines and run it with different values for a and b.

183
We can choose between two alternatives with the “if-else” statement and we can
choose between more than two alternatives with nested “if-else” statements. However,
when we have to choose between two or more alternatives, it may be more convenient
in many cases to use the C “switch” statement.

The syntax of the “switch” statement is shown in the slide. The expression in the
parentheses following the keyword “switch” is evaluated. Then the program scans the
list of labels until it finds one matching that value. The program then jumps to that line
and executes the list of statements after the matched label. If there is no matched label
but there is a line labelled as “default:”, the program jumps there. However, if there is no
matched label and there is no “default:” label, then the program proceeds to the
statement following the “switch”. Once the program has jumped to a matching label (if
any), the list of statements after the label is executed until a “break” statement is found.
The “break” statement causes the program to break out of the “switch” and skip to the
next statement after the “switch”. Without the “break” statement, every statement from
the matched label to the end of the “switch” would be processed.

184
The “switch” test expression should be one with an integer value (including type char
since characters are associated with integer ASCII codes).

The case labels must be integer-type constants or integer constant expressions (i.e.,
expressions containing only integer constants). Character-type constants are also
allowed. Variables cannot be used for case labels. The default: label is optional. If there
is no match with any label and there is no default: label, then the program will just
proceed to the statement following the “switch”.

Statements can be single statements or multiple statements. When writing multiple


statements, they do not need to be written as a compound statement (i.e., enclosed
between braces { }) as it is the case when using “if-else” statements. It is also possible to
have a label with no statements at all – in this case, we may have two or more
consecutive labels in the “switch”, which is considered as multiple labels. Any of the
values of the integer expression matching any of the labels in a multiple label will cause
the program to jump to the same set of statements.

The “break” statement makes the program exit the “switch” block. Notice that the last
“break” statement is not really needed, because in its absence the program flow goes to
the next statement of “switch” anyway, which is the statement in the “default:” label (if
there is a “default:” label) or the next statement after the “switch”. The last “break”
statement of a “switch” statement could be dropped. However, this is not an advisable
programming habit – just think that if other cases might be added later, having the
“break” already in place protects you from forgetting to add one.

185
Comparing this slide (where all the “break” statements have been removed) with the
previous one (where “break” statements are present), illustrates the effect of the
“break” statements. Notice that in the absence of “break” statements, the execution
flows continues top-to-bottom. When the execution of the statements in the current
label finishes, the program continues with the statements of the next label until the end
of the “switch” statement (or a break statement) is reached.

186
We may have two or more consecutive (multiple) labels in a “switch” statement. Any of
the values of the integer expression matching any of the labels in a multiple label will
cause the program to jump to the same set of statements (i.e., those statements written
after the multiple label). This slide illustrates with an example the use of multiple labels
to detect the letter entered by the user (regardless of whether the user entered a
lowercase letter or an uppercase letter). In this example, multiple labels are composed
of two labels each, but in practice multiple labels can be composed of any number of
labels.

187
Any “switch” statement can be rewritten in terms of nested “if-else” statements as
shown in the example of the slide (the opposite, in general, is not true). For example, an
“if-else” statement can be used to evaluate float variables/test expressions and to test
the range of a variable, which cannot be done with a “switch” statement. A “switch”
statement cannot be used to perform general relational and/or logical checks since the
implicit test condition in a “switch” statement is equality. However, when applicable, the
use of the “switch” statement leads to a clearer and more readable code.

188
“if” statement is used to decide between EXECUTING OR NOT a single/compound
statement.
“if-else” statement is used to decide between TWO single/compound statements.
Nested “if-else” statements and “switch” statement are used to decided between MORE
THAN TWO single/compound statements.

189
190
191
192
193
A computer program is a list of instructions/commands for a computer to execute/run.
The order in which individual statements are executed is what is called the program’s
flow. A programming language should provide three forms of flow:

Sequential execution: In a sequential execution flow the instructions are executed


sequentially. The program begins with the execution of the first instruction in the
program, when the execution of the first instruction finishes then the second instruction
is executed next, and so on. This is the natural form of executing a list of commands.
Unless a specific flow control statement is included in the program, instructions are
executed sequentially.

Conditional execution: Sometimes the task to be performed by a program depends on a


certain condition, leading to the conditional execution of instructions. In a conditional
execution flow, two or more (mutually exclusive) sequences of execution are possible.
The program has to chose one of them, which is done based on the result of evaluating
a test/condition. This type of execution flow is also called branching (the program
chooses one “branch”) and was covered in chapter 5.

Iterative execution: Sometimes a program needs to perform a set of operations several


times. A set of instructions is said to be executed iteratively when they are repeated a
certain number of times or until/while a specific condition is met. This type of execution
flow is also called looping and the set of instructions repeated is called a loop. Each
repetition of the set of instructions (loop) is called iteration. This type of execution is
covered in this chapter.

194
Sometimes a program needs to perform a set of operations several times. This is
accomplished by means of iterative statements. An iterative statement indicates that
another single/compound statement needs to executed/repeated several times. A set of
instructions is said to be executed iteratively when they are repeated a certain number
of times or until/while a specific condition is met. This type of execution flow is also
called looping and the set of instructions repeated is called a loop. Each repetition of the
set of instructions (loop) is called iteration.

The control of iterative statements may involve arithmetic and/or Boolean expressions
and the assignment, arithmetic, relational/comparison and logical operators are used
very often with iterative statements.

195
Loops can be classified based on two main aspects:

- Number of iterations. Depending on whether the number of times a loop is


repeated/iterated can be known beforehand, loops can be classified as:
- Definite (counting) loop: The number of times the loop is repeated is KNOWN
before the iterative execution starts.
- Indefinite loop: The number of times the loop is repeated is UNKNOWN
before the iterative execution starts.
- Execution condition: Loops are executed while a certain condition is met. Depending
on when the execution condition is evaluated, loops can be classified as:
- Entry-condition loops: The execution condition is evaluated BEFORE each
iteration. If the condition is met (true), then one more iteration is executed,
and the test/iteration process is repeated until the condition is not met
anymore (false). This type of loops may not be executed at all if the execution
condition is already false before the first iteration can be executed.
- Exit-condition loops: The execution condition is evaluated AFTER each
iteration. An iteration is executed first and then the execution condition is
verified. This type of loops is always executed at least once (one iteration) –
the execution of next iterations depends on the result of the execution
condition.

C provides three types of iterative statements in order to enable iterative execution: for,
while and do-while. These statements can be classified as shown in the table.

196
The syntax for the “for” iterative statement is shown in the slide. The parentheses
following the keyword “for” contain three elements separated by two semicolons:

- The first element (“init”) is the initialisation, which is a single statement that is
executed just once, when the “for” loop first starts and before any of the loop
statements are executed.

- The second element (“test”) is a Boolean expression (i.e., an expression that


evaluates to either true or false) representing the test condition, which determines
the execution of a new iteration of the loop. The “test” condition is evaluated BEFORE
each potential execution (iteration) of the loop. If the “test” condition is true, a new
iteration of the loop is executed. More iterations will be executed while the “test”
condition is true. However, as soon as the “test” condition is false, the execution of
the loop finishes and the program execution continues with the next statement after
the “for” loop.

- The third element (“update”) is the change or update, a statement that is executed at
the end of each iteration.

The “for” statement is completed by following it with a simple/compound statement,


which represents the body of the loop (i.e., the instruction or set of instructions that is
repeated in each iteration of the loop). Notice that the “for” statement is an entry-
condition loop (the decision to go through one more pass of the loop is made BEFORE
the loop is traversed, so it is possible that the loop is never traversed).

197
Example #1 includes a “for” loop where the iteration number is printed in each iteration.
Before the loop starts, the initialisation statement i=0; initialises the loop’s counter to
zero. Before the first iteration is executed, the test condition i<7 is checked, which is true
for the first iteration. The loop prints the iteration number with the printf() command
and after the first iteration is executed (i.e., the first printf() command is executed), the
update statement i++ is executed, which increments the loop’s counter. In the second
iteration of the loop i=1, so the test condition is still true and a new iteration of the loop
is executed, increasing the counter with i++ after the iteration. After the iteration with
i=6, the update statement i++ increases the counter to i=7 so the next time the test
condition i<7 is evaluated, it is not true anymore and the execution of the loop stops.

Example #2 uses the same principle to compute the square of integer numbers.

Example #3 illustrates a common application of “for” loops: the initialisation of arrays.


This type of loops is very helpful when assigning initial values to the elements of an
array, since this avoids having to write an individual assignment statement for each
individual element of the array. Another advantage is that the same principle can be
employed regardless of the array’s size.

Example #4 uses a “for” loop to print the values of the elements of an array.

When using “for” loops with arrays it is important not to exceed the array’s limits. Recall
that for an array of N elements the first element has index 0 and the last element hast
index N-1.

198
199
The examples shown in the previous slide are based on the use of a “for” loop following
the same principle: an index/counter starts at some number (which is initialised in the
initialisation statement of the “for” loop) and the index/counter is increased in each
iteration (in the update statement) until a maximum value for the index/counter is
reached (which is checked in the test condition of the “for” loop). This provides a simple
way to perform an operation a certain number of times.

However, “for” loops are very flexible and we can use “for” loops in other different
ways, as illustrated over the next slides.

200
201
202
The program in this example works because characters are stored as integers, so this
loop really counts by integers anyway.

203
You would use the test condition shown in this example if you were more concerned
with limiting the size of the square than with limiting the number of iterations.

Any valid Boolean expression can be used as a test condition in a “for” loop.

204
205
206
When omitting expressions in a “for” loop we have to ensure that some statement
inside the loop will eventually cause the loop to terminate. Otherwise the loop will run
forever and our program will “hang”.

207
The initialisation statement of a “for” loop does not need to initialise a variable. Any
legal statement can be used. Just remember that it is executed only once, before any
other parts of the loop are executed.

Note: This example is included for illustration purposes only. Notice that including a
printf() statement as an initialisation of a “for” loop is not a good programming style as
this leads to a unclear and rather confusing code. This same example can be written in a
much clearer way using a “while” loop, as shown later on.

208
The syntax for the “while” iterative statement is shown in the slide.

The parentheses following the keyword “while” contain a single element (“test”), which
is a Boolean expression (i.e., an expression that evaluates to either true or false)
representing a test condition, which determines the execution of a new iteration of the
loop. The “test” condition is evaluated BEFORE each potential execution (iteration) of
the loop. If the “test” condition is true, a new iteration of the loop is executed. More
iterations will be executed while the “test” condition is true. However, as soon as the
“test” condition is false, the execution of the loop finishes and the program execution
continues with the next statement after the “while” loop.

Notice that the “while” statement does not include an “update” statement as it is the
case for the “for” statement. Therefore, when you construct a “while” loop, it must
include something that changes the value of the test expression so that the expression
eventually becomes false. Otherwise, the loop will never terminate.

The body of the “while” statement (i.e., the instruction or set of instructions that is
repeated in each iteration of the loop) can be a simple/compound statement.
Remember that a compound statement is a group of statements, enclosed between
braces { }, that is executed as a block.

Notice that the “while” statement is an entry-condition loop (the decision to go through
one more pass of the loop is made BEFORE the loop is traversed, so it is possible that
the loop is never traversed).

209
The “while” statement is frequently used in loops where the number of iterations is
unknown beforehand.

This slide shows a version of the last example shown for the “for” statement, but based
on a “while” statement on this occasion. The number of times the user will be asked to
enter a number is unknown before the loop is executed for the first time (indefinite
loop), so for this particular example using a “while” loop is a more natural choice
(although the same loop can be implemented with a “for” statement as it has been
shown in previous slides).

210
211
This example is a bit more complex. It shows how to find a value in an array.

The program first defines an index variable, which is used to go through the elements of
the array until the desired value is found. Notice that the index variable is initialised to -1
but the first element of an array has index 0. For this reason the increment operator (in
prefix version) is used in the test expression of the loop in order to increment the value
of the index variable before accessing the corresponding element in the array, so that in
the first iteration of the “while” loop the element accessed in the first place is
my_array[0], which is the first element. The increment operator is also in charge of
moving towards the next element of the array, since the test condition is re-evaluated in
each iteration of the loop, which in turn increases the index variable after each iteration.

The “while” loop has two conditions in its test expression: the index variable has to be
less than the SIZE of the array (remember that the last index of an array is SIZE-1) and
the value of the current element of the array has to be different to the value we are
looking for. Therefore, the while loop can finish in two different ways:

1) The desired value is found. In such a case, the condition my_array[++index] != value
is false and then the loop terminates after having found the desired value. When the
loop terminates in this case, the value stored in the variable “index” is the index of
the element where the desired value is stored in the array.
2) The desired value is not found. In this case, the loop will finish after having checked
all the elements of the array, when the condition index < SIZE is false. When the loop
terminates in this case, the value stored in the variable “index” is equal to SIZE.

212
This slide compares the similarities and differences of the “while” and “for”
statements/loops.

Both are entry-condition loops, meaning that the execution condition or “test” is
evaluated BEFORE each iteration. This type of loops may not be executed at all if the
execution condition is already false before the first iteration can be executed.

Both loops have a “test” condition, which determines whether the loop is further
executed. If the “test” condition is true, another iteration of the loop is executed.
However, if it is false, then the loop execution finishes.

The main difference between the “for” and “while” loops is that the “for” loop has “init”
and “update” statements, which are elements that a “while” for does not have.
However, we can include such elements manually in a “while” loop in order to make it
equivalent to a “for” loop (see next slide).

213
The main difference between the “for” and “while” loops is that the “for” loop has “init”
and “update” statements, which are elements that a “while” for does not have.
However, we can include such elements manually in a “while” loop in order to make it
equivalent to a “for” loop. As shown in this slide, both “for” and “while” loops can be
used in to produce the same looping sequence.

214
Since “while” and “for” loops can be used to produce the same looping sequence, as
shown in the previous slide, one may wonder why both statements exist in C and which
one should be used in which case. The choice between a “for” or a “while” statement is
in general decided based on whether the number of iterations of the loop is known
beforehand (i.e., definite/counting loop) or not (i.e., indefinite loop).

Definite/loops are loops whose number of iterations is known beforehand. This type of
loops requires three actions: i) initialising a counter (“init”); ii) comparing the counter
with some limiting value in order to decide if a new iteration is executed (“test”); and iii)
increasing the counter in each iteration (“update”). Since the “for” loop gathers all these
three actions into one single place, the “for” loop is in general a more convenient choice
for definite/counting loops.

Indefinite loops are loops whose number of iteration is unknown beforehand. In this
case, a “test” condition is still necessary but an initialisation and/or update may not be
necessary. In such a case, the “while” loop is a more natural choice and, in general, the
more convenient one.

In some cases, choosing between a “for” or “while” loop is just a question of personal
preference.

215
The syntax for the “do-while” iterative statement is shown in the slide.

The parentheses following the keyword “while” contain a single element (“test”), which
is a Boolean expression (i.e., an expression that evaluates to either true or false)
representing a test condition, which determines the execution of a new iteration of the
loop. The “test” condition is evaluated AFTER each potential execution (iteration) of the
loop. If the “test” condition is true, a new iteration of the loop is executed. More
iterations will be executed while the “test” condition is true. However, as soon as the
“test” condition is false, the execution of the loop finishes and the program execution
continues with the next statement after the “do-while” loop.

The body of the “do-while” statement (i.e., the instruction or set of instructions that is
repeated in each iteration of the loop) can be a simple/compound statement.
Remember that a compound statement is a group of statements, enclosed between
braces { }, that is executed as a block.

Notice that the “do-while” loop itself counts as a statement and, therefore, requires a
terminating semicolon.

Notice that the “do-while” statement is an exit-condition loop (as opposed to the “for”
and “while” statements, which are entry-condition loops), which means that the
decision to go through one more pass of the loop is made AFTER the loop is traversed.
Therefore, at least one iteration/execution of the loop is guaranteed.

216
The “do-while” statement is used in loops where the number of iterations is unknown
beforehand, but at least one iteration is needed.

This slide shows an example where the “do-while” statement is used to ask the user to
enter a passcode. The number of iterations the loop will be executed is unknown
beforehand (i.e., the user may enter a wrong passcode by mistake several times before
the right one is entered), but at least one iteration is needed (i.e., the user needs to be
asked to enter the passcode at least once). The loop will iterate until the right passcode
is entered.

217
This slide compares the similarities and differences of the “while” and “do-while”
statements/loops.

Both are indefinite loops, meaning that the number of times/iterations that the loop will
be repeated is unknown beforehand.

The main difference between the “while” and “do-while” loops is that the “while” loop
is an entry-condition loop, while the “do-while” loop is an exit-condition loop. This
means that the test condition is evaluated BEFORE each iteration in the “while” loop and
AFTER each iteration in the “do-while” loop. Therefore, the “while” loop may not be
executed/iterated at all (if the test condition is already false before the first iteration),
while the “do-while” loop is executed/iterated at least once.

218
When you need to choose a loop for your program, you may wonder which loop is more
convenient. After some practice and experience, your common sense will tell you which
option is more natural or convenient. While you get used to the different types of loops,
you can refer to this slide when you have to make a decision. This slide provides some
general guidelines to help you choose the most convenient loop for your program.

The first step is to decide whether you need an entry-condition loop or an exit-condition
loop. You answer should usually be an entry-condition loop. There are several reasons to
consider entry-condition more convenient. One is the general principle that it is better
to look before you leap (loop) than after. A second is that a program is easier to read if
the loop test is found at the beginning of the loop. Finally, in many cases, it is important
that the loop be skipped entirely if the test is not initially met. If you decide that an
entry-condition loop is convenient choice for your program, then a “for” loop should be
the more natural/convenient choice for definite (counting) loops while a “while” loop
should be the more natural/convenient choice for indefinite loops. If you decide that
you need an exit-condition loop for your program, then the “do-while” loop should be
the more convenient option.

219
Normally, after the body of a loop has been entered, a program executes all the
statements in the body before doing the next loop test. The “continue” and “break”
statements can be used to skip part of a loop or even terminate it, depending on the
tests made in the body of the loop.

The “continue” statement causes a loop to skip the rest of an iteration and start with the
next iteration.
The “break” statement causes the termination of a loop.

Both statements (continue and break) can be used with all three loops (for, while and
do-while).

This slide shows two examples illustrating the different effects of these statements. The
programs capture the characters entered by the user (orange colour) and then display
the same characters (white colour), until the ‘z’ character is entered, which causes the
loop (and the program) to terminate. An exception occurs when the user enters a vowel.
In the first example, when the user enters a vowel, the “continue” statement causes the
loop to skip the rest of the iteration and start with a new iterations. As a result, the first
example does not print the characters introduced by the user if they are vowels. In the
second example, the “break” statement causes the loop to terminate when the user
enters a vowel, with no more iterations being executed (i.e., the program terminates
after the first vowel).

220
A nested loop is simply a loop inside another one. The loop containing the other loop is
called the outer loop, while the loop inside the outer loop is called the inner loop. An
inner loop runs through its whole range of iterations for a single iteration of the outer
loop. We can nest as many loops as needed in our program. If continue and break
statements are inside nested loops, they affect only the innermost loop containing it.

This slide shows an example containing two “for” loops, one nested inside the other,
and the resulting output obtained after running the program.

221
222
223
224
225
226
Chapter 2 presented the three basic data types provided by C (characters, integers and
real numbers), and Chapters 3 and 4 introduced the concepts of arrays and pointers,
respectively. In this chapter and the next one we will explore these two concepts in
more detail as well as the relation between them.

227
Computer programs take some data as input information, perform some processing on
this information and provide some new data as output information (result). All data
processed by the computer needs to be in the memory (RAM) while in use by the
processor (this ensures a fast and easy access to data). The storage and usage of data
are handled in computer programs by means of variables.

Variables are characterised by 3 aspects:

- Name: Identifier associated with a memory cell.


- Address: Location of the memory cell within the computer’s memory (RAM).
- Value: Content of the memory cell (the actual data).

When we write a program, we think of a variable has having two attributes: a name and
a value. After the program has been compiled and loaded for execution, the computer
also thinks of the same variable as having two attributes: an address and a value. An
address is the computer’s version of a name. When we want to access the value of a
variable in a program, we use the name of the variable. However, the computer uses the
memory address to find where the value of the variable (data) is stored in the
computer’s memory.

In C we can know the three properties of a variable, not only the name and the value
but also the address (this is accomplished by means of pointers).

228
The region of the memory where each variable is stored can be identified by means of a
unique number called memory address. A memory address is an identifier for a location
in the computer’s RAM. Memory addresses enable a computer to access (read and
write) the contents of the variables used in our program (the variable names are
translated to memory addresses when the program is run). In a C program we can
handle not only the variables’ contents but also their addresses. This is accomplished by
means of pointers.

A pointer is a variable used to store a memory address (i.e., a pointer is a variable whose
value is a memory address). Internally, a pointer is stored as an unsigned integer.
However, pointers cannot be used in a program as we use integers (for example, we
cannot multiply two pointers – that would not make sense indeed!). Pointers are
identified by the data type stored in the memory address they point to. Thus, we can
have pointers to integer values, pointers to float values or pointers to characters, and
their variations (signed/unsigned, short/long/long long, double/long double). The data
type of a pointer is important because, as we will see later on, certain pointer
operations depend on the amount of memory required to store each data type.

229
The way pointers are declared is very similar to the way regular variables are declared.
The only difference is the use of an asterisk, which is written right in front of the
pointer’s name/identifier. That asterisk indicates the variable itself is a pointer. Notice
that we need not only to indicate that a variable is a pointer (which is done with *), but
also specify the kind of variable to which the pointer points (char, int, float, etc.). The
reason is that different variables take up different amounts of storage in the memory of
the computer and some pointer operations require knowledge of that storage size. In
the examples shown in the slide, “pAge” is a pointer to an integer value while “pHeight”
and “pWidth” are pointers to float values.

230
The use of pointers involves two operators: the indirection/dereferencing operator (*)
and the address operator (&).

The indirection/dereferencing operator * can be used to find the content/value stored at


the address pointed by a pointer. In the example of the slide, a pointer to an integer is
declared as “int *pAge;” In this example, “pAge” is the pointer that stores the memory
address, while “*pAge” (with the indirection operator *) provides the value stored at
that address (which is an integer value). Therefore, if “pAge” points to an integer, then
“*pAge” is the integer value pointed to by pAge (the same applies to other data types).

The address operator & can be used to find the address of a variable. In the example of
the slide, an integer variable is declared as “int age;” In this example, “age” is a variable
that contains an integer value, which can be accessed by just using the name of the
variable, and “&age” (with the address operator &) provides the address where that
variable/integer value is stored. Therefore, if “age” is a variable, then “&age” is the
address where the variable is stored.

NOTE: Do not confuse the indirection/dereferencing operator * with the binary operator
* used to denote multiplication (they both are represented by an asterisk, but have very
different meanings).

231
This slide summarises the use of the dereference and address operators.

Remember that:

- The address operator is used with normal variables to obtain the corresponding
address.
- If y is a variable, then &y is the address where the variable (value) is stored.

- The dereference operator is used with pointers to obtain the corresponding value.
- If x is a pointer, then *x is the value pointed to.

Thanks to these operators we can always have access to both the value and the address
of a variable, no matter if we are working with normal variables or pointers (one of the
operators will give us the other information that cannot be obtained with the
name/identifier).

232
C offers several basic operations that can be performed with pointers, which are listed in
these slides. Some pointer operations (pointer assignment, pointer dereferencing, taking
pointer’s address and pointer comparison) can be performed with pointers to variables
any data type, while some others (pointer incrementing/decrementing and pointer
differencing) should be only used with pointers to arrays, as it will be explained later on.

Next slides illustrate the pointer operations that are valid for any variables.

233
Pointer assignment: You can assign an address to a pointer. Typically, you do this by
using the address operator (&) along with the name of a normal variable, which assigns
the address of the normal variable to the pointer. The assignment can be done in the
same line as the declaration of the pointer or it can be done afterwards in a separate
line. Both cases are illustrated in the slide.

In the example shown in the slide, the pointer ptr takes as a value the memory address
where the variable “a” is stored (therefore, ptr takes the value 9).

An important aspect when assigning addresses to a pointer is the compatibility between


the pointer type and the data type stored at the address pointed to by the pointer. A
pointer to integer (e.g., int *ptr) should be assigned the address of an int variable, a
pointer to float (e.g., float *ptr) should be assigned the address of a float variable, etc.
The rules for assigning one pointer to another are tighter than the rules for numeric
types (type conversions are not possible with pointers).

234
Pointer dereferencing: This operation gives the value stored at the address pointed to by
the pointer. This is done by using the indirection/dereferencing operator (*) along with
the name of the pointer, which gives the value stored in the pointed-to address.

In the example shown in the slide, the use of the indirection/dereferencing operator
with the pointer ptr gives as a result the value stored in the memory address pointed to
by ptr. Since ptr is pointing to the memory address 9, which is the memory address of
variable “a” (ptr = &a;), and the value stored in that memory address is 5 (i.e., the value
of variable “a”), then *ptr gives the value 5.

235
When using pointer dereferencing, an important rule that never should be forgotten is
that a pointer that has not been initialised should never be dereferenced.

The reason is that the value of a pointer that has not been initialised is random – it may
be pointing to an idle (not used) memory address but it may be pointing to a memory
address used by other variables of our program, to regions of the memory used by other
programs or it may even contain a value that points to an invalid memory address. The
result of an instruction that attempts to assign a value to the contents of an uninitialised
pointer cannot be predicted – but in many cases it may cause a run-time error that
aborts the program.

In the example shown in the slide, the second line attempts to write the value 5 to the
contents of pointer ptr. However, since the pointer ptr has not been initialised, the
address where it is pointing is unknown and it is not known where the 5 will be placed in
the memory – it might go somewhere harmless, it might overwrite data or code, or it
might cause the program to abort.

Remember that creating a pointer only allocates memory to store the pointer itself – it
does not allocate memory to store data. Therefore, before you use a pointer, it should
be assigned a memory address that has already been allocated (for example, the
memory address of an existing variable – by making use of the address operator).

236
Pointer’s address: Like all variables, pointer variables have an address and a value. We
can find the address where the pointer itself is stored by making use of the address
operator (&). We just need to use the address operator in front of the pointer’s name
and this will give us the memory address where the pointer is stored.

In the example shown in the slide, after the instruction ptr = &a; the pointer ptr points
the memory address of variable “a”, which is 9, so this means that the value of ptr is 9.
This value is stored somewhere in the computer’s memory (in the example, it is stored
in address 20). We can know the pointer’s address by just employing the address
operator. Therefore &ptr gives the memory address where the value of the pointer ptr is
stored, so &ptr gives the value 20.

Note:
- The value of a pointer can be printed with the printf() function using the conversion
specification %p (or alternatively %u or %lu).

237
Pointer comparison: You can use the relational operators to compare the values of two
pointers, provided the pointers are of the same type.

The example shown in the slide illustrates the use of relational operators to compare the
memory addresses pointed to by two pointers.

238
Pointer incrementing/decrementing: This operation increments/decrements the address
pointed to by the pointer by making use of arithmetic operators: addition (+) and
subtraction (-).

The actual amount the address is incremented/decremented depends on the pointer


type (i.e., the data type of the value stored at the memory address pointed to by the
pointer) and the computer. Many computers, including PC and Macintosh, handle
individual memory bytes, so pointer values are addresses to individual bytes. For data
types whose storage unit is more than a byte, the pointer usually points to the first byte
where the value is stored. When the pointer is incremented/decremented by n units,
the real value of the pointer does not change by n bytes, but instead by n storage units
(the storage unit depends on the data type and the computer). If the size of a char type
is 1 byte, i.e., sizeof(char) is 1, then increasing a pointer to char by 1 increases the real
pointer value (address) by 1 byte as well. If the size of a int type is 4 bytes, i.e., sizeof(int)
is 4, then increasing a pointer to int by 1 increases the real pointer value (address) by 4
bytes (which is 1 storage unit for int data type). This is one reason why you have to
declare the data type to which the pointer points. The address is not enough because
the computer needs to know how many bytes are used to store the object.

This principle allows handling arrays by means of pointers in a very easy way as it will be
shown later on.

239
Pointer differencing: This operation finds the difference between two addresses pointed
to by two pointers by making use of the arithmetic operator for subtraction (-).

The result returned by the difference of two pointers is expressed in number of storage
units. For example, for type int (and assuming that each int value requires 4 bytes in
memory), a difference value of 3 would mean that pointers point to addresses that are
3x4 = 12 bytes apart (i.e., 3 integer values apart).

A pointer difference operation is guaranteed to be valid as long as both pointers point


into the same array. Applying the operation to pointers of different arrays might produce
a value or could lead to a runtime error (depends on the compiler).

240
Valid operations:

ptr++ and ptr--: Increment and decrement, respectively, by one storage unit the value (address) of pointer ptr.

ptr2 = ptr1 + 2 and ptr2 = ptr1 – 5: Increment (by 2 storage units) and decrement (by 5 storage units), respectively,
the value (address) of pointer ptr1 and assigns the result (new pointed address) to pointer ptr2.

var = &ar[9] - &[3]: Computes the difference between the addresses of the 9th and 3rd elements of array ar and assigns
the result to int variable var. The value of the difference is 6 storage units so var is assigned the integer value 6.

ptr2 = ar + 3: ar is a pointer to the first element of the array, so this operation increases the value of that pointer by 3
storage units and assigns the resulting address to pointer ptr2.

Invalid operations:

ar++: An array name is a pointer to the first element of the array. Pointers can be increased with the ++ operator.
However, an array name is a CONSTANT pointer, which means that its value cannot change in a program. Therefore,
an attempt to change the address pointed to by an array name would lead to a compilation error.

ptr2 = ptr2 + ptr1: The addition of pointers is not defined in C and is considered an invalid operation. The only valid
arithmetic operation involving two pointers is the subtraction (i.e., pointer differencing), and the result of the
differencing operation is an integer value. The addition operation is valid when it involves a pointer and an
integer/integer expression only.

ptr2 = ptr1 * 3 and ptr2 = ptr1 * ar: The only arithmetic operations defined with pointers are the
increment/decrement and differencing. Multiplication, division and any other mathematical operations are not valid
with pointers.

241
An array is an ordered sequence of data elements of the same type (either characters,
integers or real numbers) that are stored in a contiguous region of the computer’s
memory, using consecutive memory cells for consecutive elements of the array, and
whose elements can be accessed (read and written) individually just like any
conventional variable.

242
An array can store as many elements as the memory of the computer allows (tens,
hundreds even thousands…). However, we do not need to include in our programs tens,
hundreds or thousands of declaration statements for each element (however, that
would be necessary if we wanted to use individual variables – this is an advantage of
arrays). Instead, a single declaration statement is enough for an array of any size. The
slide shows the syntax of array declarations.

The syntax for the declaration of an array is essentially the same as for a regular
variable, with the addition of the array size, which is specified between square brackets
next to the array name/identifier. Besides this, the declaration of an array follows the
same rules for regular variables (can use the same data types, the name chosen for the
identifier has to follow the same set of rules, etc.).

243
Each array element has an individual and unique index, which is assigned based on its
position within the array. The first element of an array has index 0, the 2nd element has
index 1, the 3rd element has index 2, and so forth. For an array of size N, the last element
has index N-1.

The elements of an array can be accessed individually by means of their corresponding


index. For the example shown in the slide, the 4th element of the array “prices” can be
accessed with the index number 3 prices[3]. An array element can be used as a
regular variable. For example, a value can be assigned to any array element with a
conventional assignment statement (e.g., prices[3] = 1307.84), and the value stored in
an array element can be used in a program just like a regular variable (e.g., a = b +
prices[3], or prices[1] = prices[0] + prices[4]).

The bounds of an array must not be exceeded. Remember that for an array of size N, the
last element has index N-1. There is no element with index N. If you try to access and
array element whose index exceeds the bounds of the array (i.e., index N or greater), the
compiler will not stop you from doing so (the source code will be compiled with no
errors, maybe just a warning if warnings are activated in the compiler). However, you
may obtain unexpected results in your program and in some cases even run-time errors.
It is the programmer’s responsibility not to exceed the bounds of the array!

244
Array elements can be initialised individually after declaration (treating each element
just like a regular variable).

Array elements can also be initialised within the declaration statement. In this case, the
values for each element are specified between curly brackets (braces) and separated by
comas. We may initialise all the elements of the array or just some selected elements.

When initialising the elements of an array within the declaration statement, the size of
the array does not need to be indicated explicitly (it is optional). In such a case, the array
size (i.e., number of elements) is inferred form the size of the list of initial values (e.g., if
17 values are specified between braces, then the size of the array is assumed to be 17
elements).

We can also use a “for” loop to initialise the elements of an array.

245
Sometimes you might use an array that is intended to be a read-only array. That is, the
program retrieves values from the array but it will not try to write new values into the
array. In such cases, you can and should use the const keyword when you declare and
initialise the array. This makes the program treat each element in the array as a
constant. Just as with regular variables, you should use the declaration to initialise const
data because once it is declared const, you cannot assign values later, which prevents
from changing the values of the elements by mistake.

Another common and recommended practice is to use symbolic constants to represent


the array size. If the array size needs to be modified later on, only one line of the
program needs to be changed.

246
Pointers provide a symbolic way to use addresses. Since the hardware instructions of
computers rely on addresses, pointers enable you to write programs that work in a way
that is close to how the machine works internally. This correspondence makes programs
with pointers efficient. In particular, pointers offer an efficient way to deal with arrays as
it will be shown over the next slides.

Arrays are an ordered sequence of data elements of the same type that are stored in the
computer’s memory using consecutive/contiguous memory cells, which paves the way
to access individual array elements in an easy way by means of pointers.

As a matter of fact, there is a close relation between arrays and pointers and we can
actually handle arrays by means of pointers by taking into account two considerations:

1) Array names are indeed pointers to the first element of the array (i.e., the array
name is associated with the address of the first element of the array). Therefore, if
my_array is an array in your program, then you can use the array name my_array as
a pointer to the first element of the array, meaning that it is equivalent to using
&my_array[0], which represents the address of the first element of the array
(remember the address operator &).

2) The rest of elements of an array can be used based on the pointer to the first
element (i.e., array name) and making use of some pointer operations that will be
described in the next few slides.

247
This slide illustrates the first point that establishes a relation between pointers and
arrays, and allows handling arrays by means of pointers: the array name is a pointer to
the first element of the array. Given an array “my_array”, both the array name my_array
and &my_array[0] represent the memory address of the first array element. Both are
constants because they remain fixed for the duration of a program. However, they can
be assigned as values to a pointer variable, whose value can be changed.

Notes:
- Recall that the amount of memory required by each data type may vary depending on
the compiler and computer architecture where the program is compiled and run. This
example assumes that an integer requires 4 bytes.
- Recall that the sizeof operator returns the size of a data type or variable, expressed in
number of bytes.
- The value of a pointer can be printed with the printf() function using the conversion
specification %p (or alternatively %u or %lu).

248
This slide illustrates the second point that establishes a relation between pointers and
arrays, and allows handling arrays by means of pointers: any array element can be
accessed by means of the array name, which is a pointer to the first element, and some
(arithmetic) pointer operations. This is possible thanks to the fact that array elements
are stored in the computer memory using consecutive memory cells, so we can access
other elements by just increasing a pointer pointing to the first element by the right
amount so that it points to the correct element of the array (we need an auxiliary
pointer since the array name is a constant pointer and cannot be modified). This makes
use of the arithmetic pointer operations that are normally used in conjunction with
arrays: pointer incrementing/decrementing and pointer differencing. These two
operations enable handling arrays by means of pointers as shown in the next slide.

249
This slide illustrates the second point that establishes a relation between pointers and
arrays, and allows handling arrays by means of pointers: any array element can be
accessed by means of the array name, which is a pointer to the first element, and some
(arithmetic) pointer operations in order to obtain the right memory address where the
desired array element is stored.

The relations shown in this slide sum up the close connection between arrays and
pointers. They mean that you can use a pointer to identify an individual element of an
array and to obtain its value. In essence, you have two different notations for the same
thing: array notation (the one you already know) and pointer notation. You can think of
the pointer notation as meaning “go to memory location my_array, move i units, and
retrieve the value there.”

Is there any advantage to writing the program this way? Well, not really. Pointer notation
and array notation are two equivalent methods: you can use pointer notation with
arrays and the reverse is also true (you can use array notation with pointers!). Choosing
one or other notation is a question of personal preference. Some programmers may
prefer using array notation to work with arrays while some other may prefer using
pointer notation. The former is in general a more common approach, although the latter
is also used in practice. However, whatever your personal choice is, you should always
be careful not to exceed array limits as this is not checked by the computer (it is your
responsibility!).

250
Pointers can also be used with strings, which are a particular type of array (a string is a character
array ending in a NULL character ‘\0’, see Chapter 3).

Strings can be created as a string as already explained in Chapter 3 (the slide shows some
examples of string declaration and initialisation using the array form presented in Chapter 3).
However, C also offers the possibility of creating a string using an alternative pointer form, as
shown in the slide.

In both cases, the identifier my_word is a pointer to the first character of the string. The main
difference is that my_word is a constant pointer in the array form while it is a variable pointer in
the pointer form. When the string is created, a certain amount of memory enough to store the
characters of the string “hello” is allocated and then my_word becomes a pointer to the first
character of the string (in both array and pointer forms). The length of this string will be fixed
and cannot be changed. As explained earlier in this chapter, an array name is a constant pointer,
meaning that it cannot be changed during the program. This means that in the array form,
my_word will always point to the created string, which has a constant length. The characters of
the string can be modified. However, if a longer string is needed, a new variable is needed for
the new string because in the array form my_word is a constant pointer pointing to an already
created string. On the other hand, in the pointer form, my_word is a variable pointer and its
value can be modified. This means that if a longer string is needed we can just create the new
string (the program will find a larger amount of memory to store the new longer string) and
make the pointer point to the beginning of the new string. To do this, we just need to create a
new assignment as shown in the example (the old string “hello” will still remain in the memory
of the computer, but it will not be pointed by my_word anymore). In practice, this provides an
easier way to handle strings since from a conceptual point of view we can think of my_word (in
the pointer form) as a string to which we can assign different values.

251
You should be cautious when using strings in the pointer form.

The first example shown in this slide is correct. The first line declares the pointer
my_word but the pointer is not initialised (i.e., the pointed address is random). In the
next line the pointer is initialised: the assignment my_word = “hello”; creates the string
“hello” in the computer’s memory and assigns the address of the first character to the
pointer my_word.

The second example seems to be similar to the first one, however it is not. The first line
of the second example declares the pointer my_word but does not initialises it. The
second line passes the pointer as an argument to the function scanf(). The scanf()
function will write the text entered by the user in a random location of the computer’s
memory (it might overwrite data or code and might cause a program to abort).
Therefore, the second example is a case of dereferencing an uninitialised pointer.

The solution is to allocate memory first. This can be done by making my_word point to
an already existing string whose size is large enough for the expected input or,
alternatively, by using the array declaration form as shown in the third example. Notice
that in the third example the array my_word is not initialised (it is only declared).
However, the declaration of an array allocates a block of memory to store the number of
elements indicated in the declaration (100 elements in this example). Therefore, in the
third example, the scanf() function would write the result in a safe place of the
computer’s memory.

252
253
254
255
256
257
All the arrays seen so far have been one-dimensional arrays. These arrays can be
thought of as a vector or row of data that contains a set of related values. In some
problems we may need to handle other more complex sets of data that cannot be easily
stored as a vector/row of data. For example, suppose that we need to write a program
that handles the values of a stock index 5 days a week, with 10 samples per day. In this
case, storing the information in a vector does not seem to be the most convenient way.
However, the information can be organised in a more convenient way as a table where
each row corresponds to one of the 5 days and each column corresponds to one of the
10 daily samples. This information can be stored and handled in a program as a 2-
dimensional array.

258
The slides illustrates the declaration of a 2-dimensional array. As it can be appreciated,
the main difference with respect to a 1-dimensional array is the inclusion of another pair
of square brackets containing a value (size2) that represents the size of the additional
dimension. Arrays of more dimensions are declared following the same principle. For
example, int array[3][5][12]; is the declaration of a 3-dimensional array whose
dimension sizes are 3, 5 and 12.

A 2-dimensional array can be thought of in two ways. First, it can be thought as a table
of size1 x size2 elements (5 x 10 in the example) where size1 (5) is the number of rows
and size2 (10) is the number of columns. Each individual element can be identified by its
row and column. Alternatively, it can also be thought as an array of arrays. In the
example, the multidimensional array stock_idx can be seen as an array of elements
stock_idx[5], from stock_idx[0] to stock[4], where each element is, in itself, an array of
10 float values. So if, for example, stock_idx[3] is an array of 10 elements, then the first
element is stock_idx[3][0], the second element is stock_idx[3][1], …, and the last
element is stock_idx[3][9].

2-dimensional arrays may be visualised in a easier way if we think of them as tables.


However, this point of view is applicable to 2-dimensional arrays only. The view of
multidimensional arrays as “arrays of arrays of arrays…” is general and applicable to
multidimensional arrays with any number of dimensions.

259
The two-dimensional view of an array (as a table or as an array of arrays) is merely a
convenient way of visualising an array with two indices. Internally, such an array is stored
sequentially in the computer’s memory, beginning with the size2 (10) elements of the 1st
row, followed by the size2 (10) elements of the 2nd row, etc., terminated by the size2
(10) elements of the size1-th row.

The access to the individual elements of a multidimensional array follows the same
principle as for one-dimensional arrays. For each dimension of size N, the valid indices
for that dimension are in the range from 0 to N-1 (this applies to each dimension
individually, so each dimension has its own individual index). As with one-dimensional
arrays, not exceeding array bounds in any dimension is important.

260
Manual initialisation: Each element is assigned an individual value. This initialisation can
be better understood if the array is thought as an array stock_idx[5] of five elements
where each element is, in itself, an array of 10 float values. The initialisation uses five
embraced lists of numbers (one for each element of stock_idx[5]), where each list is
composed of 10 float values, all enclosed by one outer set of braces. The data in the first
interior set of braces is assigned to the first row of the array, the data in the second
interior set goes to the second row, and so on. This principle extends for
multidimensional arrays of more dimensions.

Alternatively, the elements of a multidimensional array can be initialised in an


automated way by making use of nested loops as shown in the example. A loop with an
individual index is required for each dimension of the array.

261
262
Array elements are always stored in the computer’s memory (RAM) following a
sequential order, regardless of the dimension of the array. Individual array elements can
be accessed by means of integer indices (using array notation) or by means of pointers
(using pointer notation). The complexity of pointer view increases with number of array
dimensions.

This slide shows the relation between multidimensional arrays and pointers, and how
pointers can be used to access individual elements of multidimensional arrays.

In summary, the element (m,n) of a 2-dimensional array can be accessed using:

- Array notation: price[m][n]


- Pointer notation: *(*(price+m)+n)
- Hybrid notation: (*(price+m))[n]

Note: The hybrid/mixed notation can be quite confusing so either the array notation or
the pointer notation are used more commonly in practice.

The same principle extends to multidimensional arrays of more dimensions. For


example, for a 3-dimensional array, the element (x,y,z) can be accessed with
price[x][y][z] and *(*(*(price+x)+y)+z).

263
Declaring pointers to multidimensional arrays can be useful, in particular when we want to pass the array
as an argument to a function (the only way of passing arrays is with pointers!).

This slide illustrates the syntax needed to declare, initialise and use a pointer to a 2D array.

An important aspect to take into account is the use of parentheses in the declaration of the pointer. If
parentheses are not used, the declared object is quite different:

- float *ptr[2]: The square brackets [ ] have a higher precedence than *, so they are applied first, making
ptr an array of two something. Next, the * is applied, making ptr an array of two pointers. Finally, the
float keyword is applied, making ptr an array of two pointers to float.

- float (*ptr)[2]: The parentheses are applied first (they have the highest precedence), so the * is first
applied, making ptr a pointer. Next, the square brackets [ ] are applied, making ptr a pointer to an
array of two elements. Finally, the float keyword is applied, making ptr a pointer to an array of two
elements of type float This is what we need in order to point to the first element of the
multidimensional array.

Notice that once the pointer has been declared and assigned the address of the (first element of the)
array, we can use the pointer name instead of the array name, and we can use it using both array notation
and pointer notation. The use example shown in the slide makes use of the array notation ptr[2][1] but
we could also use *(*(ptr+2)+1).

For arrays of more dimensions, the pointer has to be declared in order to match the first element of the
array. For example, for a 3D array float price[4][2][3] the pointer should be declared as float (*ptr)[2][3],
etc. Notice that the first set of brackets indicates a pointer whereas the rest of the brackets describe the
type of data object being pointed to, hence the pointer has to be declared accordingly (i.e., including all
brackets but the leftmost set).

264
In Chapter 4 we saw that arguments can be passed to a function following two different
(but necessarily exclusive) methods: by value and by reference.

An argument is passed by value when we call the function passing variables’ values. A
copy of the values contained in the variables passed as arguments is made and used
inside the called function. In this case the function works with an individual and
independent copy of the same original value and therefore it cannot change the original
value of the variables in the calling function.

An argument is passed by reference when we call the function passing variables’


memory addresses. The function does not make a copy of the original values; instead,
the function can access the original values through the passed memory addresses. In
this case the function has access to the original values used in the calling function and
therefore it can change the original value of the variables in the calling function.

265
Arguments can be passed to a function by reference using pointers (remember that a
pointer is a variable that stores a memory address).

This slide summarises and exemplifies the 3 steps involved in the use of functions (i.e.,
declaration, definition and call) when the function receives arguments by reference (this
was explained in Chapter 4, where more details about this can be found).

266
Sometimes we need to write functions to process arrays or functions that take arrays as
input parameters.

In C we can pass arrays as function arguments. However, arrays cannot be passed by


value (i.e., it is not possible to pass to a function all the values of the elements stored in
an array). Arrays are always passed by reference, which is accomplished by making use
of the array’s name. Remember that the name of an array is a (constant) pointer to the
first element of an array. A function needs to know not only the array name but also the
number of elements to be processed in the array; however, note that the name of an
array says nothing about the number of elements of the array. Therefore, when writing
array-processing functions we need to use a second argument in order to let the
function know the number of elements to be processed in the array. To this end, we can
use two methods:

1) To pass the array name (which is a pointer to the first element) along with a second
integer argument indicating the number of elements to be processed in the array.

2) To pass a pointer to the first element to be processed along with another pointer to
the last element to be processed.

Both methods are illustrated in the next slides.

267
The first method is to pass an array to an array-processing function is to pass the array
name (which is a pointer to the first element) along with a second integer argument
indicating the number of elements to be processed in the array. This slide describes the
3 steps involved in the use of functions (i.e., declaration, definition and call) when the
function receives an array as argument using this method.

The example considered is a function that sums the elements of an array of type float.
The function receives as input parameters the name of the array (which is a pointer to
the first element) and the number of elements to be processed in the array. Notice that
the formal parameter corresponding to the array “ar” (in the function
declaration/prototype and function definition) is a pointer to float and the
corresponding actual argument in the function call is the name of an array of float
elements – both are compatible because the name of an array is a pointer to the first
element. The function also takes as an input parameter the number of elements of the
array (formal parameter “n”, which is passed by value). With these two parameters the
function has all the information needed to process all the elements of the array one by
one, which is done with the “for” loop adding the value of the current array element to
the total, then moving to the next element and so on.

268
In the context of function declarations/prototypes and a function definitions, it is
possible to use an alternative (but equivalent) form in order to highlight that the
function processes an array (the second/right-hand side form reminds the reader of the
program that not only does “ar” point to a float, it points to a float that is an element of
an array).

Remember that you can omit names in function declarations/prototypes but not in
function definitions.

The forms shown in this slide are interchangeable so you should be able to use any of
the four prototypes with either of the two definitions shown in the slide. However, for
clarity reasons, the same form should be used in both declaration and definition.

269
The second method is to pass a pointer to the first element to be processed along with
another pointer to the last element to be processed. This slide describes the 3 steps
involved in the use of functions (i.e., declaration, definition and call) when the function
receives an array as argument using this method.

The example considered is again a function that sums the elements of an array of type
float. The function receives as input parameters two pointers to the first and second
elements of the array to be processed. Notice that in this example the first pointer
argument points to the memory address OF the first array element while the second
pointer argument points to the memory address AFTER the last array element (which
actually is the first memory location after the last element of the array). C guarantees
that when it allocates space for an array, a pointer to the first location after the end of
the array is a valid pointer (but it makes no guarantees about the value stored at that
location!), which makes constructions such as the one shown in this example valid. Note
that using this “past-the-end” pointer makes the function call neat, elegant in
appearance and easy to remember.

In this case, the sum function also adds the value of the current array element to the
total before moving to the next element and then repeats the operation. The difference
with respect to the example in Method 1 is that in this case the “while” loop increases
the pointer received as first argument (i.e., pointer to the first array element) and
compares to the pointer received as second argument (i.e., pointer to the last array
element) to decide when to stop.

270
If you want to write functions that process multidimensional arrays, you need to
understand pointers well enough to make the proper declarations for function
arguments. The most difficult part of using multidimensional arrays with functions is
writing the proper function declaration with the proper declaration of formal
parameters so that the function can pass the array properly. Once this is done, the use
of the array in the function is easier – in the function body itself, you can usually get by
with array notation. This slide describes the 3 steps involved in the use of functions (i.e.,
declaration, definition and call) when the function receives a 2-dimensional array as
argument.

The example considered is a function that sums the elements of a 2-dimensional array
of type float. Notice that the formal parameter used to pass the array as an argument
needs to be a pointer to the first element of the array. In this example the array is 2-
dimensional (i.e., an array of arrays) and the first element of the 2-dimensional array is
an array of COLS elements of type float. Therefore, the pointer needs to be declared as a
pointer to an array of COLS elements of type float, hence the declaration shown in this
slide (please revise the slide explaining pointers to multidimensional arrays).

271
In the context of function declarations/prototypes and a function definitions, it is possible to use
an alternative (but equivalent) form in order to highlight that the function processes an array
(the second/right-hand side form reminds the reader of the program that the argument passed
to the function is actually a pointer related with an array).

Remember that you can omit names in function declarations/prototypes but not in function
definitions.

The forms shown in this slide are interchangeable so you should be able to use any of the four
prototypes with either of the two definitions shown in the slide. However, for clarity reasons,
the same form should be used in both declaration and definition.

Be aware that the following declarations will not work properly:

float sum(float (*ar)[], int rows);


float sum(float ar[][], int rows);

Because the compiler needs to know the size of the object/array to which “ar” points, therefore
it is necessary to specify the COLS parameter.

The following declaration is valid and will work:

float sum(float ar[ROWS][COLS], int rows);

but the size parameter ROWS is ignored by the compiler.

272
When writing a function that processes variables of fundamental/basic data type (i.e., char, int, float and
its variations), arguments can be passed by value or by reference. The usual rule is to pass variables by
value unless the program needs to alter the original value, in which case the variable is passed by
reference by means of a pointer. However, arrays do not offer this choice; arrays must always be passed
by reference. The reason for this is efficiency: passing an array by value would imply that a copy of all the
elements of the array would need to be made every time the array is passed to a function, which may
require a large amount of memory (if the array has many elements) as well as time (in order to find a
region of memory that is big enough and copy all the elements from the original array to the new array).
However, it is much faster to pass the address of the array and have the function work with the original
data.

In order to be efficient, C only allows arrays to be passed by reference. However, this technique can cause
problems. The reason why C ordinarily passes data by value is to preserve the integrity of the data – if a
function work with a copy of the original data, it will not modify the data accidentally. However, because
arrays are passed by reference, a programming error could lead to the original data to be corrupted,
which would go unnoticed.

This problem can be used by using the const keyword in the function declaration and definition (not
necessary in the function call). This tells the compiler that the function should treat the array argument a
though the array contains constant data. Then, if array contents are modified accidentally inside the
function, the compiler will give an error alerting that the function is attempting to modify constant data.
Notice that using the const keyword in this way does not require the array to be constant – it just make
the array to be treated as constant data INSIDE the function only.

In general, if you write a function intended to modify the contents of an array, you should not use the
const keyword in the function declaration and definition. However, if you write a function not intended to
modify an array, you should use the const keyword in the declaration and definition.

273
This slide illustrated how to protect the contents of an array from accidental
modifications inside a function. Notice that using the const keyword in this way does not
require the array to be constant – it just make the array to be treated as constant data
INSIDE the function only. If the array contents are modified accidentally inside the
function, the compiler will give an error alerting that the function is attempting to
modify constant data. However, the array contents can be modified in the calling
function as the array is not constant in the calling function.

274
All programs need to allocate enough memory to store all the data used by the program.
This is in general done automatically by the program/compiler/computer. For example, if
we declare in our code an array for 100 elements of type double, the program will
allocate during runtime a block of memory that is sufficiently large to store 100
elements of type double so that our program can have access to the required amount of
memory needed to work with the array. However, we cannot always anticipate how
much memory our program will need; for example, when we cannot anticipate how
many elements an array will actually have during runtime. We do not want to create a
small array that may not have enough capacity to store the data used by the program;
however, creating an array that is significantly larger than what is actually needed will
result in a waste of computer resources (i.e., memory). The solution is to allocate
memory dynamically during runtime (i.e., while the program is running/executing). C
enables us to do this by means of four functions: malloc(), calloc(), realloc() and free().
The first two functions, malloc() and calloc(), are used for dynamic memory allocation;
the third function, realloc(), is used for dynamic memory re-allocation; the last function,
free(), is used to release memory previously allocated with malloc() or calloc(). The
prototypes for these functions are declared in the standard header file stdlib.h.

275
The malloc() function allows dynamic memory allocation in runtime (i.e., while the
program is running, it is possible to allocate more memory to the program). The malloc()
function takes one argument: the number of bytes of memory we want. The malloc()
finds a suitable block of free memory and returns the address of the first byte of that
block (i.e., a pointer to the first byte of that block). This address can be assigned to a
pointer and then we can use the pointer to access the memory. The pointer returned by
the malloc() function is a “generic pointer” (in C terminology, it is a pointer-to-void),
which means that the data type for that pointer is not specific. This pointer can be
assigned to any pointer of any data type, but it needs to be type-casted to the proper
data type before the assignment. If the malloc() function fails to find the required
memory space, then it returns the null pointer (NULL).

The example shown in the slide uses malloc() to allocate a block of memory big enough
to store 100 elements of type double (remember that the operator sizeof returns the
number of bytes required to store the specified data type). The address of the first byte
of this block is returned and assigned to the pointer ptr. Because ptr is a pointer-to-
double, the pointer returned by malloc() needs to be type casted to pointer-to-double
with a typecast operator, hence the need of using (double *) before assigning the
returned address to the pointer ptr.

276
The calloc() function can also be used instead of malloc() – it performs the same tasks
and returns the same result. The only syntax difference between malloc() and calloc() is
the input arguments. While malloc() takes as input argument the number of bytes
required, calloc() takes two input parameters: the first argument indicates the number
of required memory cells and the second argument indicates the size (in bytes) for each
cell.

277
Both functions find a block of memory of a certain requested size and return a pointer to
the first byte of the allocated memory (if the allocation is successful) or the NULL
pointer (if the allocation fails for any reasons).

The are two differences between malloc() and calloc():

- While malloc() takes as input argument the number of bytes required, calloc() takes
two input parameters: the first argument indicates the number of required memory
cells and the second argument indicates the size (in bytes) for each cell.
- The malloc() function allocates memory but does not initialise the contents of the
allocated block of memory. On the other hand, the calloc() function allocates memory
and also initialises all the BITS of the block of memory to zero. In most cases, this
means that calloc() initialises the block of memory to zeros; note, however, that in
some hardware systems a floating-point value of 0 is not represented by all bits set to
0, meaning that in that case the initial values would not be zeros. The calloc()
function is slower than malloc() because the initialisation of the block of memory to
zeros takes some time. Therefore, when the initialisation is not necessary and
efficiency is important, the malloc() function should be used instead.

278
The realloc() function can be used to change the size of a memory block previously
allocated dynamically. The new size can be both lower or greater than the new one. If
the new size is lower, some part of the memory block is released. If the new size is
greater, the memory block is extended and, if necessary, moved to a new region of the
memory.

The example shown in the slide uses malloc() to allocate a block of memory big enough
to store 100 elements of type double and then uses realloc() to change the size of the
previously allocated block to a new size of 150 elements of type double.

279
The free() function is used to free/release the memory previously allocated with
malloc() or calloc() – however, it cannot be used to release memory allocated by other
means (for example, an array). The free() function takes as input a pointer to a block of
memory dynamically allocated with malloc() or calloc() and frees up the memory that
had been allocated so that it can be used by other variables and/or programs.

280
One advantage of dynamic memory management is that we can write programs that use
dynamically sized arrays (i.e., arrays whose size is decided in runtime, while the program
is running, and can be changed/resized during program execution as well). This slide
compares the features of conventional fixed-size arrays (i.e., the arrays seen so far in this
course) with the features of dynamic variable-size arrays, which can be accomplished by
using the functions explained in the previous slides. An example is shown in the next
slides.

281
This example program shows how the functions provided by C for dynamic memory
management can be used to achieve the effect of a dynamically sized array (i.e., an array
whose size can be decided in runtime and changed, if necessary, in runtime).

The program asks the user how many elements will be introduced and then calls the
malloc() function to allocate enough memory to store that number of elements. Notice
that the size of the array is decided in runtime, something that cannot be done with
conventional arrays.

282
Once the necessary memory has been allocated with the malloc() function, the
elements are stored in consecutive cells in memory as the user introduces the values
one by one with the scanf() function (this is accomplished by adding the appropriate
value “i” to the pointer where the scanf() function stores the value returned by the
user). After the loop with scanf(), the final result is a set of values stored in consecutive
memory cells (i.e., an array). We can access the elements by using array notation – this
is actually what is done with the printf() function.

Notice that the scanf() function uses the pointer notation “ptd + i”. However, we could
have used the array notation “&ptd[i]” as well, obtaining the same results. Moreover,
note that the printf() function uses array notation with the pointer “ptd[i]”. This example
shows the close connection between arrays and pointers: we can use both array and
pointer notations with both arrays and pointers!

An important aspect to highlight is the use of the free() function once the memory
allocated with malloc() or calloc() is not needed anymore. In this particular example, the
call to the free() function is not really necessary because it is called right before the
program finishes and the memory used by a program is freed when it terminates, so in
this case the memory is released anyway regardless of whether we call free(). However,
in more complex programs, the ability to free and reuse memory can be important in
order to make an efficient use of the memory resources.

283
This slide summarises some common errors that may usually occur when getting started
with the dynamic use of memory.

284
285
286
Sometimes we need to work with groups of data elements of the same type, which
involves the use of arrays. When we need to work with a group of numbers, we can use
an array of int, float or double. When we need to work with a group of characters that
form for example a name or sentence, we can use strings, which are arrays of characters
ending in a NULL character (‘\0’). Sometimes we may need to work with a group of
strings; in this case, there are two possible options: a two-dimensional array of
characters (also referred to as “rectangular array” of characters) and an array of strings
(also referred to as a “ragged array” of characters).

287
This option sets a two-dimensional (rectangular) array where each row corresponds to a
string and each column corresponds to one character of the corresponding string. Since
this is a 2D array, all rows have the same number of columns, which means that the
same storage space is used to store each string, regardless of what its actual length is.
Therefore, in order to store all the strings correctly, the number of columns of the array
needs to be equal to the number of characters of the longest string plus an additional
column to store the NULL character (‘\0’) at the end of the string. Because some strings
may be shorter than the longest one, this option leads to an inefficient use of memory
since some of the columns are not needed in some of the rows/strings, which means
that some memory is used unnecessarily (i.e., is wasted).

The syntax for this option and the resulting memory usage pattern is shown in the slide.

288
This option sets an array of strings. This array is one-dimensional and each element in
the array holds the address of a character, which is the address of the first character of
each string. Therefore:

- The 1st element of the array (fruits[0]) is a pointer to the memory location where the
1st character (‘A’) of the 1st string (‘Apple’) is stored.
- The 2nd element of the array (fruits[1]) is a pointer to the memory location where the
1st character (‘P’) of the 2nd string (‘Pear’) is stored.
- The 3rd element of the array (fruits[2]) is a pointer to the memory location where the
1st character (‘O’) of the 3rd string (‘Orange’) is stored.
- The 4th element of the array (fruits[3]) is a pointer to the memory location where the
1st character (‘S’) of the 4th string (‘Strawberry’) is stored.

The array itself does not actually holds the strings; it just holds the addresses of the
strings. The strings can (and will probably) be stored in non-continuous (independent)
positions of the computer’s memory. Because the length of each string is independent
of the length of other strings in the group (they are in independent memory locations),
the amount of memory space used to store each string is the minimum amount that is
needed (i.e., the number of characters of the string plus one additional NULL character).
As a result, no memory space is wasted as in the case of 2D arrays of characters.

The syntax for this option and the resulting memory usage pattern is shown in the slide.
When working with groups of strings, an array of strings should be used instead of a 2D
array of characters.

289
290
291
292
A file can formally be defined as a named unit of storage, usually on a disk or other type
of storage device such as hard disks, CDs/DVDs, USB memory sticks, memory cards, etc.
Files are used to store programs, documents, data, correspondence, forms, graphics and
many other kinds of information. In fact, the source code you write for your programs is
stored as a file (with extension .c).

Files are handled by the operating system (OS). The OS manages all file-related
operations such as opening files with the right program/application, closing files,
copying/moving files between folders, etc. From the point of view of the operating
system, files can be a quite complex concept. However, here we are concerned on how
files appear to a C program. From the point of view of C, a file is a continuous sequence
(stream) of bytes, each of which can be handled (i.e., read or written) individually.

293
ANSI C provides two different views of a file: binary and text.

In the binary view of a file, each and every byte in the file is handled (i.e., read/written)
individually and can be accessed/seen by the program. This means that if we access in our
program a file in binary mode, we can access each and every byte in the file individually and see
them all exactly as they are stored in the file.

In the text view of a file, the contents of the file are interpreted as characters according to the
ASCII code. This means that each byte in the file is mapped to a corresponding code according to
the ASCII table and this is what we see in our program when we access a file in text mode. This
mapping also affects the way we see the contents of the file and what we can see from the file
in our program. For example, the end-of-line character in a file has different representations in
different operating systems: Windows and related operating systems us a Carriage Return (CR)
character (‘\r’) followed by a Line Feed (LF) character (‘\n’); Macintosh and other operating
systems use just the CR character (‘\r’); and UNIX and other related operating systems such as
GNU Linux use just the LF character (‘\n’). (Have you ever tried to open in Windows a text file
created in Linux? This is the reason why you see all the lines of the file as just one line of text.) In
C, the end of a line is represented with a LF character (‘\n’). This means that when we read a
text file in text mode in a certain operating system, the corresponding combination used to
represent the end of line is mapped to the C representation ‘\n’, and when we write a text file in
C the end of line ‘\n’ is mapped to corresponding representation of the operating system where
we are working, before writing to the file. Therefore, what we ‘see’ in our program when we
work with text files may differ from what is actually in the file (this does not happen in binary
mode).

294
In C we can use any file view with any file type. This means that we can open in C a file
in either binary or text modes. If we read a text file in binary mode we will then be able
to read each single byte in the file, including for example the bytes corresponding to the
‘\r’ and ‘\n’ characters of an end-of-line in those cases where a text mode reading would
show us just the ‘\n’ character. Moreover, we can also read any file in text mode, even it
is not a text file – what we will see from our program in this case is just the ASCII
characters whose codes correspond to the bytes stored in the file (this is what we see
for example when we open a non-text file such as a JPG picture file in a text editor).

In general, we will use text view/mode with plain ASCII text files and binary view/mode
with binary files (i.e., non-text files containing general data in binary format).

295
C provides a set of standardised high-level (operating system independent) specialised
file functions, whose definitions are provided in the C standard header file stdio.h.

This slide summarises the most relevant functions, which will be covered in this chapter.

296
Before we can work in our program with a file, we need to open the file first. This is
accomplished by means of the fopen() function. This function takes two strings as input
parameters: the first string contains the name of the file and the second string
represents the mode to be used with the file (more details later on). If the operation is
successful (i.e., the file is opened successfully), then the function returns a pointer to
FILE (i.e., a pointer of type FILE). If the operation is unsuccessful for any reason, then the
function returns a pointer to NULL.

FILE is a file structure (we will learn about structures in Chapter 10, but for the time
being we can think of a structure as being a particular “data type”, so that FILE is the
“data type” for files). The fopen() function returns a pointer to a FILE. Since we already
know how to work with pointers, we can already open files and work with the returned
pointer in order to access and handle the file – this is all we need to know for the time
being. The data read from a file is stored in the computer’s memory as an intermediate
step. By means of the pointer to FILE returned by the fopen() function we can read from
that region of the memory where the contents of the file are stored, so that we can
actually have access to the contents of the file this way. Similarly, the data written to a
file is stored in the computer’s memory before it is written as a block. The pointer to
FILE allows us to write in the region of the memory where the contents are stored
before being written to a file, so this way we can write to a file. Since we know how to
work with pointers, we can also work with files.

297
The fopen() function can be used to open files in both binary and text modes, for reading only, for writing
only, and for both reading/writing, and with different actions depending on whether the file already exists
or not. This table provides the complete list of file modes in C.

The r/rb modes are used to READ files. If the file does not exist, a pointer to NULL is returned. Otherwise,
a pointer to FILE is returned.

The w/wb and a/ab modes are used to WRITE files. If the file does not exist, it is created. However, if the
file already exists, the w/wb mode truncates the file length to zero, meaning that all the contents of the
previous file are deleted and a new file is written from the scratch, while the a/ab mode does not delete
any contents, it simply appends new contents to the end of the previous file.

The modes of the 3 first rows are used to EITHER READ OR WRITE ONLY (any attempt to write a file
opened in read-mode with r/rb will lead to an error as it will be the case for any attempt to read a file
open in write-mode with w/wb or a/ab). The modes of the 3 last rows are used to BOTH READ AND
WRITE.

When opening a file in binary mode, “rb+” is equivalent to “r+b”, “wb+” is equivalent to “w+b” and “ab+”
is equivalent to “a+b”.

NOTE: When writing to a file, the difference between r+/rb+ and w+/wb+ is that the r+/rb+ mode
overwrites the contents of the file without truncating its length to zero (i.e., it starts at the beginning of
the file an overwrites whatever was contained in the previous file – the rest of contents remain
unchanged) while the w+/wb+ mode truncates the length of the file to zero (i.e., deletes all the contents
of the previous file) and then starts writing a new file from the scratch.

The best way you can understand how these options work is by writing short programs yourself to test
them and see what happens in each case.

298
299
300
301
Note: The file can be opened and tested for successful opening in the same line with:

if ((pFile = fopen("expenses.txt", "w")) == NULL)

302
C provides text-oriented file functions that can be used to work with files containing
data in text format (i.e., characters and strings).

The functions provided by C are very similar to the basic input/output functions used to
communicate with the user of a program through the keyboard and the screen, and
indeed they have a very similar usage pattern with the main difference being just the
need to include an additional argument in the function call indicating the file where the
input/operation should be performed (then the program will read from a file instead of
the keyboard and will write to a file instead of the screen). This group of functions is
normally used to work with text files - they also work if used with binary files but in this
case the information read and written has no real text meaning.

The next few slides compare C’s basic input/output functions that are used to
communicate with the user of a program through the keyboard and the screen (these
functions were explained in Chapter 3) with the equivalent functions provided by C to
work with files in text mode. If you can use adequately the input/output functions
explained in Chapter 3, then you should find straightforward using the equivalent
functions to work with files. Remember that these functions have to be used with files
that have been opened previously with the fopen() function!

303
304
The fgets() function takes 3 arguments:
- The 1st argument, as with gets(), is the address (type char*) where the input should
be stored.
- The 2nd argument is an integer representing the maximum size of the input string.
- The 3rd argument is a pointer to FILE indicating the file to be read.

The fgets() function reads input through the first newline character, until one fewer than
the upper limit of characters is read, or until the end-of-file is found; fgets() then adds a
terminating null character to form a string. Therefore, the upper limit represents the
maximum number of characters plus the NULL character. If fgets() reads in a whole line
before running into the character limit, it adds the newline character, marking the end of
the line into the string, just before the null character. Here, it differs from gets(), which
reads the newline but discards it.

The fputs() function takes 2 arguments:


- The 1st argument, as with puts(), is the address (type char*) containing the string to
be written to the file.
- The 2nd argument is a pointer to FILE indicating the file to be written.

Unlike puts(), fputs() does not append a newline when it prints.

305
306
A program reading the contents of a file needs to stop when it reaches the end of a file –
otherwise, this may lead to a run-time error. How can we know if we have already
reached the end of a file? This depends on the function used to read text from the file.

The fscanf() and getc() functions return an end-of-file indicator (EOF) when the end of
the file has been reached. EOF is defined in the standard header file stdio.h. We can
therefore keep reading the contents of a file with fscanf() and getc() until the function
returns EOF (see example in the slide).

The fgets() function returns NULL when the end of the file is reached. NULL is also
defined in the standard header file stdio.h. We can therefore keep reading the contents
of a file with fgets() until the function returns NULL.

307
The file input/output functions seen so far are text-oriented, that is functions that deals
with files containing characters and strings. However, in C there are also numerical data
(integer and real values). What if we want to save numerical data into a file? We could
use the functions seen in previous slides to save the numbers as strings, so each digit of
the number would be a text character in the file. However, in most cases this is not the
best option. First, the resulting file might be unnecessarily big. Imagine for example we
want to store an integer number of 10 digits as a string using fprintf(). Each digit would
be saved as a text character, so one integer value would require 10 bytes of storage
space in the file. However, a computer normally requires only 4 bytes for an integer of
that size (long int), or maybe 8 bytes maximum (long long int) – see Chapter 2. This
means that we are actually using more space than what is really essential to store the
data so this is not efficient. A second point is that some of the decimal digits (in the case
of real numbers) may be lost after truncating the values before saving them into a file.
After the values are saved in a file, there is no way to go back to the full precision when
the file is read. The most accurate and consistent way to store a number is to use the
same pattern of bits that the program does. When data is stored in a file using the same
representation that the program uses, we say that the data is stored in binary format.

C provides several data-oriented functions to work with files containing numerical data
in binary format. In this chapter we will discuss two functions: fwrite() and fread(). These
functions are normally used to work with files in binary mode. Remember that these
functions have to be used with files that have been opened previously with the fopen()
function!

308
The fread() and fwrite() functions take the same input arguments and return the same
type of result. The slide shows the syntax and description of the input and output
parameters of these functions.

309
This slide shows an example of how to write a 2D array to a file in binary format. Writing
1D arrays or scalar values is similar.

The first example saves the array as a single item/chunk with a size of 6 floats (the array
has 6 elements in total, 2 rows x 3 columns).

The second example saves the array as 6 items/chunks with a size of 1 float each.

Both examples are equivalent and the file created in both cases is identical.

310
This slide shows an example of how to read a 2D array to a file in binary format. Reading
1D arrays or scalar values is similar.

The first example reads the array as a single item/chunk with a size of 6 floats (the array
has 6 elements in total, 2 rows x 3 columns).

The second example reads the array as 6 items/chunks with a size of 1 float each.

Both examples are equivalent and the array snk_values ends up storing the same values,
regardless of which fread() example is used and regardless of which of the fwrite()
examples shown in the previous slide is used.

311
A program reading the contents of a file needs to stop when it reaches the end of a file –
otherwise, this may lead to a run-time error. If we do not know beforehand how much
data is stored in a binary file, how can we know if we have already reached the end of
the file?

The fread() function returns the number of items read from the file. The number of
items returned by fread(), i.e. nof_success, should match the number of items specified
in the nof_items parameter. However, the returned value nof_success can be less than
nof_items if the end of a file has been reached. We can test this condition to determine
if we have already reached the end of a file whose size is unknown.

312
One of the attributes that an open FILE has is its current position. The current position of
a file determines the byte/position within the file where the next read or write
operation will start. The current position needs to be tracked in order to enable a
controlled reading/writing of the file. This attribute is also important in order to enable
different types of access to a file. There are two types of file access: sequential and
random.

A file is said to be accessed sequentially when the bytes of the file are accessed (i.e.,
read or written) in a sequential order. After each reading/writing operation, the current
position of the file is incremented to next byte after the last byte read/written in the last
operation, so that it points to the next byte/position of the file, where the next
reading/writing operation will be performed and so on. In this type of access, the
current position is incremented after each read/write operation, and this is done
automatically (i.e., we do not need to do anything special for this to happen), so unless
we change deliberately the current position of a file, the file will be accessed
sequentially by default.

In some cases we may need to access (read or write) specific positions of a file. This is
called random access (i.e., we access a random position of the file which is not
necessarily related with the position accessed in the last operation). To access a
particular position of a file, we need to update the current file position before reading
from/writing to the file. C provides standard functions to this end: fseek() and ftell().
Next slides provides a description for these functions. Remember that these functions
have to be used with files that have been opened previously with the fopen() function!

313
The fseek() function allows us to move the current position of the file to any arbitrary
position we wish. The function takes three input arguments:
- The 1st argument is a pointer to FILE, which refers to the file to be searched. This file
has to be opened previously with fopen().
- The 2nd argument is an integer number that indicates the number of positions (in
bytes) to be shifted, counted from a specific starting point. This argument tells how
far to move from the starting point. This integer number can be positive (move
forward), negative (move backward) or zero (stay put), and needs to be specified in
long integer format. This can be done by means of a long int variable or by means of a
long int literal number. Remember that literal numbers such as 3 or -5 are treated by
C as normal integers (i.e., int type). The long type has to be specified by appending an
uppercase L to the number. Therefore, if we want to indicate an offset of 3 or -5
positions, we have to pass 3L or -5L, respectively, as the 2nd argument to the fseek()
function (3L and -5L are long integers, while 3 and -5 are just integers). The reason
why long integers are used is because they have a longer numerical range than plain
integers (refer to Chapter 2) so that they allow bigger file sizes than integers.
- The 3rd argument determines the starting point from where the offset is counted. It
can be one of the three options shown in the slide.

The value returned by fseek() is 0 if everything is ok, and -1 if there is an error, such as
attempting to move past the bounds of the file.

314
315
The ftell() function can be used to know the current position for a file previously opened
with the fopen() function. The value returned is of type long and indicates the current
position expressed in number of bytes counted from the beginning of the file.

316
Once we have finished using a file in our program and we do not need to access the file
(read or write) anymore, the file must be closed. This is accomplished by means of the
fclose() function. The function takes as input parameter a pointer to FILE and attempts
to close the file. If the file is closed successfully, then fclose() returns a value of 0.
However, if the file closing operation fails for any reason, then fclose() returns EOF. The
fclose() function can fail to close a file for example if the disk is full, or the storage device
(e.g., a USB memory stick) is removed before the file is closed, or for several other I/O
errors.

317
318
319
320
321
322
The scope of a variable is the region or regions of a program where the variable can be accessed
and it determines which parts of a program can access the variable. Based on the scope,
variables can be classified into local or global variables.

Local variables are declared inside a function and can therefore be used only inside the function
where they are declared. The formal parameters of a function (and the actual arguments passed
to a function) are also local variables within that function despite the fact they are not declared
inside the body of the function. No function can access the local variables of another function –
note that several functions can have variables with the same name/identifier, however they are
still different variables (i.e., their values are stored in different memory locations despite having
the same name; this is possible because the variables are local to the functions so they are
independent). Local variables exist while the function is being executed. This means that local
variables are created (i.e., the memory is allocated) in the point/line of the program where they
are declared (normally right after the function is called) and they are destroyed (i.e., the
memory is freed/released) when the execution of the function finishes.

Global variables are declared outside all functions and can be used by any function (more
concretely, they can be used by any function between the point/line of the program where they
are declared and the end of the file – if the declaration of a global variable is included at the
beginning of a file, which is the common practice, then they are accessible/visible to all
functions). Any function can have access to a global variable and any changes made by a
function will remain and will be visible by the rest of functions. Global variables exist for the
whole duration of the program execution. This means that global variables are created (i.e., the
memory is allocated) when the execution of the program starts and are destroyed (i.e., the
memory is freed/released) when the execution of the program finishes.

323
One attractive characteristic of global variables is that they can be used to simplify the
communication among functions. By using global variables, all functions can have access
to the same variables, which removes the need to worry about pointers, function
arguments, return values, etc. in order to enable communication among functions. This
approach, however, is highly unadvised. When using global variables, you need to worry
about coordinating how the functions change the values of the variables since one
function may alter sneakily the values being used by another function, thus leading to
logical errors that are in general very difficult to detect. This subtle danger outweighs by
far the advantage of not having to care about communication among functions. For
communicating among functions it is better to use local variables along with function
parameters/arguments, pointers and return values rather than global variables. Another
important point is that programs that use global variables indiscriminately are more
difficult to understand and are inefficient since they waste memory (the memory is
allocated to store global variables during the whole execution of the program, even
when the variables and the memory used by them are not needed during some time).

Global variables are available and can be useful in some cases. However, you should
have a good reason to use global variables (do not use global variables just because it is
easier). Before using a global variable in your program, ask yourself first whether it is
really necessary. In most cases you can solve a problem with local variables only.
Remember that relying on global variables as a means to enable function
communication is a really bad programming approach/style – good programs, in general,
do not have many (any!) global variables.

324
We can describe a variable not only in terms of its scope, but also in terms of its linkage
and storage duration, which together indicate which parts of a program can make use of
the variable.

Based on scope, and being more rigorous, variables can be classified into variables with
block scope or file scope. A block is a portion of code contained within an opening brace
{ and the matching closing brace } This can be the body of a function, or a compound
statement that is a part of a conditional statement (if, if-else, switch) or a loop (for,
while, do-while). A variable defined inside a block is said to have block scope and it can
be accessed only within that block. Function parameters also have block scope despite
the fact of being declared outside the body of a function. A variable defined and used
inside a block is also called a “local variable” (they are called local because they can be
used only locally within the function or block where they are defined – they are visible
just in that block and any blocks nested in it). A variable defined outside any function is
said to have file scope and it can be accessed anywhere within the file from the point
where it is defined to the end of the file, including all functions in the file. A variable
defined and used in a file outside any function is also called “global variable” (they are
called global because they can be used by any function in the same file – they are visible
to any functions defined after the variable’s declaration).

The linkage of a variable determines how the same identifier can or cannot refer to the
same variable throughout the different parts of a program. Variables with block scope
(i.e., local variables) have no linkage, meaning that the use of the same identifier (name)
in any other block refers to a different variable (i.e., if the same name is used for a

325
variable in any other block or function, then they are treated as different variables in
different blocks that happen to have the same name, but are still completely different
variables, that is, their values are stored in different memory locations). Variables with
file scope (i.e., global variables) can have either internal or external linkage. Global
variables with external linkage can be used anywhere within the file where they are
defined as well as in any other file that is a part of the program (i.e., any use of that
identifier/name in any file will be treated as a reference to the same variable and
therefore to the same value in the same memory location). Global variables with internal
linkage can be used anywhere within the file where they are defined but not in any other
files even if they are part of the same program (i.e., any use of that identifier/name in the
same file where the variable is defined will be treated as a reference to the same
variable, but any use of that identifier/name in any other file will be treated as a different
variable). The difference between internal/external global variables is relevant only when
we have a multi-file program (i.e., a program whose code spreads over more than one
source file).

Finally, the storage duration of a variable determines how long the variable exists in the
computer’s memory. Based on storage duration, variables can be classified into
automatic storage duration and static storage duration. Variables with file scope (i.e.,
global variables), both with internal and external linkage, have static storage duration,
meaning that they exist throughout the whole duration of the program execution.
Variables with block scope (i.e., local variables), have automatic storage duration,
meaning that they are created when the execution of the block begins (i.e., when the
function is called) and the memory allocated to them is freed when the execution of that
block finishes (i.e., when the function returns the execution flow to the calling point).

Based on these 3 properties, C defines 5 storage classes (see table in the slide). Storage
classes are not covered in this lecture/course. However, it is recommended to learn more
about C’s storage classes (see recommended reading in this slide and at the end of this
chapter).

325
Chapter 2 presented the three basic data types provided by C (characters, integers and
real numbers). In addition to these three basic data types, C also provides other derived
data types: arrays, pointers, structures and unions. Previous chapters have already
covered in detail the concepts of arrays and pointers. This chapter presents the concept
of structures and unions.

326
Sometimes you may need to process in a program a diversity of information with a
complex structure. Just imagine you need to create a program to manage the inventory
of books of a library or a book shop. Each book has different properties such as a title,
author, price, number of pages, publisher, edition, etc. Since these data are of different
nature (characters/strings, integer numbers, real numbers), separate variables of
different types would be necessary for each property of a book. Moreover, since the
program would need to manage a certain number of books, arrays would be necessary
as well. If we had to create such a program with the concepts we know so far, we would
need to use separate arrays for each property of a book (i.e., array of names, array of
authors, array of prices, etc.). So in the end each array would store information of
different books, and the details of a given book would be stored in separate arrays. This
does not look like a logical and easy way to store and manage information. It would be
easier to have a single data object that groups all the properties of a book into a single
entity, and then have several of these data objects, one for each book in the inventory.
Fortunately, C provides a new concept to deal with complex data and solve problems like
the one in this example: structures.

327
A structure can be formally defined as a collection of variables referenced under one name, providing a
convenient means of keeping related information together. Let’s have a more detailed look to what each
these words in the definition of a structure implies.

Collection: A structure is a collection of elements and as such it embraces/groups elements that have
some kind of logical relation among them. For example, we can have a structure for a book where the
elements of the structure are the properties of the book like title, author, price, publisher, year of
publication, edition, number of pages, etc. All these elements have in common that they are properties of
a book (i.e., this is the logical relation among them).

Variables: The elements of a structure can be variables of any data type, including basic data types as well
as derived data type. This means that a structure can group not only variables for characters and
integer/real numbers but also variables for derived data types such as arrays, pointers and even other
structures and unions. For example the elements of a structure for a book can be a string for the title,
another string for the author, a float for the price, an integer for the number of pages, another integer for
the year of publication, etc. The publisher can be included as a string (just the name) or as another
structure containing more detailed information about the publisher. This flexibility allows us to create
data objects able to fit the needs of virtually any problem where we need to deal with complex
information in an organised and methodical manner.

Referenced: The elements of a structure can be accessed/modified individually, but they are still part of
the group so they are referenced not only by their own names but also through the name of the structure
where they are contained (this will be shown in more detail later on).

One name: The whole structure has its own name/identifier, for which we have to follow the same rules
used to select names/identifiers for variables and functions.

328
A structure declaration forms a template that may be used to create structure objects
(that is, instances of a structure). The structure declaration does not create an actual
data object/variable (i.e., no memory is allocated when a structure is declared), but it
describes the list of elements (members/fields) that constitute such an object. The
structure declaration actually creates a new “data type” that can be used to create
variables/data objects of that data type later on.

A structure template is declared by using first the keyword “struct” followed by a “tag”,
which is an identifier that can be used to refer to this structure definition/template later
on when we need to create data objects/structures that follow this template. The list of
elements that constitute the structure (called members of fields) is enclosed in a pair of
braces, with each member of the structure being declared independently with its own
declaration ending in a semicolon. The declaration of the structure is finished by adding
a final semicolon at the end because a structure declaration is a statement itself.

329
A structure definition creates a structure data object/variable following the template of
a previously declared structure. In the structure definition, a new structure data
object/variable is created, meaning that the amount of memory required to store all the
members of the structure is allocated. The selected name for the structure is then
associated to that region of memory and can be used to access the structure and its
members.

Notice that in the definition/creation of a new structure, “struct tag” plays the same role
as “char”, “int” or “float” in the creation of variables for characters, integer numbers or
real numbers respectively: they indicate the “data type” of the new variable whose
name/identifier is indicated afterwards.

330
The declaration of a structure template and the definition of a structure variable/data
object can be combined into one single step using the syntax shown in the slide.

When using a joint declaration and definition of a structure, the tag is optional (i.e., it
can be omitted). This is something usually done when we only need one data
object/structure that follows that template. However, if we need to create other
structures/data objects that follow the same template, then we need to specify a tag
which will be used later on to create new structure variables of the same type.

331
The elements (members/fields) of a structure can be initialised in the same step as the
structure definition by using a comma-separated list of values (initialisers) enclosed in
braces. The list must match in a 1-to-1 basis the members of the structure declaration.
So if the first member/field of the structure is an array of 50 characters (title) then the
first value of the list must be a string that matches this size; if the third member/field of
the structure is a float variable then the third value of the list must be a real number,
etc. This is necessary because the values of the list are assigned 1-to-1 to the
members/fields of the structure during the initialisation. If there is no match between all
the pairs of list values and structure members, then the compiler will give you an error.

C allows an structure to be assigned to another structure of the same type (something


that cannot be done with arrays). This works even if a member of the structures
happens to be an array. This feature of structures can be used to initialise a structure to
the values of an already existing structure. The initialisation in this case is done by using
the assignment operator, just like with any other variable.

332
To have access to each individual member of a structure we have to use the structure
name, followed by a dot and followed by the name of the member we want to access.

Notice that each member/field of a structure can be treated as an individual variable so


we can print its value, change its value with a new one, etc.

333
334
335
336
The previous example can handle an inventory of books with one book only! We may
need sometimes to manage a large number of data objects/variables of the same type.
To deal with this, C provides arrays. Like any other variables, structures can also be
grouped into arrays of structures so that the previous example can be extended to an
inventory with many books (i.e., an array of structures of type “book”). Arrays of
structures are declared just like any other kind of array. As shown in the example of the
slide, the structure declaration remains the same since it is only a description of the
elements that compose one structure variable of that type. The array of structures is
created like any other kind of array (by attaching the array size, between brackets, at the
end of the array name) and individual structures of the array are accessed like the
individual elements of any array (by indicating the array index between brackets). Notice
that to access the individual elements (structures) of an array of structures the array
index is attached to the structure name and not to the end of the member name.

337
C also allows to use pointers to structures just like for any other type of variable. We
may want to use a pointer to a structure for the same reasons we use pointers to simple
variable types (e.g., passing a pointer to a structure as a function argument so that the
function can have access to structure and make changes).

The declaration of a pointer to a structure follows the same principle as the declaration
of any other kind of pointer: just precede the name of the pointer by an asterisk (*). All
the operations and other aspects of pointers seen so far in the course can be applied to
pointers to structures in the same way - structures can just be thought of as another
(more complex) data type.

The members of a structure can be accessed through a pointer. In this case, however,
the dot operator (.) needs to be replaced by an arrow operator (->), which is formed by
typing a hyphen (-) followed by the greater-than symbol (>). A structure pointer followed
by the arrow operator (->) works the same way that a structure name followed by the
dot operator (.).

Notice that ptr->title is equivalent to (*ptr).title. In the latter case, the indirection
operator (*) gives the contents of the memory location pointed to by the pointer ptr
(i.e., the contents of the structure), and then the dot operator (.) can be used to access
the member of the structure pointed to by the pointer ptr. The use of parentheses in
this case is necessary because the dot operator (.) has higher precedence than the
indirection operator (*).

338
We may need sometimes to write functions to perform operations with structures.
Functions can receive structures using 3 different methods, as shown in the slide:

1) Passing the whole structure by value as an argument to the function. Unlike arrays,
structures can be passed as function arguments by value. This creates a local copy of
the whole structure inside the function so that the function works with this local
copy of the structure. Any changes made by the function to the local copy will be
lost when the execution of the function finishes and the local copy is removed from
memory. Therefore, as with any other variables passed by value, structures passed
by value are not modified by the called function and they maintain their original
values.

2) Passing the structure by reference (i.e., a pointer to the structure). Unlike arrays,
structure names are not pointers to the structure, so if we want to pass the address
of as structure to a function we need to use the address operator along with the
structure name. In this other case, the function does not make a local copy of the
structure but works with the original structure, which can be accessed through the
pointer. Therefore, the function can make changes in the original structure and any
changes made will remain after the execution of the function finishes.

Structures can also be returned as return values by a function. The function prototype
needs to be declared in a similar way to structures. The following slides illustrate with a
simple example the use of structures with functions.

339
This example makes use of a structure funds, which contains two members: the funds of
a current account and the funds of a savings account. The program (in the main
function) creates a structure of type funds, which is named my_wealth, assigns initial
values to the members of my_wealth, and then calls the sum_funds() function, which
computes the total funds by summing the amounts of each account.

In this example, the sum_funds() receives as argument a structure of type funds by


value. Notice that the function declaration and definition need to include the
declaration of the structure as an input parameter. Then the structure my_wealth can be
passed to the function by value by just using the structure name. Since my_wealth is
passed by value in this example, this means that sum_funds() makes an independent
local copy of my_wealth and works internally with that copy. The members of that local
copy are accessed with the dot (.) operator.

340
This second example performs the same task as the previous examples. The difference
with respect to the previous example is that the sum_funds() in this example receives as
argument a structure of type funds by reference. Notice that the function declaration
and definition need to include the declaration of a pointer to the structure as an input
parameter. Then the structure my_wealth can be passed to the function by reference by
just using the structure name along with the address operator (&my_wealth), which
passes the memory address of my_wealth to the function. Since my_wealth is passed by
reference in this example, this means that sum_funds() does not make an independent
local copy of my_wealth and simply works internally with the original structure (the
same structure as in the main() function). The members of the original structure can be
accessed through the pointer by means of the arrow (->) operator.

341
Which of the two options seen before (structure passed by value or by reference) is
more convenient?

Passing a structure to a function by value creates an additional copy of the same


information, which requires additional memory to duplicate the same information and
thus may result inefficient in terms of memory space and time required to make the
copy, especially is the structure is large or contains large arrays. The advantage of this
option is that the original data is protected because the function works with an
independent copy of the data. For efficiency reasons, this option (by value) can be
acceptable when working with small structures.

Passing a structure to a function by reference does not create any additional copies
since the function works with the original structure, which is accessed through a pointer.
As a result this option is more efficient. The main disadvantage of this option is that
original data is exposed to erroneous operations in the function and therefore is less
safe. However, the use of the “const” keyword in the declaration of the function
parameters solves the problem – this makes the structure to be treated as a constant
structure (i.e., a structure whose contents cannot be changed). The use of the “const”
keyword in this context is similar to the its use to protect the contents of arrays in
functions (see Chapter 8).

Typically, programmers use pointers to structures as function arguments (i.e., structures


passed by reference) for efficiency reasons, using the “const” keyword when the data
contained in the structure needs to be protected from unintended changes.

342
Because structures are flexible enough to represent a wide variety of complex data, they
are an important tool for creating data bases. Ultimately, you would want to be able to
save the information in, and retrieve it from, a file where the data base is stored, which
implies the need to save structures in files and read structures from files. Structures can
be written to files and read from files just like any other variables. This can be
accomplished with the fwrite() and fread() functions, which can be used to write/read
structures in binary format. A reminder of the syntax of these functions is shown in the
slide (the details can be found in Chapter 9).

343
This slide shows an example of how a structure can be written to a file in binary format -
refer to Chapter 9 for details on the use of the fwrite() function.

344
This slide shows an example of how a structure can be read from a file in binary format -
refer to Chapter 9 for details on the use of the fread() function.

345
A union is a data type that allows us to store different data types (variables) in the same
memory space, but not simultaneously (only one at a time). Unions are useful when we
need to store variables whose data type can change or is unknown in advance (an
example will be shown later on to illustrate the usefulness of unions).

Unions have many features in common with structures. Unions are declared, defined,
initialised and accessed in the same way as structures. We can also create and use arrays
of unions and pointers to unions in the same way as structures. The only syntax
difference between structures and unions is that the keyword “struct” is replaced with
the keyword “union”.

The main difference between structures and unions is conceptual: while both can group
variables of different data types into one single data object, only structures can store all
their members/fields simultaneously; a union can only hold one and only one of its
elements (members/fields) at a time. Lets suppose for example a structure and a union,
both with the same following members/fields: int a, float b, char c. The structure can
store simultaneously the integer variable “a” AND the float variable “b” AND the char
variable “c”. However, the union can store at any time either the integer variable “a” OR
the float variable “b” OR the char variable “c”. The elements of union are stored in the
same memory location - since different data types have different storage requirements,
the size of a union is the size of its largest element (member/field). Remember that it is
your responsibility as a programmer to keep track of which data type is currently being
held by a union at any moment.

346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361

You might also like