CodingBison

Sometimes, we want a variable to have the same value throughput the program. C allows us to define such variable that maintain their "constantness" throughout the life of the program. We can do this using a handful of ways: (a) using const (short for constant) keyword, (b) using compile-time macros, and (c) using enums (short for enumerations).

It is an unwritten convention to keep names of const-qualified variables, macros, and enum values in upper cases, thereby indicating that they are not regular variables. This convention, of course, is not a language requirement. If you do not follow this convention, then you are bound to irritate your fellow programmers, when they read or review your program!

Constants

One way to define constant values is to use the "const" keyword. All we have to do is to put this keyword before the storage type when defining the variable. Thus, we can define a constant integer variable as: "const int TEN = 10". Once defined, changing the value of this constant would be an error and the compiler would remind you of that!

With that, let us write a simple program that does addition and multiplication using constants. Here we define TEN and FIVE as constants. When run, the output of this program is "Sum is: 15, Product is : 50".

 #include <stdio.h>

 int main () {
     const int TEN = 10; 
     const int FIVE = 5;

     int var_sum, var_product;

     var_sum = TEN + FIVE;
     var_product = TEN * FIVE;
     printf("Sum is: %d, Product is : %d\n", var_sum, var_product);
     return 0;
 }

Macros

Another way to define constants is to have macros using the "# define" semantics. Thus, to define a constant for a painting name, Mona Lisa, we can do this: "#define MONA_LISA 10".

Macros work by doing a simple substitution during compilation stage; to be accurate, macros are parsed just before compilation. This means that if a program contains the macro MONA_LISA, then the parser will simply replace all occurrences of MONA_LISA with the value 10. Since macros are constants, it would also be a compilation error to assign "MONA_LISA = 20" after its value has been assigned initially.

Let us modify our earlier program of addition and multiplication and now make use of macros. The output for this program (provided below) is same as that of the earlier program.

 #include <stdio.h>

 #define  TEN    10
 #define  FIVE   5

 int main () {
     int var_sum, var_product;

     var_sum = TEN + FIVE;
     var_product = TEN * FIVE;
     printf("Sum is: %d, Product is : %d\n", var_sum, var_product);
     return 0;
 }

Extra care must be taken when writing non-trivial macros since otherwise a simple replacement may yield unintended consequences. Let us show an example of this erroneous behavior with an incorrect usage of macros. To demonstrate this behavior, the following program replaces TEN with "6 + 4" instead of "10".

 #include <stdio.h>

 #define TEN 4 + 6 
 #define FOUR 4 

 int main () {
     int sum, product;

     sum = TEN + FOUR;
     product = TEN * FOUR; 
     printf("Sum is: %d, Product is : %d \n", sum, product);
     return 0;
 }

When we run the above program, the output is: "Sum is: 15, Product is: 26". The sum looks alright but the product is clearly incorrect. What has happened is that the compiler replaced "var_product = TEN * FIVE" with "var_product = 6 + 4 * 5". The expression "6 + 4 * 5" evaluates to "6 + 20" (or 26) because multiplication has higher precedence than addition and hence it is performed before addition.

One way out of this mess is to use parenthesis. Thus, defining TEN as "#define TEN (4+6)" would avoid this problem. In any case, we should restrict using macros to simpler cases.

Enums

Yet another way to define a set of closely related constants is to use an enumeration (or enum for short). If we have a set of variables that are closely related, then we can use one enum to define all of them in one shot. C treats enums values as integers.

Let us use a simple program to understand the behavior of enums. This program (provided below) defines an enum, painters, that holds enumeration for several painters.

 #include <stdio.h>

 #define FIRST_NAME_VALUE 10 

 enum painters {
     VINCI = FIRST_NAME_VALUE,
     GOGH, 
     PICASSO,
     MONET,
 }; 

 int main () {
     enum painters value_of_vinci = VINCI;
     int value_of_monet = MONET;

     printf("Vinci: %d, Gogh: %d, Picasso: %d, Monet: %d.\n", 
         VINCI, GOGH, PICASSO, MONET);
     return 0;
 }

The above program defines two variables, value_of_vinci and value_of_monet, and assigns values to them from the enum. Since enums are integers, we can keep the storage size as either "enum painters" or "int". Both would work!

The output of above program is: "Vinci: 10, Gogh: 11, Picasso: 12, Monet: 13.". The enum assigns FIRST_NAME_VALUE to the first element, vinci, and then sequentially keeps assigning the next integer value to the rest.

If we were to define the FIRST_NAME_VALUE as 0 or to not initialize the "vinci" element at all, then the values would start with zero and the output would have been: "Vinci: 0, Gogh: 1, Picasso: 2, Monet: 3.". If we do not need to associate these enums with a hard-coded value, then we should not bother assigning any specific value; the values (starting with 0) would be assigned automatically.

In fact, we can get more creative with enums!

If we were to define "FIRST_NAME_VALUE" as a negative value, let us say -3, then the values assigned would be "-3,-2,-1, 0". Like the case before, enum assigns the value of -3 to the first value and sequentially keeps assigning the next integer value to the rest.

Besides the first element, we can also assign values to other elements and if needed, to more than one elements. Thus, if we were to initialize PICASSO with 1, then the values would be {0,1,1,2}. This is because VINCI/GOGH would receive 0 and 1 and by the time the counter comes to picasso, the value is once again initialized to 1, so it would restart counting as 1 and 2 for PICASSO and MONET.

Clearly, initializing a middle value might make these values non-unique (because now, both gogh and picasso have a value of 1). If we are looking for unique value to these enums (which we probably are!), then we should avoid initializing enums in the middle.

Lastly, we can also use typedef to define a new type of type that can accept values only from the enum list. We rewrite the earlier example and use typedef to define painter as a new type. The output for this program is same as before, so we omit it.

 #include <stdio.h>

 #define FIRST_NAME_VALUE 10 

 typedef enum {
     VINCI = FIRST_NAME_VALUE,
     GOGH, 
     PICASSO,
     MONET,
 } painter; 

 int main () {
     painter value_of_monet = MONET;
     printf("Vinci: %d, Gogh: %d, Picasso: %d, Monet: %d.\n", 
         VINCI, GOGH, PICASSO, MONET);
     return 0;
 }




comments powered by Disqus