Control Structures in C and C++
We quickly review the control structures of the C language. C++ has
structures that don’t exist in C (e.g., try
blocks) but what we see
here for C is essentially identical in C++. These are things you’re
supposed to know already. Still, I recommend that you go through this
review and practice with all the exercises.
Declarations, Definitions and Statements
A C program is made up primarily of a series of declarations,
definitions, and statements. Declarations introduce new identifiers
for types, variables, and functions. For example, below is a
declaration of two variables of type int
:
int a, b = 0;
In this case, the declaration also defines the two variables, and it
also initializes one of them. A declaration that also defines the
identifier is also called a definition. So again, the line above
contains a declaration and definition of the two variables a
and
b
. You might wonder what would be a declaration that is not also a
definition. We discuss all of this in detail later, but here are a
few examples:
extern int counter; float sqrt(float x); struct linked_list;
The first is a declaration of an integer variable called counter
.
The second one is a declaration of a function called sqrt
that takes
a float
argument and returns a float
result. The third one is a
declaration of a struct
type. None of these declarations defines
the corresponding variable, function, or type. If you want to declare
and define a function, you can do that as you would expect:
int sum (int a, int b) { return a + b; }
So much for declarations and definitions. As I said, we will come back to them at some point. Our focus here is on statements. Those are the chunks of code that actually tell the program to do something. There are various types of statements in C: expressions, compound statements, selection statements, iteration statements, and jump statements. Let’s see them one by one.
Expression Statements
Expressions define computational actions, meaning that they typically compute values and may have side effects. Syntactically, an expression statement is an expression followed by a semicolon:
Syntax: expression ;
For example, each one of the following lines is an expression statement.
b = 2;
a = 7 + b*3;
printf("%d\n", a);
Expressions are built from basic operators, such as the arithmetic
operators of addition and subtraction (+
and -
) and many others.
The operands can be literal values such as 7
or "%d\n"
, variables
such as a
or b
, and other subexpressions, such as 7 + b*3
, which
is itself an expression containing b*3
as a subexpression.
Expressions include function calls. In fact, a function call can also
be seen as the application of an operator, the function-call operator,
to a subexpression that identifies the function, and another
subexpression that defines the list of arguments for the call. But
let’s not go into so much detail here. There is a lot to say about
expressions, but we will do that later in a dedicated chapter.
One more detail about expression statements: the expression can be empty, so it is possible to have a “null” statement. What would be the point of that? Here’s an example:
unsigned int length (const char s []) { unsigned int l; for (l = 0; s[l] != 0; ++l); return l; }
I know we’re getting ahead of ourselves, but where is the empty statement here?
Compound Statement
A compound statement is a series of declarations and statements grouped together to form a logical unit of computation. The statements within a compound statement are structured in sequence, meaning that their order in the program determines their order of execution. Syntactically, a compound statement is a series of declarations and statements enclosed in curly braces.
Syntax: {
declaration | statement … }
The “body” of a function in a function definition is a compound statement.
int main () { /* <---------------------compound statement--\ */ int a; /* declarations/definitions | */ int b; /* ... | */ int s; /* | */ scanf("%d", &a); /* expression statements | */ scanf("%d", &b); /* ... | */ s = a + b; /* | */ printf("%d + %d = $d\n", a, b, c); /* | */ } /* <-----------------------------------------------------/ */
Compound statements are also typically parts of other statements. For example, the body of a loop is a statement that is often a compound statement.
void insertion_sort (int A [], int length) { for (int i = 1; i < length; ++i) { /* <--------compound statement--\ */ int j = i - 1; /* | */ while (j > 0 && A[j - 1] > A[j]) { /* <--compound statement--\ | */ int tmp = A[j]; /* | | */ A[j] = A[j - 1]; /* | | */ A[j - 1] = tmp; /* | | */ --j; /* | | */ } /* <-------------------------------------------------------/ | */ } /* <-------------------------------------------------------------/ */
Selection Statements
It is essential for programs to be able to execute something in some cases, and something else in other cases. That is what selection statements are for. For example, you could write a function to output your score in a game:
void print_score (int p) { if (p < 0) { printf("Something is wrong: your score is negative!\n"); } else { printf("You have %d point", p); if (p != 1) printf("s"); printf("\n"); } }
Or you could write the following little program to test the Collatz conjecture one step at a time:
int main () { int n; scanf("%d", &n); /* read a number from standard input */ if (n % 2 == 1) n = 3*n + 1; n = n / 2; printf("%d\n", n); }
Did you try this program? Try it now! And now that you’ve also seen
how to read a number from the standard input with scanf("%d", &n)
,
you should also write a program to test the print_score
function of
the previous example. Do it now!
All of this is intuitive, but let’s review the syntax and semantics of
if-statements. Let’s first look at the Collatz example. The
program contains a simple if-statement that executes the expression
statement n = 3*n + 1
when \(n\) is an odd number, or more
specifically when the remainder of the integer division of \(n\) by 2
is 1. The syntax of that if-statement is as follows:
Syntax: if
(
expression )
statement
Condition Expressions
The expression in parens in the if
statement is interpreted as a
Boolean condition, meaning that it is interpreted as having a true
or false value. The statement that follows is then executed when the
condition expression is true. We say that the expression is
interpreted as a Boolean condition meaning that the expression does
not have to yield a Boolean value. In fact, until relatively
recently, the C language did not even have a proper Boolean type. C++
does have a proper Boolean type, bool
with literal values true
and
false
, and recent versions of C also support something similar.
Still, the conditional expression can be of any scalar type, such as
int
or float
, and its value is considered to be true if it
compares not equal to 0, or false otherwise. In fact, we could
rewrite the if
statement in the above program as follows:
if (n % 2)
n = 3*n + 1;
Full If Statement
An if statement can also include an else part. We have already
seen an example of that in the print_score
function example above.
Again, this is intuitive and should be well known, but let’s review
anyway. The syntax is as follows:
Syntax: if
(
expression )
statement else
statement
The program evaluates the expression in parens, and then executes the
first statement right after the expression if the result of the
condition expression compares not equal to 0, or otherwise executes
the second statement right after else
if the condition expression
compares equal to 0.
As an exercise, consider the variant of the Collatz example listed below:
int main () { int n; scanf("%d", &n); do { if (n % 2 == 1) n = 3*n + 1; else n = n / 2; printf("%d\n", n); } while (n != 1); }
Notice that here the n = n / 2
statement is executed only when n
is even. Do you see the difference with the other variant of that
computation? Here’s the analogous code for comparison:
int main () { int n; scanf("%d", &n); do { if (n % 2 == 1) n = 3*n + 1; n = n / 2; printf("%d\n", n); } while (n != 1); }
Exercise
Figure out the output of the two programs for an input value of 7. Do that first without executing the program, and then execute the programs to check your answers.
Switch Statement
Sometimes you have more than two alternative behaviors to select.
Here’s an example in which we rewrite the print_score
function to
also classify a score in various categories:
void print_score (int p) { if (p < 0) { printf("Something is wrong: your score is negative!\n"); return; } printf("You have %d point", p); if (p != 1) printf("s"); printf("\nThat is "); switch (p) { case 0: case 1: printf("a bit low"); break; case 2: printf("okay"); break; case 4: printf("very "); case 3: printf("good"); break; case 5: printf("excellent!"); break; default: printf("unbelievable!"); break; } printf("\n"); }
A switch statement allows you to jump to one of many points in a compound statement based on the value of a given expression. The syntax is as follows:
Syntax: switch
(
expression )
statement
The expression in parens after the switch
keyword must be of an
integer type. In the example, the expression is defined by the
variable p
, which is of type int
, so that works. The statement
that follows is typically a compound statement in which some
statements may be
labeled as follows:
case
constant-expression :
statement (opt)
or
default
:
statement
A constant-expression in a case
label is typically a literal value
(an integer constant or a character constant) or in any case an
expression that is evaluated by the compiler, not by the program at
run-time. In our example above, we use numeric constants. The switch
statement may contain at most one default
label.
The execution of the switch statement starts with the evaluation of
the expression in parens, and then jumps to the label corresponding to
the value of that expression, or to the default
label if there is no
corresponding label and if there is a default
label. The execution
then continues sequentially through the compound statement. A break
statement terminates the execution of the switch
statement.
Quiz 1: What’s the output of print_score(0)
?
Quiz 2: What’s the output of print_score(3)
?
Quiz 3: What’s the output of print_score(4)
?
Quiz 4: What’s the output of print_score(7)
?
Quiz 5: What if there is no default
label? What’s the output of
print_score(7)
then? I didn’t tell you, so this is just to test
your intuition and prior knowledge.
Quiz for the wiz: The paragraph above states that a break
statement terminates the execution of the switch statement. Is that
always true? If not, write and test a counter-example.
Iteration Statements
You must be able to tell a computer to do something repeatedly. You can do that with iteration statements. There are three flavors of iterations: while-loops, do-while-loops, and for-loops. At a high-level, they are pretty much the same: the program executes some code repeatedly as long as a certain condition is true.
While Loops
In this most basic form of iteration, you have a condition and a statement:
Syntax: while
(
condition-expression )
statement
The while
statement repeatedly evaluates the condition expression
and executes the statement or “body” of the loop if the condition is
true. The condition expression is evaluated before the execution
of the statement, so if the condition is false at the beginning, the
while statement terminates immediately. For example:
int main () { int n; scanf("%d", &n); while (n > 0) { print ("%d\n", n); --n; } }
Quiz 1: What happens when you input 0?
Quiz 2: What happens when you input 10?
Do-While Loops
If you want to check the loop condition after the body of the loop, then you write a do-while loop.
Syntax: do
statement while
(
condition-expression )
;
The do
-while
statement first executes the statement or “body” of
the loop and then evaluates the condition expression, and if that is
true then executes again the body of the loop, and so on. Example:
int main () { int n; scanf("%d", &n); do print ("%d\n", n--); while (n > 0); }
Quiz 1: What happens when you input 0?
Quiz 2: What happens when you input 1?
Quiz 3: What happens when you input 10?
For Loops
You often want to write loops in which, in addition to checking the
loop condition, you do something right before the loop starts, and
something else at the end of each loop iteration. A for
loop makes
those things explicit.
Syntax: for
(
init-clause ;
condition-expression ;
iteration-expression )
statement
Example:
int main () { int n; scanf("%d", &n); for (int i = 1; i < n; i *= -2) print ("%d\n", i); }
Of course you can do the same with a while loop:
int main () { int n; scanf("%d", &n); int i = 1; while (i < n) { print ("%d\n", i); i *= -2; } }
Exercise
Write a function print_sequence(int n)
that, given a non-negative
number n
, outputs \(1, 2, \ldots, n\) on a single line with all
adjacent numbers separated by a single space. In fact, write three
variants of print_sequence(int n)
with a while
, do
-while
, and
for
loop, respectively.
Exercise
Write a function print_sequence_backwards(int n)
that, given a
non-negative number n
, outputs \(n, n-1, \ldots, 1\) on a single line
with all adjacent numbers separated by a single space. Write three
variants of print_sequence(int n)
with a while
, do
-while
, and
for
loop, respectively.
Exiting or Short-Cutting a Loop
In some cases you want to terminate a loop from within the body of the
loop, without having to wait for the evaluation of the loop-condition.
In other words, you want to break out of a loop. You can do that with
a break
statement. Example:
int main () { int n; scanf("%d", &n); for (int d = 2; d*d <= n; ++d) { if (n % d == 0) { printf ("%d is composite\n", n); break; } } }
Other times you’d want to short-cut the execution of the loop. That
is, you want to immediately go to the next iteration without going
through the rest of the body of the loop. You can do that with a
continue
statement. The continue
statement works in for
loops
as well as in while
and do
-while
loops. Here’s an example of a
for
loop:
void print_primes_less_than_100 () { int C[100]; for (int i = 0; i < 100; ++i) C[i] = 0; for (int i = 2; i < 100; ++i) { if (C[i]) continue; for (int j = i*i; j < 100; j += i) C[j] = 1; printf("%d ", i); } printf("\n"); }
Notice that the continue
statement short-cuts the execution of the
body of the loop, not the loop condition or the iteration expression
in a for loop. In the example above, the continue
statement
effectively jumps to the iteration expression ++i
that is then
followed by the evaluation of the loop condition j < 100
.
In while
and do
-while
loops, a continue
statement would
effectively jump to the evaluation of the loop condition.
The continue
and break
statements apply to their immediately
enclosing iteration statement. The break
statement also applies to
its immediately enclosing switch
statement. Example:
int main () { int A[100]; int n = 0; while (n < 100 && scanf("%d", &(A[n])) > 0) { int found = 0; for (int i = 0; i < n; ++i) if (A[i] == A[n]) { found = 1; break; } if (found) continue; printf("%d\n", A[n]); ++n; } }
Here the break
statement is directly contained in the for
loop and
only indirectly contained in the while
loop. So, the break
statement terminates the for
loop, not the while
loop. The
continue
statement is contained within the while
loop, so it may
short-cut the while
loop.
Did you try the example above? And the one above it
(print_primes_less_than_100
)? Remember, this is a read-do
document!
Exercise
Explain the semantics of the example above. Explaining the semantics means describing what the code does independent of how it does it.
Jump Statements
We have already seen some jump statements. The break
and continue
statements are jump statements, since they cause the execution of the
program to “jump” to some place in the program. This is an
illustration of the “jumps” in with the example above:
int main () { int A[100]; int n = 0; while (n < 100 && scanf("%d", &(A[n])) > 0) { int found = 0; for (int i = 0; i < n; ++i) { if (A[i] == A[n]) { found = 1; break; /* JUMP */ } /* | */ } /* | */ /* HERE <--------------------' */ if (found) continue; /* JUMP */ printf("%d\n", A[n]); /* | */ ++n; /* | */ /* HERE <--------------------' */ } }
There are two more jump statements: goto
and return
.
Go-To Statement
The goto
statement transfers the control of the program to a
specified statement in the code. The target statement is identified
by a label.
Syntax: goto
label ;
You can label any statement (including a null statement) as follows:
Syntax: label :
statement
For example:
int main () { int n = 0; iterate: printf("%d\n", n); ++n; if (n < 10) goto iterate; }
Here’s another, more interesting example:
#include <stdio.h> #include <ctype.h> int main () { int c; int n = 0; space: c = getchar(); if (c == EOF) goto end; if (isspace(c)) goto space; ++n; word: c = getchar(); if (c == EOF) goto end; if (isspace(c)) goto space; goto word; end: printf("%d\n", n); }
Return Statement
The return
statement terminates the execution of the current
function. The return
statement can also specify an expression that
defines the return value of the function received by the caller.
Syntax: return
expression (optional) ;
Exercises
Anima
The following program takes some text input and produces some output. You can think of the output as an encoding of the input.
#include <stdio.h> int main () { int c; while ((c = getchar()) != EOF) { switch (c) { case ' ': putchar('\n'); break; case '\n': putchar('\n'); putchar('\n'); break; case 'a': case 'e': case 'i': case 'o': case 'u': putchar(c); putchar('s'); default: putchar(c); } } }
Write a program that performs the inverse encoding. If you call the
program above anima
, and you call anima_reverse
the program you
write to undo the encoding, then you can test that your program works
as follows. First, edit a test input input1.txt
, then run the
following commands:
$ cat input1.txt | ./anima | ./anima_reverse > input1.out $ diff input1.txt input1.out