Advanced C Programming (for First year B.Tech students) Pointers
by user
Comments
Transcript
Advanced C Programming (for First year B.Tech students) Pointers
Advanced C Programming (for First year B.Tech students) Pointers Why we need pointers? The correct understanding and use of pointers is crucial to successful C programming. There are several reasons for this: First, pointers provide the means by which functions can modify their calling arguments. Second, pointers support dynamic allocation. Third, pointers can improve the efficiency of certain routines. Finally, pointers provide support for dynamic data structures, such as binary trees and linked lists. Memory addresses are global to all functions, whereas local variable names are meaningful only within the functions in which they are declared. A function can pass the address of a local variable to another function, and the second functions can use this address to access the contents of the first function’s local variable. What are pointers? A pointer is a variable that holds a memory address. This address is the location of another object (typically another variable) in memory. For example, if one variable contains the address of another variable, the first variable is said to be point to the second. To make this explanation clearer, we’re going to look some examples of source code, and draw pictures to show what is happening in memory. To keep things consistent, we’re going to develop a pictorial notation that will be used throughout this discussion. Let’s start off with an example. This is a very simple program. The diagram on the right shows what is happening in memory. The rectangular boxes indicate memory locations. The value inside a box indicates the value in that memory location. The value under a box is the memory address of that box. In this case, we have three boxes – one for each of the three variables in Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) main(). The name of each variable has been identified to the left of each box. Not all boxes will have names: memory locations allocated by malloc() will be nameless. Finally, all of these boxes represent local variables within the function main(). We have represented this with a rounded rectangle. Look first at Diagram 2a. This shows the state of the program memory just after line 5 has been executed. The variable num was assigned the value 57 at line 4. At line 5, the address-of operator is used to retrieve the address of the variable num, and store this value in pNum. Since num resides at memory location 1000 (as is shown under the box), the value 1000 is put in pNum. pNum is now “pointing to” num. This has been indicated on the diagram with an arrow. Note how the variable pNum was declared in line 3. The asterisk (*) there is not the dereference operator. In this context, it is used to tell the C compiler that pNum is not an int, but is a pointer to an int (an int*). Do not think of the arrow as a pointer. It’s better to think of the box pNum as the pointer. A pointer is just like any other variable; the arrow is just there to help you find the box it is pointing to. Now look at Diagram 2b. This shows the program memory when the program is finished. The dereference operator is used at line 6. *pNum tells the compiler to take the value inside pNum (which is 1000), and go get the box with that value underneath it. That box happens to be the one labeled “num” in this case. The rest of line 6 tells the compiler to put the value 23 inside that box. If you run this program, the number 23 will be printed. The value of num has been changed. The pointer operators There are two pointer operators: * and &. & is a unary operator that returns the memory address of its operand. You can think of & as returning “the address of”. The second pointer operation, *, is the complement of &. It is a unary operator that returns the value located at the address that follows. You can think of * as “value at address”. Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) If we declare the following: int *a, b, *c; It means that ‘a’ and ‘c’ is a pointer of type int, and ‘b’ is simply a variable of type int. Suppose we have integral variable ‘v’ and its value is 10. The variable ‘p’ is declared as a pointer variable. The statement p=&v assign address of ‘v’ to ‘p’. i.e. ‘p’ is the pointer to variable ‘v’. The pointer ‘p’ is noting but address of the variable ‘v’. To display the value stored at that location *p is used. The pointer variables also have address and are displayed using ‘&’ operator. For example: printf(“\n Address of v = %u”, p); /* Always use %u for displaying address. */ printf(“\n Address of v = %u”, &v); printf(“\n Value of v = %d”, *p); printf(“\n Value of v = %d”, v); printf(“\n Value of v = %d”, *(&v)); printf(“\n Address of p = %u”, &p); So, *p means value at some address, p. And &v means address of some variable, v. p = q; /*means that address ‘q’ is over-written over the address ‘p’ */ *p = *q; /*means that value of address ‘q’ is over-written over the address ‘p’*/ p = &a; /*means that pointer p points to address of a*/ *p = a; /*means that the value at the address, at which p is already pointing, is overwritten by the value of a*/ Pointer Conversions You can use a pointer on the right-hand side of an assignment statement to assign its value to another pointer. Let us consider some of the cases: Case 1: When both pointers are of same type: The situation is straightforward. No explicit cast is required. For example, p1 = &x; //here, p1 and x are of same data type p2 = p1; //here, p2 and p1 are of same data type Case 2: Converting one type of pointer to another type, without using void * pointer: The conversion must be performed by using an explicit cast. However, the conversion of one type of pointer into another type may create undefined behavior. For example, p = (int *) &x; //here, data type of x is ‘double’ Case 3: Conversion between a pointer and void * pointer: Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) A void * pointer is called a generic pointer. The void * pointer is used to specify a pointer whose base type is unknown. The void * type allows a function to specify a parameter that is capable of receiving any type of pointer argument without reporting a type mismatch. No explicit cast is required to convert to or from a void * pointer. Pointer Arithmetic There are only two arithmetic operations that you can use on pointers: Addition or subtraction of an integer to a pointer: Let p1 be an integer pointer with a current value of 2000. Also, assume ints are 2 bytes long. p1++; //p1 is jumped from 2000 to 2002 p1--; //p1 is jumped from 2000 to 1998 p1 = p1 + 10; //p1 is jumped from 2000 to 2020 p1 = p1 – 10; //p1 is jumped from 2000 to 1980 Subtraction of a pointer from another: You can subtract one pointer from another in order to find the number of objects of their base type that separate the two. All other arithmetic operations are prohibited. Pointer Comparisons We can compare two pointers in a relational expression. For instance, given two pointers p and q, the following statement is perfectly valid: if (p < q) printf(“p points to lower memory location then q\n”); Generally, pointer comparisons are useful only when two pointers point to a common object, such as an array. Array of Pointers Pointers can be arrayed like any other data type. The declaration for an int pointer array of size is int *x[10]; To assign the address of an integer variable called var to the third element of the pointer array, write x[2] = &var; To find the value of var, write *x[2] Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) Note that there is no pre-defined relation between the address to which the elements of a ‘array of pointer’ will point. So here, we are to assign the address to each of the element of ‘array of pointers’. Initializing Pointers Consider the following statements: int *p; //Declares a pointer *p = 70; //Now, that particular piece of memory has a value of 70. Now, consider the following: int *p = 70; //Declares a pointer pointing to the, first cell (having address 70), piece of memory of integral size having garbage value. So, this way we can initialize a pointer to any particular memory address. This happens because while declaration, we talk about the pointer; not about its value. A pointer that does not currently point to a valid memory location is given the value null (which is zero). Null is used because C guarantees that no object will exist at the null address. Thus, any pointer that is null implies that it points to nothing and should not be used.Make a habit to define pointer while declaration, as follows: data_type *p = 0; //Now, the pointer p, points to zero-address. data_type *p = NULL; //same as above. C defines a macro NULL which means ‘a null pointer’. To use this macro, we need to include the header file <stdlib.h>. Function Pointers A function pointer is a variable that points to a function. Definition of a function pointer: returnType (*functPtr)(paramType1, paramType2) Example: fptr is a function pointer that can point to any function that takes an int and float argument and returns a float. Assigning a function pointer float (*fptr)(int, float) float func(int i, float f) { return i+f; } Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) Calling a function the function pointer points to ... fptr = func; // assignment ptr to function fptr(42, 3.14f); // Calling the function with the pointer C’s Dynamic Allocation Functions Dynamic allocation is the means by which a program can obtain memory while it is running. Memory allocated by C’s dynamic allocation functions is obtained from the heap. The heap is free memory that is not used by your program, the operating system, or any other program running in the computer. The size of heap can not usually be known in advance, but it typically contains fairly large amount of free memory. Although the size of the heap is often quite large, it is finite and can be exhausted. The core of C’s allocation system consists of functions malloc() and free(). These functions work together using the free memory region to establish and maintain a list of available storage. The malloc() function allocates memory, and the free() function releases it. Any program that uses these functions must include the header <stdlib.h>. The malloc() function The malloc() functions has the prototype: void *malloc(size_t number_of_bytes); It returns a pointer of the type void *, which means that you can assign it to any type of pointer. After successful call, it returns a pointer to first byte of the region of memory of allocated from the heap. If there is not enough available memory to satisfy the malloc() request, an allocation failure occurs and malloc() returns a null. C defines (using typedef) a special type called size_t, which corresponds loosely to an unsi`gned integer. Technically, the value returned by sizeof is of type size_t. For all practical purposes, however, you can think of it (and use it) as if it were an unsigned integer value. So, if we wish to point a float pointer to a memory of size 5 times the size of float, we can write: p = (float *)malloc(5*sizeof(float)); Here, we used the type cast (float *) to convert pointer of the type (void *) to the type of the pointer to the left side of the assignment. Since the heap is not infinite, whenever you allocate memory, you must check the value returned by malloc() to make sure that it is not null before using the pointer. Using null pointer will automatically crash your Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) program. The proper way to allocate memory and test for a valid pointer is illustrated in this code fragment: p = (int *)malloc(100); if (!p) { printf(“Out of memory.\n”); exit(1); } The free() function The free() function is the opposite of malloc() in that it returns previously allocated memory to the system. Once the memory has been freed, it may be reused by a subsequent call to malloc(). The function free() has the prototype: void free(void *p); Here, p is a pointer to the memory that was previously allocated using malloc(). It is critical that you never call free() with an invalid argument; this will damage the allocation system. It is good practice to explicitly set p to NULL after executing free(p). ARRAYS AND STRINGS Arrays: The general form of declaring a single dimension array is data _ type arr _ name[ size ] ; By this we are actually declaring a pointer of data type ‘data_type’, with name arr_name’. In C89, the size of an array must be specified using a constant expression(In C99 we can declare array of variable size). In C, all arrays have 0 as the index of their first element. Within index box [], we can use any expression while accessing. For example: [i+j], [i++], etc. Strings: String is an array of characters with its last meaningful character followed by NULL character. In C language the group of characters, digits, and symbols enclosed within quotation marks are called as string. There is no string type in C; strings are merely a sort of abbreviated notation for specifying character arrays. The control string of printf(), that we include with in double quotes, is just a string. For a character array to be treated as a string, its last meaningful character must be followed by the ASCII value 0, known as the Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) null character and indicated in C by ‘\0’. For example, the string “review” would be stored as shown in figure: r e v i e w \0 You do not need to add the null to the end of string constants manually—the compiler does this for you automatically. When declaring a character array that will hold a string, you need to declare it to be one character longer than the largest string that it will hold. Length of a string is defined as the number of non-null characters it contains, so the number of locations needed to store a string is one more than the string’s length. Note that one can display character string without knowing the length of the string. Every character array always ends with NULL (‘\0’) character. By using NULL character in while loop we can write a program to display the string. Note that ‘\0’ and ‘0’ are not same. ASCII value of ‘\0’ is 0, whereas ASCII value of ‘0’ is 48. Multidimensional arrays and strings: C supports multidimensional arrays. The simplest multi-dimensional arrays the 2-D arrays are stored in a row-column matrix, where the left index indicates the row and the right indicates the column. This means that the rightmost index changes faster than the leftmost when accessing the elements in the array in the order in which they are actually stored in memory. Some peculiar properties of Array and Strings Initialization during declaration Initialization of whole of the array (or string) can be done at once during its declaration. Declaration of an array is done as follows: type _ specifier array _ name[size1]...[sizeN ] The general form of array’s definition along with declaration is similar to that of variables, as shown here: type _ specifier array _ name[size1]...[sizeN ] = {value _ list}; The value _ list is a comma-separated list of constants whose type is compatible with type_specifier. Character arrays that hold strings allow a shorthand initialization that takes the form: char array _ name[ size ] =" string " ; For example: int marks[3] = {40, 95, 76}; char Name[7] = {‘M’, ‘u’, ‘n’, ‘i’, ‘s’, ‘h’};, or Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) char Name[7] = “Munish”; Declaration and Initialization of multi-dimensional arrays (or strings): Multi-dimensional arrays are initialized the same as single-dimension ones. When initializing a multi-dimensional array, you may add braces around the initializes for each dimension. This is called sub-aggregate grouping. For example: int sqrs[3][2] = {{1,1}, {2,4}, {3,9}} When using sub-aggregate grouping, if you don’t supply enough initializes for a given group, the remaining members will be set to zero, automatically. For example: char word_list[6][15] = {“hello”, “goodbye”, “nice day”, “it’s raining”} Since only four literals are specified, all the elements of last row got null value. Unsized array initialization: If, in an array’s (may be any data type) initialization statement, the size of the array is not specified, the compiler automatically creates an array big enough to hold all the initializes present. For multi-dimensional arrays, you must specify all but the leftmost dimension. Accessing Arrays and Strings, after declaration Overwriting the values of 1-D Array and String: We already know that an array name is pointer of data type ‘data_type’ to the first cell (of size corresponding to the data type) of the arbitrarily fixed continuous array of unused cells, of size ‘ size ’. This pointer is just as another pointer, so we access arrays as if we know a pointer to starting cell of array of cells. After declaration, while giving values to array, each element must be specified. For example: scanf(“%d %d %d”, a[0], a[1], a[2]); For string there are some inbuilt functions, where input is given in pointers that ease our job. For example: gets(Name); (Note that ‘Name’ is a pointer to a string) Overwriting the values of Multi-dimensional Array and Strings: Singly indexed name of 2-D array (or string) acts as pointer to corresponding part of the array. For a array of string, we can get each string individually. It is easy to access an individual string: We simply specify only the left index. For example, the following statement calls gets() with the third string in str_array. gets(str_array[2]); //for scanning whole of the particular string Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) Read out Similarly, we can not print all elements of an array as; printf(“%d”, a);, instead we are to write following: printf(“%d %d %d”, a[0], a[1], a[2]); The following command displays starting address of the array: printf(“%u”, a); Unlike arrays, you can print all the elements of a string at once as follows: puts(Name); Note that the following command will print whole of the string ‘Name’ except its first 2 elements: puts(Name + 2); //’Name+2’ pointer points to 3rd element of string We can also print it as we do for arrays, as: printf(“%c%c%c%c%c%c”, Name[0],Name[1],Name[2],Name[3],Name[4],Name[5]); Accessing array elements Values to array elements can be given as follows: a[1] = 6; …etc a[2] = a[1]; a[++i] = k; *((int *)a + 1) = 6; Passing arrays Assigning the values of one array to another An entire array cannot be assigned to another array because we have no variable that stands for whole of the array. Each element of x must be assigned separately to the corresponding element of y. Passing 1-D Array to Functions In C, we cannot pass an entire array as an argument to a function. We can, however, pass a pointer to an array by specifying the array’s name without and index. For example: fucnt1(a); If a function receives a pointer to a single-dimension array, we can declare its parameter in one of three ways: i) As a pointer: (int *x) ii) As a sized array: (int x[5]) iii) As an un-sized array: (int x[]) Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) Note that in all the three cases, we are assigning address of pointer ‘a’ to pointer ‘x’. Pointer types and Arrays Okay, let's move on. Let us consider why we need to identify the type of variable that a pointer points to, as in: int *ptr; One reason for doing this is so that later, once ptr "points to" something, if we write: *ptr = 2; the compiler will know how many bytes to copy into that memory location pointed to by ptr. If ptr was declared as pointing to an integer, 4 bytes would be copied. Similarly for floats and doubles the appropriate number will be copied. But, defining the type that the pointer points to permits a number of other interesting ways a compiler can interpret code. For example, consider a block in memory consisting if ten integers in a row. That is, 40 bytes of memory are set aside to hold 10 integers. Now, let's say we point our integer pointer ptr at the first of these integers. Furthermore lets say that integer is located at memory location 100 (decimal). What happens when we write: ptr + 1; Because the compiler "knows" this is a pointer (i.e. its value is an address) and that it points to an integer (its current address, 100, is the address of an integer), it adds 4 to ptr instead of 1, so the pointer "points to" the next integer, at memory location 104. Similarly, were the ptr declared as a pointer to a short, it would add 2 to it instead of 1. The same goes for other data types such as floats, doubles, or even user defined data types such as structures. This is obviously not the same kind of "addition" that we normally think of. In C it is referred to as addition using "pointer arithmetic", a term which we will come back to later. Similarly, since ++ptr and ptr++ are both equivalent to ptr + 1 (though the point in the program when ptr is incremented may be different), incrementing a pointer using the unary ++ operator, either pre- or post-, increments the address it stores by the amount sizeof(type) where "type" is the type of the object pointed to. (i.e. 4 for an integer). Since a block of 10 integers located contiguously in memory is, by definition, an array of integers, this brings up an interesting relationship between arrays and pointers. Consider the following: int my_array[] = {1,23,17,4,-5,100}; Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) Here we have an array containing 6 integers. We refer to each of these integers by means of a subscript to my_array, i.e. using my_array[0] through my_array[5]. But, we could alternatively access them via a pointer as follows: int *ptr; ptr = &my_array[0]; /* point our pointer at the first integer in our array */ And then we could print out our array either using the array notation or by dereferencing our pointer. The following code illustrates this: ----------- Sample Program ----------------------------------#include <stdio.h> int my_array[] = {1,23,17,4,-5,100}; int *ptr; int main(void) { int i; ptr = &my_array[0]; /* point our pointer to the first element of the array */ printf("\n\n"); for (i = 0; i < 6; i++) { printf("my_array[%d] = %d ",i,my_array[i]); /*<-- A */ printf("ptr + %d = %d\n",i, *(ptr + i)); /*<-- B */ } return 0; } Compile and run the above program and carefully note lines A and B and that the program prints out the same values in either case. Also observe how we dereferenced our pointer in line B, i.e. we first added i to it and then dereferenced the new pointer. Change line B to read: printf("ptr + %d = %d\n",i, *ptr++); and run it again... then change it to: printf("ptr + %d = %d\n",i, *(++ptr)); Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) and try once more. Each time try and predict the outcome and carefully look at the actual outcome. In C, the standard states that wherever we might use &var_name[0] we can replace that with var_name, thus in our code where we wrote: ptr = &my_array[0]; we can write: ptr = my_array; to achieve the same result. This leads many texts to state that the name of an array is a pointer. I prefer to mentally think "the name of the array is the address of first element in the array". Many beginners (including myself when I was learning) have a tendency to become confused by thinking of it as a pointer. For example, while we can write, ptr = my_array; we cannot write : my_array = ptr; The reason is that while ptr is a variable, my_array is a constant. That is, the location at which the first element of my_array will be stored cannot be changed once my_array[] has been declared. Now, let's delve a little further into the difference between the names ptr and my_array as used above. Some writers will refer to an array's name as a constant pointer. What do we mean by that? Well, to understand the term "constant" in this sense, let's go back to our definition of the term "variable". When we declare a variable we set aside a spot in memory to hold the value of the appropriate type. Once that is done the name of the variable can be interpreted in one of two ways. When used on the left side of the assignment operator, the compiler interprets it as the memory location to which to move that value resulting from evaluation of the right side of the assignment operator. But, when used on the right side of the assignment operator, the name of a variable is interpreted to mean the contents stored at that memory address set aside to hold the value of that variable. With that in mind, let's now consider the simplest of constants, as in: int i, k; i = 2; Here, while i is a variable and then occupies space in the data portion of memory, 2 is a constant and, as such, instead of setting aside memory in the data segment, it is imbedded directly in the code segment of memory. That is, while writing something like k = i; tells the compiler to create code which at run time will look at memory location &i to determine the value to be moved to k, code created by i = 2; simply puts the 2 in the code Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) and there is no referencing of the data segment. That is, both k and i are objects, but 2 is not an object. Similarly, in the above, since my_array is a constant, once the compiler establishes where the array itself is to be stored, it "knows" the address of my_array[0] and on seeing: ptr = my_array; It simply uses this address as a constant in the code segment and there is no referencing of the data segment beyond that. This might be a good place explain further the use of the (void *) expression. As we have seen we can have pointers of various types. So far we have discussed pointers to integers and pointers to characters. In coming chapters we will be learning about pointers to structures and even pointer to pointers. Also we have learned that on different systems the size of a pointer can vary. As it turns out it is also possible that the size of a pointer can vary depending on the data type of the object to which it points. Thus, as with integers where you can run into trouble attempting to assign a long integer to a variable of type short integer, you can run into trouble attempting to assign the values of pointers of various types to pointer variables of other types. To minimize this problem, C provides for a pointer of type void. We can declare such a pointer by writing: void *vptr; Pointers and Strings The study of strings is useful to further tie in the relationship between pointers and arrays. It also makes it easy to illustrate how some of the standard C string functions can be implemented. Finally it illustrates how and when pointers can and should be passed to functions. In C, strings are arrays of characters. This is not necessarily true in other languages. In BASIC, Pascal, Fortran and various other languages, a string has its own data type. But in C it does not. In C a string is an array of characters terminated with a binary zero character (written as '\0'). To start off our discussion we will write some code which, while preferred for illustrative purposes, you would probably never write in an actual program. Consider, for example: char my_string[40]; my_string[0] = 'T'; my_string[1] = 'e'; my_string[2] = 'd': my_string[3] = '\0'; Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) While one would never build a string like this, the end result is a string in that it is an array of characters terminated with a nul character. By definition, in C, a string is an array of characters terminated with the nul character. Be aware that "nul" is not the same as "NULL". The nul refers to a zero as defined by the escape sequence '\0'. That is it occupies one byte of memory. NULL, on the other hand, is the name of the macro used to initialize null pointers. NULL is #defined in a header file in your C compiler, nul may not be #defined at all. Since writing the above code would be very time consuming, C permits two alternate ways of achieving the same thing. First, one might write: char my_string[40] = {'T', 'e', 'd', '\0',}; But this also takes more typing than is convenient. So, C permits: char my_string[40] = "Ted"; When the double quotes are used, instead of the single quotes as was done in the previous examples, the nul character ( '\0' ) is automatically appended to the end of the string. In all of the above cases, the same thing happens. The compiler sets aside an contiguous block of memory 40 bytes long to hold characters and initialized it such that the first 4 characters are Ted\0. Now, consider the following program: ------------------program------------------------------------#include <stdio.h> char strA[80] = "A string to be used for demonstration purposes"; char strB[80]; int main(void) { char *pA; /* a pointer to type character */ char *pB; /* another pointer to type character */ puts(strA); /* show string A */ pA = strA; /* point pA at string A */ puts(pA); /* show what pA is pointing to */ pB = strB; /* point pB at string B */ putchar('\n'); /* move down one line on the screen */ while(*pA != '\0') /* line A (see text) */ { *pB++ = *pA++; /* line B (see text) */ Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) } *pB = '\0'; /* line C (see text) */ puts(strB); /* show strB on screen */ return 0; } In the above we start out by defining two character arrays of 80 characters each. Since these are globally defined, they are initialized to all '\0's first. Then, strA has the first 42 characters initialized to the string in quotes. Now, moving into the code, we declare two character pointers and show the string on the screen. We then "point" the pointer pA at strA. That is, by means of the assignment statement we copy the address of strA[0] into our variable pA. We now use puts() to show that which is pointed to by pA on the screen. Consider here that the function prototype for puts() is: int puts(const char *s); For the moment, ignore the const. The parameter passed to puts() is a pointer, that is the value of a pointer (since all parameters in C are passed by value), and the value of a pointer is the address to which it points, or, simply, an address. Thus when we write puts(strA); as we have seen, we are passing the address of strA[0]. Similarly, when we write puts(pA); we are passing the same address, since we have set pA = strA; Given that, follow the code down to the while() statement on line A. Line A states: While the character pointed to by pA (i.e. *pA) is not a nul character (i.e. the terminating '\0'), do the following: Line B states: copy the character pointed to by pA to the space pointed to by pB, then increment pA so it points to the next character and pB so it points to the next space. When we have copied the last character, pA now points to the terminating nul character and the loop ends. However, we have not copied the nul character. And, by definition a string in C must be nul terminated. So, we add the nul character with line C. It is very educational to run this program with your debugger while watching strA, strB, pA and pB and single stepping through the program. It is even more educational if instead of simply defining strB[] as has been done above, initialize it also with something like: strB[80] = "12345678901234567890123456789012345678901234567890" Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) where the number of digits used is greater than the length of strA and then repeat the single stepping procedure while watching the above variables. Give these things a try! Getting back to the prototype for puts() for a moment, the "const" used as a parameter modifier informs the user that the function will not modify the string pointed to by s, i.e. it will treat that string as a constant. Of course, what the above program illustrates is a simple way of copying a string. After playing with the above until you have a good understanding of what is happening, we can proceed to creating our own replacement for the standard strcpy() that comes with C. It might look like: char *my_strcpy(char *destination, char *source) { char *p = destination; while (*source != '\0') { *p++ = *source++; } *p = '\0'; return destination; } In this case, I have followed the practice used in the standard routine of returning a pointer to the destination. Again, the function is designed to accept the values of two character pointers, i.e. addresses, and thus in the previous program we could write: int main(void) { my_strcpy(strB, strA); puts(strB); } I have deviated slightly from the form used in standard C which would have the prototype: char *my_strcpy(char *destination, const char *source); Here the "const" modifier is used to assure the user that the function will not modify the contents pointed to by the source pointer. You can prove this by modifying the function above, and its prototype, to include the "const" modifier as shown. Then, within the Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) function you can add a statement which attempts to change the contents of that which is pointed to by source, such as: *source = 'X'; which would normally change the first character of the string to an X. The const modifier should cause your compiler to catch this as an error. Try it and see. Now, let's consider some of the things the above examples have shown us. First off, consider the fact that *ptr++ is to be interpreted as returning the value pointed to by ptr and then incrementing the pointer value. This has to do with the precedence of the operators. Were we to write (*ptr)++ we would increment, not the pointer, but that which the pointer points to! i.e. if used on the first character of the above example string the 'T' would be incremented to a 'U'. You can write some simple example code to illustrate this. Recall again that a string is nothing more than an array of characters, with the last character being a '\0'. What we have done above is deal with copying an array. It happens to be an array of characters but the technique could be applied to an array of integers, doubles, etc. In those cases, however, we would not be dealing with strings and hence the end of the array would not be marked with a special value like the nul character. We could implement a version that relied on a special value to identify the end. For example, we could copy an array of positive integers by marking the end with a negative integer. On the other hand, it is more usual that when we write a function to copy an array of items other than strings we pass the function the number of items to be copied as well as the address of the array, e.g. something like the following prototype might indicate: void int_copy(int *ptrA, int *ptrB, int nbr); where nbr is the number of integers to be copied. You might want to play with this idea and create an array of integers and see if you can write the function int_copy() and make it work. This permits using functions to manipulate large arrays. For example, if we have an array of 5000 integers that we want to manipulate with a function, we need only pass to that function the address of the array (and any auxiliary information such as nbr above, depending on what we are doing). The array itself does not get passed, i.e. the whole array is not copied and put on the stack before calling the function, only its address is sent. This is different from passing, say an integer, to a function. When we pass an integer we make a copy of the integer, i.e. get its value and put it on the stack. Within the function any manipulation of the value passed can in no way effect the original integer. Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) But, with arrays and pointers we can pass the address of the variable and hence manipulate the original values. More on Strings Well, we have progressed quite a way in a short time! Let's back up a little and look at what was done in Chapter 3 on copying of strings but in a different light. Consider the following function: char *my_strcpy(char dest[], char source[]) { int i = 0; while (source[i] != '\0') { dest[i] = source[i]; i++; } dest[i] = '\0'; return dest; } Recall that strings are arrays of characters. Here we have chosen to use array notation instead of pointer notation to do the actual copying. The results are the same, i.e. the string gets copied using this notation just as accurately as it did before. This raises some interesting points which we will discuss. Since parameters are passed by value, in both the passing of a character pointer or the name of the array as above, what actually gets passed is the address of the first element of each array. Thus, the numerical value of the parameter passed is the same whether we use a character pointer or an array name as a parameter. This would tend to imply that somehow source[i] is the same as *(p+i). In fact, this is true, i.e wherever one writes a[i] it can be replaced with *(a + i) without any problems. In fact, the compiler will create the same code in either case. Thus we see that pointer arithmetic is the same thing as array indexing. Either syntax produces the same result. This is NOT saying that pointers and arrays are the same thing, they are not. We are only saying that to identify a given element of an array we have the choice of two syntaxes, one using array indexing and the other using pointer arithmetic, which yield identical results. Now, looking at this last expression, part of it.. (a + i), is a simple addition using the + operator and the rules of C state that such an expression is Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) commutative. That is (a + i) is identical to (i + a). Thus we could write *(i + a) just as easily as *(a + i). But *(i + a) could have come from i[a] ! From all of this comes the curious truth that if: char a[20]; int i; writing a[3] = 'x'; is the same as writing 3[a] = 'x'; Try it! Set up an array of characters, integers or longs, etc. and assigned the 3rd or 4th element a value using the conventional approach and then print out that value to be sure you have that working. Then reverse the array notation as I have done above. A good compiler will not balk and the results will be identical. A curiosity... nothing more! Now, looking at our function above, when we write: dest[i] = source[i]; due to the fact that array indexing and pointer arithmetic yield identical results, we can write this as: *(dest + i) = *(source + i); But, this takes 2 additions for each value taken on by i. Additions, generally speaking, take more time than incrementations (such as those done using the ++ operator as in i++). This may not be true in modern optimizing compilers, but one can never be sure. Thus, the pointer version may be a bit faster than the array version. Another way to speed up the pointer version would be to change: while (*source != '\0') to simply while (*source) since the value within the parenthesis will go to zero (FALSE) at the same time in either case. At this point you might want to experiment a bit with writing some of your own programs using pointers. Manipulating strings is a good place to experiment. You might want to write your own versions of such standard functions as: strlen(); strcat(); strchr(); Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) and any others you might have on your system. We will come back to strings and their manipulation through pointers in a future chapter. For now, let's move on and discuss structures for a bit. STRUCTURES Declaration of a Structure A structure is a definition of new a data-type. It contains a number of old data-types grouped together. These data-types may or may not be of the same type. The general form of a structure is given below (definition of new-data type): struct <struct_name> { Declaration of <structure_element_1>; Declaration of <structure_element_2>; … }; Once the new structure data-type has been defined one or more variables can be declared to be of that type. Now, ‘struct <struct_name>’ will start functioning as new-data type. struct <struct_name> <structure_variables_1>, …, <structure_variable_n> ; Initializing a structural variable Like primary variables and arrays, structure-variables can also be initialized where they are declared. The format used is quite similar to that used to initiate arrays. struct book { char name[10]; float price; int pages; }; struct book b1 = {“Basic”, 130.00, 550}; struct book b2 = {“Physics”, 150.50, 800}; Note that the definitions like above, can be done only during declaration. The ‘structure variables’ to which definition is to be done, should not be part of ‘array of structure Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) variables’.Whatever be the elements of a structure, they are always stored in contiguous memory locations. Accessing Structure-variable’s elements In arrays we can access individual elements of an array using a subscript. Structures use a different scheme. They use a dot (.) operator. So to refer to pages of book b1 we have to use; b1.pages That is, <structure_variable>.<structure_element> We can treat it as our simple variable (just forget <structure_variable> and dot-operator) Array of structure The way of declaring and accessing an array of structures is given below: Declaration: struct book { char name[10]; float price; unsigned int pages; }; struct book b[50]; Accessing: int i; for (i=0, i<=49, i++) { printf(“\nEnter name, prices and pages”); scanf(“%c %f %u”, &b[i].name, &b[i].price, &b[i].pages); } Additional features Assigning one structure variable to another The value of a structure variable can be assigned to another structure variable of the same type using the assignment operator. We do not need to assign the value of each member separately. Both structures must be of same structure-type. Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) Passing a structure variable to a function When a structure-variable is used as an argument to a function, the entire structure-variable is passed using the normal call-by-value method. When using a structure as a parameter, remember that the type of the argument must match the type of the parameter. It is not sufficient for them simply to be physically similar; their type names must match. Thus in this case, structure definition should be global, and both argument and parameter structures should be of structure-type defined globally. Nesting of structures One structure can be nested within another structure. Using this facility complex data types can be created. main() { struct address { char phone[15]; char city[25]; int pin; }; struct emp { char name[25]; struct address a; } struct emp e = {“jeru”, “5311024”, “nagpur”, 10}; printf(“\ncity = %s pin = %d”, e.a.city, e.a.pin); } Pointers and Structures As you may know, we can declare the form of a block of data containing different data types by means of a structure declaration. For example, a personnel file might contain structures which look something like: struct tag { char lname[20]; /* last name */ Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) char fname[20]; /* first name */ int age; /* age */ float rate; /* e.g. 12.75 per hour */ }; Let's say we have a bunch of these structures in a disk file and we want to read each one out and print out the first and last name of each one so that we can have a list of the people in our files. The remaining information will not be printed out. We will want to do this printing with a function call and pass to that function a pointer to the structure at hand. For demonstration purposes I will use only one structure for now. But realize the goal is the writing of the function, not the reading of the file which, presumably, we know how to do. For review, recall that we can access structure members with the dot operator as in: --------------- program -----------------#include <stdio.h> #include <string.h> struct tag { char lname[20]; /* last name */ char fname[20]; /* first name */ int age; /* age */ float rate; /* e.g. 12.75 per hour */ }; struct tag my_struct; /* declare the structure my_struct */ int main(void) { strcpy(my_struct.lname,"Jensen"); strcpy(my_struct.fname,"Ted"); printf("\n%s ",my_struct.fname); printf("%s\n",my_struct.lname); return 0; } Now, this particular structure is rather small compared to many used in C programs. To the above we might want to add: Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) date_of_hire; (data types not shown) date_of_last_raise; last_percent_increase; emergency_phone; medical_plan; Social_S_Nbr; etc..... If we have a large number of employees, what we want to do is manipulate the data in these structures by means of functions. For example we might want a function print out the name of the employee listed in any structure passed to it. However, in the original C (Kernighan & Ritchie, 1st Edition) it was not possible to pass a structure, only a pointer to a structure could be passed. In ANSI C, it is now permissible to pass the complete structure. But, since our goal here is to learn more about pointers, we won't pursue that. Anyway, if we pass the whole structure it means that we must copy the contents of the structure from the calling function to the called function. In systems using stacks, this is done by pushing the contents of the structure on the stack. With large structures this could prove to be a problem. However, passing a pointer uses a minimum amount of stack space. In any case, since this is a discussion of pointers, we will discuss how we go about passing a pointer to a structure and then using it within the function. Consider the case described, i.e. we want a function that will accept as a parameter a pointer to a structure and from within that function we want to access members of the structure. For example we want to print out the name of the employee in our example structure. Okay, so we know that our pointer is going to point to a structure declared using struct tag. We declare such a pointer with the declaration: struct tag *st_ptr; and we point it to our example structure with: st_ptr = &my_struct; Now, we can access a given member by de-referencing the pointer. But, how do we dereference the pointer to a structure? Well, consider the fact that we might want to use the pointer to set the age of the employee. We would write: (*st_ptr).age = 63; Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) Look at this carefully. It says, replace that within the parenthesis with that which st_ptr points to, which is the structure my_struct. Thus, this breaks down to the same as my_struct.age. However, this is a fairly often used expression and the designers of C have created an alternate syntax with the same meaning which is: st_ptr->age = 63; With that in mind, look at the following program: ------------ program --------------------#include <stdio.h> #include <string.h> struct tag{ /* the structure type */ char lname[20]; /* last name */ char fname[20]; /* first name */ int age; /* age */ float rate; /* e.g. 12.75 per hour */ }; struct tag my_struct; /* define the structure */ void show_name(struct tag *p); /* function prototype */ int main(void) { struct tag *st_ptr; st_ptr = &my_struct; /* a pointer to a structure */ /* point the pointer to my_struct */ strcpy(my_struct.lname,"Jensen"); strcpy(my_struct.fname,"Ted"); printf("\n%s ",my_struct.fname); printf("%s\n",my_struct.lname); my_struct.age = 63; show_name(st_ptr); /* pass the pointer */ return 0; } void show_name(struct tag *p) { Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) printf("\n%s ", p->fname); /* p points to a structure */ printf("%s ", p->lname); printf("%d\n", p->age); } Again, this is a lot of information to absorb at one time. The reader should compile and run the various code snippets and using a debugger monitor things like my_struct and p while single stepping through the main and following the code down into the function to see what is happening. The typedef You can define new data type names by using the keyword typedef. You are not actually creating a new data type, but rather defining a new name for an existing type. The general form of typedef statement is: typedef type new_name; where, type is any valid data type, and new_name is the new name (preferably in CAPS) for this type. The new name you define is in addition to, not a replacement for, the existing type name. For example: typedef float BALANCE; typedef struct node *NODE; This statement tells the compiler to recognize BALANCE as another name for float. Next, you could create a float variable using BALANCE; BALANCE over_due; NODE p; //Here, p is a pointer of the type (strut node *). Now the BALANCE has been defined, it can be used in another typedef. typedef BALANCE OVERDRAFT; FILES We frequently use files for storing information which can be processed by our programs. In order to store information permanently and retrieve it, we need to use files. Files are not only used for data. Our programs are also stored in files. The editor which you use to enter your program and save, simply manipulates files for you. In order to use files we have to learn about File I/O i.e. how to write information to a file and how to read information from a file. We will see that file I/O is almost identical to the terminal I/O that we have being using so far. The Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) primary difference between manipulating files and doing terminal I/O is that we must specify in our programs which files we wish to use. As you know, you can have many files on your disk. If you wish to use a file in your programs, then you must specify which file or files you wish to use. Specifying the file you wish to use is referred to as opening the file. When you open a file you must also specify what you wish to do with it i.e. Read from the file, Write to the file, or both. Because you may use a number of different files in your program, you must specify when reading or writing which file we want to use. This is accomplished by using a variable called a file pointer. Every file you open has its own file pointer variable. When you wish to write to a file you specify the file by using its file pointer variable. You declare these file pointer variables as follows: FILE *fopen(), *fp1, *fp2, *fp3; The variables fp1, fp2, fp3 are file pointers. You may use any name you wish. The file <stdio.h> contains declarations for the Standard I/O library and should always be included at the very beginning of C programs using files. Constants such as FILE, EOF and NULL are defined in <stdio.h>. You should note that a file pointer is simply a variable like an integer or character. It does not point to a file or the data in a file. It is simply used to indicate which file your I/O operation refers to. A file number is used in the Basic language and a unit number is used in Fortran for the same purpose. The function fopen is one of the Standard Library functions and returns a file pointer which you use to refer to the file you have opened e.g. fp = fopen( “prog.c”, “r”) ; The above statement opens a file called prog.c for reading and associates the file pointer fp with the file. When we wish to access this file for I/O, we use the file pointer variable fp to refer to it. You can have up to about 20 files open in your program - you need one file pointer for each file you intend to use. File I/O The Standard I/O Library provides similar routines for file I/O similar to that of standard I/O. The routine getc(fp) is similar to getchar() and putc(c,fp) is similar to putchar(c). Thus the statement c = getc(fp); ----Æ reads the next character from the file referenced by fp and the statement putc(c,fp); ---Æ writes the character c into file referenced by fp. Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) /* file.c: Display contents of a file on screen */ #include <stdio.h> void main() { FILE *fopen(), *fp; int c ; fp = fopen( “prog.c”, “r” ); c = getc( fp ) ; while ( c != EOF ) { putchar( c ); c = getc ( fp ); } fclose( fp ); } In this program, we open the file prog.c for reading. We then read a character from the file. This file must exist for this program to work. If the file is empty, we are at the end, so getc returns EOF a special value to indicate that the end of file has been reached. (Normally -1 is used for EOF) The while loop simply keeps reading characters from the file and displaying them, until the end of the file is reached. The function fclose is used to close the file i.e. indicate that we are finished processing this file. We could reuse the file pointer fp by opening another file. It displays file contents on the screen, but only for a file called prog.c. By allowing the user enter a file name, which would be stored in a string, we can modify the above /* cat2.c: Prompt user for filename and display file on screen */ #include <stdio.h> void main() { FILE *fopen(), *fp; int c ; char filename[40] ; printf(“Enter file to be displayed: “); gets( filename ) ; Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) fp = fopen( filename, “r”); c = getc( fp ) ; while ( c != EOF ) { putchar(c); c = getc ( fp ); } fclose( fp ); } In this program, we pass the name of the file to be opened which is stored in the array called filename, to the fopen function. In general, anywhere a string constant such as “prog,c” can be used so can a character array such as filename. (Note the reverse is not true). The above programs suffer a major limitation. They do not check whether the files to be used exist or not. If you attempt to read from an non-existent file, your program will crash!! The fopen function was designed to cope with this eventuality. It checks if the file can be opened appropriately. If the file cannot be opened, it returns a NULL pointer. Thus by checking the file pointer returned by fopen, you can determine if the file was opened correctly and take appropriate action e.g. fp = fopen (filename, “r”) ; if ( fp == NULL) { printf(“Cannot open %s for reading \n”, filename ); exit(1) ; /*Terminate program: Commit suicide !!*/ } The above code fragment show how a program might check whether a file could be opened appropriately. The function exit() is a special function which terminates your program immediately. exit(0) mean that you wish to indicate that your program terminated successfully whereas a nonzero value means that your program is terminating due to an error condition. Alternatively, you could prompt the user to enter the filename again, and try to open it again: fp = fopen (fname, “r”) ; while ( fp == NULL) { Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) printf(“Cannot open %s for reading \n”, fname ); printf(“\n\nEnter filename :” ); gets( fname ); fp = fopen (fname, “r”) ; } In this code fragment, we keep reading filenames from the user until a valid existing filename is entered. Exercise: Modify the above code fragment to allow the user 3 chances to enter a valid filename. If a valid file name is not entered after 3 chances, terminate the program. RULE: Always check when opening files, that fopen succeeds in opening the files appropriately. Obeying this simple rule will save you much heartache. Example 1: Write a program to count the number of lines and characters in a file. Note: Each line of input from a file or keyboard will be terminated by the newline character ‘\n’. Thus by counting newlines we know how many lines there are in our input. /*count.c : Count characters in a file*/ #include <stdio.h> void main() /* Prompt user for file and count number of characters and lines in it*/ { FILE *fopen(), *fp; int c , nc, nlines; char filename[40] ; nlines = 0 ; nc = 0; printf(“Enter file name: “); gets( filename ); fp = fopen( filename, “r” ); if ( fp == NULL ) { printf(“Cannot open %s for reading \n”, filename ); Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) exit(1); /* terminate program */ } c = getc( fp ) ; while ( c != EOF ) { if ( c == ‘\n’ ) nlines++ ; nc++ ; c = getc ( fp ); } fclose( fp ); if ( nc != 0 ) { printf(“There are %d characters in %s \n”, nc, filename ); printf(“There are %d lines \n”, nlines ); } else printf(“File: %s is empty \n”, filename ); } Example 2: Write a program to display file contents 20 lines at a time. The program pauses after displaying 20 lines until the user presses either Q to quit or Return to display the next 20 lines. (The Unix operating system has a command called more to do this ) As in previous programs, we read the filename from user and open it appropriately. We then process the file: read character from file while not end of file and not finished do begin display character if character is newline then linecount = linecount + 1; if linecount == 20 then begin Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) linecount = 1 ; Prompt user and get reply; end read next character from file end /* display.c: File display program */ /* Prompt user for file and display it 20 lines at a time*/ #include <stdio.h> void main() { FILE *fopen(), *fp; int c , linecount; char filename[40], reply[40]; printf(“Enter file name: “); gets( filename ); fp = fopen( filename, “r” ); if ( fp == NULL ) /* open for reading */ /* check does file exist etc */ { printf(“Cannot open %s for reading \n”, filename ); exit(); /* terminate program */ } linecount = 1 ; reply[0] = ‘\0’ ; c = getc( fp ) ; /* Read 1st character if any */ while ( c != EOF && reply[0] != ‘Q’ && reply[0] != ‘q’) { putchar( c ) ; /* Display character */ if ( c == ‘\n’ ) linecount = linecount+ 1 ; if ( linecount == 20 ) { linecount = 1 ; Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) printf(“[Press Return to continue, Q to quit]”); gets( reply ) ; } c = getc ( fp ); } fclose( fp ); } The string reply will contain the users response. The first character of this will be reply[0]. We check if this is ‘q’ or ‘Q’. The brackets [] in printf are used to distinguish the programs message from the file contents. Errors and End of the file Error in opening the file To detect any error in opening the file, such as a write-protected or full-disk, before our program attempts to write to it, use the following code: FILE *fp; //After this, fp will point to some Garbage Address clrscr(); //Important to include fp = fopen(“Test”, “r”); if (fp == NULL) { printf(“Error in opening the file\n”); getch(); exit(0); } Error in closing the file A return value of zero signifies a successful close operation (that’s strange, as usually we associate non-successful operation with zero). The function returns EOF if an error occurs. Generally, fclose() will fail only when a disk has been prematurely removed from the drive or there is no more space on the disk. if (fclose(fp) != 0) { printf(“\nError in closing the file”); Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) getch(); } Determining end of the file The getc() returns EOF when it fails and when it reaches the end of the file. Using only the return value of getc(), it is impossible to know which occurred. C includes the function feof(), which determines when the end of the file has been encountered. Note that feof() returns true if the end of the file has been reached; otherwise it returns zero. The following routine reads a file until end of the file is encounter. for ((i=0);(!feof(fp));(++i)) { ch = getc(fp); putchar(ch); } We can use ‘feof(fp)==0’ instead of ‘!feof(fp)’ File Opening Modes Following is a list of all possible modes in which a file can be opened. The tasks performed by fopen() when a file is opened in each of these modes are also mentioned. “r” - Which file: Searches for already existing file. Operations possible: Initially pointer points to the first character in the file. Can read the already existing contents. “w” -Which file: First searches for already existing file. If file doesn’t exist, new file is created. Operations possible: Initially pointer points to the first character in the file. Already existing data can be modified.Just written data can also be modified. Printing will result in display error. “a” - Which file: First searches for already existing file. If file doesn’t exist, new file is created. Operations possible: Initially pointer points just after the last character of already existing data. Thus, already existing data can not be modified or accessed. After writing the data there Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) pointer points to next position, this becomes its minimum position. Thus, just written data can also not be modified nor accessed. “r+” – Complete access to already existingfile. Note that after writing anything (even a single character) anywhere (even in middle of the file) in the file, if we are interested in reading; we must set pointer of the file. In case we are interested only in reading the further part of the file, even then we must use: fseek ( fp , 0, SEEK _ CUR ); Also note that after writing anything to a file, we can not use the condition ! feof ( fp ) until we close that file using fclose ( fp ) and open it again. “w+” (“w” + reading) “a+” (“a” + reading) If you are interested in writing, it behaves just as “w”. If you are interested in reading, all the contents of the file (both already existing and just written) can be read out. Thus, pointer may be set to point to first character of the file. Reading and Writing in files The putc() function for input Its syntax is given below: putc(ch, fp); putc(‘character_constant’, fp); Working: The putc() function writes characters to a file that was previously opened for writing using fopen() function. New line will not be printed automatically. Return value:If putc() operation is successful, it returns the character written. Otherwise, it returns EOF. The getc() function for input Its syntax is given below: ch = getc(fp); Working: The getc() function reads characters from a file opened in read mode by fopen(). Return Value: The getc() function returns an EOF when the end of the file has been reached. However, getc() also returns EOF if an error occurs. Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) The fputs() function for input Syntax: fputs ( str , fp ); Working: The fputs() function writes the string pointed to by str to the specified stream. No newline character is written automatically (unlike puts()). Return Value: It returns EOF if an error occurs. The fgets() function for input Note: Do not practice this function. Its syntax is given below: fgets ( str , length , fp ); where, length = strlen ( str ) + 1; Working: The fgets () function reads a string from the specified stream until either a newline character is read or a maximum of length − 1 characters have been read. If a newline is read, it will be part of the string (unlike the gets() function). The resultant string will always be null terminated. A newline character is always kept in str by fgets () after length − 1 characters or already read newline character. Cases 1: Characters before newline (in specifies stream) ≥ length − 1 The string str : M U N I S \n \0 Case 2: Characters before newline = length − 2 The string str : S I T A \n \n \0 Case 3: Characters before newline = length − 3 The string str : R A M \n \n \0 Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) You do not need to add the null to the end Return value: The function returns str if successful and null pointer if an error occurs. Flushing a Stream If you wish to flush the contents of an output stream, use the fflush() function. fflush(fp); This function writes the contents of any buffered data to the file associated with fp. If you call fflush() with fp being null, all files opened for output are flushed. The fflush() function returns zero if successful; otherwise, it returns EOF. The fread() function For fread(), buffer is a pointer to a region of memory (for instance an integer-array) that will receive the data from the file. The value of count determines consecutively how many times are read, with each item being num_bytes bytes in length. Finally, fp is a file pointer to a previously opened stream. fread(void *buffer, size_t num_bytes, size_t count, FILE *fp); For example: num[3]; fread(&num, sizeof(int), 3, fp); The fwrite() function For fwrite(), buffer is a pointer to a information (for instance an integer-array) that will be written to the file. The value of count determines consecutively how many times are written, with each item being num_bytes bytes in length. Finally, fp is a file pointer to a previously opened stream. fwrite(const void *byffer, size_t num_bytes, size_t count, FILE *fp); For example: num[3] = {2, 4, 6}; fwrite(&num, sizeof(int), 3, fp); Changing the cursor position The rewind() function Do not practice it. The rewind() function resets the file position indicator to the beginning of the file (to where the pointer was pointing initially when file was opened) specified as its argument. rewind(fp); Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) The fseek() Random-Access I/O You can perform random read and write operations using the C I/O system with the help of fseek(), which sets the file position indicator. Its prototype is shown here: int fseek(FILE *fp, long int numbytes, int origin); Here, fp is a file pointer returned by a call to fopen(), numbytes is the number of bytes from origin, which will become the new current position, and origin is one of the following macros: Origin Macro Name Beginning of file SEEK_SET Current position SEEK_CUR End of file SEEK_END You can use fseek() to seek in multiples of any type of data by simply multiplying the size of the data by the number of the item you want to reach. For example: fseek(fp, -(9*sizeof(struct addr)), SEEK_CUR); You can determine current location of a file using ftell(). Its prototype is long int ftell(FILE *fp); It returns the location of the current position of the file associated with fp. If a failure occurs, it returns -1. Linked List Why Linked Lists? Linked lists and arrays are similar since they both store collections of data. The terminology is that arrays and linked lists store "elements" on behalf of "client" code. The specific type of element is not important since essentially the same structure works to store elements of any type. One way to think about linked lists is to look at how arrays work and think about alternate approaches. What Linked Lists Look Like An array allocates memory for all its elements lumped together as one block of memory. In contrast, a linked list allocates space for each element separately in its own block of memory called a "linked list element" or "node". The list gets is overall structure by using pointers to connect all its nodes together like the links in a chain. Each node contains two fields: a "data" field to store whatever element type the list holds for its client, and a "next" field which is a pointer that is used to link one node to the next node. Each node is Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) allocated in the heap with a call to malloc(), so the node memory continues to exist until it is explicitly deallocated with a call to free(). Here is what a list containing the numbers 1, 2, and 3 might look like... The beginning of the linked list is stored in a "head" pointer which points to the first node. The first node contains a pointer to the second node. The second node contains a pointer to the third node, ... and so on. The last node in the list has its .next field set to NULL to mark the end of the list. Code can access any node in the list by starting at the head and following the .next pointers. Operations towards the front of the list are fast while operations which access node farther down the list takes longer the further they are from the front. This "linear" cost to access a node is fundamentally more costly then the constant time [ ] access provided by arrays. In this respect, linked lists are definitely less efficient than arrays. Implement the linked list using arrays. There are so many types of linked list available. It is up to the user’s interest to learn more. Stack: Basic ideas A Stack is a data structure with which you can perform the following operations (their conventional names are in bold): • Add data, one item at a time, Push • Access the data item on the top (other data items are hidden from view), Top • Remove and discard the data item on the top, thus exposing the next item which now becomes the top item, Pop. The order in which data in added ("pushed"), accessed and removed ("popped") is LIFO (last in, first out). This is different from a queue, in which it's FIFO (first in, first out). Both stacks and queues are ubiquitous in RL ("real life"). Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) Stacks can be implemented based on arrays or linked lists. If the implementation is based on a statically allocated array, the stack has a limit on the number of elements you can push into it, which is the size of the array. Sample Program for stack operations using arrays int arr[10]; int index=-1; // the index of the last added (pushed) item a stack is initially empty, void main() { int n,ch,op=1; void push(int); int pop(); clrscr(); while(op==1) { printf("Stack operations....1. Push 2. Pop"); scanf("%d",&ch); switch(ch) { case 1: printf("Give the element to be pushed..."); scanf("%d",&n); push(n); break; case 2: n = pop(); printf("Element %d is poped ",n); break; default:printf("Please give a vaid choice"); break; } printf("Press 1 to continue and other digits to quit..."); scanf("%d",&op); } } Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) void push( int item ) { if( index >= 10 ){ printf( "Warning: IntStack overflow, push failed!\n"); return; } index++; arr[index]=item; } int pop(){ if( index == -1 ){ printf( "Warning: IntStack underflow, nothing to pop!\n"); return; } return arr[index--]; } Queues The queue is another data structure. A physical analogy for a queue is a line at a bank. When you go to the bank, customers go to the rear (end) of the line and customers come off of the line (i.e., are serviced) from the front of the line. Aside: In fact, other English-speaking countries use this term for a line, e.g., they might say "Queue up!" rather than "Get in a line!" Like a stack, a queue usually holds things of the same type. We usually draw queues horizontally. Here's a queue of characters with 3 elements: queue ------------|a|b|c| ------------^ | front ^ | rear Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) The main property of a queue is that objects go on the rear and come off of the front of the queue. Here are the minimal set of operations we'd need for an abstract queue: Enter (or Insert) o Places an object at the rear of the queue. Delete (or Remove) o Removes an object from the front of the queue and produces that object. IsEmpty o Reports whether the queue is empty or not. Order produced by a queue: Queues are useful because they produce a certain order in which the contents of the queue are used. Let's see what order that is by looking at a queue of characters. Now, what would a particular sequence of Enter and Deletes do to this queue: queue ------------|a|b|c| ------------^ | front ^ | rear Now, Enter(queue, 'd')... queue ----------------|a|b|c|d| ----------------^ | front ^ | rear Now, ch = Delete(queue)... queue ch ------------- ----|b|c|d| |a| Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) ------------- ----^ | ^ | front rear You'll notice that the queue enforces a certain order to the use of its contents. Is the ordering of the queue Last thing In is the First thing Out (LIFO) or First Thing In is the First thing Out (FIFO)? Answer: Queues produce FIFO order. Remember that stacks produce LIFO order. Implementing a queue with an array: Since a queue usually holds a bunch of items with the same type, we could implement a queue with an array. Let's simplify our array implementation of a queue by using an array of a fixed size, MAX_QUEUE_SIZE. What other pieces of data would you need (besides an array) to implement a queue in this way? One of the things we'll need to keep track of is the number of elements in the queue, i.e., not all the elements in the array may be holding elements of the queue at any given time. So far, the pieces of data we need for our array implementation of the queue are: an array a count Will these pieces be sufficient? Let's look at an example to find out...We'll start with a queue with 3 elements: queue (made up of 'contents' and 'count') ----------------- ----|a|b|c| | |3| ----------------- ----0 1 2 3 count contents where a is at the front and c is at the rear. Now, we enter a new element with: Enter(queue, 'd')... queue (made up of 'contents' and 'count') ----------------- ----- Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) |a|b|c|d| |4| ----------------- ----0 1 2 3 count contents All seems well. How about if we remove an element with: ch = Delete(queue)?... queue (made up of 'contents' and 'count') ----------------- ----- ----| |b|c|d| |3| |a| ----------------- ----- ----0 1 2 3 count ch contents Hmmm, we have a problem because the front of the queue is no longer at array position 0. One solution would be to move all the elements down one, giving: queue (made up of 'contents' and 'count') ----------------- ----|b|c|d| | |3| ----------------- ----0 1 2 3 count contents We reject that solution though because it is too expensive to move everything down every time we remove an element. Instead, can we use an additional piece of information to keep track of the front? Answer: Yes! We can use the index of the element at the front, giving: queue (made up of 'contents', 'front' and 'count') ----------------- ----- ----| |b|c|d| |1| |3| ----------------- ----- ----0 1 2 3 front count Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT Advanced C Programming (for First year B.Tech students) contents Now, what if we enter another element with: Enter(queue, 'e')? Currently, the rear of the queue holds 'd' and is at the end of the array. Where will we put 'e'? It is already said that moving everything down is too expensive. An alternative would be to use the array in a circular fashion. In other words, when we hit the end of the array, we wrap around and use the beginning. Now, with this choice for entering 'e', the fields look like: queue (made up of 'contents', 'front' and 'count') ----------------- ----- ----|e|b|c|d| |1| |4| ----------------- ----- ----0 1 2 3 front count contents Finally, how would it look like after: ch = Delete(queue)? queue (made up of 'contents', 'front' and 'count') ----------------- ----- ----- ----|e| |c|d| |2| |3| |b| ----------------- ----- ----- ----0 1 2 3 front count ch Contents. Implement the queue using arrays similar to that of stacks. If you are interested, try to do a case study on types of queues. Try to implement the list, stack and queues in alternate ways also. ------------------------------------------------------------------------------------------------------------ ALL THE BEST ------------------------------------------------------------------------------------------------------------ Prepared by: S. Vairamuthu & Mrs. B. Selva Rani, School of Computing Sciences, VIT