Additional Features of C

By Paribesh Sapkota

Enumerations in C

Enumerations (or enum in C) provide a way to define a set of named integer constants. They improve code readability by giving meaningful names to integer values and help in reducing errors by limiting the values a variable can take.

Defining an Enumeration

An enumeration is defined using the enum keyword, followed by a list of named integer constants enclosed in curly braces. By default, the enumeration constants start from 0 and increase by 1 for each subsequent constant.

#include <stdio.h>

// Defining an enumeration for days of the week
enum Days {
    SUNDAY,    // 0
    MONDAY,    // 1
    TUESDAY,   // 2
    WEDNESDAY, // 3
    THURSDAY,  // 4
    FRIDAY,    // 5
    SATURDAY   // 6
};

int main() {
    // Declaring a variable of type enum Days
    enum Days today;

    // Assigning a value to the enumeration variable
    today = WEDNESDAY;

    // Using the enumeration in a switch-case statement
    switch (today) {
        case SUNDAY:
            printf("Today is Sunday\n");
            break;
        case MONDAY:
            printf("Today is Monday\n");
            break;
        case TUESDAY:
            printf("Today is Tuesday\n");
            break;
        case WEDNESDAY:
            printf("Today is Wednesday\n");
            break;
        case THURSDAY:
            printf("Today is Thursday\n");
            break;
        case FRIDAY:
            printf("Today is Friday\n");
            break;
        case SATURDAY:
            printf("Today is Saturday\n");
            break;
        default:
            printf("Invalid day\n");
    }

    return 0;
}

Specifying Values for Enumeration Constants

You can explicitly assign values to the enumeration constants. If the first constant is assigned a value, the subsequent constants will continue from that value, unless explicitly specified otherwise.

#include <stdio.h>

// Defining an enumeration with specific values
enum Months {
    JANUARY = 1,
    FEBRUARY = 2,
    MARCH = 3,
    APRIL = 4,
    MAY = 5,
    JUNE = 6,
    JULY = 7,
    AUGUST = 8,
    SEPTEMBER = 9,
    OCTOBER = 10,
    NOVEMBER = 11,
    DECEMBER = 12
};

int main() {
    // Declaring a variable of type enum Months
    enum Months currentMonth;

    // Assigning a value to the enumeration variable
    currentMonth = MAY;

    // Printing the value of the enumeration variable
    printf("Current month is %d\n", currentMonth); // Output: Current month is 5

    return 0;
}

Enumeration with Duplicate Values

Enumerations can also have multiple constants with the same value.

#include <stdio.h>

// Defining an enumeration with duplicate values
enum Colors {
    RED,    // 0
    GREEN,  // 1
    BLUE,   // 2
    YELLOW = 2, // Explicitly assigning 2
    BLACK  // 3
};

int main() {
    // Declaring variables of type enum Colors
    enum Colors color1, color2;

    // Assigning values to the enumeration variables
    color1 = BLUE;
    color2 = YELLOW;

    // Printing the values of the enumeration variables
    printf("Color1: %d\n", color1); // Output: Color1: 2
    printf("Color2: %d\n", color2); // Output: Color2: 2

    return 0;
}

Advantages of Using Enumerations

  1. Readability: Enumeration constants are more descriptive than plain integers.
  2. Maintainability: Changes in the underlying values of enumeration constants can be managed in one place.
  3. Error Reduction: Limiting the possible values a variable can take reduces the likelihood of invalid values.

Disadvantages of Using Enumerations

  1. Limited Type Checking: Enumerations are essentially integers, so the type checking is limited.
  2. Lack of Scoping: Enumeration constants are in the global scope, which can lead to name clashes.

C Macros

Macros in C are a feature of the preprocessor that allows you to define reusable code snippets or constants, which are replaced by their definitions before the program is compiled. Macros can make your code more readable and maintainable by allowing you to use meaningful names instead of hard-coded values or code fragments.

Types of Macros

  1. Object-like Macros
  2. Function-like Macros

Object-like Macros

Object-like macros are used to define constants. They are called “object-like” because they look like objects or variables in the code.

#include <stdio.h>

// Defining a constant macro
#define PI 3.14159
#define MAX_BUFFER_SIZE 1024

int main() {
    // Using the macro
    printf("Value of PI: %f\n", PI);
    printf("Max Buffer Size: %d\n", MAX_BUFFER_SIZE);

    return 0;
}

Function-like Macros

Function-like macros take arguments and are used to define code snippets that can be reused with different values.

#include <stdio.h>

// Defining a macro to calculate the area of a circle
#define CIRCLE_AREA(radius) (PI * (radius) * (radius))

// Defining a macro to get the maximum of two values
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main() {
    int r = 5;
    double area;
    int x = 10, y = 20, max_val;

    // Using the macros
    area = CIRCLE_AREA(r);
    printf("Area of the circle with radius %d is %.2f\n", r, area);

    max_val = MAX(x, y);
    printf("Maximum of %d and %d is %d\n", x, y, max_val);

    return 0;
}

Advantages of Using Macros

  1. Code Reusability: Macros allow you to define reusable code snippets that can be used throughout your program.
  2. Improved Readability: Macros provide meaningful names for constants and code snippets, making the code easier to understand.
  3. Efficiency: Macros are expanded inline, eliminating the overhead of function calls.

Disadvantages of Using Macros

  1. Lack of Type Safety: Macros do not perform type checking, which can lead to unexpected behavior if used incorrectly.
  2. Debugging Difficulty: Debugging macros can be challenging because errors in macros are not always reported clearly.
  3. No Scope: Macros do not have scope, meaning they are globally replaced throughout the code, which can lead to name clashes.

Preprocessor Directives

Macros are part of the preprocessor directives in C. Here are some additional useful preprocessor directives:

