C Programming Tutorial

Welcome to the C Programming Tutorial! This tutorial covers the basics of C programming, starting from the fundamentals and going all the way to complex topics. C is one of the most widely used programming languages, known for its simplicity, efficiency, and flexibility. Whether you're a beginner or someone looking to strengthen your programming skills, this guide will serve as a solid foundation.

What is C Language?

C is a powerful, general-purpose programming language. It was developed by Dennis Ritchie in 1972 at AT&T Bell Labs. C is known for its simplicity, structured approach, and close relationship with hardware, making it a great choice for system-level programming.

History of C

The C language was created to improve the B language, which was itself derived from BCPL (Basic Combined Programming Language). Over time, C evolved, and its features laid the foundation for many other programming languages, such as C++, Java, and C#.

Features of C

How to Install C

To start programming in C, you need a compiler. A compiler translates your code into executable form. Here are some popular options:

For Windows users, it's recommended to install Code::Blocks with the GCC compiler for a simple setup. For Linux and macOS, GCC is typically pre-installed or can be easily installed via terminal.

First C Program

Let's write your first C program. This program will simply print "Hello, World!" to the screen:

#include <stdio.h>

int main() {
    printf("Hello, World!\\n");
    return 0;
}

Here's a breakdown:

Compilation Process in C

The compilation process involves several stages to transform your C code into an executable file:

  1. Preprocessing - Handles directives like #include and macro definitions.
  2. Compilation - Translates the preprocessed code into assembly language.
  3. Assembly - Converts assembly code into machine code.
  4. Linking - Combines object code files into a single executable, resolving references to external functions.

To compile a C program using GCC:

gcc filename.c -o outputname

Replace filename.c with your C file name and outputname with the desired name of the executable.

printf and scanf

The printf and scanf functions are crucial for input and output operations in C:

#include <stdio.h>

int main() {
    int number;
    printf("Enter a number: ");
    scanf("%d", &number);
    printf("You entered: %d\\n", number);
    return 0;
}

In this example:

Variables in C

Variables in C are used to store data that can be modified during program execution. They act as containers for storing data values. A variable has a data type, a name, and a value. Here's how to declare a variable in C:

int age = 25; // Integer variable
float height = 5.9; // Floating-point variable
char initial = 'A'; // Character variable

Key points about variables:

Data Types in C

Data types in C specify the type of data that a variable can hold. There are several data types in C, which are categorized into basic, derived, and user-defined types.

int a = 10; // Integer
char b = 'C'; // Character
float c = 5.25; // Float
double d = 10.987; // Double

Keywords in C

Keywords are reserved words in C that have special meaning to the compiler. They cannot be used as identifiers (variable names). C has 32 keywords, including:

int main() {
    int num = 5; // 'int' is a keyword
    return 0;    // 'return' is a keyword
}

C Identifiers

Identifiers are names given to variables, functions, arrays, etc., to identify them in the program. They must follow these rules:

int count = 10; // Valid identifier
float _value = 23.45; // Valid identifier
int 9number = 5; // Invalid identifier, cannot start with a digit

C Operators

Operators are symbols that perform operations on variables and values. C provides several types of operators:

int a = 5, b = 10;
int sum = a + b; // Arithmetic Operator
if (a > b) {     // Relational Operator
    printf("a is greater than b");
}
a += 2;          // Assignment Operator
a++;             // Increment Operator

C Comments

Comments in C are used to add notes or explanations within the code, making it easier to understand. Comments are ignored by the compiler, meaning they don’t affect the program's execution.

// This is a single-line comment
/* 
    This is a multi-line comment.
    It spans multiple lines.
*/

C Format Specifier

Format specifiers in C are used in input/output functions like printf() and scanf() to define the type of data being handled. Common format specifiers include:

int age = 25;
float height = 5.8;
char initial = 'A';
printf("Age: %d, Height: %.1f, Initial: %c", age, height, initial);

C Escape Sequence

Escape sequences in C are special characters used to represent certain whitespace, control characters, or special formatting inside strings. Each escape sequence starts with a backslash (\). Common escape sequences are:

printf("Hello\\nWorld!"); // Outputs: Hello
                            //          World!
printf("Name:\\tJohn Doe");    // Outputs: Name:   John Doe
printf("He said \\"Hi!\\"");  // Outputs: He said "Hi!"

ASCII Value in C

In C, every character has a corresponding ASCII (American Standard Code for Information Interchange) value. The ASCII value of characters can be obtained using `%d` format specifier with a character. This can be useful for operations involving character encoding.

char ch = 'A';
printf("The ASCII value of %c is %d", ch, ch); // Outputs: The ASCII value of A is 65

Here, the character `'A'` has an ASCII value of `65`, and using `%d` allows you to print its numeric value.

Constants in C

Constants in C are fixed values that do not change during program execution. They can be defined using the `const` keyword or `#define` preprocessor directive.

const int MAX_AGE = 100; // Using const keyword
#define PI 3.14159          // Using #define directive

printf("Max Age: %d", MAX_AGE); // Outputs: Max Age: 100
printf("Value of PI: %.2f", PI); // Outputs: Value of PI: 3.14

The `const` keyword makes a variable immutable, while `#define` creates a constant that the preprocessor replaces before compilation.

Literals in C

Literals in C are fixed values assigned to variables. These are constant values that do not change during program execution. There are several types of literals:

int age = 25;          // Integer literal
float pi = 3.14;        // Floating-point literal
char grade = 'A';       // Character literal
char name[] = "John";   // String literal

Tokens in C

In C, tokens are the smallest building blocks of a program. There are five major categories of tokens:

int number = 10; // 'int' is a keyword, 'number' is an identifier, '10' is a constant.

Boolean in C

The C language does not have a native `boolean` data type like some other programming languages. However, C99 introduced {``} to use `bool`, `true`, and `false`. Without it, boolean values are often represented using integers:

#include < stdbool.h>

bool isEven = true;
int isOdd = 0;  // 0 is false
if (isEven) {
    printf("The number is even.");
}

Static in C

The `static` keyword in C can be used with variables and functions. It has several purposes:

void count() {
    static int counter = 0;  // Retains value between calls
    counter++;
    printf("Counter: %d\\n", counter);
}

In this example, `counter` retains its value across multiple calls to the `count()` function.

Programming Errors in C

Errors in C programming are broadly categorized into three types:

int result = 10 / 0; // Runtime Error: Division by zero

Compile-time vs Runtime Errors

Errors in C can occur during compilation or during program execution:

// Compile-time error example
int number = "Hello";  // Type mismatch, detected during compilation

// Runtime error example
int arr[5];
arr[10] = 50;  // Array index out of bounds, detected at runtime

Conditional Operator in C

The **Conditional Operator** in C is a shorthand way to write simple `if-else` statements. It uses the syntax: condition ? expr1 : expr2. If the condition is true, `expr1` is executed; otherwise, `expr2` is executed.

int x = 10, y = 20;
int max = (x > y) ? x : y;  // max will be 20
printf("Maximum: %d\\n", max);

Bitwise Operators in C

Bitwise Operators in C operate directly on bits and are used for performing binary calculations. Common bitwise operators include:

int a = 5;     // 0101 in binary
int b = 3;     // 0011 in binary

int andResult = a & b;      // 0101 & 0011 = 0001 -> 1
int orResult = a | b;       // 0101 | 0011 = 0111 -> 7
int xorResult = a ^ b;      // 0101 ^ 0011 = 0110 -> 6
int notResult = ~a;         // ~0101 = 1010 -> -6 (in 2's complement form)
int leftShift = a << 1;     // 0101 << 1 = 1010 -> 10
int rightShift = a >> 1;    // 0101 >> 1 = 0010 -> 2

printf("AND: %d, OR: %d, XOR: %d, NOT: %d, Left Shift: %d, Right Shift: %d\\n",
    andResult, orResult, xorResult, notResult, leftShift, rightShift);

2's Complement in C

2's Complement is a method for representing signed integers in binary, allowing both positive and negative values. To get the 2's complement of a number:

For example, in an 8-bit system, the 2's complement of 5 (0000 0101) is -5, represented by inverting all bits (1111 1010) and adding 1 (1111 1011).

int x = 5;
int twosComplement = ~x + 1;  // -5 in 2's complement form
printf("2's Complement of %d: %d\\n", x, twosComplement);

C Control Statements

Control Statements in C are used to determine the flow of program execution. These statements allow decisions to be made in code based on conditions. The most commonly used control statements are if-else and switch.

C if-else

The if-else statement is used for conditional branching in C. It evaluates a condition and executes code blocks based on whether the condition is true or false. The basic syntax is:

if (condition) {
    // Code to execute if condition is true
} else {
    // Code to execute if condition is false
}

You can also use multiple conditions using else if statements.

int num = 20;

if (num > 0) {
    printf("Positive number");
} else if (num == 0) {
    printf("Zero");
} else {
    printf("Negative number");
}

C switch

The switch statement is used for multi-way branching. It evaluates an expression and executes the code block that matches the given case. If no match is found, the default case is executed (if it is defined). Here's the syntax:

switch (expression) {
    case constant1:
        // Code to execute if expression equals constant1
        break;
    case constant2:
        // Code to execute if expression equals constant2
        break;
    ...
    default:
        // Code to execute if none of the cases match
}

The break statement is used to exit the switch block once a case is matched, preventing the execution of subsequent cases.

char grade = 'B';

switch (grade) {
    case 'A':
        printf("Excellent!");
        break;
    case 'B':
        printf("Well done!");
        break;
    case 'C':
        printf("Good!");
        break;
    default:
        printf("Invalid grade");
}

if-else vs switch

Both if-else and switch statements are used for decision-making in C, but they have distinct use cases:

Here's an example showing when to use if-else vs switch:

// Using if-else
int x = 10;
if (x > 0 && x < 20) {
    printf("x is between 1 and 19");
} else {
    printf("x is out of range");
}

// Using switch
int choice = 2;
switch (choice) {
    case 1:
        printf("Choice is 1");
        break;
    case 2:
        printf("Choice is 2");
        break;
    default:
        printf("Invalid choice");
}

C Loops

Loops in C are used to execute a block of code repeatedly, based on a given condition. They are essential for iterating over data, performing repetitive tasks, and managing control flow efficiently. There are three main types of loops in C:

C do-while Loop

The do-while loop is similar to the while loop, but it guarantees that the code block is executed at least once, regardless of the condition. The condition is evaluated after the code block has executed. The syntax is:

do {
    // Code to execute
} while (condition);

Here’s an example of a do-while loop:

int count = 1;

do {
    printf("Count: %d\\n", count);
    count++;
} while (count <= 5);

In this example, the do-while loop will print the count from 1 to 5.

C while Loop

The while loop is a control structure that repeatedly executes a block of code as long as the given condition is true. The condition is checked before executing the code block, making it possible for the loop to skip execution if the condition is initially false. The syntax is:

while (condition) {
    // Code to execute
}

Here’s an example of a while loop:

int count = 1;

while (count <= 5) {
    printf("Count: %d\\n", count);
    count++;
}

