Previous | Table of Contents | Next |
Fortunately, we can solve that problem, tooby moving the declaration into a separate file. We will then include that file in greet.c, and also in main.c, which will ensure that it has the same contents in both contexts.
We will therefore define a source file called greet.h that has only the declaration of greet:
// source file greet.h void greet();
Then we use that file in both greet.c and main.c:
// file greet.c, final version #include <iostream> #include greet.h using namespace std; void greet() { cout << Hello, world! << endl; } // file main.c, final version #include greet.h int main() { greet(); return 0; }
Our small program has grown into three source files. The first, greet.h, exists only so that #include directives can copy it into other files. This ensures that if the declaration of greet ever changes, it is necessary to change it in only a single place. The next one is greet.c, which defines the greet function. By including greet.h, greet.c ensures that the definition (in greet.c) and the declaration (in greet.h) are consistent. The last file contains the main program, which needs the declaration of greet (from greet.h) but not its definition.
This kind of program structure is important for large projects, in which several people may be working on different source files independently, and in which multiple versions of source files may exist. However, it implies that there is not always a direct correspondence between what we think of as the key parts of a program and the physical manifestations of those parts. To see this, consider the greet.h file. Nothing in the C++ language requires that file to exist, or that there be only one such file. We could have written a declaration of the greet function in every translation unit that uses that function, and as long as all the declarations were mutually consistent, there would be nothing wrong. But we deliberately chose to put a single declaration for greet into a separate source file, and then used the preprocessor to cause that file to be copied into every translation unit. By doing so, we ensure that all the declarations are consistent with each other. Indeed, we effectively gain the right to talk about the declaration of greet, because all the declarations come from a single piece of source code. This technique of splitting a program into source files so as to avoid having to duplicate declarations pervades C++ programs.
7.2.2.1. Integers
Because C++ is intended to be able to run efficiently on a wide variety of hardware, it leaves many of the details of its fundamental types up to the implementation rather than defining those types precisely. The idea is to allow compilers to choose the most natural representation for each of the arithmetic types, while still constraining the types enough to allow programmers to write useful programs. C++ offers an assortment of different ways of storing data because such choices can have a crucial effect on the efficiencyespecially the space efficiencyof programs.
Accordingly, there are three distinct signed integer types and three distinct unsigned integer types. The signed types are short int (or just short), int, and long int (or just long); the unsigned types are unsigned short int, unsigned int, and unsigned long int (or unsigned short, unsigned, and unsigned long). The keywords, if there are more than one, can appear in any order. Compilers must implement each of the short types so as to contain at least 16 bits, as they must also for each of the plain int types. The long types must occupy at least 32 bits. Moreover, each long type must be at least as long as each plain int type, which must be at least as long as each short type. Compilers are allowed to use either ones- or twos-complement notation for the signed types.
In other words, a short or a plain int variable is guaranteed to hold any value between -32767 and 32767, inclusive, and implementations are permitted to allow a wider range. A long variable must be able to hold any value between -2147483647 and 2147483647, inclusive, and implementations might allow a wider range. Similarly, an unsigned short must be able to contain any value between 0 and 65535, and so on.
Why bother with separate int and short types if they have the same requirements? Although the language definition doesnt actually say, the idea is that short variables will store only small integers, regardless of context; int variables will store values that are commensurate with the size of memory; and long variables can potentially store the largest integers that the implementation can handle. In particular, array indices have type int, so an implementation that restricts the magnitude of int values to 215 will similarly restrict the sizes of arrays.
There are few surprises in the definition of integer arithmetic. The operators +, -, *, and / are defined with their usual meanings, and % is the remainder operator. Unsigned arithmetic is always modulo 2n, where n is the number of bits in the particular unsigned values being used. Signed integer division is permitted to truncate the quotient toward zero or toward −∞, as long as the behavior of / is consistent with the behavior of %. In other words, it is required that ((a/b)*b)+(a%b) be equal to a whenever b is nonzero.
Previous | Table of Contents | Next |