Você está na página 1de 27

Programming in C: A Tutorial

Introduction
Programming languages are just tools that we use to tell the computer what we want it to do. Structure and form are the real knowledge and learning new languages is just adding new tools to the toolbox to handle specific jobs. That is what this tutorial is based on. We have to assume that the user of this tutorial already has an intermediate level of knowledge in C++ programming and techniques. Since C++ is built on C, the differences are easily assimilated to provide the programmer with a new set of tools to attack software solutions that require the tools that C provides. Since C++ is built on C, many things are the same as C++. This tutorial will provides an overview to the differences between C and C++ and things the programmer needs to know to work with those differences. This is not meant to be comprehensive lessons on C programming so do not believe that it will answer all of the questions regarding programming in C. Since programming in C is a useful skill for courses such as Operating Systems and Computer Networks, this tutorial is focused on providing the student with the tools needed to deal with the more advanced concepts that will be presented in those courses.

History
C is a general purpose programming language that was originally designed by Dennis Ritchie of Bell Laboratories and implemented there on a PDP-11 in 1972. (Kelley and Pohl 1) A language called B was being used as the system language for UNIX system programming and C was developed to overcome some of the limitations Ken Thompson found in using B as his system programming language. C is a small language with few keywords and in programming, small is good. It is the native language of UNIX and a good portion of MS-DOS and OS/2 are written in C. C contains operators that allow the programmer to access the machine at the bit level, allowing for powerful programs. C is also the basis for Java as well as C++, which is why those languages are so similar.

Hello World
Most teaching documents on programming start with the simple Hello World program. Why be different here except that we will show first the C++ version that you are familiar with and then the C version for contrast. In C++ #include <iostream> int main() { cout << Hello World!\n; return 0; } In C #include <stdio.h> int main() { printf(Hello World!\n); return 0; }

Note that even though the keywords and libraries are different, there is little difference in the overall program structure. Now we will take a closer look at the differences.

Input/Output
printf() is the standard output function used in C. It takes an argument list in two parts and outputs to stdout. The two parts of the argument list are control_ string and other_arguments. For example: printf(%d, b); In this example the control_string %d sets the output string with the decimal integer conversion character inserted where variables will go in the actual output and establishing the format for those variables. The other_arguments list the variables, b in this case, in the order that they appear in the control_string. Conversion characters are a set of characters that are preceded by the % symbol to mark them as a conversion character. The only exception is that if you want to output the % symbol you insert %% at the point you want it outputted and it will print a single %. The following is a table of the conversion characters used in C:
Conversion Character c d, i u o x, X e E f g G s p How the corresponding argument is printed As a character as a decimal integer as an unsigned decimal integer as an unsigned octal integer as an unsigned hexadecimal integer as a floating-point number; ex. 7.122000e+00 as a floating-point number; ex. 7.122000E+00 as a floating-point number; ex. 7.122000 in the e-format or f-format , whichever is shorter in the E-format or f-format, whichever is shorter as a string the corresponding argument is a pointer to void; its value is printed as a hexadecimal number n
the corresponding argument is a pointer to an integer into which the number of characters written so far printed; the argument is not converted

These conversion characters can be supplemented with additional formatting flags. You can have zero or more flag characters that modify the meaning of the conversion character. These additional flags with examples are presented in the table below:
Flag Character Function Adjusts the field width of the Any Integer Number field with the contents right justified .Any non-negative Integer a minus sign 0 Sets precision for d,I,o,u,x or X conversions left adjust items in a field pads unused field space with zeros instead of spaces Example %2c outputs A if the c were the character A

%.3f outputs 3.123 %-8d outputs 1234 %05d outputs 00123

There are other flags but these are the most commonly used ones. scanf() is the reading counterpart to printf(). It has two nice properties that make it very flexible. First is that an argument list of arbitrary length can be scanned and second is that the input is controlled by simple conversion characters. scanf() uses the same control_string and other_arguments format as printf() and obtains input from stdin. scanf() returns a pointer type for most types of input so the list of storage variables are preceded with the & character to specify that the address of the item being read in is being stored there. The only exception to this is when reading with the s conversion character. Since the natural type for string data is a pointer then it accepts the pointer data without needing to specify that an address is being passed.

Here are the standard conversion characters used with scanf:


Conversion Character Characters in the input stream that are matched c any character, including white space d,i an optionally signed decimal integer u an optionally signed decimal integer o x, X an optionally signed octal integer an optionally signed hexadecimal integer e, E, f, g, G an optionally signed floatingpoint number s a sequence of nonwhite space characters p what is produced by %p in printf(), usually an unsigned hexadecimal integer n No characters in the input stream are matched. The corresponding argument is a pointer to an integer, into which gets stored the number of characters read so far % A single % character in the input stream is matched. There is no corresponding argument [] The set of characters inside the brackets [] is called the scan set. It determines what gets matched. (See the following explanation.) The corresponding argument is a pointer to the base of an array of characters that is large enough to hold the characters that are matched, including a terminating null character that is appended automatically. void * char a floating type unsigned integer unsigned integer unsigned integer integer Corresponding argument is a pointer to char

An example to further explain the [] conversion character: scanf(%[AB\n\t] , s); /* Would read into character array s a string that contains As, Bs, and whitespace characters space, tab, and newline */ This is a powerful tool allowing the programmer to designate specific characters to extract or to search out whitespace characters to separate them.

File Handling
Like with any language, file handling is an integral part of processing data. C uses the <stdio.h> header file to provide the programmer with the basic input/output tools including file handling. The first thing the programmer has to do is the same as in most languages; declare the variable for holding the file handling call. These are declared as pointers in C so the declaration for file pointers would look something like this: FILE *ifp, *ofp; FILE *ifp, *ofp; This would declare variables to hold two file pointers. To make these useful, the programmer needs to open a specific file. That is done with an assignment call to the pointer because the open function returns a pointer to the file being opened. ifp = fopen(somefile, r); ofp = fopen(outfile, w); /* ifp opens a file for reading and ofp opens a file for writing */ C requires the programmer to designate the mode that the file is opened in. The following table lists the file modes.
Mode r w a rb wb ab r+ w+ Meaning open text file for reading open text file for writing open text file for appending open binary file for reading open binary file for writing open binary file for appending open text file for reading and writing open text file for writing and reading

Attempting to open a file for reading that cannot be read or does not exist will return a NULL pointer. Opening a file for writing that does not exist will cause that file to be created and if it does exist, the file will be overwritten. From here, file processing is easy. It uses file versions of printf() and scanf() which are fprintf() and fscanf(). These are used exactly as printf() and scanf() except in the arguments , an additional argument of the file pointer is added at the beginning of the argument list so it would look something like this: while(fscanf(ifp, %d, &a) == 1) sum += a; fprintf(ofp, The sum is %d. \n, sum);

This little piece reads in a decimal integer then adds it to a variable sum and then outputs sum to the file. This also demonstrates how to check for file reading success while reading.

Pointers
Pointers are variables that point to locations in memory. The value of a pointer is a number referring to a specific memory address.

Declaring Pointers
Declaring a pointer variable looks similar to declaring other types of variables except that an asterisk * is used to indicate a pointer variable is being declared. Examples: int * pOne; char * pTwo; /* pOne declared as an integer pointer */ /* pTwo declared as a char pointer */

The above lines of code declare two pointer variables: the first pointer can point to an integer, and the second pointer can point to a character. You can place the asterix anywhere between the data-type and the variable name, so any of the following lines would declare pOne to be an integer pointer: int* int pOne; *pOne;

int * pOne;

Using the Address Operator &


Say we declare a variable x and assign it the value 5, and also declare a char z and give it the value z. We can assign our previously-declared pointer variables to point to x and z using an ampersand & preceeding x and y. Read the & as the address of. For example: int * pOne; char * pTwo; int x = 5; char z = z; pOne = &x; /* pOne is assigned the address of variable x */

10

pTwo = &z;

/* pTwo is assigned the address of variable z */

/* Now, pOne is pointing to x and pTwo is pointing to z */ The & operator can also be used in the declaration line. E.g: int * ptr = &x; Note, adding the line pOne = &z; would generate an error since pOne was declared as an int pointer and not a char pointer. Int pointers can only point to ints, char pointers can only point to chars, and so on.

Dereferencing Pointers
Continuing with the example by printing what pOne points to: int * pOne; char * pTwo; int x = 5; char z = z; pOne = &x; pTwo = &z; /* pOne is assigned the address of variable x */ /* pTwo is assigned the address of variable z */ /* same output as: printf(%d, x); */

/* Now, pOne is pointing to x and pTwo is pointing to z */ printf(%d, *pOne);

To get at the data a pointer is pointing to, we dereference the pointer. So if we wanted to print the contents of the integer pOne is pointing to, the statement printf(%d, *pOne); dereferences whatever pOne is pointing to, which

