Beruflich Dokumente
Kultur Dokumente
That is, each statement in the language tells the computer to do something: Get some input, add these numbers, divide by 6, display that output. A program in a procedural language is a list of instructions. For very small programs, no other organizing principle (often called a paradigm) is needed. The programmer creates the list of instructions and the computer carries them out Division into Functions When programs become larger, a single list of instructions becomes unwieldy. Few programmers can comprehend a program of more than a few hundred statements unless it is broken down into smaller units. For this reason, the function was adopted as a way to make programs more comprehensible to their human creators. (The term function is used in C++ and C. In other languages, the same concept may be called a subroutine, a subprogram, or a procedure.) A program is divided into functions andideally, at leasteach function has a clearly defined purpose and a clearly defined interface to the other functions in the program. The idea of breaking a program into functions can be extended by grouping a number of functions together into a larger entity called a module, but the principle is similar: a grouping of instructions that carry out specific tasks. Dividing a program into functions and modules is one of the cornerstones of structured programming, the somewhat loosely defined discipline that has influenced programming design for several decades. Problems with Structured Programming As programs grow ever larger and more complex, even the structured programming approach begins to show signs of strain. You may have heard about, or been involved in, horror stories of program development. The project is too complex, the schedule slips, more programmers are added, complexity increases, costs skyrocket, the schedule slips further, and disaster ensues (see The Mythical Man-Month, by Frederick P. Brooks, Jr., AddisonWesley, 1982, for a vivid description of this scenario). Analyzing the reasons for these failures reveals weaknesses in the procedural paradigm itself. No matter how well the structured programming approach is implemented, large programs become excessively complex. What are the reasons for this failure of procedural languages? One of the most crucial is the role played by data. In a procedural language, the emphasis is on doing thingsread the keyboard, invert the vector, check for errors, and so on. The subdivision of a program into functions continues this emphasis. Functions do things, just as single
program statements do. What they do may be more complex or abstract, but the emphasis is still on the action. What happens to the data in this paradigm? Data is, after all, the reason for a programs existence. The important part of an inventory program isnt a function that displays the data or a function that checks for correct input; its the inventory data itself. Yet data is given second-class status in the organization of procedural languages. For example, in an inventory program, the data that makes up the inventory is probably read from a disk file into memory, where it is treated as a global variable. By global, I mean that the variables that constitute the data are declared outside of any function so they are accessible to all functions. These functions perform various operations on the data. They read it, analyze it, update it, rearrange it, display it, write it back to the disk, and so on. I should note that most languages, such as Pascal and C, also support local variables, which are hidden within a single function. But local variables are not useful for important data that must be accessed by many different functions. Figure 1-1 shows the relationship between global and local variables.
Suppose a new programmer is hired to write a function to analyze this inventory data in a certain way. Unfamiliar with the subtleties of the program, the programmer creates a function that accidentally corrupts the data. This is easy to do, because every function has complete access to the data. Its like leaving your personal papers in the lobby of your apartment building: Anyone can change or destroy them. In the same way, global data can be corrupted by
functions that have no business changing it. Another problem is that, because many functions access the same data, the way the data is stored becomes critical. The arrangement of the data cant be changed without modifying all the functions that access it. If you add new data items, for example, youll need to modify all the functions that access the data so that they can also access these new items. It will be hard to find all such functions and even harder to modify all of them correctly. Its similar to what happens when your local supermarket moves the bread from aisle 4 to aisle 12. Everyone who patronizes the supermarket must figure out where the bread has gone and adjust their shopping habits accordingly. The relationship of functions and data in procedural programs is shown in Figure 1-2.
What is needed is a way to restrict access to the data, to hide it from all but a few critical functions. This will protect the data, simplify maintenance, and offer other benefits, as youll see.
Q No 2
Preprocessor Directives
Preprocessor directives are instructions to the compiler. By contrast, ordinary C++ statements, such as alpha=17; are instructions to the microprocessor, which the compiler translates into machine language that the microprocessor can understand. Preprocessor directives always start with a pound sign ( #). When the compiler encounters the #include preprocessor directive shown above, it starts to search for the file IOSTREAM.H. There is a particular subdirectory where such files are stored, usually called \INCLUDE\ (where the dots represent the first part of the path name, such as C:\BC5). This subdirectory is usually found in the general directory for the compiler youre using. The compiler should know where such a directory is loca ted; if not, it may need to be told (see the appendix in this book that applies to your particular compiler). Once it finds the file, the compiler (actually a part of the compiler called the preprocessor) simply inserts its text into the source file in place of the #include directive. If you want to see whats in IOSTREAM.H, you can go to the \INCLUDE\ directory and examine it with the compilers editor or any other text editor. (Be careful not to change the file.) The contents wont make much sense at this point, but you will at least prove to yourself that IOSTREAM.H is a text file, written in normal ASCII characters. There are other preprocessor directives besides #include; youll encounter a few more as you go along. I should note that there are two formats for specifying the file in an #include directive. Using the appropriate format speeds up the compilers search for the file. In the example above, angle brackets are used to delimit the file name. #include <filename.ext> This causes the compilers search for the file to start in the standard \INCLUDE\ directory. If quotes are used instead #include filename.ext then the search for the file will begin in the directory where the programs source files are stored. This is the format you would use if you had written the header file yourself, a situation youll encounter in Chapter 11.
Q No.3 A while loop lets you do something over and over until a condition changes. The condition is something that can be expressed by a true/false value. For example, a while loop might repeatedly ask the user to enter a character. It would then continue to cycle until the user enters the character q (for quit). Heres an example of a while loop that behaves just this way: while(ch != 'q') { cout << Enter a character: ; cin >> ch; } If the user does not press q, the loop continues. Some sample interaction might look like this: Enter a character: c Enter a character: a Enter a character: t Enter a character: s Enter a character: q A while loop consists of the keyword while followed by a test expression (also called a conditional expression or condition) enclosed in parentheses. The body of the loop is delimited by braces (but no semicolon), just like a function. Figure 2-2 shows how this looks
Syntax of the while loop If the body of the loop consists of only one statement, you dont need the bra ces. while(n < 100) n = n * 2; <--One-statement loop body, so no braces This loop will keep doubling n until n is not less than (i.e., becomes greater than or equal to) 100; it will then terminate. If n has a value of 1 when the loop is entered, what value will it have when the loop terminates? Thats right, 128. The values will go 1, 2, 4, 8, 16, 32, 64, and 128, at which point the loop terminates (i.e., control goes to the statement following the loop). Figure 2-3 is a flow chart of a while loops operation.
Note: Note that the test expression is checked before the body of the loop is executed. If the condition is false when the loop is entered, then the body of the loop will never be executed. This is appropriate in some situations, but it means you must be careful that a variable in the test expression has an appropriate value before you enter the loop. The ch in the first example must not have a value of q when you enter the loop, or the loop body will never be executed. The n in the second loop must be initialized to a value less than 100.
do Loops
The do loop (often called the do while loop) operates like the while loop except that the test expression is checked after the body of the loop is executed. This is nice when you always want something (whatever is in the body of the loop) done at least once, no matter what the initial true/false state of the condition is. Figure 2-4 shows how this looks.
Operation of the do loop Heres an example of a do loop. This fragment repeatedly performs addition on two numbers entered by the user. When the user enters 0 for the first number, the loop terminates. do { cout << \nEnter two numbers (to quit, set first to 0): cin >> x >> y; cout << The sum is << x + y; } while(x != 0);
A do loop begins with the keyword do, followed by the body of the loop in braces, then the keyword while, a test expression in parentheses, and finally a semicolon. This arrangement is shown in Figure 2-5. Note that the do loop is the only loop that is terminated with a semicolon. The semicolon is necessary because the test expression follows the loop body, so the closing brace of the loop body cant act as a delimiter for the entire loop.
Syntax of the do loop The do loop has a slightly dubious reputation among C++ programmers because its syntax is not quite so clean and easy to read as that of the while loop. The consensus is to use a while loop unless theres a really good reason to use a do loop.
Q No 4 (a) Functions are important not only because they are one of the two major components of objects but also because
they are one of the basic ways to organize C++ programs. Ill first summarize what youve learned about functions so far. Then Ill examine a new way to write member functions outside of the class specification. Ill look at the advantages of overloading functions, which means giving the same name to different functions, and default arguments, which allow a function to be called with fewer arguments than it really has. In the second half of this chapter, Ill switch gears to a related topic: storage classes. Youll see how variables acquire different characteristics from two sources: first, from the location of the variable with regard to functions and classes and second, from the use of special keywords. Finally, Ill examine references, a new way to pass arguments to functions and to return values from them. program. The statement that does this is a function call. The function call causes control to jump to the start of the function. As part of the function call, arguments also may be passed to the function. After the code in the function has been executed, control returns to the statement following the function call; at this time, the call can return a value from the function. Heres an example of a function call: func1(); This call causes the function func1() to be executed. This particular call takes no arguments and does not return a value. It consists of the name of the function followed by parentheses. Note that, like any other program statement, it is terminated with a semicolon. Functions, along with classes, serve as one of the important organizing principles of C++ programs. They also increase memory efficiency: A function can be executed many times without the need for inserting duplicates of its code into the listing. Figure 4-1 shows how three different statements cause the same section of code to be executed.
Return Values
A function can return a value to the statement that called it. Heres a function that converts pounds (lbs) to kilog rams (kg). It takes one argument, a value in pounds, and returns the corresponding value in kilograms. // lbstokg() // converts pounds to kilograms float lbstokg(float pounds) <--declarator { float kilograms = 0.453592 * pounds; return kilograms; } The type of the value returned, in this case float, must precede the function name in the function declarator. As Ive mentioned, if a function does not return any value, void is used as a return type to indicate this. If a function does return a value, the return statement must be used. The value to be returned is specified following the keyword return. return kilograms; This statement causes control to return immediately to the function call, even if it is not the last statement in the function definition. It also causes the entire function call expression to take on the value of the returned variable kilograms. This function might be called this way: kgs = lbstokg(lbs); The function call expression, lbstokg(lbs), itself takes on the value returned and can be assigned to a variable, in this case, kgs. If the pounds variable happens to be 165.0 when this call is made, then the value 74.84 (i.e., 165.0 times 0.453592) is assigned to kgs when the function returns. Figure 4-4 shows how this value is copied from the kilograms variable in the function to the kgs variable in main().
How does the compiler distinguish one function from another that has the same name? It engages in a process called name mangling. (Whats the charge, officer? Name mangling, your honor.) This consists of creating a new name by combining a functions name with the types of its arguments. The exact syntax varies from one compiler to another, and in any case is normally hidden from the programmer. However, I can speculate that the average() function for type int might be mangled into something like average_int_int(), whereas the average() function for type long might become average_long_int(). Notice, by the way, that the functions return type is not part of its mangled name. The compiler cant distinguish between two functions on the basis of return type.
Inline Functions
There are two quite different ways for the compiler to construct a function. I described the normal approach in the last session: A function is placed in a separate section of code and a call to the function generates a jump to this section of code. When the function has finished executing, control jumps back from the end of the function to the statement following the function call, as depicted in Figure 4-1. The advantage of this approach is that the same code can be called (executed) from many different places in the program. This makes it unnecessary to duplicate the functions code every time it is executed. There is a disadvantage as well, however. The function call itself, which requires not only a jump to the function but also the transfer of the arguments, takes some time, as does the return from the function and the transfer of the return value. In a program with many function calls (especially inside loops), these times can add up to sluggish performance. For large functions, this is a small price to pay for the savings in memory space. For short functions (a few lines or so), however, the savings in memory may not be worth the extra time necessary to call the function and return from it. The designers of C++ therefore came up with another approach: the inline function. An inline function is defined using almost the same syntax as an ordinary function. However, instead of placing the functions machine -language code in a separate location, the compiler simply inserts it into the normal flow of the program at the location of the function call. Figure 4-5 shows the difference between ordinary functions and inline functions.
Q No. 7
An array is a way to group together a number of variables of a single data type. Arrays are useful primarily because the individual variables stored in an array can be accessed using an index number. This makes it easy to cycle through the array, accessing one variable after another. In this session, Ill look at the fundamentals of array syntax. Later in this chapter, youll see how to put arrays to work, first as instance data in a class and then as an array of class objects.
Defining an Array
To define an array, you tell the compiler to set aside storage for a given number of data items of a specified type. You also tell the compiler the name of the array. Heres an example of an array definition that creates storage for four integers. Ill give this array the name age; perhaps it will be used to store the ages of four people. int age[4]; The int specifies the type of data to be stored, age is the name of the array, and 4 is the size of the array; that is, the maximum number of variables of type int that it will hold. Brackets [] (not braces or parentheses) surround the size. Its the brackets that tell the compiler Im defining an array and not something else, such as a function. Figure 3-1 shows the format of this array definition You can define arrays of any data type, of course. Heres an array of 100 variables of type float, called foo: float foo[100]; // 100 floats There is also no problem defining arrays of types you have created yourself, using classes: airtime DenverDepartures[50]; // array of 50 airtimes The type of the array can be any kind of class, whether it behaves like a data type or not: HotDogStand stands[6]; // array of 6 hot dog stands Here you have an array of objects that represent physical objects, not data types. It doesnt matter to the compiler.
Array Elements
Each variable stored in an array is called an element. The elements are numbered. These numbers are called index numbers or indexes. Some people also refer to them as subscripts. The index of the first array element is 0, the index of the second is 1, and so on. If the size of the array is n, the last element has the index n-1. For example, in the age array, which has a size of 4, the elements are numbered 0, 1, 2, and 3. This numbering can be the source of some confusion. Keep in mind that the last element in an array has an index one less than the size of the array. Figure 3-2 shows the array elements for the age array stored in memory. (Here each element is assumed to occupy 2 bytes.) The elements have been given the values 44, 16, 23, and 68. Dont confuse the values of the elements with their index numbers (0 to 3). 701
Multidimensional Arrays
So far, youve looked only at one-dimensional arrays. You can create arrays of as many dimensions as you like, and each dimension can be a different size. Heres the definition of a 4 by 3 array: float sales[4][3]; // define two-dimensional array Notice that each array dimension is surrounded by its own set of brackets. Dont writ e [4,3], as is done in some languages. If the first dimension represents sales districts (North, South, East, and West, say) and the second dimension represents the three months in a quarter, then I might represent this array as shown in Figure 3-4. Figure 3-4 Two-dimensional array Individual array elements are accessed using two indexes. Thus in Figure 3-4, the element in the upper-right corner of sales is sales[0][[2] and the element in the lower-left corner is sales[3][0]. To display all the elements of such an array, you would probably use two nested for loops. for(int y=0; y<3; ++y) // step from row to row { for(int x=0; x<4; ++x) // step from column to column cout << sales[x][y] << ' '; // display value and a space cout << endl; // go to next line } A two-dimensional array can be looked at as an array of arrays. The sales array is an array of four subarrays, each of which has three elements. The subarrays are one-dimensional arrays called sales[0], sales[1], and so on. This way of looking at things is important if you want to refer to the subarrays of an array, which is common in arrays of strings, as youll see later
Strings
C++ provides a sophisticated repertoire of ways to simplify text handling. The C++ method of text handling treats text as a string, which is a sequence of characters terminated by a special character. This approach to text was developed in C, long before the arrival of OOP, so such strings are not object oriented. I will sometimes call them C strings to avoid confusion with more sophisticated string classes, which can be created only in C++. However, in most cases the context is clear, and Ill simply call them strings. Although they are old fashioned, strings are a key feature of both C and C++ programming, and often form the foundation of more sophisticated string classes. Its therefore important to learn about strings, which is what youll do in the next few lessons.
String Variables
A string is a sequence of characters in which the last character has a numerical value of 0 (zero). As a character, this value can be represented by the escape sequence \0. It is often called the null character. Using a special value like this to indicate the end of a text string means that there is no reason to store the length of the text as a separate integer value, as I did in the EMPLOY1 program. Instead, string-handling routines look for the \0 to determine when the string ends. A string variable is an array of type char. Like other variables, it may or may not contain a value at any given time. Heres how you would define a string variable called str: char str[80]; // string variable; can hold up to 80 characters When the string variable is first created, no value is stored in it. Unlike variables of basic types, string variables can be different sizes; this one can hold a string of up to 80 characters. Although they are really arrays of type char, C++ treats strings in some ways like a basic data type such as int. For one thing, cin and cout know how to handle strings, so you can use ordinary stream I/O to input or output a string with a single statement. Heres how you would create a string variable, read some text into it from the keyboard, and then display this same text: char str[80]; // create a string variable str cin >> str; // get text from user, store in str << str; // display text entered by user The user types the characters of the string and then presses . (As you know, this is called entering the text.) Figure 38 shows how the array str looks in memory after the user has entered the string Amanuensis (which means one employed to copy manuscripts).
Q No. 9