int nums[] = {20,14,35,45,22,66};
printf("%d",nums[1]);
>prints out 14
Considering this code we can notice several things.
In this case we have created an array that contains 6 numbers.
The first element of the array, 20, is stored at an index.
This first index is 0.
Thus, we say that arrays in C (and many other languages) are zero indexed in that the indexes of their elements starts at 0.
We see that when creating this array we first start by specifying the type of the array we are aiming to create.
This type denotes what the datatype of the elements contained inside of the array will be.
We then name the array and put [] to denote to C that what we are creating is an array.
Finally we set it equal to an expression that denotes the elements inside of the array.
In this case, we have defined the array without explicitly specifying a size.
However, because we have made the array containing 6 elements that is the number of elements or indexes the array has.
If we want to add more elements to the array then we would have to allocate a new array and move over the existing elements.
int nums[]={0,0,0,0,0,0};
nums[0]=45;
int nums2[6];
nums2[0]=45;
Here we see that we can allocate the array in one of two ways.
The first way is the same as what we had done already.
Once we have added elements to our array we can always change them out for other elements after the fact.
The second array we create, however, is a little different.
For this one we specify a size of the array within the square brackets.
In this case we have said 6, which means our array will have 6 slots for integers.
We could set this to whatever size we want for this.
However, we need to decide this ahead of time in C once more.
In other languages we do not have this limitation necessarily.
int main(){
int i;
int* p = (int*) malloc(200*sizeof(int));
if (p == NULL) {
printf("memory failed to allocate. \n");
}
else{
for(i = 0; i < 200; i++){
p[i]=i+1;
}
}
//print the elements
for(i=0; i< 200; i++){
printf("%d, ",p[i]);
}
//dynamically deallocate memory
free(p);
p=NULL;
return 0;
}
What we have done here is quite a bit more advanced.
In this case, we have inside of our function main allocated an integer array.
In this case we are using malloc to allocate a range of memory with room for 200 integers.
We then check to see if the memory has been allocated by checking to see if the pointer is equal to NULL or note.
If the memory has been allocated properly we can then populate this array.
Once more we refer to memory in the array in much the same way as we have previously.
We can refer to a specific index of our array using our pointer and the square brackets with our index specified in the brackets.
//define our struct for our nodes
typedef struct node {
int value;
struct node*next;
}node_t;
//create our first node.
node_t * head = NULL;
head = (node_t *) malloc(sizeof(node_t));
if (head == NULL) {
return 1;
}
head->value = 42;
head->next = NULL;
In this code we have first made a struct that allows us to create as many nodes as we need.
The struct defines a value property for each node as well as has a recursive step that lets us define the node as having a next property.
This lets us link the node to the next node in the linked list.
Once we have defined this struct, we can start work on defining our first node.
This node is typically called the head and represents the front of our linked list.
When traversing a linked list, we start at this head typically and move to the next node all the way down to the tail of the list.
In this case, we are defining simply a singly linked list.
Thus, we define the node as having a next value, which for the first node added we set to NULL since we dont have any more nodes.
We also give our node a value using the value property we defined in the struct.
Now lets say we want to add another node. We can do so very simply by changing the ->next property:
//create the list as before and specify the head.
node_t * head = NULL;
head = (node_t *) malloc(sizeof(node_t));
if (head==NULL) {
return 1;
}
//specify the head's value
head->value = 42;
//add a new node onto the linked list.
head->next = (node_t *) malloc(sizeof(node_t));
head->next->value = 43;
//make sure to set the value after this new node to NULL
head->next->next = NULL;
Here we have defined our nodes in much the same way as we had previously.
We make our initial list then add our first node to the list.
Then we change the next property of our initial node to be equal to a new node and we allocate the memory for it in much the same way as we did for the previous one.
Then we can set the value of the next node as well as set the next node after it to NULL.
We can do this as many times as we would like, adding as many nodes as we see fit.
void print_linked_list(node_t * head) {
node_t * position = head;
while (position != NULL) {
printf("%d\n", position->val);
position = position->next;
}
}
In this code, we have defined a function called print_linked_list.
This function takes in a pointer to the head of a linked list.
The first step of traversing this linked list is creating a new pointer that we'll refer to as our iterator.
This iterator allows us to access each of the elements of the linked list one at a time in order from the head to the tail.
It is important that we do create this pointer instead of using our head and shifting it.
Were we to reassign the value of our head pointer this would mean we have lost access to the head of our list.
Inside of our while loop, we first print then reassign our iterator pointer.
This allows us to move along the linked list from the first value in the list to the last value in order using ->next.
#include stdio.h
#include stdlib.h
int top = -1, inp_array[SIZE];
void push();
void pop();
void peek();
int main(){
int choice;
while (1==1){
printf("Interact with your stack:\n");
printf("1.Push the element\n2.Pop the element\n3.Peek\n4.End\n\n");
printf("Enter the choice: ");
scanf("%d", &choice);
switch (choice){
case 1:
push();
break;
case 2:
pop();
break;
case 3:
peek();
break;
case 4:
exit(0);
default:
printf("\nInvalid choice!!");
}
}
}
void push(){
int x;
if (top == SIZE - 1){
printf("\nOverflow!!");
}
else{
printf("\nEnter the element to be added onto the stack: ");
scanf("%d", &x);
top = top + 1;
inp_array[top] = x;
}
}
void pop(){
if (top == -1){
printf("\nUnderflow!!");
}
else{
printf("\nPopped element: %d", inp_array[top]);
top = top - 1;
}
}
void peek(){
if (top == -1){
printf("\nUnderflow!!");
}
else{
printf("\nElements present in the stack: \n");
for (int i = top; i >= 0; --i)
printf("%d\n", inp_array[i]);
}
}
In this code we are doing several things.
First we define our array in memory which we'll use to hold our stack.
We also define our main function.
Here it simply allows us to interface with our stack via our terminal rather than just in code.
Finally we have defined three functions.
void push() checks to see if we have room in the memory we allocated for our stack to add an element then adds the element.
void pop() checks to see if there is an existing element on the stack then removes the "top" one if there are one or more elements.
void peek() checks to see if there is an existing element on the stack then look at the "top" one if there are one or more elements.
#include stdio.h
# define SIZE 100
void enqueue();
void dequeue();
void show();
int inp_arr[SIZE];
int Rear = - 1;
int Front = - 1;
main()
{
int ch;
while (1)
{
printf("1.Enqueue Operation\n");
printf("2.Dequeue Operation\n");
printf("3.Display the Queue\n");
printf("4.Exit\n");
printf("Enter your choice of operations : ");
scanf("%d", &ch);
switch (ch)
{
case 1:
enqueue();
break;
case 2:
dequeue();
break;
case 3:
show();
break;
case 4:
exit(0);
default:
printf("Incorrect choice \n");
}
}
}
void enqueue()
{
int insert_item;
if (Rear == SIZE - 1)
printf("Overflow \n");
else
{
if (Front == - 1)
Front = 0;
printf("Element to be inserted in the Queue\n : ");
scanf("%d", &insert_item);
Rear = Rear + 1;
inp_arr[Rear] = insert_item;
}
}
void dequeue()
{
if (Front == - 1 || Front > Rear)
{
printf("Underflow \n");
return ;
}
else
{
printf("Element deleted from the Queue: %d\n", inp_arr[Front]);
Front = Front + 1;
}
}
void show()
{
if (Front == - 1)
printf("Empty Queue \n");
else
{
printf("Queue: \n");
for (int i = Front; i <= Rear; i++)
printf("%d ", inp_arr[i]);
printf("\n");
}
}
In this example, we have defined a queue in C much the same as we have defined a stack in C previously.
However, this time we have done a couple of things differently.
Notice that we create our array of memory in C in much the same way.
However, we need access to the back of the queue and the front this time.
This is because we need to be able to push or enqueue to the back of the queue and we need to pop or dequeue from the front of the queue.
This means that we need two pointers, one to the front of the array of memory and one to the back.
Next we need to have the same functions as we had before but they need to behave a little differently.
enqueue() can be thought of as being the same as push().
enqueue() takes a new element being passed to this code from the terminal and adds it to the back of the queue.
dequeue() takes the oldest element (the one at the front of the queue) and removes or pops it off the queue.
show() behaves similarly to how peek would behave with a stack.
show() lets us see the next element that we would pop without having to remove it first.
#include
#include
int maximum =10; // Define maximum size of the deque
int deque[maximum]; //implement using an array
int front = -1;
int back = -1;
// Check if the deque is full
int full() {
return ((front == 0 && back == maximum - 1) || (front == back + 1));
}
// See if the deque is empty
int empty() {
return (front == -1);
}
// Insert element at front of deque
void addFront(int key) {
if (isFull()) { //check to see if we have room left in the deque
printf("Deque is full.\n");
return;
}
if (front == -1) { // If deque is initially empty
front = 0;
back = 0;
} else if (front == 0) {
front = maximum - 1; // wrap around
} else {
front = front - 1;
}
deque[front] = key;
printf("Inserted %d at the front.\n", key);
}
// Insert element at back of deque
void insertBack(int key) {
if (isFull()) {
printf("Deque is full.\n");
return;
}
if (back == -1) { // If deque is initially empty
front = 0;
back = 0;
} else if (back == maximum - 1) {
back = 0; // wrap around
} else {
back = back + 1;
}
deque[back] = key;
printf("Inserted %d at the back.\n", key);
}
// Delete an element from the front of the deque
void deleteFront() {
if (isEmpty()) {
printf("Error, deque is empty.\n");
return;
}
int removed = deque[front];
if (front == back) { // Deque has only one element
front = -1;
back = -1;
} else if (front == maximum - 1) {
front = 0; // wrap around
} else {
front = front + 1;
}
printf("Deleted %d from the front.\n", removed);
}
// Delete an element from the back of the deque
void deleteBack() {
if (isEmpty()) {
printf("Deque is empty.\n");
return;
}
int removed = deque[back];
if (front == back) { // Deque has only one element
front = -1;
back = -1;
} else if (back == 0) {
back = maximum - 1; // wrap around
} else {
back = back - 1;
}
printf("Deleted %d from the rear.\n", removed);
}
In this code block, we have done a number of things.
First, we create our array in memory with a maximum size.
This allows us to check and see if we have run out of space.
We also create variables that are our front and back indices for the array.
We could do this on the heap using pointers as well if we need to allocate a larger deque or a deque for a non primitive type.
We then declare multiple functions for adding elements as well as removing elements from the front and the back of the deque.
We could also define functions that would be used for implementing peeking at the front or the back of the deque as well if we want to see the element without removing it.
#include stdio.h
#include stdlib.h
#include string.h
/* A tree node. Holds pointers to left and right sub-trees, and some data (a string).
*/
typedef struct node {
struct node *left;
struct node *right;
char *string;
} node;
node *root; /* pointers automatically initialized to NULL */
int insert(const char *string, node *root) {
/* Add a string to the tree. Keeps in order, ignores dupes.
*/
int num = strcmp(root->string, string);
node *temp;
for(;;) {
if ( 0 == num)
/* duplicate string - ignore it. */
return 1;
else if (-1 == num) {
/* create new node, insert as right sub-tree.
*/
if ( NULL == root -> right ) {
temp = malloc(sizeof(node));
temp -> left = NULL;
temp -> right = NULL;
temp -> string = strdup(string);
return 2;
}
else
root = root -> right;
}
else if ( NULL == root ->left ) {
/* create new node, insert as left sub-tree.
*/
temp = malloc(sizeof(node));
temp -> left = NULL;
temp -> right = NULL;
temp -> string = strdup(string);
return 2;
}
else{
root = root -> left;
}
}
void print(node *root) {
/* in-order traversal -- first process left sub-tree.
*/
if ( root -> left != NULL ){
print(root->left);
}
/* then process current node.
*/
fputs(root->string, stdout);
/* then process right sub-tree
*/
if ( root->right != NULL ){
print(root->right);
}
}
int main() {
char line[100];
/* Let user enter some data. Enter an EOF (e.g., ctrl-D or F6) when done.
*/
while ( fgets(line, 100, stdin)){
insert(line, root);
}
/* print out the data, in order
*/
print(root);
return 0;
}
The code for implementing a tree listed here contains a number of components.
First, we define a node type using a struct at the top of the code.
Next we have our main piece of functionality.
This is our insert function.
This function checks to see if a string being inserted into the tree already exists.
If it does, we ignore it.
This is a design choice. In some forms of trees, we could have duplicate values.
For the purposes of this example tree structure we do not allow for duplicates.
If the element we want to insert is not a duplicate, we then insert it either to the left or the right of the current node.
This allows us to balance the tree and make sure that it grows evenly.