5  Introduction to Programming with C

Author
Affiliation

Ben Clifford

Welsh Centre for Printing and Coatings

Published

October 22, 2024

lecture cover image showing architecture, some gates and an Arduino nano board

Introduction

C is a high-level structured programming language which is often used for writing microcontroller applications. This weeks lecture will cover good coding practices, C language operators and how they can be grouped as well as flow control structures.

Lecture Topics

In this chapter we will be looking at how to produce clear well commented programs; data transfer, arithmetic, logic and relational operators; and flow control structures including if, switch-case, for and while.

5.1 C-language

Code Formatting, comments, pre-processor directives, statements and operators


5.1.1 Code formatting

  1. Write comments that explain what the program does.
  2. Use descriptive names for variables and functions.
  3. Use tabs to indent nested code blocks.
  4. Give each class/function one purpose.
  5. Delete unnecessary/redundant code.
  6. Readability is more important than cleverness.
  7. Adopt and maintain a consistent coding style.
  8. Write good comments.
  9. Refactor, refactor, refactor.
  10. Take regular backups and implement version control.

/*
 * This is a demonstration program for EG-151 microcontrollers that shows a tidy well commented program.
 * This program asks a user to input two numbers and uses the function findmax to determine which is the largest.
 *
 * Author: Ben Clifford
 * Date: 12/10/2019
 */

// include library files
#include <stdio.h> //Required for scanf and printf

// main function
int main(void) {
    // Variable Declarations
    int num1, num2, maxnum;
    // Function Declarations
    int findmax(int, int);

    printf("Enter the first number: ");
    scanf("%u", &num1);
    printf("Enter the second number: ");
    scanf("%u", &num2);

    /* invoke the function findmax passing numl and num2
     * as arguments and storing the result in the variable maxnum
     */
    maxnum = findmax(num1, num2);
    printf("The maximum number is %u", maxnum);
}

// findmax Function Definition
int findmax(int x, int y) {
    int maximum_number;
    if (x >= y) {
        maximum_number = x;
    }
    else {
        maximum_number = y;
    }
    return maximum_number;
}

5.1.2 C language comments

  • Comments are added to code in order to make the program easier to read and understand at a later time or by another reader.
  • When the compiler is reading the file, the comments are ignored.
  • Comments must follow a set of rules and a particular format.
    • In the ‘C’ language comments are surrounded by /* comment */ and can span multiple lines
    • … or start with // if they are on a single line.

5.1.2.1 Examples of comments

Single line comment \(\rightarrow\) typically found after a statement

A = 10; // This line sets variable A to 10

Multi line comment \(\rightarrow\) typically used at the start of a program or to detail a block of code or a function

/* 
 * This comment 
 * spans multiple lines 
 */

5.1.3 Pre-processor directives

  • The pre-processor is part of the compilation process and runs before the code is compiled.
  • The pre-processor looks for lines of code beginning with a # and evaluates them before compilation.
  • There are also a number of predefined macros which can be called and these are surrounded by “__” characters.
  • The most commonly used pre-processor directives, and also the ones we will be using, are #include and #define.

Some pre-processor directives

#include
#define
#undef
#if
#elif
#endif
#ifdef
#ifndef
#error
__FILE__
__LINE__
__DATE__
__TIME__
__TIMESTAMP__
# macro operator
## macro operator

5.1.3.1 The #include directive

The #include directive is used to include header files which contain declarations of existing and frequently used functions that can be substituted into your program.

Header files may contain custom function definitions, common functions, secondary data type definitions, etc.

/* 
  This variant is used for system header files. 
  The pre-processor searches for a file named in a 
  standard list of system directories
*/
#include <header_file.h>
/* 
   This variant is used for header files of your own program. 
   It searches for the file name first in the current directory, 
   then in the same directories used for system header files. 
*/
#include “my_library.h”

5.1.3.2 The #define directive

The #define directive is used to define a macro – when the macro name appears in code it will be replaced with the definition stated.

Example 1: define a value for a symbol

#define pi 3.1415

In this example the programmer is stating that the letters pi represent the number 3.1415. Now, in the program they can write

A = pi * r * r;

and after pre-processing the compiler will see

A = 3.1414 * r * r;

Example 2: define a block of reusable code