#include: Includes the contents of a file.

#include <stdio.h>
#include "myheader.h"

#undef: Undefines a macro.

#define TEMP 100
#undef TEMP

#ifdef and #ifndef: Checks if a macro is defined or not.

#define DEBUG

#ifdef DEBUG
printf("Debug mode is on\n");
#endif

#ifndef RELEASE
printf("Release mode is off\n");
#endif

#if, #elif, #else, and #endif: Conditional compilation based on macro values.

#define VERSION 2

#if VERSION == 1
printf("Version 1\n");
#elif VERSION == 2
printf("Version 2\n");
#else
printf("Unknown version\n");
#endif

Command Line Parameters in C

Command line parameters allow you to pass arguments to a C program when it is executed from the command line. These parameters are accessible within the program through the argc and argv parameters of the main function.

argc and argv

  • argc (Argument Count): An integer that represents the number of command line arguments passed to the program, including the program’s name.
  • argv (Argument Vector): An array of strings (character pointers) representing the individual arguments.
Basic Command Line Parameters

Let’s look at an example of a C program that uses command line parameters:

#include <stdio.h>

int main(int argc, char *argv[]) {
    // Print the number of arguments
    printf("Number of arguments: %d\n", argc);

    // Print each argument
    for (int i = 0; i < argc; i++) {
        printf("Argument %d: %s\n", i, argv[i]);
    }

    return 0;
}

Accessing Command Line Arguments

You can access and use the command line arguments in your program. For example, let’s create a program that adds two numbers passed as command line arguments:

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

int main(int argc, char *argv[]) {
    // Ensure there are enough arguments
    if (argc != 3) {
        printf("Usage: %s num1 num2\n", argv[0]);
        return 1;
    }

    // Convert arguments to integers
    int num1 = atoi(argv[1]);
    int num2 = atoi(argv[2]);

    // Calculate the sum
    int sum = num1 + num2;

    // Print the result
    printf("Sum: %d\n", sum);

    return 0;
}

Error Handling for Command Line Arguments

It’s important to handle errors when dealing with command line arguments to ensure the program behaves correctly when incorrect or insufficient arguments are provided. The previous example includes a basic check for the number of arguments.

Here’s an improved example that includes more robust error handling:

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

int main(int argc, char *argv[]) {
    // Ensure there are enough arguments
    if (argc != 3) {
        printf("Usage: %s num1 num2\n", argv[0]);
        return 1;
    }

    // Check if arguments are valid integers
    for (int i = 1; i < argc; i++) {
        for (char *p = argv[i]; *p != '\0'; p++) {
            if (!isdigit(*p) && !(*p == '-' && p == argv[i])) {
                printf("Invalid argument: %s\n", argv[i]);
                return 1;
            }
        }
    }

    // Convert arguments to integers
    int num1 = atoi(argv[1]);
    int num2 = atoi(argv[2]);

    // Calculate the sum
    int sum = num1 + num2;

    // Print the result
    printf("Sum: %d\n", sum);

    return 0;
}

This program:

  • Ensures exactly two additional arguments are provided.
  • Checks if each argument is a valid integer (considering negative numbers).
  • Converts the arguments to integers and calculates the sum.

Storage Classes in C

Storage classes in C define the scope, visibility, and lifetime of variables and functions. They determine where a variable or function can be accessed and how long it will remain in memory. There are four storage classes in C: auto, register, static, and extern.

1. auto Storage Class

  • Scope: Local to the block in which it is defined.
  • Lifetime: Exists only during the execution of the block in which it is defined.
  • Visibility: Visible only within the block.
  • Default: This is the default storage class for local variables.
    #include <stdio.h>
    
    void func() {
        auto int x = 10;  // Explicitly declaring auto storage class (optional)
        printf("Value of x: %d\n", x);
    }
    
    int main() {
        func();
        return 0;
    }
    

    register Storage Class

    • Scope: Local to the block in which it is defined.
    • Lifetime: Exists only during the execution of the block in which it is defined.
    • Visibility: Visible only within the block.
    • Special Characteristics: Suggests that the variable be stored in a CPU register instead of RAM for faster access. Limited to a small number of variables due to limited registers.
#include <stdio.h>

void func() {
    register int x = 10;  // Hint to store variable in a register
    printf("Value of x: %d\n", x);
}

int main() {
    func();
    return 0;
}

static Storage Class

  • Scope: Local to the block in which it is defined for local variables, but extends the lifetime to the entire program. For global variables, limits the scope to the file in which they are declared.
  • Lifetime: Exists for the lifetime of the program.
  • Visibility: Local static variables are visible only within the block. Global static variables are visible only within the file.
Local Static Variables
#include <stdio.h>

void func() {
    static int x = 0;  // Variable retains its value between function calls
    x++;
    printf("Value of x: %d\n", x);
}

int main() {
    func();  // Output: Value of x: 1
    func();  // Output: Value of x: 2
    return 0;
}
Global Static Variables
#include <stdio.h>

static int count = 0;  // Visible only within this file

void increment() {
    count++;
    printf("Count: %d\n", count);
}

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

extern Storage Class

  • Scope: Declares a global variable or function that is visible to all the program files. Used to provide the reference of a global variable or function that is defined in another file or later in the same file.
  • Lifetime: Exists for the lifetime of the program.
  • Visibility: Visible throughout all the files in a program where it is declared.
Declaration in Multiple Files

File1.c

#include <stdio.h>

int x = 10;  // Definition of global variable

void func1() {
    printf("Value of x in File1: %d\n", x);
}

File2.c

#include <stdio.h>

extern int x;  // Declaration of global variable

void func2() {
    printf("Value of x in File2: %d\n", x);
}

 

Important Questions
Comments
Discussion
0 Comments
  Loading . . .