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: 32 bit range unsigned: 16 bit range signed: 16 bit range unsigned:
- 2.14748×109 to + 2.14748×109 0 to 4.29497×109 -32768 to 32767 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); printf ("%f", temp2); printf ("%c", temp3);
//print an integer //print a floating point number //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); printf ("%f\n",temp2); printf ("%c\n",temp3);
//print an integer //print a floating point number //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;
University of Cape Town
//cryptic C shorthand. Also applies to -,*, /
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"); printf ("another statement");
// prints out if temp1 is true //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) { printf ("True"); } printf ("%i",temp1); }
//BIG BUG HERE... //this ALWAYS prints out //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; temp2 = 0x42; temp3 = temp1|temp2; printf ("%x",temp3); }
//Hexadecimal 24 written like this //Bitwise OR operator //prints temp3 in hexadecimal. Output is 66, hex.
The bitwise AND operator is a single ampersand (&), as shown in this example: temp1 = 0x78; temp2 = 0x42; temp3 = temp1&temp2; printf ("%x",temp3);
//Hexadecimal 24 written like this //Bitwise AND operator //prints temp3 in hexadecimal. Output is 40, hex.
The bitwise NOT operator is the tilde symbol, '~'. temp1 = 0x78; temp3 =~temp1; printf ("%x",temp3); temp3 =!temp1; printf ("%x",temp3);
//Hexadecimal 24 written like this //Bitwise AND operator //prints temp3 in hexadecimal. Output is fffff87 hex. //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; temp3 = temp1>>2; printf ("%x\n",temp3); temp3 = temp1<<3; printf ("%x",temp3);
//Hexadecimal 24 written like this //Bit-shift temp1 two bits to the right //prints temp3 in hexl. Output is 0x1e //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 donothing 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); if (temp1<5) continue; temp1++;
/*This prints the number 1234579*/ /*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: case 2: case 3: default: } }
printf printf printf printf
("1"); ("2"); ("3"); ("none
break; break; break; 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); int main() { func1(); }
//declare the function
//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); printf ("%i",temp3);
//invoke the function
} int func2 (int var1, int var2) { return var1 + var2; }
//define the function //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) { return var1 + var2; printf ("The answer is 42"); } University of Cape Town
//define the function //function body //this never prints
102 We can conditionally return a value as well: #include <stdio.h> int func2 (int var1, int var2); int main() { int temp1,temp2,temp3; temp1 = 50; temp2 = 30; temp3 = func2(temp1,temp2); printf ("%i",temp3); } int func2 (int var1, int var2) { if (var2!=0) return var1/var2; printf ("Impossible"); return 0; }
//declare the function
//invoke the function
//define the function //function body //Illegal denominator
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) { return var1 + var2; }
//define the function //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)); printf ("%i",func2 (temp1,temp2)); system("PAUSE");
//50+10 //50+30
} int func2 (int var1, int var2) { return var1+var2; }
//define the function //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); system("PAUSE"); } void myfunc (int var1) { var1=222; }
//prints 1, NOT 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]; temp1[3]=1;
//array of integers
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
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, ...);
•
int sprintf(char* s, const char* format, ...);
printf(f, ...) is equivalent to fprintf(stdout, f, ...) 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, ...);
•
int sscanf(char* s, const char* format, ...);
•
int fgetc(FILE* stream);
•
int putchar(int c);
•
int puts(const char* s);
scanf(f, ...) is equivalent to fscanf(stdin, f, ...) Like fscanf, but input read from string s. Returns next character from (input) stream stream, or EOF on end-of-file or error. putchar(c) is equivalent to putc(c, stdout). 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);
•
int atoi(const char* s);
•
long atol(const char* s);
•
int system(const char* s);
Converts string to double Converts string to integer Converts string to long. 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);
•
void srand(unsigned int seed);
Returns pseudo-random number in range 0 to RAND_MAX. 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);
•
char* strncpy(char* s, const char* ct, size_t n);
Copies ct to s including terminating NUL and returns s. 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);
•
char* strncat(char* s, const char* ct, size_t n);
•
int strcmp(const char* cs, const char* ct);
Concatenate ct to s and return s. Concatenate at most n characters of ct to s. NUL-terminates s and return it. Compares cs with ct, returning negative value if csct. •
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 csct. •
char* strchr(const char* cs, int c);
•
char* strrchr(const char* cs, int c);
•
size_t strlen(const char* cs);
Returns pointer to first occurrence of c in cs, or NULL if not found. Returns pointer to last occurrence of c in cs, or NULL if not found. Returns length of cs. •
void* memcpy(void* s, const void* ct, size_t n);
•
void* memmove(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. 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 csct. •
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);
•
double log(double x);
•
double log10(double x);
exponential of x natural logarithm of x
University of Cape Town
110 base-10 logarithm of x •
double pow(double x, double y);
•
double sqrt(double x);
•
double ceil(double x);
•
double floor(double x);
•
double fabs(double x);
•
double ldexp(double x, int n);
•
double modf(double x, double* ip);
•
double sin(double x);
•
sine of x double cos(double x); cosine of x
•
double tan(double x);
•
double asin(double x);
•
double acos(double x);
•
double atan(double x);
x raised to power y square root of x smallest integer not less than x largest integer not greater than x absolute value of x x times 2 to the power n returns fractional part and assigns to *ip integral part of x, both with same sign as x
tangent of x arc-sine of x arc-cosine of 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 int main() { char mystring [3]; mystring [0]= getch(); mystring [1]= getche(); mystring [2]=0; printf (mystring); }
//a string is an array of characters //get a character and store it //get a character, store it and print it //add a null terminator //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 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); printf ("%i",myint); scanf ("%f",&myfloat); printf ("%f",myfloat); }
//read in an integer //read in a floating point number
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); myint=atoi (mystring); printf ("%i",myint);
//read in a string //convert the string to an integer /*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); printf (mystring); }
//read in a string
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 %d %i %e %E %f %g %G %o %s %u %x %X %%
character signed integers signed integers scientific notation, with a lowercase "e" scientific notation, with a uppercase "E" floating point use %e or %f, whichever is shorter use %E or %f, whichever is shorter octal a string of characters unsigned integer unsigned hexadecimal, with lowercase letters 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); puts (mystring); }
//read in a string //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 \b \f \n \r \t \v \\ \' \" \?
"BELL" - i.e. a beep Backspace Form Feed (new page) New Line (line feed) Real carriage return Tab 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; strcpy (myarray,"Hello World"); printf (mypointer);
/*make mypointer point to the first element of myarray*/ //put something into myarray /*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 2 3 4 5 6 7 8 9 10
#include <stdio.h> int quadruple (int input); int main() { int (*myfuncptr)(int); int temp; myfuncptr=quadruple; temp=(*myfuncptr)(20); printf ("%i\n",temp); }
11 12
int quadruple (int input) {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"); smme[0].age=21; smme[0].salary=2122.34; smme[0].year_of_service=0;
//give the employee a name //give the employee an age //give the employee a salary //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]); printf ("%x\n",my_int.chars[2]); printf ("%x\n",my_int.chars[1]); printf ("%x\n",my_int.chars[0]); }
//display //display //display //display
12 34 56 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 fscanf putc getc fputs fgets
//write to file, flexible //read from file, flexible //write one character to a file //get one character from a file //write a string to a file //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