Definition
A structure type allows us to group several types together . This can be useful for organizing our data. Let us assume that we want to work a "person" type in our program. A person can have a first name, last name and an age. We can create a structure to hold the properties.
File: s1.c
#include <stdio.h>
#include <string.h>
struct Person
{
char firstName[25] ;
char lastName[25] ;
int age ;
};
int main()
{
struct Person person1 ;
strcpy( person1.firstName , "Buster" ) ;
strcpy( person1.lastName , "Douglas" ) ;
person1.age = 45 ;
printf( "Person details:Name %s %s Age %d\n" , person1.firstName , person1.lastName , person1.age ) ;
}
Output:
Person details:Name Buster Douglas Age 45
The statement:
struct Person
{
char firstName[25] ;
char lastName[25] ;
int age ;
};
defines our custom type that can hold 3 data members. We have given a name to our type -- Person. Inside the "main" function we use the type "Person" to create a variable called "person1" . The "person1" variable has 3 data members that we can set the values of and then retrieve them later on. A variable of type struct can access it's data member with the "." notation. We do not need to name our type and in that case we cannot use it to create our variables later on in the program but can do so at the point of declaration.
File: "s1a.c"
#include <stdio.h>
#include <string.h>
struct
{
char firstName[25] ;
char lastName[25] ;
int age ;
};
int main()
{
}
File: "s2.c"
#include <stdio.h>
#include <string.h>
struct
{
char firstName[25] ;
char lastName[25] ;
int age ;
} person1;
int main()
{
strcpy( person1.firstName , "Buster" ) ;
strcpy( person1.lastName , "Douglas" ) ;
person1.age = 45 ;
printf( "Person details:Name %s %s Age %d\n" , person1.firstName , person1.lastName , person1.age ) ;
}
In the above we did not give a name for our type and created a single variable "person1" for this structure.
File: "s3.c"
#include <stdio.h>
#include <string.h>
struct Person
{
char firstName[25] ;
char lastName[25] ;
int age ;
} ;
int main()
{
struct Person person1 = { "Buster", "Douglas" , 45 } ;
printf( "Person details:Name %s %s Age %d\n" , person1.firstName , person1.lastName , person1.age ) ;
}
In the above we initialized our "person1" object with the values on the right hand side.
File: "s4.c"
#include <stdio.h>
#include <string.h>
struct Person
{
char firstName[25] ;
char lastName[25] ;
int age ;
} ;
int main()
{
struct Person person1 = { .age = 45, .firstName = "Buster", .lastName = "Douglas" } ;
printf( "Person details:Name %s %s Age %d\n" , person1.firstName , person1.lastName , person1.age ) ;
}
The above shows another way to initialize the data members of a structure where the property name is listed in the initialization list allowing us to change the order from the declaration order.
Nested Structures
We can have nested structures also.
File: "s5.c"
#include <stdio.h>
#include <string.h>
struct Person
{
struct name
{
char firstName[25] ;
char lastName[25] ;
} nameOfPerson ;
int age ;
} ;
int main()
{
struct Person person1 = { "Buster" , "Douglas" , 45 } ;
printf( "Person details:Name %s %s Age %d\n" , person1.nameOfPerson.firstName ,
person1.nameOfPerson.lastName , person1.age ) ;
}
Output:
2030008434:struct ajay.mittal$ ./a.out
Person details:Name Buster Douglas Age 45
We have a structure "name" that is defined in the "Person" structure and a variable "nameOfPerson" of the type "name" . In the "main" function we initialize all the variables with a single pair of curly braces. We could also have chosen to place the definition of the "name" structure outside the "Person" structure .
File: "s6.c"
#include <stdio.h>
#include <string.h>
struct name
{
char firstName[25] ;
char lastName[25] ;
} ;
struct Person
{
struct name nameOfPerson ;
int age ;
} ;
int main()
{
struct Person person1 = { "Buster" , "Douglas" , 45 } ;
printf( "Person details:Name %s %s Age %d\n" , person1.nameOfPerson.firstName ,
person1.nameOfPerson.lastName , person1.age ) ;
}
Output:
2030008434:struct ajay.mittal$ ./a.out
Person details:Name Buster Douglas Age 45
Pointer to structures
Just as we have pointers to integers we can pointers to structures.
File: "s8.c"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct Person
{
char firstName[25] ;
char lastName[25] ;
int age ;
} ;
int main()
{
struct Person person1 = { "Buster" , "Douglas" , 45 } ;
struct Person* ptrToPerson = &person1 ;
printf( "Person details:Name %s %s Age %d\n" , ptrToPerson->firstName ,
ptrToPerson->lastName , ptrToPerson->age ) ;
printf( "Person details:Name %s %s Age %d\n" , (*ptrToPerson).firstName ,
(*ptrToPerson).lastName , (*ptrToPerson).age ) ;
struct Person* dynamicPtr = (struct Person*) malloc (sizeof(struct Person) ) ;
strcpy( dynamicPtr->firstName , "George" ) ;
strcpy( dynamicPtr->lastName , "Foreman" ) ;
dynamicPtr->age = 55 ;
printf( "Person details:Name %s %s Age %d\n" , dynamicPtr->firstName ,
dynamicPtr->lastName , dynamicPtr->age ) ;
free( dynamicPtr ) ;
}
Output:
2030008434:struct ajay.mittal$ ./a.out
Person details:Name Buster Douglas Age 45
Person details:Name Buster Douglas Age 45
Person details:Name George Foreman Age 55
The line :
struct Person* ptrToPerson = &person1 ;
takes the address of the object "person1" that is of type "Person" and assigns it to a pointer variable "ptrPerson" . We can use the pointer variable to access the data members using the notation "->" or "*" . The "->" is easier to read. The data members are printed out using both the notations in the above example. We can also allocate memory for a structure using "malloc" . The "sizeof" function can be used to determine the size of the structure that we have defined. We need to cast the return value of "malloc" since the normal return value is of type "void*" .
We can then use the "dynamicPtr" as a pointer to the structure just as we did for the earlier variable "ptrToPerson" .
We saw how we can nest structures. Can we nest the same structure. Suppose we have a "Person" structure and we want to store a manager who is also a person in this structure.
File: "self1.c"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//--------------------------------------------------------------
struct Person
{
char firstName[25] ;
char lastName[25] ;
int age ;
struct Person manager ;
} ;
//--------------------------------------------------------------
int main()
{
struct Person person1 ;
}
//--------------------------------------------------------------
The above will not compile.
$ gcc self1.c
self1.c:12:23: error: field ‘manager’ has incomplete type
12 | struct Person manager ;
This makes sense because in order to create an object of type manager we will need encounter the first name, last name , age and another manager and this will lead to an infinite chain conceptually. We can solve the above problem by using pointers.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//--------------------------------------------------------------
struct Person
{
char firstName[25] ;
char lastName[25] ;
int age ;
struct Person* manager ;
} ;
//--------------------------------------------------------------
int main()
{
struct Person person1 ;
strcpy( person1.firstName , "Archie" ) ;
strcpy( person1.lastName , "Moore" ) ;
person1.age = 41 ;
struct Person manager1 ;
strcpy( manager1.firstName , "Mister" ) ;
strcpy( manager1.lastName , "Boss" ) ;
manager1.age = 51 ;
}
//--------------------------------------------------------------
Using Typedef
We need to use "struct Person" every time we need to create a variable such as:
struct Person person1 ;
We can avoid this by using "typedef" to define a type .
File: "type1.c"
#include <stdio.h>
#include <string.h>
struct Person
{
char firstName[25] ;
char lastName[25] ;
int age ;
};
typedef struct Person PERSONTYPE ;
int main()
{
PERSONTYPE person1 ;
strcpy( person1.firstName , "Buster" ) ;
strcpy( person1.lastName , "Douglas" ) ;
person1.age = 45 ;
printf( "Person details:Name %s %s Age %d\n" , person1.firstName , person1.lastName , person1.age ) ;
}
Operations on Structures.
The only operation allowed on structures are taking the address, de referencing a pointer to a structure or the copy operation. We cannot perform operations like "==" equals comparison operator .
File: "s10.c"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct Person
{
char firstName[25] ;
char lastName[25] ;
int age ;
} ;
//Operations on structures
int main()
{
struct Person person1 = { "Buster" , "Douglas" , 45 } ;
struct Person person2 = person1 ;
struct Person person3 ;
//Easiest way to copy
person3 = person1 ;
printf( "Person 1 details:Name %s %s Age %d\n" , person1.firstName ,
person1.lastName , person1.age ) ;
printf( "Person 3 details:Name %s %s Age %d\n" , person3.firstName ,
person3.lastName , person3.age ) ;
struct Person person4 ;
memcpy( &person4 , &person1 , sizeof( struct Person) ) ;
printf( "Person 4 details:Name %s %s Age %d\n" , person4.firstName ,
person4.lastName , person4.age ) ;
struct Person person5 ;
struct Person* ptr1 = &person1 ;
struct Person* ptr2 = &person5 ;
*ptr2 = *ptr1 ;
printf( "Person 5 details:Name %s %s Age %d\n" , person5.firstName ,
person5.lastName , person5.age ) ;
/* Not allowed by the compiler
if ( person1 == person2 )
printf( "Structures are equal.\n" ) ;
*/
}
Output:
2030008434:struct ajay.mittal$ ./a.out
Person 1 details:Name Buster Douglas Age 45
Person 3 details:Name Buster Douglas Age 45
Person 4 details:Name Buster Douglas Age 45
Person 5 details:Name Buster Douglas Age 45
The lines:
//Easiest way to copy
person3 = person1 ;
state that the contents of "person1" are copied to "person3" . The copy is memory bitwise copy so whatever "person1" had is copied exactly as it is in "person3" . We can confirm that by printing out the "person3" details. There are other ways to do the copying such as "memcpy" and using the pointer notation "*ptr2 = *ptr1" . All the 3 methods do the same memory bitwise copy. There is a pitfall to this method in the sense that it performs a shallow copy. Let us examine the following code.
File: "s11.c"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct Person
{
char firstName[25] ;
char lastName[25] ;
int age ;
int* ptr1 ;
} ;
//Operations on structures
int main()
{
struct Person person1 = { "Buster" , "Douglas" , 45 } ;
person1.ptr1 = ( int* ) malloc( sizeof(int) * 1 ) ;
*(person1.ptr1) = 100 ;
struct Person person2 = person1 ;
//Free up the pointers
free( person1.ptr1 ) ;
free( person2.ptr1 ) ;
}
Output:
$ ./a.exe
Aborted (core dumped)
The statement
struct Person person2 = person1 ;
copies all the fields from the "person1" object to "person2" object . That also copies the pointer address "ptr1" ,
Now let's say we free up the memory for each structure and say :
//Free up the pointers
free( person1.ptr1 ) ;
free( person2.ptr1 ) ;
This a big no no. We cannot delete the memory for the same address twice and the program crashes. What we need to do is allocate separate memory for the pointer when copying a structure.
File: "s12.c"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct Person
{
char firstName[25] ;
char lastName[25] ;
int age ;
int* ptr1 ;
} ;
//Operations on structures
int main()
{
struct Person person1 = { "Buster" , "Douglas" , 45 } ;
person1.ptr1 = ( int* ) malloc( sizeof(int) * 1 ) ;
*(person1.ptr1) = 100 ;
struct Person person2 = person1 ;
//Do a deep copy
person2.ptr1 = ( int* ) malloc( sizeof(int) * 1 ) ;
*( person2.ptr1 ) = *(person1.ptr1) ;
printf( "%d\n" , *( person2.ptr1 ) ) ;
//Free up the pointers
free( person1.ptr1 ) ;
free( person2.ptr1 ) ;
}
Structure Arrays
Just as we can have arrays of integers and characters we can have arrays of structures.
File: "arr1.c"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//--------------------------------------------------------------
struct student
{
char name[256] ;
int id ;
float grade ;
} ;
//--------------------------------------------------------------
void print( struct student* ptr1 , int size )
{
for( int i1=0 ; i1< size ; i1++ )
{
printf( "Name:%-15s Id:%5d Grade:%5.1f\n" , ptr1[i1].name ,
ptr1[i1].id , ptr1[i1].grade ) ;
} //for
}
//--------------------------------------------------------------
int main()
{
struct student arr1[2] ;
strcpy( arr1[0].name , "Joe Frazier" ) ;
arr1[0].id = 1 ; arr1[0].grade = 5 ;
strcpy( arr1[1].name , "Ken Norton" ) ;
arr1[1].id = 2 ; arr1[1].grade = 15 ;
print( arr1 , 2 ) ;
}
//--------------------------------------------------------------
Output:
$ gcc arr1.c ; ./a.exe
Name:Joe Frazier Id: 1 Grade: 5.0
Name:Ken Norton Id: 2 Grade: 15.0
The notation " struct student arr1[2] ;" states that the arr1 is an array of structures. We can refer to each element
of the array using the notation "arr1[0]." . We can pass the structure array as a pointer to a function as in this example. Notice how the "printf" has used the formatting options. The "-" for the string means left justified and the 15 in the string "%-15s " represents the width as 15 .
We can also allocate space for an array of structures dynamically. The below program allocates the space needed by the user.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//--------------------------------------------------------------
struct student
{
char name[256] ;
int id ;
float grade ;
} ;
//--------------------------------------------------------------
void print( struct student* ptrStudents , int size )
{
for( int i1=0 ; i1< size ; i1++ )
{
printf( "Name:%-15s Id:%5d Grade:%5.1f\n" , ptrStudents[i1].name ,
ptrStudents[i1].id , ptrStudents[i1].grade ) ;
} //for
}
void readData ( struct student* ptrStudents , int noOfStudents )
{
for( int i1=0 ; i1 < noOfStudents ; i1++ )
{
printf( "\nEnter data for student#:%d\n" , (i1+1) ) ;
printf( "Enter student name:" ) ;
scanf( "%[^\n]" , ptrStudents[i1].name ) ;
printf( "Enter id:" ) ; scanf( "%d" , &ptrStudents[i1].id ) ;
printf( "Enter grade:" ) ; scanf( "%f" , &ptrStudents[i1].grade ) ;
getchar() ;
}
}
//--------------------------------------------------------------
int main()
{
struct student* arrayOfStudents ;
int noStudents ;
printf( "Enter the #of students:" ) ;
scanf( "%d" , &noStudents ) ; getchar() ;
arrayOfStudents = (struct student*) malloc( sizeof ( struct student ) * noStudents ) ;
readData ( arrayOfStudents , noStudents ) ;
printf( "Test: %d\n" , arrayOfStudents[0].id ) ;
print( arrayOfStudents , noStudents ) ;
free( arrayOfStudents ) ;
}
//--------------------------------------------------------------
Output:
Enter the #of students:2
Enter data for student#:1
Enter student name:Ajay Kumar
Enter id:1
Enter grade:32
Enter data for student#:2
Enter student name:Sam West
Enter id:3
Enter grade:32
Test: 1
Name:Ajay Kumar Id: 1 Grade: 32.0
Name:Sam West Id: 3 Grade: 32.0
The above shows
Union
A union is similar to structure that it can contain variables of different types but all the variables occupy share the same memory. The memory located for the union is the size of the largest variables in the union. The union type is used usually to save space .
File: "un1.c"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//--------------------------------------------------------------
union sampleUnion
{
int x1 ;
char ch ;
} ;
//--------------------------------------------------------------
int main()
{
union sampleUnion var1 ;
var1.ch = 'A' ;
printf( "%d %d\n" , var1.x1 , sizeof(var1) ) ;
}
//--------------------------------------------------------------
Output:
$ ./a.exe
65 4
RAM
0
x1 1 Byte 1 ch
Byte 2
Byte 3
Byte 4
In the above we have a union that has 2 variables; an integer x1 that takes up 4 bytes and a character that takes 1 byte. In the RAM diagram the variables occupy the same memory starting at the address 1. We assign the value of 'A' to ch which is 65 in the ASCII chart . If we print the value of x1 we can see that x1's memory also got affected and it prints 65. Also the size of function returns the size 4 which is the size of an integer on this machine.
Bitwise operators
Bitwsie operators work on integer types and perform binary arithmetic.
Binary arithmetic only works on 1's and 0's .
Or Operator
0 | 1 = 1
1 | 0 = 1
0 | 0 = 0
1 | 1 = 1
If we have a "1" in any of the arguments then the result is 1 .
And Operator
0 & 0 = 0
0 & 1 = 0
1 & 0 = 1
1 & 1 = 1
Both the arguments must be "1" and then the result is "1" .
Exclusive Operator
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
If both the arguments are different then the result is 1 else the result is 0 .
File: "bit1.c"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//--------------------------------------------------------------
//--------------------------------------------------------------
int main()
{
char ch1 = 4 ;
char ch2 = 2 ;
char ch3 = ch1 | ch2 ;
printf( "Or of 4 and 2: %d\n" , ch3 ) ;
ch3 = ch1 & ch2 ;
printf( "And of 4 and 2: %d\n" , ch3 ) ;
ch3 = ch1 ^ ch2 ;
printf( "Exclusive or 4 and 2: %d\n" , ch3 ) ;
ch1 = ch1 << 1 ;
printf( "4 shifted left by 1 bit: %d\n" , ch1 ) ;
ch2 = ch2 >> 1 ;
printf( "2 shifted right by 1 bit: %d\n" , ch2 ) ;
}
//--------------------------------------------------------------
Output:
Or of 4 and 2: 6
And of 4 and 2: 0
Exclusive or 4 and 2: 6
4 shifted left by 1 bit: 8
2 shifted right by 1 bit: 1
Enumeration
Sometimes we have a case where we need to define some related constants. C provides the enum construct for that.
File: "enum1.c"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//--------------------------------------------------------------
int main()
{
enum months {
JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
enum months particularMonth ;
particularMonth = FEB ;
printf( "%d\n" , particularMonth ) ;
particularMonth = 30 ;
printf( "%d\n" , particularMonth ) ;
}
//--------------------------------------------------------------
Output:
$ gcc enum1.c ; ./a.exe
1
30
In the above we declare the type "months" which represents the months of the year. We then list the constants that we want to use. The values that are assigned to the constants are integers that start with 0 . The above program prints out the month of "FEB" as 1 . There is no type safety for enum variables. We can assign a value out of range such as "30" in the example above even though the month of "DEC" has the highest value 11 we can still assign 30 to the month variable.
File: "enum2.c"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//--------------------------------------------------------------
int main()
{
enum months {
JAN= 10, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
enum months particularMonth ;
particularMonth = FEB ;
printf( "February %d\n" , particularMonth ) ;
printf( "December %d\n" , DEC ) ;
}
//--------------------------------------------------------------
Output:
$ gcc enum2.c ; ./a.exe
February 11
December 21
//type def
//multiple files
//forward reference
// malloc pointer array
//linked list
//Nested structures
//pointers
//Dynmaic structures
//Forward declaration malloc