...

Advanced C Programming (for First year B.Tech students) Pointers

by user

on
Category: Documents
11

views

Report

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
Fly UP