Chapter 5. Arrays, Qualifiers, and Reading Numbers

That mysterious independent variable of political calculations, Public Opinion.

Thomas Henry Huxley

This chapter covers arrays and more complex variables.

Arrays

So far in constructing our building we have named each brick (variable). That is fine for a small number of bricks, but what happens when we want to construct something larger? We would like to point to a stack of bricks and say, “That’s for the left wall. That’s brick 1, brick 2, brick 3. . . .”

Arrays allow us to do something similar with variables. An array is a set of consecutive memory locations used to store data. Each item in the array is called an element. The number of elements in an array is called the dimension of the array. A typical array declaration is:

// List of data to be sorted and averaged
int    data_list[3];

This declares data_list to be an array of the three elements data_list[0], data_list[1], and data_list[2], which are separate variables. To reference an element of an array, you use a number called the subscript (the number inside the square brackets [ ]). C++ is a funny language and likes to start counting at 0, so these three elements are numbered 0-2.

Tip

Common sense tells you that when you declare data_list to be three elements long, data_list[3] would be valid. Common sense is wrong: data_list[3] is illegal.

Example 5-1 computes the total and average of five numbers.

Example 5-1. five/five.cpp
#include <iostream>

float data[5];  // data to average and total 
float total;    // the total of the data items 
float average;  // average of the items

int main(  )
{
    data[0] = 34.0;
    data[1] = 27.0;
    data[2] = 46.5;
    data[3] = 82.0;
    data[4] = 22.0;

    total = data[0] + data[1] + data[2] + data[3] + data[4];
    average =  total / 5.0;
    std::cout << "Total " << total << " Average " << average << '
';
    return (0);
}

Example 5-1 outputs:

Total 211.5 Average 42.3

Strings

C++ comes with an excellent string package. Unlike the numbers such as int and float, which are built-in to the core language, the std::string type comes from the C++ library. Before you can use this type, you must bring in the std::string definition with the statement:

#include <string>

After this you can declare strings like any other variable:

std::string my_name;

A string constant is any text enclosed in double quotes. So to assign the variable my_name the value “Steve”, we use the statement:

    my_name = "Steve";

Strings may be concatenated using the + operator. Example 5-2 illustrates how to combine two strings (and a space) to turn a first and last name into a full name.

Example 5-2. string/string.cpp
#include <string>
#include <iostream>

std::string first_name; // First name of the author
std::string last_name;  // Last name of the author
std::string full_name;  // Full name of the author

int main(  )
{
    first_name = "Steve";
    last_name = "Oualline";
    full_name = first_name + " " + last_name;
    std::cout << "Full name is " << full_name << '
';
    return (0);
}

A string can be treated like an array of characters. Each character in the string can be accessed through the [ ] operation. For example:

char first_ch;    // First character of the name
....
    first_ch = first_name[0];

If the subscript is out of range, the result is undefined. That means that sometimes you’ll get a random character, sometimes your program will crash, and sometimes something else will happen. You could protect things with an assert statement, which is described later in this chapter, but there’s a better way to do it.

That is to access the characters through the at member function. (We’ll learn all about what exactly a member function is in Chapter 13.) It works just like [] except that if the index is out of range, it throws an exception and shuts your program down in an orderly manner. See Chapter 22 for a description of exceptions.)

So to get the first character of our string safely, we need to use the statement:

    first_ch = first_name.at(0);

The length of a C++ string can be obtained with the length member function. For example:

std::cout << full_name << " is " << full_name.length(  ) << " long
";

Finally, to extract a portion of a string, there is the substr member function. The general form of this function is:

               string.substr(first, length)

This function returns a string containing all the characters from first to length. For example, the following code assigns the variable sub_string the word “is”:

//             01234567890123
main_string = "This is a test";
sub_string = main_string.substr(5, 6);

If first is out of range, an exception is generated and your program is terminated. If last is too big, the substr function returns as much of the string as it can get.

If you omit the last parameter, substr returns the rest of the string.

Tip

The std::string variable type has lots of useful and advanced features. For a full list, see your C++ documentation or help text.

Wide Strings

The wstring data type acts just like the string data type except that uses wide characters (wchar_t) instead of characters (char). Wide string constants look just like string constants, but they begin with L" instead of ". For example:

wstring name = L"";

Reading Data

So far you’ve learned how to compute expressions and output the results. You need to have your programs read numbers as well. The output class object std::cout uses the operator << to write numbers. The input object std::cin uses the operator >> to read them. For example:

std::cin >> price >> number_on_hand;

This code reads two numbers: price and number_on_hand. The input to this program should be two numbers, separated by whitespace. For example, if you type:

32 5

price gets the value 32 and number_on_hand gets 5.

Tip

This does not give you very precise control over your input. C++ does a reasonable job for simple input. If your program expects a number and you type <enter> instead, the program will skip the <enter> (it’s whitespace) and wait for you to type a number. Sometimes this may lead you to think your program’s stuck.

