Chapter 1: Structures and Pointers – Organizing
Complex Data
1.1 Introduction: The Need for Structured Data
In the realm of programming, we often encounter situations where a single entity is characterized
by multiple attributes. For instance, a student might be described by their name, roll number,
and academic scores. Similarly, a book has a title, author, ISBN, and publication date. While we
could use separate variables for each attribute, managing them becomes cumbersome, especially
when dealing with collections of such entities. C programming addresses this challenge through
structures, a powerful user-defined data type that allows us to aggregate variables of different
data types under a single, meaningful name. This chapter delves into the definition, declaration,
manipulation, and application of structures, laying the groundwork for organizing complex data
effectively.
1.2 Defining a Structure: Blueprint for Data
A structure definition acts as a blueprint, specifying the names and data types of the members
that will constitute the structure. The struct keyword is employed for this purpose.
Syntax:
struct structure_tag {
data_type member_name_1;
data_type member_name_2;
// ... additional members
};
Here, structure_tag is an optional identifier for the structure, and member_name_1, member_name_2,
etc., are the names of the individual data items within the structure.
Illustrative Example: The Student Structure
Consider the representation of a student. We can define a structure named Student to hold their
essential information:
struct Student {
char name[50]; // To store the student's name (up to 49 characters + null
terminator)
int roll_number; // To store the student's unique roll number
float marks; // To store the student's overall marks
};
This definition creates a template. No memory is allocated yet; we've merely described the shape
of the data.
1.3 Declaring and Accessing Structure Variables and Members
Once a structure is defined, we can declare variables of that structure type. These variables are
instances of the structure, each capable of holding its own set of member values. To interact with
the individual components (members) of a structure variable, we use the dot operator (.).
Declaration Syntax:
struct structure_tag variable_name;
Member Access Syntax:
variable_name.member_name
Practical Application:
Let's instantiate and populate a Student variable:
#include <stdio.h>
#include <string.h> // Required for string manipulation functions like strcpy
// Structure definition as shown previously
struct Student {
char name[50];
int roll_number;
float marks;
};
int main() {
// Declare a variable of type struct Student
struct Student student1;
// Assign values to the members using the dot operator
strcpy([Link], "Alice Wonderland"); // Use strcpy for character arrays (strings)
student1.roll_number = 101;
[Link] = 85.5f; // 'f' suffix denotes a float literal
// Retrieve and display the member values
printf("Student Information:\n");
printf(" Name: %s\n", [Link]);
printf(" Roll Number: %d\n", student1.roll_number);
printf(" Marks: %.2f\n", [Link]); // %.2f formats the float to two decimal
places
return 0;
}
In this code, student1 is an object of the Student structure. We use [Link],
student1.roll_number, and [Link] to access and modify the data within this specific
student1 instance.
Student Information:
Name: Alice Wonderland
Roll Number: 101
Marks: 85.50
1.4 Structure Initialization: Setting Initial Values
Initialization provides a concise way to assign values to structure members at the time of
declaration. This enhances code readability and reduces the need for separate assignment
statements.
Initialization Syntax:
struct structure_tag variable_name = {initializer_1, initializer_2, ...};
The initializers must correspond to the members in the order they are declared within the
structure definition.
Example:
#include <stdio.h>
struct Student {
char name[50];
int roll_number;
float marks;
};
int main() {
// Initialize a Student structure variable directly
struct Student student2 = {"Bob The Builder", 102, 78.0f};
printf("Student Information:\n");
printf(" Name: %s\n", [Link]);
printf(" Roll Number: %d\n", student2.roll_number);
printf(" Marks: %.2f\n", [Link]);
return 0;
}
Here, "Bob The Builder" is assigned to name, 102 to roll_number, and 78.0f to marks in a single
declaration.
Student Information:
Name: Bob The Builder
Roll Number: 102
Marks: 78.00
1.5 Copying and Comparing Structure Variables
Copying: The assignment operator (=) can be used to copy the entire contents of one structure
variable to another, provided they are of the same structure type. This performs a member-wise
copy.
Example of Copying:
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int roll_number;
float marks;
};
int main() {
struct Student student3 = {"Charlie Chaplin", 103, 92.5f};
struct Student student4; // Declare another structure variable
// Copy the entire content of student3 to student4
student4 = student3;
printf("Original Student (student3):\n");
printf(" Name: %s, Roll: %d, Marks: %.2f\n", [Link], student3.roll_number,
[Link]);
printf("\nCopied Student (student4):\n");
printf(" Name: %s, Roll: %d, Marks: %.2f\n", [Link], student4.roll_number,
[Link]);
return 0;
}
Original Student (student3):
Name: Charlie Chaplin, Roll: 103, Marks: 92.50
Copied Student (student4):
Name: Charlie Chaplin, Roll: 103, Marks: 92.50
Comparing: Direct comparison of structure variables using == or != is not permitted in C. To
compare two structures, you must compare each of their members individually. For string
members, the strcmp() function from the <string.h> library is essential.
Example of Member-wise Comparison:
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int roll_number;
float marks;
};
int main() {
struct Student s1 = {"David Copperfield", 104, 88.0f};
struct Student s2 = {"David Copperfield", 104, 88.0f};
struct Student s3 = {"Eve Adams", 105, 90.0f};
// Comparing s1 and s2
if (strcmp([Link], [Link]) == 0 && s1.roll_number == s2.roll_number && [Link] ==
[Link]) {
printf("s1 and s2 represent identical student records.\n");
} else {
printf("s1 and s2 are different.\n");
}
// Comparing s1 and s3
if (strcmp([Link], [Link]) == 0 && s1.roll_number == s3.roll_number && [Link] ==
[Link]) {
printf("s1 and s3 represent identical student records.\n");
} else {
printf("s1 and s3 are different.\n");
}
return 0;
}
Output
s1 and s2 represent identical student records.
s1 and s3 are different.
1.6 Array of Structures: Managing Collections
When dealing with multiple records of the same type (e.g., a list of students), an array of
structures is the ideal data structure. Each element in the array is a complete structure variable.
Syntax:
struct structure_tag array_name[array_size];
Example: A Roster of Students
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int roll_number;
float marks;
};
int main() {
// Declare an array to hold 3 Student records
struct Student student_roster[3];
// Populate the array with student data
strcpy(student_roster[0].name, "Frank Sinatra");
student_roster[0].roll_number = 106;
student_roster[0].marks = 75.0f;
strcpy(student_roster[1].name, "Grace Kelly");
student_roster[1].roll_number = 107;
student_roster[1].marks = 82.5f;
strcpy(student_roster[2].name, "Heidi Klum");
student_roster[2].roll_number = 108;
student_roster[2].marks = 91.0f;
// Iterate through the array to display records
printf("--- Student Roster ---\n");
for (int i = 0; i < 3; i++) {
printf("Record %d:\n", i + 1);
printf(" Name: %s\n", student_roster[i].name);
printf(" Roll Number: %d\n", student_roster[i].roll_number);
printf(" Marks: %.2f\n", student_roster[i].marks);
}
return 0;
}
This approach allows us to manage a collection of student data efficiently, accessing individual
records using array indexing (student_roster[i]) and then their members using the dot operator.
--- Student Roster ---
Record 1:
Name: Frank Sinatra
Roll Number: 106
Marks: 75.00
Record 2:
Name: Grace Kelly
Roll Number: 107
Marks: 82.50
Record 3:
Name: Heidi Klum
Roll Number: 108
Marks: 91.00
1.7 Arrays within Structures: Nested Data
Structures can also contain arrays as members, enabling the representation of data where an
attribute itself is a collection. For instance, a student might have marks for multiple subjects.
Example: Student with Subject Marks
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int roll_number;
float subject_marks[3]; // An array to store marks for 3 subjects
};
int main() {
struct Student student_details;
// Assigning values to members
strcpy(student_details.name, "Ivy League");
student_details.roll_number = 109;
// Initializing the array member
student_details.subject_marks[0] = 70.0f; // Marks for Subject 1
student_details.subject_marks[1] = 75.5f; // Marks for Subject 2
student_details.subject_marks[2] = 80.0f; // Marks for Subject 3
// Displaying the structured data
printf("Student Details:\n");
printf(" Name: %s\n", student_details.name);
printf(" Roll Number: %d\n", student_details.roll_number);
printf(" Subject Marks:\n");
for (int i = 0; i < 3; i++) {
printf(" Subject %d: %.2f\n", i + 1, student_details.subject_marks[i]);
}
return 0;
}
This demonstrates how structures can encapsulate increasingly complex data relationships,
making your programs more organized and robust.
Student Details:
Name: Ivy League
Roll Number: 109
Subject Marks:
Subject 1: 70.00
Subject 2: 75.50
Subject 3: 80.00
Chapter 2: Pointers – Navigating Memory Addresses
2.1 Introduction: The Power of Indirect Access
At the heart of C programming lies the concept of pointers. A pointer is not merely a variable;
it is a variable that holds the memory address of another variable. This indirection grants
programmers unparalleled control over memory, enabling efficient data manipulation, dynamic
memory allocation, and the construction of sophisticated data structures like linked lists and trees.
This chapter demystifies pointers, explaining how to declare, initialize, and use them to access
and modify data indirectly.
2.2 Understanding the Concept: Addresses and Values
Imagine your computer's memory as a vast collection of mailboxes, each uniquely identified by
an address. When you declare a variable, say int age = 30;, the C runtime system allocates a
mailbox (a memory location) for age and stores the value 30 inside it. A pointer variable, on the
other hand, is like a slip of paper where you write down the address of one of these mailboxes.
By having the address, you can locate the mailbox and interact with its contents.
2.3 The Address-of Operator (&): Locating Variables
To ascertain the memory address of any variable, C provides the address-of operator (&).
When placed before a variable name, it returns the memory address where that variable resides.
Syntax:
&variable_name
Illustrative Example:
#include <stdio.h>
int main() {
int counter = 100;
float temperature = 25.75f;
// Obtain and print the memory address of 'counter'
printf("The memory address of 'counter' is: %p\n", &counter);
// Obtain and print the memory address of 'temperature'
printf("The memory address of 'temperature' is: %p\n", &temperature);
return 0;
}
The %p format specifier is crucial for displaying memory addresses, typically in hexadecimal
notation.
The memory address of 'counter' is: 0x7ffee1234567 // Example address
The memory address of 'temperature' is: 0x7ffee1234570 // Example address
2.4 Declaring Pointer Variables: Specifying the Target Type
Declaring a pointer variable involves specifying the data type of the variable it is intended to point
to, followed by an asterisk (*) and the pointer's name. This declaration informs the compiler about
the type of data expected at the memory address the pointer will hold.
Syntax:
data_type *pointer_variable_name;
Examples:
• int *ptr_integer; declares ptr_integer as a pointer capable of storing the address of
an int variable.
• char *ptr_character; declares ptr_character as a pointer for a char variable.
• float *ptr_float; declares ptr_float as a pointer for a float variable.
It is vital that a pointer is declared to match the data type of the variable whose address it will
store. Mismatched types can lead to undefined behavior.
2.5 Initialization of Pointers: Pointing to Something Valid
A pointer variable must be initialized to point to a valid memory location before it can be reliably
used. The most common method is to assign it the address of an existing variable using the &
operator.
Initialization Syntax:
data_type *pointer_name = &existing_variable;
Example:
#include <stdio.h>
int main() {
int score = 95;
int *ptr_score; // Declare a pointer to an integer
// Initialize ptr_score by assigning the address of 'score'
ptr_score = &score;
printf("The value of 'score' is: %d\n", score);
printf("The address of 'score' is: %p\n", &score);
printf("The value stored in 'ptr_score' (the address) is: %p\n", ptr_score);
return 0;
}
In this snippet, ptr_score now holds the memory address of score, and consequently, both &score
and ptr_score will display the same address.
The value of 'score' is: 95
The address of 'score' is: 0x7ffee1234588 // Example address
The value stored in 'ptr_score' (the address) is: 0x7ffee1234588 // Same as above
The Concept of Null Pointers:
A pointer that does not point to any valid memory location is termed a null pointer. It is good
practice to initialize pointers to NULL (a predefined macro in C, typically (void*)0) when their target
is not yet known or valid. This prevents accidental dereferencing of uninitialized pointers, which
can cause program crashes.
int *uninitialized_ptr = NULL;
2.6 Accessing Variables Through Their Pointer: Dereferencing
The dereference operator (*), also known as the indirection operator, is used to access the
value stored at the memory address held by a pointer. This operation is fundamental to pointer
usage.
Dereferencing Syntax:
*pointer_variable_name
Example: Indirect Access and Modification
#include <stdio.h>
int main() {
int quantity = 20;
int *ptr_quantity; // Pointer declaration
// Initialize the pointer
ptr_quantity = &quantity;
// Accessing the value of 'quantity' directly
printf("Direct access to 'quantity': %d\n", quantity);
// Accessing the value of 'quantity' indirectly via the pointer
printf("Indirect access to 'quantity' (*ptr_quantity): %d\n", *ptr_quantity);
// Modifying the value of 'quantity' through the pointer
printf("\nModifying 'quantity' via the pointer...\n");
*ptr_quantity = 35; // This changes the value at the address ptr_quantity points to
// Observe the change in the original variable
printf("Value of 'quantity' after modification: %d\n", quantity);
printf("Value via pointer after modification (*ptr_quantity): %d\n", *ptr_quantity);
return 0;
}
In this example, *ptr_quantity not only retrieves the value stored at the address ptr_quantity
holds but also allows us to modify the original quantity variable indirectly. This capability is a
cornerstone of C's power and flexibility.
Direct access to 'quantity': 20
Indirect access to 'quantity' (*ptr_quantity): 20
Modifying 'quantity' via the pointer...
Value of 'quantity' after modification: 35
Value via pointer after modification (*ptr_quantity): 35