C++, like C, enables you to specify structure members that occupy a particular number of bits. This can be handy for creating a data structure that corresponds, say, to a register on some hardware device. The field type should be an integral or enumeration type (enumerations are discussed later in this chapter), and a colon followed by a number indicates the actual number of bits to be used. You can use unnamed fields to provide spacing. Each member is termed a bit field. Here’s an example:
struct torgle_register
{
unsigned int SN : 4; // 4 bits for SN value
unsigned int : 4; // 4 bits unused
bool goodIn : 1; // valid input (1 bit)
bool goodTorgle : 1; // successful torgling
};
You can initialize the fields in the usual manner, and you use standard structure notation to access bit fields:
torgle_register tr = { 14, true, false };
...
if (tr.goodIn) // if statement covered in Chapter 6
...
Bit fields are typically used in low-level programming. Often, using an integral type and the bitwise operators listed in Appendix E, “Other Operators,” provides an alternative approach.
A union is a data format that can hold different data types but only one type at a time. That is, whereas a structure can hold, say, an int
and a long
and a double
, a union can hold an int
or a long
or a double
. The syntax is like that for a structure, but the meaning is different. For example, consider the following declaration:
union one4all
{
int int_val;
long long_val;
double double_val;
};
You can use a one4all
variable to hold an int
, a long
, or a double
, just as long as you do so at different times:
one4all pail;
pail.int_val = 15; // store an int
cout << pail.int_val;
pail.double_val = 1.38; // store a double, int value is lost
cout << pail.double_val;
Thus, pail
can serve as an int
variable on one occasion and as a double
variable at another time. The member name identifies the capacity in which the variable is acting. Because a union holds only one value at a time, it has to have space enough to hold its largest member. Hence, the size of the union is the size of its largest member.
One use for a union is to save space when a data item can use two or more formats but never simultaneously. For example, suppose you manage a mixed inventory of widgets, some of which have an integer ID, and some of which have a string ID. In that case, you could use the following:
struct widget
{
char brand[20];
int type;
union id // format depends on widget type
{
long id_num; // type 1 widgets
char id_char[20]; // other widgets
} id_val;
};
...
widget prize;
...
if (prize.type == 1) // if-else statement (Chapter 6)
cin >> prize.id_val.id_num; // use member name to indicate mode
else
cin >> prize.id_val.id_char;
An anonymous union has no name; in essence, its members become variables that share the same address. Naturally, only one member can be current at a time:
struct widget
{
char brand[20];
int type;
union // anonymous union
{
long id_num; // type 1 widgets
char id_char[20]; // other widgets
};
};
...
widget prize;
...
if (prize.type == 1)
cin >> prize.id_num;
else
cin >> prize.id_char;
Because the union is anonymous, id_num
and id_char
are treated as two members of prize
that share the same address. The need for an intermediate identifier id_val
is eliminated. It is up to the programmer to keep track of which choice is active.
Unions often (but not exclusively) are used to save memory space. That may not seem that necessary in these days of gigabytes of RAM and terabytes of storage, but not all C++ programs are written for such systems. C++ also is used for embedded systems, such as the processors used to control a toaster oven, an MP3 player, or a Mars rover. In these applications space may be at a premium. Also unions often are used when working with operating systems or hardware data structures.
The C++ enum
facility provides an alternative to const
for creating symbolic constants. It also lets you define new types but in a fairly restricted fashion. The syntax for enum
resembles structure syntax. For example, consider the following statement:
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
This statement does two things:
• It makes spectrum
the name of a new type; spectrum
is termed an enumeration, much as a struct
variable is called a structure.
• It establishes red
, orange
, yellow
, and so on, as symbolic constants for the integer values 0–7. These constants are called enumerators.
By default, enumerators are assigned integer values starting with 0 for the first enumerator, 1 for the second enumerator, and so forth. You can override the default by explicitly assigning integer values. You’ll see how later in this chapter.
You can use an enumeration name to declare a variable of the enumeration type:
spectrum band; // band a variable of type spectrum
An enumeration variable has some special properties, which we’ll examine now.
The only valid values that you can assign to an enumeration variable without a type cast are the enumerator values used in defining the type. Thus, we have the following:
band = blue; // valid, blue is an enumerator
band = 2000; // invalid, 2000 not an enumerator
Thus, a spectrum
variable is limited to just eight possible values. Some compilers issue a compiler error if you attempt to assign an invalid value, whereas others issue a warning. For maximum portability, you should regard assigning a non-enum
value to an enum
variable as an error.
Only the assignment operator is defined for enumerations. In particular, arithmetic operations are not defined:
band = orange; // valid
++band; // not valid, ++ discussed in Chapter 5
band = orange + red; // not valid, but a little tricky
...
However, some implementations do not honor this restriction. That can make it possible to violate the type limits. For example, if band
has the value ultraviolet
, or 7
, then ++band
, if valid, increments band
to 8
, which is not a valid value for a spectrum
type. Again, for maximum portability, you should adopt the stricter limitations.
Enumerators are of integer type and can be promoted to type int
, but int
types are not converted automatically to the enumeration type:
int color = blue; // valid, spectrum type promoted to int
band = 3; // invalid, int not converted to spectrum
color = 3 + red; // valid, red converted to int
...
Note that in this example, even though 3
corresponds to the enumerator green
, assigning 3
to band
is a type error. But assigning green
to band
is fine because they are both type spectrum
. Again, some implementations do not enforce this restriction. In the expression 3 + red
, addition isn’t defined for enumerators. However, red
is converted to type int
, and the result is type int
. Because of the conversion from enumeration to int
in this situation, you can use enumerations in arithmetic expressions to combine them with ordinary integers, even though arithmetic isn’t defined for enumerations themselves.
The earlier example
band = orange + red; // not valid, but a little tricky
fails for a somewhat involved reason. It is true that the +
operator is not defined for enumerators. But it is also true that enumerators are converted to integers when used in arithmetic expressions, so the expression orange + red
gets converted to 1 + 0
, which is a valid expression. But it is of type int
and hence cannot be assigned to the type spectrum
variable band
.
You can assign an int
value to an enum
, provided that the value is valid and that you use an explicit type cast:
band = spectrum(3); // typecast 3 to type spectrum
What if you try to type cast an inappropriate value? The result is undefined, meaning that the attempt won’t be flagged as an error but that you can’t rely on the value of the result:
band = spectrum(40003); // undefined
(See the section “Value Ranges for Enumerations,” later in this chapter for a discussion of what values are and are not appropriate.)
As you can see, the rules governing enumerations are fairly restrictive. In practice, enumerations are used more often as a way of defining related symbolic constants than as a means of defining new types. For example, you might use an enumeration to define symbolic constants for a switch
statement. (See Chapter 6, “Branching Statements and Logical Operators,” for an example.) If you plan to use just the constants and not create variables of the enumeration type, you can omit an enumeration type name, as in this example:
enum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
18.218.157.34