02 Imperative C
02 Imperative C
Introduction to Imperative C
The primary goal of this section is to be able to write programs that use I/O and
mutation.
2
Functional vs. Procedural Programming
In CS 135 the focus is on functional programming, where functions
behave very “mathematically”. The only purpose of a function is to return
a value, and the value depends only on the argument value(s).
Similarly, (sort numbers <) returns a new list that is sorted, whereas
the original list numbers remains unchanged.
4
Functional vs. Procedural Programming
In this course, our focus is on the procedural programming paradigm, a
subtype of the imperative programming paradigm.
In linguistics, imperative is one of multiple moods that signal the verb’s modality, e.g.,
• Indicative (facts, reality): “I am cranky.”
• Subjunctive (opinions, hypotheticals): “If I were rich …”
• Imperative (direct commands): “Get rich or die tryin’!”
5
Functional vs. Procedural Programming
In this course, our focus is on the procedural programming paradigm.
int main(void) {
trace_int(1 + 1); // First, do this ...
assert(3 > 2); // ... then do this ...
return EXIT_SUCCESS; // ... and then do this.
}
Consider the following “real world” example: You have a blank piece of paper, and
then you write your name on that paper.
You have changed the state of that paper: at one moment it was blank, and in the next
it was “autographed”.
In other words, the side effect of writing your name was that you changed the state of
the paper.
7
Types of Side Effects
In this course, we encounter two types of side effects:
• Input & Output (I/O for short)
• Mutation (memory modification & variables)
8
I/O
Text I/O (Input)
I/O
I/O is the term used to describe how programs interact with the “real
world”. For example, a program (“app”) on your phone may interact with
you in many ways.
int main(void) {
printf("Hello, CS136.");
}
printf is different than the tracing tools (trace_int, etc.) we use to debug and
informally test our code (more on this distinction later).
11
Text I/O – Line Break
The newline character ('\n') is necessary to properly format output to
appear on multiple lines.
12
Text I/O – Placeholders & Formatting
To output values, use a format specifier as placeholder within the format
string, and provide the data as an additional argument.
For an integer in “decimal format” the format specifier is "%d".
13
Text I/O – Placeholders & Formatting
To output a percent sign (%), use two (%%).
Similarly, to print a backslash (\), use two (\\), to print a quote ("), add
an extra backslash (\")
printf("This \\is\\\n\"confusing\"!\n");
14
Text I/O – Placeholders & Formatting
Some characters have special meaning in the syntax of printf
% => indicates the beginning of a placeholder
Generally, these special characters must be escaped if you want to use them in their
original meaning.
15
Text I/O – Placeholders & Formatting
Many computer languages have a printf function and use the same placeholder
syntax as C.
The full C printf placeholder syntax allows you to control the format and align your
output.
16
Quiz time!
Which of the following expressions would NOT display the output:
17
Quiz time!
Answers will be discussed in class!
18
Functions with Side Effects
Documentation & Terminology
void Functions
Functions with Side Effects
Both functions below return the same value: an integer (𝑛2 ).
20
Documenting Side Effects
The printf function has a side effect: it changes the output stream, i.e.,
the state of the system, permanently (and irrevocably). By calling
printf, the function noisy_sqr inherits its side effect.
21
Documenting Side Effects
If the side effect does not always occur, preface it with “may” in your
contract.
22
Side Effects versus Debugging
Statements used for debugging and informal testing (e.g., assert,
trace_int) are not considered side effects.
Leave your tracing code in your assignments: they will not affect your test
results.
Large software projects often have thousands of assert and tracing statements to aid
debugging.
They are usually disabled (or “turned off”) when a project is finalized to improve
performance, but they remain in the code.
23
Side Effects versus Debugging
Our tracing tools print to a different I/O stream than printf (like writing on two
different pieces of paper):
Our tracing tools output to the stderr (standard error) stream, which is used for
errors and other diagnostic messages.
In Marmoset, and when edX performs an [I/O TEST], only the stdout stream is
tested.
In edX, the two streams may appear mixed together in the console output.
24
Functions with Side Effects: Terminology
In the context of I/O, be conscientious with your terminology.
int quiet_sqr(int n) {
return n * n;
}
This is poor terminology: quiet_sqr does not read input and does not
print any output.
25
Functions with Side Effects: Terminology
int noisy_sqr(int n) {
printf("I'm squaring %d\n", n);
return n * n;
}
int main(void) {
trace_int(noisy_sqr(7));
}
It is common for beginners to confuse output (e.g., via printf) and the return
value.
Ensure you understand the correct terminology and read your assignments very
carefully.
26
Functions with Side Effects: Terminology
int noisy_sqr(int n) {
scanf(...);
printf("I'm squaring %d\n", n);
return n * n;
}
int main(void) {
trace_int(noisy_sqr(7));
}
27
Quiz time!
Considering the two functions below and select all true statements.
28
Quiz time!
Answers will be discussed in class!
int noisy_sqr(int n) {
printf("Squaring \"%d"...\n", n);
return n * n;
}
29
void Functions
A function may be designed to only generate a side effect, and not return
a value.
The void keyword is also used to define a function that returns
“nothing”.
31
Variables
Definition
Mutation
Scope
System State
Another way for storing system state is storing information in memory,
and associating it with an identifier.
33
Manipulating State: Variables
We use variables to store mutable state information (values).
int my_variable = 7;
The equal sign (=) and semicolon (;) complete the syntax.
34
Manipulating State: Mutation
At every moment in time, a variable must have a value. When mutation
occurs, the state (value) of the variable changes.
int main(void) {
int m = 5; // definition (with initialization)
trace_int(m);
m = 6; // mutation
trace_int(m);
}
> m => 5
> m => 6
35
Manipulating State: Assignment Operator
In C, mutation is achieved with the assignment operator (=).
m = m + 1;
The “left hand side” (LHS) of the assignment operator must be the name
of a variable (for now).
The RHS must be an expression with the same type as the LHS.
The variable on the LHS is changed (mutated) to store the value of the
RHS. In other words, the RHS value is assigned to the variable.
36
Manipulating State: Assignment Operator
The assignment operator is not symmetric:
x = y = z = 0;
x = y = 0;
x = 0;
Avoid having more than one side effect per expression statement.
38
Manipulating State: Assignment Operator
Remember, always use a double == for equality, not a single = (which we now know
is the assignment operator).
if (i = 13) {
printf("Disaster!\n");
}
Both initialization and assignment use the equal sign (=), but they have
different semantics.
The distinction is not too important now, but the subtle difference
becomes important later.
x += 2; // => x = x + 2;
42
More Assignment Operators
There are also increment and decrement operators that increase or
decrease a variable by one (either prefix or postfix).
If you follow our “one side effect per expression” rule it does not
matter if you use prefix or postfix.
x = 5;
j = x++; // j => 5, x => 6
x = 5;
j = ++x; // j => 6, x => 6
44
More Assignment Operators
C allows you to define more than one variable at once.
int x = 0, y = 2, z = 3;
int x, y = 2;
45
Quiz time!
Which of these is the best way to assign (x + 1) to y?
A. y = x + 1;
B. y += x + 1;
C. y = x += 1;
D. y = ++x;
E. y - 1 = x;
46
Quiz time!
Answers will be discussed in class!
47
Constants
It is good style to use const when appropriate, as it:
• communicates the intended use of the variable,
• prevents “accidental” or unintended mutation, and
• may help to optimize (speed up) your code
We often omit const in the slides, even where it would be good style, to keep the
slides uncluttered.
48
Constants
A constant stores data that is immutable (not mutable). In other words,
the value of a constant cannot be changed.
In this course, the term “variable” is used for both variable and constant identifiers.
In the few instances where the difference matters, we use the terms “mutable
variables” and “constants”.
49
Global and Local Data
Variables and constants can have global or local scope.
int main(void) {
int my_local_variable = 11;
trace_int(my_global_variable);
trace_int(my_local_variable);
}
> 7
> 11
50
Data Scope
The scope of data is the region of code where it is “accessible” or “visible”.
For global data, the scope is anywhere below its definition.
int main(void) {
printf("%d\n", course);
}
For now, always make sure you define your data above any code that
references it.
51
Block (Local) Scope
Local data has block scope. Its scope extends from its definition to the
end of the block it is defined in.
int foo(int n) {
// ...
int a = 42;
// ...
if (n > 0) { // beginning of the block where b is defined
// ...
int b = 136; // b is defined
// ...
} // end of the block where b is defined
// ...
}
52
Block (Local) Scope
Local data has block scope. Its scope extends from its definition to the
end of the block it is defined in.
53
Manipulating State: Variable Scope
Local variables are also known as block variables because their scope is restricted to
the block they are defined in.
Variables with the same name can shadow other variables from outer scopes, but this
is obviously very poor style. The following code defines three different variables
named n.
54
Manipulating State: Variable Scope
In older versions of C, all the local variable definitions had to be at the start of the
function block (before any statements).
This improves readability and ensures that when a variable is first used its type and
initial value are accessible.
55
Manipulating State: Mutating Global Variables
A function that mutates a global variable does have a side effect (which
makes it a impure function).
int main(void) {
trace_int(cll_cnt());
trace_int(cll_cnt());
trace_int(cll_cnt());
}
int main(void) {
trace_int(addn(5));
n = 100;
trace_int(addn(5));
}
57
Manipulating State: Mutating Local Variables
Mutating a local variable does not give a function a side effect. It does
not affect state outside of the function (global state).
int add1(int n) {
int k = 0;
k += 1;
return n * k;
}
58
Manipulating State: Mutating Parameters
Parameters are nearly indistinguishable from local variables and can also
be mutated without causing any side effects.
This version of add1 is also a pure function, i.e., has no side effects.
59
Manipulating State: Mutating Parameters
In the following example, the variable n in main is never mutated
because only the value of n (here: 10) is passed to add1. In other words,
the variable n in main and the parameter n in add1 are independent
from each other.
int add1(int n) {
n += 1;
return n;
}
int main(void) {
int n = 10;
trace_int(n);
trace_int(add1(n));
trace_int(n);
}
> n => 10
> add1(n) => 11
> n => 10
60
Quiz time!
How many variables are in scope when // HERE is reached?
int main(void) {
int seven = 7;
foo(seven);
}
61
Quiz time!
Answers will be discussed in class!
A. 2: two, four
B. 3
C. 4
D. 5
E. 6
62
Global Dependency
A function that depends on a global mutable variable is “impure” even if
it has no side effects.
A “pure” function only depends on its argument values.
int n = 10;
int addn(int k) {
return k + n;
}
int main(void) {
assert(addn(5) == 15);
n = 100;
assert(addn(5) == 105);
}
63
Avoiding Global (Mutable) Variables
Global (mutable) variables are almost always poor style and should be
avoided.
Unless otherwise specified, you are not allowed to use global (mutable)
variables on your assignments.
On the other hand, global constants are great style and strongly
encouraged.
64
I/O
Text I/O (Input)
I/O-based Testing
Text Input
In this course we have provided some helper functions to make reading in
input easier.
The converse of the C output function printf() is the input function scanf(), but
we are not quite ready to use it (we introduce scanf() in Section 05).
66
Text Input: Reading Input
When running our program in edX via [Run Code], it will read input from
the Stdin window.
int main(void) {
int input = read_int(); // either the next integer
printf("%d", input); // or READ_INT_FAIL
}
67
Text Input: Reading Input
We can use a recursive approach to read all integers from input.
68
Text Input: Reading Input
We can use a recursive approach to read all integers from input.
69
Text Input: Reading Input
When reading input, you must be careful: once you read in a value, it can
not be read again!
Typically, you want to store the read value in a variable so you can refer to
it later. For example, consider this incorrect partial implementation:
The first read_int reads in the first integer, but then that value is now
“lost”. The next read_int reads in the second integer. This is likely not
the desired behaviour.
70
Text Input: Invalid Input
In this course, unless otherwise specified, you do not have to worry about us testing
your code with invalid input files.
The behaviour of read_int on invalid input can be a bit tricky (see CP:AMA 3.2). For
example, for the input:
> 4 23 skidoo 57
The first call to read_int returns 4, the second call returns 23, and any further calls
return READ_INT_FAIL.
71
Functions with Side Effects: Testing I/O
As we saw previously, assertion-based testing can test if functions return
the correct value:
int main(void) {
assert(quiet_sqr(3) == 9);
assert(quiet_sqr(7) == 49);
assert(noisy_sqr(3) == 9);
assert(noisy_sqr(7) == 49);
}
72
Functions with Side Effects: Testing I/O
int noisy_sqr(int n) {
printf("I'm squaring %d\n", n);
return n * n;
}
int main(void) {
assert(noisy_sqr(3) == 9);
assert(noisy_sqr(7) == 49);
}
73
Functions with Side Effects: Testing I/O
In edX there are two ways of “running” your program. The only difference
is the I/O behaviour:
• With the [Run Code] button, input is read from the Stdin-window.
Any output is displayed in the console (Code Output).
• With the [Run With Tests] button, input is read from an input file
(e.g., square.in). Then edX checks the output produced by the
program against the expected output file (e.g., square.expect) to see
if they match.
74
Functions with Side Effects: Testing I/O
We expect the output of this program to be:
> I'm squaring 3
> I'm squaring 7
>
To test that, we can create an .expect-file with that expected text. Even
though this program does not read any input, we must also create a
corresponding .in-file for the I/O test to work correctly.
75
Functions with Side Effects: Testing I/O
“Run With Tests” now runs the program and compares the its output
(Produced output) with the content of the .expect-file (Expected output).
If both match, the test passes.
76
Testing Harness
For testing
• values returned by a function: assertion-based testing (e.g., in main)
• input and output by a function: I/O-based testing (.in and .expect files)
77
Testing Harness
Here, test_sqr tests the noisy_sqr function via I/O-based testing by
continuously reading in arguments for noisy_sqr from input and
printing out the values returned by noisy_sqr.
int main(void) {
test_sqr();
}
78
Text Input: Reading Input
To test our program using I/O-based testing in edX, we could add the
following two test files:
test.in test.expect
> 0 > 0
> 1 > 1
> 4 > 16
> 1000 > 1000000
> -1 > 1
> -4 > 16
> -1000 > 1000000
>
79
Text Input: Input Formatting
When C reads in integer values, it skips over any whitespace, i.e., newline
('\n') and spaces (' ').
The inputs
> 1
> 2
> 3
> 4
> 5
and
> 1 2 3
> 4 5
80
Testing Harness
This strategy is useful for testing both “pure” and “impure” functions.
For this harness, new tests can be added by editing text files. This strategy allows
adding test without re-compiling the program.
81
Quiz time!
What would the output of that void foo(int n) {
function call foo(1) be? if (n == 1) {
printf("pen ");
}
[Select the most appropriate answer!] if (n == 2) {
printf("crayon ");
} else if (n == 1) {
A. > pen pen pen printf("pen ");
} else if (n == 1) {
B. > pen pen pen pen printf("pen ");
C. > pen crayon crayon }
if (n = 2) {
D. > pen pen crayon marker printf("crayon ");
E. > pen pen crayon } else if (n == 1) {
printf("pen ");
} else {
printf("marker ");
}
}
82
Quiz time!
Answers will be discussed in class!
83
End of the Session
84