0% found this document useful (0 votes)
4 views15 pages

Lab #11

The document outlines the objectives and key concepts of a Computer Programming lab at Birla Institute of Technology & Science, focusing on structures, unions, enumerators, linked lists, and file handling. It provides detailed explanations and examples of defining and using structures and unions in C, as well as an introduction to linked lists, including their advantages and disadvantages over arrays. Additionally, it includes code snippets demonstrating linked list creation, traversal, insertion, and deletion operations.

Uploaded by

madhavdalvi06
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views15 pages

Lab #11

The document outlines the objectives and key concepts of a Computer Programming lab at Birla Institute of Technology & Science, focusing on structures, unions, enumerators, linked lists, and file handling. It provides detailed explanations and examples of defining and using structures and unions in C, as well as an introduction to linked lists, including their advantages and disadvantages over arrays. Additionally, it includes code snippets demonstrating linked list creation, traversal, insertion, and deletion operations.

Uploaded by

madhavdalvi06
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 15

Birla Institute of Technology & Science, Pilani

Second Semester 2024-2025, Computer Programming [CS F111]


Lab #11

Week #12-13 [14th April, 2025 To 26th April, 2025]

Objectives

1. Structures
2. Unions
3. Enumerators
4. Linked List and its operations
5. File Handling

11.0 Structure, Union, Enumeration


11.1 Structures
A structure allows you to group one or more variables that may be of different data
types into one. It can contain any valid data type like int, char, float, array, pointers
and even structures. A variable in the structure is called a structure member.
Structures help us build compact user defined data types.

11.1.1 Declaring a Structure


struct keyword is used to define a structure as follows,
struct struct_name {
structure member ;
} ;
The following example defines a person structure which contains a persons first name, last name,
and
age.
struct struct_name {
char first[20];
char last [20];
int age;
} ;

Instead of defining a structure we can define independent variables for first name, last name, and
age. However creating and managing variables to manipulate the information of 100 or 1000 uses
is both confusing and tedious.
Note that the above code only defines a structure person. However, it does not create
any variable of type person. We can declare a structure variable as follows:

Declare structure variables together with the structure definition.


struct struct_name {
char first[20];
char last [20];
int age;
} person1, person2, person3;
typedef structure can be used to omit the keyword struct while declaring a structure variable.
typedef struct struct_name {
char first[20];
char last [20];
int age;
} person;
. . .
person p1 , p2 ;
11.1.2 Accessing Member Variables
Dot operator ‘.’ between the structure name and member to access a structure
member.
person p1 ;
scanf(”%s%s”,&p1.fname,&p1.lname);
printf( ”%s %s”, p1.fname, p1.lname) ;
11.1.3 Array and Structure
We can create an array of structure data types as follows:
person p1[10];
strcpy(p1[0].first, ”Shyam”);
strcpy(p1[0].last, ”Rao”) ;

11.1.4 Pointers and Structures


The address of a structure variable can be stored in a pointer variable, just like the
address of any other type of variable. There are two ways to access the member
variables while using pointers on structure variables:
person ∗p1 ;
p1=(person∗) malloc(sizeof(person));
(∗p1).age = 21 ;
p1−>age =22;
‘− >’ operator is the most preferred way to access the member variables.

11.2 Union
Like structure, union is a user defined data type. In union, all members share the same
memory location (i.e. Unions only allocate enough space to store the largest member
variable, and all fields are stored at the same space.). Thus, only one variable can be
used at any instant of time. For example in the following C program, both x and y share
the same location. If we change x, we can see the changes being reflected in y.
#include <stdio.h>
typedef union test { int x , y ; } T;
int main ()
{
T t ;
t.x = 2 ;
printf(”After making x=2: \n x=%, y=%d\n\n”, t.x, t.y);
t.y = 10 ;
printf(”After making Y=10 : \ n x=%d, y=%d\n\n”, t.x, t.y) ;
return 0 ;
}
Unions are useful when the type of data being passed through functions is unknown,
using a union which contains all possible data types can remedy this problem.
The usage of unions are similar to that of structures. Hence, all the things discussed
above for structures are valid for unions too.
11.3 Enum
Enumeration (or enum) is a user defined data type in C. It is mainly used to assign names
to integral constants, the names make a program easy to read and maintain.

