0% found this document useful (0 votes)
20 views84 pages

02 Imperative C

Uploaded by

Arnav Shukla
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views84 pages

02 Imperative C

Uploaded by

Arnav Shukla
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 84

CS136

Introduction to Imperative C

January 18 – 23, 2024


Introduction to Imperative C
• Readings: CP:AMA 2.4, 3.1, 4.2–4.5, 5.2, 10
• The ordering of topics is different in the text
• Some portions of the above sections have not been covered yet
• Some previously listed sections have now been covered in more detail

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).

The functional programming paradigm is to only use constant values that


never change. Functions return new values rather than changing existing
ones.

A programming paradigm can also be thought of as a programming “approach”,


“philosophy” or “style”.
3
Functional vs. Procedural Programming
(define n 5)
(add1 n) ;; 6
n ;; 5

With functional programming, (add1 n) produces a new value; it does


not actually change n. Once n is defined, it is a constant and always has
the same value.

(define numbers '(15 23 4 42 8 16))


(sort numbers <) ;; '(4 8 15 16 23 42)
numbers ;; '(15 23 4 42 8 16)

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.

In procedural programming, a sequence of instructions (or “statements”)


are executed. We have already seen this:

int main(void) {
trace_int(1 + 1); // First, do this ...
assert(3 > 2); // ... then do this ...
return EXIT_SUCCESS; // ... and then do this.
}

Many modern languages are “multi-paradigm”. Racket was primarily designed as a


functional language but also supports procedural language features.
6
Side Effects and State
In general, a side effect occurs when the state of something “changes”.

State refers to the value of some data (or “information”) at a moment in


time.

The most significant difference between the functional and procedural


paradigms is that procedural programming uses side effects, functional
programming does not.

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)

Both involve changing the state of the system permanently.

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.

Input may include:


• System input (Files, network data)
• User input (Touch screen, voice)
• Sensor input (Camera, Accelerometer, GPS)

Output may include:


• Visual (screen)
• Auditory (sounds)
• Tactile (vibration)

In this course, we only use simple text-based I/O.


10
Text I/O
To display text output in C, we use the function printf.

int main(void) {
printf("Hello, CS136.");
}

> Hello, CS136.

The (first) parameter of printf is a string, sometimes called format


string (see later).
Until we discuss strings in Section 09, this is one of the few places you are
allowed to use strings.

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.

int main(void) { int main(void) {


printf("Hello, CS136."); printf("Hello, CS136.\n");
printf("C is fun!"); printf("C is\nfun!");
} }

> Hello, CS136.C is fun! > Hello, CS136.


> C is
> fun!

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".

printf("2 plus 2 is: %d", 2 + 2);

> 2 plus 2 is: 4

The %d placeholder is replaced with the value of the additional argument.

There can be multiple placeholders, each requiring an additional


argument.

printf("%d times %d is: %d", 3, 5, 3 * 5);

> 3 times 5 is: 15

13
Text I/O – Placeholders & Formatting
To output a percent sign (%), use two (%%).

printf("I am %d%% sure, you will do well in CS 136!\n", 100);

> I am 100% sure, you will do well in CS 136!


>

Similarly, to print a backslash (\), use two (\\), to print a quote ("), add
an extra backslash (\")

printf("This \\is\\\n\"confusing\"!\n");

> This \is\


> "confusing"!
>

14
Text I/O – Placeholders & Formatting
Some characters have special meaning in the syntax of printf
% => indicates the beginning of a placeholder

Some characters have special meaning in the syntax of C


' => surrounds a character: char c = 'c';
" => surrounds a string: printf("Hello!");
\ => indicates that next char has special meaning: \t
escapes a character with special meaning: \"

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.

printf("4 digits with zero padding: %04d\n", 42);

> 4 digits with zero padding: 0042


>

See CP:AMA 22.3 for more details.

In this course, simple %d formatting is usually sufficient.


Using the incorrect format specifier can be catastrophic!

16
Quiz time!
Which of the following expressions would NOT display the output:

> The answer is 42.


>

[Select the most appropriate answer!]

A. printf("The answer is 42.\n");


B. printf("The answer ");
printf("is 42.\n");
C. printf("The answer is %d.\n", 42);
D. printf("The answer is 4 %d.", 1337 / 500);
E. printf("The answer is %d%d.\n", 4, 2 * true);

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 ).

// quiet_sqr(n) squares n. // noisy_sqr(n) squares n.


int quiet_sqr(int n) { // effects: produces output
return n * n; int noisy_sqr(int n) {
} printf("Squaring %d\n", n);
return n * n;
}

However, noisy_sqr does more than returning a value; it also produces


output. In other words, noisy_sqr has a side effect.

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.

// noisy_sqr(n) squares n and writes a confirmation to output.


// effects: produces output
int noisy_sqr(int n) {
printf("I'm squaring %d\n", n);
return n * n;
}

Clearly communicate such a side effect in the effects–section!

21
Documenting Side Effects
If the side effect does not always occur, preface it with “may” in your
contract.

// noisy_sqr(n) squares n and may write a confirmation to output.


// effects: may produce output
int excited_sqr(int n) {
if (n < 0) {
printf("In case you did not realize, n is negative!\n");
}
return n * n;
}

22
Side Effects versus Debugging
Statements used for debugging and informal testing (e.g., assert,
trace_int) are not considered side effects.

Do not document asserts or the use of tracing tools in the effects-section.

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):

