Introduction To C Programming

Download as pdf or txt
Download as pdf or txt
You are on page 1of 37

4 C Programming

4.1 Introduction
This is only a very basic guide to get you started with the C programming language. There's lots
more where this came from....

4.1.1 Int Main: The First thing that you'll need

All C programs will have a part of them that looks like this:

int main ()
{
//There will be a whole lot of C program code here
}

This piece of code is the “entry point” into the program. The name “main” is reserved for the main
piece of code in your software.

The curly brackets “{}” form a pair. Within these brackets will be the main program code. In
general curly brackets are used to group multiple statements into one compound statement.
Comments come in two forms, the double slash as used above means that everything following on
the line is a comment. The other form of comment is like this:

/* This is
a multiple line
comment */

Everything between the slash and stars is a comment, no matter how many lines it spans. Not all
compilers support the use of the double slash form. Comments are ignored by the compiler; they are
purely for documentation purposes.

C is CASE SENSITIVE. A small letter is not the same thing as a capital letter. By default the C
function names will use small letters.

4.1.2 Variables: Places to store things


Variables are used to store information. There are many different types of things that you might
want to store, and therefore there are many different types of variables. Some of these types are as
follows:

• char
• unsigned char
• int
• unsigned int
• float
• double

87
88
The “char” type is used to represent a single character. This requires 8 bits (1 byte) of storage.
Chars may be stored in “signed” or “unsigned” format. If it is signed then the range of this
number is -128 to +127 decimal. If unsigned format is used then the range will be 0 to 255 decimal.
All of the ranges shown here include both ends. Two's compliment is used to represent the signed
numbers. A char may be thought of in two different ways. You could either regard it as a simple 8
bit number, or you could regard it as a letter of other ASCII symbol. Some people find this
confusing, but remember that each symbol that can be represented is simply an 8 bit number. The
variable stores that number. The only difference is in the interpretation that is used when reading
the number. A copy of the ASCII table is shown here: (sourced from
http://www.jimprice.com/ascii-0-127.gif)

Ignore the “control character” column for now; it will become clearer later on.

The integer (int) type is used to store integers of a moderate size in an efficient manner. The range
of storage depends on the compiler being used. For our PC based compiler it is 32 bits and for our
microcontroller compilers it will be either 8 or 16 bits.

• 32 bit range signed: - 2.14748×109 to + 2.14748×109


• 32 bit range unsigned: 0 to 4.29497×109
• 16 bit range signed: -32768 to 32767
• 16 bit range unsigned: 0 to 65536

The “float” data type is a 32bit floating point number. This is a number represented using a 32
bit representation in which precision is traded off for size. The bigger the number the fewer digits
on the right hand side of the decimal point.

Digital Electronics – EEE3017W (2008)


89
The “double” data type is similar to the float, but is a 64 bit representation.

The option to make some types unsigned is exercised by means of “type modifiers”, other type
modifiers are “long” and “const”.

We can use this to make our program more complicated:

int main ()
{
unsigned int length, breadth; /*declare 2 variables
called length and breadth*/
char fred;
//still no code here...
}

I can also declare arrays of variables by putting the size of the array (number of elements) after the
array declaration:

int arr1 [10];

This will create an array of 10 integers. The array can be represented graphically as follows:

arr1[0] arr1[1] arr1[2] arr1[3] arr1[4] arr1[5] arr1[6] arr1[7] arr1[8] arr1[9]

Each of the empty boxes can hold one integer. Note the syntax that is used if we wish to access one
element of the array. When we access an array element we state its index in square brackets. In C
all array indexes start from zero. There is no “option base” available. When we declare an array we
always put the number of elements in square brackets.

4.1.3 Output: Printing things on the screen


In order to make these examples more interesting we can print the output that the produce onto the
screen. This is done using the “printf” command. In its simplest form we can say:

#include <stdio.h>
int main ()
{
printf (“Hello World”);
}

The printf statement causes the text Hello World to be put on the screen. The printf
command is defined in a library called “stdio”, and for that reason we need the line
#include <stdio.h> to tell the C compiler where to find this command.

If we want to print a variable we will need to be slightly more complicated. We need to tell the C
compiler what formatting to use for our type of variable.

University of Cape Town


90
An example of this is shown here:

#include <stdio.h>

int main()
{
int temp1;
float temp2;
char temp3;

printf ("Number 1: ");

temp1 = 2;
temp2 = 34567890;
temp3 = 'X';

printf ("%i", temp1); //print an integer


printf ("%f", temp2); //print a floating point number
printf ("%c", temp3); //print a character
}

If we want to put each new number on a new line we can add formatting characters like this:
printf ("%i\n",temp1); //print an integer
printf ("%f\n",temp2); //print a floating point number
printf ("%c\n",temp3); //print a character

'\n' is a newline character. We can also put text and variables on a line like this:

printf("The magic answer is:%i\n",temp1); // print text and variables

In this case the variable will be inserted into the text at the location of the %i marker. The newline
is after the formatting string, and thus the text and the variable will appear on the same line. Using
the wrong formatting string for your variable type can result in strange behaviour.

4.1.4 Basic Arithmetic


C provides a set of basic arithmetic operators for simple operations. They are:

• +, -, *, / as you would expect and also


• ++ increment a variable
• -- decrement a variable

We can use these as follows:

Digital Electronics – EEE3017W (2008)


91
#include <stdio.h>
int main()
{
int temp1;
float temp2;
float temp3;

temp1 = 2;
temp2 = 34567890;
temp3 = temp1*temp2;

printf ("The product is: %f\n",temp3); //print an integer


}

We can increment a variable as follows:

int temp1;
temp1 = 2;
temp1++;
//at this point temp1 = 3.

We can also decrement:


int temp1;
temp1 = 2;
temp1--;
//at this point temp1 = 1.

There is one little catch: Pre and Post increment.

Example:
int temp1, temp2;
temp1 = 3;
temp2 = temp1++;
//at this point temp1= 4 and temp2=3.

This is because temp1 was only incremented after the assignment had taken place.
If you want to increment temp1 before the assignment takes place you do it like this:

int temp1, temp2;


temp1 = 3;
temp2 = ++temp1;

The same principle applies to decrementing.

C provides a shorthand for some arithmetic operations. It is shown here:

temp = temp + 10; //perfectly legal

is equivalent to:

temp += 10; //cryptic C shorthand. Also applies to -,*, /

University of Cape Town


92

4.1.5 Decisions: The if statement


The “if” statement is used to make decisions based on a condition. The basic usage of this
statement is as follows:

if (condition) statement;

The condition must evaluate to either true or false. C does not provide Boolean data types. Rather
the “false” is represented by zero (e.g. an int or char with all the bits cleared) and true is
represented by non-zero.

Example:
#include <stdio.h>

int main()
{
int temp1;
temp1 = 2;
if (temp1)printf ("True"); // Will print the word True.
}

Example:
#include <stdio.h>
int main()
{
int temp1;
temp1 = 0;
if (temp1)printf ("True"); // Will NOT print the word True.
}

There is also an optional 'else' condition:

#include <stdio.h>
int main()
{
int temp1;
temp1 = 1;
if (temp1)printf ("True");
else printf ("False");
}

How does the compiler know where the if statement ends? C has a simple rule for if/else
statements: An if statement will ALWAYS only execute one statement or compound statement.
Compound statements are formed by enclosing a set of statements in curly brackets. Look carefully
at the difference between these two code examples:

Example 1:
if (temp1)
printf ("True"); // prints out if temp1 is true
printf ("another statement"); //prints out irrespective of temp1

Example 2:
if (temp1)
{
printf ("True"); // prints out if temp1 is true
printf ("another statement"); // prints out if temp1 is true
}

Digital Electronics – EEE3017W (2008)


93

Forgetting the curly braces is a common source of programming errors. Some programmers always
put the opening curly bracket on the same line as the if statement.

4.1.6 Logical, Comparison and Bitwise Operations


In order to form a useful set of conditions for if statements and similar constructs C has a range of
built in operators for use when comparing two conditions. The most basic of these are AND, OR
and NOT.

In order to AND two logical conditions together we use the && operator. An example:

#include <stdio.h>
int main()
{
int temp1;
int temp2;
temp1 = 0;
temp2 = 1;
if ((temp1)&&(temp2))
{
printf ("True"); /* only prints out if temp1 is true and temp2 is
true.*/
}
}

Notice in the above example how the individual conditions have been bracketed. This removes all
doubt about the interpretation of this condition.

We can OR two conditions using the “double pipe” operator:


#include <stdio.h>
int main()
{
int temp1;
int temp2;
temp1 = 0;
temp2 = 1;
if ((temp1)||(temp2))
{
printf ("True"); /* this prints out if either temp1 is true or temp2
is true*/
}
}

We can NOT a condition by using the ! Operator:

#include <stdio.h>
int main()
{
int temp1;
temp1 = 0;
if (!(temp1))
{
printf ("False"); //this prints out if temp1 is false
}
}

University of Cape Town


94
C also provides a nice range of comparison operators. These are:

• == Double equal sign. Use to check equality

Example:

#include <stdio.h>
int main()
{
int temp1;
int temp2;
temp1 = 10;
if (temp1==10)
{
printf ("True"); //this prints out if temp1 is 10
}
}

Be careful over here. The single = is used for assignment. This is shown in the line temp1 = 10.
The double == is used as an equality operator. If you use a single = in an if statement it will
perform assignment instead of comparison!

Example:

#include <stdio.h>
int main()
{
int temp1;
int temp2;
temp1 = 0;
if (temp1=10) //BIG BUG HERE...
{
printf ("True"); //this ALWAYS prints out
}
printf ("%i",temp1); //This prints new value of temp1, i.e 10
}

If you want to see if two things are not equal you use the != operator:

if (temp1!=10)
{
printf ("True"); //this prints out if temp1 is not 10
}

You can also check relative magnitudes of numbers using the >, < <= and >= operators. These
are all shown in this example:

Digital Electronics – EEE3017W (2008)


95
#include <stdio.h>
int main()
{
int temp1;
temp1=0;

if (temp1<=10)
{
printf ("Less or equal"); /* prints if temp1 is less
than or equal to 10 */
}
if (temp1>=10)
{
printf ("Greater or Equal"); /* prints if temp1 is
greater than or equal to 10 */
}
if (temp1<10)
{
printf ("Smaller"); /* prints if temp1 is less
than 10 */
}
if (temp1>10)
{
printf ("Greater"); /* prints if temp1 is
greater than 10 */
}
}

The above operators all work on a whole variable at a time. There are also bitwise operators which
work on individual bits of a variable. These operators are AND, OR, NOT and XOR.
The OR operator is shown in action here. The symbol for bitwise OR is a single pipe | symbol.
Don't confuse it with a double pipe symbol.

#include <stdio.h>
int main()
{
int temp1;
int temp2;
int temp3;
temp1 = 0x24; //Hexadecimal 24 written like this
temp2 = 0x42;
temp3 = temp1|temp2; //Bitwise OR operator
printf ("%x",temp3); //prints temp3 in hexadecimal. Output is 66, hex.
}
The bitwise AND operator is a single ampersand (&), as shown in this example:

temp1 = 0x78; //Hexadecimal 24 written like this


temp2 = 0x42;
temp3 = temp1&temp2; //Bitwise AND operator
printf ("%x",temp3); //prints temp3 in hexadecimal. Output is 40, hex.

The bitwise NOT operator is the tilde symbol, '~'.

temp1 = 0x78; //Hexadecimal 24 written like this


temp3 =~temp1; //Bitwise AND operator
printf ("%x",temp3); //prints temp3 in hexadecimal. Output is fffff87 hex.
temp3 =!temp1;
printf ("%x",temp3); //prints temp3 in hexadecimal.Output is 0

In this example there are a few important features. Firstly notice the difference between the '!'
operator and the '~' operator. The '~' has taken each bit in temp1 and inverted it. Secondly note
University of Cape Town
96
that the '!' has taken the number 0x78 as a binary condition (it is non-zero, so it is true) and
inverted that to arrive at an answer of zero, which is equivalent to a false condition. The “int” data
type is 32 bits long, so when we assign the number 0x78 to it, we are actually assigning it the
number 0x00000078. When this is complimented we therefore get 0xffffff87.

There are also left and right shift operators. These are the “>>” and “<<” operators. The direction in
which they shift is fairly intuitive.

temp1 = 0x78; //Hexadecimal 24 written like this


temp3 = temp1>>2; //Bit-shift temp1 two bits to the right
printf ("%x\n",temp3); //prints temp3 in hexl. Output is 0x1e
temp3 = temp1<<3;
printf ("%x",temp3); //Output is 0x3c0

Notice here that 0x78 = 120 decimal. 120 decimal divided by four (shifted 2 bits to the right) is 30
decimal, which is 0x1E, as expected. Similarly 120 decimal multiplied by 8 (shift 3 bits to the left)
is 960 decimal, which is 0x3C0.

4.1.7 The while Loop


The while loop is used to repeat a statement or compound statement as long as some condition is
true. The basic structure of a while loop is like this:

while condition statement

The condition must evaluate to either true or false. There must either be only one statement, or there
can be multiple statements grouped within brace brackets. The condition is formulated in the same
way as it was for the if statement.

One common mistake is to put a semicolon after the while statement. A semicolon is effectively a
statement, so any while loop with a semicolon immediately following it will be an infinite do-
nothing loop.

#include <stdio.h>
int main()
{
int temp1;
temp1 = 0;
while (temp1<10)
{
//compound statement...
temp1++;
printf ("%i",temp1); //this prints 12345678910,number by number
}

temp1 = 0;
while (temp1<10); //infinite loop
{
//compound statement...
temp1++;
printf ("%i",temp1); //this never executes.
}
}

The condition for a while loop is examined before any code inside the loop body is executed. This
means that if the loop condition is false the code in the loop will never be executed.

Digital Electronics – EEE3017W (2008)


97
4.1.8 The do...while Loop

This loop differs from the while loop in that the loop condition is examined at the end of the
statement. The basic structure of the loop is:

do statement while condition;

An few examples are shown:

#include <stdio.h>
int main()
{
int temp1;
temp1 = 0;
do
{
temp1++;
printf ("%i",temp1); // this prints 12345678910, number by number
} while (temp1<10);

temp1 = 20;
do
{
temp1++;
printf ("%i",temp1); //This prints 21
}
while (temp1<10); // this is only examined after the first printf

temp1 = 20;
while (temp1<10); // this is examined before any printf is executed
{
temp1++;
printf ("%i",temp1); //This never executes
}
}

4.1.9 Loop Modifiers: break and continue


Sometimes we want to modify the operation of a loop. These two statements do this for us.

The “break” statement tells the computer to break out of the loop immediately. The statement
following the loop will be executed after the program performs a break operation. The break
statement works identically in all types of loops.

University of Cape Town


98
#include <stdio.h>
int main()
{
int temp1;
temp1 = 0;
do
{
temp1++;
printf ("%i",temp1);//this only executes once, printing the number 1
break;
}
while (temp1<10);

temp1 = 0;
while (temp1<10)
{
temp1++;
printf ("%i",temp1);//this only executes once, printing the number 1
break;
}
}

The “continue” statement causes the loop to re-examine the loop condition and start the next
loop through the code if that condition is still true.

#include <stdio.h>
int main()
{
int temp1;
temp1=0;
do
{
temp1++;
printf ("%i",temp1); /* This prints 12345678910, we only
increment once per loop.*/
continue;
temp1++; // this line never executes
}
while (temp1<10);

temp1 = 0;
while (temp1<10)
{
temp1++;
printf ("%i",temp1); /* This prints 12345678910, we
only increment once per loop*/
continue;
temp1++; //this line never executes
}

temp1 = 0;
while (temp1<10)
{
temp1++;
printf ("%i",temp1); /*This prints the number
1234579*/
if (temp1<5) continue;
temp1++; /*This line only executes when
temp1 is greater than 5.*/
}
}

Digital Electronics – EEE3017W (2008)


99
4.1.10 Doing Things a Fixed Number of Times: The for Loop
The for loop is essentially a variation on the while loop, with some useful modifications. The
basic structure of the for loop is as follows:

for (loop variable initialization; test condition; loop modifier) statement

Every time the for loop executes (every iteration) it modifies a variable called the “loop
variable”. It modifies this variable by performing the “loop modifier” operation. Examples
of loop modifiers would be increment, decrement etc. Every iteration the test condition is checked
and if it is found to be true then the next iteration will commence.

#include <stdio.h>
int main()
{
int temp1;
for (temp1 = 0;temp1 < 10;temp1++)
{
printf ("%i ",temp1); //prints out 0 1 2 3 4 5 6 7 8 9
}
}

In the above example the loop variable is called temp1. Every iteration it is incremented and
compared with the number 10. If it is smaller than 10 the next iteration will occur. Note carefully
that in the above program the output started at zero. This is because the increment (loop modifier)
occurs at the end of the iteration.

The for loop assumes only one statement or compound statement. Watch out for misplaced
semicolons and missing brace brackets. The loop modifier need not be a simple increment. It can be
more complicated as shown here:

#include <stdio.h>
int main()
{
int temp1;
for (temp1 = 1;temp1 < 65;temp1*=2)
printf ("%i ",temp1); //prints out 1 2 4 8 16 32 64
}

or you can even list multiple loop modifiers by separating them into a list using a comma separator.

#include <stdio.h>
int main()
{
int temp1;
int temp2;
temp2 = 1;
for (temp1=1;temp1<65;temp1*=2,temp2*=3)
{
printf ("%i ",temp1); //prints out 1 2 4 8 16 32 64
printf ("%i ",temp2); //prints out 1 3 9 27 81 243 729
}
}

In this example the outputs from the two printf statements will be interleaved, thus we will get:
1 1 2 3 4 9 8 2716 81 32 243 64 729

University of Cape Town


100
4.1.11 Multi-Way Decisions: Case Statements
The “if” statement is good for selecting between a few different options. If you have a larger set of
things to choose between then the “case” statement provides an alternative. The case statement
takes in a variable and compares it to a number of options, or cases. If one of those cases is true
then a section of code is executed. An example of this is shown here:
#include <stdio.h>
int main()
{
int temp1;
temp1 = 3;
switch (temp1)
{
case 1: printf ("1"); break;
case 2: printf ("2"); break;
case 3: printf ("3"); break;
default: printf ("none of the above");
}
}

The variable which is being examined here is temp1. It is being checked to see if it has the value 1,
in which case the program output will be “1”, or 2, which causes “2” to print or 3, in which case the
program outputs “3”. If temp1 is not one of the above then the “default” clause will take effect
and the program will print “none of the above”. You will notice that after every case there is a break
statement. This causes the program flow to skip all of the following cases and resume execution
after the closing brace of the case statement. If this is omitted C does a strange thing: it will cause
ALL of the cases following the valid case to be executed. We do not need a break statement after
the default statement because there are no more cases after that. The order of the cases does not
matter.

The variable which is being examined in the case statement must be an integer-style data type. Ints,
chars and variations on these are acceptable, but doubles and floats are not. This is because a
floating point data type will not have a reliably precise value, it will only be an approximation
(albeit very close) to the desired value.

4.1.12 Bigger Things: C Functions


Sooner or later we need to start breaking our programs down into respectable size blocks of code.
We do this by breaking it down into functions, each of which encapsulates a function that we wish
to perform.

For every C function we will pass data to that function, in the form of parameters (also called
arguments) and the function will return exactly one value, called the “return value”.

We now need to introduce our final basic data type. This is called void. This data type holds no
value and takes no space. It is a place holder used where no data is to be exchanged.

To define a function we use the following structure:

return type function name (parameter list)


{
//function body
}

Digital Electronics – EEE3017W (2008)


101

We also need to declare each function before we invoke it. This is done as follows:

return type function name (parameter list);

When we wish to invoke the function (call it, execute it) we use the function name followed by the
parameters which we wish to pass into the function.
function name (parameters);

An example of this is shown here:


#include <stdio.h>
void func1 (void); //declare the function

int main()
{
func1(); //invoke the function
}

void func1 (void) //define the function


{
printf ("My first function"); //function body
}

In this example we have a function which prints output to the screen. Because the function always
does the same thing and there is no data being passed to it, there is a single void parameter. The
function produces no output and so the return type is also void. Notice that we declared and defined
the function outside of any other function.

A more complicated function is shown here:

#include <stdio.h>
int func2 (int var1, int var2); //declare the function

int main()
{
int temp1,temp2,temp3;
temp1 = 2;
temp2 = 3;

temp3 = func2(temp1,temp2); //invoke the function


printf ("%i",temp3);
}

int func2 (int var1, int var2) //define the function


{
return var1 + var2; //function body
}

Now the function takes in two parameters, adds them together and returns the result. Note the use of
the “return” keyword. This tells the function to send the answer back to the caller, in this case
main. The return keyword causes the function to exit.

int func2 (int var1, int var2) //define the function


{
return var1 + var2; //function body
printf ("The answer is 42"); //this never prints
}

University of Cape Town


102
We can conditionally return a value as well:
#include <stdio.h>
int func2 (int var1, int var2); //declare the function

int main()
{
int temp1,temp2,temp3;
temp1 = 50;
temp2 = 30;
temp3 = func2(temp1,temp2); //invoke the function
printf ("%i",temp3);
}

int func2 (int var1, int var2) //define the function


{
if (var2!=0) return var1/var2; //function body
printf ("Impossible"); //Illegal denominator
return 0;
}

When the function is invoked it will execute and return an integer. The caller sees the function as if
it were an integer itself. We say that the function evaluates to an integer. Because of this the
following is also legal:

#include <stdio.h>
int func2 (int var1, int var2); //declare the function

int main()
{
int temp1, temp2;
temp1 = 50;
temp2 = 30;
printf ("%i",func2 (temp1,temp2));
}

int func2 (int var1, int var2) //define the function


{
return var1 + var2; //function body
}

We can give the parameters default values. This means that we do not always need to specify the
value of a parameter. An example is shown here:

#include <stdio.h>
int func2 (int var1, int var2=10); //declare the function

int main()
{
int temp1,temp2;
temp1 = 50;
temp2 = 30;

printf ("%i",func2 (temp1)); //50+10


printf ("%i",func2 (temp1,temp2)); //50+30
system("PAUSE");
}

int func2 (int var1, int var2) //define the function


{
return var1+var2; //function body
}

Digital Electronics – EEE3017W (2008)


103
The default value of the second parameter is given in the declaration of the function. When you pass
parameters into a function they are copied rather than using the original variable. The copy of the
parameter is normally placed on the stack in the memory of the computer. This has some important
effects. An illustration:
#include <stdio.h>

void myfunc (int var1);

int main()
{
int temp1;
temp1 = 1;
myfunc (temp1);
printf ("%i",temp1); //prints 1, NOT 222
system("PAUSE");
}

void myfunc (int var1)


{
var1=222; //Overwrites a COPY of the original variable
}

One exception to this rule is when we pass an array into the function. Because copying an entire
array might be a lengthy process it is avoided and only the starting address of the array is copied
onto the stack instead of the entire array. This means that if a function modifies an array element
then the original array will be modified.

#include <stdio.h>

void myfunc (int var1[]);

int main()
{
int temp1 [5]; //array of integers
temp1[3]=1;

myfunc (temp1); //pass in one element of the array


printf ("%i",temp1[3]); //prints 222
}
void myfunc (int var1[] )
{
var1[3]=222; //Overwrites the original array element
}

This example shows two things. Firstly it shows how the original array element has been
overwritten. Secondly it shows the syntax of passing an array into a function as a parameter.

We also have a concept called “scope”. The scope of a variable is the region of code in which that
variable is valid. This leads to the concept of “local variables” and “Global variables”. Local
variables are only accessible within the function that they are declared and global variables are
accessible anywhere within the program. Local variables are normally created on the stack and
therefore they are destroyed when you return from their function. An example of different scopes is
shown:

University of Cape Town


104
#include <stdio.h>

void myfunc (int var1);

int global_var; //a global variable

int main()
{
int temp1; //A variable which is local to main
//Your code here...
}

void myfunc (int var1)


{
int local_var; //A variable which is local to myfunc
}

Because a local variable exists on the stack it will lose its value between function calls. Remember
though that C does not perform automatic initialization of variables unless it is specifically told to.
This program will probably do what is expected even though it is wrong:

#include <stdio.h>

void myfunc1 ();

int main()
{
int temp1; //A variable which is local to main

for (temp1=0;temp1<10;temp1++)
{
myfunc1();
}
}

void myfunc1 ()
{
int local_var; //A variable which is local to myfunc1
local_var++;
printf ("%i ",local_var);
}

The reason that this will work is that the stack on the computer is being used in the same way for
each function call. This means that each time the function runs it uses the same piece of memory for
the local variable. This means that the local variable will appear to hold its value. As soon as you
start using the stack in a more complex way this convenient behaviour will vanish. Here is a
program that looks identical except that there are now two functions being called alternately.

Digital Electronics – EEE3017W (2008)


105
#include <stdio.h>

void myfunc1 ();


void myfunc2 ();

int main()
{
int temp1; //A variable which is local to main
for (temp1 = 0;temp1<10;temp1++)
{
myfunc1();
myfunc2();
}
}
void myfunc1 ()
{
int local_varx; //A variable which is local to myfunc1
local_varx++;
printf ("%i ",local_varx);
}
void myfunc2 ()
{
int local_vary; //A variable which is local to myfunc2
local_vary--;
printf ("%i ",local_vary);
}

When you run this program you will see that the first function increments its local variable. The
second function uses the same space on the stack and decrements the variable. Overall the variable
goes nowhere.

There are several ways of fixing the problem. You could:


• Pass the variable back and forwards, i.e. the function increments the variable and passes is back
to its caller. The next time the caller calls the function it passes the variable into the function.
• Use a global variable which persists for the duration of the program's execution.
• Use the “static keyword to tell the compiler that the variable must persist.

Two of these solutions are shown

#include <stdio.h>

void myfunc1 ();


int myfunc2 (int var1);

int main()
{
int temp1,temp2=0; //A variable which is local to main
for (temp1=0;temp1<10;temp1++)
{
myfunc1();
temp2=myfunc2(temp2);
}
}
void myfunc1 ()
{
static int local_var; //A variable which is local to myfunc1 but static

local_var++;
printf ("%i ",local_var);
}

University of Cape Town


106
int myfunc2 (int var1)
{
var1--;
printf ("%i ",var1);
return var1;
}

The method which passes the variable back and forwards also gives us an easy way of initializing
that variable. When a local variable and a global variable have the same name the variable with
more local scope is always used. Functions in C can only ever return one item. You cannot return
many variables or even arrays. We will see a solution to this later.

4.1.13 Strings: Groups of Characters


C does not have an explicit type to hold strings of text. Rather it uses arrays of characters to make
strings. Most string functions are supported through library functions, which are mentioned later. In
reality strings come in all sorts of lengths. In order to mark the end of a string C uses the character
with a representation of 0000 0000. This is written '\0' in your C program. Here is a program
that shows a simple string in action:

#include <stdio.h>

int main()
{
char mystring [4]; //a string is an array of characters

mystring[0]='C';
mystring[1]='a';
mystring[2]='t';
mystring[3]='\0';
printf (mystring); //This prints the word "Cat".
}

You will need to leave one character open at the end of the string of the zero element, which is
called a “null terminator”. If you want a three character string you will need a four element array. If
you forget the null terminator then the program will print whatever it finds in memory until it
reaches the first null character that happened to be there.

There is a set of string handling functions in the standard C libraries. We will look at that in a few
section's time. To whet you appetite here is an example function which determines the length of a
string:
int len (const char instr [])
{
int count=0;
while (instr[count]) count++;
return count;
}

We have used the 'const' keyword to specify that the function can not change the string array.
Remember that in the case of arrays changing the contents of the array will change the original
array, not just a copy.

We can initialize strings when we declare them by using the following syntax:

char mystring [5]={'d','o','g','s','\0'};

The \0 (backslash zero) is the null terminator.

Digital Electronics – EEE3017W (2008)


107
4.1.14 Pre Processor Commands
Pre processor commands are commands that give instructions to the compiler, rather than
instructions that form part of the code to be run. These commands all begin with a hash ('#')
character. There are quite a few of them, but the most common ones are

#include <filename>
This directive effectively tells the compiler to take the code from the file with name “filename”
and paste it into the current file at the position of the directive. This is used to include header files
(files of function declarations) into your code.

#include “filename”
This acts in the same way as the above directive except that it is used with non standard files, such
as your own files. If you project gets too big for one file then you can use #include and multiple
files.

#define myconst 32
#define directives are used to implement replacements. In the above example the compiler will
look for the word “myconst” and replace it with the number 32. If at some stage myconst needed
to be changed then all we'd have to do would be to change the number 32. #define is also used to
make code more intelligible, although this is often lost on the reader.

Here is a more complete example:

#include <stdio.h>
#define integer "%i"

int main()
{
printf (integer,42);
}

The word “integer” will be replaced with a %i formatting string. There is a set of directives
which include #if, #ifdef etc, which are used for conditional compilation, but they will not be
discussed here.

4.1.15 Library functions


C provides a set of standard library functions, called the ANSI C Libraries. These are broken down
into logical groupings. Library functions are used to implement much of the standard functionality
which you would expect from a high level programming language.

We are only going to discuss some libraries and some functions here, as the complete list is
extensive.

University of Cape Town


108

i. <stdio.h>
This is a library of input and output functions. It is humongous. I have only shown a few percent of
it here. We will discuss more functions from this library in later sections.
• int printf(const char* format, ...);
printf(f, ...) is equivalent to fprintf(stdout, f, ...)
• int sprintf(char* s, const char* format, ...);
Like fprintf, but output written into string s, which must be large enough to hold
the output, rather than to a stream. Output is NUL-terminated. Returns length (excluding
the terminating NULL).
• int scanf(const char* format, ...);
scanf(f, ...) is equivalent to fscanf(stdin, f, ...)
• int sscanf(char* s, const char* format, ...);
Like fscanf, but input read from string s.
• int fgetc(FILE* stream);
Returns next character from (input) stream stream, or EOF on end-of-file or error.
• int putchar(int c);
putchar(c) is equivalent to putc(c, stdout).
• int puts(const char* s);
Writes s (excluding terminating NUL) and a newline to stdout. Returns non-negative
on success, EOF on error.

ii. <stdlib.h>
This is a library of general purpose useful functions. You'll note that some of the more complex
functions are quite tricky to call because of their complicated parameter lists.

• double atof(const char* s);


Converts string to double
• int atoi(const char* s);
Converts string to integer
• long atol(const char* s);
Converts string to long.
• int system(const char* s);
If s is not NULL, passes s to environment for execution, and returns status reported by
command processor; if s is NULL, non-zero returned if environment has a command
processor.
• void* bsearch(const void* key, const void* base, size_t n, size_t size, int
(*cmp)(const void* keyval, const void* datum));
Searches ordered array base (of n objects each of size size) for item matching key
according to comparison function cmp. cmp must return negative value if first argument
is less than second, zero if equal and positive if greater. Items of base are assumed to be
in ascending order (according to cmp). Returns a pointer to an item matching key, or
NULL if none found.
• void qsort(void* base, size_t n, size_t size, int (*cmp)(const void*, const
void*));
Arranges into ascending order array base (of n objects each of size size) according to
comparison function cmp. cmp must return negative value if first argument is less than
second, zero if equal and positive if greater.
• int rand(void);
Returns pseudo-random number in range 0 to RAND_MAX.
• void srand(unsigned int seed);
Uses seed as seed for new sequence of pseudo-random numbers. Initial seed is 1.

Digital Electronics – EEE3017W (2008)


109

iii. <string.h>
String handling functions. For now you'll have to accept that the expression “char*” means a
string, as represented by an array characters. The * operator is used to indicate that the whole string
is not returned, but rather that the function's return value tells the caller where to find the returned
value. Some of the libraries functions look redundant, but generally there will be differences in
implementation which make it possible for a knowledgeable programmer to optimize his/her code.

• char* strcpy(char* s, const char* ct);


Copies ct to s including terminating NUL and returns s.
• char* strncpy(char* s, const char* ct, size_t n);
Copies at most n characters of ct to s. Pads with NUL characters if ct is of length less
than n. Note that this may leave s without NUL-termination. Return s.
• char* strcat(char* s, const char* ct);
Concatenate ct to s and return s.
• char* strncat(char* s, const char* ct, size_t n);
Concatenate at most n characters of ct to s. NUL-terminates s and return it.
• int strcmp(const char* cs, const char* ct);
Compares cs with ct, returning negative value if cs<ct, zero if cs==ct, positive
value if cs>ct.
• int strncmp(const char* cs, const char* ct, size_t n);
Compares at most (the first) n characters of cs and ct, returning negative value if
cs<ct, zero if cs==ct, positive value if cs>ct.
• char* strchr(const char* cs, int c);
Returns pointer to first occurrence of c in cs, or NULL if not found.
• char* strrchr(const char* cs, int c);
Returns pointer to last occurrence of c in cs, or NULL if not found.
• size_t strlen(const char* cs);
Returns length of cs.
• void* memcpy(void* s, const void* ct, size_t n);
Copies n characters from ct to s and returns s. s may be corrupted if objects overlap.
• void* memmove(void* s, const void* ct, size_t n);
Copies n characters from ct to s and returns s. s will not be corrupted if objects
overlap.
• int memcmp(const void* cs, const void* ct, size_t n);
Compares at most (the first) n characters of cs and ct, returning negative value if
cs<ct, zero if cs==ct, positive value if cs>ct.
• void* memchr(const void* cs, int c, size_t n);
Returns pointer to first occurrence of c in first n characters of cs, or NULL if not
found.
• void* memset(void* s, int c, size_t n);
Replaces each of the first n characters of s by c and returns s.

iv. <math.h>
Mathematical library. Angles are generally in radians.

• double exp(double x);


exponential of x
• double log(double x);
natural logarithm of x
• double log10(double x);

University of Cape Town


110
base-10 logarithm of x
• double pow(double x, double y);
x raised to power y
• double sqrt(double x);
square root of x
• double ceil(double x);
smallest integer not less than x
• double floor(double x);
largest integer not greater than x
• double fabs(double x);
absolute value of x
• double ldexp(double x, int n);
x times 2 to the power n
• double modf(double x, double* ip);
returns fractional part and assigns to *ip integral part of x, both with same sign as x
• double sin(double x);
sine of x
• double cos(double x);
cosine of x
• double tan(double x);
tangent of x
• double asin(double x);
arc-sine of x
• double acos(double x);
arc-cosine of x
• double atan(double x);
arc-tangent of x

Digital Electronics – EEE3017W (2008)


111

4.1.16 User Interface: Console IO


Console IO is a simple way of getting input from a user and giving information back. This set of
functions is implemented with function calls to the library stdio.h and the library conio.h.

The simplest of these functions is the “getch()” function. This function gets one character from
the keyboard and returns it. There is also a “getche()” function which gets one character from
the keyboard and echoes (prints) it to the screen. If no key has been pressed then these functions
will cause your program to wait for a key to be pressed.
Here's an example:

#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

int main()
{
char mystring [3]; //a string is an array of characters
mystring [0]= getch(); //get a character and store it
mystring [1]= getche(); //get a character, store it and print it
mystring [2]=0; //add a null terminator
printf (mystring); //Print out the two keys that were pressed
}

There is a function that simply checks if a key has been pressed. This is the kbhit() function. It
returns 0 (zero) if no key has been pressed and non-zero if a key has been pressed but not yet read
in by the program.

Here is a piece of code that loops around counting until a key is pressed:

#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
int main()
{
int counter=0; //a string is an array of characters
while (!(kbhit()))
{
counter++;
printf ("%i\n",counter);
}
}

If you want to read in a whole word or a number or anything bigger than a single character at a time
you can use the “scanf” function. Scanf reads something in and needs to return it. Because of
the limited flexibility of C's functions (they can only have one return value) scanf uses its
parameters to pass values back to the calling function. Remember from before that if we pass an
array to a function then the original array is modified when the function modifies its variables. As a
result of this when we call scanf to read in a string we pass the string in as a parameter. We also
need to tell scanf what kind of input to expect. We do this with formatting codes, similar to those
that we used previously with printf. Here is an example:

University of Cape Town


112
#include <stdlib.h>
#include <stdio.h>

int main()
{
char mystring [30];
scanf ("%s",mystring); /*read the string into mystring. This echoes to the
screen as well. */
printf (mystring); /*display the input that we just read from the
keyboard.*/
}

We can use other formatting strings to read in different types of input. To read in floating point
numbers we can use %d and to read in integers we can use “%i”.

#include <stdlib.h>
#include <stdio.h>

int main()
{
int myint;
float myfloat;
scanf ("%i",&myint); //read in an integer
printf ("%i",myint);
scanf ("%f",&myfloat); //read in a floating point number
printf ("%f",myfloat);
}

Notice the ampersand before the parameters that we send into the scanf function in the program
above. They are there to specify that when the function is called a copy must NOT be made of those
parameters, rather the original parameter must be changed. This is the mechanism used by scanf
to send data back to the caller.

Notice that if you expect the user to type in a number and they input letters then your program will
do strange things. A more secure way of inputting numbers is to input them as strings and then
convert them to numbers:

#include <stdlib.h>
#include <stdio.h>

int main()
{
char mystring [30];
int myint;
float myfloat;

scanf ("%s",mystring); //read in a string


myint=atoi (mystring); //convert the string to an integer
printf ("%i",myint); /*print the integer, return 0 if total rubbish
entered*/
//otherwise converts numbers up to the first non-numeric

scanf ("%s",mystring); //read in a string


myfloat=atof (mystring);//convert the string to a floating point number
printf ("%f",myfloat); //print the float, return 0 if rubbish entered
}

Digital Electronics – EEE3017W (2008)


113
You'll notice when you use scanf for strings that sometimes it is quite picky about input
formatting. If you put a space in your input then you'll find that scanf only returns characters up
to the space. This is quite inconvenient sometimes. There is another function, called gets(), that
is simpler and less fussy, although less powerful.

#include <stdlib.h>
#include <stdio.h>

int main()
{
char mystring [30];
gets (mystring); //read in a string
printf (mystring);
}

We have already seen the printf function that is used to print things on the screen. Printf is
fairly flexible in that it allows us to print many different types of things. The type of thing that is to
be printed depends on the format code. Some allowable format codes are:

%c character
%d signed integers
%i signed integers
%e scientific notation, with a lowercase "e"
%E scientific notation, with a uppercase "E"
%f floating point
%g use %e or %f, whichever is shorter
%G use %E or %f, whichever is shorter
%o octal
%s a string of characters
%u unsigned integer
%x unsigned hexadecimal, with lowercase letters
%X unsigned hexadecimal, with uppercase letters
%% a '%' sign

There are also some modifiers which allow the above meanings to be altered slightly, for example
to restrict the length of floating point numbers.

There is a simpler function which can be used to print strings only. This is the puts() function. The
parameter is the string to be printed. An example program:

#include <stdlib.h>
#include <stdio.h>
int main()
{
char mystring [30];
gets (mystring); //read in a string
puts (mystring); //print the string
}

There are several special purpose characters with reserved meanings. These are listed over here.
These characters all represent characters in the ASCII table and therefore can be stored in a char
type variable.

University of Cape Town


114

• \a "BELL" - i.e. a beep


• \b Backspace
• \f Form Feed (new page)
• \n New Line (line feed)
• \r Real carriage return
• \t Tab
• \v Vertical Tab
• \\ Backslash
• \' Single Quote
• \" Double Quote
• \? Question Mark

Here is a program to make your computer beep:

#include <stdlib.h>
#include <stdio.h>

int main()
{
char mystring [30];
puts ("\a");
}

Notice that although the \a is a single character we have still used double quotation marks around it
because the puts function expects a string input.

4.1.17 Pointers
This is the section which ties many concepts and as yet unexplained things together.

Please use this blank space to take a deep breath....

We are used to the concept of variables. These are locations in which we store data. We understand
that each of the variables in our program occupy some space in the memory of our computer.
Finally we understand that each location in our computer's memory has a unique number, called an
address, associated with it.

When we declare a variable we do it as follows:


int myint;

We have reserved a space which is large enough to store an integer and given that space the name
“myint”.

Sometimes it is useful to be able to refer to a variable indirectly. We do this by creating a new type
which holds the address of a variable. This is called a pointer. The C to do this is as follows:

int *mypointer;

The mypointer variable has been declared as holding the address of an integer.

Digital Electronics – EEE3017W (2008)


115
As an example:

“The first C example in these notes is on page 1” is a pointer to a C example.


If I have an integer stored in memory address 223F hex then remembering the number 223F is
useful because I can use it to find that integer.

The * operator is a unary operator (it only operates on one item) which associates itself with the
thing to the right of it. Thus in the following example pointer is a pointer and integer is a normal
variable:

int *pointer, integer;

On a Pentium based PC all pointers are 32bits long regardless of what they are pointing to. This
means that a pointer to an integer occupies the same amount of space as a pointer to a char which
occupies the same amount of space as a pointer to a floating point number. Despite this C treats
pointers to different variable types as different types of pointer. This is because when we write into
the location “pointed to” by the pointer the number of storage locations affected will depend on the
type of the pointer.

If we have a variable and we want to know where it is stored we can use the unary & operator. This
operator is used as follows:

int myint;
int *intpointer;
intpointer=&myint;

In this example we have declared an integer and something that points to an integer. We have then
told the pointer to point to that integer by assigning the pointer (which holds an address) to the
address of the integer (provided by the & operator).

If we wish to refer to the “thing pointed to by a pointer” then we can access that by prefacing the
name of the pointer with an asterisk. A full example is given below. In this example we set up an
integer and a pointer to an integer. After that we make the pointer point to the integer. We assign a
number to the integer and we print out the contents of the item being pointed to by the pointer, i.e.
the original integer.

#include <stdio.h>
int main()
{
int myint;
int *intpointer;
intpointer =& myint;
myint = 3456;
printf ("%i",*intpointer); //prints 3456;
}

When we first create the pointer it does not point to anything. If you store something in the
referenced location you have no control over where it is stored, therefore the operation is
dangerous. It may result in unpredictable behaviour.

We can create a pointer which does not point to anything. These are called NULL pointers.

University of Cape Town


116
Suppose I want to write a function to swap two variables around. Here is an example of the sort of
thing that I want:

#include <stdio.h>
void swap (int arg1, int arg2);

int main()
{
int num1,num2;

num1 = 3;
num2 = 5;
swap (num1,num2);
printf ("%i\n",num1);
printf ("%i\n",num2);
}

void swap (int arg1, int arg2)


{
int temp;
temp = arg1;
arg1 = arg2;
arg2 = temp;
}

The swap routine takes in two parameters and swaps them around. Unfortunately the parameters
that are swapped are only copies of the original parameters and so the function is useless. The trick
to fixing the problem lies in ensuring that the original parameters, not copies, are changed. If we
send the address of the data to be swapped then we can have the function swapping the originals,
not the copies. We can do this by passing a “reference” into the function. This reference is basically
the address of the data which the function must work on. Because we are passing the address of the
original data we achieve our aim.

void swap (int &arg1, int &arg2)


{
int temp;
temp = arg1;
arg1 = arg2;
arg2 = temp;
}

There is some trickery involved. The compiler knows that we are passing references into the
function and automatically de-references them, so that when we refer to arg1 and arg2 in the
above example the compiler understands that we are working with “the thing referenced by the
address that was passed in”.

This leads to two ways of passing parameters. Before this all parameters were passed “by value”, in
other words we sent in the actual value for the function to work on. In the latest example we passed
our parameters “by reference”, we told the function where to find its parameters.

We can do the above example in a slightly more complicated way as shown here:

Digital Electronics – EEE3017W (2008)


117
#include <stdio.h>
void swap (int *arg1, int *arg2);

int main()
{
int num1,num2;

num1 = 3;
num2 = 5;
swap (&num1,&num2);
printf ("%i\n",num1);
printf ("%i\n",num2);
}

void swap (int *arg1, int *arg2)


{
int temp;
temp =* arg1;
*arg1=* arg2;
*arg2= temp;
}

In this example we pass pointers into the function. Inside the function we dereference the
parameters manually by referring to their contents with the * operator. When we pass the
parameters into the function, inside main, we need to pass pointers to the integers into the function
so that the type of our parameters matches with the declaration. We do this conversion by using the
& operator which gets the address (a pointer) of the variables.

We can now revisit a line which you saw a few pages back:

scanf ("%i",&myint); //read in an integer

You should now understand that the function must modify its parameter (that is the whole point of
calling this function) and in order to do this it needs to have its parameter passed by reference.

Pointers and arrays have a very close relationship. The name of an array is actually implemented as
a pointer to the first element of that array. Because of this I can do the following:

#include <stdio.h>
#include <string.h>

int main()
{
char myarray [26];
char *mypointer;
mypointer=myarray; /*make mypointer point to the first
element of myarray*/
strcpy (myarray,"Hello World"); //put something into myarray
printf (mypointer); /*show that printing mypointer
accesses the contents of myarray*/
}

There are two things to consider over here. Firstly there is a pointer, which is the starting address of
the array, and secondly it is crucial to note that when we declared the array a storage space was set
aside to actually place the contents. We must have both of these things.

From the above you can see why the line

University of Cape Town


118

myarray=”Hello World”

does not make sense. We cannot assign a string to a pointer because they are different types.
In addition we cannot store data into a pointer unless we have assigned space for it to reside.

If we wish to pass an array into a function by reference then we can do this by passing in a pointer,
as seen above, which explains the declaration of strcpy:

char* strcpy(char* s, const char* ct);

When strcpy returns an answer it passes a pointer back which tells the caller where to find the
answer.

Pointers are actually variables, and so we can do arithmetic on them. This leads to the following
implementation of strlen:

int strlen (const char *s)


{
int i=0;
while (*s++!='\0') i++;
return i;
}

Over here we increment the pointer BEFORE we apply the * operator to it. Incrementing the
pointer has the effect of moving to the next element in the array. The C compiler knows how big
each array element is and moves that many bytes every time the pointer is incremented.

We can create a pointer to a C function. A pointer to a function points to the address of the first byte
of code in that function. When we refer to the contents of that function we are referring to the code
that runs when the function is called. Function pointers are complicated slightly by the fact that
functions have parameters and return types. The syntax needed is illustrated here. Line numbers
have been added:
1 #include <stdio.h>
2 int quadruple (int input);
3 int main()
4 {
5 int (*myfuncptr)(int);
6 int temp;
7 myfuncptr=quadruple;
8 temp=(*myfuncptr)(20);
9 printf ("%i\n",temp);
10 }

11 int quadruple (int input)


12 {return 4*input;}

Line 2 declares a function. As you can see the function takes in an integer parameter and returns an
integer. Line 5 creates a pointer to a function. The pointer is given the integer type because the
function that it will point to will return an integer. This constraint is important. We distinguish the
function pointer from a normal integer pointer by putting the parameter type list in brackets after the
name of the function. The bracketing on this line is important because of the priority of the
operators. If we omit the brackets then the asterisk associates incorrectly and we end up with the
wrong type for our pointer. Once we have a pointer to a function we can point it to a function of the

Digital Electronics – EEE3017W (2008)


119
correct type as we have done in line 7. In order to call the function we refer to the “thing being
pointed to” by the pointer, as shown on line 8.

It is possible to do some extremely fancy things with function pointers. We will use them later to
construct interrupt vector tables.

4.1.18 C Structures
We have come across the concept of an array. An array is a group of things of the same type
bundled together. A structure also bundles things together, but they need not be of the same type.
This is useful for dealing with grouped information.

As an example of grouped information suppose that we have an employee about whom we wish to
store basic characteristics. We can create an employee structure, effectively grouping a number of
different types of information about each employee. We can then refer to an employee's details by
using the dot operator. This is illustrated here:
#include <stdio.h>
#include <string.h>

//An employee is represented by the following info:


//name, age, salary and how many years he has worked for the company.
struct employee
{
char name [50];
int age;
float salary;
int year_of_service;
};
//declare an employee
struct employee emp1;

int main()
{
strcpy (emp1.name,"Fred Cat"); /*give the employee a name. Note the dot
operator*/
emp1.age=21; //give the employee an age
emp1.salary=2122.34; //give the employee a salary
emp1.year_of_service=0; //number of years of employment
//now we print out the employee's information
printf
("%s\t%i\t%f\t%i\n",emp1.name,emp1.age,emp1.salary,emp1.year_of_service);
}

Notice how we defined what an employee's record consists of and then we defined a single
employee.

If we have lots of employees then we could create an array of employees:

University of Cape Town


120
#include <stdio.h>
#include <string.h>

struct employee
{
char name [50];
int age;
float salary;
int year_of_service;
};

struct employee smme[50]; /*array of 50 employees, small medium or micro


enterprise.*/

int main()
{
strcpy (smme[0].name,"Fred Cat"); //give the employee a name
smme[0].age=21; //give the employee an age
smme[0].salary=2122.34; //give the employee a salary
smme[0].year_of_service=0; //number of years of employment
//now we print out the employee's information
printf
("%s\t%i\t%f\t%i\n",smme[0].name,smme[0].age,smme[0].salary,smme[0].year_o
f_service);
system ("Pause");
}

We access each employee using standard array indexing. Passing a struct into a function will
cause the entire struct to be copied over onto the stack. This can be inefficient. If we want a
function to modify one of the struct's members then we have a problem. We cannot return a
struct as it consists of more than one item. We are forced to pass a pointer into the function so
that we can modify the original item, not a copy. The solution, as before, is to pass a pointer to a
struct into the function. This is done as follows:

#include <stdio.h>
#include <string.h>

struct employee
{
char name [50];
int age;
float salary;
int year_of_service;
};

void new_year (struct employee *theguy);


//declare an employee
struct employee smme[50];

int main()
{
strcpy (smme[0].name,"Fred Cat"); //give the employee a name
smme[0].age=21; //give the employee an age
smme[0].salary=2122.34; //give the employee a salary
smme[0].year_of_service=0; //number of years of employment

//now we print out the employee's information


new_year (&smme[0]);
printf
("%s\t%i\t%f\t%i\n",smme[0].name,smme[0].age,smme[0].salary,smme[0].year_o
f_service);
}

Digital Electronics – EEE3017W (2008)


121

void new_year (struct employee *theguy)


{
(*theguy).year_of_service++; //watch the brackets.
}

Because of the common and unwieldy de-reference operation in the above function there is a
shortened form available, as shown here:

void new_year (struct employee *theguy)


{
theguy->year_of_service++;
}

4.1.19 Unions
Unions are similar to structs but the members of a union occupy the same memory space. This can
be useful for splitting bigger variables into smaller variables. As an example suppose we have an
integer, 32 bits, and we want to split it into 4 chars. We could convert it using bit shift and mask
operations, but this is quite wasteful. We could declare a union of an integer and four chars (maybe
an array) and then writing into the integer will automatically result in the chars taking on the desired
values:
#include <stdio.h>
#include <string.h>

union convert
{
int integer;
char chars[4];
};
union convert my_int;

int main()
{
my_int.integer=0x12345678;
printf ("%x\n",my_int.chars[3]); //display 12
printf ("%x\n",my_int.chars[2]); //display 34
printf ("%x\n",my_int.chars[1]); //display 56
printf ("%x\n",my_int.chars[0]); //display 78
}

The array indexing for the character array appears to be in reverse order. This is because of the
order in which the computer stores multi-byte data items. On some processor platforms, such as
some microcontrollers, this will not happen.

4.1.20 Using Files for Storing Information


C handles files in a very similar way to console IO. As a result we have the following commands
(among others):

• fprintf //write to file, flexible


• fscanf //read from file, flexible
• putc //write one character to a file
• getc //get one character from a file
• fputs //write a string to a file
• fgets //read a string from a file

University of Cape Town


122
Before we start using a file we need to open it and after using it we need to close it. We use a file
pointer to refer to the file while using it. We use the following commands to do this:

fopen (filename, access mode)

The fopen function returns a file pointer. The filename is the name of the file to access

The access mode is:


• r: read the file
• w: open the file, wipe previous contents and prepare to write
• a: append to file
• r+: open for reading and writing
• w+: open file and wipe it for reading and writing
• a+: open file for reading and appending

Here is an example which writes text into a file:

#include <stdio.h>

FILE* fp; //create a file pointer called fp

int main()
{
fp=fopen ("myfile.txt","w"); /*open the file called myfile.txt
for writing, destroy previous contents*/
fprintf (fp,"This is a text file"); //put something into the file
fclose (fp); //close the file
}

Here is an example which reads a file and displays its contents:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

FILE* fp;
char mystring [100];
int main()
{fp=fopen ("myfile.txt","r"); //open the file and assign file pointer. File is
read only
fscanf (fp,"%s",mystring); //get a string from the file
printf ("%s\n",mystring); //put the string on the screen – CONSOLE IO
fclose (fp); //close the file
}

There are many other ways of accessing files, which are not dealt with here.

4.1.21 Casting and Type Conversion


Casting is the process of changing variables from one type to another type. A example of this is the
following code segment:

unsigned char num1;


unsigned int num2;
double num3;

num1=22;

Digital Electronics – EEE3017W (2008)


123
num2=2;
num3=num1*num2;

In this case the program is forced to convert numbers from one type to another. This involves
changing their type and representation. In this case num1 will be converted to variable of the same
type as num2, the multiplication will be performed and the result will be converted to the same type
as num3.

There are a few nasty catches. Suppose I want to take two unsigned chars and concatenate them to
form an integer:

int main()
{
unsigned char num1,num2;
unsigned int num3;
num1 = 128;
num2 = 128;
num3 = (num1<<8)+num2;
printf ("%i",num2);
}

This will sometimes give an incorrect answer. The char num1 is shifted 8 bits to the left, while
still remaining an 8 bit variable, resulting in a nonsense value. This value is added to num2, and
only after that is the result converted to an integer.

This piece of code works correctly:

int main()
{
unsigned char num1,num2;
unsigned int num3;
num1 = 128;
num2 = 128;
num3 = num1;
num3 = (num3<<8)+num2;
printf ("%i",num3);
}

Generally the compiler will cast values from a smaller representation to a bigger one.

University of Cape Town

You might also like