In this example, the while loop will print the count from 1 to 5.

C for Loop

The for loop is a control structure used for iterating over a block of code a fixed number of times. It is often preferred when the number of iterations is known beforehand. The syntax is:

for (initialization; condition; increment/decrement) {
    // Code to execute
}

Here’s an example of a for loop:

for (int i = 1; i <= 5; i++) {
    printf("Iteration: %d\\n", i);
}

In this example, the for loop will iterate 5 times, printing the iteration number each time.

Nested Loops in C

Nested Loops in C are loops inside other loops. The inner loop is executed completely each time the outer loop runs once. They are particularly useful for working with multi-dimensional data structures like 2D arrays. Here’s a simple example of nested for loops:

for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        printf("i = %d, j = %d\\n", i, j);
    }
}

In this example, the inner loop will iterate three times for each single iteration of the outer loop, resulting in 9 total iterations.

Infinite Loop in C

An Infinite Loop continues to execute indefinitely because its condition always evaluates to true. Use infinite loops with caution, typically in scenarios where the loop is controlled by an external condition or a break statement. Here’s an example using a while loop:

while (1) {
    // Code will run indefinitely
    printf("This is an infinite loop\\n");
}

In this example, the condition is always 1 (true), so the loop will run forever unless interrupted manually or using a break.

C break

The break statement is used to immediately exit a loop, regardless of the iteration count or loop condition. It's often used when a specific condition is met within a loop. Here's an example:

for (int i = 1; i <= 10; i++) {
    if (i == 5) {
        break;  // Exit the loop when i is 5
    }
    printf("i = %d\\n", i);
}

This loop will print numbers 1 to 4, then exit when i is 5.

C continue

The continue statement skips the current iteration of a loop and continues with the next iteration. It’s useful when certain conditions need to be bypassed. Here's an example:

for (int i = 1; i <= 5; i++) {
    if (i == 3) {
        continue;  // Skip the iteration when i is 3
    }
    printf("i = %d\n", i);
}

This loop will print numbers 1, 2, 4, and 5, skipping 3.

C goto

The goto statement provides a way to jump to a labeled section of code. Although it’s not commonly used due to readability concerns, it can be helpful in specific scenarios where structured loops are insufficient. Here’s an example:

int number = 0;

start:
    printf("Enter a positive number (0 to exit): ");
    scanf("%d", &number);

    if (number < 0) {
        printf("Negative number, try again!\n");
        goto start;  // Jump back to the label 'start'
    }

In this example, if a negative number is entered, the program jumps back to the start label to retry.

Type Casting in C

Type Casting in C is the conversion of a variable from one data type to another. It can be implicit (automatically done by the compiler) or explicit (manually specified by the programmer). Here’s an example of explicit type casting:

int a = 10;
int b = 3;
float result;

result = (float)a / b;  // Casting 'a' to float
printf("Result = %.2f\n", result);

In this example, a is cast to a float before division, ensuring a floating-point result.

Function in C


What is a Function in C?

A Function in C is a block of code that performs a specific task. Functions help organize code, promote reusability, and make programs easier to read and maintain. Each function has a unique name, which is used to invoke it. Here’s a basic structure of a C function:

// Function declaration
void greet();

// Main function
int main() {
    greet();  // Function call
    return 0;
}

// Function definition
void greet() {
    printf("Hello, World!\n");
}

In this example, the function greet() is defined to print a message and is called from the main() function.

Function Call: Call by Value & Call by Reference

Functions in C can be called using two methods: Call by Value and Call by Reference.

Call by Value

In Call by Value, a copy of the actual parameter is passed to the function. Changes made to the parameter inside the function do not affect the original value. Here's an example:

void increment(int x) {
    x = x + 1;  // Change is local to this function
}

int main() {
    int num = 10;
    increment(num);
    printf("Value of num: %d\n", num);  // Output: 10
    return 0;
}

In this example, num remains unchanged after calling increment(num) because only a copy was modified.

Call by Reference

In Call by Reference, the address of the actual parameter is passed to the function. Changes made inside the function affect the original parameter. Here's an example:

void increment(int *x) {
    *x = *x + 1;  // Change affects the original value
}

int main() {
    int num = 10;
    increment(&num);
    printf("Value of num: %d\n", num);  // Output: 11
    return 0;
}

In this example, num is incremented because its memory address was modified in the increment function.

Recursion in C

Recursion is when a function calls itself to solve a smaller instance of the problem. It is useful for problems that can be broken down into similar sub-problems, like calculating a factorial or solving the Fibonacci sequence. Here’s an example of a recursive function to calculate a factorial:

int factorial(int n) {
    if (n == 0) {
        return 1;  // Base case
    } else {
        return n * factorial(n - 1);  // Recursive call
    }
}

int main() {
    int number = 5;
    printf("Factorial of %d is %d\n", number, factorial(number));  // Output: 120
    return 0;
}

In this example, the function factorial() calls itself with a reduced value until the base case n == 0 is reached.

Storage Classes in C

Storage Classes define the scope, visibility, and lifetime of variables and functions in C. There are four primary storage classes:

1. Auto

The `auto` storage class is the default for local variables. It defines variables that are visible only within the block they are declared. Their lifetime is limited to the block scope.

void function() {
    auto int localVar = 10;  // Local to this block
}

2. Static

The `static` storage class retains a variable's value even after the block ends. It allows data persistence within functions. Here’s an example:

void counter() {
    static int count = 0;  // Static variable retains its value
    count++;
    printf("Count: %d\\n", count);
}

int main() {
    counter();  // Output: Count: 1
    counter();  // Output: Count: 2
    return 0;
}

3. Extern