11.3.1 Declaring an Enumeration


There are two different types of enumerations
declarations: Named type:
enum state {START, STOP, PAUSE};

Unnamed type:
enum {ZERO, ONE};

Values may be assigned to specific enum value names. Any names without assigned values will
get one higher than the previous entry. If the first name does not have an assigned value, it
gets the value of zero. It is even legal to assign the same value to more than one name.

enum Errors {

NONE=0, // Redundant. The first one would be zero anyway

MINOR1=100, MINOR2, MINOR3, // 100 , 101 , and 102

MAJOR1=1000 , MAJOR2, DIVIDE BY ZERO=1000} ; // 1000 , 1001 , and 1000 again

11.4 INTRODUCTION TO LINKED LIST


Like arrays, Linked List is a linear data structure. Unlike arrays, linked list elements are not stored at
contiguous location; the elements are linked using pointers.

11.4.1 Why Linked List?


Arrays can be used to store linear data of similar types, but arrays have following limitations.
1) The size of the arrays is fixed: So we must know the upper limit on the number of elements in
advance. Also, generally, the allocated memory is equal to the upper limit irrespective of the usage.
2) Inserting a new element in an array of elements is expensive, because room has to be created for the
new elements and to create room existing elements have to shifted. For example, in a system if we
maintain a sorted list of IDs in an array id[].
id[] = [1000, 1010, 1050, 2000, 2040].
And if we want to insert a new ID 1005, then to maintain the sorted order, we have to move all the
elements after 1000 (excluding 1000). Deletion is also expensive with arrays until unless some special
techniques are used. For example, to delete 1010 in id[], everything after 1010 has to be moved.

11.4.2 Advantages over arrays


Dynamic size, Ease of insertion/deletion
Drawbacks:
1. Random access is not allowed. We have to access elements sequentially starting from the first
node. So we cannot do binary search with linked lists.
2. Extra memory space for a pointer is required with each element of the list.

11.4.3 Representation in C
A linked list is represented by a pointer to the first node of the linked list. The first node is called head. If
the linked list is empty, then value of head is NULL. Each node in a list consists of at least two parts:
(a) data
(b) pointer to the next node

In C, we can represent a node using structures. Below is an example of a linked list node with an integer
data.
struct node
{
int data;
struct node *next;
};

11.4.4 First Simple Linked List in C


Let us create a simple linked list with 3 nodes.

#include<stdio.h>
#include<stdlib.h>

struct node
{
int data;
struct node *next;
};

// Program to create a simple linked list with 3 nodes


int main()
{
struct node* head = NULL;
struct node* second = NULL;
struct node* third = NULL;

// allocate 3 nodes in the heap


head = (struct node*)malloc(sizeof(struct node));
second = (struct node*)malloc(sizeof(struct node));
third = (struct node*)malloc(sizeof(struct node));

/* Three blocks have been allocated dynamically.


We have pointers to these three blocks as first, second and third
head second third
| | |
| | |
+---+-----+ +----+----+ +----+----+
| # | # | | # | # | | # | # |
+---+-----+ +----+----+ +----+----+

# represents any random value.


Data is random because we haven’t assigned anything yet */

head->data = 1; //assign data in first node


head->next = second; // Link first node with the second node

/* data has been assigned to data part of first block (block


pointed by head). And next pointer of first block points to
second. So they both are linked.

head second third


| | |
| | |
+---+---+ +----+----+ +-----+----+
| 1 | o----->| # | # | | # | # |
+---+---+ +----+----+ +-----+----+
*/

second->data = 2; //assign data to second node


second->next = third;

/* data has been assigned to data part of second block (block pointed by
second). And next pointer of the second block points to third block.
So all three blocks are linked.

head second third


| | |
| | |
+---+---+ +---+---+ +----+----+
| 1 | o----->| 2 | o-----> | # | # |
+---+---+ +---+---+ +----+----+ */

third->data = 3; //assign data to third node


third->next = NULL;

/* data has been assigned to data part of third block (block pointed
by third). And next pointer of the third block is made NULL to indicate
that the linked list is terminated here.

We have the linked list ready.

head
|
|
+---+---+ +---+---+ +----+------+
| 1 | o----->| 2 | o-----> | 3 | NULL |
+---+---+ +---+---+ +----+------+

Note that only head is sufficient to represent the whole list. We can
traverse the complete list by following next pointers. */

return 0;
}