In Example 5-3, we use std::cin to get a number from the user, then we double it.

Example 5-3. double/double.cpp
#include <iostream>

int   value;       // a value to double

int main(  )
{
    std::cout << "Enter a value: ";
    std::cin >> value;
    std::cout << "Twice " << value << " is " << value * 2 << '
';
    return (0);
}

Notice that there is no at the end of Enter a value:. This is because we do not want the computer to print a newline after the prompt. For example, a sample run of the program might look like this:

Enter a value: 12
Twice 12 is 24

If we replaced Enter a value: with Enter a value: the result would be:

Enter a value: 
12
Twice 12 is 24

Question 5-1: Example 5-4 is designed to compute the area of a triangle, given its width and height. For some strange reason, the compiler refuses to believe that we declared the variable width. The declaration is right there on line two, just after the definition of height. Why isn’t the compiler seeing it?

Example 5-4. comment/comment.cpp
#include <iostream>

int  height;   /* the height of the triangle
int  width;    /* the width of the triangle */
int  area;     /* area of the triangle (computed) */

int main(  )
{
    std::cout << "Enter width height? ";
    std::cin >> width >> height;
    area = (width * height) / 2;
    std::cout << "The area is " << area << '
';
    return (0);
}

The general form of a std::cin statement is:

std::cin >> variable;

This works for all types of simple variables such as int, float, char, and wchar_t.

Reading strings is a little more difficult. To read a string, use the statement:

std::getline(std::cin, string);

For example:

std::string name;    // The name of a person

std::getline(std::cin, name);

We discuss the std::getline function in Chapter 16.

When reading a string, the std::cin class considers anything up to the end-of-line part of the string. Example 5-5 reads a line from the keyboard and reports the line’s length.

Example 5-5. len/len.cpp
#include <string>
#include <iostream>

std::string line;       // A line of data

int main(  )
{
    std::cout << "Enter a line:";
    std::getline(std::cin, line);

    std::cout << "The length of the line is: " << line.length(  ) << '
';
    return (0);
}

When we run this program we get:

Enter a line:test 
The length of the line is: 4

Initializing Variables

C++ allows variables to be initialized in the declaration statement. For example, the following statement declares the integer counter and initializes it to 0.

   int counter(0);    // number cases counted so far

The older C-style syntax is also supported:

   int counter = 0;    // number cases counted so far

Arrays can be initialized in a similar manner. The element list must be enclosed in curly braces ({}). For example:

// Product numbers for the parts we are making
int product_codes[3] = {10, 972, 45};

This is equivalent to:

product_codes[0] = 10; 
product_codes[1] = 972; 
product_codes[2] = 45;

The number of elements in the curly braces ({}) does not have to match the array size. If too many numbers are present, a warning will be issued. If there are not enough numbers, the extra elements will be initialized to 0.

If no dimension is given, C++ will determine the dimension from the number of elements in the initialization list. For example, we could have initialized our variable product_codes with the statement:

// Product numbers for the parts we are making
int product_codes[] = {10, 972, 45};

Bounds Errors

In Example 5-2 the data array has five elements. So what happens if we accidently use an illegal index? For example:

int data[5];
// ...
result = data[99];   // Bad

The results are undefined. Most of the time you’ll get a random number. Other times, your program may abort with an error. Things get worse if you use the bad index on the left side of an assignment:

int data[5];
// ...
data[99] = 55;   // Very bad

In this case, if you’re lucky, the program will abort. If you’re not so lucky, the program will change a random memory location. This location could hold another variable, vital system data, or something else important. The result is that your program fails in a strange and hard to debug manner.

Therefore it is extremely important to make sure that any time you use an array index that it’s within bounds. Example 5-6 illustrates a program that contains an array bounds error.

Example 5-6. bounds/bound_err.cpp
#include <iostream>

const int N_PRIMES = 7; // Number of primes
// The first few prime numbers
int primes[N_PRIMES] = {2, 3, 5, 7, 11, 13, 17};

int main(  )
{
    int index = 10;

    std::cout << "The tenth prime is " << primes[index] << '
';
    return (0);
}

When this program executes on some machines it outputs:

Segmentation violation
Core dumped

On others it just gives us funny information:

The tenth prime is 0

Bounds errors are ugly, nasty things that should be stamped out whenever possible. One solution to this problem is to use the assert statement. The assert statement tells C++, “This can never happen, but if it does, abort the program in a nice way.” One thing you find out as you gain programming experience is that things that can “never happen” happen with alarming frequency. So just to make sure that things work as they are supposed to, it’s a good idea to put lots of self checks in your program.

The assert statement is one form of self check. When we add it to Example 5-6 we get Example 5-7.

Example 5-7. bounds/bound_c1.cpp
#include <iostream>
#include <cassert>