#define cube(x) (x) * (x) * (x)

If the programmer writes:

v = cube(2) // = 8

The compiler sees:

v = (2) * (2) * (2)  // = 8

5.1.4 Statements in C

  • C functions are made up of statements, each of which is terminated with a semicolon ;.
  • A statement is made up of at least one operator and some operands.
  • Operands can be variables or data.
Note

Missing the semicolon is one of the most common causes of compilation errors in C programming.


5.1.4.1 C functions

Figure 5.1 is a schematic diagram of the C function. You can think of it as something that takes some inputs (arguments) and returns some output.

A schematic diagram of a function in C.
Figure 5.1: A schematic diagram of a function in C

5.1.4.2 C code for a function

A simple function is presented below.

/* 
 * This function is called a_function. 
 * It takes no aguments (inputs).
 * It returns no output.  
 */
void a_function(void)
{
    statement1;
    statement2;
}

5.1.5 Variables in C

The term variable is used for a name which describes a memory address. These names follow the same naming convention as used for functions1.

For example, a variable named num1 describes a particular memory address at which the first number is found and a second variable num2 describes a second memory address while a third variable, total, is a third memory address.

In a function we may then have the following statements:

num1 = 10;
num2 = 20;
total = num1 + num2; // 30!

Each of the statements in the example above tells the computer system to assign/store a value into the memory address described by the variable. This is called assignment.

Assignment statements use the equals (=) sign to assign the value on the right of = to the variable on the left of =2


5.1.6 Operators in C

Each statement or instruction is made up of operators and operands, where the operator represents an action and the operands represents the data.

In C, the operators can be split into four categories based on the type of actions that they perform3.


They are:

We will consider each of these in the following sections.


5.1.6.1 Data transfer operators in C

In the C language, the data transfer operations are mostly covered by assignment without ever having to deal directly with the registers.

For example:

a = 10;
b = 20;
sum = a + b;
PTFD = sum; // output total to PORT F

This is because C ia high-level language and the compiler takes care of the details.


5.1.6.2 Arithmetic operators in C

The order of arithmetic operations follow the BODMAS (BIDMAS) rules4.

Operator Used for
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulus
++ Increment
-- Decrement

Examples

Assume a, b, c and r = 2 are defined as variable of type integer (int)5.

a = r * 5 + 6 / 3;    // -> a = 12;

Brackets can be used to improve readability6

b = (3.14 * r * r);   // -> b = 12 not 12.56!   

There are also special operators like increment and decrement

c = c++;  // -> c = c + 1
d = d--;  // -> d = d - 1;

Assume d to h are defined as integer variables (int) and i as a floating point number (float):

d = 5 / 3;    // -> d = 1:  gives the whole part of the fraction
e = 5 % 3;    // -> e = 2: gives thremainder of the fraction
f = 6 % 3;    // -> f = 0: gives 0 as there is no remainder
g = 5.5 / 2;  // -> g = 2: float is converted to int
h = 5.5 % 2;  // -> invalid – will not compile
i = 5.5 / 2;  // -> i = 1.75    - evaluates correctly if i is defined as a float 

The same rules apply to multiplication operation.


5.1.6.3 Logical operators in C

Logical operators are used in expressions which return true (1) or false (0).

Operator Meaning
&& Logical and
|| Logical or
! . Logical not

Example of the and operator (&&)
/* 
 * If the voltage is greater than 10 and the
 * current is less than 20 the condition is true
 * and the value of the expression will be 1, 
 * otherwise it is false and the value of the 
 * expression will be 0.
 */
if ((voltage > 10) && (current < 20)) {...}

Example of the or operator (||)
/*
 *  If the voltage is greater than 10 or the
 *  current is greater than 20 the condition is
 *  true and the value of the expression will be
 *  1, otherwise it is false and the expression 
 *  will be 0.
 */
if((voltage > 10) || (current > 20)) {...}

Example of the not operator (!)
/* 
 *  The unary operator (!) is usually used to
 *  turn true into false and vice versa.
 */
c = 0;  // -> c = 0 which is "false"
d = !c; // -> d = 1 which is "true".

Bitwise logical operators in C

In addition to the logical operators designed to evaluate multiple conditions there are bitwise logical operators which operate on the binary digits (bits) of their operands.