in this case happens to be the variable x. Since x was assigned the value 5, dereferencing pOne and printing this to screen would display 5 to the screen. *pOne should be read as the memory location pointed to by pOne. Essentially, *pOne and x refer to the same integer 5. Now, take note that the following two lines would print drastically different numbers: printf(%d, pOne); printf(%d, *pOne); /* prints an address to screen */ /* prints the number 5 */

11

The first printf function prints the value of pOne, which is an address. The second printf call prints the data pOne is pointing to, which is a 5. We can also change the value of whatever data a pointer is pointing to. Remember variable z stored the char z? We can change this value to saya through the pointer that points to z like so: *pTwo = a; z = a;

This line would have the same effect as:

Pointer Assignment
Take an example involving two doubles and two pointers declared and assigned the address of these doubles: double x = 2.3; double y = -5.0; double * pOne = &x; double * pTwo = &y; printf(%f, *pOne); printf(%f, *pTwo); pOne = pTwo; printf(%f, *pOne); printf(%f, *pTwo); /* yields -5.0 */ /* yields -5.0 */ /* yields -2.3 */ /* yields -5.0 */

Using this example, we change the memory address a pointer points to like so: pOne = pTwo; Now, pOne and pTwo both point to the same variable y. Dereferncing both pointers yields the same result: printf(%f, *pOne); printf(%f, *pTwo); /* yields -5.0 */ /* yields -5.0 */

Further, variable y can now be altered through either pointer, e.g: *pOne = 99.0;

12

Pointers as Arguments
First, lets discuss the problem of passing by value in C. Say we have the following program which incorporates a function TimesTwo that multiplies its argument by two: void TimesTwo(int argument) { argument = argument * 2; } int main() { int number = 22; /* call TimesTwo with number containing value 22 */ TimesTwo(number); printf(%d, number); return 0; } This example shows that when a function is called by a variable, a copy of that variable is made and passed into the function. As such, a function cannot make lasting changes on its given arguments. The TimesTwo function simply multiplied 2 with the copy of the argument passed. To circumvent this problem, we pass a pointer to number instead. void TimesTwo(int argument) { argument = argument * 2; } int main() { int number = 22; int * numPointer = &number; /* expecting 22 * 2 = 44...*/ /* this prints 22 not 44 */

13

/* call TimesTwo with number containing value 22 */ TimesTwo(number); printf(%d, number); return 0; } Now, we can make lasting changes outside of the scope of the TimesTwo function. Also, we dont necessarily have to declare a pointer variable in main to pass a pointer to number. Instead, we could simply call the TimesTwo function with the address of number like so: int main() { int number = 22; TimesTwo(&number); printf(%d, number); return 0; } /* expecting 22 * 2 = 44...*/ /* this prints 22 not 44 */

Returning Pointers from Functions


You can also return pointers from functions. For instance, take the following function: char * returnAnA() { char a = 'a'; char * ptr = &a; return ptr; } The return value from returnAnA is a char pointer, so the calling function main can use this returned pointer to alter the contents of the variable it points to. So returnAnA returns a

14

pointer to an a. This return was assigned to a char * charPointer in main. Then, the contents of what charPointer is pointing to is changed to a b: #include <stdio.h> int main() { char * charPointer = returnAnA(); printf(%c, *charPointer); *charPointer = 'b'; printf(%c, *charPointer); } /* prints: b */ /* prints: a */

Pointers to Structs
We are also allowed pointers to structs. Suppose we declare a structure as follows: struct employeeStruct { int ID; char * name; }; We can create a pointer to an employee struct just like we defined other types of pointers: employeeStruct employee1; employeeStruct * empPointer = &employee1; empPointer->ID = 2938756; empPointer->name = "fred"; printf(%s, empPointer->name); printf(d, empPointer->ID); To get at the data of a structure pointer, we use the -> operator like we did in the line: empPointer->name = fred; /* change employee1s ID */

15

Read empPointer->name as empPointer at name.

Instead of using the -> operator, we could simply combine the * and . operators: (*empPointer).name = fred; /** empPointer->name = fred; employee1.name = fred; ** ** **/

* these lines do the same thing *

Remember, when we are dealing with a structure variable itself, we use the . operator in-between the structure name and its fields. When we are using a pointer to a structure, we use the -> operator in-between the pointer name and the field.

