7. User-Defined Data Types – C Programming Essentials

Chapter 7

User-Defined Data Types

In this chapter, we discuss several new data types that are available in C. These data types are convenient to handle and simplify the programming task.

7.1 | STRUCTURES

Consider that we want to write a program where we need to keep a group of dissimilar data items together. The techniques discussed so far are inapplicable in this regard. Arrays cannot achieve the purpose since an array requires all the items stored in it to be of the same data type. In this situation, we would use C structures to keep the group of dissimilar data items under a single composite variable. Thus, in contrast to arrays, a structure is used to represent a collection of data items, possibly of different data types, by a single name. So, by means of a structure, a group of logically connected items of different data types can be handled in a convenient manner. It organizes the data items so that they may be referenced either separately, when necessary, or as a single unit (the structure variable).

7.2 | DECLARING A STRUCTURE

An example will clarify how we create a structure for use in our program. Suppose that we have a collection of books and we want to create a data item that contains information about a book, such as:

  • the title of the book
  • publisher of the book
  • author of the book
  • number of pages in the book and
  • the cost of the book

We can achieve the task by declaring a structure as:

struct books{
	char title[20];
	char publisher[25];
	char author[20];
	int no_of_pages;
	float cost;
};

A structure declaration has three basic parts. They are:

  • data type specifier
  • structure tag
  • structure members

The keyword struct conveys that the type of data is a structure. In other words, it informs the compiler that the declaration is that of a structure. The identifier for the structure is placed right after the struct keyword and it gives the name of the structure. The name of the structure is also referred to as the structure tag. In the above example, the structure tag is books. Lastly, the books structure consists of five different variables: three character arrays (for storing book title, publisher name, and author name), an integer variable (for storing the number of pages), and a float variable (for the cost of the book). Each of these variables is called a structure member. Note that there may be one or more members in a structure.

Furthermore, it is important to note that the above declaration is the declaration for the structure itself. That is, the declaration creates a new data type, books, which consists of the fields as specified by the member variables. The declaration does not create any variable that can be used in our program. It simply creates a new data type for use. This new data type can be used similar to the built-in data types we have already dis cussed. Here lies the wonder of C. User-defined data types can be accessed in the same manner as built-in data types.

So far, we have declared a structure named books. This structure declaration creates a template (not to be confused with C++ templates) that describes the contents of the structure, but not a variable. A data type should, by definition, contain definitions for its name, its storage, and operations that can be performed on the data. Here, books becomes a user-defined data type, since we have specified its name and storage (the collective storage of its member variables). The operations to be performed on books are a collection of the operations that can be performed on its constituent data members as well as all other data type-related general operations that can be performed on the structure variable once we create it.

It is worthwhile to note that a structure declaration is not a definition; i.e., it does not allocate storage. Stor age is allocated in accordance with the structure declaration once we create structure variables or objects. We shall make use of the structure tag at the time of defining a structure variable.

The general form of a structure declaration is given below:

       struct structure_tag{
           Member variable declarations;
       };

The structure tag identifier can be named using the conventions for naming identifiers as discussed in Chapter 2. Another important point to note is that a structure declaration must contain a terminating semicolon after the closing brace of the member variable declarations.

7.3 | DEFINING A STRUCTURE VARIABLE

From the discussion so far, it is obvious that structure declarations are of no use without defining a structure object (variable). We shall be using the terms structure object and structure variable synonymously. The general form of defining a structure is:

       struct structure_tag <structure variables list>;

Note that the definition is similar to a variable definition of a built-in data type. The only notable difference is that the entire definition is preceded by the struct keyword. This is to inform the compiler that the structure tag (identifier) that follows the struct keyword is what it is intended to mean, and not the name of a built-in data type.

If we want to define a variable with the identifier book1 of the structure type books, it may be defined as shown below.

       struct books book1;

Multiple structure objects can also be defined as a list of structure objects separated by commas. The following definition would create three structure objects, b1, b2, and b3 of structure type books.

       struct books b1,b2,b3;

The keyword struct indicates that the variable defined is a structure; it is followed by the structure tag (in this case, books) and a list of books objects separated by commas. This definition causes the C compiler to follow three steps listed below:

  • Move to the structure tag list
  • Find the structure tag – books
  • Allocate storage for as many structure objects of type books as present in the list with the identifiers for the objects as specified in the list. The storage is allocated in accordance with the structure declaration, which specifies the storage requirements for the structure