11.4.5 Linked List Traversal


In the previous program, we have created a simple linked list with three nodes. Let us traverse the
created list and print the data of each node. For traversal, let us write a general purpose function
printList() that prints any given list.

#include<stdio.h>
#include<stdlib.h>
struct node
{
int data;
struct node *next;
};
// This function prints contents of linked list starting from the given node
void printList(struct node *n)
{
while (n != NULL)
{
printf(" %d ", n->data);
n = n->next;
}
}
int main()
{
struct node* head = NULL;
struct node* second = NULL;
struct node* third = NULL;

// allocate 3 nodes in the heap


head = (struct node*)malloc(sizeof(struct node));
second = (struct node*)malloc(sizeof(struct node));
third = (struct node*)malloc(sizeof(struct node));

head->data = 1; //assign data in first node


head->next = second; // Link first node with the second node

second->data = 2; //assign data to second node


second->next = third;

third->data = 3; //assign data to third node


third->next = NULL;

printList(head);

return 0;
}

11.4.6 Insertion in a Linked List


A node can be added in three ways
(a) At the front of the linked list
(b) After a given node.
(c) At the end of the linked list.

(a) Add a node at the front (four step process)


The new node is always added before the head of the given Linked List. And newly added node becomes
the new head of the Linked List. For example if the given Linked List is 10->15->20->25 and we add an
item 5 at the front, then the Linked List becomes 5->10->15->20->25. Let us call the function that adds
at the front of the list is push(). The push() must receive a pointer to the head pointer, because push
must change the head pointer to point to the new node

/* Given a reference (pointer to pointer) to the head of a list and an int,


inserts a new node on the front of the list. */
void push(struct node** head_ref, int new_data)
{
/* 1. allocate node */
struct node* new_node = (struct node*) malloc(sizeof(struct node));

/* 2. put in the data */


new_node->data = new_data;

/* 3. Make next of new node as head */


new_node->next = (*head_ref);

/* 4. move the head to point to the new node */


(*head_ref) = new_node;
}

(b) Add a node after a given node (five step process)


We are given pointer to a node, and the new node is inserted after the given node.

/* Given a node prev_node, insert a new node after the given prev_node */
void insertAfter(struct node* prev_node, int new_data)
{
/*1. check if the given prev_node is NULL */
if (prev_node == NULL)
{
printf("the given previous node cannot be NULL");
return;
}

/* 2. allocate new node */


struct node* new_node =(struct node*) malloc(sizeof(struct node));

/* 3. put in the data */


new_node->data = new_data;

/* 4. Make next of new node as next of prev_node */


new_node->next = prev_node->next;

/* 5. move the next of prev_node as new_node */


prev_node->next = new_node;
}

(c) Add a node at the end (six step process)


The new node is always added after the last node of the given Linked List. For example if the given
Linked List is 5->10->15->20->25 and we add an item 30 at the end, then the Linked List becomes 5->10-
>15->20->25->30. Since a Linked List is typically represented by the head of it, we have to traverse the
list till end and then change the next of last node to new node.

/* Given a reference (pointer to pointer) to the head


of a list and an int, appends a new node at the end */
void append(struct node** head_ref, int new_data)
{
/* 1. allocate node */
struct node* new_node = (struct node*) malloc(sizeof(struct node));

struct node *last = *head_ref; /* used in step 5*/

/* 2. put in the data */


new_node->data = new_data;

/* 3. This new node is going to be the last node, so make next of it as


NULL*/
new_node->next = NULL;

/* 4. If the Linked List is empty, then make the new node as head */
if (*head_ref == NULL)
{
*head_ref = new_node;
return;
}

/* 5. Else traverse till the last node */


while (last->next != NULL)
last = last->next;

/* 6. Change the next of last node */


last->next = new_node;
return;
}

(d) Driver program to test above functions

int main()
{
/* Start with the empty list */
struct node* head = NULL;

// Insert 6. So linked list becomes 6->NULL


append(&head, 6);

// Insert 7 at the beginning. So linked list becomes 7->6->NULL


push(&head, 7);

// Insert 1 at the beginning. So linked list becomes 1->7->6->NULL


push(&head, 1);
// Insert 4 at the end. So linked list becomes 1->7->6->4->NULL
append(&head, 4);

// Insert 8, after 7. So linked list becomes 1->7->8->6->4->NULL


insertAfter(head->next, 8);

printf("\n Created Linked list is: ");


printList(head);

return 0;
}

