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
- Readability: Enumeration constants are more descriptive than plain integers.
- Maintainability: Changes in the underlying values of enumeration constants can be managed in one place.
- Error Reduction: Limiting the possible values a variable can take reduces the likelihood of invalid values.
Disadvantages of Using Enumerations
- Limited Type Checking: Enumerations are essentially integers, so the type checking is limited.
- 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
- Object-like Macros
- 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
- Code Reusability: Macros allow you to define reusable code snippets that can be used throughout your program.
- Improved Readability: Macros provide meaningful names for constants and code snippets, making the code easier to understand.
- Efficiency: Macros are expanded inline, eliminating the overhead of function calls.
Disadvantages of Using Macros
- Lack of Type Safety: Macros do not perform type checking, which can lead to unexpected behavior if used incorrectly.
- Debugging Difficulty: Debugging macros can be challenging because errors in macros are not always reported clearly.
- 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); }