The `extern` storage class is used to declare a global variable or function that is defined in another file or at a later point in the program. It tells the compiler that the variable exists but is defined elsewhere.

extern int globalVar;  // Declared, defined elsewhere

4. Register

The `register` storage class hints that the variable will be heavily used, and requests the compiler to store it in a CPU register for faster access. However, this is just a suggestion and may not be honored by the compiler.

void process() {
    register int counter = 0;  // Request to store in CPU register
}

Pointers in C


A Pointer in C is a variable that stores the memory address of another variable. Pointers are powerful and allow direct manipulation of memory, enabling tasks like dynamic memory allocation and creating complex data structures. Here’s a basic example:

int main() {
    int num = 10;
    int *ptr = #  // 'ptr' stores the address of 'num'
    printf("Value of num: %d\\n", num);
    printf("Address of num: %p\\n", ptr);
    printf("Value at address stored in ptr: %d\\n", *ptr);  // Dereferencing
    return 0;
}

In this example, `ptr` is a pointer that holds the address of `num`. The `*` operator is used to dereference the pointer, accessing the value stored at that address.

Pointer to Pointer

A Pointer to Pointer is a pointer that stores the address of another pointer. This is often used for handling multi-level data structures. Here’s an example:

int main() {
    int num = 20;
    int *ptr = #      // Pointer to 'num'
    int **ptr2 = &ptr;    // Pointer to pointer 'ptr'
    
    printf("Value of num: %d\\n", num);
    printf("Value using ptr: %d\\n", *ptr);
    printf("Value using ptr2: %d\\n", **ptr2);  // Double dereferencing
    return 0;
}

In this example, `ptr2` is a pointer to the pointer `ptr`. Using `**ptr2` allows access to the value of `num`.

Pointer Arithmetic

Pointer Arithmetic involves operations like incrementing, decrementing, and adding integers to pointers. These operations are based on the data type size. Here's an example demonstrating basic pointer arithmetic:

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int *ptr = arr;  // Points to the first element of 'arr'

    printf("Value at ptr: %d\\n", *ptr);       // Output: 10
    ptr++;  // Move to the next integer (4 bytes ahead)
    printf("Value at ptr after increment: %d\\n", *ptr);  // Output: 20
    ptr += 2;  // Move two integers ahead
    printf("Value at ptr after += 2: %d\\n", *ptr);  // Output: 40
    return 0;
}

In this example, `ptr++` increases the pointer to point to the next integer, adjusting the address by the size of an `int` (typically 4 bytes).

Dangling Pointers in C

A Dangling Pointer occurs when a pointer still references a memory location that has been freed or is no longer valid. Accessing such a pointer can cause undefined behavior. Here’s an example:

int* createDanglingPointer() {
    int x = 42;
    return &x;  // Returning address of local variable (Dangerous)
}

int main() {
    int *danglingPtr = createDanglingPointer();
    // 'x' no longer exists, danglingPtr is now a dangling pointer
    printf("Dangling Pointer Value: %d\\n", *danglingPtr);  // Undefined behavior
    return 0;
}

In this example, `danglingPtr` points to a local variable `x` that goes out of scope when `createDanglingPointer` returns, leaving `danglingPtr` dangling.

sizeof() Operator in C

The `sizeof()` operator in C is used to determine the size (in bytes) of a data type or variable. This is useful for understanding memory requirements and managing dynamic memory allocation. Here’s a basic usage:

int main() {
    int num;
    double d;
    char c;

    printf("Size of int: %zu bytes\\n", sizeof(num));    // Typically 4 bytes
    printf("Size of double: %zu bytes\\n", sizeof(d));  // Typically 8 bytes
    printf("Size of char: %zu byte\\n", sizeof(c));     // Typically 1 byte
    printf("Size of int array[10]: %zu bytes\\n", sizeof(int[10]));  // 40 bytes if int is 4 bytes
    return 0;
}

The `sizeof()` operator returns the number of bytes occupied by the specified data type or variable.

const Pointer in C

A const pointer in C can be of two types:

  1. Pointer to a const value: The value being pointed to cannot be modified.
  2. const pointer: The pointer itself cannot be reassigned to point elsewhere.
int main() {
    int x = 10;
    int y = 20;

    // Pointer to a const value
    const int *ptr1 = &x;
    // *ptr1 = 15;  // Error: cannot modify the value of x through ptr1
    ptr1 = &y;    // OK: can change the address ptr1 points to

    // Const pointer
    int *const ptr2 = &x;
    *ptr2 = 15;   // OK: can modify the value of x through ptr2
    // ptr2 = &y;  // Error: cannot change the address ptr2 points to
    return 0;
}

In the example above, `const int *ptr1` prevents modifying the value, while `int *const ptr2` prevents changing the address.

void Pointer in C

A void pointer (or generic pointer) in C can point to any data type, but it cannot be directly dereferenced without type casting. Here's an example:

int main() {
int x = 42;
char y = 'A';
void *ptr;

ptr = &x;  // void pointer pointing to an integer
printf("Value of x: %d\\n", *(int *)ptr);  // Type casting to dereference

ptr = &y;  // void pointer pointing to a char
printf("Value of y: %c\\n", *(char *)ptr);  // Type casting to dereference
return 0;
}

The void pointer is versatile, but requires explicit type casting to access the value it points to.

Dereferencing a Pointer in C

Dereferencing a pointer means accessing the value stored at the address it points to using the * operator. Here’s an example:

int main() {
int num = 100;
int *ptr = #

printf("Address of num: %p\\n", ptr);
printf("Value of num using pointer: %d\\n", *ptr);  // Dereferencing ptr
return 0;
}