A structure variable definition may also be combined with the structure declaration as an extended syntax for structure declaration. The syntax for the corresponding statement is shown below as a more generalized form of a structure declaration statement combined with optional structure object definitions. This optional part is present as the addition of the structure object names to be defined. The objects are mentioned as a list after the closing brace of our original structure declaration and before the terminating semicolon.

       struct structure_tag{
            Member variable declarations;
       }<structure variables list>;

For our example structure, the statement would be written as follows:

       struct books{
            char title[20];
            char publisher[25];
            char author[20];
            int no_of_pages;
            float cost;
       }b1,b2,b3;

There is also another, more restricted, form of a structure declaration. It consists of an absence of the structure tag. One such example is shown below:

       struct {
            char title[20];
            char publisher[25];
            char author[20];
            int no_of_pages;
            float cost;
       }b1,b2,b3;

It might seem strange that we can leave out the structure tag from the structure declaration. It is clear from the context that the structure is without a name and we cannot reference the structure further in our program. Such a form of a structure is used when we have a predefined set of structure objects of their structure type and there is no need to reference the structure further in the program, except through the objects that we define along with the structure declaration. However, this form is rarely used in practice and structure tags are not absent.

Thus, we can rewrite our syntax for the structure declaration more generally as:

       struct <structure_tag>{
           Member variable declarations;
       }<structure variables list>;

where the structure tag and the structure variables list are optional components of the declaration.

7.4 | INITIALIZING AND REFERENCING STRUCTURE MEMBERS

So far, we have discussed how we can declare structures and define structure objects. We now move on to the techniques of initializing and referencing structure objects. A structure object can be directly initialized at its definition — similar to the techniques for initialization of variables of built-in data types as we have seen in the previous chapters. For example, if we want to initialize the book1 object at the time of its definition, we may do so by writing:

       struct books book1 = {
            “C Programming”,
            “Pearson Education”,
            “Kernighan”,
            272,
            79.00
       };

The initialization implies that for the structure variable book1, the value for the

  • title member is “C Programming”
  • publisher member is “Pearson Education”
  • author member is “Kernighan”
  • no_of_pages member is 272
  • cost member is 79.00

The initialization as mentioned can be performed along with object definitions in the structure declaration statement. The general form of initialization of structure objects is left to the reader as an exercise. We shall encounter more examples of initialization as we proceed.

Moving on to the issue of referencing structure members, let us again bring our example structure, books, into consideration. Let us assume we want to print or change the cost member of the object boo1l. In such a situation, we would require access to the cost field of the object book1. A reference to a member of a structure in C is achieved by using the dot operator. Thus, any member of a structure object can be accessed by writing:

       structure_object.member_variable

For the access to the cost member variable of book1, we write:

       bookl.cost

Thus, to change the value of cost of book1, we would write:

       book1.cost=97.50;

The member reference of a structure object can be used in any valid C expression, as in the case with variables of the built-in data types. Note the dot (.) operator that separates the structure variable name and the structure member name. The above reference instructs the compiler to retrieve the book1 object, locate its member variable named cost, and assign the value 97.50 into that member of the book1 structure object.

We now present out first complete working C program that uses structures. The program is self-explanatory and summarizes the techniques that we have discussed so far.

 


Program Ch07n01.c

/*Program to create a structure and access it*/

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

struct books{
       char title[20];
       char publisher[25];
       char author[20];
       int no_of_pages;
       float cost;
};
int main()
{
  struct books book1;

  strcpy(book1.title, “C Programming”);
  strcpy(book1.publisher, “Pearson Education”);
  strcpy(book1.author, “Kernighan”);
  book1.no_of_pages=281;
  book1.cost=72.00;

  printf(“Book Title: %s\n”,book1.title);
  printf(“Book Publisher: %s\n”,book1.publisher);
  printf(“Book Author: %s\n”,book1.author);
  printf(“No. of Pages: %d\n”,book1.no_of_pages);
  printf(“Cost: %f\n”,book1.cost);

  return 0;
}
End Program
7.5 | NESTING OF STRUCTURES