By default, printf outputs to the stdout (standard output) stream.

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;
}

Informally, someone might say: “if you input 7 into quiet_sqr, it


outputs 49.”

This is poor terminology: quiet_sqr does not read input and does not
print any output.

Correct would be: “if you pass 7 to quiet_sqr, it returns 49.”

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));
}

For noisy_sqr, you should say: if you pass 7 to noisy_sqr, it outputs a


message and returns 49.

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));
}

Passing and returning occurs between functions.


Input and output occurs between a function and an external source,
e.g., stdin, stdout, or a file.

27
Quiz time!
Considering the two functions below and select all true statements.

int quiet_sqr(int n) { int noisy_sqr(int n) {


return n * n; printf("Squaring \"%d"...\n", n);
} return n * n;
}

[Select all that apply!]

A. Both functions return the same value.


B. Both functions are equivalent.
C. The output of the function call noisy_sqr(3) is 9.
D. The function quiet_sqr has no side effect, but noisy_sqr has.
E. By the way, there is a syntax error in noisy_sqr...

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”.

// display_score(score, max) displays the score out of max.


// requires: max and score must be non-negative
// max must not be smaller than score
// effects: produces output
void display_score(int score, int max) {
assert(max >= 0 && score >= 0);
assert(max >= score);
printf("Your score is %d out of %d.\n", score, max);
return; // this is optional
}

In a void function, the return–statement is optional and has no


expression; when the end a void function is reached, the control flow
automatically returns to the caller.
30
Procedural Programming: Side Effects
Some purists insist that a function with a side effect is no longer a
“function” in the mathematical sense and call it a “procedure” (or a
“routine”).

We are more relaxed: a function can have side effects.

The “imperative programming paradigm” is also closely related to the


“procedural programming paradigm”.

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;

To define a variable in C, we need (in order):


• the type (here: int)
• the identifier (here: my_variable)
• the initial value (here: 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

Mutation is a side effect.

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.

This is a side effect: the state of the variable has changed!

36
Manipulating State: Assignment Operator
The assignment operator is not symmetric:

x = y is not the same as y = x

Some languages use different operators to make it clearer that it is an asymmetric


assignment.
x := y "Smalltalk"
x <- y # R
37
Manipulating State: Assignment Operator
In addition to the mutation side effect, the assignment operator (=) also
returns the value of the expression on the right-hand side.
This is occasionally used to perform multiple assignments:

x = y = z = 0;

x = y = 0;

x = 0;

Or to use assignments in combination with other operators:

printf("y is %d\n", y = 5); // nope


printf("y is %d\n", y = 5 + (x = 3)); // why???
z = 1 + (z = z + 1); // uh oh :-(

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");
}

(i = 13) assigns 13 to i and returns the value 13, so the if-expression


always evaluates to true!

Some defensive programmers get in the habit of writing (13 == i) instead of (i ==


13). This causes an error if they accidentally use a single =.
39
Manipulating State: Variable Initialization
C allows you to declare variables without initializing them, but it is bad
style.

int my_variable; // declaring variable; variable remains


// uninitialized, though!
my_variable = 5; // Assigning value to variable

int my_variable = 7; // Initializing variable


// (= declaring & assigning)

Always initialize your variables.

In Section 04 we discuss the behaviour of uninitialized variables.


40
Manipulating State: Initialization vs. Assignment
The = used in initialization is not the assignment operator.

Both initialization and assignment use the equal sign (=), but they have
different semantics.

int n = 5; // initialization syntax


n = 6; // assignment operator

The distinction is not too important now, but the subtle difference
becomes important later.

This distinction is important in C++, see CS246.


41
More Assignment Operators
The compound addition assignment operator (+=) combines the addition
and assignment operator (for convenience).

x += 2; // => x = x + 2;

Additional compound operators include:


-=
*=
/=
%=

42
More Assignment Operators
There are also increment and decrement operators that increase or
decrease a variable by one (either prefix or postfix).

++x; x++; // => x += 1 => x = x + 1;


--x; x--; // => x -= 1 => x = x - 1;

If you follow our “one side effect per expression” rule it does not
matter if you use prefix or postfix.

The language C++ is a pun: one bigger (better) than C.


43
More Assignment Operators
The prefix (++x) and the postfix increment operator (x++) have different a precedence
within the order of operations.

x++ produces the “old” value of x and then increments x.


++x increments x and then produces the “new” value of x.

x = 5;
j = x++; // j => 5, x => 6

x = 5;
j = ++x; // j => 6, x => 6

++x is usually preferred to improve clarity and efficiency.

44
More Assignment Operators
C allows you to define more than one variable at once.

int x = 0, y = 2, z = 3;

Most modern style guides discourage this (bad style).

In the following example, x remains uninitialized.

int x, y = 2;

45
Quiz time!
Which of these is the best way to assign (x + 1) to y?

[Select the most appropriate answer!]

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.

To define a C constant, we add the const keyword to the type.

const int ANSWER = 42;

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.

const int PI = 3; // global constant


int my_global_variable = 7;

int main(void) {
int my_local_variable = 11;
trace_int(my_global_variable);
trace_int(my_local_variable);
}

> 7
> 11

Global data is defined outside of functions (at the top level).


Local data is defined inside of functions.

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);
}

