Sie sind auf Seite 1von 16

Traditional programming languages are procedural languages.

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.

Relationship to the Real World


Procedural programs are often difficult to design. The problem is that their chief componentsfunctions and data structuresdont model the real world very well. For example, suppose you are writing code to create the elements of a graphics user interface: menus, windows, and so on. Quick now, what functions will you need? What data structures? The answers are not obvious, to say the least. It would be better if windows and menus corresponded more closely to actual program elements.

New Data Types


There are other problems with traditional languages. One is the difficulty of creating new data types. Computer languages typically have several built-in data types: integers, floating-point numbers, characters, and so on. What if you want to invent your own data type? Perhaps you want to work with complex numbers, or two-dimensional coordinates, or datesquantities the built-in data types dont handle easily. Being able to create your own types is called extensibility because you can extend the capabilities of the language. Traditional languages are not usually extensible. Without unnatural convolutions, you cant bundle both x and y coordinates into a single variable called Point and then add and subtract values of this type. Traditional programs are more complex to write and maintain.

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.

Other Header Files


There are many header files. Some are used with the C function library. For example, if you need to use mathematical functions such as sin() and cos(), you need to include a header file called MATH.H. If youre going to operate on strings, youll need STRING.H; if youre going to perform certain kinds of data conversion, you may need to include STDLIB.H. Various specialized I/O functions may require CONIO.H or STDIO.H. Other header files are needed for other kinds of class specifications. For example, your compiler may be bundled with a container class library. Container is a general name for a data structure, such as an array, a stack, a queue, or a linked list. The container class library contains classes that model these data structures, but if you want to use one, youll need to include a header file specific to the particular container, such as ARRAYS.H, STACKS.H, or QUEUES.H. Dont worry if there seems to be an endless number of header files. All you really need to know about header files at this point is that they are an important part of C++ programs and youll need to include IOSTREAM.H to run any program that does stream I/O.

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().

Q No. 6 Overloaded Function


One really nice convenience built into C++ is function overloading. This means that you can use the same name for different functions. You could, for example, have three functions all called afunc(), but each one would have a separate function definition. It may not seem obvious why you would want to do this, so Ill explain.

Need for Function Overloading


Suppose there is a function that calculates the average of an array of numbers of type int. Such a function might look like this: int iaverage(int array[], int size); { int total = 0; // set total to 0 for(int j=0; j<size; j++) // for every array member, total += array[j]; // add it to total return total/size; // return total div by array size } The statement that calls this function passes the array name and the number of elements in the array as arguments: avg = iaverage(int_array, 50); Now, what happens if I want a function that averages arrays of type long? Such a function might look like this: long laverage(long array[], int size); { long total = 0; // set total to 0 for(int j=0; j<size; j++) // for every array member, total += array[j]; // add it to total return total/size; // return total div by array size } The call to the function would be similar: avg = laverage(long_array, 50); There might be a similar function faverage() for type float and daverage() for type double. This arrangement works all right, but notice that, even though iaverage(), laverage(), and so on do the same thing average numbersthey have different names. This is too bad, because the programmer must remember all the names. In a reference book, each name requires a separate section or messy cross-referencing. The multiplicity of names makes everyones life harder. Using different names for functions that do the same thing to different types is common in the C language library. For example, the function that returns the absolute value of a number is abs() for type int, fabs() for type double, labs() for type long, and so on. This is called name proliferation. C++ avoids the pitfalls of name proliferation by making it possible for several different functions to have the same name. Thus, the iaverage(), laverage(), and similar functions can be called average(). You may wonder how the compiler knows which function definition to call when the user writes a statement such as avg = average(larray, 20); The compiler looks at the type and number of the functions arguments to figure out what function is intended. Here the second argument is always int, but the first argument varies with the type of data to be averaged. In this statement, the type of larray is long, so the compiler calls the average() function that operates with the long type. If I say avg = average(iarray, 20); where iarray is type int, the compiler will call the average function that works with the int type. Figure 4-6 shows how the argument types determine which function is called.

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.

Specifying an Inline Function


You can request the compiler to make any function an inline function by using the keyword inline in the function definition. inline void func1() { // statements } In other respects, the function is defined in the same way as an ordinary function. If you need to use a function declaration, it must reflect the inline as well. inline void func1(); // declaration However, calls to the function are made in the normal way: func1();

When Should You Inline a Function?


The decision to inline a function must be made with some care. If a function is more than a few lines long and is called many times, then inlining it may require much more memory than an ordinary function. Its appropriate to inline a function when it is short, but not otherwise. If a long or complex function is inlined, too much memory will be used and not much time will be saved.

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

Overloading Binary Arithmetic Operators


The C++ operators can be divided roughly into binary and unary. Binary operators take two arguments. Examples are a+b, a-b, a/b, and so on. Unary operators take only one argument: -a, ++a, a--. (There is also one ternary operatormeaning it takes three argumentsin C++, the conditional operator (?:), but we wont worry about overloading it). For a discussion of overloaded operators, it makes sense to discuss the binary operators first, because they are the most straightforward, and then move on to the somewhat more obscure unary operators An overloaded operator, when applied to objects (not to basic types), can carry out whatever operation the class creator wants. The + operator can concatenate two xString objects, add two airtime objects, and so on. Why would you want to overload operators? The basic reason is that you want to make your listing easier to read. Perhaps the most familiar operator is the plus sign (+), so Ill begin my exploration of overloading with that. Suppose you want to add two values of type airtime and to assign the result to another airtime variable. Its considerably easier to understand whats happening when you see at3 = at1 + at2; than when the same operation is expressed as at3 = at1.sum(at2); or the even less obvious at3.sum(at1, at2);

You Could Do It with Functions


Overloading doesnt actually add any capabilities to C++. Everything you can do with an overloaded operator you can also do with a function. However, by making your listing more intuitive, overloaded operators make your programs easier to write, read, and maintain. They are also a lot of fun to create and to use. You might think of overloaded operators as a way to transfer some of the labor from the class user to the class creator. If the class creator spends a little time overloading operators for appropriate tasks, the class user can spend less time writing the code that invokes these tasks, because the operations will be more intuitive.

Not for All Classes


Some kinds of classes lend themselves to using overloaded operators, but others dont. If youre talking about objects of class employee, for example, it probably doesnt make sense to say emp3 = emp1 + emp2; After all, what would it mean to add two employees together? Of course, if you do come up with a reasonable meaning for the + operator in this context, you are free to overload it. In general, however, overloaded operators are best used with classes that represent numerical data types. Examples are times, dates, imaginary numbers (x+iy), and geographical positions. String classes can also profit from overloaded operators.

You Cant Overload Everything


Incidentally, you cant overload operators that dont already exist in C++. You cant make up a ** operator for (say) exponentiation or a <- operator for some other obscure purpose. You can overload only the built-in operators. Even a few of these, such as the dot operator (.), the scope resolution operator (::), the conditional operator (?:), and several others you havent encountered yet, cant be overloaded.

Das könnte Ihnen auch gefallen