Structures in C may be nested. To substantiate the requirement for the nesting of structures, we reconsider our example of the books structure. Let us assume that we want to store some additional information, such as date of purchase and date of publication for each book. To include this information, we need to modify our structure declaration for books. The requirement may be met by adding six additional member variables: three integers for the day, month, and year of purchase, and three more for the day, month, and year of publication. This modification results in a rather cumbersome structure declaration as shown below:

       struct books{
              char title[20];
              char publisher[25];
              char author[20];
              int no_of_pages;
              float cost;
              int day_of_purchase; 
              int month_of_purchase;
              int year_of_purchase;
              int day_of_publication;
              int month_of_publication;
              int year_of_publication;
       };

In addition, the above structure declaration does not reflect the fact that the day, month, and year of pub lication are related to each other. It also fails to convey the collective purpose of day, month, and year as a com posite field (the date). Nesting of structures involves structure object declarations within structure declarations as member variables. The more elegant solution to the new specification can be declared as follows, without the problems introduced by the more cumbersome solution of adding six more integer member variables.

       struct date{
            int day;
            int month;
            int year;
       };
            struct books{
            char title[20];
            char publisher[25];
            char author[20];
            int no_of_pages;
            float cost;
            struct date purchase_date;
            struct date publication_date;
       };

Note that the purchase_date member of the structure object of type books would itself be a structure object, as evident from the declaration of the books structure. There is theoretically no limit on nesting, unless disallowed by other constraints, such as availability of memory, etc.

In addition, a structure declaration can also be nested within another structure declaration. Unnamed structures (structures without structure tags) are allowed in this case, but with the restriction that every structure nested in another structure must contain at least one object declaration along with its own declaration. We can rewrite the above example using nested declarations as:

       struct books{
            char title[20];
            char publisher[25];
            char author[20];
            int no_of_pages;
            float cost;>
            struct date{
                int day;
                int month;
                int year;
            }purchase_date, publication_date;
       };

An important point to note about the nested declaration of structures is that nesting of a structure declara tion does not confine access to the nested structure tag (in our case, date) to only from within the enclosing structure (in our case, books). Thus, the two preceding declaration segments are equivalent and we can write the following without error:

       struct books{
            char title[20];
            char publisher[25];
            char author[20];
            int no_of_pages;
            float cost;>
            struct date{
                int day;
                int month;
                int year;
            }purchase_date, publication_date;
       };
       struct date d1;

Nesting of structures is an important feature of C, and it enhances the readability and elegance of a program that requires its usage. Nesting of structures allows complex composite data to be represented in a simple and powerful format.

7.6 | OPERATIONS ON STRUCTURES

C supports only a few operations on structures. One such operation is the ability to assign one structure variable to another, provided they are of the same structure type. So, with two structure objects, b1 and b2 of type books, and on the basis that the object b1 has already been initialized, we may write:

       b2=b1;

The statement copies the book information stored in the structure object b1 to the structure object b2.

Besides, we may pass a structure object to a function, and a structure object can also be returned from a function. Passing structure objects as parameters shall not be discussed until later in the chapter. We can also create pointers to structure objects and operate on them similar to the operations on variables of built-in data types. Arrays of structures can be created and accessed much in the same manner as arrays of variables of built-in data types. However, there is not much more that we can do with structures. An example of the restriction on the usage of structures is that we cannot compare structure variables for equality or use any of the relational operators on structure variables, that we have discussed in Chapter 2. Thus, the following code is erroneous and generates compilation errors:

      …
       struct test{
	    int a;
            float b;
       }p,q;

       int main{
            p.a=50;
            p.b=22;
            q.a=50;
            q.b=22;
            if(p==q){
	        printf(“Equal.\n”);
            }
            …
            return 0;
       }
       …
7.7 | POINTERS TO STRUCTURES

A pointer to a structure identifies (stores) the address of the structure in memory. It may be used to access the address and the contents of the structure object in the same manner as applicable for pointers to variables of other data types. For example, the definition:

       struct books book1, *pb;

defines a variable book1 of the structure type books and pb as a pointer to an object of type books. We can safely write an assignment statement, as in the other cases with pointers, as:

       pb=&book1;