const int N_PRIMES = 7; // Number of primes
// The first few prime numbers
int primes[N_PRIMES] = {2, 3, 5, 7, 11, 13, 17};

int main(  )
{
    int index = 10;

    assert(index < N_PRIMES);
    assert(index >= 0);
    std::cout << "The tenth prime is " << primes[index] << '
';
    return (0);
}

The statement:

#include <cassert>

tells C++ that we want to use the assert module. Now we know that the index must be in range. After all, our program could never contain an error that might generate a bad index. But just to make sure, we check to see if it’s in range using the statement:

assert(index < N_PRIMES);
assert(index >= 0);

Now when the program hits the assert statement, the assertion fails and an error message is issued:

bound_c1: bound_c1.cpp:11: int main(  ): Assertion `index < 7' failed.
Abort (core dumped)

The program then aborts. Aborting is a nasty way of handling an error, but it’s better than doing nothing at all. (This is not true in every case; see sidebar.)

There is a problem with our code. The programmer must remember that the array data has a limit of N_PRIMES. This means that he has to count and to keep track of two things: the number of items in data and the value of N_PRIMES. It would be better to do things automatically.

The C++ operation sizeof returns the size of a variable in bytes. So sizeof(data) is 28 (7 elements of 4 bytes each). This assumes that integers are 4 bytes long. (This is system-dependent.)

But we want the number of elements in data, not the number of bytes. The size of the first element is sizeof(data[0]), and since each element is the same size, this is the size of every element. So we have the total number of bytes in the array and the number of bytes in an element. From this we can compute the number of elements in the array:

sizeof(data) / sizeof(data[0])

Now we can rewrite the code and eliminate the need to count elements. The results are in Example 5-8.

Example 5-8. bounds/bound_c2.cpp
#include <iostream>
#include <cassert>

// The first few prime numbers
int primes[] = {2, 3, 5, 7, 11, 13, 17};

int main(  )
{
    int index = 10;

    assert(index < (sizeof(primes)/sizeof(primes[0])));
    assert(index >= 0);

    std::cout << "The tenth prime is " << primes[index] << '
';
    return (0);
}

Multidimensional Arrays

Arrays can have more than one dimension. The declaration for a two-dimensional array is:

               type 
               variable[size1][size2]; // comment

For example:

// a typical matrix
int matrix[2][4];

Notice that C++ does not follow the notation used in other languages of matrix[10,12].

To access an element of the matrix we use the following notation:

matrix[1][2] = 10;

C++ allows you to use as many dimensions as needed (limited only by the amount of memory available). Additional dimensions can be tacked on:

four_dimensions[10][12][9][5];

Initializing multidimensional arrays is similar to initializing single-dimension arrays. A set of curly braces { } encloses each element. The declaration:

// a typical matrix
int matrix[2][4];

can be thought of as a declaration of an array of dimension 2 whose elements are arrays of dimension 4. This array is initialized as follows:

// a typical matrix
int matrix[2][4] = 
    { 
        {1, 2, 3, 4}, 
        {10, 20, 30, 40} 
    };

This is shorthand for:

matrix[0][0] = 1;
matrix[0][1] = 2;
matrix[0][2] = 3;
matrix[0][3] = 4;

matrix[1][0] = 10;
matrix[1][1] = 20;
matrix[1][2] = 30;
matrix[1][3] = 40;

Question 5-2: Why does the program in Example 5-9 print incorrect answers?

Example 5-9. array/array.cpp
#include <iostream>

int array[3][5] = {     // Two dimensional array
   { 0,  1,  2,  3,  4 },
   {10, 11, 12, 13, 14 },
   {20, 21, 22, 23, 24 }
};

int main(  )
{
    std::cout << "Last element is " << array[2,4] << '
';
    return (0);
}

When run on a Sun 3/50 this program generates:

Last element is 0x201e8

Your answers may vary.

You should be able to spot the error because one of the statements looks like it has a syntax error in it. It doesn’t, however, and the program compiles because we are using a new operator that has not yet been introduced. But even though you don’t know about this operator, you should be able to spot something funny in this program.

C-Style Strings

C++ lets you use not only the C++ std::string class, but also older C-style strings, as well. You may wonder why we would want to study a second type of string when the first one does just fine. The answer is that there are a lot of old C programs out there that have been converted to C++, and the use of C-style strings is quite common.

C-style strings are arrays of characters. The special character '' ( NUL) is used to indicate the end of a string. For example:

char    name[4]; 

int main(  ) 
{ 
    name[0] = 'S'; 
    name[1] = 'a'; 
    name[2] = 'm'; 
    name[3] = ''; 
    return (0); 
}

This creates a character array four elements long. Note that we had to allocate one character for the end-of-string marker.

String constants consist of text enclosed in double quotes (“). You may have already noticed that we’ve used string constants extensively for output with the std::cout standard class. C++ does not allow one array to be assigned to another, so you can’t write an assignment of the form:

name = "Sam";    // Illegal

Instead you must use the standard library function std::strcpy to copy the string constant into the variable. (std::strcpy copies the whole string, including the end-of-string character.) The definition of this function is in the header file cstring (note the lack of .h on the end).

To initialize the variable name to " Sam " you would write:

#include <cstring> 

char    name[4]; 

int main(  ) 
{ 
    std::strcpy(name, "Sam");    // Legal
    return (0); 
}

C++ uses variable-length strings. For example, the declaration:

#include <cstring> 

char a_string[50]; 

int main(  ) 
{ 
    std::strcpy(a_string, "Sam");

creates an array (a_string) that can contain up to 50 characters. The size of the array is 50, but the length of the string is 3. Any string up to 49 characters long can be stored in a_string. (One character is reserved for the NUL that indicates the end of the string.)

There are several standard routines that work on string variables. These are listed in Table 5-1 .

Table 5-1. String functions

Function

Description

std::strcpy(string1, string2)

Copies string2 into string1

std::strncpy(string1, string2, length)

Copies string2 into string1, but doesn’t copy over length characters (including the end of string character)

std::strcat(string1, string2)

Concatenates string2 onto the end of string1

std::strncat(string1, string2, length)

Concatenates string2 onto the end of string1, but only length characters (will not put an end of string character on the result if length characters are copied)

length = std::strlen(string)

Gets the length of a string

std::strcmp(string1, string2)

Returns 0 if string1 equals string2;

A negative number if string1 < string2

A positive number if string1 > string2

Example 5-10 illustrates how std::strcpy is used.

Example 5-10. str/sam.cpp
#include <iostream>
#include <cstring>

char name[30];  // First name of someone

int main(  )
{
    std::strcpy(name, "Sam");
    std::cout << "The name is " << name << '
';
    return (0);
}

Example 5-11 takes a first name and a last name and combines the two strings. The program works by initializing the variable first to the first name (Steve). The last name (Oualline) is put in the variable last. To construct the full name, the first name is copied into full_name. Then strcat is used to add a space. We call strcat again to tack on the last name.

The dimensions of the string variables are 100 because we know that no one we are going to encounter has a name more than 98 characters long. (One character is reserved for the space and one for the NUL at the end of the string.) If we get a name more than 99 characters long, our program will overflow the array, corrupting memory.

Example 5-11. name2/name2.cpp
#include <cstring>
#include <iostream>

char first[100];        // first name
char last[100];         // last name
char full_name[100];    // full version of first and last name

int main(  )
{
    std::strcpy(first, "Steve");     // Initalize first name
    std::strcpy(last, "Oualline");   // Initalize last name

    std::strcpy(full_name, first);   // full = "Steve"
    // Note: strcat not strcpy
    strcat(full_name, " ");     // full = "Steve " 
    strcat(full_name, last);    // full = "Steve Oualline" 

    std::cout << "The full name is " << full_name << '
';
    return (0);
}

The output of this program is:

The full name is Steve Oualline

C++ has a special shorthand for initializing strings, using double quotes (“) to simplify the initialization. The previous example could have been written:

char name[] = "Sam";

The dimension of name is 4, because C++ allocates a place for the '' character that ends the string.

C++ uses variable-length strings. For example, the declaration:

char long_name[50] = "Sam";

creates an array (long_name) that can contain up to 50 characters. The size of the array is 50, and the length of the string is 3. Any string up to 49 characters long can be stored in long_name. (One character is reserved for the NUL that indicates the end of the string.)

Tip

Our statement initialized only 4 of the 50 values in long_name. The other 46 elements are not initialized and may contain random data.

Safety and C Strings

The problem with strcpy is that it doesn’t check to see if the string being changed is big enough to hold the data being copied into it. For example, the following will overwrite random memory:

char name[5];
//...
strcpy(name, "Oualline");  // Corrupts memory

There are a number of ways around this problem:

  • Use C++ strings. They don’t have this problem.

  • Check the size before you copy:

    assert(sizeof(name) >= sizeof("Oualline"));
    strcpy(name, "Oualline");

    Although this method prevents us from corrupting memory, it does cause the program to abort.

  • Use the strncpy function to limit the number of characters copied. For example:

    std::strncpy(name, "Oualline", 4);

    In this example, only the first four characters of “Oualline” (Oual) are copied into name. A null character is then copied to end the string for a total of 5 characters—the size of name.

    A more reliable way of doing the same thing is to use the sizeof operator:

    std::strncpy(name, "Oualline", sizeof(name)-1);

    In this case we’ve had to add an adjustment of -1 to account for the null at the end of the string.

    This method does not corrupt memory, but strings that are too long will be truncated.

The strcat function has a similar problem. Give it too much data and it will overflow memory. One way to be safe is to put in assert statements:

char full_name[10];

assert(sizeof(name) >= sizeof("Steve"));
std::strcpy(name, "Steve");

// Because we're doing a strcat we have to take into account
// the number of characters already in name
assert(sizeof(name) >= ((strlen(name) + sizeof("Oualline")));
std::strcat(name, "Oualline");

The other way of doing things safely is to use strncat. But strncat has a problem: if it reaches the character limit for the number of characters to copy, it does not put the end-of-string null on the end. So we must manually put it on ourselves. Let’s take a look at how to do this. First we set up the program:

char full_name[10];

std::strncpy(name, "Steve", sizeof(name));

Next we add the last name, with a proper character limit:

std::strncat(name, "Oualline", sizeof(name)-strlen(name)-1);

If we fill the string, the strncat does not put on the end-of-string character. So to be safe, we put one in ourselves:

name[sizeof(name)-1] = '';

If the resulting string is shorter than the space available, strncat copies the end-of-string character. In this case our string will have two end-of-string characters. However, since we stop at the first one, the extra one later on does no damage.

Our complete code fragment looks like this:

char full_name[10];

std::strncpy(name, "Steve", sizeof(name));
std::strncat(name, "Oualline", sizeof(name)-strlen(name)-1);
name[sizeof(name)-1] = '';

You may notice that there is a slight problem with the code presented here. It takes the first name and adds the last name to it. It does not put a space between the two. So the resulting string is “SteveOualline” instead of “Steve Oualline” or, more accurately, “SteveOual” because of space limitations.

There are a lot of rules concerning the use of C-style strings. Not following the rules can result in programs that crash or have security problems. Unfortunately, too many programmers don’t follow the rules.

One nice thing about C++ strings is that the number of rules you have to follow to use them goes way down and the functionality goes way up. But there’s still a lot of C code that has been converted to C++. As a result, you’ll still see a lot of C-style strings.

Reading C-Style Strings

Reading a C-style string is accomplished the same way as it is with the C++ string class, through the use of the getline function:

char name[50];
// ....
std::getline(std::cin, name, sizeof(name));

A new parameter has been introduced: sizeof(name). Because C-style strings have a maximum length, you must tell the getline function the size of the string you are reading. That way it won’t get too many characters and overflow your array.

Converting Between C-Style and C++ Strings

To convert a C++ string to a C-style string, use the c_str( ) member function. For example:

char c_style[100];
std::string a_string("Something");
....
    std::strcpy(c_style, a_string.c_str(  ));

Conversion from C-style to C++-style is normally done automatically. For example:

a_string = c_style;

or

a_string = "C-style string constant";

However, sometimes you wish to make the conversion more explicit. This is done through a type change operator called a cast. The C++ operator static_cast converts one type to another. The general form of this construct is:

static_cast<new-type>(expression)

For example:

a_string = static_cast<std::string>(c_style);

Tip

There are actually four flavors of C++-style casts: static_cast, const_cast, dynamic_cast, and reinterpret_cast. The other three are discussed later in the book.

The Differences Between C++ and C-Style Strings

C++ style strings are easier to use and are designed to prevent problems. For example, the size of C-style strings is limited by the size of the array you declare. There is no size limit when you use a C++ std::string (other than the amount of storage in your computer). That’s because the C++ string automatically manages the storage for itself.

Size is a big problem in C-style strings. The std::strcpy and std::strcat functions do not check the size of the strings they are working with. This means that it is possible to copy a long string into a short variable and corrupt memory. It’s next to impossible to corrupt memory using C++ strings because size checking and memory allocation is built into the class.

But there is overhead associated with the C++ std::string class. Using it is not as fast as using C-style strings. But for almost all the programs you will probably write, the speed difference will be negligible. And since the risk associated with using C-style strings is significant, it’s better to use the C++ std::string class.

Types of Integers

C++ is considered a medium-level language because it allows you to get very close to the actual hardware of the machine. Some languages, such as Perl, go to great lengths to completely isolate the user from the details of how the processor works. This consistency comes at a great loss of efficiency. C++ lets you give detailed information about how the hardware is to be used.

For example, most machines let you use different-length numbers. Perl allows you to use only one simple data type (the string[1]). This simplifies programming, but Perl programs are inefficient. C++ allows you to specify many different kinds of integers so you can make best use of the hardware.

The type specifier int tells C++ to use the most efficient size (for the machine you are using) for the integer. This can be 2 to 4 bytes depending on the machine. Sometimes you need extra digits to store numbers larger than what is allowed in a normal int. The declaration:

long int answer;        // the answer of our calculations

is used to allocate a long integer. The long qualifier informs C++ that you wish to allocate extra storage for the integer. If you are going to use small numbers and wish to reduce storage, use the qualifier short.

short int year;         // Year including the century

C++ guarantees that the storage for short <= int <= long. In actual practice, short almost always allocates 2 bytes; long, 4 bytes; and int, 2 or 4 bytes. (See Appendix B for numeric ranges.)

Long integer constants end with the character “L”. For example:

long int var = 1234L;    // Set up a long variable

Actually you can use either an uppercase or a lowercase “L”. Uppercase is preferred since lowercase easily gets confused with the digit “1”.

long int funny = 12l;   // Is this 12<long> or one hundred twenty-one?

The type short int usually uses 2 bytes, or 16 bits. Normally, 15 bits are used for the number and 1 bit for the sign. This results in a range of -32,768 (-215) to 32,767 (215 - 1). An unsigned short int uses all 16 bits for the number, giving it a range of 0 to 65,535 (216 - 1). All int declarations default to signed, so the declaration:

    signed long int answer;     // final result

is the same as:

    long int answer;            // final result

Finally there is the very short integer, the type char. Character variables are usually 1 byte long. They can also be used for numbers in the range of -128 to 127 or 0 to 255. Unlike integers, they do not default to signed; the default is compiler-dependent.[2]

Question: Is the following character variable signed or unsigned?

char foo;

Answers:

  1. It’s signed.

  2. It’s unsigned.

  3. It’s compiler-dependent.

  4. If you always specify signed or unsigned, you don’t have to worry about problems like this.

Reading and writing very short integers is a little tricky. If you try to use a char variable in an output statement, it will be written—as a character. You need to trick C++ into believing that the char variable is an integer. This can be accomplished with the static_cast operator. Example 5-12 shows how to write a very short integer as a number.

Example 5-12. two2/twoc.cpp
#include <iostream>

signed char ch; // Very short integer 
                // Range is -128 to 127

int main() {
    ch = 37;
    std::cout << "The number is " << static_cast<int>(ch) << '
';      
    return (0);
}

We start by declaring a character variable ch. This variable is assigned the value 37. This is actually an integer, not a character, but C++ doesn’t care. On the next line, we write out the value of the variable. If we tried to write ch directly, C++ would treat it as a character. The code static_cast<int>(ch) tells C++, “Treat this character as an integer.”

Reading a very short integer is not possible. You must first read in the number as a short int and then assign it to a very short integer variable.

Summary of Integer Types

long int declarations allow the programmer to explicitly specify extra precision where it is needed (at the expense of memory). short int numbers save space but have a more limited range. The most compact integers have type char. They also have the most limited range.

unsigned numbers provide a way of doubling the range at the expense of eliminating negative numbers. The kind of number you use will depend on your program and storage requirements. The ranges of the various types of integers are listed in Appendix B.

Types of Floats

The float type also comes in various flavors. float denotes normal precision (usually 4 bytes). double indicates double precision (usually 8 bytes), giving the programmer twice the range and precision of single-precision (float) variables.

The quantifier long double denotes extended precision. On some systems this is the same as double; on others, it offers additional precision. All types of floating-point numbers are always signed.

On most machines, single-precision floating-point instructions execute faster (but less accurately) than double precision. Double precision gains accuracy at the expense of time and storage. In most cases float is adequate; however, if accuracy is a problem, switch to double (see Chapter 19).

Constant and Reference Declarations

Sometimes you want to use a value that does not change, such as π. The keyword const indicates a variable that never changes. To declare a value for pi, we use the statement:

const float PI = 3.1415926;    // The classic circle constant

Note

By convention variable names use lowercase only while constants use uppercase only. However, there is nothing in the language that requires this, and some programming projects use a different convention.

In fact, there are major programming environments such as the X Windows System that use their own naming conventions with mixed-case constants and variables (VisibilityChangeMask, XtConvertAndStore, etc.). The Microsoft Windows API also uses its own unique naming convention.

Constants must be initialized at declaration time and can never be changed. For example, if we tried to reset the value of PI to 3.0 we would generate an error message:

PI = 3.0;      // Illegal

Integer constants can be used as a size parameter when declaring an array:

const int TOTAL_MAX = 50;    // Max. number of elements in total list
float total_list[TOTAL_MAX]; // Total values for each category

Another special variable type is the reference type. The following is a typical reference declaration:

int count;                  // Number of items so far
int& actual_count = count;  // Another name for count

The special character & is used to tell C++ that actual_count is a reference. The declaration causes the names count and actual_count to refer to the same variable. For example, the following two statements are equivalent:

count = 5;            // "Actual_count" changes too
actual_count = 5;     // "Count" changes too

In other words, a simple variable declaration declares a box to put data in. A reference variable slaps another name on the box, as illustrated in Figure 5-1.

Reference variables
Figure 5-1. Reference variables

This form of the reference variable is not very useful. In fact, it is almost never used in actual programming. In Chapter 9, you’ll see how another form of the reference variable can be very useful.

Qualifiers

As you’ve seen, C++ allows you to specify a number of qualifiers for variable declarations. Qualifiers may be thought of as adjectives that describe the type that follows. Table 5-2 summarizes the various qualifiers; they are explained in detail in the following sections.

Table 5-2. Qualifiers and simple types

Special

Constant

Storage class

Size

Sign

Type

volatile

const

register

long

signed

int

<blank>

<blank>

static

short

unsigned

float

  

extern

double

<blank>

char

  

auto

<blank>

 

wchar_t

  

<blank>

  

<blank>

Special

The volatile keyword is used for specialized programming such as I/O drivers and shared memory applications. It is an advanced modifier whose use is far beyond the scope of this book.

volatile

Indicates a special variable whose value may change at any time

<blank>

Normal variable

Constant

The const keyword indicates a value that cannot be changed.

const

Indicates that this is a declaration of constant data

<blank>

Normal variable

Storage Class

The class of a variable is discussed in detail in Chapter 9. A brief description of the various classes follows:

register

This indicates a frequently used variable that should be kept in a machine register. See Chapter 17.

static

The meaning of this word depends on the context. This keyword is described in Chapter 9 and Chapter 23.

extern

The variable is defined in another file. See Chapter 23 for more information.

auto

A variable allocated from the stack. This keyword is hardly ever used.

<blank>

Indicates that the default storage class is selected. For variables declared outside a function, this makes the variable global. Variables inside a function are declared auto.

Size

The size qualifier allows you to select the most efficient size for the variable.

long

Indicates a larger than normal number.

short

Indicates a smaller than normal integer.

double

Indicates a double-size floating-point number.

<blank>

Indicates a normal-size number.

Sign

Numbers can be signed or unsigned. This qualifier applies only to char and int types. Floating-point numbers are always signed. The default is signed for int and undefined for characters.

Type

This specifies the type of the variable. Simple types include:

int

Integer.

float

Floating-point number.

char

Single character, but can also be used for a very short integer.

wchar_t

Single wide character. Can also be used for a short integer, but most people don’t because the other integer types are more appropriate to use.

Hexadecimal and Octal Constants

Integer numbers are specified as a string of digits, such as 1234, 88, -123, and so on. These are decimal (base 10) numbers: 174 or 17410. Computers deal with binary (base 2) numbers: 101011102. The octal (base 8) system easily converts to and from binary. Each group of three digits (23 = 8) can be transformed into a single octal digit. Thus 101011102 can be written as 10 101 110 and changed to the octal 2568. Hexadecimal (base 16) numbers have a similar conversion, but 4 bits at a time are used. For example, 100101002 is 1001 0100, or 9416.

The C++ language has conventions for representing octal and hexadecimal values. Leading zeros are used to signal an octal constant. For example, 0123 is 123 (octal) or 83 (decimal). Starting a number with “0x” indicates a hexadecimal (base 16) constant. So 0x15 is 21 (decimal). Table 5-3 shows several numbers in all three bases.

Table 5-3. Integer examples

Base 10

Base 8

Base 16

6

06

0x6

9

011

0x9

15

017

0xF

23

027

0x17

Question 5-3: Why does the following program fail to print the correct Zip code? What does it print instead?

#include <iostream>
long int zip;         // Zip code

int main(  )
{
    zip = 02137L;       // Use the Zip code for Cambridge MA

    std::cout << "New York's Zip code is: " << zip << '
';
    return(0);
}

Operators for Performing Shortcuts

C++ not only provides you with a rich set of declarations, but also gives you a large number of special-purpose operators. Frequently a programmer wants to increment (add 1 to) a variable. Using a normal assignment statement, this would look like:

total_entries = total_entries + 1;

C++ provides you a shorthand for performing this common task. The ++ operator is used for incrementing:

++total_entries;

A similar operator, -- , can be used for decrementing (subtracting 1 from) a variable.

--number_left; 
// Is the same as
number_left = number_left - 1;

But suppose you want to add 2 instead of 1. Then you can use the following notation:

total_entries += 2;

This is equivalent to:

total_entries = total_entries + 2;

Each of the simple operators shown in Table 5-4 can be used in this manner.

Table 5-4. Shorthand operators

Operator

Shorthand

Equivalent statement

+=

x += 2;

x = x + 2;

-=

x -= 2;

x = x - 2;

*=

x *= 2;

x = x * 2;

/=

x /= 2;

x = x / 2;

%=

x %= 2;

x = x % 2;

++

++x;

x = x + 1;

--

-- x;

x = x - 1;

Side Effects

Unfortunately, C++ allows you to use side effects. A side effect is an operation that is performed in addition to the main operation executed by the statement. For example, the following is legal C++ code:

size = 5; 
result = ++size;

The first statement assigns size the value of 5. The second statement:

  1. Increments size (side effect)

  2. Assigns result the value of size (main operation)

But in what order? There are three possible answers:

  1. result is assigned the value of size (5), then size is incremented.

    result is 5 and size is 6.

  2. size is incremented, then result is assigned the value of size (6).

    result is 6 and size is 6.

  3. If you don’t write code like this, you don’t have to worry about these sorts of questions.

The correct answer is 2: The increment occurs before the assignment. However, 3 is a much better answer. The main effects of C++ are confusing enough without having to worry about side effects.

Tip

Some programmers highly value compact code. This is a holdover from the early days of computing when storage cost a significant amount of money. It is my view that the art of programming has evolved to the point where clarity is much more valuable than compactness. (Great novels, which a lot of people enjoy reading, are not written in shorthand.)

C++ actually provides two forms of the ++ operator. One is variable ++ and the other is ++ variable. The first:

    number = 5; 
    result = number++;

evaluates the expression and then increments the number, so result is 5. The second:

    number = 5; 
    result = ++number;

increments first and then evaluates the expression. In this case result is 6. However, using ++ or -- in this way can lead to some surprising code:

    o = --o - o--;

The problem with this is that it looks like someone is writing Morse code. The programmer doesn’t read this statement; she decodes it. If you never use ++ or -- as part of any other statement, but always put them on a line by themselves, the difference between the two forms of these operators is not noticeable.

Note

The prefix form ++variable is preferred over the suffix form variable++ because it allows the compiler to generate slightly simpler code.

(Actually, the code for simple integers is not more complex, but when we get into operator overloading, we’ll see that it takes more code to write the suffix version of the increment operator (variable++) than it does to write the prefix version (++variable). (See Chapter 18.) To be constant, we always use the prefix form.)

More complex side effects can confuse even the C++ compiler. Consider the following code fragment:

value = 1; 
result = (value++ * 5) + (value++ * 3);

This expression tells C++ to perform the following steps:

  1. Multiply value by 5 and add 1 to value.

  2. Multiply value by 3 and add 1 to value.

  3. Add the results of the two multiples together.

Steps 1 and 2 are of equal priority, unlike the previous example, so the compiler can execute them in any order it wants to. It may decide to execute step 1 first, as shown in Figure 5-2, but it may execute step 2 first, as shown in Figure 5-3.

Expression evaluation, method 1
Figure 5-2. Expression evaluation, method 1
Expression evaluation, method 2
Figure 5-3. Expression evaluation, method 2

Using the first method, we get a result of 11; using the second method, the result is 13. The result of this expression is ambiguous. By using the operator ++ in the middle of a larger expression, we created a problem. (This is not the only problem that ++ and -- can cause. We will get into more trouble in Chapter 10.)

To avoid trouble and keep programs simple, always put ++ and -- on a line by themselves.

Programming Exercises

Exercise 5-1: Write a program that converts Celsius to Fahrenheit. The formula is

F = 9/5 C + 32.

Exercise 5-2: Write a program to calculate the volume of a sphere,

4/3πr3.

Exercise 5-3: Write a program to print out the perimeter of a rectangle given its height and width.

perimeter = 2 · (width + height)

Exercise 5-4: Write a program that converts kilometers per hour to miles per hour.

miles = (kilometers · 0.6213712)

Exercise 5-5: Write a program that takes hours and minutes as input and outputs the total number of minutes (e.g., 1 hour 30 minutes = 90 minutes).

Exercise 5-6: Write a program that takes an integer as the number of minutes and outputs the total hours and minutes (e.g., 90 minutes = 1 hour 30 minutes).

Answers to Chapter Questions

Answer 5-1: The programmer accidentally omitted the end-comment symbol ( */ ) after the comment for height. The comment continues onto the next line and engulfs the width variable declaration. Example 5-13 shows the program with the comments underlined.

Example 5-13. Triangle area program
#include <iostream>

int  height;   /* The height of the triangle
                  int  width;    /* The width of the triangle*/
int  area;     /* Area of the triangle (computed) */

int main(  )
{
    std::cout << "Enter width and height? ";
    std::cin >> width >> height;
    area = (width * height) / 2;
    std::cout << "The area is " << area << '
';
    return (0);
}

Some people may think that it’s unfair to put a missing comment problem in the middle of a chapter on basic declarations. But no one said C++ was fair. You will hit surprises like this when you program in C++ and you’ll hit a few more in this book.

Answer 5-2: The problem is with the way we specified the element of the array: array[2,4]. This should have been written: array[2] [4].

The reason that the specification array[2,4] does not generate a syntax error is that it is legal (but strange) C++. There is a comma operator in C++ (see Chapter 29), so the expression 2,4 evaluates to 4. So array[2,4] is the same as array[4]. C++ treats this as a pointer (see Chapter 15), and what’s printed is a memory address, which on most systems looks like a random hexadecimal number.

Answer 5-3: The problem is that the Zip code 02137 begins with a zero. That tells C++ that 02137 is an octal constant. When we print it, we print in decimal. Because 021378 is 111910 the program prints:

New York's Zip code is: 1119


[1] Perl does have numbers such as 5, 8.3, and 20.8, but they are identical to the strings “5”, “8.3”, and “20.8”.

[2] Borland-C++ even has a command-line switch to make the default for type char either signed or unsigned.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.157.6