int course = 136;

> use of undeclared identifier '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.

int foo(int n) { // parameters are in scope for the entirety


// ... // of the function
int a = 42;
// ...
if (n > 0) {
// ...
int b = 136;
// ...
}
// ...
}

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.

int n = 1; // global scope


int main(void) {
trace_int(n); // >> n => 1
int n = 2;
trace_int(n); // >> n => 2
{
int n = 3;
trace_int(n); // >> n => 3
}
}

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).

In C99, you may define a local variable anywhere in a block.

Modern programming guides recommend that you define a variable:


• in the narrowest scope possible
• as close to its first use as possible

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 call_counter = 0; // call counter

// cll_cnt() returns the number of times it has been called.


// effects: mutates global variable call_counter
int cll_cnt(void) {
call_counter += 1;
return call_countr;
}

int main(void) {
trace_int(cll_cnt());
trace_int(cll_cnt());
trace_int(cll_cnt());
}

> cll_cnt() => 1


> cll_cnt() => 2
> cll_cnt() => 3
56
Manipulating State: Mutating Global Variables
Even if a function does not have a side effect, its behaviour may depend
on other mutable global variables.

int n = 10; // global variable

// addn(k) returns the sum of k and the global variable n.


int addn(int k) {
return k + n;
}

int main(void) {
trace_int(addn(5));
n = 100;
trace_int(addn(5));
}

> addn(5) => 15


> addn(5) => 105

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;
}

The statement k += 1 has a side effect (mutation), but it only affects


state inside of the function (local state). Therefore, the function add1 has
no side effects and is still a pure function.

58
Manipulating State: Mutating Parameters
Parameters are nearly indistinguishable from local variables and can also
be mutated without causing any side effects.

// add1(n) returns n incremented by 1.


int add1(int n) {
n += 1;
return n;
}

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?

[Select the most appropriate answer!]

const int one = 1; A. 2


int two = 2;
B. 3
void foo(int three) { C. 4
int four = 4;
if (three % 2 == 0) { D. 5
int five = 5; E. 6
}
// HERE
int six = 6;
}

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.

read_int() returns either the next integer read from input or


READ_INT_FAIL.

The constant READ_INT_FAIL is returned by read_int() when


• The next integer could not be successfully read from input, or
• The end of input (e.g., EOF) is encountered

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.

// read_all_int() reads all integers from input and ...


// effects: reads from input
void read_all_int(void) {
int input = read_int();
if (input == READ_INT_FAIL) { // base condition
return;
} else { // recursive condition
// do something with input
read_all_int();
// do something with input
}
}

68
Text Input: Reading Input
We can use a recursive approach to read all integers from input.

// count_odd() reads all integers from input and returns the


// number of odd integers read.
// effects: reads from input
int count_odd(void) {
int input = read_int();
if (input == READ_INT_FAIL) { // base condition
return 0;
} else { // recursive condition
if (input % 2 == 1) { // input is odd
return 1 + count_odd();
} else { // input is even
return count_odd();
}
}
}

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:

if (read_int() == READ_INT_FAIL) { // Bad!


return 0;
} else if(read_int() % 2 == 0) { // Bad!
//...
}

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);
}

Assertion-based testing, however, cannot be used to test output, for


example, that of noisy_sqr.

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);
}

We expect the output of this program to be:


> I'm squaring 3
> I'm squaring 7
>

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)

There is an alternate approach for testing return values.

Instead of using assertion-based testing for returned values, we can write


a dedicated test function that reads in argument values from a .in-file,
calls the function to be tested with these values, and then prints out the
corresponding return values.

This strategy is known as a testing harness.

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.

// test_sqr() tests the function my_sqr.


// effects: reads from input
// writes to output
void test_sqr(void) {
int input = read_int();
if (input != READ_INT_FAIL) {
int result = noisy_sqr(n);
printf("%d\n", result);
test_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
>

The .in-file contains the values The .expect-file contains the


read by read_int. expected output from the program.

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

are indistinguishable to a function like read_int.

80
Testing Harness
This strategy is useful for testing both “pure” and “impure” functions.

On some assignment questions, we may build a testing harness for you


(later, you may be expected to build your own).

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

• Explain what a side effect is Any further


• Document side effects questions?
• Produce output via printf
• Read from input via read_int
• Explain variables and constants, and use them in
your implementation
• Explain scope and consider it in your implementation
• Use the assignment operator and its short forms

84

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy