2.9.1. Constants, switch, enum, and typedef
Constants, switch statements, enumerated types, and typedef are features of the
C language that are useful for creating more readable code and maintainable
code. Constants, enumerated types, and typedefs are used to define aliases for
literal values and types in programs. Switch statements can be used in place
of some chaining if-else if
statements.
C Constants
A constant is an alias for a C literal value. Constants are used in place of the literal value to make code more readable and easier to modify. In C, constants are defined outside of a function body using the following syntax:
#define const_name (literal_value)
Here is an example of a partial program that defines and uses
three constants (N
, PI
, and NAME
):
#include <stdio.h>
#include <stdlib.h>
#define N (20) // N: alias for the literal value 20
#define PI (3.14) // PI: alias for the literal value 3.14
#define NAME ("Sarita") // NAME: alias for the string literal "Sarita"
int main(void) {
int array[N]; // an array of 20 ints
int *d_arr, i;
double area, circ, radius;
radius = 12.3;
area = PI*radius*radius;
circ = 2*PI*radius;
d_arr = malloc(sizeof(int)*N);
if(d_arr == NULL) {
printf("Sorry, %s, malloc failed!\n", NAME);
exit(1);
}
for(i=0; i < N; i++) {
array[i] = i;
d_arr[i] = i*2;
}
...
Using constants makes the code more readable (in an
expression, PI
has more meaning than 3.14
).
Using constants also makes code easier to modify.
For example, to change the bounds of the arrays and the
precision of the value of pi in the above program, the
programmer only needs to change their
constant definitions and recompile; all the code that uses the
constant will use their new values. For example:
#define N (50) // redefine N from 20 to 50
#define PI (3.14159) // redefine PI to higher precision
int main(void) {
int array[N]; // now allocates an array of size 50
...
area = PI*radius*radius; // now uses 3.14159 for PI
d_arr = malloc(sizeof(int)*N); // now mallocs array of 50 ints
...
for(i=0; i < N; i++) { // now iterates over 50 elements
...
It is important to remember that constants are not lvalues—they are aliases for literal values of C types. As a result, their values cannot be changed at runtime like those of variables. The following code, for example, causes a compilation error:
#define N 20
int main(void) {
...
N = 50; // compilation error: `20 = 50` is not valid C
Switch statements
The C switch
statement can be used in place of some, but not all, chaining
if
-else if
code sequences. While switch
doesn’t provide any additional
expressive power to the C programming language, it often yields more concise
code branching sequences. It may also allow the compiler to produce branching
code that executes more efficiently than equivalent chaining if
-else if
code.
The C syntax for a switch
statement looks like:
switch (<expression>) {
case <literal value 1>:
<statements>;
break; // breaks out of switch statement body
case <literal value 2>:
<statements>;
break; // breaks out of switch statement body
...
default: // default label is optional
<statements>;
}
A switch statement is executed as follows:
-
The
expression
evaluates first. -
Next, the
switch
searches for acase
literal value that matches the value of the expression. -
Upon finding a matching
case
literal, it begins executing the statements that immediately follow it. -
If no matching
case
is found, it will begin executing the statements in thedefault
label if one is present. -
Otherwise, no statements in the body of the
switch
statement get executed.
A few rules about switch
statements:
-
The value associated with each
case
must be a literal value — it cannot be an expression. The original expression gets matched for equality only with the literal values associated with eachcase
. -
Reaching a
break
statement stops the execution of all remaining statements inside the body of theswitch
statement. That is,break
breaks out of the body of theswitch
statement and continues execution with the next statement after the entireswitch
block. -
The
case
statement with a matching value marks the starting point into the sequence of C statements that will be executed — execution jumps to a location inside theswitch
body to start executing code. Thus, if there is nobreak
statement at the end of a particularcase
, then the statements under the subsequentcase
statements execute in order until either abreak
statement is executed or the end of the body of theswitch
statement is reached. -
The
default
label is optional. If present, it must be at the end.
Here’s an example program with a switch
statement:
#include <stdio.h>
int main(void) {
int num, new_num = 0;
printf("enter a number between 6 and 9: ");
scanf("%d", &num);
switch(num) {
case 6:
new_num = num + 1;
break;
case 7:
new_num = num;
break;
case 8:
new_num = num - 1;
break;
case 9:
new_num = num + 2;
break;
default:
printf("Hey, %d is not between 6 and 9\n", num);
}
printf("num %d new_num %d\n", num, new_num);
return 0;
}
Here are some example runs of this code:
./a.out enter a number between 6 and 9: 9 num 9 new_num 11 ./a.out enter a number between 6 and 9: 6 num 6 new_num 7 ./a.out enter a number between 6 and 9: 12 Hey, 12 is not between 6 and 9 num 12 new_num 0
Enumerated Types
An enumerated type (enum
) is a way to define a group of related integer
constants. Often switch statements and enumerated types are used together.
The enumerated type should be defined outside of a function body, using
the following syntax (enum
is a keyword in C):
enum type_name {
CONST_1_NAME,
CONST_2_NAME,
...
CONST_N_NAME
};
Note that the constant fields are specified by a comma separated list of names and are not explicitly given values. By default, the first constant in the list is assigned the value 0, the second the value 1, and so on.
Below is an example of defining an enumerated type for the days of the week:
enum days_of_week {
MON,
TUES,
WED,
THURS,
FRI
};
A variable of an enumerated type value is declared using the type name
enum type_name
, and the constant values it defines can be used in
expressions. For example:
enum days_of_week day;
day = THURS;
if (day > WED) {
printf("The weekend is arriving soon!\n");
}
An enumerated types is similar to defining a set of constants using #define
like the following:
#define MON 0
#define TUES 1
#define WED 2
#define THURS 3
#define FRI 4
The constant values in the enumerated type can be used in a similar way as
constants are used to make a program easier to read and code easier to update.
However, an enumerated type has an advantage of grouping together a set of
related integer constants together. It also is a type definition so variables
and parameters can be declared to be an enumerated type, whereas constants are
aliases for literal values. In addition, in enumerated types the specific
values of each constant is implicitly assigned in sequence starting at 0
, so
the programmer doesn’t have to specify each constant’s value.
Another nice feature of enumerated types is that
it is easy to add or remove constants from the set without
having to change all their values. For example, if the user wanted to add
Saturday and Sunday to the set of days and maintain the relative
ordering of the days, they can add them to the enumerated type
definition without having to explicitly redefine the values of the
others as they would need to do with #define
constant definitions:
enum days_of_week {
SUN, // SUN will now be 0
MON, // MON will now be 1, and so on
TUES,
WED,
THURS,
FRI,
SAT
};
Although values are implicitly assigned to the constants an enumerated type,
the programmer can also assign specific values to them
using = val
syntax. For example, if the programmer wanted the values of the
days of the week to start at 1 instead of 0, they could do the following:
enum days_of_week {
SUN = 1, // start the sequence at 1
MON, // this is 2 (next value after 1)
TUES, // this is 3, and so on
WED,
THURS,
FRI,
SAT
};
Because an enumerated type defines aliases for a set of int
literal values,
the value of an enumerated type prints out as its int
value and not
as the name of the alias. For example, given the above definition of the
enum days_of_week
, the following prints 3
not the string "TUES"
:
enum days_of_week day;
day = TUES;
printf("Today is %d\n", day);
Enumerated types are often used in combination with switch statements
as shown in the example code below.
The example also shows a switch statement with several cases
associated with the same set of statements, and a case statement
that does not have a break
before the next case statement
(when val
is FRI
two printf
statements are executed
before a break
is encountered, and when val
is MON
or WED
only one of the printf
statements is executed
before the break
):
// an int because we are using scanf to assign its value
int val;
printf("enter a value between %d and %d: ", SUN, SAT);
scanf("%d", &val);
switch (val) {
case FRI:
printf("Orchestra practice today\n");
case MON:
case WED:
printf("PSYCH 101 and CS 231 today\n");
break;
case TUES:
case THURS:
printf("Math 311 and HIST 140 today\n");
break;
case SAT:
printf("Day off!\n");
break;
case SUN:
printf("Do weekly pre-readings\n");
break;
default:
printf("Error: %d is not a valid day\n", val);
};
typedef
C provides a way to define a new type that is an alias for an existing
type using the keyword typedef
. Once defined, variables
can be declared using this new alias for the type. This feature
is commonly used to make the program more readable and to use shorter type
names, often for structs and enumerated types.
The following is the format for defining a new type with typedef
:
typedef existing_type_name new_type_alias_name;
Here is an example partial program that uses typedefs:
#define MAXNAME (30)
#define MAXCLASS (40)
enum class_year {
FIRST = 1,
SECOND,
JUNIOR,
SENIOR,
POSTGRAD
};
// classYr is an alias for enum class_year
typedef enum class_year classYr;
struct studentT {
char name[MAXNAME];
classYr year; // use classYr type alias for field type
float gpa;
};
// studentT is an alias for struct studentT
typedef struct studentT studentT;
// ull is an alias for unsigned long long
typedef unsigned long long ull;
int main(void) {
// declare variables using typedef type names
studentT class[MAXCLASS];
classYr yr;
ull num;
num = 123456789;
yr = JUNIOR;
strcpy(class[0].name, "Sarita");
class[0].year = SENIOR;
class[0].gpa = 3.75;
...
Because typedef is often used with structs, C provides syntax for combining
a typedef and a struct definition together by prefixing the struct
definition with typedef
and listing the name of the type alias
after the closing }
in the struct definition. For example, the
following defines both a struct type struct studentT
and an alias for
the type named studentT
:
typedef struct studentT {
char name[MAXNAME];
classYr year; // use classYr type alias for field type
float gpa;
} studentT;
This definition is equivalent to doing the typedef separately, after the the struct definition as in the previous example.