In This Chapter
How Big Is a Small Project
Bootstrapping Your Knowledge
Creating the Project
Examining the Code
Code Spelunking
Analyzing the Code
With a fairly simple program such as Decryptix!, my first goal is to get a version up and running--and to keep it running. After it is working, I'll add features, redesigning on-the-fly as I go.
On a large project, this can be a fatally inefficient process. As features are added, the complexity of the overall project grows, and without a good design you end up with code that is hard to maintain.
With a smaller project such as Decryptix!, however, the risk is minimal. If a new feature requires a complete redesign and rewrite of the program, no problem: It only takes a couple of days to write it in the first place.
How Big Is a Small Project?
How small must a program be to design it as you go? I'd argue that any program that takes one person more than a few weeks to program ought to be subject to a more rigorous design process. Here's why: Programs that evolve organically, rather than by design, often have to be rewritten at least once. If doing so is painful, it is worth working out the design up front. If rewriting is trivial, however, nothing was lost by diving right in.
"Why," I hear you ask, "show an example of organic design if all large projects require formal design?" The answer is fairly straightforward: There's a lot to learn in both programming and design. This book aims to teach programming; you'll find lots of books (including a few I wrote) on object-oriented analysis and design. You can't learn everything at once.
Nothing I teach in this book is inconsistent with good design; we just won't take the time to design everything up front. I don't know about you, but I'm eager to dive into some code.
Bootstrapping Your Knowledge
In a classic C++ primer, I'd start with the structure of the program, introduce statements and expressions, add variables and constants, and then turn to classes. I'd build skill upon skill, and I'd be about 600 pages into the book before you could even begin to write your Decryptix! program.
This book is different. You're going to jump right in and wallow around awhile. Not all of it will make sense at first, and I'll gloss over lots of detail only to return to it later in the book, but the essential flow of the program can be explained pretty quickly. From time to time you'll take an "Excursion" to related--but not strictly relevant--areas of C++.
Creating the Project
This book is designed to be of use regardless of which compiler you are using or what platform (for example, Windows or Mac) you are developing for. From time to time, however, I'll demonstrate how you can accomplish a specific task in Microsoft Visual C++ 6.0. Your compiler might be somewhat different, but the principles are the same. With the knowledge that is provided here you can easily read the documentation for your compiler and make the necessary adjustments.
I begin by creating a project. On the drive on which I installed my compiler I have created a directory called Decryptix Projects. This will house all the versions of the program I will create.
First I start Visual C++ and tell it to create a new Win32 Console Application called Decryptix, as shown in Figure 2.1.
Figure 2.1 Microsoft Visual C++ New Project.
If your compiler offers a wizard (a series of dialog boxes that helps you make these decisions), choose whatever provides you with the simplest, text-based, non-windowed, ISO-standard environment. In this case, I choose empty application.
After creating the project, Visual C++ drops me in the Integrated Development Environment (IDE). I choose File, New, and enter a new C++ source file named Decryptix.cpp.
In other environments, for example a text editor in UNIX, I'd just open a new file and save it as Decryptix.cpp. Often, in IDEs, saving the file with the .cpp extension signals that this is C++ source code and turns on source code indentation support (and, sometimes, color-coded text!). Source code indentation support means that when you type your source code the editor automatically indents it properly for you. Thus, if you enter
if ( someValue > thisValue )
and then press Enter, the editor automatically indents the next line. (Don't worry about what this code does, it will all be explained in time.)
NOTE: To learn what support your editor provides, please check the documentation that comes with your compiler.
Examining the Code
Now take a look at a preliminary version of Decryptix, in Listing 2.1. You can open a file in your project, save it as Decryptix.cpp, and then enter this code, exactly as shown.
TIP: I strongly advise you to enter all the source code yourself because that is the best way to learn. If you simply can't stand the thought of all that typing, however, you can retrieve this code from the CD that accompanies this book, or you can download this code--and all the code for this book--from my Web site (go to www.libertyassociates.com and click on Books & Resources).
This program is quite advanced, and of course you won't understand much of what you are reading. Don't be intimidated, however; this chapter and Chapter 3, "Program Flow," go over it line by line. You might find, however, that you can get a pretty good idea of what the program is doing just by reading it as prose.
Try running it and examining what it does, and then try matching the code to the output.
Listing 2.1 First Glimpse of Decryptix!
0: #include <iostream> 1: 2: int main() 3: { 4: std::cout << "Decryptix. Copyright 1999 Liberty "; 5: std::cout << "Associates, Inc. Version 0.2\n " << std::endl; 6: std::cout << "There are two ways to play Decryptix: "; 7: std::cout << " either you can guess a pattern I create, "; 8: std::cout << "or I can guess your pattern.\n\n"; 9: 10: std::cout << "If you are guessing, I will think of a\n "; 11: std::cout << "pattern of letters (e.g., abcde).\n\n"; 12: 13: std::cout << "On each turn, you guess the pattern and\n"; 14: std::cout << " I will tell you how many letters you \n"; 15: std::cout << "got right, and how many of the correct\n"; 16: std::cout << " letters were in the correct position.\n\n"; 17: 18: std::cout << "The goal is to decode the puzzle as quickly\n"; 19: std::cout << "as possible. You control how many letters \n"; 20: std::cout << "can be used and how many positions\n"; 21: std::cout << " (e.g., 5 possible letters in 4 positions) \n"; 22: std::cout << "as well as whether or not the pattern might\n"; 23: std::cout << " contain duplicate letters (e.g., aabcd).\n\n"; 24: 25: std::cout << "If I'm guessing, you think of a pattern \n"; 26: std::cout << "and score each of my answers.\n\n" << std::endl; 27: 28: const int minLetters = 2; 29: const int maxLetters = 10; 30: const int minPositions = 3; 31: const int maxPositions = 10; 32: 33: int howManyLetters = 0, howManyPositions = 0; 34: bool duplicatesAllowed = false; 35: int round = 1; 36: 37: std::cout << "How many letters? ("; 38: std::cout << minLetters << "-" << maxLetters << "): "; 39: std::cin >> howManyLetters; 40: 41: std::cout << "How many positions? ("; 42: std::cout << minPositions << "-" << maxPositions << "): "; 43: std::cin >> howManyPositions; 44: 45: char choice; 46: std::cout << "Allow duplicates (y/n)? "; 47: std::cin >> choice; 48: 49: return 0; 50: }
Compile, link, and run this program. In Visual C++ you can do all this at once by pressing Ctrl+F5. Here's the output:
Decryptix. Copyright 1999 Liberty Associates, Inc. Version 0.2 There are two ways to play Decryptix: either you can guess a pattern I create, or I can guess your pattern. If you are guessing, I will think of a pattern of letters (e.g., abcde). On each turn, you guess the pattern and I will tell you how many letters you got right, and how many of the correct letters were in the correct position. The goal is to decode the puzzle as quickly as possible. You control how many letters can be used and how many positions (e.g., 5 possible letters in 4 positions) as well as whether or not the pattern might contain duplicate letters (e.g., aabcd). If I'm guessing, you think of a pattern and score each of my answers. How many letters? (2-10):
Analyzing the Code
The very first line of this program (Line 0) is
#include <iostream>
The goal of this line is to add to your current file the information it needs to support Input and Output streaming: the capability to read from the keyboard (input) and write to the screen (output).
Input Stream--How data comes into your program; typically from the keyboard
Output Stream--How data leaves your program; typically to the display
Here's how it works: C++ now includes a group of supporting code called the standard library, which provides objects to handle input and output. cin is an object that handles input from the keyboard, and cout is an object that handles output to the screen. The details of how they work are not important at this point, but to use them you must include in your program the file iostream, which provides their definitions. The definition of an object tells the compiler what it needs to know in order for the object to be used.
cin is pronounced see-in, and cout is pronounced see-out.
You include this file in your program with the #include statement. When your compiler is invoked, the precompiler runs, reading through your program and looking for lines that begin with the # symbol. When it sees #include, it knows it must read in a file. The angle brackets (< and >) say "look in the usual place." When you installed your compiler, it should have set up "the usual place" to look for these files.
Some folks pronounce # as hash, others as cross-hash. I call it pound, so I pronounce this line of code pound include eye-oh-stream .
The net effect is that the file iostream is read into your program at this point, which is just what you want. You can now use the cout object, as you'll see in a few moments.
NOTE: Using the angle brackets, <iostream> indicates that the precompiler is to "look in the usual place." An alternative is to use double quote marks--for instance "myfile.h"--which say "look in the current project directory and, failing that, look in the usual place."
Unlike the code in Chapter 1, "Introduction," this version uses the new ANSI/ISO standard library header file <iostream> rather than <iostream.h> (note that the new header doesn't use .h).
These headers support the new namespace protocols, which enable you to avoid conflicts in the names of objects and methods when working with code you buy from other vendors. For example, there might be two objects named cout. We solve this by "qualifying" the name with std::, as shown on lines 4-26. This qualification with std:: indicates to the compiler that it is to use the cout object that is defined in the standard (std) library, which comes with your compiler.
Unfortunately, this makes the code look much more complicated and difficult to read.
To simplify this code and to make it easier for us to focus on the issues we care about, I'll rewrite the preceding example by adding the keywords
using namespace std;
This signals to the compiler that the code I'm writing is within the std (standard) namespace. In effect, it tells the compiler that when it sees cout it is to treat it like std::cout.
NOTE: All the rest of the code in the book uses this trick, which makes the code much easier to read and follow, at the cost of undermining the protection that namespaces afford.
When you write your commercial applications you might want to eschew the using namespace idiom because you might want to ensure namespace protection.
Listing 2.1a is an exact replica of Listing 2.1, except that it takes advantage of the using namespace idiom.
0: #include <iostream> 1: using namespace std; 2: int main() 3: { 4: cout << "Decryptix. Copyright 1999 Liberty "; 5: cout << "Associates, Inc. Version 0.2\n " << endl; 6: 7: cout << "There are two ways to play Decryptix: "; 8: cout << " either you can guess a pattern I create, "; 9: cout << "or I can guess your pattern.\n\n"; 10: 11: cout << "If you are guessing, I will think of a\n "; 12: cout << "pattern of letters (e.g., abcde).\n\n"; 13: 14: cout << "On each turn, you guess the pattern and\n"; 15: cout << " I will tell you how many letters you \n"; 16: cout << "got right, and how many of the correct\n"; 17: cout << " letters were in the correct position.\n\n"; 18: 19: cout << "The goal is to decode the puzzle as quickly\n"; 20: cout << "as possible. You control how many letters \n"; 21: cout << "can be used and how many positions\n"; 22: cout << " (e.g., 5 possible letters in 4 positions) \n"; 23: cout << "as well as whether or not the pattern might\n"; 24: cout << " contain duplicate letters (e.g., aabcd).\n\n"; 25: 26: cout << "If I'm guessing, you think of a pattern \n"; 27: cout << "and score each of my answers.\n\n" << endl; 28: 29: const int minLetters = 2; 30: const int maxLetters = 10; 31: const int minPositions = 3; 32: const int maxPositions = 10; 33: 34: int howManyLetters = 0, howManyPositions = 0; 35: bool duplicatesAllowed = false; 36: int round = 1; 37: 38: cout << "How many letters? ("; 39: cout << minLetters << "-" << maxLetters << "): "; 40: cin >> howManyLetters; 41: 42: cout << "How many positions? ("; 43: cout << minPositions << "-" << maxPositions << "): "; 44: cin >> howManyPositions; 45: 46: char choice; 47: cout << "Allow duplicates (y/n)? "; 48: cin >> choice; 49: 50: return 0; 51: }
Code Spelunking
One of the most powerful ways to learn C++ is to use your debugger. I highly recommend that immediately after entering this code into your project (or downloading it from my site), you compile, link, and run it. You'll need to check your documentation for how to do this, but most modern IDEs offer a menu choice to "Build the entire project."
If you are using Visual C++, you can simply point your cursor at the buttons on the toolbar until you find the ones that compile and link or that build the entire project.
After it is working, set this book aside and pick up the documentation for your debugger, which you'll find with the documentation for your compiler. Set a break point on the first line of code in main() (see line 5 in Listing 2.1). In Visual C++ you just put your cursor on that line and press F9, or press the break point toolbar button. Once the break point is set, run to the break point (in Visual C++, press F5). Step over each line of code and try to guess what is going on. Again, you'll need to check your documentation for how to step over each line of code (in Visual C++ it is F10).
The debugger is one of the last things most primers introduce; I feel that it needs to be one of the very first things you learn. If you get stuck, see the exploration of debugging at the end of this chapter.
Every C++ program has a main() function (Listing 2.1, line 2). The general purpose of a function is to run a little code and then return to whomever called you.
All functions begin and end with parentheses, as you can see on lines 3 and 51. A function consists of a series of statements, which are all the lines that are shown between the parentheses.
This is the essence of a structured program. Program flow continues in the order in which the code appears in the file until a function is called. The flow then branches off to the function and follows line by line until another function is called or until the function returns (see Figure 2.2).
In a sense, a function is a subprogram. In some languages, it is called a subroutine or a procedure. The job of a function is to accomplish some work and then return control to whatever invoked the function.
Figure 2.2 When a program calls a fuction, execution switches to the function and then resumes at the line after the function call.
When main() executes, we execute Statement1. We then branch to line 1 of Func1(). Func1's three lines execute, and then processing returns to main(), where we execute Statement2. Func2 is then called, which in turn calls Func3(). When Func3 completes it returns to Func2(), which continues to run until its own return statement, at which time we return to main() and execute Statement3. We then call Func4(), which executes its own code and then returns to main(), where we execute Statement4.
When a function returns to whoever called it, it can return a value. You'll see later what the calling function can do with that value.
Every function must declare what kind of value it returns: For example, does it return an integer or a character? If a function does not return a value, it declares itself to return void, which means that it returns nothing.
main() is a special function in C++. All C++ programs begin with main(); when main ends, the program ends. In a sense, the operating system (Windows, DOS, and so on) calls main().
main() always returns an int (integer). I'll discuss the various types of values later in the book; for now it is sufficient to know that you must always declare main to return an integer.
NOTE: On some older compilers, you can have main() return void, but that is not legal under the new ISO standard. It is a good idea to get into the habit of having main() return an int every time.
You'll notice that main() does return an integer (in this case, 0) on line 50. When programs are run from batch files or scripts, you can examine these values. For the programs in this book (and probably for most of the programs you will write), this value is discarded. By convention, you'll return 0 to indicate that the program ran without incident.
Most of the statements in this very first program are designed to write to the screen. Use the standard output object cout. You send a string of characters to cout by enclosing them in quotation marks and by using the output redirection operator (<<), which you create by holding the Shift key and pressing the comma key twice.
This actually takes advantage of a very advanced feature in C++ called operator overloading, which is discussed in detail in Chapter 6, "Using Linked Lists." Fortunately, for now you can use this feature without fully understanding it. The net effect is that the words
Decryptix. Copyright 1999 Liberty
are sent to the screen.
Operator Overloading--The capability of user-created types to use the operators that built-in types use, such as +, =, and ==. I explain how to do this in Chapter 6.
Line 5 prints the words
Associates, Inc. Version 0.2
to the screen. Notice that before the closing quotes, line 5 includes \n. These are two special marks within quoted strings. The slash is called an escape character, and when it is found in a quoted string it means "what follows is a special instruction to the compiler." The letter n, when it follows the escape character, stands for "new line." Thus, the effect is to print, to the output, a new line.
Escape character--A character that serves as a signal to the compiler or precompiler that the letter that follows requires special treatment. For example, the precompiler usually treats the character n as a letter, but when it is preceded by the escape character (\n), it indicates a new line.
Notice also that this line ends with
<< endl;
cout can receive more than just strings. In this case, the redirection operator is being used to send endl.
NOTE: endl is pronounced end-ell and stands for "end line."
This sends another new line to the output and flushes out the buffers. Buffers will be explained later, when I talk about streams, but the net effect ensures that all the text is written to the screen immediately.
Line 7 begins to print another line, which is continued on line 8 and completed on line 9.
Together, these lines print the following output:
Decryptix. Copyright 1999 Liberty Associates, Inc. Version 0.2 There are two ways to play Decryptix: either you can guess a pattern I create, or I can guess your pattern.
Note first that there is no new line after Liberty and before Associates. There was no instruction to cout to print a new line, so none was printed. Two new lines appear after 0.2. The first, created by the \n character, ends the line; the second, created by endl, skips a line.
You can achieve the effect of skipping a line by putting in two \n characters, as shown on line 9.
Table 2.1 illustrates the other special printing characters.
Table 2.1Special Printing Characters
Character |
What it means |
\n |
new line |
\t |
tab |
\b |
rings the bell |
\" |
prints a double quote |
\' |
prints a single quote |
\? |
prints a question mark |
\\ |
prints a backslash |
A variable is a place to store a value during the progress of your program.
Variable--A place to store a value
In this case, at line 36, you want to keep track of what round of play you are up to. Store this information in a variable named round:
int round = 1;
One way to think of your computer's memory is as a series of cubbyholes. Each cubbyhole is one byte, and every byte is numbered sequentially: The number is the address of that memory. Each variable reserves one or more bytes in which you can store a value.
Your variable's name (round) is a label on one of these cubbyholes, which enables you to find it easily without knowing its actual memory address.
Think of it like this: When you jump in a cab in Washington, D.C., you can ask for 1600 Pennsylvania Avenue, or you can ask for The White House. The identifier "the White House" is the name of that address.
Figure 2.3 is a schematic representation of this idea. As you can see from the figure, round starts at memory address 103. Depending on the size of round, it can take up one or more memory addresses.
Figure 2.3 A schematic representation of memory.
RAM is random access memory. When you run your program, it is loaded into RAM from the disk file. All variables are also created in RAM. When programmers talk about memory, they are usually referring to RAM.
When you define a variable in C++, you must tell the compiler what kind of variable you are declaring: an int, char, and so forth. The type tells the compiler the size of the variable. For example, a char is 1 byte, and on modern computers an int is 4 bytes; thus, the variable round consumes four bytes (cubbyholes) of memory.
You define a variable by stating its type, followed by one or more spaces, the variable name, and a semicolon:
int round;
The variable name can be virtually any combination of letters, but it cannot contain spaces. Legal variable names include x, J23qrsnf, and myAge. It is good programming practice to use variable names that tell you what the variables are for. This makes them easier to understand, which makes it easier for you to maintain your program.
C++ is case sensitive; therefore, a variable named round is different from Round, which is different from ROUND. Avoid using multiple variables whose names differ only by capitalization--it can be terribly confusing.
NOTE: Some compilers enable you to turn case sensitivity off. Don't be tempted to do this. Your programs won't work with other compilers, and other C++ programmers will be very confused by your code.
C++ reserves some words, and you cannot use them as variable names. These are keywords that are used by the compiler to control your program. Keywords include if, while, for, and main. Your compiler manual probably provides a complete list, but generally, any reasonable name for a variable is almost certainly not a keyword.
Creating More Than One Variable at a Time
You can create more than one variable of the same type in one statement by writing the type and then the variable names, separated by commas. For example
int howManyLetters, howManyPositions; bool valid, duplicatesAllowed;
Back in listing 2.1, at line 36, a local variable is defined by stating the type (int) and the variable name (round).
This actually allocates memory for the variable. Because an int is four bytes, this allocates four bytes of memory. When the compiler allocates memory, it reserves the memory for the use of your variable and assigns the name that you provide (in this case, round).
Scope refers to the region of a program in which an identifier--something that is named, such as an object, variable, function, or constant--is valid. When I say a variable has local scope, I mean that it is valid within a particular function.
Scope--The region of a program in which an identifier (that is, the name of something) is valid.
Local scope--When an identifier has local scope, it is valid within a particular function.
There are other levels of scope (global, static member, and so on) that I will discuss as I progress through the program.
Local variables, such as round, have a value when they are created regardless of whether you initialize them. If you don't initialize them (as shown here), whatever happened to already be in the bit of memory is assigned to them--that is, a random garbage value.
It is good programming practice to initialize your variables. When you initialize a variable, you create it and give it a specific value, all in one step:
int round = 1;
This creates the variable round and initializes it with the value 1.
Just as you can define more than one variable at a time, you can initialize more than one variable. For example,
int howManyLetters = 0, howManyPositions = 0;
initializes the two variables howManyLetters, each to the value 0. You can even mix definitions and initializations:
int howManyLetters = 0, round, howManyPositions = 2;
This example defines three variables of type int, and it initializes the first and third.
On line 46 of Listing 2.1, you created a character variable (type char) named choice. On most computers, character variables are 1 byte, enough to hold 256 values. A char can be interpreted as a small number (0-255) or as a member of the ASCII set. ASCII stands for the American Standard Code for Information Interchange. The ASCII character set and its ISO (International Standards Organization) equivalent are a way to encode all the letters, numerals, and punctuation marks.
ASCII--The American Standard Code for Information Interchange
ISO--The International Standards Organization
You create a character by placing the letter in single quotes. Therefore, 'a' creates the character a.
In the ASCII code, the lowercase letter a is assigned the value 97. All the lower- and uppercase letters, all the numerals, and all the punctuation marks are assigned values between 1 and 128. Another 128 marks and symbols are reserved for use by the computer maker.
Characters and Numbers
When you insert a character--'a', for example--into a char variable, what is really there is just a number between 0 and 255. The compiler knows, however, how to translate back and forth between characters and one of the ASCII values.
The value/letter relationship is arbitrary; there is no particular reason that the lowercase a is assigned the value 97. As long as everyone (your keyboard, compiler, and screen) agrees, there is no problem. It is important to realize, however, that there is a big difference between the value 5 and the character '5'. The latter is actually valued at 53, much as the letter 'a' is valued at 97.
Listing 2.2 is a simple program that prints the character values for the integers 32-127. Pay no attention to the details of this program--we will walk through how this works later in the book.
Listing 2.2 Printing out the Characters
#include <iostream > using namespace std; int main() { for (int i = 32; i<128; i++) cout << (char) i; return 0; } !"#$%G'()*+,./0123456789:;<>?@ABCDEFGHIJKLMNOP _QRSTUVWXYZ[\]^'abcdefghijklmnopqrstuvwxyz<|>~s
NOTE: Your computer might print a slightly different list.
C++ comes right out of the box with knowledge of a number of primitive built-in types. The type of a variable or object defines its size, its attributes, and its capabilities.
For example, an int is, on modern compilers, 4 bytes in size. It holds a value from -2,147,483,648 to 2,147,483,647. For more on bytes and why 2,147,483,648 is a round number, see Appendix A, "Binary and Hexadecimal."
You might think that an integer is an integer, but it isn't quite. The keyword integer refers to a four-byte value, but only if you are using a modern compiler on a modern 32-bit computer. If your software or computer is 16-bit, however, an integer might be only two bytes. The keyword short usually refers to a two-byte integer, and the keyword long most often refers to a four-byte integer, but neither of these is certain. The language requires only that a short is shorter than or equal to an integer, and an integer is shorter than or equal to a long. On my computer, a short is 2 bytes and an integer is 4, as is a long.
ISO C++ provides the types that are listed in Table 2.2.
Table 2.2 Variable Types
Type |
Size |
Values |
unsigned short int |
2 bytes |
0 to 65,535 |
short int |
2 bytes |
-32,768 to 32,767 |
unsigned long int |
4 bytes |
0 to 4,294,967,295 |
long int |
4 bytes |
-2,147,483,648 to 2,147,483,647 |
int (16-bit) |
2 bytes |
-32,768 to 32,767 |
int (32-bit) |
4 bytes |
-2,147,483,648 to 2,147,483,647 |
unsigned int (16-bit) |
2 bytes |
0 to 65,535 |
unsigned int (32-bit) |
4 bytes |
0 to 4,294,967,295 |
char |
1 byte |
256 character values |
float |
4 bytes |
1.2e-38 to 3.4e38 |
double |
8 bytes |
2.2e-308 to 1.8e308 |
bool |
1 byte |
true or false |
NOTE: ISO C++ recently added a new type, bool, which is a true or false value. bool is named after the British mathematician George Bool (1815-1864), who invented Boolean algebra, a system of symbolic logic.
This book assumes that you are using a 32-bit computer (for example, a Pentium) and that you are programming with a 32-bit compiler. With that development environment, an integer is always 4 bytes. Listing 2.3 can help you determine the size of the built-in types on your computer, using your compiler.
Listing 2.3 Finding the Size of Built-In Types
1: #include <iostream> 2: using namespace std; 3: int main() 4: { 5: cout << "The size of an int is:\t\t"; 5a: cout << sizeof(int) << " bytes.\n"; 6: cout << "The size of a short int is:\t"; 6a: cout << sizeof(short) << " bytes.\n"; 7: cout << "The size of a long int is:\t"; 7a: cout << sizeof(long) << " bytes.\n"; 8: cout << "The size of a char is:\t\t"; 8a: cout << sizeof(char) << " bytes.\n"; 9: cout << "The size of a float is:\t\t"; 9a: cout << sizeof(float) << " bytes.\n"; 10: cout << "The size of a double is:\t"; 10a: cout << sizeof(double) << " bytes.\n"; 11: cout << "The size of a bool is:\t"; 11a: cout << sizeof(bool) << " bytes.\n"; 12: return 0; 13: } The size of an int is: 4 bytes. The size of a short int is: 2 bytes. The size of a long int is: 4 bytes. The size of a char is: 1 bytes. The size of a float is: 4 bytes. The size of a double is: 8 bytes. The size of a bool is: 1 bytes.
NOTE: On your computer, the number of bytes presented might be different. If the number of bytes reported for an int (the first line of output) is 2 bytes, you are using an older (and probably obsolete) 16-bit compiler.
TIP: If you are using Visual C++, you can run your program with Ctrl+F5. Your ouput displays, followed by
Press any key to continue
This gives you time to look at the output; then, when you press a key, the window closes.
If your compiler does not provide this service, you might find that the text scrolls by very quickly, and then you are returned to your IDE.
In this case, add the following line to the top of the file:
#include <conio.h>
Add the following line just before return 0;:
_getch();
This causes your program to pause after all the output is completed; it waits for you to press the spacebar or other character key. Add these lines to every sample program.
Most of Listing 2.3 is probably pretty familiar to you. The one new feature is the use of the sizeof() operator, which is provided by your compiler and tells you the size of the object you pass as a parameter. For example, on line 5, the keyword int is passed into sizeof(). Using sizeof(), I determined that on my computer an int is equal to a long int, which is 4 bytes.
Using an Integer Variable
In Decryptix!, we want to keep track of how many different letters the code can contain. Because this is a number (between 1 and 26), you can keep track of this value with an int. In fact, because this number is very small, it can be a short int, and because it must be a positive number, in can be an unsigned short int.
Using a short int might save me two bytes. There was a time when such a savings was significant; today, however, I rarely bother. Short ints can be written just as short, so it is the height of profligacy to waste bytes because I'm too lazy to write short rather than int.
When I was a boy, bytes were worth something, and I watched every one. Today, for most applications, bytes are cheap. They are the pennies of programming, and these days most programmers just keep a small cup of bytes out on the counter and let customers take them when they need a few, occasionally tossing extra bytes into the cup for the next person to use.
NOTE: In the 1960s, many programmers worked in primitive languages, which represented dates as characters. Each number in the date consumed one byte (so 1999 consumed 4 bytes). Obsessive concern about saving a byte here and a byte there led many programmers to shorten dates from four digits (1999) to two (99), thus saving two bytes and creating the Y2K problem.
For the vast majority of programs, the only built-in types to be concerned with are int, char, and bool. Now and again you'll use unsigned ints, floats, and doubles. Of course, most of the time you'll use your own programmer-created types.
Signed and Unsigned
After you determine the size of an integer, you're not quite done. An integer (and a short and a long) can be signed or unsigned. If it is signed, it can store negative and positive numbers. If it is unsigned, it can store only positive numbers.
Because signed numbers can store negative as well as positive numbers, the absolute value they can store is only half as large.
Wrapping Around an Unsigned Integer
The fact that unsigned long integers have a limit to the values they can hold is only rarely a problem--but what happens if you do run out of room?
When an unsigned integer reaches its maximum value, it wraps around and starts over, much like a car odometer. Listing 2.4 shows what happens if you try to put too large a value into a short integer.
Listing 2.4 Wrapping Around an Unsigned Integer
1: #include <iostream> 2: using namespace std; 3: int main() 4: { 5: unsigned short int smallNumber; 6: smallNumber = 65535; 7: cout << "small number:" << smallNumber << endl; 8: smallNumber++; 9: cout << "small number:" << smallNumber << endl; 10: smallNumber++; 11: cout << "small number:" << smallNumber << endl; 12: return 0; 13: } small number:65535 small number:0 small number:1
On line 4, smallNumber is declared to be an unsigned short int, which on my computer is a two-byte variable that can hold a value between 0 and 65,535. On line 5, the maximum value is assigned to smallNumber, and it is printed on line 6 using the standard output library function. Note that because we did not add the line using namespace std; we must explicitly identify cout. The keyword endl is also part of the standard library and must be explicitly identified.
On line 7, smallNumber is incremented; that is, one is added to it. The symbol for incrementing is ++ (as in the name C++, an incremental increase from C). Thus, the value in smallNumber is 65,536. However, unsigned short integers can't hold a number larger than 65,535, so the value is wrapped around to 0, which is printed on line 8.
Incremented--When a value is incremented, it is increased by one.
On line 9, smallNumber is incremented again, and then its new value, 1, is printed.
A signed integer is different from an unsigned integer in that half of the values you can represent are negative. Instead of picturing a traditional car odometer, you might picture one that rotates up for positive numbers and down for negative numbers. One mile from 0 is either 1 or -1. When you run out of positive numbers, you run right into the largest negative numbers and then count back down to 0. Listing 2.5 shows what happens when you add 1 to the maximum positive number in short integer.
Listing 2.5 Wrapping Around a Signed Integer
1: #include <iostream> 2: using namespace std; 3: int main() 4: { 5: short int smallNumber; 6: smallNumber = 32767; 7: cout << "small number:" << smallNumber << endl; 8: smallNumber++; 9: cout << "small number:" << smallNumber << endl; 10: smallNumber++; 11: cout << "small number:" << smallNumber << endl; 12: return 0; 13: } small number:32767 small number:-32768 small number:-32767
On line 4, smallNumber is declared to be a signed short integer. (If you don't explicitly say that it is unsigned, it is assumed that it is signed.) The program proceeds much as the preceding program does, but the output is quite different.
The bottom line is that just like an unsigned integer, the signed integer wraps around from its highest positive value to its highest negative value.
The point of a variable is to store a value. I call it a variable because it might vary: That is, the value might change over the course of the program. howManyLetters starts out as 0, but is assigned a new value based on the user's input.
At times, however, you need a fixed value--one that won't change over the course of the program. The minimum number of letters a user is allowed to choose is an example of a fixed value; the programmer determines it long before the program runs.
A fixed value is called a constant. There are two flavors of constants: literal and symbolic.
A literal constant is a value that is typed directly into your program wherever it is needed. For example
int howManyLetters = 7;
howManyLetters is a variable of type int; 7 is a literal constant. You can't assign a value to 7, and its value can't be changed.
Like variables, symbolic constants are storage locations, but their contents never change during the course of your program. When you declare a constant, what you are really doing is saying to the compiler, "Treat this like a variable, but if I ever change the value stored here, let me know." The compiler then tells you with a compiler error.
You'll define minLetters to be a symbolic constant--specifically, a constant integer whose value is 2. Note that the constant minLetters is used on a number of different lines of code. If you decide later to change the value to 3, you need to change it only in one place--the change affects many lines of code. This helps you avoid bugs in your code. Changes are localized, so there is little chance of one line assuming that the minimum number of letters is two, whereas another line assumes that it is three.
There are actually two ways to declare a symbolic constant in C++. The old, traditional, and now obsolete way is with a preprocessor directive: #define.
To define a constant in the traditional way, enter the following code:
#define minLetters 2
Note that when it is declared this way, minLetters is of no particular type (int, char, and so on). #define does a simple text substitution. Every time the preprocessor sees the word minLetters, it puts in the text 2.
Because the preprocessor runs before the compiler, your compiler never sees your constant; it sees the number 2. Later, when I discuss debugging, you'll find that #defined constants do not appear as symbolic constants in the debugger; you see only the literal value (2).
Although #define works, there is a newer, much better way to define constants in C++:
const int minLetters = 2;
This creates the symbolic constant minLetters but ensures that it has a particular type--integer. If you try to write
const int minLetters = 3.2;
the compiler complains that you have declared it to be an int but that you are trying to initialize it with a float.
The purpose of a debugger is to enable you to peek inside the machine and watch your variables change as the program runs. You can accomplish a lot with a debugger, and aside from your text editor, it is your most important tool.
Unfortunately, most novices don't become comfortable with their debugger until very late in their experience of C++. This is a shame because the debugger is a terrific learning tool.
Although every debugger is different and you definitely want to consult your documentation, this excursion illustrates how you might debug Listing 2.1 using the Microsoft Visual C++ 6.0 Enterprise Edition debugger. Your exact experience might vary, but the principles are the same.
Follow these steps:
1. Create a project called Decryptix.
2. Open a new file called decryptix.cpp.
3. Enter the program as it is written or download it from my Web site.
4. Place the cursor on the first line after the opening brace and press F9. You see a red dot in the margin next to that line, indicating a break point (see Figure 2.4).
Figure 2.4 A break point showing in Visual C++.
5. Press Go (F5). The debugger starts, and your code runs.
6. Choose View/Debug Windows and make sure that the Watch and Variables windows are open (see Figure 2.5).
Figure 2.5 Checking that the Watch and Variables windows are open.
7. Press Step Over (F10) to walk through the code line by line.
8. Scroll down to the line on which duplicatesAllowed is defined, and place a break point there. Press Go (F5) to run until this second break point.
9. Note in the variables window that howManyLetters and howManyPositions are zero, but that duplicatesAllowed has a random value (see Figure 2.6).
Figure 2.6 Looking at values in the debugger.
10. Press Step Over (F10) to step over this one line of code. This causes duplicatesAllowed to be created and initialized. Note that the value that is shown in the variables window is now correct (false is indicated as 0, and true is indicated as 1). Note also that round has a random value. Press F10 again; round is initialized to 1.
11. Explore the debugger and read through the documentation and help files. The more time you spend in the debugger, the more you will come to appreciate its tremendous value, both for finding bugs and for helping you understand how programs work.
© Copyright 1999, Macmillan Computer Publishing. All rights reserved.