The statement assigns the address of the structure variable book1 to the pointer variable pb. After this assign ment, the pointer variable pb can be used to access the members of the object book1 indirectly. For example, to access the cost member through the pointer variable pb, we write:

       (*pb).cost

Similarly, (*pb).no_of_pages gives access to the no_of_pages member of book1 indirectly through pb, and so on. Notice that while writing expressions such as the above ones, we should be careful where we put the parentheses. Here, the parentheses are necessary because the precedence of the structure member operator (the dot) is higher than the unary * (value at address) operator. Thus, if we had written:

       *pb.cost

it would have been interpreted by the compiler wrongly as:

       *(pb.cost)

The above interpretation results in an illegal statement since pb.cost is not a pointer and pb does not contain the member cost.

There is an alternative and equivalent notation provided to avoid the rather cumbersome usage of parentheses, which has to be used in conjunction with the dot operator while accessing structure member variables through pointers to structure objects. The alternative notation is provided as a direct mechanism of accessing member variables through pointers to structures. This form of shorthand is used more frequently than the alternative we presented above. The access, in our case, using the pointer to member operator can be re-written as:

       pb->cost

The -> is called the arrow operator or the pointer to member operator. The general form of using this operator in expressions is:

       pointer_to_structure_object -> structure_member

The following program demonstrates accessing the member variables of a structure object using pointers to structures. It has the same effect as the previous program and is self-explanatory, too. It summarizes the points we discussed about pointers to structures.

 


Program Ch07n02.c

/*Program to create a structure and access it using pointers to structures*/

#include <stdio.h>
#include <string.h>
   struct books{
	char title[20];
	char publisher[25];
	char author[20];
	int no_of_pages;
	float cost;
};
int main()
{
	struct books book1,*pb=&book1;

	strcpy(pb->title,“C Programming”);
	strcpy((*pb).publisher,“Pearson Education”);
	strcpy(pb->author,“Kernighan”);
	pb->no_of_pages=281;
	pb->cost=72.00;

	printf(“Book Title: %s\n”,pb->title);
	printf(“Book Publisher: %s\n”,pb->publisher);
	printf(“Book Author: %s\n”,pb->author);
	printf(“No. of Pages: %d\n”,pb->no_of_pages);
	printf(“Cost: %f\n”,pb->cost);

	return 0;
}
End Program
7.8 | STRUCTURES AND FUNCTIONS