In the example, *ptr is used to access the value at the address stored in ptr.

Null Pointer in C

A null pointer is a special pointer that doesn't point to any valid memory location. It is used as a sentinel value to indicate that the pointer is not intended to reference any object or memory. Example:

int main() {
int *ptr = NULL;  // Initialize with NULL

if (ptr == NULL) {
    printf("Pointer is null, no memory assigned.\\n");
} else {
    printf("Pointer is not null.\\n");
}
return 0;
}

The NULL macro is defined in <stddef.h> and is used to initialize or check if a pointer is null.

Function Pointer in C

A Function Pointer is a pointer that points to the address of a function. It can be used to call functions dynamically. Here's an example:

void display() {
    printf("Hello, World!\\n");
}

int main() {
    void (*funcPtr)() = display;  // Function pointer pointing to 'display'
    funcPtr();  // Calling the function using the pointer
    return 0;
}

Here, funcPtr is a pointer to the display function. It can be used to invoke display dynamically.

Function Pointer as Argument in C

A function pointer can also be passed as an argument to another function, enabling callback mechanisms. Here’s an example:

void greet() {
    printf("Greetings!\\n");
}

void execute(void (*func)()) {
    func();  // Calling the function via pointer
}

int main() {
    execute(greet);  // Passing function pointer to another function
    return 0;
}

In this example, execute takes a function pointer as an argument and invokes it, allowing dynamic function calls.

Dynamic Memory in C


In C, dynamic memory management allows you to allocate memory at runtime, instead of compile time. This is useful when you don’t know the size of the data in advance. The standard library provides functions to manage dynamic memory: malloc, calloc, realloc, and free.

malloc() in C

The malloc (Memory Allocation) function allocates a specified number of bytes of memory and returns a pointer to the allocated memory. The memory is uninitialized, meaning it contains garbage values. Here's an example:

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

int main() {
    int *ptr;
    int n = 5;

    // Allocating memory for 5 integers
    ptr = (int *)malloc(n * sizeof(int));

    // Check if memory has been successfully allocated
    if (ptr == NULL) {
        printf("Memory not allocated.\\n");
        exit(0);
    } else {
        printf("Memory successfully allocated using malloc.\\n");

        // Initialize and display memory content
        for (int i = 0; i < n; ++i) {
            ptr[i] = i + 1;
            printf("%d ", ptr[i]);
        }
    }
    free(ptr);  // Freeing the allocated memory
    return 0;
}

In this example, malloc is used to allocate memory for 5 integers. Always check if memory allocation is successful before using it.

calloc() in C

The calloc (Contiguous Allocation) function also allocates memory but initializes all bits to zero. It requires two parameters: the number of elements and the size of each element. Here's an example:

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

int main() {
    int *ptr;
    int n = 5;

    // Allocating memory for 5 integers using calloc
    ptr = (int *)calloc(n, sizeof(int));

    // Check if memory has been successfully allocated
    if (ptr == NULL) {
        printf("Memory not allocated.\\n");
        exit(0);
    } else {
        printf("Memory successfully allocated using calloc.\\n");

        // Displaying memory content (initialized to 0)
        for (int i = 0; i < n; ++i) {
            printf("%d ", ptr[i]);
        }
    }
    free(ptr);  // Freeing the allocated memory
    return 0;
}

In this example, calloc allocates memory for 5 integers, and each element is initialized to 0.

realloc() in C

The realloc function is used to resize a previously allocated memory block without losing the data. It takes two parameters: a pointer to the existing memory and the new size. Here's an example:

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

int main() {
    int *ptr;
    int n = 5;

    // Allocating memory for 5 integers
    ptr = (int *)malloc(n * sizeof(int));

    // Check if memory has been successfully allocated
    if (ptr == NULL) {
        printf("Memory not allocated.\\n");
        exit(0);
    }

    // Resizing the allocated memory to 10 integers
    n = 10;
    ptr = (int *)realloc(ptr, n * sizeof(int));

    if (ptr == NULL) {
        printf("Memory reallocation failed.\\n");
        exit(0);
    } else {
        printf("Memory successfully reallocated using realloc.\\n");

        // Initialize and display memory content
        for (int i = 0; i < n; ++i) {
            ptr[i] = i + 1;
            printf("%d ", ptr[i]);
        }
    }
    free(ptr);  // Freeing the allocated memory
    return 0;
}

In the example, realloc changes the memory size from 5 to 10 integers. If resizing is successful, the data from the original memory is preserved.

free() in C

The free function is used to deallocate dynamically allocated memory. It takes a pointer as an argument and frees the memory it points to, making it available for other uses. Always free memory after it's no longer needed to avoid memory leaks. Here's an example:

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

int main() {
    int *ptr = (int *)malloc(5 * sizeof(int));

    if (ptr == NULL) {
        printf("Memory not allocated.\\n");
        exit(0);
    }

    // Using the allocated memory
    for (int i = 0; i < 5; ++i) {
        ptr[i] = i + 1;
        printf("%d ", ptr[i]);
    }

    free(ptr);  // Freeing the allocated memory
    printf("\\nMemory has been successfully freed.\\n");
    return 0;
}

In this example, the free function releases the allocated memory after its usage.

Strings in C


In C, a string is an array of characters terminated by a null character \0. Strings can be manipulated using character arrays and standard string functions provided by the C Standard Library.

gets() & puts() in C

The gets() function is used to read a string from the user, while puts() is used to display a string. Here's an example:

#include < stdio.h>