11.4.7 Deletion algorithm is one of the basic algorithm which is required to maintain the list.
Basically the deletion algorithm adjusts the pointer of the relevant nodes to make sure that the list
remains in the correct order (consistent) after the node is removed.

Consider the diagrammatic representation of the linked list given below which holds a character value:

list (start pointer)


|
v
--------- --------- ---------
| a | --+---> | b | --+---> | c | --+---> NULL
--------- --------- ---------
info next info next info next

The diagram above shows the final form of the linked list. The task is to remove a node form the linked
list with a given value X (which can be a parameter to the function delete).

This function should be called as:

ListDelete (&list, X); // address of the list and value of the node must be
passed.

We'll consider our elements and nodes to have the following types:

typedef char elementT;

typedef struct nodeTag {


elementT element;
struct nodeTag *next;
} nodeT;

and thus, the pointer to the beginning of the list will be: nodeT *list;

Different cases:
There are a few steps to deleting a specific element from the list:
1. Find the node with the element (if it exists).
2. Remove that node.
3. Reconnect the linked list.
4. Update the link to the beginning (if necessary).
Finding the node in question is a matter of traversing the list and looking at each node's element.
Reconnecting the list once a node is to be removed is more interesting. Let's consider at least 3 cases:
• Removing a node from the beginning.
• Removing a node from the middle.
• Removing a node from the end.

a.Removing from the beginning

When removing the node at the beginning of the list, there is no relinking of nodes to be performed,
since the first node has no preceding node. For example, removing node with a:

list
|
v
--------- --------- ---------
| a | --+---> | b | --+---> | c | 0 |
--------- --------- ---------

However, we must fix the pointer to the beginning of the list:

list
|
+-------------+
|
v
--------- --------- ---------
| a | --+---> | b | --+---> | c | 0 |
--------- --------- ---------

b. Removing from the middle

Removing a node from the middle requires that the preceding node skips over the node being removed.
For example, removing the node with b:

list
|
v
--------- --------- ---------
| a | --+--+ | b | --+---> | c | 0 |
--------- | --------- ---------
| ^
+----------------+
This means that we need some way to refer to the node before the one we want to remove.

c. Removing from the end

Removing a node from the end requires that the preceding node becomes the new end of the list (i.e.,
points to nothing after it). For example, removing the node with c:

list
|
v
--------- --------- ---------
| a | --+---> | b | 0 | | c | 0 |
--------- --------- ---------

Note that the last two cases (middle and end) can be combined by saying that "the node preceding the
one to be removed must point where the one to be removed does."