Among the operations that can be performed on structures, we have already seen how to copy a structure as a unit to another structure object of the same structure type, as well as the techniques involved in assigning values to a structure object. As we mentioned earlier, a structure can be passed as a unit to a function and a function may return a structure as a whole. There are three different ways to pass a structure to a function, and are discussed below:

  1. Structure members may be separately and individually passed as arguments to a function. In this case, each such structure member is treated in the invoked function as a separate single variable without any composite semantic for the structure object as a whole. A structure member may also be returned from a function as a variable. This sort of argument passing to a function and returning values is similar to ordinary argument passing and returning to and from a function, which we have discussed earlier in the chapter on Functions. The following sample program demonstrates the usage of these techniques. The program is redundant in its tasks, but serves the purpose of demonstration of this technique that we have discussed:

     

    
    Program Ch07n03.c
    
    /*Program to illustrate structure member passing to and from functions*/
    
    #include <stdio.h>
    #include <string.h>
       struct books{
    	char title[20];
    	char publisher[25];
    	char author[20];
    	int no_of_pages;
    	float cost;
    };
    float modify_cost(float);
    
    int main()
    {
    	struct books book1,*pb=&book1;
    
    	strcpy(pb->title,“C Programming”);
    	strcpy((*pb).publisher,“Pearson Education”);
    	strcpy(pb->author,“Kernighan”);
    	pb->no_of_pages=281;
    	pb->cost=72.00;
    	printf(“Book Title: %s\n”,pb->title);
    	printf(“Book Publisher: %s\n”,pb->publisher);
    	printf(“Book Author: %s\n”,pb->author);
    	printf(“No. of Pages: %d\n”,pb->no_of_pages);
    	printf(“Cost: %f\n”,pb->cost);
    	pb->cost=modify_cost(pb->cost);
    	printf(“New Cost: %f\n”,pb->cost);
    	return 0;
    }
    
    float modify_cost(float cost)
    {
    	struct books temp;
    	temp.cost=++cost;
    	return temp.cost;
    }
    End Program
  2. For the second technique, a structure object may be passed as a unit to a function and may also be returned as a unit from a function. To illustrate this technique, let us consider the following example that initializes two points and sends these points to a function mid( ) to compute the middle point of the two given points. Each point is a structure having two floating-point members for its abscissa and ordinate (x and y coordinates). The function called mid( ) receives two such point structures as a unit and returns a structure of type point as a unit, too.

    The example in Ch07n04.c highlights passing of structure objects as a unit to and from functions through the function call:

    pm=mid(p1, p2);
    
    Program Ch07n04.c
    
    /*Program to create a structure and access it using pointers to structures*/
    
    #include <stdio.h>
       struct point{
    	float abscissa;
    	float ordinate;
    };
    struct point mid(struct point,struct point);
    
    int main()
    {
    	struct point p1,p2,pm;
    
    	printf(“Enter coordinates of two points:\n”);
    	printf(“\tFor the first point (x1,y1) : ”);
    	scanf(“%f,%f”,&p1.abscissa,&p1.ordinate);
    	fflush(stdin);
    	printf(“\tFor the second point (x2,y2) : ”);
    	scanf(“%f,%f”,&p2.abscissa,&p2.ordinate);
    	pm=mid(p1,p2);
    	printf(“The middle point is :
    		   (%f,%f).\n”,pm.abscissa,pm.ordinate);
    	return 0;
    }
    
    struct point mid(struct point a,struct point b)
    {
    	a.abscissa=(a.abscissa+b.abscissa)/2;
    	a.ordinate=(a.ordinate+b.ordinate)/2;
    	return a;
    }
    End Program

    The statement also indicates that the function mid( ) returns a structure as a whole, and the returned value is assigned to the structure object pm as a unit. Although the function mid( ) changes the contents of its first parameter, it does not affect the first point in main, since the arguments are passed by value.

  3. A pointer to a structure object may be passed as an argument to a function which gives access to the entire structure in the invoker function (through pointers) from within the invoked function. This is similar to accessing ordinary local variables in other functions through pointers in the invoked function. A structure pointer may also be returned by a function. Let us take up the task of writing a program similar to the one in the above example. The only change is that we choose to pass pointers to the point structures for the two points and return a pointer to the middle point from the mid( ) function. The program is presented in Ch07n05.c:

 


Program Ch07n05.c

/*Program to create a structure and access it using pointers to structures*/

#include <stdio.h>
   struct point{
	float abscissa;
	float ordinate;
};
struct point *mid(struct point *,struct point *);

int main()
{
	struct point p1,p2,pm;
	printf(“Enter coordinates of two points:\n”);
	printf(“\tFor the first point (x1,y1) : ”);
	scanf(“%f,%f”,&p1.abscissa,&p1.ordinate);
	fflush(stdin);
	printf(“\tFor the second point (x2,y2) : ”);
	scanf(“%f,%f”,&p2.abscissa,&p2.ordinate);
	pm=* mid(&p1,&p2);
	printf(“The middle point is :
		   (%f,%f).\n”,pm.abscissa,pm.ordinate);
	return 0;
}
struct point *mid(struct point *ptr1,struct point *ptr2)
{
	struct point *pt;
	pt->abscissa=(ptr1->abscissa + ptr2->abscissa)/2;
	pt->ordinate=(ptr1->ordinate + ptr2->ordinate)/2;
	return pt;
}
End Program

The above program is easy to trace. The function mid( ) now returns the address of a point structure and on return, the value at the address of the calculated midpoint is assigned to the point variable pm in the function main. We need to take a little care while writing a function that receives pointers as arguments. Any change made to the member variables of the objects using pointers will be reflected normally in the local variables in the caller function, whose addresses are being passed to the invoked function. Thus, in this case, we should not try to store the middle point within the first pointer argument. If we do so, we will be changing the contents of the first point in the main function.