Pointers-to-Pointers
The following code snippet illustrates how to use pointers-to-pointers. We place two asterisks between the data type and the variable name. Pointers-to-pointers work much like typical pointers. When dereferenced, a pointer-to-a-pointer yields the address of the pointer it is pointing to. Take a look at the following example: #include <stdio.h> int main() { int num; int *ptr; /* declares an int */ /* declares a pointer to an int */

int **ptr2; /* declares a pointer to a pointer to an int /* num = 17; ptr = &num; ptr2 = &ptr; /* assign ptr the address of num */ /* assign ptr2 the address of ptr */

/* print the value of num by first dereferencing ptr2 (which yields ptr1), then dereferencing again to yield the value of num */ 16

printf("%d", **ptr2); return 0; }

/* prints 17 */

We assign an int num the value 17, set ptr1 to point to num, then set ptr2 to point to ptr1. As such, we can get at the value of num by dereferencing ptr2 twice. The first application of dereferencing *ptr2yields ptr1. The second yields what ptr1 is pointing to. The ability to use pointers-to-pointers comes in handy whenever we must change what a pointer is pointing to from within another function. Pointers-to-pointers must be used whenever the assignment of the pointer itself must be changed from within the scope of a function. Remember that in C, all arguments to functions are passed by value, so if we pass a pointer to a function, how can we change what the pointer the location to which the pointer is pointing to? The solution: We must pass a pointer to a pointer.

17

Pointer Arithmetic
Suppose we declare an integer array of 8 elements and assign a pointer to the first int and a pointer to the last int. int array[8]; int * ptr1 = &array[0]; int * ptr2 = &array[6]; Graphically, the code looks like this: /* make ptr1 point to the first item */ /* make ptr2 point to the 7th item */

ptr1

ptr2

array 0 1 2 3 4 5 6 7

We can now do some interesting stuff using pointer arithmetic. We can access any of the elements in this array through the pointers by adding and subtracting integers from the pointers, or even by subtracting a pointer from another pointer.

Adding/Subtracting Integers from Pointers


Whenever a number is added to a pointer, the pointer contains a new memory address equal to that number times the size of the data-type the pointer is pointing to. So, if 3 is added to an int pointer, and assuming the size of an integer is 4 bytes, then adding 3 to an integer pointer will make the pointer point to an address 3 * 4 bytes further in memory.

18

The following graphics illustrate adding integers to pointers ptr1 and ptr2. ptr1 = ptr1 + 2; ptr1 ptr2

array 0 1 2 3 4 5 6 7

ptr2++; ptr1 ptr2

array 0 1 2 3 4 5 6 7

ptr1 = ptr1 2; ptr1 ptr2

array 0 1 2 3 4 5 6 7

19

Subtracting a Pointer from a Pointer


You can also subtract one pointer from another. The result is the distance between the two pointers. So if we want to know how many elements are between ptr1 and ptr2: int i = ptr1 ptr2; i = ptr2 ptr1; /* i = 7 */

/* i = -7 */

20

Array Processing Using Pointers


Using Array Names as Pointers
The name of an array can be used as a pointer to the first element in the array. For instance: Consider the following operations on an integer array of size 10. Each line of code is followed by a graphical representation.

int a[8];

/* declare a to be an array of 10 integers */

a 0 1 2 3 4 5 6 7

*a = 7;

/* is the same as

a[0] = 7

*/

7 0 1 2 3 4 5 6 7

*(a + 4) = 12;

/* is the same as

a[4] = 12 */

7 0 1 2 3

12 4 5 6 7

In general, *(a + j) = 5 is equivalent to a[j] = 5 if a is the name of an array.

21

Using Pointers as Array Names


Pointers can also be treated as arrays. If I assign a pointer to an element in an array, then I can use brackets to access individual elements in the array. char *sue = Sue; printf(%s, sue); printf(%c, sue[0]); sue[0] = B; sue[1] = o; sue[2] = b; printf(%s, sue); /* prints out Bob */ /* print out /* prints out sue s */ */

/* using sue pointer as an array to change each char */

Using Pointers in Multidimensional Arrays


Remember: an array can be treated as if it were a pointer. Take a look at this code: #include <stdio.h> int main() { /* declaring 3 C-strings of size 5 */ char names[3][5] = {{F, r, e, d, \0}, {M, a, r, y, \0}, {J, o, h, n, \0}}; printf("%c \n", names[0][0]); printf(%c \n, *names[0]); printf(%c \n, **names); printf("%s \n", names[0]); } names[0][0] refers to the same letter as *names[0] and **names; /* prints: /* prints: /* prints: /* prints: F F F */ */ */ */