In addition, since we need to fix the pointer to the beginning of the list, we'll need some way to get the
new beginning pointer out of the function. One way to do so (and the one we'll initially use) is to return
the pointer from the deletion function. This means we will have a deletion function as follows:

nodeT *ListDelete(nodeT *list, elementT value);

and use it as:

list = ListDelete(list, value);

Implementation:

Again, we'll need to be able to change the pointer to the list, i.e., list, in the case that the first node in
the list is removed. However, instead of passing back a new value for the beginning of the list via the
return mechanism, as in:

list = ListDelete(list, value);

we'll pass in the pointer to the beginning of the list by reference (as a pointer to a pointer), so that we
can change it in ListDelete() when necessary. Thus, the prototype for our function will be:

void ListDelete(nodeT **listP, elementT value);

Note: This time, we pass the address of the "list" variable to the function, so it makes sense to call the
parameter that receives that address "listP", since it is a pointer to a "list".

Now we will iterate (i.e., loop) through the list. It's easy to see how to start a pointer at the beginning
and move it from one node to the next:

--------- --------- ---------


| x | --+---> | y | --+---> | z | --+--->
--------- --------- ---------
^
|
currP

In other words:
• Start at the beginning:
• currP = *listP
Note that to access the address of the first node, we have to dereference listP with the star (*)
since listP is a "pointer to a pointer to the first node" (and we want to remove one level of
pointer-ness to get the "pointer to the first node").
• Advance to the next node when necessary:
• currP = currP->next
• Stop when there are no more nodes, i.e., make sure that:
• currP != NULL

It's easy to combine these into a for loop:

for (currP = *listP; currP != NULL; currP = currP->next)

However, when we find the one to remove, we'll also need a pointer to the previous node:
--------- --------- ---------
| x | --+---> | y | --+---> | z | --+--->
--------- --------- ---------
^ ^
| |
prevP currP

Thus, we also need to maintain a previous pointer at each step of the loop:

for (currP = *listP; currP != NULL; prevP = currP, currP = currP->next)

(Notice that there are 2 increments separated by a comma.)

To complete the function, we'll have to:


• Have some way to indicate when there is no previous node.
Remember that removing the first node is a special case because...
o It requires no relinking of nodes.
o We have to update the pointer to the beginning of the list. We don't have to return the
new pointer to the beginning (in case it was changed) since we have direct access to
it in the function (via listP).
• Relink the list (i.e., skip over the node to be deleted).
• Remove the node.

One implementation of this function might be:

void ListDelete(nodeT **listP, elementT value) {


nodeT *currP, *prevP;

// For 1st node, indicate there is no previous


prevP = NULL;

// Visit each node, maintaining a pointer to the previous node visited


for (currP = *listP; currP != NULL; prevP = currP, currP = currP->next) {
if (currP->element == value) { // Found it
if (prevP == NULL) { // Fix beginning pointer
*listP = currP->next;
} else {
// Fix previous node's next to skip over the removed node
prevP->next = currP->next;
}

// Deallocate the node


free(currP);

// Done searching
return;
}
}
}

We do handle the situation of removing the first node, i.e., when there is no previous. See how we use a
previous pointer value of NULL to indicate this and do the right thing.

11.5 File Handling

A file represents a sequence of bytes on the disk where a group of related data is stored. File is
created for permanent storage of data. It is a readymade structure.

In C language, we use a structure pointer of file type to declare a file.

FILE *fp;

11.5.1 Advantage of a file


It will contain the data even after program exit. Normally we use variable or array to store data,
but data is lost after program exit. Variables and arrays are non-permanent storage medium
whereas file is permanent storage medium.

11.5.2 Basic file operations in C programming

There are five major operations that can be performed on a file are:
➢ Creation of a new file.
➢ Opening an existing file.
➢ Reading data from a file.
➢ Writing data in a file.
➢ Closing a file.

11.5.3 Steps for Processing a File

➢ Declare a file pointer variable.


➢ Open a file using fopen() function.
➢ Process the file using suitable function.
➢ Close the file using fclose() function.
11.5.4 Functions for file handling
There are many functions in C library to open, read, write, search and close file. A list of file functions

is given below:

11.5.6 Opening a File or Creating a File

The fopen() function is used to create a new file or to open an existing file. The syntax of fopen()
function is given as:
FILE *fopen( const char * filename, const char * mode );
Here filename is the name of the file to be opened and mode specifies the purpose of opening the

file. The access mode can have one of the following values:

11.5.7 Closing a File

The fclose() function is used to close an already opened file. The prototype of this function is:-
int fclose( FILE *fp );
Here fclose() function closes the file and returns zero on success, or EOF if there is an error in
closing the file. This EOF is a constant defined in the header file stdio.h.
11.5.8 Writing a File

The fputc() function is used to write a single character into file. It outputs a character to a stream.
int fputc(int c, FILE *fp);

The function fputc() writes the character value of the argument c to the output stream referenced
by fp. It returns the written character written on success otherwise EOF if there is an error.

11.5.9 Reading a File

Given below is the simplest function to read a single character from a file –

int fgetc(FILE * fp);

The fgetc() function reads a character from the input file referenced by fp. The return value is the
character read, or in case of any error, it returns EOF. The following function allows to read a string
from a stream –

char* fgets(char *buf, int n, FILE *fp);

The functions fgets() reads up to n-1 characters from the input stream referenced by fp. It copies
the read string into the buffer buf, appending a null character to terminate the string.

Exercises:

1. Write a program to copy contents of one file to another file. While doing so replace all
lowercase characters to their equivalent uppercase characters.
2. Write a program to append the content of one file at the end of another.
3. Write a program to find the number of lines in a text file.

You might also like