At this point, we would like to make the readers alert about pointer usage again. The program given in ch07n05.c will not execute properly. A deliberate mistake is done in this program. Within the function mid() a local pointer variable pt is defined that is used to store the middle point, but this pointer variable pt is not assigned to any address. This sort of errors is very common and difficult to detect. To make the program work ing, one need to add the statement after defining the pointer pt, so that pt can point to a memory area that can store a structure of point type. Remember to include stdlib.h header at beginning, as we are using funtion malloc( ).

       pt>(struct point *) malloc (sizeof (struct point));
7.9 | ARRAYS OF STRUCTURES

An array of structures is defined just as in any other C array definition. The only difference is that in this case, every element of the array is a structure. Thus, an array of structures provides a mechanism of grouping similar composite data items together.

To demonstrate arrays of structures, let us consider the following specification for a program: “Consider that we have a set of pairs of integer values and we want to get the average of each of these pairs."

To achieve the task of writing a solution for the problem, we would need two integer arrays for holding the integer pairs and a float array for holding their averages. One possible implementation would be to use three parallel arrays, array first, array second, and array average, as:


       int first[20];
       int second[20];
       float average[20];

Usage of these parallel arrays can be achieved in another more elegant fashion —by an array of structures. As we know from the problem specification, each pair of integer values is associated with its average value. Thus, we may declare a structure as:


       struct pair{
             int first;
             int second;
             float average;
       };

Then we define an array of structures of type pair as:

struct pair triplet[20];

The statement will define an array of 20 number of elements, each of which is a structure of type pair, called triplet. The program to achieve this task by using this representation is presented next. It reads 20 pairs of integers from the standard input device and stores them within the members first and second of the corresponding array element of triplet. The average member of each of the array elements is computed by calling a function, findavg( ), which receives the arguments — the structure array (triplet) and the array size (in our case, 20).

Notice that as the structure array triplet is passed as an argument to findavg( ), the function findavg( ) actually receives the address of the first element of the structure array (&triplet[0]). This is the same as with other arrays, where the array passed to a function by name actually passes the address of the first element of the array. So, it is possible to modify the triplet elements from within findavg( ), and these changes will be reflected in the invoking function.

 


Program Ch07n06.c

/*Program to demonstrate arrays of structures*/

#include <stdio.h>
# define limit 20
struct pair{
	int first;
	int second;
	float average;
};

void findavg(struct pair *,int);

int main()
{
	struct pair triplet[limit];
	int i;

	printf(“Enter the integer pairs (a,b):\n”);
	for(i=0;i<limit;i++)
		scanf(“%d,%d”,&triplet[i].first,&triplet[i].second);

	findavg(triplet,limit);

	for(i=0;i<limit;i++)
		printf(“%d,%d - %f\n”,(triplet+i)->first,
			   (triplet+i)->second,(triplet+i)->average);
	return 0;
}

void findavg(struct pair *ptr,int lim)
{
	while(--lim>=0)
	{
		ptr->average=(float)(ptr->first + ptr->second)/2;
		++ptr;
	}
}
End Program

Note the usage of the arrow operator in the printf statement of the above program. The expression:

       (triplet+i)->first

is the same as writing:

       (*(triplet+i)).first

It is also the same as the following expression:

       triplet[i].first
7.10 | SELF-REFERENTIAL STRUCTURES

In this section, we discuss how to use structures and pointers to build more sophisticated data structures like linked lists and trees. Though it is beyond the scope of this book to go into the details for using such data structures, we attempt to introduce the mechanisms involved in it. Continuing from where we left off on the nesting of structures, we now explore structures that have members that are pointers to structures of their own type. For example, the structure declaration:

       struct node{
          int info;
          struct node *link;
       };

is a valid structure declaration, since the structure pointer variable link is a pointer to a struct node and is not itself of type struct node. However, self-reference using structure objects is not permitted.

       //the following declaration is erroneous

       struct node{
          int info;
          struct node a;
       };

At this point, a brief discussion on linked lists is necessary. A list is a finite sequence of zero or more elements of a given type. The number of elements, that is, the length of a list is finite, but not known beforehand. For this reason, it is not desirable to keep the elements in an array, though dynamic arrays can be achieved using the dynamic allocation functions as discussed in thechapter on Arrays. Another disadvantage of using arrays for this purpose arises when we need to insert or delete elements in the middle of the list, since we need to shift the elements of the array to perform the operation. Shifting of elements takes time for long lists and is considered a disadvantage of representing lists by using arrays. The efficiency of arrays in representing lists is solely from the rapid search that can be performed by the technique of binary search, as already discussed.

