수안이의 컴퓨터 연구실

  • Mainpage
  • About Me
  • Tags
  • Metapage
  • Notice
  • Location
  • Keywords
  • Guestbook
  • Admin
  • Write an Article
  • Total | 1694418
  • Today | 163
  • Yesterday | 606

1 Articles, Search for 'Data Types'

  1. 2007/07/27 More on Handling Basic Data Types
Programming/C++2007/07/27 17:50

More on Handling Basic Data Types

출처 : http://www.devarticles.com/c/a/cplusplu ··· types%2F

More on Handling Basic Data Types
(Page 1 of 13 )

Have you ever wanted to learn how basic types of C++ variables interact in complex situations? Ivor Horton explains this, and also describes some interesting features of C++. This article is from chapter 3 of Ivor Horton's Beginning ANSC C++ The Complete Language (Apress, 2004; ISBN 1590592271).

IN THIS CHAPTER, I expand on the types that I discussed in the previous chapter and explain how variables of the basic types interact in more complicated situations. I also introduce some new features of C++ and discuss some of the ways that these are used.

In this chapter you’ll learn

  • How expressions involving mixed types of data are evaluated
  • How you can convert a value from one basic type to another
  • What the bitwise operators are and how you can use them
  • How you can define a new type that limits variables to a fixed range of possible values
  • How you can define alternative names for existing data types
  • What the storage duration of a variable is and what determines it
  • What variable scope is and what its effects are
Mixed Expressions

You’re probably aware that your computer can only perform arithmetic operations on pairs of values of the same type. It can add two integers, and it can add two floating-point values, but it can’t directly add an integer to a floating-point value. The expression 2 + 7.5, for example, can’t be evaluated as it stands because 2 is an integer and 7.5 is a floating-point number.

The only way you can do this calculation is to convert one of the values into the same type as the other—typically, the integer value will be converted to its floating-point equivalent, so the expression will be calculated as 2.0 + 7.5. The same applies to mixed expressions in C++. Each binary arithmetic operation requires both operands to be of the same type; if they’re different, one of them must be converted to the type of the other. Consider the following sequence of statements:

  int value1 = 10;
  long value2 = 25L;
  float value3 = 30.0f;
  double result = value1 + value2 + value3;   // Mixed  
   calculation

The value of result is calculated as the sum of three different types of variables. For each add operation, one of the operands will be converted to the type of the other before the addition can be carried out. The conversion to be applied, and which operand it applies to, is determined by a set of rules that are checked in sequence until one is found that applies to the operation to be carried out. The preceding statement is actually executed with the following steps:

  1. value1 + value2 is calculated by converting value1 to type long before the addition. The result is also of type long, so the calculation is 10L + 25L = 35L.
  2. The next operation is 35L + value3. The previous result, 35L, is converted to type float before it’s added to value3. The result is of type float, so the operation will be 35.0f + 30.0f = 65.0f.
  3. Finally, the previous result is converted to type double and stored in result.

The rules for dealing with mixed expressions only come into play when the types of the operands for a binary operator are different. These rules, in the sequence in which they’re applied, are as follows:

  1. If either operand is of type long double, the other is converted to long double.
  2. If either operand is of type double, the other is converted to double.
  3. If either operand is of type float, the other is converted to float.
  4. Any operand of type char, signed char, unsigned char, short, or unsigned short is converted to type int, as long as type int can represent all the values of the original operand type. Otherwise, the operand is converted to type unsigned int.
  5. An enumeration type is converted to the first of int, unsigned int, long, or unsigned long that accommodates the range of the enumerators.
  6. If either operand is of type unsigned long, the other is converted to unsigned long.
  7. If one operand is of type long and the other is of type unsigned int, then provided type long can represent all the values of an unsigned int, the unsigned int is converted to type long. Otherwise, both operands are converted to type unsigned long.
  8. If either operand is of type long, the other is converted to type long.

You haven’t seen enumeration types yet, but you’ll look at them little later in this chapter. They appear here so you have the complete set of rules. This all looks rather complicated, but it really isn’t. Some of the apparent complexity arises because the range of values for integer types can be implementation dependent, so the rules need to accommodate that. The compiler checks the rules in sequence until it finds one that applies. If the operands are the same type after applying that rule, then the operation is carried out. If not, another rule is sought.

The basic idea is very simple. With two operands of different types, the type with the lesser range of values is converted to the other. The formal rules roughly boil down to the following:

  1. If the operation involves two different floating-point types, the one with the lesser precision will be promoted to the other.
  2. If the operation involves an integer and a floating-point value, the integer will be promoted to the floating-point type.
  3. If the operation involves mixed integer types, the type with the more limited range will be promoted to the other.
  4. If the operation involves enumeration types, they’ll be converted to a suitable integer type.

The term conversion means an automatic conversion of one type to another. The term promotion generally means a conversion of a data value from a type with a lesser range to a type with a greater range. You’ll see shortly that you can convert explicitly from one data type to another. Such a conversion is referred to as a cast, and the action of explicitly converting a value to a different type is called casting.

Just because C++ supports expressions involving mixed types doesn’t mean it’s a good idea in general. The results are often not what you expect, especially if you mix signed and unsigned types, so you should avoid writing mixed expressions as far as possible.

Assignments and Different Types

If the type of an expression on the right of an assignment operator is different from that of the variable on the left, the result of evaluating the expression on the right side will automatically be converted to the type of the variable on the left before it’s stored. In many cases, you can lose information in this way. For example, suppose you have a floating-point value defined as

  double root = 1.732;

If you now write the statement

int value = root;

the conversion of the value of root to int will result in 1 being stored in value. A variable of type int can only store a whole number, so the fractional part of the value stored in root is discarded in the conversion to type int. You can even lose information with an assignment between different types of integers:

  long count = 60000;
  short value = count;

If short is 2 bytes and long is 4 bytes, the former doesn’t have the range to store the value of count, and an incorrect value will result.

Many compilers will detect these kinds of conversions and provide you with a warning message when they occur, but don’t rely on this. To prevent these kinds of problems, you should, as far as possible, avoid assigning a value of one type to a variable of a type with a lesser range of values. Where such an assignment is unavoidable, you can specify the conversion explicitly to demonstrate that it’s no accident and that you really meant to do it. Let’s see how that works.

Explicit Casts

With mixed arithmetic expressions involving the basic types, your compiler automatically arranges conversions of operands where necessary, but you can also force a conversion from one type to another by using an explicit cast.To cast the value of an expression to a given type, you write the cast in the following form:

static_cast<the type to convert to>(expression)

The keyword static_cast reflects the fact that the cast is checked statically—that is, when your program is compiled. Later, when you get to deal with classes, you’ll meet dynamic casts, where the conversion is checked dynamically—that is, when the program is executing. The effect of the cast is to convert the value that results from evaluating expression to the type that you specify between the angled brackets. The expression can be anything from a single variable to a complex expression involving lots of nested parentheses.

Here’s a specific example of the use of static_cast<>():

  double value1 = 10.5;
  double value2 = 15.5;
  int whole_number = static_cast (value1) + static_cast
  (value2);

The initializing value for the variable whole_number is the sum of the integral parts of value1 and value2, so they’re each explicitly cast to type int. The variable whole_number will therefore have the initial value 25. The casts do not affect the values stored in value1 and value2, which will remain as 10.5 and 15.5, respectively. The values 10 and 15 produced by the casts are just stored temporarily for use in the calculation and then discarded. Although both casts cause a loss of information in the calculation, the compiler will always assume that you know what you’re doing when you explicitly specify a cast.

In the situation that I referred to earlier relating to assignments with different types, you can always make it clear that you know the cast is necessary by making it explicit:

  int value = static_cast<int> (root);

Generally, the need for explicit casts should be rare, particularly with basic types of data. If you have to include a lot of explicit casts in your code, it’s often a sign that you could choose more suitable types for your variables. Still, there are circumstances when casting is necessary, so let’s look at a simple example of this situation.

More on Handling Basic Data Types - Try It Out: Explicit Casting
(Page 2 of 13 )

Suppose you need to be able to convert a length in yards (as a decimal value) to yards, feet, and inches (as integer values). You can put together a program to do this:

// Program 3.1 Using Explicit Casts
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main() {
  const long feet_per_yard = 3;
  const long inches_per_foot = 12;
  double yards = 0.0;           // Length as decimal yards
  long yds = 0;                 // Whole yards
  long ft = 0;                  // Whole feet
  long ins = 0;                 // Whole inches
  cout << "Enter a length in yards as a decimal: ";
  cin >> yards;
  // Get the length as yards, feet, and inches
  yds = static_cast<long>(yards);
  ft = static_cast<long>((yards - yds)
  *feet_per_yard);
  ins = static_cast<long>
          (yards * feet_per_yard *
       inches_per_foot)% inches_per_foot;
cout << endl
     << yards << " yards converts to "
     << yds   << " yards "
     << ft    << " feet "
     << ins   << " inches.";
cout << endl;
return 0;
}

Typical output from this program will be:

==========================================================

Enter a length in yards as a decimal: 2.75

2.75 yards converts to 2 yards 2 feet 3inches.

==========================================================

HOW IT WORKS

The first two statements in main() declare a couple of conversion constants that you’ll use:

  const long feet_per_yard = 3;
  const long inches_per_foot = 12;

You declare these variables as const to prevent them from being modified accidentally in the program, and you use the type long to be consistent with the other values. Although the type short would have been adequate to store these values, using it may actually increase (rather than decrease) the size of your program in the long run. This is because of additional, implicit conversions that may be necessary when using them in expressions with other integer types.

The next four declarations define the variables you’ll use in the calculation:

double yards = 0.0;                 // Length as decimal yards
long yds = 0;                         // Whole yards
long ft = 0;                           // Whole feet
long ins = 0;                         // Whole inches

You prompt for the required input and then read a value from the keyboard with these statements:

  cout << "Enter a length in yards as a decimal: ";
  cin >> yards;

The next statement computes the whole number of yards from the input value with an explicit cast:

  yds = static_cast (yards);

The cast to type long discards the fractional part of the value in yards and stores the integral result in yds. If you omit the explicit cast here, some compilers will compile the program without warning you that they’ve inserted the required conversion, even though there’s clearly a potential loss of data in this conversion operation. You should always write an explicit cast in such cases to indicate that you intend this to happen. If you leave it out, it’s not clear that you realized the need for the conversion and the potential loss of data.

You obtain the number of whole feet in the length with the following statement:

ft = static_cast ((yards – yds) * feet_per_yard);

You want the number of whole feet that aren’t contained in the whole yards, so you subtract the value in yds from yards. The compiler will arrange for the value in yds to be converted automatically to type double for the subtraction, and the result will be of type double as well. The value of feet_per_yard will then be converted automatically to double to allow the multiplication to take place, and finally your explicit cast will be applied to the result to convert it from type double to type long.

The final part of the calculation is to obtain the residual number of whole inches:

  ins = static_cast<long>
  (yards * feet_per_yard * inches_per_foot) %
   inches_per_foot;

This is done by calculating the total number of inches in the original length, converting this to type long with an explicit cast, and then getting the remainder after dividing by the number of inches in a foot.

Lastly, you output the results with the following statement:

cout << std::endl
     << yards << " yards converts to "
     << yds   << " yards"
     << ft    << "  feet "
     << ins   << "  inches.";



Old-Style Casts

Prior to the introduction of static_cast<>() (and the other casts, const_cast<>(), dynamic_cast<>(), and reinterpret_cast<>(), which I discuss later in the book) into C++, an explicit cast of the result of an expression to another type was written like this:

(the_type_to_convert_to)expression

The result of expression is cast to the type between the parentheses. For example, the statement to calculate ins in the previous example could be written like this:

ins = (long)(yards * feet_per_yard * inches_per_foot) %
  inches_per_foot;

Essentially, there are four different kinds of casts, and the old-style casting syntax covers them all. Because of this, code using the old-style casts is more prone to error— it isn’t always clear what you intended, and you may not get the result you expected. Although you’ll still see old-style casting used extensively (it’s still part of the language), I strongly recommend that you stick to using only the new casts in your code.

More on Pseudo-Random Number Generation

Now that you know about casting, you can make sure that you don’t run into difficulties with using a value returned by the rand()in an arithmetic expression. I noted in the last chapter that rand()returns values from 0 to RAND_MAX, and RAND_MAX could be defined as any positive int value up to the maximum in the range. Assuming type long has a greater range than type int, you can avoid any possible problems when you want to perform arithmetic with a random integer by casting the value that is returned by the function to type long, for example:

long even = 2*static_cast<long>(std::rand());

With the value from rand() as type long, the multiply operation will be carried out after converting the value 2 to the same type. Thus the result of the multiplication will always be within range. You can produce the same effect by defining the literal as type long:

long even = 2L* std::rand();

Because 2L is of type long, the compiler will arrange to cast the value that is returned by rand() to type long before executing the multiply operation.

You could use rand() to obtain random integers in a more limited range than 0 to RAND_MAX. For instance, suppose you wanted random integers from to 0 to 10 inclusive. You could generate that from the value returned by the rand() function:

const int limit = 11;
int random_value = static_cast<int>(
                         (limit*static_cast<long>
                   (std::rand()))/(RAND_MAX+1L));

What you’re effectively doing here is dividing the range 0 to RAND_MAX into limit segments, in which all the values returned by rand() within a given segment will result in one of the values in the range 0 to limit-1 inclusive. You do this by multiplying limit by the ratio rand()/(RAND_MAX+1L). You divide by RAND_MAX+1L rather than RAND_MAX to deal with the case in which rand() returns a value that is exactly RAND_MAX. If you were to divide by RAND_MAX, the result would be limit in this singular case, instead of limit-1. The constant 1L that you add to RAND_MAX is of type long, so RAND_MAX will be converted to type long too before the addition is carried out. As I’ve already said, RAND_MAX is defined to be the largest possible integer of type int with some implementations of rand(). In this case, you can’t add 1 and get a correct result without converting to type long first.

If you want your random values to be between 1 and some upper limit, rather than having a lower limit of 0, this is also very easy to arrange:

const int limit = 100;
int random_value = static_cast<int>(
                    1L+(limit*static_cast<long>(std::rand
                   ()))/(RAND_MAX+1L));

Here you use the same expression as you did previously to produce values from 0 to limit-1 inclusive, and you add 1 to produce values from 1 to limit.

As I said at the beginning, all this assumes that type long has a greater range than type int. If this isn’t the case and you need to generate random values outside the range of type int, your only recourse is to cast the value returned by rand() to a floating-point type and store the result of your calculations as floating-point. For example, to produce values in a range from 0 to limit, you could use the following statements:

const double limit = 11.0;
double random_value = limit*std::rand()/(RAND_MAX+1.0);

Because you’ve declared limit to be of type double, the compiler will promote the integer that’s returned by rand() to that type so you don’t need to insert an explicit cast.

More on Handling Basic Data Types - Finding Out About Types
(Page 3 of 13 )

I’ve mentioned several times that the number of bytes used for some types isn’t specified in the C++ standard and that this is therefore set by your compiler. It’s quite possible that you would want to know how many bytes particular types of variables will occupy in your compiler. You could hunt for this information in your compiler’s documentation, but you can also get the information programmatically by using the sizeof operator.

sizeof is a unary operator, so it takes a single operand. It will return an integer value that is a measure of the amount of memory occupied by a particular variable, or by a type. The value returned by sizeof is actually defined as a multiple of the size of type char, but because variables of type char occupy 1 byte, the value returned will be a measure of the number of bytes that the operand occupies.

To obtain the number of bytes occupied by variables of type type, you use the expression sizeof(type). You could therefore output the size of variables of type int with this statement:

std::cout << std::endl
              << "Size of type int is "
              << sizeof(int);             // Output the  
                                    size of type int

The expression sizeof(int) returns the size of anything declared as type int, and you can find out the size of any data type in this way. To get the size of long double values, you could write this:

std::cout << std::endl
          << "Size of type long double is "
          << sizeof(long double);  //Output the size of                  type long double            

You can also apply the sizeof operator to a particular variable, or even to an expression. In this case, the expression doesn’t have to be between parentheses, although you can include them if you wish. Here’s an example that will output the number of bytes occupied by the variable number:

long number = 999999999;
std::cout << std::endl
          << "Size of the variable number is "
          << sizeof number;         // Output the size of
                              a variable

You can treat the value returned by the sizeof operator as an integer, but in fact it’s of type size_t. This isn’t really a new type, though. The word size_t is defined in the standard header as a synonym for one of the fundamental integer types, usually as unsigned int. Because it’s a synonym and not a name for an entity in the standard library, the name is not within the std namespace, so you can use it as is. I can hear your next question already: “What’s the point of having a different type name here? Why not just make it unsigned int?”

The reason for specifying the type returned by the sizeof operator is that it builds in flexibility. The sizeof operator always returns a value of type size_t, which your compiler may well define to be unsigned int, but it doesn’t have to be so. It might be convenient for the developers of a C++ compiler for a particular hardware platform to define size_t to be equivalent to some other integral type, and they’re free to do so. It won’t affect your code, because your assumption is that its type is size_t. You’ll see how to define a synonym for an existing type for yourself later in this chapter. Incidentally, the t in size_t stands for “type,” so the name was chosen to indicate that it is a type for a size.

More on Handling Basic Data Types - Try It Out: Finding the Sizes of Data Types
(Page 4 of 13 )

You can easily put together a program to list the sizes of all the data types you have seen:

// Program 3.2 Finding the sizes of data types
#include <iostream>
using std::cout;
using std::endl;
int main() {
  // Output the sizes for integer types
  cout << endl
      << "Size of type char is "
      << sizeof(char);
  cout << endl
      << "Size of type short is "
      << sizeof(short);
  cout << endl
      << "Size of type int is "
      << sizeof(int);
  cout << endl
      << "Size of type long is "
      << sizeof(long);
  // Output the sizes for floating-point types
  cout << endl
      << "Size of type float is "
      << sizeof(float);
  cout << endl
      << "Size of type double is "
      << sizeof(double);
  cout << endl
      << "Size of type long double is "
      << sizeof(long double);
  cout << endl;
  return 0;
}

On my computer, this program produces the following output:

======================================================

Size of type char is 1
Size of type short is 2
Size of type int is 4
Size of type long is 4
Size of type float is 4
Size of type double is 8
Size of type long double is 8

======================================================

You could modify this example to use the sizeof operator to obtain the sizes of sample variables and expressions, too.


Finding the Limits

There are occasions when you may want to know more about a particular type than just its size. You might want to know what the upper and lower limits on the values it can hold are, for instance. The standard header called <limits> makes this kind of information available for all the standard data types. The information is provided through a class for each type, and because I haven’t discussed classes yet, the way this works won’t be obvious to you at this point. However, you’ll look at how you get the information using the facilities provided by the <limits> header, and I’ll leave the detail explanations until I cover classes specifically in Chapter 13.

Let’s look at an example. To display the maximum value you can store in a variable of type double, you could write this:

std::cout << std::endl
    << "Maximum value of type double is "
    << std::numeric_limits<double> ::max();

The expression std::numeric_limits ::max() produces the value you want. By putting different type names between the angled brackets, you can obtain the maximum values for other data types. You can also replace max() with min() to get the minimum value that can be stored, but the meaning of minimum is different for integer and floating-point types. For an integer type, min() results in the true minimum, which will be a negative number for a signed integer type. For a floating-point type, min() returns the minimum positive value that can be stored.

You can retrieve many other items of information about various types. The number of binary digits, for example, is returned by this expression:

std::numeric_limits<type_name>::digits

You just insert the type_name in which you’re interested between the angled brackets. For floating-point types, you’ll get the number of binary digits in the mantissa. For signed integer types, you’ll get the number of binary digits in the value—that is, excluding the sign bit.

To see this sort of thing in action, you can put together another little example to display the maximums and minimums for the numerical data types.


Try It Out: Finding Maximum and Minimum Values

Here’s the program code:

// Program 3.3 Finding maximum and minimum values or data   types
#include <limits>
#include <iostream>
using std::cout;
using std::endl;
using std::numeric_limits;
int main() {
cout << endl
     << "The range for type short is from "
    
<< numeric_limits<short>::min()
    
<< " to "
    
<< numeric_limits<short>::max();
cout << endl
    
<< "The range for type int is from "
    
<< numeric_limits<int>::min()
    
<< " to "
    
<< numeric_limits<int>::max();
cout << endl
    
<< "The range for type long is from "
    
<< numeric_limits<long>::min()
    
<< " to "
    
<< numeric_limits<long>::max();
cout << endl
    
<< "The range for type float is from "
    
<< numeric_limits<float>::min()
    
<< " to "
    
<< numeric_limits<float>::max();
cout << endl
    
<< "The range for type double is from "
    
<< numeric_limits<double>::min()
    
<< " to "
    
<< numeric_limits<double>::max();
cout << endl
    
<< "The range for type long double is from "
    
<< numeric_limits<long double>::min()
    
<< " to "
    
<< numeric_limits<long double>::max();
cout << endl;
return 0;
}

On my computer, this program produces the following output:

======================================================

The range for type short is from -32768 to 32767
The range for type int is from -2147483648 to 2147483647
The range for type long is from -2147483648 to 2147483647
The range for type float is from 1.17549e-038 to 3.40282e+038
The range for type double is from 2.22507e-308 to 1.79769e+308
The range for type long double is from 2.22507e-308 to1.79769e+308

=====================================================

HOW IT WORKS

This is a straightforward application of what you learned in the previous section. The values retrieved in the manner you’ve used in this example have a type and are therefore type-checked by the compiler when you use them.

Just so that you’re aware of them, the range limits for numeric data types are also defined by macros in headers that are inherited from C. The <cfloat> header defines symbols for floating-point limits and the <climits> header defines symbols relating to limits for integer types. For example, in the <climits> header, the symbol INT_MAX represents the maximum value of a value of type int, and SCHAR_MIN represents the minimum value of a signed char. There are type_MAX and type_MIN symbols for the other types too. However, these are just symbols that the compiler will replace in your code by the equivalent number as a literal. For this reason, it’s usually better to use the runtime_limits<> mechanism when you’re accessing limits for types at runtime.

Bitwise Operators

As their name suggests, the bitwise operators enable you to operate on an integer variable at the bit level. You can apply the bitwise operators to any type of integer, both signed and unsigned, including the type char. However, they’re usually applied to unsigned integer types.

A typical application for these operators is when you want to use individual bits in an integer variable to store information. An example of this would be flags, which is the term used to describe binary state indicators. You can use a single bit to store any value that has two states: on or off, male or female, true or false.

You can also use the bitwise operators to work with several items of information stored in a single variable. For instance, color values are often recorded as three 8-bit values for the intensities of the red, green, and blue components in the color. These are usually packed into 3 bytes of a 4-byte word. The fourth byte is not wasted either; it often contains a value that is a measure of the transparency of a color. Obviously, to work with individual color components, you need to be able to separate out the individual bytes from a word, and the bitwise operators are just the tool for this.

Let’s consider another example. Suppose that you need to record information about fonts. You might want to store information about the style and the size of each font, plus whether it’s bold or italic. You could pack all of this information into a 2-byte integer variable, as shown in Figure 3-1.


Figure 3-1.  Packing font data into 2 bytes

You could use 1 bit to record whether the font is italic—a 1 value signifies italic and 0 signifies normal. In the same way, another bit could be used to specify whether the font is bold. You could use a byte to select one of up to 256 different styles. With another five bits, you could record the point size up to 32. Thus, in one 16-bit word you have four separate pieces of data recorded. The bitwise operators provide you with the means of accessing and modifying the individual bits and groups of bits from an integer very easily so they provide you with the means of assembling and disassembling the 16-bit word.

The Bitwise Shift Operators

The bitwise shift operators shift the contents of an integer variable by a specified number of bits to the left or right. These are used in combination with the other bitwise operators to achieve the kind of operations I described previously. The >> operator shifts bits to the right, and the << operator shifts bits to the left. Bits that fall off either end of the variable are lost.

All the bitwise operations work with integers of any type, but you’ll use 16-bit words in this chapter’s examples, which should keep the illustrative diagrams simple. You can declare and initialize a variable called number with the statement

unsigned short number = 16387U;

As you saw in the last chapter, you should write unsigned literals with a letter U or u appended to the number.

You can shift the contents of this variable and store the result with the statement

unsigned short result = number << 2;  //Shift left two
     bit positions

The left operand of the shift operator is the value to be shifted and the right operand specifies the number of bit positions that the value is to be shifted by. Figure 3-2 shows the effect of the operation.


Figure 3-2.  Shift operations

As you can see from Figure 3-2, shifting the value 16,387 two positions to the left produces the value 12. The rather drastic change in the value is the result of losing the high order bit.

To shift the value to the right, you can write

result = number >> 2;     // Shift right two bit positions

This shifts the value 16,387 two positions to the right and produces the result 4,096. Shifting right two bits is effectively dividing the value by 4.

As long as bits aren’t lost, shifting n bits to the left is equivalent to multiplying the value by 2, n times. In other words, it’s equivalent to multiplying by 2n. Similarly, shifting right n bits is equivalent to dividing by 2n. But beware: As you saw with the left shift of the variable number, if significant bits are lost, the result is nothing like what you would expect. However, this is no different from the “real” multiply operation. If you multiplied the 2-byte number by 4 you would get the same result, so shifting left and multiplying are still equivalent. The incorrect answer arises because the value of the result of the multiplication is outside the range of a 2-byte integer.

When you want to modify the original value using a shift operation, you can do so by using an op= assignment operator. In this case, you would use the >>= or <<= operator, for eexample:

number >>= 2;       // Shift contents of number two 
        positions to the right

This is equivalent to

number = number >> 2; // Shift contents of number two positions to
             the right

You might imagine that confusion could arise between these shift operators and the insertion and extraction operators that you’ve been using for input and output. As far as the compiler is concerned, the meaning will generally be clear from the context. If it isn’t, in most cases the compiler will generate a message, but you do need to be careful. For example, if you want to output the result of shifting a variable number left by two bits, you could write

cout << (number << 2);

Here, the parentheses are essential. Without them, the compiler will interpret the shift operator as a stream insertion operator so you won’t get the result that you intended.

Shifting Signed Integers

You can apply the bitwise shift operators to both signed and unsigned integers. However, the effect of the right shift operator on signed integer types can vary between different systems, and it depends on your compiler’s implementation. In some cases, the right shift will introduce “0” bits at the left to fill the vacated bit positions. In other cases, the sign bit is propagated to the right, so “1” bits fill the vacated bit positions to the left.

The reason for propagating the sign bit, where this occurs, is to maintain consistency between a right shift and a divide operation. You can illustrate this with a variable of type char, just to show how it works. Suppose you define value to be of type signed char with the value –104 in decimal:

signed char value = -104;

Its binary value is 10011000. You can shift it two bits to the right with this operation:

value >>= 2;     // Result 11100110

The binary result when the sign is propagated is shown in the comment. Two 0s are shifted out at the right end, and because the sign bit is 1, further 1s are inserted on the left. The decimal value of the result is –26, which is the same as if you had divided by 4, as you would expect. With operations on unsigned integer types, of course, the sign bit isn’t propagated and 0s are inserted on the left.

As I said, what actually happens when you right-shift negative integers is implementation defined, so you must not rely on it working one way or the other. Because for the most part you’ll be using these operators for operating at the bit level—where maintaining the integrity of the bit pattern is important—you should always use unsigned integers to ensure that you avoid the high-order bit being propagated.

Logical Operations on Bit Patterns

The four bitwise operators that you can use to modify bits in an integer value are shown in Table 3-1.

Table 3-1. Bitwise Operators

Operator Description
~ This is the bitwise complement operator. This is a unary operator that will

invert the bits in its operand, so 1 becomes 0 and 0 becomes 1.
& This is the bitwise AND operator, which will AND the corresponding bits in its

operands. If the corresponding bits are both 1, then the resulting bit is 1.

Otherwise, it’s 0.
^ This is the bitwise exclusive OR operator, which will exclusive-OR the

corresponding bits in its operands. If the corresponding bits are different (that

is, one is 1 and the other is 0), then the resulting bit is 1. If the corresponding

bits are the same, the resulting bit is 0.
| This is the bitwise OR operator, which will OR the corresponding bits in its

operands. If either of the two corresponding bits is 1, then the result is 1. If both

bits are 0, then the result is 0.

The operators appear here in order of precedence, so the bitwise complement operator has the highest precedence in this set, and the bitwise OR operator the lowest. As you can see in the complete operator precedence table in Appendix D, the shift operators << and >> are of equal precedence, and they’re below the ~ operator but above the & operator.

If you haven’t come across operators like these before, you’re likely to be thinking, “Very interesting, but what are they for?” Let’s put them into some kind of context.

More on Handling Basic Data Types - Using the Bitwise AND
(Page 5 of 13 )

You’ll typically use the bitwise AND operator to select particular bits or groups of bits in an integer value. To see what this means, you can reuse the example presented at the beginning of this section, which used a 16-bit integer to store the characteristics of a font.

Suppose you want to declare and initialize a variable to specify a 12-point, italic, style 6 font—in fact, the very same one illustrated in Figure 3-1. In binary, the style will be 00000110, the italic bit will be 1, the bold bit will be 0, and the size will be 01100. Remembering that there’s an unused bit as well, you need to initialize the value of the font variable to the binary number 0000 0110 0100 1100.

Because groups of four bits correspond to a hexadecimal digit, the easiest way to do this is to specify the initial value in hexadecimal notation:

unsigned short font = 0x064C;      // Style 6, italic, 12
            point


NOTE When you set up bit patterns like this, hexadecimal notation is invariably more appropriate than using decimal values.

To use the size, you need to be able to extract it from the font variable; the bitwise AND operator will enable you to do this. Because bitwise AND only produces 1 bit when both bits are 1, you can define a value that will “select” the bits defining the size when you AND it with font. All you need to do is define a value that contains 1s in the bit positions that you’re interested in, and 0s in all the others. This kind of value is called a mask, and you can define such a mask with the statement

unsigned short size_mask = 0x1F;    // Mask is 0000 0000
        0001 1111 to select size

The five low-order bits of font represent its size, so you set these bits to 1. The remaining bits are 0, so they will be discarded. (Binary 0000 0000 0001 1111 translates to hexadecimal 1F.)

You can now extract the point size from font with the statement

unsigned short size = font & size_mask;

Where both corresponding bits are 1 in an & operation, the resultant bit is 1. Any other combination of bits results in 0. The values therefore combine like this:


font                   0000 0110 0100 1100
size_mask              0000 0000 0001 1111
font & size_mask       0000 0000 0000 1100

Showing the binary values in groups of four bits has no real significance other than making it easy to identify the hexadecimal equivalent; it also makes it easier to see how many bits there are in total. As you can see, the effect of the mask is to separate out the five rightmost bits, which represent the point size.

You could use the same mechanism to select out the style for the font, but you’ll also need to use a shift operator to move the style value to the right. You can define a mask to select the left eight bits as follows:

unsigned short style_mask = 0XFF00;   // Mask is 1111    
       1111 0000 0000 for style

You can then obtain the style value with the statement

unsigned short style = (font & style_mask) >> 8; // 
       Extract the style

The effect of this statement is


font                     0000 0110 0100 1100
style_mask               1111 1111 0000 0000
font & style_mask        0000 0110 0000 0000
(font & style_mask) >> 8 0000 0000 0000 0110

You should be able to see that you could just as easily isolate the bits indicating italic and bold by defining a mask for each, with the appropriate bit set to 1. Of course, you still need a way to test whether the resulting bit is 1 or 0, and you’ll see how to do that in the next chapter.

Another use for the bitwise AND operator is to turn bits off. Part of the effect you saw previously is that any bit that is 0 in a mask will produce 0 in the result. To turn the italic bit off, for example, and leave the rest unchanged, you just bitwise-AND the font variable with a mask that has the italic bit as 0 and all the other bits as 1. You’ll look at the code to do this in the context of the bitwise OR operator, for reasons that I’ll explain next.

Using the Bitwise OR

You can use the bitwise OR operator for setting single or multiple bits. Continuing with your manipulations of the font variable, it’s conceivable that you would want to set the italic and bold bits on demand. You can define masks to select these bits with the statements

unsigned short italic = 0X40U;     // Seventh bit from 
       the right
unsigned short bold = 0X20U;       // Sixth bit from the
       right

Now you can set the bold bit with the statement

font |= bold;                      // Set bold

The bits combine here as follows:


font                  0000 0110 0100 1100
bold                  0000 0000 0010 0000
font | bold           0000 0110 0110 1100

Now, the font variable specifies that the font it represents is bold as well as italic. Note that this operation will result in the bit being set, regardless of its previous state. If it was on before, it remains on.

You can also set multiple bits by ORing the masks together, so the following statement will set both the bold and the italic bit:

font |= bold | italic;           // Set bold and italic

It’s easy to fall into the trap of allowing language to make you select the wrong operator. Because you say “Set italic and bold” there’s a temptation to use the & operator, but this would be wrong. ANDing the two masks together would result in a value with all bits 0, so you wouldn’t change anything.

As I said at the end of the last section, you can use the & operator to turn bits off— you just need a mask that contains a 0 at the position of the bit you want to turn off and 1 everywhere else. However, this raises the issue of how you specify such a mask. If you want to specify it explicitly, you’ll need to know how many bytes there are in your variable—not exactly convenient if you want your program to be in any way portable. However, you can obtain the mask that you want by using the bitwise complement operator on the mask that you would normally use to turn the bit on. You can obtain the mask to turn bold off from the bold mask itself:


bold                      0000 0000 0010 0000
~bold                     1111 1111 1101 1111

The effect of the complement operator is that each bit in the original is flipped, 0 to 1 or 1 to 0. You should be able to see that this will produce the result you’re looking for, regardless of whether the bold variable occupies 2, 4, or 8 bytes.


NOTE The bitwise complement operator is sometimes called the NOT operator, because for every bit it operates on, what you get is not what you started with.


Thus, all you need to do when you want to turn bold off is to bitwise-AND the complement of the mask, bold, with the variable, font. The following statement will do it:

font &= ~bold;                // Turn bold off

You can also set multiple bits to 0 by combining several masks using the & operator, and then bitwise-ANDing the result with the variable you want to modify:

font &= ~bold & ~italic;     // Turn bold and italic off

This sets both the italic and bold bits to 0 in the font variable. Note that no parentheses are necessary here, because ~ has a higher precedence than &. However, if you’re ever uncertain about operator precedence, put parentheses in to express what you want. It certainly does no harm, and it really does good when they’re necessary.

More on Handling Basic Data Types - Using the Bitwise Exclusive OR
(Page 6 of 13 )

The bitwise exclusive OR operator is used much less frequently than the & and | operators, and there are few common examples of its use. An important application, though, arises in the context of graphics programming. One way of creating the illusion of motion on the screen is to draw an object, erase it, and then redraw it in a new position. This process needs to be repeated very rapidly if you are to get smooth animation, and the erasing is a critical part of it. You don’t want to erase and redraw the whole screen, as this is time consuming and the screen will flash. Ideally, you want to erase only the object or objects onscreen that you’re moving. You can do this and get reasonably smooth animation by using what is called exclusive OR mode.

Exclusive OR mode is based on the idea that once you’ve drawn an object on the screen in a given color, it will then disappear if you redraw it in the background color. This is illustrated by the sequence in Figure 3-3.


Figure 3-3.  Drawing in exclusive OR mode

When you draw an object on the screen in exclusive OR mode, the color automatically alternates between the color you’ve selected for the object and the background color each time you draw the object. The key to achieving this is the use of the bitwise exclusive OR operator to alternate the colors rapidly and automatically. It uses a characteristic of the exclusive OR operation, which is that if you choose your values suitably, you can arrange to flip between two different values by repeated exclusive-OR operations. That sounds complicated, so let’s see how it works by looking at a specific example.

Suppose you want to alternate between a foreground color (you’ll use red), and a background color (white). As I noted earlier, color is often represented by three 8-bit values, corresponding to the intensities for each of red, blue, and green, and stored in a single 4-byte integer. By altering the proportions of red, blue, and green in a color, you can get around 16 million different colors in the range from white to black and everything in between. A bright red would be 0xFF0000, where the red component is set to its maximum and the intensities of the other two components for green and blue are zero. In the same scheme, green would be 0xFF00 and blue would be 0xFF. White has equal, maximum components of red, blue, and green, so it would be 0xFFFFFF.

You can therefore define variables representing red and white with the statements

unsigned long red = 0XFF0000UL;     // Color red
unsigned long white = 0XFFFFFFUL;   // Color white – RGB
      all maximum

Next, you’ll create a mask that you can use to switch the color back and forth between red and white. You’ll also initialize the variable containing the drawing color to red:

unsigned long mask = red ^ white;  // Mask for switching
      colors
unsigned long draw_color = red;    // Drawing color

The variable mask is initialized to the bitwise exclusive OR of the colors that you want to alternate, so it will be


red                           1111 1111 0000 0000 0000 0000
white                         1111 1111 1111 1111 1111 1111
mask (which is red ^ white)   0000 0000 1111 1111 1111 1111


If you exclusive-OR mask with red you’ll get white, and if you exclusive-OR mask with white you’ll get red. This is a very useful result. This means that having drawn an object using the color in draw_color, whichever it is, you can switch to the other color with the statement

draw_color ^= mask;        // Switch the drawing color

The effect of this when draw_color contains red is as follows:


draw_color           1111 1111 0000 0000 0000 0000
mask                 0000 0000 1111 1111 1111 1111
draw_color ^ mask    1111 1111 1111 1111 1111 1111


Clearly, you’ve changed the value of draw_color from red to white. Executing the same statement again will flip the color back to red:

draw_color ^= mask;      // Switch the drawing color

This works as follows:


draw_color                 1111 1111 1111 1111 1111 1111
mask                       0000 0000 1111 1111 1111 1111
draw_color ^ mask          1111 1111 0000 0000 0000 0000

As you can see, draw_color is back to the value of red again. This technique will work with any two colors, although of course it has nothing to do with colors in particular at all—you can use it to alternate between any pair of integer values.

More on Handling Basic Data Types - Try It Out: Using the Bitwise Operators
(Page 7 of 13 )

You can put together an example that exercises the bitwise operators, so that you can see them working together. You can also illustrate the use of the exclusive OR for switching between two values, and how you use masks to select and set individual bits. Here’s the code:

// Program 3.4 Using the bitwise operators
#include <iostream>
#include <iomanip>
using std::cout;
using std::endl;
using std::setfill;
using std::setw;
int main() {
unsigned long red = 0XFF0000UL;   // Color red
unsigned long white = 0XFFFFFFUL; // Color white -  RGB 
      all maximum
cout << std::hex;                 // Set hexadecimal
      output format
cout << setfill('0');             // Set fill character
      for output
cout << "\nTry out bitwise AND and OR operators.";
cout << "\nInitial value red      = " << setw(8) << red;
cout << "\nComplement ~red        = " << setw(8) << ~red;
cout << "\nInitial value white    = " << setw(8) << white;
cout << "\nComplement   ~white      = " << setw(8) <<
      ~white;
cout << "\n Bitwise AND red & white = " << setw(8) << 
      (red  & white);
cout << "\n Bitwise OR red | white  = " << setw(8) << 
      (red | white);
cout << "\n\nNow we can try out successive exclusive OR
       operations.";
unsigned long mask = red ^ white;
cout << "\n      mask = red ^ white = " << setw(8) << 
       mask;
cout << "\n             mask ^ red = " << setw(8) <<
      (mask  ^ red);
cout << "\n           mask ^ white = " << setw(8) <<
      (mask  ^ white);
unsigned long flags = 0xFF;        // Flags variable
unsigned long bit1mask = 0x1;      // Selects bit 1
unsigned long bit6mask = 0x20;     // Selects bit 6
unsigned long bit20mask = 0x80000; // Selects bit 20
cout << "\n\nNow use masks to select or set a particular
       flag bit.";
cout << "\nSelect bit 1 from flags : "   << setw(8) <<
      (flags & bit1mask);
cout << "\nSelect bit 6 from flags : "   << setw(8) <<
      (flags & bit6mask);
cout << "\nSwitch off bit 6 in flags : " << setw(8) <<
      (flags &= ~bit6mask);
cout << "\nSwitch on bit 20 in flags : " << setw(8) <<
      (flags |= bit20mask);
cout << endl;
return 0;
}

This example produces the following output:

======================================================

Try out bitwise AND and OR operators.
Initial value red          = 00ff0000
Complement ~red            = ff00ffff
Initial value white        = 00ffffff
Complement ~white          = ff000000
Bitwise AND red & white    = 00ff0000
Bitwise OR red | white     = 00ffffff
Now we can try out successive exclusive OR operations.
       mask = red ^ white = 0000ffff
               mask ^ red = 00ffffff
             mask ^ white = 00ff0000
Now use masks to select or set a particular flag bit.
Select bit 1 from flags    : 00000001
Select bit 6 from flags    : 00000020
Switch off bit 6 in flags  : 000000df
Switch on bit 20 in flags  : 000800df

====================================================

HOW IT WORKS

There is an #include directive for the <iomanip> standard header, which you saw in the last chapter, because the code uses manipulators to control the formatting of the output. To start with, you define two integer variables containing values representing the colors that you’ll use in subsequent bitwise operations:

unsigned long red = 0XFF0000UL;      // Color red
unsigned long white = 0XFFFFFFUL;    // Color white - RGB
     all maximum

You’ll want to display your data as hexadecimal values, so you specify this with this statement:

cout << std::hex;         // Set hexadecimal output format

Here, hex is a manipulator that sets the output representation for integer values as hexadecimal. Note that this is modal—all subsequent integer output to the standard output stream in the program will now be in hexadecimal format. You don’t need to keep sending hex to the output stream, cout. If necessary, you could change back to decimal output with this statement:

cout << std::dec;         // Set decimal output format

This uses the dec manipulator to reset integer output to the default decimal representation. Note that setting the output format to hexadecimal only affects integer values. Floating-point values will continue to be displayed in normal decimal form.

It would also make things clearer if you output your integers with leading zeros, and you set this mode with this statement:

cout << setfill('0'); // Set fill character for output

Here, setfill() is a manipulator that sets the fill character to whatever character you put between the parentheses. This is also modal, so any subsequent integer output will use this fill character when necessary. Both decimal and hexadecimal output is affected. If you wanted asterisks instead, you would use this:

cout << setfill('*');     // Set fill character for output

To set the fill character back to the default, you just use a space between the parentheses:

cout << setfill(' ');     // Set fill character for output

The value of red and its complement are displayed by these statements:

cout << "\nInitial value red     = " << setw(8) << red;
cout << "\nComplement ~red       = " << setw(8) << ~red;

You use the setw() manipulator that you saw in the last chapter to set the output field width to 8. If you make sure all your output values will be in a field of the same width, it will be easier to compare them. Setting the width is not modal; it only applies to the output from the next statement that comes after the point at which the width is set. From the output for red and white, you can see that the ~ operator is doing what you expect: flipping the bits of its operand.

You combine red and white using the bitwise AND and OR operators with these statements:

cout << "\n Bitwise AND  red & white = " << setw(8) << 
     (red & white);
cout << "\n Bitwise OR   red | white = " << setw(8) <<
     (red | white);

Notice the parentheses around the expressions in the output. These are necessary because the precedence of << is higher than & and |. Without the parentheses, the statements wouldn’t compile. If you check the output, you’ll see that it’s precisely as discussed. The result of ANDing two bits is 1 if both bits are 1; otherwise the result is 0. When you bitwise-OR two bits, the result is 1 unless both bits are 0.

Next, you create a mask to use to flip between the values red and white by combining the two values with the exclusive OR operator:

unsigned long mask = red ^ white;

If you inspect the output for the value of mask, you’ll see that the exclusive OR of two bits is 1 when the bits are different and 0 when they’re the same. By combining mask with either of the two color values using exclusive OR, you can obtain the other, as demonstrated by these statements:

cout << "\n           mask ^ red = " << setw(8) << (mask 
      ^ red);
cout << "\n         mask ^ white = " << setw(8) << (mask
      ^ white);

The last group of statements demonstrates how to use a mask to select a single bit from a group of flag bits. The mask to select a particular bit must have that bit as 1 and all other bits as 0. Thus, the masks to select bits 1, 6, and 20 from a 32-bit long variable are defined as follows:

unsigned long bit1mask = 0x1;            // Selects bit 1
unsigned long bit6mask = 0x20;           // Selects bit 6
unsigned long bit20mask = 0x80000;       // Selects bit 20

To select a bit from flags, you just need to bitwise-AND the appropriate mask with the value of flags, for example:

cout << "\nSelect bit 6 from flags    : " << setw(8) <<
     (flags & bit6mask);

You can see from the output that the result of the expression (flags & bit6mask) is an integer with just bit 6 set. Of course, if bit 6 in flags was 0, the result of the expression would be 0.

To switch a bit off, you need to bitwise-AND the flags variable with a mask containing 0 for the bit you want to switch off and 1 everywhere else. You can easily produce this by applying the complement operator to a mask with the appropriate bit set, and bit6mask is just such a mask. The statement to switch off bit 6 in flags and display the result is as follows:

cout << "\nSwitch off bit 6 in flags : " << setw(8) <<
      (flags &= ~bit6mask);

Of course, if bit 6 were already 0, it would remain as such. To switch a bit on, you just OR flags with a mask having the bit you want to switch on as 1:

cout << "\nSwitch on bit 20 in flags : " << setw(8) <<
       (flags |= bit20mask);

This sets bit 20 of flags to 1 and displays the result. Again, if the bit were already 1, it would remain as 1.

More on Handling Basic Data Types - More on Output Manipulators
(Page 8 of 13 )

Taking the last chapter into account, you’ve now seen five of the modal output manipulators that the <iostream> header defines: scientific, fixed, dec, hex, and oct. The time seems right, therefore, to list these and all the other similar manipulators in one place (see Table 3-2). Don’t worry for now about the bool values mentioned in the last two entries—they’re coming up in the next chapter.

Table 3-2. Output Manipulators

Manipulator Action Performed
dec Formats integer values as base 10 (decimal). This is the default

representation.
hex Formats integer values as base 16 (hexadecimal).
oct Formats integer values as base 8 (octal).
left Left-aligns values in the output field and pads them on the right with the

fill character. The default fill character is a space, as you’ve seen.
right Right-aligns values in the output field and pads them on the left with the

fill character. This is the default alignment.
fixed Outputs floating-point values in fixed-point notation—that is, without an

exponent.
scientific Outputs floating-point values in scientific notation—that is, as the

mantissa plus an exponent. The default mode selects fixed or scientific

notation, depending on the value to be displayed.
showpoint Shows the decimal point and trailing zeros for floating-point values.
noshowpoint The opposite of the showpoint manipulator. This is the default.
showbase Prefixes octal output with 0 and hexadecimal output with 0x or 0X.
noshowbase Shows octal and hexadecimal output without the prefix. This is the

default.
showpos Shows plus signs (+) for positive values.
noshowpos Doesn’t show plus signs for positive values. This is the default.
uppercase Displays uppercase A through F for hexadecimal digits when outputting

integers in hexadecimal format and 0X if showbase is set. Displays E for

the exponent when outputting values in scientific notation, rather than

using lowercase e.
nouppercase Uses lowercase for the preceding items. This is the default.
boolalpha Displays bool values as true and false.
noboolalpha

Displays bool values as 1 and 0.



You may want to set more than one of these modes at a time, and one way to do this is to insert multiple manipulators into the stream. For example, if you want to output your integer data as hexadecimal values that are left aligned in the output field, you could write

std::cout << std::hex << std::left << value;

which will output value (and all subsequent integers in the program, unless the settings are changed) as a left-justified hexadecimal number.

Table 3-3 shows the manipulators that expect you to supply an argument value.

Table 3-3. Output Manipulators That Require an Argument Value

Manipulator Action Performed
setfill() Sets the fill character as specified by the argument. The default fill

character is a space.
setw() Sets the field width as specified by the argument.
setprecision() Sets the precision for floating-point values as specified by the argument.

The precision is the number of decimal digits in the output.


More on Handling Basic Data Types - Enumerated Data Types
(Page 9 of 13 )

You’ll sometimes be faced with the need for variables that have a limited set of possible values that can be usefully referred to by name—the days of the week, for example, or the months of the year. There’s a specific facility, called an enumeration, in C++ to handle this situation. When you define an enumeration, you’re really creating a new type, so it’s also referred to as an enumerated data type. Let’s create an example using one of the ideas I just mentioned—a variable that can assume values corresponding to days of the week. You can define this as follows:

enum Weekday { Monday, Tuesday, Wednesday, Thursday,
     Friday, Saturday, Sunday };

This declares an enumerated data type called Weekday, and variables of this type can only have values from the set that appears between the braces, Monday through Sunday. If you try to set a variable of type Weekday to a value that isn’t one of the values specified, it will cause an error. The symbolic names that are listed between the braces are called enumerators.

In fact, each of the names of the days will be automatically defined as representing a fixed integer value. The first name in the list, Monday, will have the value 0, Tuesday will be 1, and so on through to Sunday with the value 6. You can declare today as an instance of the enumeration type Weekday with the statement

Weekday today = Tuesday;

You use the Weekday type just like any of the basic types you’ve seen. This declaration for today also initializes the variable with the value Tuesday. If you output the value of today, the value 1 will be displayed.

By default, the value of each successive enumerator in the declaration of an enumeration is one larger than the value of the previous one, and the values begin at 0. If you would prefer the implicit numbering to start at a different value, a declaration like this one will make the enumerators equivalent to 1 through 7:

enum Weekday { Monday = 1, Tuesday, Wednesday, Thursday,
      Friday, Saturday, Sunday };

The enumerators don’t need to have unique values. You could define Monday and Mon as both having the value 1, for example, with this statement:

enum Weekday { Monday = 1, Mon = 1, Tuesday, Wednesday,
      Thursday, Friday, Saturday, Sunday };

This allows the possibility of using either Mon or Monday as the value for the first day of the week. A variable, yesterday, that you’ve declared as type Weekday could then be set with this statement:

yesterday = Mon;

You can also define the value of an enumerator in terms of a previous enumerator in the list. Throwing everything you’ve seen so far into a single example, you could declare the type Weekday as follows:

enum Weekday { Monday,                   Mon = Monday,
              Tuesday   = Monday + 2,   Tues = Tuesday,
              Wednesday = Tuesday + 2,  Wed = Wednesday,
              Thursday = Wednesday + 2, Thurs = Thursday,
              Friday = Thursday + 2,    Fri = Friday,
              Saturday = Friday + 2,    Sat = Saturday,
              Sunday = Saturday + 2,    Sun = Sunday
           };

Now, variables of type Weekday can have values from Monday to Sunday and from Mon to Sun, and the matching pairs of enumerators correspond to the integer values 0, 2, 4, 6, 8, 10, and 12.

If you’d like, you can assign explicit values to all the enumerators. For example, you could define this enumeration:

enum Punctuation { Comma = ',', Exclamation = '!',
    Question='?' };

Here, you’ve defined the possible values for variables of type Punctuation as the numerical equivalents of the appropriate symbols. If you look in the ASCII table in Appendix A, you’ll see that the symbols are 44, 33, and 63, respectively, in decimal, which demonstrates that the values you assign don’t have to be in ascending order. If you don’t specify all of them explicitly, values will continue to be assigned by incrementing by 1 from the last specified value, as in the second Weekday example.

The values that you specify for enumerators must be compile-time constants— that is, constant expressions that the compiler can evaluate. Such expressions can only include literals, enumerators that have been defined previously, and variables that you’ve declared as const. You can’t use non-const variables, even if you’ve initialized them.

Anonymous Enumerations

By declaring variables at the same time as you define the enumeration, you can omit the enumeration type, provided that you don’t need to declare other variables of this type later on, for example:

enum { Monday, Tuesday, Wednesday, Thursday, Friday,
    Saturday, Sunday } yesterday, today, tomorrow;

Here, you declare three variables that can assume values from Monday to Sunday. Because the enumeration type isn’t specified, you can’t refer to it. You can’t declare other variables for this enumeration at all, because doing so would require you to name the enumeration type, which is simply not possible.

A common use of anonymous enumeration types is as an alternative way of defining integer constants, for example:

enum { feetPerYard = 3, inchesPerFoot = 12, yardsPerMile =
     1760 };

This enumeration contains three enumerators with explicit values assigned. Although you’ve declared no variables of this enumerated data type, you can still use the enumerators in arithmetic expressions. You could write this statement:

std::cout << std::endl << "Feet in 5 miles = " << 5 *
     feetPerYard * yardsPerMile;

The enumerators are converted to type int automatically. It may look as if little (if anything) is to be gained by using an enumeration to define integer constants, but you’ll see when you learn about classes that it provides a very useful way of including a constant within a class. For now, let’s look a little more closely at the conversion of enumerated data types.

Casting Between Integer and Enumeration Types

In addition to the enumerators themselves, you can use a variable of an enumeration type in a mixed arithmetic expression. An enumerated data type will be cast automatically to the appropriate type, but the reverse isn’t true; there’s no automatic conversion from an integer type to an enumeration type. If you’ve declared the variable today to be of type Weekday that you defined previously, you can write

today = Tuesday;             // Assign an enumerator value
int day_value = today + 1;   // Calculate with an
     enumerator type

The value of today is Tuesday, which corresponds to 1, so day_value will be set to 2. Although the enumerator Wednesday corresponds to the value 2, the following statement will not compile:

today = day_value;          // Error – no conversion!

However, you can achieve the objective of this statement by putting in an explicit cast:

today = static_cast<Weekday>(day_value); // OK

With an explicit cast, the integer value you’re casting must be within the range of the enumerators, or the result is undefined. This doesn’t mean that it must correspond to the value of an enumerator—just that it must be equal to or greater than the lowest enumerator, and less than or equal to the highest enumerator. For example, you could define an enumeration, Height, and declare a variable of that type with this statement:

enum Height { Bottom, Top = 20 } position;

The enumerator Bottom corresponds to the value 0, and Top corresponds to the value 20. The range is therefore from 0 to 20, so you could assign a value to the variable position with this statement:

position = static_cast<Height> (10);

The value assigned to position doesn’t correspond to either of the enumerators, but it’s nonetheless a legal value because it falls within the range of the minimum and maximum values of the enumerators. For a variable of the Punctuation type that you saw earlier, you could legally cast any integer from 33 to 63 to that type and store it, although in this instance it’s difficult to see what purpose it would serve.

More on Handling Basic Data Types - Try It Out: Enumerated Data Types
(Page 10 of 13 )

Enumerations become more obviously useful when you can make decisions by comparing the value of a variable of an enumerated data type against possible enumerators. You’ll look at that in the next chapter, so here you can just work through a simple example to demonstrate some of the operations on enumerated data types you’ve seen so far:

// Program 3.5 – Exercising an enumeration
#include <lostream>
using std::cout;
int main() {
enum Language { English, French, German, Italian,
     Spanish };
// Display range of enumerators
cout << "\nPossible languages are:\n"
     << English << ". English\n"
     << French  << ". French\n"
     << German  << ". German\n"
     << Italian << ". Italian\n"
     << Spanish << ". Spanish\n";
Language tongue = German;
cout << "\n Current language is " << tongue;
tongue = static_cast<Language> (tongue + 1);
cout << "\n Current language is now " << tongue
     << std::endl;
return 0;
}

This will display the following output:

======================================================

Possible languages are:
0.  English
1.  French
2.  German
3.  Italian
4.  Spanish
Current language is 2
Current language is now 3

=====================================================

HOW IT WORKS

You first define an enumeration, Language, with this statement:

enum Language { English, French, German, Italian,
     Spanish };

Variables of type Language can have any of the enumerators between the braces as a value. You list all the possible values with the next statement:

cout << "\nPossible languages are:\n"
     << English << ". English\n"
     << French  << ". French\n"
     << German  << ". German\n"
     << Italian << ". Italian\n"
     << Spanish << ". Spanish\n";

An enumerator is displayed as its numeric value, so you output a text string alongside each one to show what language it corresponds to.

You declare and initialize a variable of type Language with this statement:

Language tongue = German;

The value of this variable displays as 2, and then you give it a new value in the next statement:

tongue = static_cast<Language>(tongue + 1);

In the expression tongue + 1, the value of tongue is converted to type int, and then 1 is added to produce the value 3 as type int. This is then converted back to type Language by the explicit cast, before it gets stored back in tongue. Without the explicit cast, the statement wouldn’t compile because there’s no automatic conversion from an integer type to an enumeration type. Of course, the value of tongue then displays as 3.


Synonyms for Data Types

You’ve seen how enumerations provide a way to define your own data types. The typedef keyword enables you to specify your own data type name as an alternative to another type name. Using typedef, you can declare the type name BigOnes as being equivalent to the standard type long with the following declaration:

typedef long BigOnes;       // Defining BigOnes as a type
      name

Of course, this isn’t defining a new type. This just defines BigOnes as an alternative type specifier for long, so you could declare a variable mynum as type long with this statement:

BigOnes mynum = 0;           // Declare & initialize a 
      long int variable

There’s no difference between this declaration and one using the standard built-in type name. You could equally well use this:

long int mynum = 0;          // Declare & initialize a
      long int variable

which has exactly the same result. In fact, if you declare your own type name (such as BigOnes), you can use both type specifiers within the same program to declare different variables that will end up having the same type. However, it’s hard to come up with a justification for doing this.

Because typedef simply creates a synonym for a type that already exists, it may appear to be a bit superfluous. This isn’t at all the case. One important use for typedef is to provide flexibility in the data types used by a program that may need to be run on a variety of computers. The standard library defines the type size_t that you saw earlier in this chapter by using typedef. Let’s consider a particular instance in which you might want to use typedef.

Suppose you’re writing a program that uses several variables to record values that count events of some kind—you could be recording the number of chocolate bars produced per hour on high-speed manufacturing machinery, for instance. You know that the typical values for these counts require 4-byte integers to be used.

On some computers, type int will be 2 bytes, which is insufficient for the range of integers in the program. On other computers, type int will be 4 bytes, which is just what you want. You could resolve this by using type long, which will generally be at least 4 bytes, but on some machines it may be 8 bytes, which is wasteful—particularly if your program stores a lot of integers. You can provide the flexibility to deal with this situation by declaring your own type for use throughout the program, for example:

typedef int Counter;      // Define the integer type for
    the program

Now you can write your program in terms of the type Counter, rather than the standard type, int. This gives you the advantage that should you want to compile your program on a machine on which the range for type int is insufficient, you can redefine Counter as

typedef long Counter;    // Define the integer type for
    the program

Now, all the integers that are declared as Counter within the program will be of type long.

You’ll be able to do much better than this when you learn more about preprocessing directives. By using the information from the <climits> header, you can code a program so that it will automatically define your Counter type depending on the range of values available with each integer type.

You’ll see later that typedef can also fulfill a very useful role in enabling you to simplify more complex type declarations than you’ve met so far. You’ll also see later that classes provide you with a means of defining completely new data types, in which you have complete control over the properties and operations that apply to the new type.

More on Handling Basic Data Types - The Lifetime of a Variable
(Page 11 of 13 )

All variables have a finite lifetime when your program executes. They come into existence from the point at which you declare them and then, at some point, they disappear—at the latest, when your program terminates. How long a particular variable lasts is determined by a property called its storage duration. A variable can have three different kinds of storage duration:

  • Automatic storage duration
  • Static storage duration
  • Dynamic storage duration

The storage duration that a variable has depends on how you create the variable. I defer discussion of variables with dynamic storage duration until Chapter 7, but you’ll look into the characteristics of the other two kinds of storage duration in this chapter.

Another property that variables have is scope. The scope of a variable is simply that part of your program in which the variable name is valid. Within a variable’s scope, you can legally refer to it, set its value, or use it in an expression. Outside of the scope of a variable, you can’t refer to its name—any attempt to do so will cause a compiler error. Note that a variable may still exist outside of its scope, even though you can’t refer to it by name. You’ll see examples of this situation a little later in this discussion.

All the variables that you’ve declared up to now have had automatic storage duration, and are therefore called automatic variables. Let’s take a closer look at these first.

Automatic Variables

The variables that you’ve declared so far have been declared within a block—that is, between a pair of curly braces. These are called automatic variables and are said to have local scope or block scope. An automatic variable is “in scope” from the point at which it is declared until the end of the block containing its declaration.

An automatic variable is “born” when it’s declared and automatically ceases to exist at the end of the block containing the declaration. This will be at the closing brace matching the first opening brace that precedes the declaration of the variable. Every time the block of statements containing a declaration for an automatic variable is executed, the variable is created anew, and if you specified an initial value for the automatic variable, it will be reinitialized each time it’s created.

You can use the auto keyword to specify explicitly that a variable is automatic, but this keyword is rarely used because it’s implied by default. Let’s put together an example of what you’ve learned so far.


Try It Out: Automatic Variables

You can demonstrate the lifetime of automatic variables with the following example:

  // Program 3.6 Demonstrating variable scope
 #include <lostream>
  using std::cout;
  using std::endl;
  int main() {               // Function scope starts here
  int count1 = 10;
  int count3 = 50;
 cout << endl << "Value of outer count1 = " << count1;
  {                // New block scope starts here...
  int count1 = 20; // This hides the outer count1
  int count2 = 30;
 cout << endl << "Value of inner count1 = " << count1;
  count1 += 3;     // This changes the inner count1
  count3 += count2;
}                // ...and ends here.
  cout << endl
      << "Value of outer count1 = " << count1
      << endl
      << "Value of outer count3 = " << count3;
  // cout << endl << count2; // Uncomment to get an error
  cout << endl;
  return 0;
}                              // Function scope ends here

The output from this example is as follows:

===========================================

Value of outer count1 = 10
Value of inner count1 = 20
Value of outer count1 = 10
Value of outer count3 = 80

===========================================

HOW IT WORKS

The first two statements declare and define two integer variables, count1 and count3, with initial values of 10 and 50, respectively:

int count1 = 10;
int count3 = 50;

Both of these variables exist from this point in the code to the closing brace at the end of the program. The scope of these variables also extends to the closing brace at the end of main().


NOTE Remember that the lifetime and scope of a variable are two different things. Lifetime is the period of execution time over which a variable survives. Scope is the region of program code over which the variable name can be used. It’s important not to get these two ideas confused.

Following the variable definitions, the value of count1 is presented in the first line of output by this statement:

cout << endl << "Value of outer count1 = " << count1;

There’s then a second opening brace that starts a new block. Two variables, count1 and count2, are defined within this block, with the values 20 and 30, respectively. The count1 variable declared here is different from the first count1. Although the first count1 still exists, its name is masked by the second count1. Any use of the name count1 following the declaration within the inner block refers to the count1 declared within that block.

I’ve duplicated names in this way only to illustrate what happens—it’s not a good approach to programming in general. Doing this kind of thing in a real program would be confusing and unnecessary, and produce code that was extremely prone to error.

The output statement shows by the value in the second line that you’re using the count1 in the inner scope—that is, inside the innermost braces:

{                       // New block scope starts here...
int count1 = 20;       // This hides the outer count1
int count2 = 30;
cout << endl << "Value of inner count1 = " << count1;

Had you still been using the outer count1, this statement would have output the value 10. The variable count1 is then incremented by this statement:

count1 += 3;          // This changes the inner count1

The increment applies to the variable in the inner scope, because the outer one is still hidden. However, count3, which was defined in the outer scope, is incremented without any problem by the next statement:

  count3 += count2;

This shows that the variables that were defined at the beginning of the outer scope are still accessible in the inner scope. They could have been defined after the second of the inner pair of braces and still be within the outer scope, but in that case they wouldn’t exist at the point that you’re using them.

After the brace ending the inner scope, count2 and the inner count1 cease to exist. Their lifetime has come to an end. The variables count1 and count3 are still there in the outer scope, and their values are displayed by this statement, demonstrating that count3 was indeed incremented in the inner scope:

cout << endl
     << "Value of outer count1 = " << count1
     << endl
     << "Value of outer count3 = " << count3;

If you uncomment the next line

// cout << endl << count2;   // uncomment to get an error

the program will no longer compile correctly, because it attempts to output a nonexistent variable.


Positioning Variable Declarations

You have great flexibility in where you place the declarations for your variables. The most important issue to consider is what scope the variables need to have. Beyond that, you should generally place a declaration close to where the variable is first to be used in a program. You should always write your programs with a view to making them as easy as possible for another programmer to understand, and declaring a variable close to its first point of use can be helpful in achieving that.

It’s possible to place variable declarations outside all of the functions that make up a program. Let’s look what effect that has on the variables concerned.

Global Variables

Variables declared outside of all blocks and classes are called globals and have global scope (which is also called global namespace scope). This means that they’re accessible in all the functions in the source file, following the point at which they’re declared. If you declare them at the very top, they’ll be accessible from anywhere in the file.

Globals also have static storage duration by default. Global variables with static storage duration will exist from the start of execution of the program until execution of the program ends. If you don’t specify an initial value for a global variable, it will be initialized with 0 by default. Initialization of global variables takes place before the execution of main() begins, so they’re always ready to be used within any code that’s within the variable’s scope.

Figure 3-4 shows the contents of a source file, Example.cpp, and the arrows indicate the scope of each of the variables.


Figure 3-4.  Variable scope

Figure 3-4 illustrates the extent of the scope of each variable in a file. The variable value1 that appears at the beginning of the file is declared at global scope, as is value4, which appears after the function main(). The global variables have a scope that extends from the point at which they’re declared to the end of the file. Even though value4 exists when execution starts, it can’t be referred to in main() because main() isn’t within the variable’s scope. For main() to use value4, you would need to move its declaration to the beginning of the file. Both value1 and value4 will be initialized with 0 by default, which isn’t the case for the automatic variables. Remember that the local variable called value1 in function() will hide the global variable of the same name.

Because global variables continue to exist for as long as the program is running, this might raise the following question in your mind: “Why not make all variables global and avoid this messing about with local variables that disappear?” This sounds very attractive at first, but there are serious disadvantages that completely outweigh any advantages that you might gain.

Real programs are generally composed of a large number of statements, a significant number of functions, and a great many variables. Declaring all at the global scope greatly magnifies the possibility of accidental, erroneous modification of a variable, and it makes the job of naming them sensibly quite intractable. They’ll also occupy memory for the duration of program execution. By keeping variables local to a function or a block, you can be sure they have almost complete protection from external effects. They’ll only exist and occupy memory from the point at which they’re defined to the end of the enclosing block, and the whole development process becomes much easier to manage.

More on Handling Basic Data Types - Try It Out: The Scope Resolution Operator
(Page 12 of 13 )

As you’ve seen, a global variable can be hidden by a local variable with the same name. However, it’s still possible to “get at” the global variable by using the scope resolution operator (::), which you saw in Chapter 1 when you learned about namespaces. Here’s a demonstration of how this works with a revised version of the last example:

// Program 3.7 Using the scope resolution operator
#include <iostream>
using std::cout;
using std::endl;
int count1 = 100;    // Global version of count1
  int main() {           // Function scope starts here
  int count1 = 10;
  int count3 = 50;
 
cout << endl << "Value of outer count1 = " << count1;
 
cout << endl << "Value of global count1 = " << ::count1;
  {                      // New block scope starts here...
 
int count1 = 20;     // This hides the outer count1
 
int count2 = 30;
 
cout << endl << "Value of inner count1 = " << count1;
 
cout << endl << "Value of global count1 = " << ::count1;
 
count1 += 3;         // This changes the inner count1
 
count3 += count2;
 
}                    // ...and ends here.
 
cout << endl
      << "Value of outer count1 = " << count1
     
<< endl
     
<< "Value of outer count3 = " << count3;
 
// cout << endl << count2; // Uncomment to get an error
 
cout << endl;
 
return 0;
}                            // Function scope ends here

If you compile and run this example, you’ll get the following output:

======================================================

Value of outer count1 = 10
Value of global count1 = 100
Value of inner count1 = 20
Value of global count1 = 100
Value of outer count1 = 10
Value of outer count3 = 80

======================================================

HOW IT WORKS

The lines in bold indicate the changes made to the previous example, and they’re the only ones whose effects I need to discuss. The declaration of count1 prior to the definition of the function main() is global, so in principle it’s available anywhere through the function main(). This global variable is initialized with the value of 100 in its declaration:

int count1 = 100;         // Global version of count1

However, you have two other variables called count1 that are defined within main(), so the global count1 is hidden by the local count1 variables throughout the program. In fact, in the inner block it is hidden behind two variables called count1: the inner count1 and the outer count1.

The first new output statement is as follows:

int count1 = 10;
int count3 = 50;
cout << endl << "Value of outer count1 = " << count1;
cout << endl << "Value of global count1 = " << ::count1;

This uses the scope resolution operator, ::, to indicate to the compiler that you want to reference the global count1, not the local one. You can see that this works by the value displayed in the output. The global scope resolution operator also does its stuff within the inner block, as you can see from the output generated by this statement:

  int count1 = 20;          // This hides the outer count1
  int count2 = 30;
  cout << endl << "Value of inner count1 = " << count1;
  cout << endl << "Value of global count1 = " << ::count1;

This outputs the value 100, as before—the long arm of the scope resolution operator used in this fashion always reaches a global variable.

You’ll see a lot more of this operator when I cover object-oriented programming, where it’s used extensively. I’ll also talk further about namespaces, including how to create your own, in Chapter 10.


Static Variables

It’s conceivable that you might want to have a variable that’s defined and accessible locally within a block, but that also continues to exist after exiting the block in which it is declared. In other words, you need to declare a variable within a block scope, but give it static storage duration. The static keyword provides you with the means of doing just this, and the need for it will become more apparent when you begin to deal with functions in Chapter 8.

A variable that you declare as static will continue to exist for the life of a program, even though it’s declared within a block and is only available from within that block (or its sub-blocks). It still has block scope, but it has static storage duration. To declare a static variable called count, you would write

static int count;

Variables with static storage duration are always initialized for you if you don’t provide an initial value yourself. The variable count declared here will be initialized with 0. If you don’t specify an initial value when you declare a static variable, it will always be initialized with 0 and converted to the type applicable to the variable. Remember that this is not the case with automatic variables. If you don’t initialize your automatic variables, they’ll contain junk values left over from the program that last used the memory they occupy.

The register Storage Class Specifier

The register specifier is used to indicate that a variable is critical to the speed of execution and should therefore be placed in a machine register. (A register is a special, high-speed storage facility located separately from main memory, usually on the processor chip.) Here’s an example of how you use this modifier:

register int index = 0;

Here, you’re requesting that the variable index use a register. The compiler is under no obligation to accede to this request, and in many compilers it won’t result in a register being allocated for this purpose.

In general, you shouldn’t use register unless you’re absolutely sure of what you’re doing. Most compilers will do a better job of deciding how registers should be used without any prompting.

The volatile Type Modifier

The volatile modifier is used to indicate that the value of a variable can be modified asynchronously by an external process, such as an interrupt routine:

volatile long data = 0L;  // Value may be changed by
    another process

The effect of using the volatile modifier is to inhibit the optimization that the compiler might otherwise carry out. For example, when a program references a non-volatile variable, the compiler might be able to reuse an existing value for that variable that was previously loaded into a register, to avoid the overhead of retrieving the same value from memory. If the variable was declared as volatile, its value will be retrieved every time it’s used.

More on Handling Basic Data Types - Declaring External Variables
(Page 13 of 13 )

You saw in Chapter 1 that programs can consist of several source files and most programs of any size will generally do so. If you have a program that consists of more than one source file, you may need to access a global variable from one source file that is declared in another. The extern keyword allows you to do this. Suppose you have one program file that contains the following:

// File1.cpp
int shared_value = 100;
// Other program code …

If you have code in another file that needs to access the shared_value variable, you can arrange for this as follows:

// File2.cpp
extern int shared_value;       // Declare variable to be
     external
int main() {
int local_value = shared_value + 10;
..// Plus other code...
}

The first statement in File2.cpp declares shared_value to be external, so this is only a declaration, not a definition. The reference to shared_value in main() is then to the variable defined in the first file, File1.cpp.

You must not use an initializing value when declaring an external variable. If in the second file you wrote

extern int shared_value = 0;   // Wrong! Not an external
     declaration.

the variable would be defined locally, and the extern declaration would be ignored.

Precedence and Associativity

You have accumulated quite a number of new operators in this chapter. Table 3-4 summarizes the precedence and associativity of the operators you’ve seen so far.

Table 3-4. Operator Precedence and Associativity

Operator Associativity
static_cast<>() right
Postfix ++
Postfix -
Unary + right
Unary
Prefix ++
Prefix -
~
* / % left
Binary + left
Binary
<< >> left
& left
^ left
| left
= op= right

The operators in Table 3-4 appear in sequence from highest precedence to lowest, and each group in the table contains operators that have the same precedence. The sequence of execution of operators with the same precedence in an expression is determined from their associativity. As I mentioned previously, a table showing the precedence of all the C++ operators appears in Appendix D.

Summary

In this chapter, you learned some of the more complicated aspects of computation in C++. You also learned a little about how you can define data types of your own, although what you’ve seen here has nothing to do with the ability to define completely general types that I discuss in Chapter 11. The essentials of what you’ve learned in this chapter are as follows:

  • You can mix different types of variables and constants in an expression. The compiler will arrange for variables to be automatically converted to an appropriate type where necessary.
  • Automatic conversion of the type of the right side of an assignment to that of the left side will also be made where these are different. This can cause loss of information when the left-side type isn’t able to contain the same information as the right-side type—double converted to int, for example, or long converted to short.
  • You can explicitly convert a value of one basic type to another by using static_cast<>().
  • By default, a variable declared within a block is automatic, which means that it only exists from the point at which it is declared to the end of the block in which its declaration appears, as indicated by the closing brace of the block that encloses its declaration.
  • A variable may be declared as static, in which case it continues to exist for the life of the program. However, it can only be accessed within the scope in which it was defined. If you don’t initialize a static variable explicitly, it will be initialized to 0 by default.
  • Variables can be declared outside of all the blocks within a program, in which case they have global namespace scope and static storage duration by default. Variables with global scope are accessible from anywhere within the program file that contains them, following the point at which they’re declared, except where a local variable exists with the same name as the global variable. Even then, they can still be reached by using the scope resolution operator (::).
  • The keyword typedef allows you to define synonyms for other types.
  • The extern keyword allows you to reference a global variable defined in another file.
Exercises

The following exercises enable you to try out what you’ve learned in this chapter. If you get stuck, look back over the chapter for help. If you’re still stuck, you can download the solutions from the Downloads area of the Apress website (http://www.apress.com), but that really should be a last resort.

Exercise 3-1. Write a program that calculates the reciprocal of a nonzero integer entered by the user. (The reciprocal of an integer, n, is 1/n.) The program should store the result of the calculation in a variable of type double and then output it.

Exercise 3-2. Create a program that prompts the user to input an integer in decimal form. Then, invert the last bit of its binary representation. That is, if the last bit is 1, then change it to 0, and vice versa. The result should then be displayed as a decimal number. How does the adjustment affect the resulting integer value? (Hint: Use a bitwise operator.)

Exercise 3-3. Write a program to calculate how many square boxes can be contained in a single layer on a rectangular shelf, with no overhang. Use variables of type double for the length and depth of the shelf (in feet) and for the length of one side of a single box (in inches) and read values for these from the keyboard. You’ll need to declare and initialize a constant to convert from feet to inches. Use a single statement to calculate the number of boxes that the shelf can hold in a single layer, assigning the answer to a variable of type long.

Exercise 3-4. Without running it, can you work out what the following code snippet will produce?

unsigned int k = 430U;
unsigned int j = (k >> 4) & ~(~0 << 3);
std::cout << j;

Exercise 3-5. Write a program to read four characters from the keyboard and pack them into a single 4-byte integer variable. Display the value of this variable as hexadecimal. Unpack the 4 bytes of the variable and output them in reverse order, with the low-order byte first


"C++" 카테고리의 다른 글
  • Function Pointers (1)2007/07/30
  • DLL Conventions: Issues and Solutions (0)2007/07/27
  • More on Handling Basic Data Types (0)2007/07/27
  • C++ in theory: Bridging Your Classes with PIMPLs (0)2007/07/27
  • C++ In Theory: The Singleton Pattern (0)2007/07/27
2007/07/27 17:50 2007/07/27 17:50
Posted by webdizen
Tags C++Keyword C++, Data Types, Handling
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3103

Leave your greetings.

[로그인][오픈아이디란?]

«Prev  1  Next»

RSS HanRSS
Blog Image
webdizen
이곳은 컴퓨터에 대해 연구하고, 공유하고, 소통하기 위한 연구실입니다. 개인적으로는 OLAP, Data Mining, Semantic Web, Data Modeling에 대해서 연구하고 있습니다.

Categories

전체 (3009)
Webdizen (141)
Life (6)
Diary (16)
Blog (9)
IDEA (2)
Travel (10)
Book (16)
Photo (7)
Movie (8)
Music (14)
Leisure Sports (10)
Funny (6)
Hardware (121)
Software (120)
Windows (5)
Unix & Linux (120)
Installation (5)
Kernel (10)
System (34)
Develop (22)
X-Window (0)
Applicaton (31)
Security (4)
Framework (2)
Hadoop (2)
Programming (804)
Algorithm & Data Structure (1)
Assembly (38)
UNIX/Linux C (95)
C++ (128)
STL (4)
Java (38)
Win32 API (92)
ATL/COM (44)
MFC (151)
.NET (26)
WCF/WPF (4)
C# (28)
Network Programming (17)
Database Programming (12)
OpenGL / DirectX (13)
Multimedia Programming (0)
Game Programming (21)
Parallel Distributed Progra... (0)
Reverse Engineering (0)
Debugging (9)
Python (1)
Ruby (1)
Ruby on Rails (1)
QT (4)
GTK (0)
JSP (0)
PHP (6)
ASP.NET (6)
ASP (2)
Development (28)
Useful Library (2)
Data Modeling (0)
Database (105)
Oracle (4)
MSSQL (41)
MySQL (2)
Data Warehouse (2)
Data Mining (4)
Network (66)
Web (79)
DHTML (4)
XHTML (1)
Javascript (1)
CSS (1)
AJAX (9)
XML (11)
Flex (1)
Silverlight (3)
Security (91)
DoS (1)
Kernel (10)
Scanning (3)
Sniffing (0)
Spoofing (4)
Overflow (28)
Web (11)
Shell (10)
Format String (14)
Window (2)
Embedded (70)
Multimedia (27)
Mobile (14)
Graphic (24)
Management (633)
Knowledge (581)
Hadoop (0)

Notice

  • 메타 블로그 사이트에 등록
  • 새해 맞이 블로그의 변화
  • 블로그 명칭 변경
  • 도메인(www.webdizen.net) 구...
  • TEXTCUBE 1.6.1로 업그레이드...

Tags

  • Apache
  • Ubuntu
  • ADODB
  • Excel
  • 한국정보과학회
  • Named
  • 문자열 검색
  • 패킷 분석
  • 이상치 탐지
  • TLS
  • DB
  • 구본관
  • Programming Optimization
  • @@Error
  • DSS
  • 인덱스
  • 중앙도서관
  • 수영
  • 컴퓨터
  • DFS

Recent Articles

  • 트위터(Twitter)의 시작!.
  • 청년 리더의 조건.
  • 애플의 타블렛 PC - 아이패드....
  • 미래의 인터페이스 - 육감 기....
  • 기초발성법 동영상 강좌.

Recent Comments

  • 학교 과제물중 쓰레드에 대하....
    장진혁 03/17
  • 관리자만 볼 수 있는 댓글입....
    비밀방문자 03/12
  • 상대방의 이야기를 열심히 경....
    DoNuts 03/03
  • Lots of students know techn....
    Bobbi35Shannon 02/25
  • 좋은글 잘 보고 갑니다..
    Und_hacker 01/08

Recent Trackbacks

  • printf,scanf를 이용한 형식....
    yundream의 프로그래밍 이야기 03/10
  • 파일 열기/저장하기 CFileDialog.
    은마군의 나태블록 2009
  • World IT Show 2008.
    상우 :: Oranzie's BLOG 2008
  • cvs서버 설치하기.
    3인3색 2008
  • 속속 공개되는 Google Chart....
    PHP와 Web 2.0 2007

Archive

  • 2010/02 (1)
  • 2010/01 (6)
  • 2009/12 (5)
  • 2009/09 (3)
  • 2009/08 (1)

Calendar

«   2010/03   »
일 월 화 수 목 금 토
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      

Bookmarks

    • Administration
      • IIS.NET
      • NTFAQ
      • OS의 모든 것
      • 리눅스포털
    • Database
      • SQL Server Central
      • SQL Team
    • Development
      • .NET Heaven
      • ASP Alliance
      • ASP.NET 2.0
      • Bullog.net
      • C# Corner
      • C++ (C PlusPlus.com)
      • C++ Reference
      • CodeGuru
      • CodePlex
      • DebugLab
      • Dev Articles
      • Devpia
      • DotNet Junkies
      • DotNet Zone
      • Driver Online
      • GOSU.NET
      • HOONS 닷넷
      • Joinc 팀블로그
      • KOSR
      • MSDN Home Page
      • OSR Online
      • Sky.ph - 개발자 커뮤니...
      • TAEYO.NET
      • The Code Project
      • WindowsClient.net
      • 김상욱의 개발자 Side
      • 조인시 위키
    • Human Networks
      • belief21c's e-space
      • I think I can
      • Invisible Rover's Blog :D
      • Rodman®
      • ■ Feel So Good~! ■
      • 까만 나비
      • 나를 가꾸는 시간.
      • 나만의 즐거움~~!
      • 단녕
      • 상우 :: Oranzie's BLOG
    • Information Technology
      • Microsoft TechNet
      • 지디넷코리아 - 글로벌...
    • Security
      • FoundStone
      • milw0rm
      • NewOrder
      • OpenRCE
      • Phrack.org
      • Reverse Engineering b1...
      • Reverse Engineering Team
      • RootKit
      • SecurityFocus
      • SecurityXploded by Nag...
      • Wow Hacker
      • Zone-H
Textcube
Louice Studio Inc.
Powered by Textcube. Original designed by Tistory.