int main() {
    char str[100];

    // Reading a string using gets()
    printf("Enter a string: ");
    gets(str);  // Warning: Use of gets() is unsafe, prefer fgets()

    // Displaying the string using puts()
    printf("You entered: ");
    puts(str);

    return 0;
}

In this example, gets() reads a line of text from the user, and puts() prints it back. Note that gets() is unsafe due to potential buffer overflow issues; consider using fgets() for safer input handling.

String Functions in C

The C Standard Library provides various functions to perform operations on strings, such as calculating length, copying, concatenating, comparing, and manipulating case.

strlen() in C

The strlen() function returns the length of a string (excluding the null character). Here's an example:

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

int main() {
    char str[] = "Hello, World!";
    int length = strlen(str);

    printf("The length of the string is: %d\\n", length);
    return 0;
}

strcpy() in C

The strcpy() function copies one string to another. Here's an example:

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

int main() {
    char source[] = "Hello, C!";
    char destination[50];

    // Copying source to destination
    strcpy(destination, source);

    printf("Copied string: %s\\n", destination);
    return 0;
}

strcat() in C

The strcat() function concatenates (appends) one string to the end of another. Here's an example:

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

int main() {
    char greeting[50] = "Hello";
    char name[] = ", C Programmer!";

    // Concatenating name to greeting
    strcat(greeting, name);

    printf("Greeting: %s\\n", greeting);
    return 0;
}

strcmp() in C

The strcmp() function compares two strings. It returns 0 if they are equal, a positive value if the first string is greater, and a negative value if the second string is greater. Here's an example:

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

int main() {
    char str1[] = "Hello";
    char str2[] = "World";

    int result = strcmp(str1, str2);

    if (result == 0) {
        printf("The strings are equal.\\n");
    } else {
        printf("The strings are not equal.\\n");
    }
    return 0;
}

strrev() in C

The strrev() function reverses a string. Here's an example:

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

int main() {
    char str[] = "Hello";

    // Reversing the string
    strrev(str);

    printf("Reversed string: %s\\n", str);
    return 0;
}

strlwr() in C

The strlwr() function converts a string to lowercase. Here's an example:

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

int main() {
    char str[] = "HELLO WORLD";

    // Converting to lowercase
    strlwr(str);

    printf("Lowercase string: %s\\n", str);
    return 0;
}

strupr() in C

The strupr() function converts a string to uppercase. Here's an example:

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

int main() {
    char str[] = "hello world";

    // Converting to uppercase
    strupr(str);

    printf("Uppercase string: %s\\n", str);
    return 0;
}

strstr() in C

The strstr() function finds the first occurrence of a substring in a string. Here's an example:

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

int main() {
    char str[] = "Hello, World!";
    char *subStr = "World";

    // Finding the substring
    char *position = strstr(str, subStr);

    if (position != NULL) {
        printf("Substring found at position: %ld\\n", position - str);
    } else {
        printf("Substring not found.\\n");
    }
    return 0;
}

Math in C


The C Standard Library provides a set of mathematical functions defined in the <math.h> header file. These functions allow you to perform common mathematical operations like trigonometric calculations, power functions, rounding, and more.

Common Math Functions in C

Below is a list of commonly used math functions in C:

Square Root: sqrt()

The sqrt() function calculates the square root of a number. Here's an example:

#include < stdio.h>
#include < math.h>

int main() {
    double num = 25.0;
    double result = sqrt(num);

    printf("Square root of %.2f is %.2f\\n", num, result);
    return 0;
}

Power: pow()

The pow() function is used to raise a number to a specified power. Here's an example:

#include < stdio.h>
#include < math.h>

int main() {
    double base = 5.0;
    double exponent = 3.0;
    double result = pow(base, exponent);

    printf("%.2f raised to the power of %.2f is %.2f\\n", base, exponent, result);
    return 0;
}

Absolute Value: abs()

The abs() function returns the absolute value of an integer. Here's an example:

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

int main() {
    int num = -42;
    int result = abs(num);

    printf("The absolute value of %d is %d\\n", num, result);
    return 0;
}

Rounding: ceil() & floor()

The ceil() function rounds a number up to the nearest integer, while floor() rounds it down. Here's an example:

#include < stdio.h>
#include < math.h>

int main() {
    double num = 4.56;

    printf("Ceil of %.2f is %.2f\\n", num, ceil(num));
    printf("Floor of %.2f is %.2f\\n", num, floor(num));
    return 0;
}

Trigonometric Functions: sin(), cos(), tan()

Trigonometric functions like sin(), cos(), and tan() are used to calculate the sine, cosine, and tangent of an angle (in radians). Here's an example:

#include < stdio.h>
#include < math.h>

int main() {
    double angle = 0.5;  // Angle in radians

    printf("sin(%.2f) = %.2f\\n", angle, sin(angle));
    printf("cos(%.2f) = %.2f\\n", angle, cos(angle));
    printf("tan(%.2f) = %.2f\\n", angle, tan(angle));
    return 0;
}

Logarithmic & Exponential Functions: log(), exp()

The log() function returns the natural logarithm of a number, while exp() calculates the exponential value (e^x). Here's an example:

#include < stdio.h>
#include < math.h>

int main() {
    double num = 2.0;

    printf("Natural log of %.2f is %.2f\\n", num, log(num));
    printf("Exponential of %.2f is %.2f\\n", num, exp(num));
    return 0;
}

Rounding to Nearest: round()

The round() function rounds a floating-point number to the nearest integer. Here's an example:

#include < stdio.h>
#include < math.h>

int main() {
    double num = 5.67;

    printf("Rounded value of %.2f is %.0f\\n", num, round(num));
    return 0;
}

C Structures and Unions