In such situations, we may work with a singly linked list. A singly linked list is made up of nodes, each of which contains an element of the list and a pointer to the next node of the list. The representation requires self-referential structures and is evident from the problem statement. The declaration for node that we assumed earlier will serve the purpose. The structure node declares a structure whose first member (info) is of integral type and this will be used to store the elements of the list. The other member of node (link) is a pointer to another node structure.

The advantage of such linked structures is that the list elements may be inserted and deleted simply by adjusting the pointers and without the requirement of shifting of elements, as in the case with arrays. The storage for a new element may be allocated dynamically for an insertion and freed when an element is to be deleted from the list. To give a brief idea about the use of such structures, we construct a small linked list that has three entries. We define three variables as:

       struct node n1, n2, n3;

Now, to make a link between n1 and n2, and then between n2 and n3, we set the link member of n1 to the address of the object n2. Thus, n1 will be pointing to n2. The link creation between n2 and n3 is similarly done. We summarize the tasks by the following two statements:

       n1.link=n2;
       n2.link=n3;

To terminate the linked list, we set the null pointer (a simple pointer with value 0) to the link member of the last node in the list (in our case, n3). The following preprocessor definition has already been included in the header file stdio.h:

       #define NULL 0

which defines the constant NULL to be of value 0. Thus, to terminate our three-member linked list, we may write the following where the symbolic constant NULL has been typecast to be of type “pointer to struct node”.

       n3.link=(struct node *) NULL;

The reader is expected to read any standard book on data structures to learn more about linked lists and their implementation.

7.11 | UNIONS

A union in C is a small chunk of memory that can hold different types of data items. The distinct advantage of using a union is that it allows us to store different-sized data items in the same memory space. When a union is defined, it is allocated enough storage space to hold the largest-sized data item in the list of union members.

Effectively, a union is a structure where all member variables have an offset of zero from the base address of the union. The alignment is made appropriate to the types in the union.

The syntax for defining and using a union in C is similar to that for structures, and is shown below.

       union <union_tag>{
             Member variable declarations;
       }<union variables list>;

Here, the union tag and the union variables list are optional components of the declaration.

The following statement declares an example union with the union tag item.

       union item{
             char cvalue;
             int ivalue;
             float fvalue;
             double dvalue;
       }value;

The above definition declares a union named item and a variable, value, of the union data type. This variable holds sufficient storage to hold the largest member in the union definition. That is, the variable value is allocated the storage equivalent to that of a double, since among all union member variables, the double member dvalue requires maximum storage. If a union declaration is made without defining the object variables, the declaration serves as a reference to what lies in the context of the union, but no variables of the type exist, i.e., no storage is allocated. Variable definitions, when made, allocate storage for the union.

The union tag is optional as in structures, and it may be left out if the programmer does not want to create objects of the union in future. If the tag is present, the union objects can be defined later on as:

       union union_tag <union variables list>;

To illustrate the use of a union, we write a small program as our next example. The program declares a union of type item and then defines a union object value of type item. It assigns some values to the member variables and then prints them together with their memory addresses. On execution of the program, the reader may find that the memory addresses are identical for all the member variables. This is because the data is stored and taken from the same memory location. But it is to be noted that the number of bytes retrieved from that memory loca tion is different in different cases, and is determined by the union member referenced.

 


Program Ch07n07.c

/*Program to demonstrate usage of unions*/

#include <stdio.h>
   union item {
	char cvalue;
	int ivalue;
	float fvalue;
	double dvalue;
};
int main()
{
	union item value;

	value.cvalue='C';
	printf(“cvalue=%c, address=%u\n”,value.cvalue,&value.cvalue);

	value.ivalue=58;
	printf(“ivalue=%d, address=%u\n”,value.ivalue,&value.ivalue);

	value.fvalue=79.0;
	printf(“fvalue=%f, address=%u\n”,value.fvalue,&value.fvalue);

	value.dvalue=3849.273;
	printf(“dvalue=%g, address=%u\n”,value.dvalue,&value.dvalue);

	printf(“\nNew Values…\n”);

	printf(“cvalue=%c, address=%u\n”,value.cvalue,&value.cvalue);
	printf(“ivalue=%d, address=%u\n”,value.ivalue,&value.ivalue);
	printf(“fvalue=%f, address=%u\n”,value.fvalue,&value.fvalue);
	printf(“dvalue=%g, address=%u\n”,value.dvalue,&value.dvalue);

	return 0;
}
End Program