Operator Used for
& bitwise AND
| bitwise OR
^ bitwise XOR
~ bitwise NOT
>> shift right
<< shift left

Example of the bitwise and operator
b = 0xA3 & 0xD5; // -> b = 0x81

\[ \begin{array}{lrr} & 1010 & 0011 \\ \& & 1101 & 0101 \\\hline & 1000 & 0001 \end{array} \]


Example of the bitwise or operator
c = 0xA3 | 0xD5; // -> b = 0xF7

\[ \begin{array}{lrr} & 1010 & 0011 \\ | & 1101 & 0101 \\\hline & 1111 & 0111 \end{array} \]


Example of the bitwise not operator
c = ~0xA3; // -> c = 0x5C

\[ \begin{array}{lrr} \sim & 1010 & 0011 \\\hline & 0101 & 1100 \end{array} \]


Bitwise operators used for I/O ports

The use of these operators are important when working with microcontrollers, in particular in the case of I/O, as they can be used to mask bits of a port. This is useful in C as we have limited data transfer options, i.e. we have to read/write to an entire port (memory location) rather than an individual bit.

PTAPE = 0x0F;       // bit pattern: 0000 1111
PTAD = 0xF0;        // bit pattern: 1111 0000
PTAD = PTAD | 0x0C; // bit pattern: 1111 1100

Since XORing any bit with a 1 forces it to return the opposite value, it can be used to toggle the state of a port:

portA = portA ^ 0xFF; // e.g. 0101 0110 -> 1010 1001

Relational operators in C

The final group of operators are the relational operators which are used to test a relationship between two variables or a variable and data.

Relational operator Meaning
== is equal to
!= is not equal to
< is less than
<= is less than or equal to
> is greater than
>= is greater than or equal to

Example: “is x equal to 2?” would be written as x == 27.

Relational operators are most often used in the expressions used in the conditions of the flow control structures discussed in Section 5.2.


5.1.6.4 Program control operators in C

These are discussed in Section 5.2.

5.2 Flow control

Flow control statements if, for, while, and switch

Decorative section background - showing code.


5.2.1 Flow control structures in C

Figure 5.2 illustrates the flow control structures that are provided in the C language. We will illustrate the most commonly used8 in the following sections.

An organogram showing a classification of the flow control strucures provided by the C language.
Figure 5.2: Classification of the flow control strucures provided by the C language.

5.2.2 Flow control structures to be considered

  1. The if statement
  2. The switch statement
  3. The while statement
  4. The for statement

5.2.3 The if statement in C

The if statement allows branching within code and can be used to check if a particular condition has been met.


Flow chart of the if statement

A flow chart illustrating the structure of the if-statement.
Figure 5.3: A flow chart illustrating the structure of the if statement

The equivalent of the if statement illustrated in the Figure 5.3 is:

Statement_1;
if (expression != 0) { // in C anything that is not 0 is true
    Statement_2;
}
Statement_3;
  • At line 3, if the condition has been met, the sequence of statements in the following block (here just line 4) is executed.
  • If the condition is not met at line 3, then Statement_2 (line 4) is not executed, and the program will branch over to the next statement outside of the flow control statement: here Statement_3 (line 5).

5.2.3.1 The if-else statement

The if-else statement allows one set of statements to be executed if the condition is met and an alternative set of statements ito be executed if the condition hasn’t been met.


Flow chart of the if-else statement

A flow chart illustrating the structure of the if-else statement.
Figure 5.4: A flow chart illustrating the structure of the if-else statement

The equivalent of the if-else statement illustrated in the Figure 5.4 is:

Statement_1;
if (expression != 0) { // in C anything that is not 0 is true
    Statement_2;
}
else {                 // expression is false == 0
    Statement_3;
}
Statement_4;

5.2.3.2 The if-elseif-else statement

The third example of the if statement is the if-elseif-else statement which allows multiple conditions to be tested and blocks of statements to be executed for each decision.


Flow chart of the if-elseif-else statement

A flow chart illustrating the structure of the if-elseif-else statement.
Figure 5.5: A flow chart illustrating the structure of the if-elseif-else statement

The equivalent of the if-elseif-else statement illustrated in the Figure 5.5 is:

Statement_1;
if (expression1) {      // expression1 is true
    Statement_2;
}
elseif (expression2) {  // expression2 is true
    Statement_3;
}
else {                  // neither expression1 nor expression2 is true
    Statement_4;
}
Statement_5;

5.2.4 The switch statement in C

  • The switch statement allows selection between several possible defined options.
  • The test condition is referred to as the expression and the choices are referred to as the cases9.
  • Each switch expression will have a default choice for when no choices match the expression.
  • The switch statement represents an easier and more readable way of managing multiple if statements10

Flow chart of the switch statement

A flow chart illustrating the structure of the switch statement.
Figure 5.6: A flow chart illustrating the structure of the switch statement

The equivalent of the switch statement illustrated in the Figure 5.6 is:

Statement;
switch (expression1) { // branch according to the value of the expression
    case case_1 :
        code_block_1;
        break; // break is needed to avoid drop through to next case
    case case_2 :
        code_block_2;
        break; // break is needed to avoid drop through to next case
    case case_3 :
        code_block_3;
        break; // break is needed to avoid drop through to default case
    // you can have any number of case statements
    
    default : // optional
        default_code_block;
}
Statement_2;

Example program with a switch statement

char student_grade = 'B';
printf("Your grade was %c: ",student_grade);
switch (student_grade)
{
   case 'A':
     printf ("excellent!\n");
     break; // prevents fall-through to default
   case 'B' :
     printf("very good!\n");
     break;
   case 'C' :
     printf("good!\n");
     break;
   case 'D' :
     printf("satisfactory!\n");
     break;
   case 'E' :
     printf("needs work!\n");
     break;
   case 'F' : 
     printf("sorry you failed!\n");
     break;
   default: 
     printf("Error! The grade %c is invalid\n",student_grade);
}

If the break was not present, the program would drop down to the next case which is not usually what you want. Instead, you usually break out to the case statement as soon as a valid match is made.

You can play with this program here: grader. See what happens when you remove break.


5.2.5 The while statement in C

In order to write a function that loops, i.e. execution of a sequence of statements until a particular condition is met, a while statement can be used.

The while statement allows for a block of statements to be repeatedly executed as long as a condition is true.


5.2.5.1 Flow chart of the while statement

A flow chart illustrating the structure of the while statement.
Figure 5.7: A flow chart illustrating the structure of the while statement

The equivalent of the while statement illustrated in the Figure 5.7 is:

Statement;
while (expression) {
    Statement2;
}
// end of while-loop
Statement3;

The statements at line 2 are executed again and again until condition is false. Clearly, if the loop is to end, one of the statements needs to change the condition from true to false.


5.2.5.2 The do-while statement

The do-while statement is almost identical to the while statement however the condition is checked after the statements have run.


Flow chart of the do-while statement

A flow chart illustrating the structure of the do-while statement.
Figure 5.8: A flow chart illustrating the structure of the do-while statement

The equivalent of the do-while statement illustrated in the Figure 5.8 is:

Statement;
do {
    Statement2;
}
while (expression);
// end of do-while loop
Statement3;

The statements at line 2 are executed and then if condition is true, they are executed again.

This means that even if condition is false, the statements are executed one time.

When condition is false, the loop ends.

Warning

Be careful: it is very easy to get stuck in an infinite while loop if your test condition is always true.

For either loop to terminate, one of the statements needs to change the condition from true to false.


5.2.6 The for statement in C

Another way of writing a while statement is to use a for loop.

The term loop is used for the execution of a sequence of statements until a particular condition is met.


5.2.6.1 Flow chart of the for statement

A flow chart illustrating the structure of the for statement.
Figure 5.9: A flow chart illustrating the structure of the for statement

The equivalent of the for statement illustrated in Figure 5.9 is:

for (initialize counter; counter < final value; increment counter) {
    statements;
}
statement;
Note

The initialization and increment expressions in the for-loop are implicit in the for statement, but have to be drawn explicitly in the equivalent flow-chart.


Example for loop

Here is an actual for loop which solves the problem given in Lecture 3: Example 2.

It better illustrates how it might be written in a real program:

int sum, x;
/* Add the numbers from 1 to 10 and print the sum */
for (x = 0; x < 11; x++) {
    sum = sum + x;
}
printf("sum = %d", sum);

The sequence performed by the for loop is:

  1. Line 3:—the variable x is initialized to 0;
  2. Line 4:—the statements in the block11 (line 4) are executed in sequence;
  3. Line 3:—x is incremented by 1 (x++ \(\rightarrow\) x = x + 1);
  4. Line 3:—the value of the logical expression x < 11 is evaluated: if it is true we return to step 2.
  5. Line 3:—if x < 11 is false (i.e. when x == 11) the loop ends the result is printed to the screen (line 5).

To execute this program see sum.c.


5.2.6.2 Equivalence of for and while

You can always write a for loop using while12.

The previous example could be written:

int sum, x;
// initialize x
x = 0; 
while (x < 11) 
{
    sum = sum + x;
    x++; // increment x
}
printf("Sum = %d",sum);

It is arguable that the for loop is easier to read and understand.


5.2.6.3 Nested for statements

A flow chart illustrating the structure of the nested-for statement.
Figure 5.10: A flow chart illustrating the structure of the nested for statement

The equivalent of the nested for statement illustrated in Figure 5.10 is:

for (counter1 = 0; counter1 < 60; counter1++) {
    for (counter2 = 0; counter2 < 60; counter2++) {
        statements;
    }
}

See nested-counter for an executable example of this program.

What could you use this nested for-loop for?

Summary

In this section we have:

  • Covered basic concepts in coding to create clear and concise code as well as how to add comments to key lines of a program.
  • Introduced and discussed the different operators available to the C language programmer including how they are categorized.
  • Looked at the flow control statements available in the C language as well as how they are represented using flowchart diagrams and examples of the required syntax.

On Canvas

On the canvas course page, there is a series of short videos providing a history of the C language and a brief overview of programming paradigms as well as videos on functions and data type with a quiz to test your knowledge.


Any Questions?

Please use the Course Question Board on Canvas or take advantage of the lecturers’ office hours.


Next time



  1. Similarly to a function these variable names need to be declared before their use indicating their data type. More on this can be found in the self-directed study material for on Canvas for this week.↩︎

  2. Please do not confuse = in C with equality in mathematics. After assignment, the value in the variable can change. In mathematics \(a = b\) means that \(a\) is always equal to \(b\). If we change the value of \(b\), the value of \(a\) changes too. In C, a = b copies the current value of b into the storage assigned to a. If we change the value of b later, a will not change. To confuse matters still further, there is another use of the equals symbol: == means is equal to and is used in decision statements such as is num1 equal to num2?↩︎

  3. We will see similar categories of operators when we come to look at assembly language. For example, for data transfer we have the register instructions LDI (load register immediate), LDS (load register from store), and STS (store register to store).)↩︎

  4. BODMAS is a mnemonic which stands for Backets, Operations, Division/Multiplication, Addition/Subtraction. It describes the order of calculation in an expression that involves operators. Brackets, which are considered first, are used to disambiguate expressions that would otherwise produce wrong results. For example a + b/c is intrepreted as \(a + (b/c)\) not \((a + b)/c\).↩︎

  5. An integer type means the value is a whole number (not a fractional number) that can be positive, negative, or zero.↩︎

  6. The execution of expression 3.14 * r * r would most likely result in a decimal (floating point) number. This would be truncated to an integer before it is assigned to b. This is a common cause of mathematical error in programming.↩︎

  7. Important: don’t confuse the double equals sign (==) with =. The latter is used for assignment operations. That is (x = 2) is different from (x == 2).↩︎

  8. Most of the unconditional branching statements such as goto (for an unconditional branch to a label) and continue (to ignore a condition without breaking out of a loop) are rarely used in modern programs. The break statement is often used in switch statements and occasionally for breaking out of a loop when some exit condition is met.↩︎

  9. Each case must be a constant expression: i.e. a number or a character.↩︎

  10. i.e. avoids ifelseifelseifelseifelseif – … – else↩︎

  11. in the C language a block is any sequence of statements surrounded by curly brackets { ... }↩︎

  12. Indeed most c-compilers produce equivalent code for for and while.↩︎

Copyright © 2021-2024 Swansea University. All rights reserved.