In C programming, Structures and Unions are user-defined data types that allow the grouping of different data types under a single name. Structures help manage related data, while unions are used to save memory by sharing space between members.

C Structure

A Structure in C is a collection of variables of different data types grouped together under a single name. Each variable in a structure is known as a member.

#include < stdio.h>

// Defining a structure
struct Person {
    char name[50];
    int age;
    float salary;
};

int main() {
    // Creating a structure variable
    struct Person person1;

    // Assigning values to members
    person1.age = 25;
    person1.salary = 55000.50;
    strcpy(person1.name, "John Doe");

    // Displaying structure values
    printf("Name: %s\\n", person1.name);
    printf("Age: %d\\n", person1.age);
    printf("Salary: %.2f\\n", person1.salary);

    return 0;
}

typedef in C

The typedef keyword is used in C to give a new name (alias) to an existing data type. It can simplify the usage of complex types like structures.

#include < stdio.h>

// Using typedef to create an alias for struct
typedef struct {
    char name[50];
    int age;
} Student;

int main() {
    // Using the alias to declare a variable
    Student student1;

    student1.age = 20;
    strcpy(student1.name, "Alice");

    printf("Student Name: %s\\n", student1.name);
    printf("Student Age: %d\\n", student1.age);

    return 0;
}

C Array of Structures

You can create an array of structures to store data for multiple entities. Each element of the array is a structure.

#include < stdio.h>

struct Book {
    char title[100];
    char author[50];
    int pages;
};

int main() {
    // Array of structures
    struct Book library[3] = {
        {"Book One", "Author A", 300},
        {"Book Two", "Author B", 250},
        {"Book Three", "Author C", 400}
    };

    for (int i = 0; i < 3; i++) {
        printf("Title: %s, Author: %s, Pages: %d\\n", 
                library[i].title, library[i].author, library[i].pages);
    }

    return 0;
}

C Nested Structure

A Nested Structure in C is a structure that contains another structure as a member. This is useful for creating complex data types.

#include < stdio.h>

struct Address {
    char city[50];
    int zipCode;
};

struct Employee {
    char name[50];
    struct Address address;
};

int main() {
    struct Employee emp;

    strcpy(emp.name, "Bob");
    strcpy(emp.address.city, "New York");
    emp.address.zipCode = 10001;

    printf("Employee Name: %s\\n", emp.name);
    printf("City: %s, Zip Code: %d\\n", emp.address.city, emp.address.zipCode);

    return 0;
}

Structure Padding in C

Structure Padding refers to the extra bytes added by the compiler between structure members to align data in memory. This improves access speed but can increase the memory size of a structure.

#include < stdio.h>

struct Padded {
    char a;     // 1 byte
    int b;      // 4 bytes
    char c;     // 1 byte
}; // Padding may be added for alignment

int main() {
    printf("Size of Padded structure: %lu bytes\\n", sizeof(struct Padded));
    return 0;
}

C Union

A Union in C is similar to a structure, but it allows storing different data types in the same memory location. The memory size of a union is equal to the size of its largest member.

#include < stdio.h>

union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;

    data.i = 10;
    printf("data.i: %d\\n", data.i);

    data.f = 220.5;
    printf("data.f: %.2f\\n", data.f);

    strcpy(data.str, "Hello");
    printf("data.str: %s\\n", data.str);

    // Note: Assigning a new value overwrites the previous one in a union.
    return 0;
}

C File Handling


Introduction to C File Handling

In C, file handling refers to the process of reading from and writing to files. C provides a set of built-in functions to perform these operations. These functions are defined in the stdio.h library and allow interaction with files in various modes (read, write, append, etc.).

C fprintf() and fscanf()

The fprintf() function is used to write formatted data to a file, similar to printf() for console output. The fscanf() function is used to read formatted data from a file, similar to scanf().

#include < stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w");  // Open file for writing

    if (file == NULL) {
        printf("Unable to open file\\n");
        return 1;
    }

    // Writing data to the file
    fprintf(file, "Hello, World!\\n");
    fprintf(file, "This is a test file.\\n");

    fclose(file);  // Close the file

    // Reading from the file
    file = fopen("example.txt", "r");  // Open file for reading
    if (file == NULL) {
        printf("Unable to open file\\n");
        return 1;
    }

    char buffer[100];
    while (fscanf(file, "%[^\n]%*c", buffer) != EOF) {
        printf("%s\\n", buffer);
    }

    fclose(file);  // Close the file
    return 0;
}

C fputc() and fgetc()

The fputc() function is used to write a single character to a file, while the fgetc() function is used to read a single character from a file.

#include < stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w");  // Open file for writing

    if (file == NULL) {
        printf("Unable to open file\\n");
        return 1;
    }

    // Writing a single character to the file
    fputc('A', file);
    fputc('\\n', file);
    fputc('B', file);
    fputc('\\n', file);

    fclose(file);  // Close the file

    // Reading a single character from the file
    file = fopen("example.txt", "r");  // Open file for reading
    if (file == NULL) {
        printf("Unable to open file\\n");
        return 1;
    }

    char ch;
    while ((ch = fgetc(file)) != EOF) {
        printf("%c", ch);
    }

    fclose(file);  // Close the file
    return 0;
}

C fputs() and fgets()

The fputs() function is used to write a string to a file, while fgets() is used to read a line from a file.

#include < stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w");  // Open file for writing

    if (file == NULL) {
        printf("Unable to open file\\n");
        return 1;
    }

    // Writing a string to the file
    fputs("Hello, File Handling in C!\\n", file);
    fputs("We are learning file operations.\\n", file);

    fclose(file);  // Close the file

    // Reading from the file
    file = fopen("example.txt", "r");  // Open file for reading
    if (file == NULL) {
        printf("Unable to open file\\n");
        return 1;
    }

    char buffer[100];
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        printf("%s", buffer);
    }

    fclose(file);  // Close the file
    return 0;
}