It is left to the reader to execute the above program, and analyse and explain the output. As a parting note on unions, since a change made to one union variable is likely to be reflected in the others as well, it is the responsibility of the programmer to keep track of the union contents.

7.12 | ENUMERATED DATA TYPES

The basic purpose of enumerated data types is to provide an alternative way to define integer constants within a program. Just like a structure or a union declaration, an enumeration is declared as:

       enum enum_tag <member list>;

An enumerated variable may be defined as:

       enum_tag <list of variables>;

The members that appear within the enumerator declaration are automatically assigned equivalent integers starting from the integer 0. We consider the enumerator declaration:

       enum mar_status {single, married, divorced, widow};

Here, the enumeration constants and their corresponding assigned values are listed in the following table:

Enumeration Constants Assigned Integer Values
single
0
married
1
divorced
2
widow
3

An enumeration declaration may also be of the following form:

       enum day_of_week{mon=1, tue, wed, thu, fri, sat, sun>;

In this case, the assigned values to the enumeration constants will start from 1 instead of 0, since the value for the first enumeration constant, mon, has been specified as 1. Moreover, the values for the enumeration constants may be directly specified at the time of declaring the enumerator. The following table lists the enumerated values for the above statement:

Enumeration Constants Assigned Integer Values
mon
1
tue
2
wed
3
thu
4
fri
5
sat
6
sun
7

Consider the following enumerator declaration:

       enum sex{male, female>;

and the variable declarations like

       sex sex_of_person, sex_of_spouse;

A program may then contain an assignment like:

       sex_of_spouse=(enumsex)(++sex_of_person%2);

The above statement reflects that the values assigned to the enumeration constants may easily take part in forming a C expression. Note that in the assignment statement we did not directly assign the integer value to the sex_of_spouse variable; instead, we converted that integer to (enumsex) type by explicit casting and then performed the assignment.

7.13 | TYPEDEF

In C, the typedef is used to create new data-type names. Take for example the declaration:

typedef int Integer;

In the above declaration, the name Integer is created as a synonym for int. In fact, it allows us to make a shorthand name for complex data declarations. Say, for example, we need to define three integer arrays each having 10 elements. We may do so by the following block of declarations:

       //Establishing typedef
       typedef int arrays[10];

       //defining variables
       arrays first, second, third;

The above declarations would be equivalent to the more conventional method of the declaration:

       int first[10], second[10], third[10];

A typedef is normally used when the attribute list for a data type is fairly long and complex. A typedef has three specific advantages.

  • It provides better documentation for a program. For example:
           typedef char* STRING;
           …
           …
           STRING message, reply;

    Here, the use of the typedef clearly states the purpose of message and reply in the program.

  • It allows consolidating complex data types into a single name, thereby helping minimize effort and probable typing errors that arise from long definitions used several times in a program.
  • It helps in parameterizing a program against possible portability problems. If the attribute list needs to be changed while porting the program from one machine to the other, a single change in the typedef sets would change the implementation of every use for the corresponding typedef in the program.

The following program is a rework of the first program ch07n01.c in the chapter and uses typedef to illustrate creation of new data types.

 


Program Ch07n08.c

/*Program to illustrate typedef*/

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

   typedef struct {
	char title[20];
	char publisher[25];
	char author[20];
	int no_of_pages;
	float cost;
} books;

int main()
{
	books book1;
	strcpy(book1.title,“C Programming”);
	strcpy(book1.publisher,“Pearson Education”);
	strcpy(book1.author,“Kernighan”);
	book1.no_of_pages=281;
	book1.cost=72.00;
	printf(“Book Title: %s\n”,book1.title);
	printf(“Book Publisher: %s\n”,book1.publisher);
	printf(“Book Author: %s\n”,book1.author);
	printf(“No. of Pages: %d\n”,book1.no_of_pages);
	printf(“Cost: %f\n”,book1.cost);
	return 0;
}
End Program