Fred

22

Command-Line Arguments
C programs prepared to accept command-line arguments are written as follows: int main (int argc, char* argv[]) /* instead of int main() */

argc represents the count of the number of arguments passed to a program, and it is always guaranteed to be greater than or equal to 1 since the path of a program is its first argument. The first char pointer in the argv array, argv[0], points to the path of the program that is executing. If you called a printf(argv[0]), youd see output like: C:\Documents and Settings\xx5555\Desktop\yourProgram.exe We can pass in all sorts of information to a program at a command-line prompt: e.g. if at a linux prompt we saw: $ ./someProgram Charlie 10 we would be executing someProgram and passing in Charlie into argv[1] and 10 into argv[2] . However, all captured arguments are stored in the argv array as C-strings, and so it may be the case that we must convert an argument into a different data type. One such function, atoi, is useful for converting a C-string into an integer. Consider this program in which we pass two arguments Charlie and 10 at a linux prompt: ./someProgram Charlie 10 #include <stdio.h> int main(int argc, char* argv[]) { printf(argv[0]); printf("\n%d", argc); printf(%s, argv[1]); printf(%s, argv[2]); /* prints program name */ /* prints # of arguments passed */ /* prints: /* prints: Charlie 10 */ 23 */

/* we can use an argument as the control for a loop: */ /* this loop will print Charlie 10 times */ for (int i = 0; i < atoi(argv[2]); i++) { printf("\n"); printf(argv[1]); } /* notice the atoi function call. converts 10 into an int 10 */ return 0; } Calling printf with argv[0] as its argument will print the programs path and filename to the screen. Calling printf with argc as its argument will print 3 to the screen (the first argument is the program name, the second is Charlie, and the third is 10. Then, the atoi function is used to convert the C-string argv[2]which is the string 10into the integer 10 such that it can be used as an ending condition in the for loop. Other useful conversion functions exist too, such as atof which converts a C-string into a float. atoi converts a C-string atoi

into an integer i.e. if argv[2] is the string 10,

24

Dynamic Memory Allocation


Oftentimes, it is advantageous to dynamically allocate memory rather than statically allocate memory. Statically allocated memory comes from the stack which is great for quick access, but the available stack space is very limited in size. Dynamically allocated memory comes from the heap.

malloc
malloc has the following prototype: void * malloc(size_t size); Note, size_t is simply an unsigned integer. malloc allocates a block of size bytes and returns a pointer to the beginning of this block. So, to use malloc, we must always know how many bytes we need beforehand. This process can be simplified with the sizeof operator. For instance, if I wanted to allocate an array of five integers. I would write: int * myArray = malloc(5 * sizeof(int)); Graphically:

myArray 0 1 2 3 4 5

Note malloc returns a void pointer and not an integer pointer. This void pointer can essentially point to any type of data-type.

calloc
calloc works similar to that of malloc except that calloc initializes all elements to zero. Take note callocs prototype is slightly different from mallocs: void * calloc(size_t number, size_t size);

25

If I wanted an array of five integers initialized to zero: int * myArray = calloc(5 , sizeof(int)); Graphically:

myArray

0 0

0 1

0 2

0 3

0 4

0 5

free
free works as it sounds: free releases dynamically-allocated memory. The prototype of free looks as follows: void free ( void * ptr );

So if myArray is a pointer to a block of integers. I can call free passing in myArray. free(myArray);

Compilers
Most programmers are familiar with using command line compilers. Compiling C is no different than compiling C++ from the command line. However, there is a catch. Most C++ compilers will compile a C program with no problems since C++ is built on C and as long as you include the correct libraries, the converse is not true. Working in the Linux environment, if you want to compile a C program with a native C compiler you can call the gcc compiler vice the g++ compiler: gcc myfile.c o myfile.o If you try this with a .cpp file that is written with C++ syntax, you will get a list of errors to demonstrate the differences.

26

Bibliography
Gaddis, Tony. Starting Out with C++ from Control Structures Through Objects 5th Edition. Addison - Wesley, n.d. Kelley, Al and Ira Pohl. A Book on C. Boston, San Francisco, New York, Toronto, Montreal, London, Munich, Paris, Madrid, Capetown, Sydney, Tokyo, Singapore, Mexico City: AddisonWesley, 1998. King, K. N. C Programming A Modern Approach Second Edition. New York, n.d.

27

Você também pode gostar