C fseek(), rewind(), ftell()

The fseek() function is used to move the file pointer to a specific position in a file. The rewind() function is used to reset the file pointer to the beginning of the file. The ftell() function returns the current position of the file pointer.

#include < stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w+");  // Open file for both reading and writing

    if (file == NULL) {
        printf("Unable to open file\\n");
        return 1;
    }

    // Writing to the file
    fputs("This is a test for fseek, ftell and rewind.\\n", file);

    // Moving the file pointer and checking the position
    fseek(file, 10, SEEK_SET);
    long position = ftell(file);
    printf("Current file position: %ld\\n", position);

    // Rewinding the file
    rewind(file);
    printf("File pointer moved to the beginning after rewind.\\n");

    fclose(file);  // Close the file
    return 0;
}

C Preprocessor


The C preprocessor is a tool that processes the source code before compilation. It performs various tasks such as macro substitution, file inclusion, conditional compilation, and more. Preprocessor directives are lines of code that begin with the # symbol and tell the preprocessor to perform specific actions before the actual compilation of the program.

C Macros

Macros are preprocessor directives used to define code snippets that can be reused throughout the program. These are defined using the #define directive.

#include < stdio.h>

#define PI 3.14
#define SQUARE(x) ((x) * (x))

int main() {
    printf("Value of PI: %f\\n", PI);
    printf("Square of 5: %d\\n", SQUARE(5));

    return 0;
}

C #include

The #include directive is used to include external files, such as header files, into your program. This is commonly used to include standard libraries or custom header files.

#include < stdio.h >   // Standard Library

int main() {
    printf("Hello, World!\\n");
    return 0;
}

C #define

The #define directive is used to define a macro or constant in C. It replaces all occurrences of the macro with the specified value or code during preprocessing.

#define MAX 100

int main() {
    int arr[MAX];
    printf("Array size: %d\\n", MAX);
    return 0;
}

C #undef

The #undef directive is used to undefine a previously defined macro. This is useful if you want to redefine a macro or prevent its use.

#define MAX 100
#undef MAX  // Undefining the macro

int main() {
    // Uncommenting the next line will cause an error because MAX is undefined
    // printf("Max value: %d\\n", MAX);
    return 0;
}

C #ifdef and #ifndef

The #ifdef (if defined) and #ifndef (if not defined) directives are used for conditional compilation. These allow you to include or exclude code depending on whether a macro is defined.

#define DEBUG

#ifdef DEBUG
    printf("Debugging is enabled.\\n");
#endif

#ifndef RELEASE
    printf("Release version is not defined.\\n");
#endif

int main() {
    return 0;
}

C #if and #else

The #if and #else directives allow conditional compilation based on expressions. If the expression evaluates to true, the code block following #if is included. Otherwise, the code after #else is included.

#define VALUE 10

#if VALUE > 5
    printf("VALUE is greater than 5.\\n");
#else
    printf("VALUE is not greater than 5.\\n");
#endif

int main() {
    return 0;
}

C #error

The #error directive is used to generate a compile-time error with a custom error message. This can be useful for checking certain conditions during compilation.

#ifndef MAX
#error "MAX is not defined!"
#endif

int main() {
    return 0;
}

C #pragma

The #pragma directive is used to provide additional instructions to the compiler. The functionality of #pragma is compiler-dependent, and it is often used to control optimization, warning messages, or other compiler-specific behavior.

#pragma warning(disable: 4996)  // Disable specific warning

int main() {
    char str[50];
    printf("Enter some text: ");
    gets(str);  // Deprecated function, but warning is disabled
    return 0;
}

C Command Line Arguments


Command-line arguments are values passed to a program when it is executed from the command line or terminal. These arguments are useful for passing data such as filenames, options, or configuration settings without modifying the program’s code. In C, the main() function can accept command-line arguments via its parameters int argc and char *argv[].

Command Line Arguments Structure

The main() function in C can be defined as:


int main(int argc, char *argv[])

- argc (Argument Count): It stores the number of command-line arguments passed to the program, including the program name. - argv (Argument Vector): It is an array of strings that holds the actual arguments passed to the program.

#include < stdio.h>      
int main(int argc, char *argv[]) {
    printf("Number of arguments: %d\\n", argc);
    
    for (int i = 0; i < argc; i++) {
        printf("Argument %d: %s\\n", i, argv[i]);
    }
    
    return 0;
}

Example of Command Line Arguments

Here’s an example of how command-line arguments work. If you compile the above program and run it from the command line like so:

    ./program arg1 arg2 arg3

The output will be:

Number of arguments: 4 
Argument 0: ./program
Argument 1: arg1
Argument 2: arg2
Argument 3: arg3

Practical Use Case of Command Line Arguments

Command-line arguments are frequently used in real-world applications. For example, they can be used to pass file names to a program, specify flags for debugging or mode selection, or configure settings dynamically. Let’s look at a more practical example of a program that takes two numbers and adds them:

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

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Please provide two numbers as arguments!\\n");
        return 1;
    }
    
    int num1 = atoi(argv[1]);  // Convert argument to integer
    int num2 = atoi(argv[2]);  // Convert argument to integer
    
    printf("Sum: %d\\n", num1 + num2);
    
    return 0;
}

In this example, the program adds two numbers passed as arguments. You can run the program like this:

        ./add_numbers 5 10
    
The output will be:
        Sum: 15