C++ Robert Lafore
C++ Robert Lafore
C++ Robert Lafore
Welcome to the exciting world of object-oriented programming! In this first chapter, Ill start by discussing why object-oriented programming (OOP) was invented and why it offers advantages to the programmer. Ill also provide a quick overview of the main features of object-oriented languages. Youll learn about the two most fundamental aspects of OOP, objects and classes. Then Ill focus on a particular kind of objecthot dog standand show how real hot dog stands on the street relate to hot dog stand objects in a program. Youll see how to use C++ to describe a class of hot dog stand objects and how to make these objects carry out tasks. This approach goes to the very heart of OOP. Most books begin by skittering around the edges of C++, talking about old-fashioned procedural details. This one attacks objects and classes head-on. If you think of OOP as a fierce firebreathing dragon, then youre going to walk right up to it, look it squarely in the eye, and tell it you want answers, now!
Procedural Languages
Pascal, C, BASIC, Fortran, and similar 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.
Page 1 of 701
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.
Figure 1-1 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.
Page 2 of 701
Figure 1-2 The procedural paradigm 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.
Page 3 of 701
Figure 1-3 The object-oriented paradigm I should mention that what are called member functions in C++ are called methods in some other object-oriented (OO) languages such as Smalltalk, one of the first OO languages. Also, data items may be called instance variables. Calling an objects member function is often referred to as sending a message to the object. These terms are often used by C++ writers.
This view of corporate organization is shown in Figure 1-4. In the same way, objects provide an approach to program organization while helping to maintain the integrity of the programs data.
another. By using a larger organizational unit, the object, which combines functions and data, weve restored order to a chaotic situation.
Quiz 1
(Note: In thisand all quizzes in this booka question may have more than one correct answer.)
1. Pascal, BASIC, and C are __________ languages, whereas C++ is ___________. a. object-oriented, traditional b. interpreted, compiled c. traditional, procedural d. procedural, compiled e. procedural, object-oriented 2. Which of the following are weaknesses of traditional languages such as C? a. Important data is vulnerable to accidental modification. b. Such languages are hard to use for small programs. c. Functions dont correspond neatly to real-world entities. d. It is difficult to extend such languages by adding new data types. e. The syntax is excessively complex. 3. In C++, you will typically access an objects data using a. member functions of other objects in that class. b. member functions of any class. c. any function with that objects password. d. member functions associated with that particular object. e. any function outside of the objects class. 4. The two major components of an object are __________ and ____________. a. a class, its data
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 6 of 701
b. data, functions that may act on that data c. messages, member functions d. encapsulation, polymorphism e. hidden data, ordinary data 5. Asking the sales manager of a company to get you data from the sales department is like a. calling the member function of an object to access the objects data. b. creating a class of objects with data hidden inside. c. programming a member function that can insert data in an object. d. creating an object with a member function that can access its own data. e. sending a message to a class.
Real-World Objects
When you approach a programming problem in an object-oriented language, you no longer ask how the problem will be divided into functions, but how it will be divided into objects. Thinking in terms of objects rather than functions has a surprisingly helpful effect on how easily you can design programs. This results from the close match between objects in the programming sense and objects in the real world. What kinds of things become objects in object-oriented programs? The answer is limited only by your imagination, but here are some typical categories to start you thinking:
Physical objects Elevators in an elevator control program Automobiles in a traffic flow simulation Countries in an economics model Aircraft in an air traffic control system Elements of the computer user environment Windows Menus Graphics objects (lines, rectangles, circles) The mouse, keyboard, disk drives, printer
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 7 of 701
Data-storage constructs Customized arrays Stacks Linked lists Binary trees Human entities Employees Students Customers Salespeople Collections of data An inventory A personnel file A dictionary A table of the latitudes and longitudes of world cities User-defined data types Time Angles Complex numbers Points on the plane Components in computer games Ghosts in a maze game Positions in a board game (chess, checkers) Animals in an ecological simulation Opponents and friends in adventure games
Page 8 of 701
Classes
In OOP, objects are instances of classes. What does this mean? Lets look at an analogy. Almost all computer languages have built-in data types. For instance, a data type int, meaning integer, is predefined in C++. You can declare as many variables of type int as you need in your program: int day; int count; int divisor; int answer; In a similar way, you can define many objects of the same class, as shown in Figure 1-5. A class serves as a plan, or a template. It specifies what data and what functions will be included in objects of that class. Defining the class doesnt create any objects, just as the mere existence of a type int doesnt create any variables of type int.
Page 9 of 701
Figure 1-5 A class and its objects A class is thus a description of a number of similar objects. This fits your nontechnical understanding of the word class. Sting, Madonna, and the artist formerly known as Prince, are members of the class of rock musicians. There is no one person called rock musician, but specific people with specific names are members of this class if they possess certain characteristics. An object can be called an instance or an instantiation of a class because the object is a real example or an instance of the specifications provided by the class. This leads to a name commonly used for an objects data: instance data. It is called this because there is separate data for each object; that is, for each instance of the class.
Inheritance
The idea of classes leads to the idea of inheritance. In our daily lives, we use the concept of classes being divided into subclasses. You know that the class of animals is divided into mammals, amphibians, insects, birds, and so on. The class of vehicles is divided into cars, trucks, buses, and motorcycles. The principle in this sort of division is that each subclass shares common characteristics with the class from which its derived. Cars, trucks, buses, and motorcycles all have wheels and a motor and are used to transport people or goods; these are the defining characteristics of vehicles. In addition to the characteristics shared with other members of the class, each subclass also has its own particular characteristics: Buses, for instance, have seats for many people, whereas trucks have space for hauling heavy loads. This idea is shown in Figure 1-6. Notice in the figure that features A and B, which are part of the base class, are common to all the derived classes, but that each derived class also has features of its own.
Page 10 of 701
Figure 1-6 Inheritance In a similar way, an OOP class can be used as the basis for one or more different subclasses. In C++, the original class is called the base class; other classes can be defined that share its characteristics, but add their own as well. These are called derived classes. Inheritance is somewhat analogous to using functions to simplify a traditional procedural program. If you find that three different sections of a procedural program do almost exactly the same thing, you can recognize an opportunity to extract the common elements of these three sections and put them into a single function. The three sections of the program can call the function to execute the common actions and they can perform their own individual processing as well. Similarly, a base class contains elements common to a group of derived classes. As functions do in a procedural program, inheritance shortens an object-oriented program and clarifies the relationship among program elements.
Reusability
Once a class has been written, created, and debugged, it can be distributed to other programmers for use in their own programs. This is called reusability. It is similar to the way a library of functions in a procedural language can be incorporated into different programs. However, in OOP, the concept of inheritance provides an important extension to the idea of reusability. A programmer can take an existing class and, without modifying it, add features and capabilities to it. This is done by deriving a new class from the existing one. The new class will inherit all the capabilities of the old one, but may also include new features of its own. For example, you might have written (or purchased from someone else) a class that creates a menu system, such as that used in the Turbo C++ Integrated Development System (IDE). This class works fine and you dont want to change it, but you want to add the capability to make some menu entries flash on and off. To do this, simply create a new class that inherits all the capabilities of the existing one but adds flashing menu entries. The ease with which existing software can be reused is a major benefitpossibly the major benefitof OOP. Many companies find that reusing classes on a second project provides a major return on their original investment. Ill have more to say about this as I go along. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 11 of 701
C++ and C
Although this book assumes no knowledge of the C language, you may be curious about how C and C++ are related. C++ is derived from the C language. Strictly speaking, it is a superset of C: Almost every correct statement in C is also a correct statement in C++, although the reverse is not true. The most important elements added to C to create C++ are concerned with classes, objects, and object-oriented programming. (C++ was originally called C with classes.) However, C++ has many other features as well, including an improved approach to I/O and a new way to
write comments. Figure 1-7 shows the relationship between C and C++. Figure 1-7 The relationship between C and C++ C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
Page 12 of 701
In fact, the practical differences between C and C++ are larger than you might think. You can write a program in C++ that looks like a program in C, but doing so misses the whole point of object-oriented programming. If you already know C, you will have a head start in learning C++ (although you may also have some habits to unlearn), but much of the material will be new.
OOP in a Nutshell
In summary, OOP is a way of organizing programs. Object orientation has to do with how programs are designed, not the details of individual program statements. In particular, OOP programs are organized around objects, which contain both data and functions. These functions, which act on the data, are called member functions. A class is the specification for a number of similar objects. C++ is a superset of C. It adds to the C language the capability to implement OOP. It also adds a variety of other features. In addition, the emphasis is changed in C++ so that some features common to C, although still available in C++, are seldom used, whereas others are used far more frequently. The result is a surprisingly different language. The general concepts discussed in this chapter will become more concrete as you learn more about the details of C++. You may want to refer back to this chapter as you progress further into this book.
Quiz 1. When you design a program for an object-oriented language, you look first for _________ in real life that will correspond to __________ in the program. a. organizations, data types b. information, data c. things, objects d. actions, functions e. categories, classes 2. A widget is to the blueprint for a widget as an object is to a. a member function. b. a class. c. an operator. d. a data item. e. a program. 3. Inheritance allows you to start with ___________ and derive ___________ from it, which will share common characteristics with it. a. a class, objects b. an object, classes c. a member function, other member functions d. an object, other objects
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 13 of 701
e. a class, other classes 4. Which of the following would probably make reasonable objects in an OOP program? a. The 4:30 flight to Dallas. b. The alphabet. c. A document. d. Happiness. e. Bill Jones. 5. Overloading an operator in C++ involves a. giving an operator different meanings depending on the context. b. writing new member functions. c. allowing an operator to work on a different data type. d. causing the operator to carry out several operations at once. e. making the operator carry out an operation on user-defined objects.
Page 14 of 701
Figure 1-8 Hot dog stand Actually, an object in this program is not going to model an entire hot dog stand. It will model only the data necessary to run the stand. These objects dont cook the hot dogs or make change. Their job is to keep track of an important aspect of the stands operation: how many hot dogs and buns are on hand at the stand. It would be more accurate to say that an object will model the hot dog stands inventory. Lets say an entrepreneur named Sally owns six hot dog stands, located in different parts of town. She has hired someone to operate each stand while she remains at the central office with her computer. Each stand has a telephone, so she can stay in touch with the operators. The stands are numbered from 1 to 6. Sally has hired you to write a program that she can use to keep track of the supplies on hand at each stand. At the beginning of the day, each stands operator calls Sally and tells her the number of buns and hot dogs on hand. Also, each time a hot dog is sold, the operator calls to inform her of this fact. (This may not sound too realistic but remember, these are not high-volume locations.) With this input and the output of the hot dog stand program, Sally can keep track of how many buns and hot dogs remain in each stand. This information enables her to call the supplier to order more hot dogs and buns at appropriate times.
1. enter the number of hot dogs and buns on hand at the beginning of the day 2. record each hot dog sale (which decreases the number of buns and hot dogs by 1) 3. ask the program how many hot dogs and buns remain at any given time
Figure 1-9 shows how Sally interacts with some of her hot dog stands.
Page 15 of 701
Figure 1-9 Data flow between Sally and her hot dog stands For the moment, dont worry about the part of the program that asks you which of these three options you want to choose. Instead, concentrate on designing the class of hot dog stands. Ill present the overall program in the next chapter.
Page 16 of 701
Recording a Sale
When an operator calls Sally and tells her a hot dog has been sold, she simply enters the stand number: Enter stand number: 3 <--user enters stand number The program then subtracts one hot dog and one bun from its data.
Initialize the number of buns and hot dogs in each stand at the beginning of the day Tell the stand that a sale has been made Ask the stand for the number of buns and hot dogs remaining
The two data items and the three member functions will be the same for each object.
Page 17 of 701
Variable Declarations
Two variables, HotDogsOnHand and BunsOnHand, are declared and specified to be integers (type int). This is one of several basic data types available in C++. Ill discuss data types in detail in the next session. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 18 of 701
int HotDogsOnHand; int BunsOnHand; Notice that these declarations dont give values (such as 47) to these variables, they merely give them names and specify that they will require a certain space in memory (although memory space is not actually set aside until an object is created).
Functions
There are three functions in the class specification: initData(), SoldOneDog(), and displayData(). Tell the C++ compiler that these names apply to functions (and not variables or something else) by appending parentheses to the name: void initData() <--parentheses designate a function { } The keyword preceding the function name indicates the type of data returned by the function. None of the hot dog stand functions returns a value, so the void keyword is used. Later youll see that functions may return a value of any data type. If a function returns an int, for example, you can write int someFunc() { } You could also add parameters to the function by placing values or variable names within the parentheses. Arguments convey values to the function, like this: void anotherFunc(float temperature, float humidity) { } However, Ill postpone a discussion of return types and parameters until later. As with the class specification itself, the body of the function is delimited with braces: void initData() <-- function named initData with no return type, no arguments { <-- start of function body <-- other statements would go here } <-- end of function body (NO semicolon) For simplicity, I havent inserted any statements into the function bodies. Youll need to learn a little more before were ready for that. Remember that whereas a class specification is terminated by a semicolon, a function is not.
Page 19 of 701
Figure 1-10 Public and private access The public and private keywords, followed by colons, are used to designate these parts. Here all the data is private and all the member functions are public. This is the usual situation: You want to hide the data from the outside world so it is protected from accidental alteration. However, you want the member functions to be public so other parts of the program can call them to tell the object to do something. To work with the data, dont access it directly; instead, ask a member function to access it for you.
Quiz 3 1. An object in our hot dog stand program will correspond to what entity in the real world? a. An entire hot dog stand, including the operator. b. All relevant data for all the hot dog stands. c. The inventory data for a particular hot dog stand. d. The physical hot dogs and buns at a particular stand. e. A hot dog. 2. What items will be part of a HotDogStand object?
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 20 of 701
a. A function to initialize the number of hot dogs and buns to zero. b. The number of buns. c. The number of hot dogs. d. A function to increment the number of buns and hot dogs. e. A function to display the number of hot dogs and buns on hand. 3. Data is often private and member functions are often public because a. data is not accessed as often as functions. b. an objects data should be hidden for safety but its member functions should be accessible so the rest of the program can interact with the object. c. data must be accessed only by objects of the same class, whereas member functions can be accessed by any object. d. data must not be changed, whereas member functions may be. e. data takes up less memory space than member functions do. 4. An objects member functions usually operate on a. data within the functions themselves. b. global data. c. data in another specified object. d. data in that object. e. data in any object of the same class. 5. Which item is not always part of a class specification? a. Braces. b. A semicolon. c. The keyword class. d. A class name. e. The definition of an object.
As youll see later, you can use C++ to define any data type you want, but the built-in types save you from going to this trouble in most common situations. There are seven basic types built into C++. Of these basic types, one represents characters, three represent whole numbers (integers), and three represent real (floating-point) numbers. Table 1-1 summarizes the C++ data types. Table 1-1 C++ data types Type Name - Used to Store - Examples of Values Stored
char - Characters - a, B, $, 3, ? short - Small whole numbers - 7, 30,000, -222 int - Normal-sized whole numbers (same as short or same as long) long - Large whole numbers - 1,000,000,000, -123,456,789 float - Small real numbers - 3.7, 199.99, -16.2, 0.000125 double - Large real numbers - 7,553.393.95,47, -0.048512934 long double - Extra-large real numbers - 9,123,456,789,012,345.666
Characters
Lets look at each of these data types in turn. Characters are stored in variables of type char. The statement char ch3; creates space in memory for a character and names it ch3. To store a particular character in this variable, use a statement like ch3 = a; Character constants, such as a, B, & or 4, are surrounded by single quotes.
Assignment Operator
The equal sign (=) causes the value on its right to be assigned to (placed in) the variable on its left; that is, following this statement, the variable ch3 will have the value a. The equal sign is called the assignment operator because it assigns the value on the right to the variable on the left. All characters are actually stored as numbers (which, as you know, is all a computer understands). The ASCII code is used to translate characters into numbers. Thus A is 65, B is 66, and so on. The ASCII code is shown in Appendix E.
Escape Sequences
Various special characters are represented by letters preceded by a backslash. This is called an escape sequence because the backslash causes the interpretation of the next character to escape from the normal ASCII code and indicate something different. Table 1-2 shows the most common escape sequences. Table 1-2 Common escape sequence Escape Sequence - Character Represented \n - New line. Causes the cursor to move to the start of the next line. (Same as a carriage return plus a line feed.) \t - Tab character. \b - Backspace. \r - Carriage return. Causes the cursor to move to the start of this line. Also generated by the key.
Variables of type char are occasionally used to store whole numbers rather than characters. You can say ch3 = 44; However, the range of numerical values that can be stored in type char is from -128 to 127, so this works only for very small numbers. Whole numbers are usually stored in variables of type int, which is faster for the computer to process than type char. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 22 of 701
Integers
Integers represent whole numbers, that is, values that can be counted, such as the number of people living in Thomasville (12,348) or lines of text on a page (33). Integers cannot represent numbers with decimal points or fractional parts, such as 2.3 or 4/7. Integers can be negative: -7, -413. There are three integer types in C++: short, int, and long. They are similar but occupy different amounts of memory and can handle numbers in different numerical ranges, as Table 1-3 shows. I also include type char in this table, even though it is mostly used for characters, because it can be used to store small whole numbers as well. Table 1-3 Integer types in C++ Type - Name - Size - Range char - 1 byte (8 bits) - -128 to 127 short - 2 bytes (16 bits) - -32,768 to 32,767 int - Same as short on 16-bit systems, same as long on 32-bit systems long - 4 bytes (32 bits) - -2,147,483,648 to 2,147,483,647 Type short always occupies 2 bytes. It can store numbers in the range of -32,768 to 32,767. Type long always occupies 4 bytes and can store numbers in the range of -2,147,483,648 to 2,147,483,647. In 16-bit systems, type int occupies 2 bytes, the same as short. In 32-bit systems, it occupies 4 bytes, the same as long, and can therefore handle a larger range of values. Older operating systems, such as DOS and Windows 3.1, are 16-bit systems. Newer systems, such as Windows 95, OS/2, and Windows NT, are 32-bit systems. Unix has always been a 32-bit system. The int type is the most commonly used integer type and operates the most efficiently whatever system you use. However, if you want to guarantee a 2-byte variable even in a 32-bit system (to save space), you must use short; if you want to guarantee a 4-byte variable on a 16-bit system (to hold large values), you must use long. Heres an example of defining some integer variables and giving them values: int MilesDriven; <-- declare variables long population; MilesDriven = 1024; <-- give the values population = 1405836L; The L is used to designate a type long constant, one that wont fit in an integer (in a 16-bit system).
Unsigned Integers
All the integer types have unsigned versions. Unsigned variables cant hold negative numbers, but their range of positive values is twice as large as that of their signed brothers. Table 1-4 shows how this looks. Table 1-4 Unsigned integers Type Name - Size - Range
unsigned char - 1 byte (8 bits) - 0 to 255 unsigned short - 2 bytes (16 bits) - 0 to 65,535 unsigned int or unsigned - Same as unsigned short on 16-bit systems, Same as unsigned long on 32-bit systems unsigned long - 4 bytes (32 bits) - 0 to 4,294,967,295
Ordinary integers, without the unsigned designation, are signed by default. You can use the keyword signed, but its not necessary.
Page 23 of 701
Floating Point
Floating-point numbers are used to represent values that can be measured, such as the length of a room (which might be 5.32 meters) or the weight of a rock (124.65 pounds). Floating-point values are normally expressed with a whole number to the left of a decimal point and a fraction to the right. Instead of a decimal point, you can use exponential notation for floating-point numbers. Thus, 124.65 in normal notation is 1.2465e2 in exponential notation, where the number following the e indicates the number of digits the decimal point must be moved to the right to restore the number to normal notation. Exponential notation is commonly used to display numbers that are inconveniently long in decimal notation. Thus, 9,876,000,000,000,000,000 in normal notation is 9.876e18 in exponential notation. There are three kinds of floating-point numbers in popular operating systems, as shown in Table 1-5. (Some systems dont have long double.) Table 1-5 Floating-point numbers Type Name - Size - Range - Precision float - 4 bytes (32 bits) - 10e-38 to 10e38 5 digits double - 8 bytes (64 bits) - 10e-308 to 10e308 15 digits long double - 10 bytes (80 bits) - 10e-4932 to 10e4932 19 digits
The most common floating-point type is probably type double, which is used for most C++ mathematical library functions. Type float requires less memory than double and may speed up your calculations. Type long double is used in the floating-point processor in Intel microprocessors and is useful for very large numbers. Heres an example of defining and using variables of the various floating-point types: float pi_float; double pi_double; long double pi_long_double; pi_float = 3.1415; pi_double = 3.14159265358979; pi_long_double = 3.141592653589793238; Here Ive assigned constants representing the mathematical constant pi to variables of the three types, using as many digits of precision as each type will allow. Figure 1-11 shows the amounts of memory required for all the data types except long double. You can initialize variables to specific values when they are first declared. Thus the six statements above could be condensed into float pi_float = 3.1415; double pi_double = 3.14159265358979; long double pi_long_double = 3.141592653589793238;
Page 24 of 701
Whitespace
C++ doesnt mind if you put extra spaces into a program line. You can use them to align things so theyre easier to read. You could say float pi_float = 3.1415; double pi_double = 3.14159265358979; long double pi_long_double = 3.141592653589793238; You can put as many spaces, tabs, and new lines as you want in your program. These characters constitute whitespace, and the C++ compiler ignores whitespace in almost all situations. Programmers use whitespace to make the program easier for humans to follow.
Comments
You can add comments to your program listing to help yourselfand anyone else who might need to look at your listingunderstand what it does. Comments are preceded with a double slash: //. Heres a code fragment, taken from the previous section, that uses a full-line comment and comments following each of three statements: // these variables are declared and initialized at the same time C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 25 of 701
float pi_float = 3.1415; // 5-digit precision double pi_double = 3.14159265358979; // 15-digit precision long double pi_long_double = 3.141592653589793238; // 19-digit precision Any text following the // symbol until the end of the line is ignored by the compiler. Another kind of comment allows you to write comments that span multiple lines. Heres an example: /* if you have a really long multiline comment it is easier to use this arrangement than to write the double-slash symbol before every line */ The /* symbol starts the comment and the */ ends it. The end of a line does not automatically end a comment that starts with /* (as it does with those starting with //), so you can write as many comment lines as you want before terminating with the */ symbol. This comment style is harder to type and requires two symbols per comment instead of one, so it is not usually used for single-line comments. As you know, adding numerous comments to your code makes it far more comprehensible to anyone else who must understand your program. And, difficult as it may be to believe, you yourself may find comments helpful if you try to read a listing you havent seen for some time.
Quiz 4 1. To represent a 4-digit employee serial number, you would most likely use type a. unsigned int. b. long. c. char. d. float. e. double. 2. Several different integer types (and also several different floating-point types) are necessary because a. there is a trade-off between operating speed and memory storage space. b. there is a trade-off between speed and memory space, on the one hand, and the size of the numbers that can be stored, on the other. c. there is a trade-off between the number of significant digits that can be used and the size of the exponent. d. different computer systems have different storage requirements. e. different computer languages have different storage requirements. 3. To represent the atomic weights of elements (e.g., iodine is 126.932) you would most likely use type
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 26 of 701
a. double. b. long double. c. unsigned long. d. unsigned char. e. int. 4. The assignment operator a. asserts that one variable is equal to another. b. places the value on the right into the variable on the left. c. sets the constant on the right equal to the constant on the left. d. places the constant on the left into the variable on the right. e. makes an equivalence between the names of two variables. 5. The term whitespace in a source file is related to a. the margins of the paper. b. the characters , \t, \n and perhaps a few others. c. formatting used in a program listing to make it easier for humans to read. d. program statements that dont change any data. e. symbols that are ignored by the compiler.
Now that you know enough about C++ to write actual program statements, Ill include exercises at the end of each lesson.
Exercise 1
Write statements to create variables called LetterCode, SalePrice, and Quantity of types char double, and long.
Exercise 2
Write statements to set the three variables in Exercise 1 to V, 67.95, and 1,000,000, respectively.
Midchapter Discussion
Imagine that this book is being used in a programming class and that the students get together from time to time to discuss how the course is going. The following is an example of one such discussion. Ill include two discussions per chapter: one halfway through the chapter and one at the end. George: Estelle: George: Oh, boy, Im in trouble. The only thing I understood at all was about data types, because theyre the same as C. You know C? Then this course should be a pushover for you. Are you kidding? Everything but the data types was over my head. Objects? Classes? I dont have Page 27 of 701
a clue. I know what you mean; its not totally clear to me either. I think I have to take it on faith that all this will lead to something useful. But you get the main idea, dont you? About needing a better way to design programs because the old procedural approach just got too out of control? Well, I guess I understand it in theory. But Ive never written such a huge program. Just what I took in my Pascal class. I was involved in a big project in C at work, and it certainly got complicated. If theres some way to make working with huge programs easier, Im all for it. Yeah, but how does this object stuff lead to anything practical? Im not learning how to write program statements that actually do things, like adding two numbers or whatever. But C++ isnt a procedural language. Its based on objects, so Ive got to learn to make objects before I can do anything useful. Right. In a procedural language, the programmer gives instructions to the computer to do things but in an OOP language, the programmer tells objects to do things. And you cant tell an object something if you dont have any objects. And to create objects you need a class to tell you what the objects will look like. But to specify the class you need to know how to declare data variables, which I just learned about, and write functions, which I bet I get to this afternoon. Well, I want to display some numbers! Do some arithmetic! I want a complete program! Patience, George, patience.
the next chapter.) Figure 1-12 shows how cout works. Figure 1-12 Output with cout
String Constants
The text This text will appear on the screen. is called a string constant. A string constant is surrounded by double quotes (unlike a character constant, which is surrounded by single quotes). The entire statement, as with all C++ statements, is terminated with a semicolon. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 28 of 701
You can output numerical constants the same way. The statement cout << 27; causes a 27 to appear on the screen, whereas cout << 123.45; displays 123.45. You can display the values of variables as well as numbers. For example, this code displays 2.7: float fvar1 = 2.7; cout << fvar1; More than one value can be displayed with a single cout statement, using multiple put to operators: float height = 2.7; cout << It is << height << meters high.; This would produce the output It is 2.7 meters high. You can also reformat such a statement so that the put to operators line up vertically, thus making the statement easier to read: cout << It is << height << meters high.; Or you could use three separate cout statements to produce the same output.
Formatting Output
Its nice to have some control over the way output is formatted. In C++ stream I/O, a variety of techniques are available for this purpose. Ill show two examples here.
Escape Sequences
One of the easiest ways to format data is to insert a character called an escape sequence into a string constant. For example, you can use the \n escape sequence to start a new line: cout << \nNow I'll ask you some questions about yourself.; cout << \nFirst, enter your age: ; produces the display Now I'll ask you some questions about yourself. First, enter your age: The \n before First causes the second string constant to be displayed on a separate line from the first. (The '\n before Now ensures that the first line begins on a new line as well, even if something else has already been printed by a preceding statement.) You can use the escape sequence '\t to generate tab characters. The code cout << \nJoe\tFred\tSally; cout << \nGeorge\tBud\tSandy; lines up the names in columns. Joe Fred Sally George Bud Sandy
common manipulator is endl (a contraction of end line). Inserted into a cout statement, endl causes the cursor to move to the start of the next line, just as '\n does. (Also, endl flushes any output that may be waiting in the output buffer to the display.) Earlier, I showed two lines that used the \n escape sequence to start text on a new line. Heres how to achieve the same result using endl: cout << endl; cout << Now I'll ask you some questions about yourself. << endl; cout << First, enter your age: ; The first statement ensures you start on a new line and the endl at the end of the second statement causes the text beginning with First to start on a new line.
number and press . Figure 1-13 shows how this looks. Figure 1-13 Input with cin Usually, of course, you want to prompt the user before waiting for input: int age; cout << Enter your age: ; cin >> age; This produces the following interaction: Enter your age: 34 where the user enters the 34. You can use the get from operator multiple times in the same statement: int age; float height; cout << Enter your age and height:; cin >> age >> height; Here the user presses <ENTER>,<SPACE>, or <TAB>after each value before entering the next one. However, its usually better to prompt the user for only one value at a time, to avoid any possibility of confusion.
Stream I/O
The techniques for input and output Ive shown here are called stream I/O. A stream is a general term for a flow of data. As youll see when you write a complete program, to use stream I/O, you need to include a file of declarations in your program. This file, IOSTREAM.H, is called a header or include file. There is much more to be said about stream I/O in C++. Ill return to this topic in Chapter 10.
Page 30 of 701
Old-Style C I/O
If you are a C programmer, you are familiar with a completely different style of input and output using printf(), scanf(), and similar library functions (declared in STDIO.H). You can use these functions in C++ as well, but the preferred approach is to use stream I/O. Why? First, its easier to avoid mistakes. Have you ever used the wrong format specifier in printf() (e.g., %d instead of %f), so that data is displayed incorrectly? Its easy to do, and in scanf(), the wrong specifier can crash your program as well. Second, the stream I/O approach lets you use cout and cin with classes you write yourself. This allows you to perform I/O on any programming object the same way its performed on basic data types. This is a very powerful technique, which is unavailable with standard C I/O. Some old-time C programmers cant get the hang of stream I/O and persist in using standard I/O, but they are missing out on some powerful and important features of C++.
Quiz 5 1. Which of the following are true? a. >> means get in. b. << means get out. c. cfile transfers data to files. d. cout represents the screen. e. cout represents the keyboard. 2. Which of the following will display Nancy is 27 on the screen (assuming the variables have appropriate values)? a. cin >> Nancy is >> 27; b. cin >> Nancy is
cin >> age;
c. cout << Nancy is ; cout << age; d. cout << Nancy is << 27; e. cout >> Nancy is ;
cout >> age;
3. The statement cin >> name >> age >> height; will a. potentially confuse the user. b. display name, age, and height. c. probably accept input of different types. d. require the user to separate constants with spaces or similar characters. e. display prompts.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 31 of 701
a. cout << 1\t2\n3\t4\n; b. cout <<1<<\t<<2<<\n<<3<<\t<<4<<\n; c. cout << 1\n2\t3\n4\t; d. cout <<1<<\t<<2<<\n<<3<<\t<<4<<\n; e. cout << 1 << \n << 2 \t; cout << 3 \n 4 \t; 5. In a comparison of C++ I/O (stream I/O) and old-fashioned C I/O (standard I/O), which of the following is true? a. Stream I/O is faster. b. You can modify almost any object to use stream I/O but not standard I/O. c. All objects know how to display themselves automatically in stream I/O. d. Standard I/O is more intuitive. e. Standard I/O allows you to avoid format specifiers. Exercise 1
Thinking about the hot dog program, write a program fragment that displays the number of hot dogs and buns on hand. The output might look like this: Hot dogs on hand = 125 Buns on hand = 133
Exercise 2
Write a program fragment that asks the user for the initial values of hot dogs and buns on hand, gets responses from the user, and then sets appropriately named variables to these values.
Exercise 3
Write a program fragment that does the following:
Creates two variables, num and denom, that represent the numerator (top) and denominator (bottom) of a fraction. Asks the user to supply values for the numerator and denominator. Puts the values supplied into the variables. Displays the fraction in the format 2/3, with a slash between the two numbers.
Some sample interaction with this program fragment might look like this: Enter the numerator: 4 Enter the denominator: 7 Fraction = 4/7 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 32 of 701
Recording a Sale
When a hot dog is sold, the stand operator calls Sally to tell her this fact. When she receives such a call, she wants the program to decrease both the number of hot dogs and the number of buns by one. Heres a member function that does the job: void SoldOneDog() { HotDogsOnHand = HotDogsOnHand - 1; // subtract 1 from variable BunsOnHand = BunsOnHand - 1; // subtract 1 from variable } Here theres no interaction with the user, only a little arithmetic. Figure 1-14 shows how Sally interacts with an object of class HotDogStand, using its member functions.
Page 33 of 701
Displaying Data
Remember that a HotDogStand object records the numbers of hot dogs and buns on hand, in the variables HotDogsOnHand and BunsOnHand. You can use cout statements to display these values. The resulting displayData() function looks like this: void displayData() { cout << Hot dogs on hand = << HotDogsOnHand << endl; cout << Buns on hand = << BunsOnHand << endl; } The output from this function will be something like this: Hot dogs on hand = 347 Buns on hand = 298 Youve simply inserted two statements into the function. These statements make up the function body. (As youve seen, you could produce the same output with four cout statements or with only one.)
Page 34 of 701
Arithmetic Operators
In the SoldOneDog() function, shown above, you used the subtraction operator (-) to subtract one from two values. C++ includes the usual four arithmetic operators plus a fifth, less common one, as shown in Table 1-6. Table 1-6 Arithmetic operators Operator - Purpose
+ - Addition - - Subtraction * - Multiplication / - Division % - Remainder The first four operators perform familiar operations. The remainder operator, % (also called the modulus operator), is used to calculate the remainder when one integer is divided by another. Thus the expression 20 % 3 evaluates to 2, because 20 divided by 3 is 6, with a remainder of 2. You can use any of the arithmetic operators just as you would in normal arithmetic or algebra or most other programming languages. They are called binary operators because they operate on two quantities. That is, expressions involving them have the form alpha+beta, where the + operates on alpha and beta. Of course, you can make arithmetic expressions as complicated as you want. For example, c = (f-32) * 5 / 9; converts a temperature in Celsius to one in Fahrenheit. Note that I use parentheses so the subtraction will be carried out first, despite its lower precedence. (The term precedence refers to the order in which operations are carried out. The * and / operators have higher precedence than the + and - operators.) In C++, its perfectly all right to mix different arithmetic types in the same expression. For example, in the above statements, f might be type int and c might be type float. The compiler would not complain. Instead, it would automatically convert the types appropriately before carrying out the arithmetic.
Quiz 6 1. The proper role of member functions is limited to a. performing stream I/O.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 35 of 701
b. allowing a class to alter its data. c. allowing a class to interact with the outside world. d. allowing objects to carry out any appropriate activity. e. performing arithmetic operations on instance data. 2. The member functions of the HotDogStand class can a. report to the user the number of buns and the number of hot dogs at a stand. b. calculate the profit made at each stand. c. record the sale of any number of hot dogs with a single function call. d. make change. e. carry out an arithmetic operation on a stands inventory. 3. To what entities can the five basic C++ arithmetic operators be applied, assuming you dont overload these operators? a. objects b. integer variables c. floating-point constants d. characters e. classes 4. The expression 41 % 7 evaluates to a. 7 b. 6 c. 5 d. 4 e. 1 5. If the value of age is 21, what statement (or statements) will change it to 23? a. ++age; b. age = ++age + 1; c. age = 2 + --age;
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 36 of 701
Exercise 2
Imagine a class called Employee with two data items, employee_number and employee_salary, which record an employees ID number and salary. Write statements to create these variables, the first type int and the second type double. Write a function, get_emp(), to get values for these variables from the user and another function, put_emp(), to display the values.
Ill devote the remainder of this lesson to the exercises, which will do more than anything to solidify your understanding of class specifications.
Quiz 7 1. The HotDogStand class specification a. creates a number of HotDogStand objects. b. provides data and functions that will be accessed from main(). c. serves as part of an inventory program for hot dog stands. d. allows the creation of a number of hot dog stand objects. e. accomplishes nothing useful until other parts of the program are written. 2. The SoldOneDog() function a. hands the hot dog to the customer and makes change. b. decrements the number of buns and hot dogs. c. displays the number of buns and hot dogs. d. obtains the number of buns and hot dogs from the user. e. calculates the number of buns and hot dogs remaining. 3. When a member function in the HotDogStand class performs I/O with cout or cin, it is interacting with the user on behalf of a. a class. b. an object. c. a function. d. data. e. main(). 4. If you created a class where each object represented an aircraft, this class would likely include a. airport runway data. b. a function to change the aircrafts direction. c. the positions of other aircraft. d. the aircrafts altitude.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 38 of 701
e. luggage-handling capability. 5. In a traffic simulation program, its most likely there would be a class of a. yellow school buses. b. highways. c. vehicles. d. acceleration characteristics of various vehicles. e. speeds. Exercise 1
Rewrite the class specification for the HotDogStand class to include a data item for the cash on hand (you can call it CashOnHand), stored in dollars and cents format (e.g., 198.95). Assume the retail price of a hot dog (including the bun) is $1.95. Modify the member functions as follows:
Add statements to initData() so the user can specify the initial amount of cash on hand at the beginning of the day. Add statements to SoldOneDog() that subtract the price of a hot dog from CashOnHand when a sale is made. Add statements to displayData() that display the cash on hand along with the number of dogs and buns. Exercise 2
Write a complete class specification, including member functions, for an elevator class. Each object of this class should include a data item that records the floor the elevator is currently on (e.g., from 1 at the bottom to 20 at the top). Write member functions that take the following actions:
Display the floor the elevator is on. Move the elevator up one floor. Move the elevator down one floor.
What happens when this statement is executed? First, your program finds the specification for the HotDogStand class (which must have appeared earlier in your listing). It figures out how large such an object needs to be and sets aside enough memory to hold it. It then gives this memory space a name: stand1. This is exactly what it would do with a variable of a basic type. However, stand1 is more complicated because it has several pieces of data and several member functions as well. You can create as many objects as you need. HotDogStand stand1; HotDogStand stand2; HotDogStand stand3; Or (as with basic data types) you can also use a single statement to create multiple objects of the same type. HotDogStand stand1, stand2, stand3; So you see that, although writing a class specification may appear difficult or at least unfamiliar, creating objects based on this specification is simplicity itself.
this looks. Figure 1-15 Syntax for sending a message When this statement is executed, stand1 will display its data Hot dogs on hand = 347 Buns on hand = 298 (or whatever values are actually stored in its data variables). You can send the same message to a different object, for example, stand2. stand2.displayData(); It will then display its data (which is probably not the same as that in stand1). Hot dogs on hand = 74 Buns on hand = 82 Similarly, you could send a message to stand3 telling it to get initial data values from the user. stand3.initData(); When this statement is executed, stand3 will call its initData() function and the following interaction with the user might take place: C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 40 of 701
Enter hot dogs on hand: 19 Enter buns on hand: 21 where the user enters the 19 and 21. Once the stand3 object has been given these initial values, you could call the SoldOneDog() function every time a hot dog is sold at this stand. stand3.SoldOneDog(); This would cause the HotDogsOnHand and the BunsOnHand variables in stand3 to be decremented, although nothing would appear on the screen. If you then executed stand3.displayData(); you would see Hot dogs on hand = 18 Buns on hand = 20 In other words, you can call member functions of specific objects to make the objects do useful work for us. This is what OOP is all about.
Quiz 8 1. Creating an object normally involves a. declaring a variable. b. causing the compiler to designate memory space for the object. c. specifying what data and functions go in the object. d. using the object as a specification for a class. e. giving a name to the object. 2. Sending a message to an object normally involves a. modifying the objects data. b. calling one of the objects member functions. c. specifying the particular object and the message to be sent to it. d. using a dot (or other) operator to connect the object name and the member function name. e. changing the class specification. 3. If you create a class of aircraft named acraft and you want to tell an aircraft called ac1 to turn right, you might say a. ac1.rightTurn();. b. acraft.ac1.rightTurn();. c. rightTurn(ac1);. d. rightTurn().ac1;. e. acraft.rightTurn();.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 41 of 701
4. When you send a message to an object, the message content is determined by a. which object the message is sent to. b. the data in the object at the time. c. who sends the message. d. which member function you call. e. arguments to a member function. 5. To send a message to an object, you must always a. include the data to be placed in the object. b. name the class of the object. c. specify the member function youre calling. d. use the name of the object you are sending the message to. e. declare the object. Exercise 1
Start with the elevator class specification of Exercise 2 in Session 7. Write statements that create an elevator object, display its position, move it up two floors, and display its new position.
Exercise 2
Start with the augmented HotDogStand class specification of Exercise 1 in Session 7. Write code that will allow the user to enter, for a particular stand, the hot dogs, buns, and cash on hand. Then tell the HotDogStand object to sell two hot dogs and display the same three variables.
Summary: Chapter 1
Lets review the main points in this chapter. Youve seen that the fundamental building blocks of C++ programs are objects, which are created using a class as a blueprint. Designing a program means figuring out what the objects will represent. The two important constituents of objects are instance data and member functions. An objects data is usually made private, or inaccessible to all parts of the program except the objects own member functions. This makes it less likely the data will be corrupted by a programming error. Once a class is specified and objects of that class are created, a program sends messages to the objectsthat is, it calls their member functionsto cause them to carry out appropriate tasks, usually involving the objects own data. In Chapter 2, youll put together everything youve learned in this chapter, plus a few new details, to create a complete, working C++ program.
End-Of-Chapter Discussion
I think Im starting to get it. Everything in the program is an object. A class specification describes how a bunch of similar objects will look. So the trick in designing a program is to figure out what the objects are going to be. Estelle: That seems hard now, but I bet itll get easier. George: I think its nuts. If I want to print a line of text, Ive got to figure out what objects to use, write a C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 42 of 701 Don:
specification for a class, define objects, and send one of them a message to print the text. Youve got to be kidding! I can print a text with one statement in regular C code! So if all your program does is print one line, write it in C. The point is, most programs do more than that. So be sarcastic. Im just afraid all this OOP stuff will be so complicated it wont be worth the trouble, even for major programs. Lots of people seem to think it works fine for major programs. Ive got a different question. I cant understand how anyone ever figured out that it would be a good idea to put data and functions together to make objects. I know, its not exactly an obvious concept. It must have been a major inspiration. More likely a lucky guess. If it works at all, which I doubt. Itll work. In the next chapter, well see a complete program. Its about time!
Page 43 of 701
class HotDogStand // class specification { private: int HotDogsOnHand; // hot dogs on hand int BunsOnHand; // buns on hand public: void displayData() // display hot dogs and buns { cout << \n Hot dogs = << HotDogsOnHand; cout << \n Buns = << BunsOnHand; } void SoldOneDog() // record sale of one dog { --HotDogsOnHand; --BunsOnHand; } void initData() // set initial quantities { cout << \n Enter hot dogs on hand: ; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 44 of 701
};
cin >> HotDogsOnHand; cout << Enter buns on hand: ; cin >> BunsOnHand; } // end of HotDogStand class
///////////////////////////////////////////////////////////// void main() { HotDogStand stand1; // create hot-dog stand objects HotDogStand stand2; // set initial data cout << \nInitialize data for stand 1; stand1.initData(); cout << \nInitialize data for stand 2; stand2.initData(); // record some sales cout << \nSelling 2 dogs from stand1; stand1.SoldOneDog(); stand1.SoldOneDog(); cout << \nSelling 3 dogs from stand2; stand2.SoldOneDog(); stand2.SoldOneDog(); stand2.SoldOneDog(); cout << endl; // display current data cout << \nSupplies on hand, stand1; stand1.displayData(); cout << \nSupplies on hand, stand2; stand2.displayData(); } Youve seen the specification for the HotDogStand class before. Youve also seen the statements that create hot dog stand objects and that access their member functions. Lets look at two unfamiliar parts of the program.
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 located; 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 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 45 of 701
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.
Enter hot dogs on hand: 223 Enter buns on hand: 227 Selling two dogs from stand 1 Selling three dogs from stand 2 <--Interaction with SoldOneDog()
Supplies on hand, stand 1 <--Interaction with displayData() Hot dogs = 101 <--(Program displays numbers) Buns = 105 Supplies on hand, stand 2 Hot dogs = 220 Buns = 224 As youve seen, the user enters the initial amounts by calling the initData() function for each object. The program then somewhat arbitrarily sells two dogs from stand 1 and three dogs from stand 2, and displays the resulting supplies on hand. Of course, this kind of interaction isnt really very useful, because the program always causes the exact same number of hot dogs to be sold. A more practical program would allow the user to enter a sale for any hot dog stand at any time, and print out supplies on hand for a particular stand when requested. Youll see how to achieve this after you learn about loops and decisions later in this chapter.
Program Organization
Heres how your source file is usually arranged. Preprocessor directives such as #include come first, followed by class specifications, followed by main(). The main() function contains statements that define objects and send messages to them. Figure 2-1 shows what this looks like.
Figure 2-1 Source file organization In larger programs, things may be a bit more complicated. There will probably be many files. Some will contain class specifications, whereas others will hold the code that uses these classes. Nevertheless, the simple arrangement I use here reveals the essentials of OOP. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 47 of 701
Quiz 1 1. Preprocessor directives a. tell the compiler to do something before translating the source file into machine code. b. give the compiler instructions that will be translated into machine code. c. are executed just before the program executes. d. are executed just before the program is compiled. e. are executed just before the program is linked. 2. Which of the following will most efficiently include the file CONIO.H, located in the compiler systems \INCLUDE\ directory, into your source file? a. #include conio.h b. #include ..\include\conio.h c. #include <conio.h> d. #include <..\include\conio.h>; e. #include conio; 3. The main() function a. must contain any class specifications. b. is a member function of all classes. c. may return void or int. d. is the first function to be executed when a program is executed. e. may contain statements that create objects. 4. In HOTDOG1, the user cant interactively signal the program every time a hot dog is sold because a. such interaction is impossible in an object-oriented language. b. there is no member function to handle this activity. c. you havent learned yet how to write loops and decisions in C++. d. the main() function does not include code to handle this activity. e. there are no I/O statements in the program.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 48 of 701
5. The general scheme of program organization youve seen so far is that a. a class is specified outside of main() and objects of that class are created in main(). b. objects are defined outside of main() and told what to do within main(). c. all parts of the program are within main(). d. objects are told what to do inside of main(). e. classes tell objects what to do and when to do it. Exercise 1
Write a complete C++ program based on the elevator class specification and instructions of Exercise 1 in Chapter 1, Session 8, which encouraged you to display the elevators position, move it up two floors, and display its position again. Run this program. Its output should look like this: Elevator location: floor 1 Elevator location: floor 3
Exercise 2
Start with Exercise 2 in Chapter 1, Session 8. This augmented hot dog stand class (hotdogstand2) included a cashon-hand data item in addition to the number of hot dogs and buns. Create a complete program, with functionality similar to HOTDOG1, that uses the capabilities of this class. Create two hot dog stand objects from the hotdogstand2 class. Interaction with the program should look something like this: Initialize data for stand 1 Enter hot dogs on hand: 103 Enter buns on hand: 107 Enter cash on hand: 100.00 Initialize data for stand 2 Enter hot dogs on hand: 223 Enter buns on hand: 227 Enter cash on hand: 200.00 Selling two dogs from stand 1 Selling three dogs from stand 2 Supplies on hand, stand 1 Hot dogs = 101 Buns = 105 Cash = 96.10 Supplies on hand, stand 2 Hot dogs = 220 Buns = 224 Cash = 194.15
Session 2: Loops
You cant write interesting programs without loops. A loopless program does something once, then exits. A program with a loop, on the other hand, can do something as many times as necessary. Now Im going to introduce the three kinds of loops available in C++. If you already know C, you can probably skip this lesson, because loops are the same in C and in C++. To determine how many times to cycle around a loop, all C++ loops check whether an expression is true or false. This tells them whether to cycle one more time or to exit the loop immediately. Thus, to understand loops you must first examine what makes an expression true or false,and how to construct such true/false expressions. Then you can examine specific kinds of loops: the while loop, the do loop, and the for loop. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 49 of 701
Relational Operators
Relational operators compare two values and evaluate to a true/false value, depending on whether the comparison is true or not. There are six such operators, as shown in Table 2-1. Table 2-1 Relational operators Symbol - Meaning - Example == - equal to - a == b != - not equal to - a != b < - less than - a < b > - greater than - a > b <= - less than or equal to - a <= b >= - greater than or equal to - a >= b The expressions in the Example column will be either true or false depending on the values of a and b. For example, suppose a is 9 and b is 10. Then a==b is not true, because 9 does not equal 10, but a!=b is true, as are a<b and a<=b. The expressions a>b and a>=b are false. You can compare characters as well as numbers, because characters have underlying (ASCII) numerical values. Thus, its true that a<b and that A==65, but not true that z<=a (because z in fact has a higher ASCII value than a). Now that you understand relational operators, lets look at the three kinds of C++ loops and see how they decide what to do based on expressions that use these operators.
while Loops
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 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 50 of 701
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.
Figure 2-2 Syntax of the while loop If the body of the loop consists of only one statement, you dont need the braces. 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.
Page 51 of 701
Figure 2-3 Operation of the while loop 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.
Figure 2-4 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. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 52 of 701
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.
Figure 2-5 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.
for Loops
In both the while and do loops, you usually dont know, at the time you enter the loop, how many times the loop will be executed. The condition that terminates the loop arises spontaneously inside the loop: The user answers n instead of y, for example. This is not the case with for loops. In a for loop, the number of times the loop will be executed is (usually) stated at the beginning of the loop. Heres a loop that prints 20 asterisks in a line across the page: int j; // define the loop variable for(j=0; j<20; ++j); // cycle 20 times cout << '*'; // print asterisk The parentheses following the keyword for contain three different expressions, separated by semicolons. In the most common situation, these three expressions operate on a variable called the loop variable, which in this example is j. These three expressions are
The initialization expression, which usually initializes the value of a loop variable.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 53 of 701
The test expression, which usually checks the value of the loop variable to see whether to cycle again or to quit the loop. The increment expression, which usually increments (or decrements) the value of the loop variable.
In the example, the loop variable j is given an initial value of 0, then the test expression is evaluated. If this expression is true (if j is less than 20), the body of the loop is executed and the increment expression is executed (js value is increased by 1). If the test expression is false, the loop terminates. Figure 2-6 shows the syntax of the
for loop, and Figure 2-7 depicts its operation. Figure 2-6 Syntax of the for loop
Page 54 of 701
Figure 2-7 Operation of the for loop How many times will the loop in the example be executed? 20 times. The first time through the loop, j is 0; the last time, its 19. (It does not run from 1 to 20, as you might expect.) Starting at 0 and continuing until the loop variable is 1 less than a constant is the most common arrangement in for loops because, among other reasons, array indexes (which youll learn about in Chapter 3) typically start at 0 and go up to 1 less than the size of the array. If you did want the loop variable to run from 1 to 20, you could write for(j=1; j<=20; ++j) // body of loop where the less-than-or-equals operator is used instead of the less-than operator. However, this is not a common idiom in C++. Notice that, as in the while loop, the test expression is evaluated before the loop body is executed the first time. Thus, the loop body may not be executed at all if the test expression is false to begin with. Heres another example of a for loop, this one with multiple statements in the loop body. As in the other loops, multiple statements must be surrounded by braces. int j; // define loop variable int total = 0; // define and initialize total for(j=0; j<10; ++j) // cycle 10 times { total = total + j; // add j to total cout << total << ' '; // display total } This fragment will display the sequence 0 1 3 6 10 15 21 28 36 45 Notice that there is no rule that says that the loop variable must be increased by 1 each time through the loop. You can also decrease it by 1: for(j=10; j>0; --j) cout << j << ' '; which will display 10 9 8 7 6 5 4 3 2 1 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 55 of 701
or you can increase it or decrease it by any other amount. This code for(j=0; j<100; j=j+10) cout << j << ' '; will display 0 10 20 30 40 50 60 70 80 90 There is a surprising amount of flexibility in what you can put in the three expressions in a for loop. For example, you can use multiple statements in the initialization expression and the test expression. Heres an example of a for loop with such multiple statements: for(j=0, total=0; j<10; ++j, total=total+j) cout << total << ' '; // display total This loop prints 0 1 3 6 10 15 21 28 36 45 as in the earlier example. However, here the variable total is set to 0 in the initialization expression instead of before the loop, and increased by j in the increment expression instead of in the loop body. The individual statements in these expressions are separated by commas. Another option is to leave out any or all of the three for loop expressions entirely, retaining only the semicolons. Generally, taking advantage of the flexibility of the for loop in these ways causes more confusion than its worth, but big-time C gurus enjoy it.
Nested Loops
You can nest one loop inside another. For example, the following program fragment prints a 10 by 10 square of Xs, like this: xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx in the upper-left corner of the screen. for(y=0; y<10; y++) // outer loop, drops down line-by-line { for(x=0; x<10; x++) // inner loop, goes across char-by-char cout << 'X'; // print 'X' cout << endl; // go to new line } Of course, you can embed any kind of loop inside any other kind of loop, and loops can be nested in loops that are nested in other loops, and so on.
Logical Operators
Its often convenient to test the true/false values of several expressions at the same time and combine the results into a single true/false value. For instance, you might want to create a true value any time both x<10 and y<5 are true. True/false values can be operated on with logical operators. There are three logical operators in C++ (see Table 22). Two of them combine two true/false values, and the third negates a true/false value. Table 2-2 Logical operators Logical Operator | Meaning | Example && | AND | x>0 && x<10 || | OR |x==3 || x<1 ! | NOT |!x C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 56 of 701
An AND expression is true only if the expressions on both sides of the && operator are true. Thus, the example in the table is true only if x is greater than 0 and also less than 10. The AND operator is often used to determine whether something is in a specified range. In the example in the table, the expression is true if x is in the range of 1 to 9. Similarly, the expression ch >= 'a' && ch <= 'z' is true if ch is a lowercase letter. The OR (||) operator evaluates to true if either or both of its operands are true. For example, j==0 || ch == 'q' is true if either j is 0 or ch equals q, or if both are true. The OR operator is often used to discover if a value is outside a specified range. The expression temp<65 || temp>75 is true if temp is outside the 65 to 75 range (the comfort zone for Fahrenheit temperatures). The ! (NOT) operator negates the variable following it. Thus, !alpha is true if alpha is false, and false if alpha is true. (As you can see, this operator has an Alice in Wonderland quality.) Its often used when you want a while loop to continue until something is true, instead of continuing until its false. For example, while( !alpha ) { } will cycle until alpha becomes true (nonzero). Logical expressions can often be written in several ways. The expression temp<65 || temp>75 could also be written !(temp>=65 && temp<=75) because a value being not in range is the same as its being out of range.
Precedence
In normal algebraic expressions, multiplication and division are carried out before addition and subtraction. For example, in the expression 2*2+3*3 the 2s are multiplied (which gives 4), then the 3s are multiplied (giving 9), and only then are these results added, yielding 13. The multiplications are carried out before the addition because the * operator has a higher precedence than the + operator. If there was no precedence and the compiler just evaluated expressions blindly from left to right, the compiler would obtain a different answer. Multiplying the 2s gives 4, adding the 3 makes 7, and multiplying the result by 3 gives 21, which is not what you expect. Thus precedence is important in normal arithmetic expressions. Its also important when different C++ operators are used. You may have wondered why, when I say temp<65 || temp>75 how I can be sure that the true/false value of temp<65 and temp>75 are evaluated first, before being ORed together. If the processor proceeded from left to right, for example, temp<65 would be evaluated, ORed with temp (whatever that would mean, because temp is a number, not a true/false value), and the result compared with 75. This isnt what I want, of course. The expression is evaluated correctly because relational operators have a higher precedence than logical operators. How is the expression n + 2 < x + 3 evaluated? You want to compare two arithmetic expressions, n+2 and x+3. Is that whats happening? Yes, because arithmetic operators have a higher precedence than relational operators. Table 2-3 shows the precedence relations, with the higher precedence operators higher in the table. Table 2-3 Precedence relations Operators | Operator Types | Precedence / % | Multiplicative | Higher Page 57 of 701
+ - | Additive < > <= >= == != | Relational && || | Logical = | Assignment | Lower
Notice that the assignment operator, =, has the lowest precedence of all; that is, its applied after all the other operators. Youll see other examples of precedence relations as you go along.
Quiz 2 1. True values and false values are represented in C++ by the numerical values _____ and _____, respectively. a. any number except 0, 0 b. any positive number, 0 c. 0, any negative number d. 0, 1 e. 1, 0 2. Relational operators compare two a. true/false values and evaluate to a number (such as 77). b. numerical values and evaluate to a true/false value. c. logical values and evaluate to a true/false value. d. logical values and evaluate to a number. e. true/false values and evaluate to a logical value. 3. What expression(s) will cause the while loop to cycle as long as j is greater than 0 and ch is not q? a. while( !(j<=0 || ch==q) ) b. while(j > (0 && ch) != q) c. while(j > 0 && ch != q) d. while(j <= 0 && ch == q) e. while( !(j <= 0) || !(ch == q) ) 4. If you want a loop variable to run from 12 to 36 (inclusive) in steps of 3, you might write a. for(alpha==12, alpha<37, alpha=alpha+3)
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 58 of 701
b. for(x==12; x<=37; x=x+3) c. for(b=12, b<37, b+b+3) d. for( gamma=12; gamma<37; gamma=gamma+3 ) e. for(rad=12; rad<=36; ++rad, ++rad, ++rad) 5. while loops and do loops differ in that a. a do loop is terminated by a condition arising within the loop. b. the number of times a while loop will cycle is known before the loop is entered. c. a while is terminated by a condition arising within the loop. d. the body of a do loop is always executed at least once. e. the loop variable may be incremented within a do loop. Exercise 1
Write a loop that repeatedly asks the user to type a character and then displays the ASCII value of the character. Hint: You can cause a variable of type char to print as a number by converting it to type int. Use an expression such as int(ch), which converts the variable ch to an int. Have the loop exit when the user enters the character q.
Exercise 2
Write a program fragment, probably consisting of some nested loops, that asks the user what character to use to create a 10 by 10 square on the upper-left corner of the screen. Then display this square and ask for another character. Exit from the outermost loop when the user enters q .
The if Statement
The simplest way to make a decision in C++ is with the if statement. Heres an example of an if statement at work: if(denominator == 0) cout << Division by zero is illegal; If a variable called denominator is 0, this fragment causes a message to be displayed. If denominator is not 0, then nothing happens. As with loops, if you use more than one statement in the body of an if statement, you need to surround them with braces. if(choice == 's') // if user chooses sold dog' option { cout << Selling a dog; // verify I sold the dog stand1.SoldOneDog(); // sell the dog } An if statement consists of the keyword if followed by a test expression in parentheses. The loop body, which follows, consists of either a single statement or multiple statements surrounded by braces. (With the exception of the keyword, an if statement has the same syntax as a while statement.) Notice that there is no then keyword in C++, as there is in some other languages. The body of the loop follows immediately after the test expression. Figure 2-8 shows the syntax of the if statement and Figure 2-9 show how it operates. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 59 of 701
Actually, this simple approach doesnt handle the situation very well when hours is 0 (midnight) or 12 (noon). Youll see how to deal with these cases in the next lesson. As with other C++ constructions, the if body or the else body may consist of multiple statements surrounded by braces rather than the single statements I show here.
Test Expression
The test expression in an if or an ifelse statement can be just as complicated as expressions in loops. For example, this ifelse statement advances the day from February 28 to either March 1 or February 29, depending on whether the year is a leap year or not: // if it's Feb 28 and it's a leap year if(day==28 && month==2 && year%4==0 && year%100 != 0) day = 29; // then the next day is the 29th else // otherwise, { day = 1; // next day is March 1st month = 3; } Leap years occur when the year is divisible by 4 (e.g., 1996 is a leap year), but not divisible by 100 (so 1900 is not a leap year, although it is divisible by 4. The remainder operator (%) is used to find if the year is divisible by 4 (and by 100) with no remainder. The AND operators make sure that February 29 occurs only when all the conditions are true at once. (Yes, I know, leap years have other corrections as well.)
if(age<18) cout << \nChild; } else // this else goes with the top if cout << \nInfant; // appropriate response The braces make the entire if statement invisible to the else. (It would be nice if we could conceal ourselves from telemarketers this easily.) The moral is to be careful with complicated ifelse statements. Forgetting which if gets the else is a common source of annoying program bugs.
class HotDogStand // class specification { private: int HotDogsOnHand; // hot dogs on hand int BunsOnHand; // buns on hand public: void displayData() // display hot dogs and buns { cout << \n Hot dogs = << HotDogsOnHand; cout << \n Buns = << BunsOnHand; } void SoldOneDog() // record sale of one dog { --HotDogsOnHand; --BunsOnHand; } void initData() // set initial quantities { cout << \n Enter hot dogs on hand: ; cin >> HotDogsOnHand; cout << Enter buns on hand: ; cin >> BunsOnHand; } }; // end of HotDogStand class ///////////////////////////////////////////////////////////// void main() C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 62 of 701
{ char choice = 'x'; HotDogStand stand1; cout << \nInitialize data; stand1.initData();
// user's letter choice // create hot dog stand object // set initial data
while(choice != 'q') // { cout << \nEnter s to record cin >> choice; if(choice == 's') // { // cout << Selling a dog; stand1.SoldOneDog(); } } // end while // cout << \nSupplies on hand; stand1.displayData(); }
loop until 'q' typed sale, q to quit: ; if user entered 's' then sell a dog
A Glimpse of Reusability
One of the most important things to notice about Listing 2-2 is that the specification for the HotDogStand class is exactly the same as it was in the HOTDOG1 program. I have made significant changes to the functionality of the program, but I have not altered the class at all. In an embryonic way, this demonstrates one of the major strengths of OOP. I have reused the HotDogStand class in a new program. I havent worried about integrating or modifying the parts of the class; I simply inserted the entire class, just as I found it, into the new program. In a small program, the gain may not appear significant, but in larger programs, the savings in time and effort that result from being able to reuse already existing classes can be substantial. The savings may be especially large when an entire library of classes can be reused. Of course, it is also possible to reuse code, especially functions, in old-fashioned procedural languages such as C. However, the OOP approach, which reuses classes instead of functions, provides a more coherent and easily understood package for reuse, with both data and functions combined into a single entity. The interface between the class, which specifies how objects behave, and main(), which creates objects and sends messages to them, is cleaner and more easily understood than the relationship between some functions and some data thats unrelated to the functions and statements in main() that call functions and access the data.
Quiz 3 1. The test expression in an if statement a. is an expression that counts how often the if statement body will be executed. b. may contain logical and relational operators. c. determines whether the if statement body will be executed. d. may be evaluated after the if statement body is executed. e. may be any expression that evaluates to a true/false value.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 63 of 701
2. Which of the following are test expression(s) for an if statement that determine whether a character ch is a digit; that is, if it is in the range of characters from '0 to 9. a. if(ch>=0 && ch<=9) b. if ( ch >= 0 && ch <= 9 ) c. if(ch>0) else (ch<9) d. if( ch < 0 || ch > 9 ) e. if(ch > -1 && ch < 10) 3. Which of the following are true? a. An else is associated with the if that has the same indentation as the else. b. An else is associated with the if that is closest to and above the else, if that if is not surrounded by braces. c. An else is associated with the if that is surrounded by braces and immediately precedes the else. d. The body of an else is executed if the test expression in the corresponding if is true. e. The body of an else is executed if the test expression following the else is true. 4. The interface between a class and the rest of a program normally includes a. the class sending messages to objects. b. the program creating objects. c. the program sending messages to objects. d. objects sending messages to main(). e. the program manipulating the objects data. 5. OOP offers superior reusability because a. other parts of the program need not be concerned with how data is structured in an object. b. its easier for objects to communicate with each other. c. every object is built to the same specification. d. other parts of the program need to relate only to one thingobjectsnot to data and functions separately. e. the programmer need not be aware of the class specification.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 64 of 701
Exercise 1
Rewrite the program of Exercise 1 in Session 1 in this chapter so that it continuously waits for a character to be entered. The user should be able to make the elevator go up one floor by entering a + (pressing the + and <ENTER> ) and go down one floor by entering a -. After each floor change, the program should display the floor the elevator is on. Pressing q should terminate the program. Assume that the elevator starts on floor 1 and that the program informs the user of this fact when it is first started. Heres some sample interaction: Elevator is now on floor 1 Enter choice: + Elevator is now on floor 2 Enter choice: + Elevator is now on floor 3 Enter choice: Elevator is now on floor 2 Enter choice: q
Exercise 2
Rewrite the program of Exercise 2 in Session 1 in this chapter (which uses the hotdogstand2 class with a CashOnHand variable) so that it has the same functionality as the HOTDOG2 program (Listing 2-2).
else if(hours == 12) cout << Noon; else if(hours < 12) cout << hours << am; else cout << hours-12 << pm; The if that follows each else is simply moved up onto the same line, thus creating a sort of artificial else if construction and removing the multiple levels of indentation. This arrangement not only saves space, it presents a clearer picture of the programs logic (at least, after youve gotten used to it). Notice that the else if construction is not really a part of the syntax of the C++ language; its merely a way to rewrite an ifelse ladder by rearranging the whitespace on the page. You can do this becauseas you know the compiler doesnt care about whitespace.
Fine-Tuning Loops
This is a good place to introduce the break and continue statements, even though they pertain to loops, because they are used most effectively in conjunction with decisions. Also, break is an important feature in the switch statement, which Ill demonstrate next. Usually loops work well with the straightforward syntax I showed in the last session. However, sometimes you need to fudge things a bit to make a loop behave as you want. The break and continue statements provide this added flexibility.
Figure 2-10 Operation of the break statement The break statement is often used to handle unexpected or nonstandard situations that arise within a loop. For example, heres a code fragment that sets the variable isPrime to 1 if an integer n is a prime number or to 0 if n is not a prime number. (A prime number is divisible only by itself and 1.) To tell if n is prime, I use the straightforward approach of trying to divide it by all the numbers up to n-1. If any of them divide evenly (with no remainder), then its not prime. isPrime = 1; // assume n is prime for(j=2; j<n; ++j) // divide by all integers from 2 to n-1 { if(n%j == 0) // if evenly divisible, { isPrime = 0; // n is not a prime break; // no point in looping again C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 66 of 701
} } I want to divide by all the numbers up to n-1, so I use a for loop with appropriate expressions. However, if one of the j values does divide evenly, theres no use remaining in the loop and dividing by the remaining the j values. As soon as the program finds the first number that divides evenly, it should set isPrime to 0 and then immediately exit from the loop. The break statement allows you to exit from the loop at any time.
Figure 2-11 Operation of the continue statement Whereas the break statement causes an exit from a loop, the continue statement causes part of the loop to be short-circuited or bypassed while the loop keeps running. That is, following a continue, control goes back to the top of the loop. Heres an example: do { cout << Enter dividend: ; cin >> dividend; cout << Enter divisor: ; cin >> divisor; if(divisor == 0) // if user error, { cout << Divisor can't be zero\n; continue; // go back to top of loop } cout << Quotient is << dividend / divisor; cout \nDo another (y/n)? ; cin >> ch; } while(ch != 'n'); Division by zero is illegal, so if the user enters 0 for the divisor, control goes back to the top of the loop and the program prompts for a new dividend and divisor so the user can try again. To exit from the loop, the user must answer n to the Do another question.
{ case 33: // if diskSpeed is 33 cout << Long-playing album; break; case 45: // if diskSpeed is 45 cout << Single-selection; break; case 78: // if diskSpeed is 78 cout << Old single-selection; break; default: // if nothing matches cout << Unknown format; } The switch statement consists of the keyword switch followed by a variable name in parentheses. The body of the switch statement, enclosed in braces, follows. Within the body are a number of labels, which are names followed by a colon. In a switch statement, these labels consist of the keyword case followed by a constant and then the colon. When the value of the switch variable is equal to the constant following a particular case, control will go to the statements following this case label. The above section of code prints different messages depending on the value of the diskSpeed variable. If diskSpeed is 33, control jumps to the label case 33. If diskSpeed is 45, control jumps to the label case 45, and so on. If diskSpeed doesnt match any of the cases, control jumps to the default label (or, if there is no default, falls through the bottom of the switch). Figure 2-12 shows the syntax of the switch statement and Figure 2-13 shows its operation.
Page 68 of 701
Page 69 of 701
Figure 2-13 Operation of the switch statement The variable or expression used to determine which label is jumped to (diskSpeed, in this example) must be an integer or a character or must evaluate to an integer or a character. The values following the cases must beor must evaluate tointeger or character constants. That is, you can use variable names or expressions, such as alpha, j+20, and ch+0, as long as alpha, j, and ch already have appropriate values. Once control gets to a label, the statements following the label are executed one after the other from the label onward. In this example, the cout statement will be executed. Then what? If the break werent there, control would continue down to the next cout statement, which is not what you want. Labels dont delimit a section of code, they merely name an entry point. The break causes control to break out of the switch entirely. Heres another example that might be used in the hot dog stand program. It gives the user the choice of three stands for which to record the sale of a hot dog. The user types a digit from 1 to 3, which is then used as the switch variable. cin >> choice; switch(choice) { case '1'; stand1.SoldOneDog(); break; case '2'; stand2.SoldOneDog(); break; case '3'; stand3.SoldOneDog(); break; } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 70 of 701
class HotDogStand // class specification { private: int HotDogsOnHand; // hot dogs on hand int BunsOnHand; // buns on hand public: void displayData() // display hot dogs and buns { cout << \n Hot dogs = << HotDogsOnHand; cout << \n Buns = << BunsOnHand; } void SoldOneDog() // record sale of one dog { --HotDogsOnHand; --BunsOnHand; } void initData() // set initial quantities { cout << \n Enter hot dogs on hand: ; cin >> HotDogsOnHand; cout << Enter buns on hand: ; cin >> BunsOnHand; } }; // end of HotDogStand class ///////////////////////////////////////////////////////////// void main() { char choice = 'x'; // user's letter choice HotDogStand stand1; HotDogStand stand2; HotDogStand stand3; // create hot dog stand objects
// set initial data cout << \nInitialize data for stand 1; stand1.initData(); cout << \nInitialize data for stand 2; stand2.initData(); cout << \nInitialize data for stand 3; stand3.initData(); while(choice != 'q') // loop until user enters 'q' { cout << \nEnter stand number, or q to quit: ; cin >> choice; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 71 of 701
switch(choice) { case '1': cout << Selling a dog at stand 1; stand1.SoldOneDog(); break; case '2': cout << Selling a dog at stand 2; stand2.SoldOneDog(); break; case '3': cout << Selling a dog at stand 3; stand3.SoldOneDog(); break; } // end switch } // end while // display current data cout << \nSupplies on hand at stand 1; stand1.displayData(); cout << \nSupplies on hand at stand 2; stand2.displayData(); cout << \nSupplies on hand at stand 3; stand3.displayData(); } The user can enter initial amounts of hot dogs and buns for three different stands, then record an arbitrary number of sales at each stand, and then, at the end of the day, display the remaining inventory before exiting from the program. Heres some sample interaction: Initialize data for stand 1 Enter hot dogs on hand: 100 Enter buns on hand: 110 Initialize data for stand 2 Enter hot dogs on hand: 200 Enter buns on hand: 220 Initialize data for stand 3 Enter hot dogs on hand: 300 Enter buns on hand: 330 Enter stand number, or Selling a dog at stand Enter stand number, or Selling a dog at stand Enter stand number, or Selling a dog at stand Enter stand number, or Selling a dog at stand Enter stand number, or Selling a dog at stand Enter stand number, or Selling a dog at stand Enter stand number, or q 1 q 1 q 1 q 2 q 2 q 3 q to quit: 1 to quit: 1 to quit: 1 to quit: 2 to quit: 2 to quit: 3 to quit: q <--sell 1 dog from stand 3 <--quit program <--sell 2 dogs from stand2 <--sell 3 dogs from stand 1
Supplies on hand at stand 1 Hot dogs = 97 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
Page 72 of 701
Buns = 107 Supplies on hand at stand 2 Hot dogs = 198 Buns = 218 Supplies on hand at stand 3 Hot dogs = 299 Buns = 329 In this example, the user enters initial amounts for the three stands and sells three hot dogs from stand 1, two from stand 2, and one from stand 3. Entering q displays the current inventory and exits the program. Again, notice that Ive altered the functionality of the program without changing the class specification.
Page 73 of 701
Figure 2-15 Operation of the conditional operator Heres another example. The statement absvalue = (n<0) ? -n : n; imitates an absolute value function. (The absolute value of a number is simply the number with any negative sign removed.) The result is -n if n is less than 0 and +n otherwise.
Quiz 4 1. Suppose you want to display Weekday if a variable day is between 1 (Monday) and 5 (Friday), but you want to display Saturday if day is 6 and Sunday if day is 7. Which of the following fragments might reasonably be part of the code for this task? Assume day is always in the range of 1 to 7. a. if else(day==7) cout << Sunday; b. else if(day==6) cout << Saturday; else cout << Sunday; c. else if(day<6) cout << Weekday; else if(day==6); d. else cout << Weekday; e. if(day<6) cout << Weekday else cout << Saturday; 2. Which of the following are true? a. break brings you back to the top of a loop, whereas continue continues on from the same point within the loop. b. continue brings you back to the top of the loop, whereas break takes you out of the bottom of the loop. c. break takes you out of all the loops in which you may be nested
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 74 of 701
d. continue takes you immediately to the top of the innermost loop in which youre nested e. break takes you immediately out of an if statement body 3. What will be displayed if the following code is executed when var has the value b?
switch(var) { case 'a': cout << Alpha ; case 'b': cout << Beta ; case 'c': cout << Gamma ; break; default: cout << Not on list; }
a. Alpha b. Beta c. Gamma d. Alpha Beta e. Beta Gamma 4. If you want to display Too slow if speed is less than 40, Too fast if speed is greater than 65, and nothing otherwise, it would be appropriate to use a. a switch statement. b. a series of if statements. c. nested ifelse statements. d. an else if ladder. e. a conditional operator. 5. You want to send a message to each object in a group of objects. You already know the names of all the objects. The message would retrieve a certain item of instance data from each object. Your program would then display Found one! if this data item in any object was divisible by 7. You would be likely to use a. a switch in a do loop. b. nested ifelse statements in a while loop. c. an else if ladder in a do loop. d. an if in a for loop.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 75 of 701
Exercise 2
Rewrite the program of Exercise 2 in Session 3 in this chapter (which uses the hotdogstand2 class with a CashOnHand variable) so that it has the same functionality as the HOTDOG3 program in this session and uses a switch statement to distinguish among the users choices.
Midchapter Discussion
Estelle: George: So George, what terrible negative thoughts do you have today? No negative thoughts. Im on top of the whole thing. I see how a C++ program fits together. Its like there are two halves: the class specification and main(). In the specification, you define what the objects are and how theyll act. In main(), you make objects based on the class and interact with them. Well said. I think Im beginning to see something else, too. Theres a philosophical difference between a class specification and the code in main(). The class is more general. You can imagine one programmer writing a class specification, and then a whole bunch of different programmers using the class for their own special purposes, each one writing a different main(). Its like someone designing a tool and other people using it in different ways. Anyone with a chain of hot dog stands is a potential customer for the HotDogStand class, but they might want to write their own main(). Exactly. Im also relaxed because all the material on if and while and for isnt exactly news to an old C programmer such as myself. Lucky you. Actually, I didnt think it was too hard either, except for the conditional operator, which has too much going on in too small a space. Itll take me all day to figure out a line of code if its got one of those things in it. You dont need to use a conditional operator if you dont want to; just use ifelse instead. What I thought was silly is how in a switch statement you need to put break statements at the end of every case. Why not have control jump to the end of the switch automatically after a case is completed? Its a trade-off. The advantage of requiring a case is that, if you want, two cases can go to the same code. When would you ever do that? Hmmoh, I know. Suppose you wanted your program to do the same thing if the user typed either an upper- or lowercase version of the same character. Then you could put two cases right after each other. Here, Ill write it on the board:
Don:
George: Don:
case 'a': case 'A': // do something break; Don: I guess thats reasonable. But you know what I think is weird? The continue statement. George: Dont worry about it. It doesnt come up that much in C programming, so I bet it doesnt in C++ either.
particular, it will represent the time used for airline flight reservation systems. This time has two data items: one for hours and one for minutes. There is (regrettably) no need for seconds in airline scheduling. Ill call this new data type airtime.
Dates, which have separate values for year, month, and day Fractions, which have separate values for the numerator and denominator Points on the Cartesian plane, which have separate x and y coordinates Complex numbers in mathematics, which have a real and an imaginary component
It will take several iterations before youre ready to use the + operator to add two values of a user-defined data type. Along the way, youll learn to add such values using a member function, add(). But first, lets create a time class with no capability to add values and see what that looks like.
// 0 to 23 // 0 to 59 Page 77 of 701
// for colon
cout << Enter time (format 23:59): ; cin >> hours >> dummy >> minutes; } void display() { cout << hours << ':' << minutes; } }; void main() { airtime t1, t2; // create two airtime variables cout << For t1, ; t1.set(); // set t1 cout << For t2, ; t2.set(); // set t2 cout << \nt1 = ; t1.display(); // display t1 cout << \nt2 = ; t2.display(); // display t2 } The airtime class specifies two items of instance data, hours and minutes, and two member functions, set() and display(), which get an airtime value from the user and display a value. Heres some sample interaction with the program: For t1, Enter time (format 23:59): 10:15 For t2, Enter time (format 23:59): 23:30 t1 = 10:15 t2 = 23:30 The user enters two times and the program displays them. Considering that it creates a data type instead of a group of hot dog stands, this program is surprisingly similar to the HOTDOG1 program. But when you think about it, time quantities and hot dog stands share common characteristics. They both store data. Also, they should have similar capabilities: they should specify their initial data, modify it, and display it.
t1.set(); t2 = t1;
cout << \nt2 = ; t2.display(); // display t2 } How can you set one object equal to another? The capability is built into C++. Although an airtime value contains two data items (hours and minutes), the compiler has no trouble transferring these two values from one variable to another. No matter how many items of instance data there are in an object, they will all be copied to another object during assignment. This may seem natural enough, but its a pretty slick capability.
functions, so all objects share the class member functions, as shown in Figure 2-16. Figure 2-16 Objects, data, functions, and memory When you call a member function for a certain object, as in C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 79 of 701
t2.display(); // display t2 you always call the same display() function, but it operates on different data, depending on what object called it. In this case, display() operates on the data stored in t2.
Quiz 5 1. Data types are to variables as a. classes are to member functions. b. variables are to values. c. data is to values. d. variables are to member functions.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 80 of 701
e. classes are to objects. 2. The following are advantages of representing multivalued quantities (such as time or coordinates) by class objects rather than using separate variables. a. Arithmetic is faster on objects than on separate variables. b. Its easier to declare an object than several variables. c. Its easier to write a statement to perform an arithmetic operation on an object than on separate quantities. d. Its easier to write assignment statements for objects than for separate quantities. e. Objects take up less space in memory than using separate variables for each quantity. 3. If you wanted to line up three integer values (which would never exceed 32,767) in three columns, you might say a. cout << endl << setw(4) << alpha1 <<< alpha2 << alpha3; b. cout << endl << setw(7) << alpha1 << alpha2 << alpha3; c. cout << endl << setw(7) << alpha1
<< setw(7) << alpha2 << setw(7) << alpha3;
4. If you developed a class called AirCraftPosition, appropriate instance data might be a. latitude b. longitude c. speed d. altitude e. capacity 5. When you execute
alpha = beta;
a. the compiler checks that their instance data has the same values. b. betas data and member functions are copied to alpha. c. betas data but not its member functions are copied into alpha. d. the compiler sets beta equal to alpha. e. the compiler doesnt care if alpha and beta are of the same class. Exercise 1
Rewrite the specification for the airtime class in the TIME1 program so it includes a seconds data member. The new class should work with the same main() that the old class did.
Exercise 2
Add a member function to the original airtime class that will advance the time by 1 minute. Handle the case where the minutes value is 59 and adding a minute will cause the hours value to be incremented. If the hours value becomes 24, set it back to 00.
Page 82 of 701
Figure 2-17 Syntax of a function call with arguments Heres the definition of the addhours() member function of the airtime class: void addhours(int h) { hours = hours + h; // add hours if(hours > 23) // if carry to next day, hours = hours - 24; // subtract a day } The int in void addhours(int h) specifies the type of data expected as an argument and the h is the name of the argument. This argument h is a variable that can be accessed anywhere in the function. However, note that h is visible (has meaning) only within the function; it is unknown to other parts of the program. The addhours() function adds h to the hours value of its object. It then checks to see if the resulting hours value exceeds 23; if so, it subtracts 24. (Dont worry about the date.)
class airtime { private: int hours; int minutes; public: void set() { char dummy;
// 0 to 23 // 0 to 59
cout << Enter time (format 23:59): ; cin >> hours >> dummy >> minutes; } void display() { cout << hours << ':' << setfill('0') << setw(2) << minutes; } void addhours(int h) { hours = hours + h; // add hours if(hours > 23) // if carry to next day, hours = hours - 24; // subtract a day } }; void main() { airtime t1; int diffhours; char choice; do { cout << For t1, ; t1.set();
// set t1
cout << Enter hours to add: ; cin >> diffhours; t1.addhours(diffhours); // add hours to t1 cout << t1 = ; t1.display(); // display t1 cout << \nDo another (y/n)? ; cin >> choice; } while(choice != 'n'); } The user enters an airtime value and an hours value, and the program adds them. This continues until the user enters n to quit the loop. For t1, Enter time (format 23:59): 10:45 Enter hours to add: 3 t1 = 13:45 Do another (y/n)? y For t1, Enter time (format 23:59): 23:30 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
Page 84 of 701
Enter hours to add: 5 t1 = 4:30 Do another (y/n)? n Lets look at some other examples where function arguments are helpful.
23 59 // set airtime value // (values supplied by arguments) m; // display airtime value << minutes;
cout << For t1, enter time (format 23:59): ; cin >> hhs >> dummy >> mms; t1.set(hhs, mms); // set t1 values cout << For t2, enter time (format 23:59): ; cin >> hhs >> dummy >> mms; t2.set(hhs, mms); // set t2 values cout << \nt1 = ; t1.display(); // display t1 cout << \nt2 = ; t2.display(); // display t2 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 85 of 701
} The set() member function now takes two arguments: h and m. The body of the function simply sets the hours and minutes instance data items to the values of these arguments. void set(int h, int m) // (values supplied by arguments) { hours = h; minutes = m; // set airtime value } Notice the breakdown of effort between the two parts of this program, the class specification and main(). Compared with the TIME1 program, the set() function is simpler, but main() has become more complicated. Actually, in this particular case, this is probably a mistake. The idea in OOP is that classes will be created once but will be used many times in many different programs. Thus it makes sense to embed as much functionality as possible within the class so that every program that uses the class can be simpler.
Passing by Value
The method of passing arguments that I have shown here is called passing by value. This means that the function creates an entirely new variable (a place in memory with a name and a suitable size) to hold the value of each argument passed to it. In the skeleton example above, there are three variables in main(), iarg, farg, and carg. When the function is called, it creates three new variables, ivar, fvar, and cvar, and copies the values into them, as shown in Figure 2-18. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 86 of 701
Figure 2-18 Passing by value The function may modify the variables it has created, but doing so has no effect on the variables in main(). This provides a built-in protection mechanism: main() can use variables as arguments without worrying that they might be modified by the function it supplies them to. Sometimes you want a function to be able to modify the variables passed to it. When this is the case, you can use a different approach: passing by reference. With this mechanism, the function operates on the original variables in main(). This approach is used less often, but sometimes its essential, as youll see later.
Quiz 6 1. Function arguments are generally used to a. download responsibilities from a class to main(). b. create new variables that the function can use however it likes. c. make a copy of data in case of harm to the original. d. pass information to the function. e. make functions more versatile. 2. If var1 is 11 and var2 is 13, the statement obj1.dotask(var1, var2); a. assigns the values 11 and 13 to the variables var1 and var2 in the dotask() function. b. transmits 11 and 13 to the dotask() member function of obj1. c. writes 11 and 13 into the instance data of obj1.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 87 of 701
d. gets new values of var1 and var2 from obj1. e. assigns 11 and 13 to some other variables in the dotask() function of obj1. 3. Which of the following might be a reasonable way to tell an object of the ship class to locate itself at +120 degrees longitude and -17 degrees latitude? (In case you wondered, west longitude is plus and east is minus, north latitude is plus and south is minus). a. RoyalStar.setPosition(120, -17); b. RoyalStar.setLongitude(120);
RoyalStar.setLatitude(-17);
e. RoyalStar.setLongitude120();
RoyalStar.setLatitude-17();
a. are optional, because the function already knows the values and types being sent. b. specify the data type of the expected values, so the function can check that the correct types are passed. c. convert the values passed to the function to type int and type float, respectively. d. tell the function the types of two variables in main() so the function can access their values. e. create new variables of type int and float, called ivar and fvar. 5. When arguments are passed from main() by value, the called function a. cannot modify the variables whose names are supplied by main(). b. makes copies of the variables supplied by main() to hold the values passed. c. can modify the variables whose names are supplied by main(). d. cannot access the values passed to it. e. refers to the same variables as main() but uses different names.
Page 88 of 701
Exercise 1
Rewrite the HOTDOG1 program so that the initData() member function takes two arguments, one for the number of hot dogs and one for the number of buns. To keep the same functionality, rewrite main() so it obtains this information from the user.
Exercise 2
Write a member function for the airtime class that will change the seconds data by an integer amount supplied as an argument to the function. Youll need to handle the cases where seconds overflow past 60 and hours overflow past 23. Write a main() program to test this new member function.
// 0 to 59 // 0 t0 23
// for colon
cout << Enter time (format 23:59): ; cin >> hours >> dummy >> minutes; } void display() { cout << hours << ':' << setfill('0') C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 89 of 701
<< setw(2) << minutes; } void add(airtime at1, airtime at2) { minutes = at1.minutes + at2.minutes; hours = at1.hours + at2.hours; if(minutes > 59) { minutes = minutes - 60; hours = hours + 1; } if(hours > 23) hours = hours - 24; } }; void main() { airtime t1, t2, t3; char choice; do { cout << For t1, ; t1.set(); cout << For t2, ; t2.set(); t3.add(t1, t2);
// add minutes // add hours // if carry, // adjust minutes // and hours // if carry, // adjust hours
cout << t3 = ; t3.display(); // display t3 cout << \nDo another (y/n)? ; cin >> choice; } while(choice != 'n'); } Figure 2-19 shows how the function call t3.add(t1, t2) interacts with the the add() function.
Page 90 of 701
Figure 2-19 Operation of function call with arguments Its important to notice that you can use function arguments of types (classes) youve defined yourself (airtime in this case) just as you can with basic types such as int. Here the add() member function takes two values of type airtime as arguments: void add(airtime at1, airtime at2) This is a powerful capability. Variables of a user-defined type can be treated almost exactly the same way as basic types in every aspect of C++ programming. The add() function adds the minutes for the two airtime values, then adds the hours. If the sum of the minutes exceeds 59, then 1 hour is added to hours and 60 is subtracted from minutes. If the sum of the hours exceeds 23, then 24 is subtracted from hours. (Dont worry that the resulting airtime is the next day.) If using functions such as add() to carry out arithmetic seems crude, rest assured that you will eventually learn how to perform arithmetic operations by overloading the arithmetic operators such as + and *. This will make for more natural-looking arithmetic expressions, like t3 = t1 + t2; instead of t3.add(t1, t2);
These values are copied in the functions at1 and at2 objects. Statements within the function can then access individual data items using the names at1.minutes, and so on. This is done in the statements minutes = at1.minutes + at2.minutes; // add minutes hours = at1.hours + at2.hours; // add hours As you may recall, private data items such as at1.minutes cannot be accessed by main() directly. Because they are private, they can be accessed only by member functions of their own class. However, member functions can access the private data, not only of the object for which they are called (t3 in this example) but of any object, provided its from the same class (as t1 and t2 are). An objects data is private to the outside world, but not to other objects of its own class. Figure 2-20 shows how this looks.
Figure 2-20 Accessing other objects Lets try some sample interaction with the TIMEADD program. For t1, Enter time (format 23:59): 10:10 For t2, Enter time (format 23:59): 10:50 t3 = 21:00 Do another (y/n)? y For t1, Enter time (format 23:59): 23:59 For t2, Enter time (format 23:59): 0:01 t3 = 0:00 Do another (y/n)? n
// 0 to 59 // 0 to 23
// for colon
cout << Enter time (format 23:59): ; cin >> hours >> dummy >> minutes; } void display() { cout << hours << ':' << setfill('0') << setw(2) << minutes; } // convert minutes to airtime void MinsToAirtime(int imins) { hours = imins / 60; minutes = imins - hours*60; } }; void main() { airtime t1; int iminutes; // minutes (can be > 59) char choice; do { cout << Enter minutes (for example, 241): ; cin >> iminutes; t1.MinsToAirtime(iminutes); cout << t1 = ; t1.display(); // display t1 cout << \nDo another (y/n)? ; cin >> choice; } while(choice != 'n'); } In the class specification for airtime, I define the member function MinsToAirtime(), which converts an int minutes value to an airtime value. To do this, the function first divides the minutes by 60 to get the hours. The fractional part of this result (representing minutes) is chopped off when the number is converted to type int because integers dont have a fractional part. The resulting hours value becomes the hours instance data in the object that called MinsToAirtime(), which, as can be seen in main(), is t1 in this example. To obtain minutes, the function subtracts the number of minutes in the hours variable (hours*60) from the original number of minutes and assigns this to the instance variable minutes. In main(), I ask the user for a minutes value and then convert it to airtime by calling MinsToAirtime() for the t1 object. The resulting value of t1 is then displayed in the usual way with the display() member function, just to prove that the conversion works.
Nonautomatic Conversions
Conversions, such as the one from minutes to airtime, play an important role in C++ programs. Remember that C++ automatically converts basic types from one to another. For example, you can say C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 93 of 701
int ivar = 27; float fvar; fvar = ivar; and the C++ compiler will arrange for the integer value 27 to be converted to the equivalent floating-point value, 27.0, for storage in fvar. Such conversions are completely automatic. The conversion routines are built into the compiler, and it knows how to use them. However, when you convert between basic types and types youve specified yourself, such as airtime, there are no routines built into the compiler to handle the conversion. (After all, it doesnt know what type you might invent.) You must write the routines yourself, as Ive done here with MinsToAirtime(). Ill be returning to the subject of conversions.
// 0 to 59 // 0 t0 23
// for colon
cout << Enter time (format 23:59): ; cin >> hours >> dummy >> minutes; } void display() { cout << hours << ':' << setfill('0') << setw(2) << minutes; } // multiply airtime by float void mult(airtime at1, int mplier) { // convert to minutes int im = at1.hours * 60 + at1.minutes; int ianswer = im * mplier; // do the multiply hours = ianswer / 60; // convert back to minutes = ianswer - hours*60; // hours and minutes } }; void main() C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 94 of 701
{ cout << For t1, ; // t1.set(); // cout << Enter multiplier: cin >> m; // t2.mult(t1, m);
// multiply t1 by m, result in t2
cout << t2 = ; t2.display(); // display t2 cout << \nDo another (y/n)? ; cin >> choice; } while(choice != 'n'); } The user enters an airtime value and an integer to be used as a multiplier. Heres some sample interaction. As you can see, servicing the 23 engines will take 28 hours and 45 minutes. For t1, Enter time (format 23:29): 1:01 Enter multiplier: 3 t2 = 3:03 Do another (y/n)? y For t1, Enter time (format 23:29): 1:15 Enter multiplier: 23 t2 = 28:45 Do another (y/n)? n The mult() member function takes two arguments, one of type airtime (which is t1) and one of type int (called m, for multiplier). It multiplies these together and stores the resulting airtime value in the object that called it (t2). This function first converts the t1 airtime value to an integer, im, representing minutes. It then multiplies this value by the multiplier mplier and saves the answer in ianswer. To convert this all-minutes value back to an hours-and-minutes airtime value, it uses the same approach shown in the TIMECONV1 program. As you can see, two different data type conversions are actually carried out in this one function: airtime to minutes and minutes to airtime. Notice that in both the sub() and the mult() functions, the answer is stored in the airtime object that called the function. The answer to a calculation can be placed in other objects as well, as youll see in the next lesson, on function return values.
The MinsToAirtime() function, because it is being called from a member function, is smart enough to act on the same object as the function that called it. That is, it will operate on the hours and minutes data in t3. It doesnt need to be called with an object name and the dot operator, as member functions do in main(). Of course, the MinsToAirtime() function would also need to appear in the class specification. // convert minutes to airtime void MinsToAirtime(int imins) { hours = imins / 60; minutes = imins - hours*60; }
Quiz 7 1. Performing arithmetic on user-defined types is useful a. when the user-defined type represents something that acts like a number. b. for the same reason its useful to perform arithmetic on basic types such as int and float. c. because otherwise its impossible to access an objects private data. d. in situations where the data in an object is numerical. e. any time two objects have similar data. 2. In the statement t3.add(t1, t2); found in the TIMEADD program in this lesson,
t1
a. the function add() could, if it were rewritten in the class specification, add the values of and t3 and place the result in t2, without changing the format of the call. b. the arguments t1 and t2 are variables of a basic C++ type.
c. the values in t1 and t2 are added and the result is placed in t3. d. the function add() is a member function of the t3 object. e. the add() function can access private data in three objects. 3. We use a member function of class A to perform arithmetic on objects of class A because a. the compiler wont let you call a function of class A from an object of any other class. b. only member functions of class A can access the private data of class A objects. c. only member functions of class A can access public parts of class A objects. d. functions in class A cannot access public data in class A. e. functions in other classes cannot access the private data of objects of class A. 4. A member function that converts a value from a basic data type to a user-defined data type a. is impossible because a user-defined type is not a number.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 96 of 701
b. can place the new value in the object that called the conversion function. c. can get the old value from the object that called the conversion function. d. can take the user-defined type as a pass-by-value argument. e. can take the basic type as a pass-by-value argument. 5. The statement t3.mult(t1, t2); in which the variables are all of class airtime (as defined in the TIMEMULT program in this lesson) a. will apparently cause t1 to be multiplied by t2. b. implies that the mult() function can modify data only in t1, t2, or t3. c. implies that the mult() function can modify data only in t1 or t2. d. implies that the mult() function can modify data only in t3. e. doesnt make sense, because multiplying two airtime values has no meaning. Exercise 1
Write a sub() member function for the airtime class that subtracts one airtime value from another. Write a main() that allows the user to test this function. Assume that the smaller (earlier) time value will always be subtracted from the larger so that negative values will not arise.
Exercise 2
Write a minutes-to-airtime conversion member function for the airtime class. You can call it MinsToAirtime(). This function should take an all-minutes time value, which can have values such as 65 and 241, as its only argument. The function should convert this minutes value to an airtime value in hours and minutes and store these values in the object that called it. Write a main() that tests this function by asking the user for a minutes value, and then convert this quantity into an airtime and display it.
#include <iostream.h> #include <iomanip.h> class airtime { private: int minutes; int hours; public: void set() { char dummy;
// 0 to 59 // 0 to 23
// for colon
cout << Enter time (format 23:59): ; cin >> hours >> dummy >> minutes; } void display() { cout << hours << ':' << setfill('0') << setw(2) << minutes; } int AirtimeToMins() // convert airtime to minutes { int imins = hours*60 + minutes; return imins; } }; void main() { airtime t1; int iminutes; char choice; do { cout << For t1, ; t1.set(); // get airtime value from user
//
// convert airtime to minutes iminutes = t1.AirtimeToMins(); cout << Minutes = // display minutes << iminutes; cout << \nDo another (y/n)? ; cin >> choice; } while(choice != 'n'); } In main(), I obtain an airtime value from the user and store it in t1. Then I call AirtimeToMins() with t1 as an argument. The return value from this function is the equivalent time in minutes, which is then displayed. Notice how I treat the entire expression t1.AirtimeToMins() as if it were a value, assigning it to the variable iminutes.
a function to return a value, it must use a return statement. The keyword return can be followed by an expression that evaluates to the value to be returned. In AirtimeToMins(), I say return imins; which causes the minutes value, stored in imins, to be returned, as shown in Figure 2-21.
Figure 2-21 Operation of the return statement A return statement causes control to jump immediately out of the function and to return to the code that called it. The expression following return must be the same type as the type of the function. This expression is optional. If its not included, then the function must be of type void, meaning it does not return a value. In void functions, a return statement is not necessary (as youve already seen in numerous examples), provided you want the function to end at its last statement. Using return gives you the option of exiting from the function anywhere.
int imins = hours*60 + minutes; Defining a variable and calculating it in the same statement is not a common idiom in C, but its used frequently in C++. When a function returns to the program that called it, any automatic variables created within the function, such as imins, are destroyed. (Thats why theyre called automatic; theyre created automatically when the function is called and destroyed automatically when the function returns.) In this case, the value of imins is returned just in timeas soon as the return statement has finished executing, the function returns and imins is destroyed.
The Stack
Automatic variables are stored in a part of computer memory called the stack. The stack grows and shrinks as functions are called and returned. It has a maximum size, usually several thousand bytes, so you cant use huge amounts of automatic data (unless you make special arrangements to enlarge the stack). When an automatic variable is first created, it has a random garbage value, probably not zero. This is because the stack has just expanded into an (often) previously occupied part of memory. The moral is: Dont trust that an automatic variable will have an initial value of 0; be sure to initialize it before you use it.
class airtime { private: int minutes; // 0 to 59 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
// 0 t0 23
// for colon
cout << Enter time (format 23:59): ; cin >> hours >> dummy >> minutes; } void display() { cout << hours << ':' << setfill('0') << setw(2) << minutes; } airtime add(airtime at2) { airtime temp; temp.minutes = minutes + at2.minutes; // add minutes temp.hours = hours + at2.hours; // add hours if(temp.minutes > 59) // if carry, { temp.minutes = temp.minutes - 60; // adjust minutes temp.hours = temp.hours + 1; // and hours } if(temp.hours > 23) // if carry, temp.hours = temp.hours - 24; // adjust hours return temp; } }; void main() { airtime t1, t2, t3; char choice; do { cout << For t1, ; t1.set(); cout << For t2, ; t2.set(); t3 = t1.add(t2);
cout << t3 = ; t3.display(); // display t3 cout << \nDo another (y/n)? ; cin >> choice; } while(choice != 'n'); } In the add() function, the program creates an object, called temp, of class airtime. This is an automatic variable and it will be destroyed when the function returns, just as any automatic variable is. Remember, the compiler treats all variables the same, whether they are of a built-in type or a type defined in the program. You might wonder if theres a way to generate nameless temporary objects. There is, but to use them youll need to learn about constructors, a topic Ill get to in Chapter 5. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 101 of 701
Returning by Value
I should note that the return statements I examine in this session return by value. The actual value is passed back to the code that called the function. In other words, there will beat least brieflytwo variables holding the same value: one in the function and one in the code that called the function. Another mechanism can be used to handle return values: returning by reference. In this case, the code that calls the function receives only a reference to the original variable in the function. However, youll need to learn about constructors before I talk about returning by reference.
Library Functions
Now that you know about function arguments and return values, I can mention the existence of C-style library functions. These functions were developed for the C language, but can also be used in C++. Many of these functions are not as useful in C++ as they are in C (such as I/O functions, assuming you use the C++ stream classes). However, C library functions are still an essential aspect of C++ programming. There are library functions for input/output, data conversions, string handling, directory and file control, memory allocation, math, process control, and so on. Youll encounter many of these functions. As an example, lets look at the sqrt() function, which returns the square root of a floating-point number. This skeleton code shows the essentials. #include <math.h> // needed for sqrt() double answer, somenum; // sqrt operates on type double answer = sqrt(somenum); // find the square root To use a library function, youll need to look it up either in your compilers online help or in the manual. The first thing youll need to know is the name of the appropriate header file. Every library function requires that a header file be included before the function is called; here its MATH.H. Most of the math-related functions use this same header file. The documentation will also tell you the data types of the functions arguments and return value. The sqrt() function takes a single argument of type double and returns the same type, so in this example, both somenum and answer are type double. Other examples of library functions will pop up in future lessons.
Quiz 8 1. In the TIMECNV2 program, converting a value from airtime to minutes involves using a function that a. returns a minutes value. b. accesses a minutes value provided as an argument. c. accesses the private data of the airtime object that called it. d. accesses the private data of the airtime variable provided as an argument. e. cannot exist, because airtime values are not numbers. 2. Depending on how its written, a return statement may cause a. control to return from a function when the closing brace is reached. b. control to return from a function immediately. c. control to return from a function once the return value is calculated.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 102 of 701
d. a function definition to assume a value. e. a function call to assume a value. 3. An automatic variable is created when which two events have occurred? a. The program loads. b. The program executes. c. The variable is defined. d. The variable is assigned a value. e. Control is transferred to the function in which the variable is defined. 4. Returning the answer from a function that adds two user-defined values is superior to putting the answer in the object that called the function because a. the object that called the function is destroyed when the function returns. b. you can use the equal sign as its used in arithmetic. c. one of the values to be added is the object that called the function. d. one of the values to be added is an argument to the function. e. its awkward to use an argument to send the answer back to the code that called the function. 5. Returning from a function by value means that a. a nameless temporary object (or variable) is always created in the function. b. a function call can be assigned to a variable. c. a reference to a variable defined in a function is returned. d. a reference to a variable defined outside a function is returned. e. the function must be of type void. Exercise 1
Write statements to create variables called LetterCode, SalePrice, and Quantity of types char double and long.
Exercise 2
Write statements to set the three variables in Exercise 1 to V, 67.95 and 1,000,000, respectively.
Summary: Chapter 2
In this chapter, youve seen two quite different kinds of complete, working C++ programs: one in which a class represents hot dog stand inventory data and another in which a class models a data type that represents time. Because time is a numerical entity, you can treat a class that models it as a new, user-defined C++ data type. I also focused on some of the nuts and bolts of C++ programming: loops (while, do, and for), decisions (if, ifelse, the else if construction, switch, and the conditional operator), and function arguments and return values. I used various kinds of loops and decisions in programs that further extend the capability of the hot dog stand and time programs. A major focus was expanding the kinds of member functions you can write. You saw how to write member functions that perform arithmetic on objects and that convert from one data type to another. Finally, I discussed several more subtle features of C++. You discovered that you can assign the value of one object to another the same way you can with basic C++ types. You also learned that, although it may be convenient to think of each object as containing both data and member functions, the member functions are actually shared by all objects of a class. The function examples youve seen so far pass arguments by value; that is, values are copied from the calling program to variables in the function. The functions also return by value, which means that a copy of a variable in the function is returned to the calling program. You learned that member functions can access the object for which they are called. They can also access objects sent to them as arguments, provided these objects are of the same class as the member function. Automatic variables or objects are created automatically when a function is called and are destroyed when the function returns. Automatic variables are stored on the stack. Some automatic variables have no name and are created temporarily to store the results of evaluating expressions. C-style library functions are available to carry out many tasks. They are not object oriented but they are very helpful in some situations, such as mathematics operations.
End-Of-Chapter Discussion
George: I was just getting used to the idea that objects represent things in the real world, and now it turns out they can represent data types. Anyway, doing arithmetic on objects is too weird. No one would do addition using a function! Poor George. I know you hate this answer, but again I think the payoff is going to come later. Right. It doesnt buy you much to add two airtime values with a function such as add(), but itll be a lot more interesting to say t3=t1+t2, just like in ordinary arithmetic. Yeah, that may be cute, but what good is it? I can calculate time values in C just by using separate variables for hours and minutes. I dont see that this airtime class is making my life any easier. These sample programs hes showing us are more complicated than a C program would need to be. Thats because youre looking at both parts of the program: the class specification and the main() function. But suppose you bought a whole airtime class library. Then the class specification and all the member functions would already be written and you wouldnt need to put them in your listing, you could just use an #include. You probably wouldnt even need to look at the source files for the class. Youd just need to create objects and send them messages. You mean define weird things and call their member functions. Whichever way you like to say it. Youd need a description of how the member functions worked, like what arguments and return values to use for add() and display() or whatever. Right, so youd know how to use them. But looking up a short description of a function is a lot easier than trying to figure it out from the source code. Usually. And if you look at the listings of the programs weve seen so far, about half the lines of code are the class specification. If all you had to worry about was main(), things would be pretty easy. Easy as chopping wood with a broom, as my granny used to say. Come on, George. Get with the program
Estelle:
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.
Figure 3-1 Syntax of 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).
cout << Enter the value of element << j << : ; cin >> age[j]; } Interaction with this fragment might be Enter the value of element Enter the value of element Enter the value of element Enter the value of element
0: 1: 2: 3:
44 16 23 68
definition, the values are separated by commas, and the list is delimited by braces, as shown in Figure 3-3. Figure 3-3 Syntax of array initialization You could display these values with a for loop: for(int j=0; j<6; ++j) cout << coins[j] << ; which would produce the output 1 5 10 25 50 100 Heres another example: int days_per_month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; A surprising feature of array initialization is that you dont need to count how many items are being initialized unless you want to. The definition int coins[] = { 1, 5, 10, 25, 50, 100 }; works just as well as the earlier version, despite not specifying the array size. The compiler cleverly counts how many values there are and uses this for the array size. What happens if you specify an array size but it disagrees with how many initialization values are actually on the list? If there are more values than the array size specified, the compiler complains. If there are fewer values in the list than the array size, the compiler will simply fill out the balance of the array with 0s. Thus if you want to initialize an arrayof any sizewith all 0s, you need only say int anarray[10] = { 0 }; // initialize 10 ints to 0 The first element is initialized to 0 explicitly and the remaining 9 are initialized to 0 because no value is given for them. If they are not initialized, the elements of arrays declared inside a function will have random (garbage) values. Arrays declared outside of a function or classthat is, as external variablesare initialized to zero automatically. Youll learn more about the external storage class in Session 5 in Chapter 4.
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 write [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. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 109 of 701
To initialize a two-dimensional array, make a list of lists float sales[4][3] = { { 1437.07, 234.50, 654.01}, { 322.00, 13838.32, 17589.88}, { 9328.34, 934.00, 4492.30}, {12838.29, 2332.63, 32.93} }; where each subarray is initialized with its own list of numbers separated by commas and delimited with braces; these four lists are in turn separated by commas and delimited with braces. If you initialize an array as shown here and then use the nested for loops to display its contents, the output will be 1437.07, 234.50, 654.01 322.00, 13838.32, 17589.88 9328.34, 934.00, 4492.30 12838.29, 2332.63, 32.93 A three-dimensional array would be defined with the three dimensions in brackets int cube[4][3][5]; // array of 60 ints and its elements would be accessed with expressions such as cube[1][2][3]. Arrays of higher dimensions can be defined and accessed similarly.
Quiz 1 1. To specify an array of 30 variables of type char you would say a. int[30] array_name; b. char array_name[30]; c. array_name char[30]; d. char[30] array_name; e. array_name[30]; 2. To access the last element of an int array alpha of 10 elements, you would say a. int[10] b. int[9] c. int alpha[0] d. alpha[10] e. alpha[9]
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 110 of 701
3. A for loop to access all the elements of an array of size 7 would most likely be written as a. for(k=1; k<10; ++k) b. for(k=1; k<7; ++k) c. for(k=1; k<=10; ++k) d. for(k=0; k<=7; ++k) e. for(k=0; k<7; ++k) 4. To initialize an array to the squares of the first six integers, you could write a. int squares[6] = { 1, 4, 9, 16, 25, 36 }; b. int squares[6] = { [1], [4], [9], [16], [25], [36] }; c. int squares[] = {1; 4; 9; 16; 25; 36}; d. int squares{6} = {1, 4, 9, 16, 25, 36}; e. int squares[] = ( 1, 4, 9, 16, 25, 36 ); 5. In the two-dimensional array defined as int addr[2][20]; that represents the starting and ending locations (in that order) of 20 programs (dont worry about what location means), you would refer to the ending address of the third program as a. addr[2][20] b. addr[1][2] c. addr[2][1] d. addr[2][3] e. addr[3][2]
Since I am concentrating on syntax rather than complete programs in this session, there are no exercises.
// for getche()
name[n] = ch; // store in name array ++n; } while(ch != '\r'); // quit if Enter key cout << \n Enter serial number: ; cin >> serial_number; } void output() // display employee data { cout << Name = ; for(int j=0; j<n; ++j) // display one character cout << name[j]; // at a time cout << \n Serial number = << serial_number; } }; void main() { employee e1, e2; cout << Enter employee 1 data << endl; e1.input(); cout << Enter employee 2 data << endl; e2.input(); cout << \nEmployee 1 << endl; e1.output(); cout << \nEmployee 2 << endl; e2.output(); } I must confess that this source file may elicit warning messages from the compiler, such as Functions containing do are not expanded inline and Functions containing for are not expanded inline. (Your compiler may generate somewhat different messages.) The compiler prefers that I use standalone member functions when the functions contain loops. Youll see what this means when I discuss functions in Chapter 4. In the meantime, dont worry about these messages. The compiler will do the right thing. In main(), the program creates two employee objects, gets the data from the user for each one, and then displays the data. Some sample interaction with the program looks like this: Enter employee 1 data <--User enters data Enter name: George Harrison Enter serial number: 1119 Enter employee 2 data Enter name: Paul McCartney Enter serial number: 2229 Employee 1 Name = George Harrison Serial number = 1119 Employee 2 Name = Paul McCartney Serial number = 2229 <--Program displays data
want: getche(). This function waits until a single key is pressed on the keyboard and then returns with the ASCII value of the key. Theres no need to press to get the character. The getche() function requires that the CONIO.H header file be included. I used this function in a do loop to obtain all the letters of the employee name. do { ch = getche(); // get one char at a time name[n] = ch; // store in name array ++n; // increment the array index } while(ch != '\r'); // quit if Enter key Each time through the loop, the getche() function returns with a new character, which is then assigned to an array element. The index n starts at 0 and is incremented each time through the loop by the ++n; statement. (Ill
The Key
What is the ASCII code for the key? It turns out its 13 (decimal), but you dont really need to know this, because the escape code \r (for carriage Return) represents this code. When the program encounters this code, it exits from the do loop. At this point, it obtains the serial number from the user, a simpler process. Displaying a name is the reverse of storing it. The program goes through the array one character at a time, using a for loop, displaying each one. for(int j=0; j<n; ++j) // display one character cout << name[j]; // at a time The program knows how long the name is because it counted the incoming characters with n, which is included as instance data for the class so it can be accessed by all member functions. (When I discuss real C strings, youll see that you dont need to store a string length.)
Can I actually do this? Well, its perfectly legal syntax as far as the compiler is concerned. However, theres a glitch. The index n starts off at 0, and I want to put the first character in array element 0, so I dont want to increment n until after the contents of ch have been placed in the array. Unfortunately, ++n causes n to be incremented before it is used. The result will be that the first character of the name will go in name[1] instead of in name[0]. Is there a way to increment n after its used? The designers of C and C++ anticipated just this situation and built the necessary capability into the increment and decrement operators. Heres the statement rewritten so it works properly: name[n++] = ch; When the ++ operator follows its operand, its called a postfix operator. When it precedes its operand, as youve seen several times before, its called a prefix operator. The prefix operator is applied before the value of the variable is used, whereas the postfix operator is used after the variable is used. This is summarized as follows. ++n Prefix operator Incremented before being used n++ Postfix operator Incremented after being used Using the postfix operator, I can rewrite the do loop in EMPLOY1 as do { ch = getche(); // get one char at a time name[n++] = ch; // store in name array } while(ch != '\r'); // quit if Enter key This saves a line of code, which is widely believed to make the listing more readable. You will not be surprised to learn that there is a postfix version of the decrement operator (n--) as well as of the increment operator.
Figure 3-6 Pushing and popping from a stack In this example, I use an array, as instance data of the Stack class, to store a number of integers. Listing 3-2 shows STACK1, which specifies the Stack class and then tests it by creating a stack, pushing three integers on it, and then popping them back off and displaying them. Listing 3-2 STACK1 // stack1.cpp // class models a stack #include <iostream.h> class Stack // a stack holds up to 20 ints { private: int st[20]; // integers are stored in array int top; // index of last item pushed public: void init() // initialize index { top = -1; } void push(int var) // place an item on the stack { st[++top] = var; } int pop() // remove an item from the stack { return st[top--]; } }; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 116 of 701
cout << s1.pop() << endl; // pop 3 items and display them cout << s1.pop() << endl; cout << s1.pop() << endl; } When you pop items off a stack, they appear in reversed order. Thus the output from this program is 13 12 Notice that I use both prefix and postfix operators to manipulate the array index. This instance variable, top, represents the top of the stack. (Think of a pointer to the top cafeteria tray.) Its initialized to -1 with the init() member function when main() first starts. When items are pushed, top is incremented first (++top), and only then used as the index, so top always points one place beyond the last item pushed. When items are popped, they are accessed first and then the index is decremented (top--), so top again points just above the top of the stack.
Not a Constructor
Initializing top to -1 using a function like init(), as I do in the STACK1 program, is not the favored approach in OOP. One reason is that the programmer who writes main() must remember to call this init() function every time an object (in this example, the Stack object s1) is created. According to Murphys law, anything that can be forgotten, will eventually be forgotten, so init() is not a good idea. The solution to this is the constructor, which Ill discuss in Chapter 5.
Quiz 2 1. To insert into an array the characters that constitute a name (such as John Smith) entered by the user, you might say a. j=0; while(ch != ) { ch=getche(); charray[++j]=ch; } b. j=1; while( ch != \r ) { ch=getche(); charray[++j]=ch; } c. j=0; while(ch != \r) { ch=getche(); charray[j++]=ch; } d. j=1; while( ch != ) { ch=charray[j++]; getche(ch); } e. j=0; while(ch != 13) { ch=charray[++j]; getche(ch); }
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 117 of 701
2. The employee class in the EMPLOY1 program a. contains an instance data item that holds the names of all the employees. b. stores data for a number of employees in each object. c. includes a member function that displays an employees name and serial number. d. requires the length of the name data item to be initialized to -1. e. requires that main() create an array of employee objects. 3. Data storage structures such as stacks and linked lists a. can be implemented with classes. b. must be implemented with classes. c. should not be implemented with classes. d. exist because an array isnt always the most useful approach to data storage. e. can store data using a different approach than an array. 4. When you push a data item onto a stack in the STACK1 program, you are actually a. creating an object. b. reading data. c. calling an objects member function. d. inserting the data item in array position 0. e. storing the data item in an array. 5. Which of the following arrangements of Stack class member functions would work together properly, without wasting space? a.
void init() void push(int var) int pop() { top = 1; } { st[--top] = var; } { return st[top++]; }
b.
void init() void push(int var) int pop() { top = 0; } { st[++top] = var; } { return st[top--]; }
c.
void init() { top = -1; } void push(int var) { st[++top] = var; } int pop() { return st[top--]; } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
d.
void init() void push(int var) int pop() { top = 0; } { st[top++] = var; } { return st[--top]; }
e.
void init() void push(int var) int pop() { top = -1; } { st[++top] = var; } { return st[--top]; }
Exercise 1
Seven years after an employee leaves the Amalgamated Widgets company, the employee records are purged of all information about that employee. Add a member function called purge() to the employee class of the EMPLOY1 program. It should write over the existing data for an employee so that when displayed, the name will have no characters and the serial number will be 0. Modify main() so it tests this function.
Exercise 2
Sometimes its useful to examine the data item at the top of a stack without actually popping the item off the stack. (That is, after youve read its value, the item remains at the top of the stack.) A member function that does this is traditionally called peek(). Write such a function that works with the Stack class of the STACK1 program. Modify main() to check it out.
and further suppose that in main(), youve defined an array to 10 objects of class X Xerxes xa[10]; // array xa of 10 objects of class Xerxes Now the question is, how do you call the member function afunc() for the object that is in, say, the third element of the array xa? You know you cant say xa.afunc(); // no good; xa is an array, not a single variable because this gives no indication of which element in the xa array is calling the function. You need to mix array notation with the dot operator, like this: xa[2].afunc(); // accesses afunc() for the third object in xa This syntax is straightforward, although it looks a little odd at first. Notice that the subscript operator ([]) has higher precedence than the dot operator, so the expression is interpreted as (xa[2]).afunc() and not as xa([2].afunc()). Lets see how this syntax is used in an actual program.
// 0 to 59 // 0 to 23
// for colon
cout << Enter time (format 23:59): ; cin >> hours >> dummy >> minutes; } void display() { cout << hours << ':' << setfill('0') << setw(2) << minutes; } }; void main() { airtime at[20]; int n=0; char choice; do { // get time from user cout << Airtime << n << . ; at[n++].set(); // and insert in array cout << Do another (y/n)? ; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
cin >> choice; } while(choice != 'n'); for(int j=0; j<n; ++j) // display every airtime { // in the array cout << \nAirtime << j << = ; at[j].display(); } } Some sample interaction with the program looks like this: Airtime 0. Enter time (format 23:59): 1:01 Do another (y/n)? y Airtime 1. Enter time (format 23:59): 23:30 Do another (y/n)? y Airtime 2. Enter time (format 23:59): 10:00 Do another (y/n)? n Airtime 0 = 1:01 Airtime 1 = 23:30 Airtime 2 = 10:00 The important statements in ARRAYAIR are at[n++].set(); // insert data into object in array which calls the set() member function so the user can enter data for the object in the array at index n (and which subsequently increments n) and at[j].display(); // display data from object in array which calls the display() member function to display the data for the object stored at index j.
// for getche()
class employee { private: char name[20]; // name (20 chars max) int n; // length of name int serial_number; public: void input() // get data from user { char ch; n = 0; cout << Enter name: ; do { ch = getche(); // get one char at a time name[n++] = ch; // store in name array } while(ch != '\r'); // quit if Enter key C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 122 of 701
cout << \n Enter serial number: ; cin >> serial_number; } void output() // display employee data { cout << Name = ; for(int j=0; j<n; ++j) // display one character cout << name[j]; // at a time cout << \n Serial number = << serial_number; } }; void main() { employee emps[100]; int n = 0; char choice; do { cout << Enter employee << n << data << endl; emps[n++].input(); // get data cout << Do another (y/n)? ; cin >> choice; } while(choice != 'n'); for(int j=0; j<n; j++) { cout << \nData for employee << j << endl; emps[j].output(); // display data } } There should be no surprises in the operation of this program. The class specification for employee is the same as in earlier examples, and the array of objects is handled as it was in ARRAYAIR. Sample interaction with the program looks like this: Enter employee 0 data Enter name: Henry James Enter serial number: 1119 Do another (y/n)? y Enter employee 1 data Enter name: Nathaniel Hawthorne Enter serial number: 2229 Do another (y/n)? y Enter employee 2 data Enter name: Edgar Allan Poe Enter serial number: 3339 Do another (y/n)? n Data for employee 0 Name = Henry James Serial number = 1119 Data for employee 1 Name = Nathaniel Hawthorne Serial number = 2229 Data for employee 2 Name = Edgar Allan Poe Serial number = 3339 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 123 of 701
One trouble with arrays is that you must declare an array that can hold the largest number of objects that you will ever expect to be placed in it. Most of the time, the array will hold fewer objects, so memory space is wasted. A linked list, or an array of pointers to objects, can reduce this kind of memory extravagance, as youll see later.
Quiz 3 1. The member function f() takes no arguments. To call this function for the fourth element in an array arr of objects of type X, you would say a. f().arr[3]; b. f(X).arr[3]; c. arr.f(X); d. arr[3].f(); e. X arr.f()[3]; 2. Suppose you have a two-dimensional array of airtime values, defined as
airtime aat[50][100];
How would you call the display() member function for the airtime variable located at the 25th element in the 20th subarray? a. aat[19, 24].display(); b. aat[19][24].display(); c. aat[24][19].display(); d. aat.display([24][19]); e. aat[25][20].display(); 3. An array of objects is useful when a. there are many objects of the same class. b. a number of objects of different classes must be stored and accessed. c. a number of objects will all have the same values in their instance data. d. there are many variables of the same basic type to store as class instance data. e. a number of objects all have the same member functions. 4. Assume that n is 5 just before the statement emps[n++].input();, taken from the ARRAYEMP program, is executed. You can conclude that
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 124 of 701
a. the sixth element of the array will be accessed. b. the statement contains a subtle bug. c. no additional statement is necessary to move the index to the next array element. d. no elements of emps will be modified. e. data will be obtained from the user and placed in an object of type employee. 5. In the ARRAYEMP program, assume the user has entered three employees called Henry James, Nathaniel Hawthorne, and Edgar Allan Poe (as in the example). How many bytes of memory are allocated to storing names? a. 45 b. 2,000 c. 48 d. 60 e. 51 Exercise 1
Start with the program HOTDOG3 from Session 4 in Chapter 2. Write a program that uses the HotDogStand class, allows the user to enter data for up to 10 hot dog stands, and then displays the data for all the stands, as in the arrayemp program. Use an array to hold the HotDogStand objects.
Exercise 2
Starting with the program in Exercise 1, modify main() so the user can choose what action to take by entering one of four letters: i to initialize the data at a particular stand, s to record a sale at a particular stand, r to report the current data for all the stands, or q to quit the program. You may want to use a switch statement to select among these choices and a while or do loop to cycle repeatedly through the switch.
Session 4: Strings
You learned in Session 2 in this chapter that textsuch as a namecan be treated strictly as an array of characters. Loops can be used to input and output the text from such arrays, one character at a time. This is all a little awkward. Text is so common in computer programs that having to program a loop every time text must be handled is not acceptable. Fortunately, 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 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 125 of 701
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; << str; // get text from user, store in 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 3-8 shows how the array str looks in memory after the user has entered the string Amanuensis (which means one employed to copy manuscripts).
String Constants
You can initialize a string variable to a value when you define it, just as you can with other variables. Heres one way to initialize a string variable called name to the string George: char name[20] = {'G', 'e', 'o', 'r', 'g', 'e', '\0'}; This is the format used to initialize arrays of other types: a list of values separated by commas. However, typing all those quotes and commas is inefficient to say the least, so the designers of C and C++ took pity on us and acknowledged that strings were slightly more than arrays of type char by allowing this kind of initialization: char name[20] = George; where double (not single) quotes delimit text. A sequence of characters surrounded by double quotes like this is called a string constant. Although you cant see it, a null \0) is included as the last character of this string constant George; its inserted automatically by the compiler. Thus, although there are only six characters in the name George, there are seven characters in the string constant George: six letters and the \0 character. If youre creating a string variable, you must keep this in mind. The array you define must be large enough to hold the largest number of characters you will ever store in it plus one more for the null character. char name[6] = George; // not OK, the '\0' won't fit char name[7] = George; // OK, it just fits A string constant is a value that can be placed in a string variable. You might wonder whether this means you can say name = George; // illegal Alas, this doesnt work. In this situation, C++ thinks of a string variable as an array, and you cant use the equal sign to assign a whole bunch of values to an array, even though the equal sign is used to initialize an array. (Later youll see that you can make the equal sign do anything you want by overloading it.) As with any array, you dont need to specify the number of elements in a string variable if you initialize it when you define it. You can say char somestring[] = Four score and seven years ago; and the compiler will make somestring just the right length to hold the string constant (including the null character). This can save you a lot of counting.
use the same number here that you use for the size of the array. You can then input strings of up to, in this case, 79 characters.
cin.get(str, size); // get string from user cout << str; // display string Now if you change the value of size in the first statement, making size equal to 40, for example, then both the array size and the character limit in cin.get() will be changed automatically, ensuring they remain the same. This is a safer way to program, but I can go further. An array size cannot be modified during the course of a program. This implies that the variable size should be a constant, so that the programmer is prevented from inadvertently modifying it. But how can a variable be a constant? I can mandate this seeming contradiction using the keyword const. This keyword is used in the definition of a variable that cannot change its value during the course of the program. Heres how you might use const: const int SIZE = 80; // array size: cannot be changed char str[SIZE]; // define array
cin.get(str, SIZE); // get string from user cout << str; // display string I use all uppercase letters as a reminder that SIZE is a constant. Now I can change the value of SIZE if I want, but only by rewriting its declaration and recompiling the program. Attempts by other program statements to modify it will elicit a compiler error. Because it cannot be changed, a const variable must be initialized to a value when its first defined. Old-time C programmers may remember when a preprocessor directive, #define, was used to create constants, as in #define SIZE 80 This construction can also be used in C++, but it has fallen out of favor because no data type can be applied to the name, which means the compiler cant verify that the correct type is used. This makes programs potentially more error prone. The const keyword can be used to improve program reliability in a variety of other situations, notably with function arguments. Ill explore these possibilities in Session 7 in Chapter 5.
cout << Enter the patient's age: ; cin >> age; // get a number cin.ignore(10, '\n'); // eat the newline cout << Enter the patient's name: cin.get(name, SIZE); // get a string Here the ignore() function reads and discards up to 10 characters (which probably wont be there) and the new line. The moral: If youre having trouble using cin.get(), try using cin.ignore() to eat any extra new lines first.
class employee { private: char name[SMAX]; // name (20 chars max) int serial_number; public: void input() // get data from user { cout << Enter name: ; cin.get(name, SMAX); cout << Enter serial number: ; cin >> serial_number; } void output() // display employee data { cout << Name = << name; cout << \n Serial number = << serial_number; } }; void main() { employee emps[EMAX]; int n = 0; int j; char choice = 'x'; while(choice != 'q') {
// // // //
array of employee objects current number of objects in array loop variable (ensure it's not 'q') // exit on 'q' Page 129 of 701
cout << \n'a' to add an employee \n'd' to display all employees \n'q' to quit program \nEnter letter choice: ; cin >> choice; // get user's choice cin.ignore(10, '\n'); // eat extra '\n' switch(choice) { case 'a': // get employee data cout << Enter data for employee << (n+1) << endl; emps[n++].input(); break; case 'd': // display all employees for(j=0; j<n; j++) { cout << \nData for employee << (j+1) << endl; emps[j].output(); } break; case 'q': // let the while loop break; // terminate the program default: cout << Unknown command; break; } // end switch } // end while // end main()
String I/O
Notice how using real strings simplifies the input() and output() member functions of employee. The do and for loops have vanished. I use cin.get() to read the employees name in the input() member function. This requires that I use cin.ignore() in main() to eat the \n after the users letter choice so cin.get() wont become confused. The switch statement embedded in a while loop makes the program interactive. It displays a list of possible letter choices; when the user enters one, the program carries out the appropriate task before returning to the list of choices. Heres some sample interaction with STREMP: 'a' to add an employee 'd' to display all employees 'q' to quit program Enter letter choice: a Enter data for employee 1 Enter name: James Joyce Enter serial number: 1117 'a' to add an employee 'd' to display all employees 'q' to quit program Enter letter choice: a Enter data for employee 2 Enter name: Henry James <--input employee 2 Page 130 of 701 <--input employee 1
Enter serial number: 2227 'a' to add an employee 'd' to display all employees 'q' to quit program Enter letter choice: d Data for employee 1 Name = James Joyce Serial number = 1117 Data for employee 2 Name = Henry James Serial number = 2227 'a' to add an employee 'd' to display all employees 'q' to quit program Enter letter choice: q
External Variables
I should mention that SMAX and EMAX are defined in a new way. The variables youve seen before have been defined either inside a class or inside the main() function. SMAX and EMAX , on the other hand, are defined outside of everything: There are no braces surrounding them. Variables defined this way are called external variables. External variables are accessible to all parts of the program, whereas variables defined within a class are accessible only within the class and variables defined inside a function are accessible only within the function. Ill delve into external variables Session 5 in Chapter 4.
Quiz 4 1. In C and C++, a string constant a. has a character with a numerical value of 0 (zero) at the end. b. has a character count stored in memory along with it. c. can be assigned to any string variable using the = operater. d. is terminated by the null character \n. e. must be manipulated one character at a time for input/output. 2. Which of the following correctly defines a string variable? a. char stringvar[8] = piggyback; b. char stringvar[8]; c. char piggyback; d. stringvar[8] = piggyback; e. char stringvar[80] = rolling stone;
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 131 of 701
3. The expression cin.get(buff, MAX) a. displays characters. b. reads from the keyboard. c. obtains up to MAX characters and places them in buff. d. implies that buff should be an array of MAX characters. e. is identical to cin >> buff. 4. The keyword const, used when declaring a variable, a. implies that the variables value can be changed only by member functions of the const class. b. implies that the variables value cannot be assigned to another variable. c. implies that the variable must be given a value at the same time its defined. d. creates variables that can be used for storing keyboard input. e. implies that the variables value wont be changed by other program statements. 5. The architecture of an interactive program, where the user enters one-letter choices, might reasonably involve a. a switch in a while loop. b. a switch in a for loop. c. an else if ladder in a for loop. d. an else if ladder in a do loop. e. no loops at all. Exercise 1
Modify the employee class in the STREMP program of this lesson so that it includes an additional item of instance data to hold the employees title, such as Editorial Assistant, Vice-president of Marketing, Laborer, and so forth. This modified class should work with the same main() found in STREMP.
Exercise 2
Write a class called book that contains the data necessary to manage books in a bookstore. This includes the title, publisher, price, and quantity on hand. Write a main() that stores book objects in an array and allows the user to add a book to the array interactively, display all the books, and sell a book by decrementing the number on hand.
Midchapter Discussion
Well, at least arrays are pretty straightforward. Except for that little detail about the last element being numbered one less than the size of the array. Or was it one more? I can never remember. Anyway, whatever happened to 10 apples C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 132 of 701 Don: George:
Estelle: Don:
numbered from 1 to 10? Thats the natural way to number things. But then you waste the place for number 0. In computers, the first address, or the first anything, is always 0. Its more logical. You could do it either way, but in C++, arrays just happen to start with 0. You better get used to it. No need to get huffy. How come I learned about two different kinds of strings? Huh? You know, I started off with homemade strings, as in the EMPLOY1 program in Session 2. Then I learned about real C++ strings, which are terminated with a zero. I didnt know there was a difference. Uh, oh. Youre in trouble. The homemade strings just showed how to treat text as as array of characters. But doing that isnt practical, because you need loops to handle the characters individually. Real C strings are a lot easier to use. You can use a single operator such as cout << to output a real string all at once, instead of doing it one character at a time. And real strings are null terminated, instead of having to store a separate character count. Is that good? Well, probably. Its another deal where either way would work. A C string is always one character longer than the number of characters, but on the other hand, you dont need an extra integer variable to specify the length. Isnt there a lot of stuff about strings I dont know how to do? Adding strings together, copying them, and comparing them? Are there operators built into C++ to do all that? I think they use library functions instead of operators, and I bet well get to them soon.
Copying Strings
You can copy a string variable or constant to another string variable using the strcpy() function. char src[] = Songs; // string initialized to a value char dest[80]; // empty string variable C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 133 of 701
strcpy(dest, src); // copies the contents of src into dest After this statement has been executed, dest will contain Songs. The string src will not be changed. Notice that strcpy() causes the string named in its right argument to be copied to the string in its left argument. If this right-to-left direction seems backward to you, remember that its how the assignment operator (=) works: The value on the right is copied into the variable on the left.
Appending Strings
Appending strings might be called adding strings. If you append the string hurry onto the string You better the result is You better hurry. Heres how that might look as a program fragment: char s1[80] = You better ; char s2[] = hurry; strcat(s1, s2); The string s1 is now You better hurry. The library function strcat() appends one string to another. The cat in strcat() stands for concatenate, another word for append (although some writers use concatenate to mean that a third string is created that consists of the other two). Some languages allow you to concatenate strings using the + operator, as in s1+s2; this isnt permitted with ordinary C strings. (However, as Ill demonstrate later, you can do this by overloading the + operator in user-defined string classes.) Lets make a slight change in the previous code fragment. Will these statements operate correctly? char s1[] = You better ; // s1 is 12 bytes long char s2[] = hurry; // s2 is 6 bytes strcat(s1, s2); // Error: now s1 needs to be 17 bytes No, because the compiler makes s1 just large enough to hold You better and concatenating anything else onto it will overflow s1, leading to unpredictable but probably unpleasant results. The moral is, when you put a string anywhere, youd better be sure theres enough space for it.
Comparing Strings
Its often useful to compare stringsin checking passwords, for example. The strcmp() function compares two strings and returns a number indicating that the strings are the same or, if they arent the same, which comes first alphabetically. This function is case sensitive, so Smith is not the same as smith. char name[] = Smith; n1 = strcmp(name, Renaldo); <--returns 1 (first argument follows second) n2 = strcmp(name, Smith); <--returns 0 (first argument same as second) n3 = strcmp(name, Townsend); <--returns -1 (first argument precedes second) Some string functions have close cousins that perform similar but not quite identical tasks. For example, the stricmp() function compares two strings in the same way that strcmp() does but is not case sensitive; the i stands for case insensitive. Thus it will tell you that Blue and blue are identical. The strncmp() function is also similar to strcmp(), except that it looks only at a specified number of characters in the strings its comparing. Many other string functions have similar variations, signaled by the addition of a letter in the function name. Some string functions cannot be used effectively without an understanding of pointers, which youll encounter in Chapter 8. These include functions that search a string for a given character and search a string for another string. Because the C++ function library was inherited from C, it is not based on classes. However, most C++ compilers also include a class library that contains a string class. For example, Borland C++ includes a class called cstring. Im going to ignore such prebuilt classes for the moment, because it will be more educational to develop our own string class as we go along.
You cant copy one string to another with the assignment operator (=). The string functions dont warn you if you overflow a string array. You cant concatenate strings with the + operator. You cant compare strings with the ==, !=, <, and > operators.
In this section, Im going to take a first step toward creating a homemade string class. Ill address the first two items on the list here; the last three require that you know how to overload C++ operators, so Ill defer them until later. Ill name the string class xString. Its instance data consists of an ordinary string and it includes member functions to initialize itself to an ordinary string, get a string from the user, display its own contents, and append one string to another. A surprising benefit of making a string into a class is that you can use the equal sign to set one xString object equal to another. Listing 3-6 shows STRCLASS. Listing 3-6 STRCLASS // strclass.cpp // uses a class to models strings #include <iostream.h> #include <string.h> const int MAX = 80;
class xString { private: char str[MAX]; // ordinary C string public: void init( char s[] ) // initialize with string { strcpy(str, s); } void input() // get string from user { cin.get(str, MAX); } void display() // display string { cout << str; } void append(xString xs) // append argument string { if(strlen(str) + strlen(xs.str) < MAX-1) strcat(str, xs.str); else cout << \nError: xString too long << endl; } }; void main() { xString s1, s2, s3; s1.init(Greetings, );
cout << Enter your name: ; s2.input(); // get s2 from user s1.append(s2); s3 = s1; s3.display(); // append s2 to s1 // set s3 to s1 // display s3
} In main(), the program creates three xString objects. It initializes s1 to an ordinary string using the function init(), gets text from the user for s2 with the input() function, appends s2 to s1 with append(), sets s3 equal to s1 using the assignment operator, and finally displays s3 with display(). This may not be exactly what you would want to do with three string objects, but it does provide a chance to exercise all the member functions of the xString class. Now lets look at some features of this program.
Library Functions
I use the strcpy(), strlen(), and strcat() library functions to copy, find the length of, and append ordinary C strings. As you can see, these functions are far more convenient than writing your own loops to do the same thing character by character.
Clean Format
You can see how easy it is to create xString objects. You dont need to define arrays, only simple variables (or what look like simple variables but are actually user-defined class objects).
Data Conversions
As Ive noted, the init() function is a rather clumsy way to initialize an object. Youll learn a more elegant approach when I talk about constructors. However, the init() function does do something interesting and useful: It acts as a conversion function, converting an ordinary C string into an xString object. The C string is given as an argument to init(), which assigns its value to the xString object that called it. Converting from one data type to another is an important topic in OOP; Ill return to it later.
Appending
The member function append() appends its argument to the object that called it. In this example, whatever name the user enters into s2 is appended to Greetings, .
Assignment
Perhaps surprisingly, the assignment operator (=) works with objects (at least these objects). The program sets s3 equal to s1; when it displays s3, you can sees that s3 has indeed taken on the value of s1. How can the equal sign work with objects that contain arrays, when it doesnt work with arrays? It seems odd at first, but makes sense when you think about it. For one thing, because all objects of the same class are identical (except for their contents), its easy to copy the data from one into another, whereas one array may be a different size than another.
Overflow Protection
The input() member function provides built-in protection against the user overflowing the xString storage buffer (the str array). The programmer doesnt need to take any special precautions, such as specifying a buffer size. Also, the append() function checks that the two strings that its about to append wont exceed the size of an xString. Thus the xString class is safer to use than ordinary C strings. Of course theres a downside to this safety: All xStrings must be the same length. This may waste a lot of memory space for short xStrings and precludes using long strings. (Later Ill show how objects of a string class can be made exactly as large as they need to be using dynamic memory allocation.)
Wrapping
Imagine that the xString class was created by one programmer, but another programmer must write a main() that makes use of this class. The main() programmer could use ordinary C strings and library functions such as strcpy(), or she could choose to use the xString class instead. Thus, although the xString class itself uses ordinary C strings and string library functions internally, these are invisible to the main() programmer, who instead sees xString objects and member functions such as input() and append(). This is another example of wrapping one set of functions with another to create a new user interface.
Quiz 5 1. String library functions such as strlen() and strcpy() a. work with user-defined class objects. b. are useful for ordinary C strings. c. are used exclusively with null-terminated strings. d. are member functions. e. cannot be applied to nonstring arrays of characters. 2. To use the strcpy() library function to copy the string s1 to the string s2, you would say a. s1.strcpy(s2); b. s2.strcpy(s1); c. strcpy(s1, s2); d. strcpy(s2, s1); e. s2 = strcpy(s1); 3. Which of these statements are true? a. The chief task of the single equal sign (=) in C++ is to assign a value to a variable. b. You can assign one C string variable to another with a function. c. You can assign one C string variable to another with an equal sign. d. You can assign any object to another of the same class with the strcpy() function. e. You can assign any object to another of the same class with the = operator (assume that = is not overloaded). 4. A string class can reasonably be expected to be superior to ordinary C strings for which of the following reasons: a. Its less error prone.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 137 of 701
b. Its easier to program. c. It can do more things. d. It executes faster. e. It takes less memory to store a string. 5. One might want to use a class to wrap a group of functions and data in a different group of functions because a. there were flaws in the way the original functions operated. b. different program design goals may favor a new approach. c. the idea of classes is easier to understand. d. using classes is generally more efficient than using library functions. e. the programmers interface can be made easier or more capable. Exercise 1
For the xString class, create a member function called compare() that compares an xString given to it as an argument with the xString that called it. This function should return -1 if the argument comes before the object alphabetically, 0 if the two are the same, and 1 if the argument comes after the object.
Exercise 2
For the xString class, create a member function that takes two xString objects as arguments, concatenates them, and places the resulting value in the object that called the function. You can name this function concat(). Add some statements to main() to test it.
Figure 3-9 Array of empty strings You might write some code so the user could enter names into this array: for(j=0; j<5; j++) { cout << Enter name (or press Enter to exit loop): ; cin.get(names[j], 10); if( strlen(names[j])==0 ) // if user presses [Enter], break; // exit from loop } Here the for loop wont let the user enter more than five names. By pressing and thus inserting a 0-length name into the array, the user can exit the loop after entering fewer than five names. Notice that a single string is referred to as names[j], with only one index. As I mentioned earlier, this is how you refer to one subarray in a two-dimensional array.
Wednesday,Thursday, Friday, Saturday}; The string constants used for initialization are separated by commas and surrounded by braces, just as constants are in one-dimensional arrays. This causes each string constant to be installed in the appropriate subarray of day_name. Figure 3-10 shows how this looks.
Figure 3-10 Array of strings Note: Note in Figure 3-10 that there is some space wasted at the ends of the shorter string constants because all the string variables are the same length. You can avoid this if you use pointers, as youll see later. Ive used the variable names MAX and DPW for the array dimensions and made them type const, as discussed earlier. Ive also made the entire array type const because presumably the days of the week will never need to be changed.
const char day_name[DPW][MAX] = // array of day names { Sunday, Monday, Tuesday, Wednesday,Thursday, Friday, Saturday}; class weekday // class of day names { private: C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
int day_number; public: void inday() { char tempday[MAX]; int gotit = 0; int j;
// Sunday=0, etc. // user inputs day name // holds user input // match found? (0=false) // loop variable
while( !gotit ) // cycle until user enters { // a correct name cout << Enter day of week (e.g., Friday): ; cin >> tempday; for(j=0; j<DPW; j++) // compare user input { // with list of names if( stricmp(tempday, day_name[j])==0 ) { // if there's a match, gotit = 1; // set flag, break; // break out of for loop } } // end for day_number = j; // record what day it was } // end while // end inday()
void outday() // display the day name { cout << day_name[day_number]; } void outnumber() { cout << (day_number + 1); } void add(int days) { day_number += days; day_number %= DPW; } }; // end weekdays class // display the day number
// // // //
//////////////////////////////////////////////////////////////// void main() { weekday wd; // make a weekday object cout << What day is it? << endl; wd.inday(); // cout << You entered ; wd.outday(); // cout << \nThat's day number ; wd.outnumber(); // wd.add(10); // cout << \nTen days later is ; wd.outday(); //
user gives it a value display its day name display its day number add 10 days to it
display its day name } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
In main(), the program creates an object of the weekday class, wd. Then it gets a name from the user and compares it with all the weekday names in the day_name array. If theres a match, it stores the number of the day in the object. Then it tells this object to display its day name and day number. Ive added a member function to the weekday class to enable the user to add a number of days to a weekday object. In main(), the program tells wd to add 10 days to itself, and then reports what day of the week wd has become (10 days after Wednesday is a Saturday). Heres the output from WEEKDAYS: What day is it? Enter day of week (e.g., Friday): wednesday You entered Wednesday That's day number 4 Ten days later is Saturday There are several new wrinkles in the operation of WEEKDAYS. It uses a variation of the string function strcmp() and it uses a new kind of operator.
Figure 3-11 Arithmetic assignment operator It turns out that there are arithmetic assignment operators for all the arithmetic operations. a += b; // same as a = a + b a -= b; // same as a = a - b a *= b; // same as a = a * b a /= b; // same as a = a / b a %= b; // same as a = a % b C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 142 of 701
There are a few other assignment operators as well, but you can ignore them for now. In the WEEKDAYS program, I use the last of these operators, %=, to make sure that day_number is always in the range of 0 to 6, no matter how many days you add to it. day_number %= DPW; This sets day_number to the remainder produced when day_number is divided by 7. Arithmetic assignment operators provide a way to make your listing look less cumbersome, possibly at the expense of some clarity for newcomers to C++.
Quiz 6 1. Which of the following statements define(s) an array of 100 empty strings, each of which can hold up to 80 characters? a. char strarray[100]; b. char strarray[80][100]; c. int strarray[80, 100]; d. char strarray[100, 80]; e. char strarray[100][80]; 2. If you initialize an array of strings called names, a. each character is placed in a one-dimensional subarray of names. b. each string constant is placed in a one-dimensional subarray of names. c. each character is placed in an element of a one-dimensional subarray of names. d. each string constant is placed in an element of a one-dimensional subarray of names. e. all the string constants are placed in a subarray of names. 3. The weekday class a. models the number of work hours in each day of the week. b. must have access to the day_name array. c. can convert from a day name such as Monday to a number and back again. d. stores the name of a day of the week as instance data. e. stores a number as instance data. 4. If an external variable is defined at the beginning of a file, and there is only one file for the program, the variable is a. not accessible by any statement in the program. b. accessible by statements in member functions in the program.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 143 of 701
c. accessible by statements in main(). d. accessible by statements that are neither in standalone functions such as main() nor in member functions. e. not accessible unless its made into a class. 5. Arithmetic assignment operators such as += and *= a. exist to shorten and simplify the program listing. b. perform arithmetic only on imaginary numbers. c. combine the tasks of the equal sign (=) and another binary operator. d. perform arithmetic on two variables and leave the result in a third. e. assign something and then perform arithmetic on it. Exercise 1
Add a member function called difference() to the weekday class. This function should subtract the day number of the object that called it from the day number of its argument and return an integer representing the number of days between these two days of the week. Heres how it might be called from main(): cout << Enter the starting day: start_day.inday(); cout << Enter the ending day: end_day.inday(); cout << \nThe project will take ; cout << start_day.difference(end_day); cout << days;
Exercuse 2
Create a class called ordinal that models the names of the ordinal numbers, first, second, third, fourth, fifth, and so on, up to twelfth. Provide input and output member functions that get and display such values using either of two formats: ordinary integers (1, 2, and so on) or ordinals. Also, provide an add() function that allows you to add a fixed integer to objects of the class. Assume that no number larger than 12 or less than 1 will be represented.
Session 7: Structures
A C++ structure is a way to group together several data items that can be of different types. An array, by contrast, groups a number of data items of the same type. Structures are typically used when several data items form a distinct unit but are not important enough to become a class. Structures are more important in C, where there are no classes. In C++, a class plays many of the roles filled by structures in C. Nevertheless, there are still many situations where structures are useful.
Specifying a Structure
Heres an example of a structure specification: struct part { int modelnumber; int partnumber; float cost; }; A structure consists of the keyword struct, followed by the structure name (also called the tag) and braces that surround the body of the structure. It is terminated with a semicolon. The body of the structure usually consists of C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 144 of 701
various data items, which may be of different types. These individual data items are called members. (As you recall, the individual data items within an array are called elements.) Figure 3-12 shows the syntax.
Figure 3-12 Syntax of the structure specifier As you can see, a structure is quite similar syntactically to a class. However, structures and classes are usually used in quite different ways. Typically, a class contains both data and member functions, whereas a structure contains only data. Ill return to the relationship between structures and classes at the end of this lesson. As with a class, a structure specification does not actually create a structure. It merely specifies how a structure will look when its created.
Figure 3-13 Structures and structure variables I should mention for completeness that theres a shortcut for defining structure variables. The variable names are telescoped into the structure specification, like this: struct part { int modelnumber; int partnumber; float cost; } cp1, cp2; This code specifies the structure and creates two variables of that structure at the same time. A peculiarity of this format is that you can remove the structure name or tag (part); its not needed because the variables already know what structure theyre based on. This shortcut format is not as easy to understand as using separate statements for the specification and the definition, so Ill avoid it in the example programs.
cout << Enter model number: cin >> cp1.modelnumber; // access data member in structure C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 146 of 701
cout << Enter part number: ; cin >> cp1.partnumber; // access data member in structure cout << Enter cost: ; cin >> cp1.cost; // access data member in structure The variable name precedes the dot and the name of the member data follows it: cp1.cost. Figure 3-14 shows how this looks if the user enters 6244, 373, and 217.55 for the model number, part number, and cost; Figure 3-15 shows how the variable name and member data name are related to whats stored in memory.
Structure Usage
Lets modify an earlier example program so that it uses a structure. Notice in the STACK1 program, from Session 2 in this chapter, that the array that stores data items and the index to this array (the top of the stack) are closely related. You might find it convenient to combine these two data items into a structure, and then use the structure as a single data member of the Stack class. Listing 3-8 shows how this might look. Listing 3-8 STRUSTAK // strustak.cpp // class models a stack, uses struct for array and index #include <iostream.h> struct stackette { int arr[20]; int top; };
class Stack // { private: stackette st; // public: void init() // { st.top = -1; } void push(int var) // { st.arr[++st.top] = var; } int pop() // { return st.arr[st.top--]; } }; void main() { Stack s1; s1.init(); s1.push(11); s1.push(12); s1.push(13);
class
cout << s1.pop() << endl; // pop 3 items and display them cout << s1.pop() << endl; cout << s1.pop() << endl; } Here the structure stackette holds the array of integers and the index that points to the top of the stack (the last item placed in the array). struct stackette { int arr[20]; // storage array int top; // index of top of stack }; The only item of instance data in the Stack class is now a variable of class stackette. stackette st; Member functions of Stack now refer to individual data members of st, using the dot operator st.top = -1; and st.arr[++st.top] = var; Notice that main() is identical to the main() in the STACK1 program. Ive modified the class but kept the class interface the same, so no rewriting is necessary for the functions that use the class. Its doubtful whether in this particular programming example the use of a structure provides significant simplification. However, once I discuss pointers, youll encounter examples where the use of structures as class members can indeed clarify the workings of a program. (See the linked list example in Session 7 in Chapter 8.)
in C++ than structures are. Classes form the very basis of object-oriented programming, whereas structures are a minor part of C++ usage. However, these differences obscure the fact that the syntax of structures and classes is almost identical. You can install member functions within a structure just as you can in a class, and conversely you can remove the member functions from a class so that it acts like a structure. There is only one real syntactical difference between a structure and a class: The members of a structure are public by default, whereas the members of a class are private by default. That is, if you dont use the keywords public or private, class members are private. class Foo { // no need for keyword, private by default int george; int harry; public: // public must be specified void init() { } void display() { } }; For clarity, Ill always use the keyword private in class specifications in the example programs, but in fact its optional. In structures, the situation is reversed. struct Bar { // no need for keyword, public by default void init() { } void display() { } private: // private must be specified int george; int harry; }; In structures, you usually want all the data members to be public, so you normally leave out this keyword. These syntax distinctions are all rather academic because in most situations, using a structure as a class or vice versa would simply cause confusion. No doubt, the designers of C++ borrowed the syntax of C structures when they invented classes and thenperhaps to make compiler design easieraugmented structures to make them as similar as possible to classes.
Quiz 7 1. Specifying a structure a. uses syntax similar to a class specification. b. creates a structure variable. c. requires the use of the keyword public. d. usually involves a number of member data items. e. usually involves a number of member functions. 2. Structures are normally used a. as an alternative way to create classes and objects.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 150 of 701
b. to group data items of the same type. c. to group data items of different types. d. to combine data items that are closely related into a single unit. e. to increase memory efficiency. 3. Accessing a structures member data a. is normally carried out using the structures member functions. b. is normally carried out by a function located somewhere outside the structure. c. uses a similar format to accessing member functions in classes. d. is easy because the data is public by default. e. requires the dot operator (ignore pointers). 4. In the STRUSTAK program, a. statements in main() access the stackette structure using the dot operator. b. a structure variable is included in the Stack class. c. a structure specification is included in the Stack class. d. there is only one member data item in the Stack class. e. member functions in Stack need not be modified when a stackette structure is inserted in the class in place of individual data items of basic types. 5. Structures and classes differ in that a. the public versus private distinction is applicable to classes but not to structures. b. structures are usually used only for data, not for data and functions. c. data members may be accessed using the dot operator in classes, but not in structures. d. structures use brackets, whereas classes use braces. e. both data and function members are public by default in structures, but private by default in classes. Exercise 1
In Exercise 2 in Session 2 in this chapter, I suggested you create a peek() function, which would access the item on the top of the stack without removing it. Modify this function so it works with the version of Stack in the STRUSTAK program.
Exercise 2
Modify the EMPLOY1 program from Session 2 in this chapter so that the array name and the integer n, used as instance data in the employee class, are combined into a structure. Substitute a variable of this structure type for these two member data items and make whatever modifications are necessary for the employee class to work as it did in EMPLOY1.
enclosing a list of comma-separated value names. Figure 3-16 shows the format. Figure 3-16 Syntax of enum specifier Enumerated means that all the values are listed explicitly. This is unlike the specification of an int, for example, which has a range of possible values (such as -32,768 to 32,767). In an enum, you must give a specific name to every allowable value. This is potentially confusing. Remember, the names used for enum values are not the names of variables. An int has values 0, 1, 2, and so on. The enum shown above has the values Sun, Mon, and so on up to Sat. Figure 3-17 shows the difference between an int and an enum.
Figure 3-17 Usage of ints and enums As with classes and structures, specifying an enumerated type is not the same as actually creating variables of that type. That step requires a separate statement.
day1 = Mon; // give them values day2 = Wed; One of the advantages of using enumerated types is that attempting to assign a nonspecified value will cause a compiler error, alerting you to program bugs. For example, even if Easter is defined as an int variable, day1 = Easter; will not compile because day1 is a variable of type days_of_week and Easter is not on the list of possible values for days_of_week. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 153 of 701
enum Suit { clubs, diamonds, hearts, spades }; const const const const int int int int jack = 11; queen = 12; king = 13; ace = 14; // from 2 to 10 are // integers without names
class card { private: int number; // 2 to 10, jack, queen, king, ace Suit suit; // clubs, diamonds, hearts, spades public: void init(int n, Suit s) // initialize card { suit = s; number = n; } void display() // display the card { if( number >= 2 && number <= 10 ) cout << number; else switch(number) { case jack: cout << J; break; case queen: cout << Q; break; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 154 of 701
case king: cout case ace: cout } switch(suit) { case clubs: cout case diamonds: cout case hearts: cout case spades: cout } } // end display() }; // end class card void main() { card deck[52]; int j = 0; int num;
cout << endl; for(num=2; num<=14; num++) { deck[j].init(num, clubs); deck[j+13].init(num, diamonds); deck[j+26].init(num, hearts); deck[j++ +39].init(num, spades); }
// for each number // // // // set set set set club diamond heart spade
cout << \nOrdered deck:\n; for(j=0; j<52; j++) // display ordered deck { deck[j].display(); cout << ; if( !( (j+1) % 13) ) // newline every 13 cards cout << endl; } randomize(); for(j=0; j<52; j++) { int k = random(52); card temp = deck[j]; deck[j] = deck[k]; deck[k] = temp; } // seed random number generator // for each card in the deck, // pick another card at random // and swap them
cout << \nShuffled deck:\n; for(j=0; j<52; j++) // display shuffled deck { deck[j].display(); cout << ; if( !( (j+1) % 13) ) // newline every 13 cards cout << endl; } getch(); // wait for keypress } // end main C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
This program uses two library functions that may not exist on all compilers. These are randomize(), which seeds a random number generator (using the current time), and random(), which returns a random number. These functions exist in Borland compilers but other compilers use slight variations, so you may need to modify the source code to get this program to run. Heres the output from CARDARAY. First the deck is displayed in the order it was initialized. Then its displayed after shuffling. Ordered deck: 2c 3c 4c 5c 6c 7c 8c 9c 10c Jc Qc Kc Ac 2d 3d 4d 5d 6d 7d 8d 9d 10d Jd Qd Kd Ad 2h 3h 4h 5h 6h 7h 8h 9h 10h Jh Qh Kh Ah 2s 3s 4s 5s 6s 7s 8s 9s 10s Js Qs Ks As Shuffled deck: 3c 5c Qc 9s Ah Kd 6h 7h 4s As 2h 5d Ks 7c Js 3s 10h 8s Jc Jh Ac 5s 7s Qs 10d 2c Jd 8d 4d 2d 6s 4h 10s 6d 4c Ad Qh 7d 6c 10c 9c 3h 8c 5h 2s Kc 9h Qd 8h 3d 9d Kh The program uses an enum type for the suit because there are only four possible values: clubs, diamonds, hearts, and spades. This makes it impossible for the program to assign any value other than these four. If you try to use an integer value, for example, the compiler will issue a warning. The enum approach would be unwieldy for the card number, so I use an integer that can have a value from 2 to 14, with the special names jack, queen, king, and ace given to the values 11, 12, 13, and 14. The display() member function uses switch statements to figure out the appropriate display, given the suit and number of each card.
// for stricmp()
const int MAX = 10; // maximum length of day name, +1 const int DPW = 7; // days per week const char day_name[DPW][MAX] = // array of day names { Sunday, Monday, Tuesday, Wednesday,Thursday, Friday, Saturday}; class weekday // class of day names { private: int day_number; // Sunday=0, etc. public: void inday() // user inputs day name { bool gotit = false; // match found? int j; // loop variable char tempday[MAX]; // holds user input while( gotit==false ) // cycle until user enters { // a correct name cout << Enter day of week (e.g., Friday): ; cin >> tempday; for(j=0; j<DPW; j++) // compare user input { // with list of names if( stricmp(tempday, day_name[j])==0 ) { // if there's a match, gotit = true; // set flag, break; // break out of for loop } } // end for day_number = j; // record what day it was } // end while } // end inday() void outday() { cout << day_name[day_number]; } void outnumber() { cout << (day_number + 1); } void add(int days) { day_number += days; day_number %= DPW; } }; // end weekday class // display the day name
// // // //
//////////////////////////////////////////////////////////////// void main() { weekday wd; // make a weekday object cout << What day is it? << endl; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
wd.inday(); cout << You entered ; wd.outday(); cout << \nThat's day number ; wd.outnumber(); wd.add(10); cout << \nTen days later is ; wd.outday(); }
// user gives it a value // display its day name // display its day number // add 10 days to it // display its day name
Quiz 8 1. The enum keyword specifies a. a list of variable names. b. a new data type c. a list of values. d. a list of data types. e. the numerical equivalent for each name on a list. 2. Enumerated data types are useful when a. only a small number of variables of a certain type will be used. b. you will redefine how arithmetic works with variables of the type. c. you want to ensure that a variable is not given an incorrect value. d. a variable can take on a limited number of values. e. a variable contains instance data of several basic types. 3. Declaring a variable of an enumerated type a. requires you to use the int basic type. b. involves specifying a list of possible values. c. requires that the variable be simultaneously initialized to a value. d. has the same syntax as declaring an object of a class. e. creates a variable that is represented in computer memory by an integer. 4. In the cardaray program, a card object a. can display its number and suit. b. must be initialized to an appropriate card value when it is defined.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 158 of 701
c. represents a deck of 52 cards. d. can be moved to a different location in the array deck. e. might have the value { jack, hearts }. 5. The keyword bool a. represents an enum type. b. is used to create variables that can have only one of two values. c. represents an integer type. d. is a value given to variables. e. defines variables used to store the results of logic tests. Exercise 1
Revise the main() part of the CARDADAY program so that, after shuffling the deck, it deals a hand of 13 cards to each of four players (as in bridge). A player can be represented by an array holding that players cards. After dealing the four hands, display them.
Exercise 2
Revise the airtime class in the ARRAYAIR program in Session 3 in this chapter so it works with 12-hour time. In other words, 1:30 should be displayed as 1:30 am and 13:30 should be displayed as 1:30 pm. Create an enumerated data type called meridian that has two possible values: am and pm. Rewrite the set() and display() functions so they use this type to help handle the 12-hour format.
Summary: Chapter 3
In this chapter, Ive focused on arrays and strings, with brief looks at structures and enumerated types. An array is a way to group a number of variables of the same type and to refer to them using an index number or subscript. The index number is represented by a number in brackets, such as [5]. Arrays are the most commonly used data storage structure, but they do not warn the programmer when an array index is out of bounds, so its easy to make programming mistakes that cause serious malfunctions. The increment and decrement operators, ++ and --, can be placed either before or after their operands. If placed before (++x), the operand is incremented (or decremented, if -- is used) before its value is used in an expression. If placed after (x++), the operand is incremented (or decremented) after its value is used. A string is a group of characters terminated with a null character, \0, which has the numerical value 0. A string variable is an array of type char used for storing strings. A string constant is defined using double quotes: George Smith. Special library functions exist for processing strings. Strings (i.e., ordinary C strings) have nothing to do with classes and OOP. However, string classes can be created that act like strings and improve upon them in various ways. A variable defined with the const modifier must be initialized to a value when its defined; this value cannot be modified during the course of the program. A structure is, from a syntax standpoint, almost identical to a class; the only difference is that a structures members are public by default, whereas a classs are private. However, a structure usually does not have member functions; its used only as a way to group data items together. In many cases in C++, a class replaces a structure. Enumerated types allow the programmer to specify a new data type that can be given only a small number of values whose names are specified. This can aid in clarity and safety.
End-of-Chapter Discussion
George: Estelle: Don: Estelle: Don: George: Don: George: Estelle: Don: Great. Now I have three kinds of strings to worry about. Homemade, regular, and classes. When do you use what? Forget about homemade strings. No one would use them in a real program; that was just a teaching device. In C++, assuming you have a string class, its easier to use strings that are class objects than ordinary C strings. Why? Because you can define whatever string operation you want, using member functions. But to write the class in the first place, you need to use C strings. Theyre the fundamental way to handle text. So you need to understand both? Ideally. But in your case, Id stick with one or the other. No use getting confused. A wise guy, eh? I dont understand why I need structures. It seems like classes do everything structures do and then some. I dont think structures are as important in C++ as they are in C, for just that reason. But every now and then you probably want to stick some data items together without going to the trouble of making it a full-blown class. What about enumerated types? What are they really for? I bet if they hadnt already been part of C when C++ was invented, no one would have bothered. For sure you can use a class to do everything that enumerated types do. At least, I think so. But because theyre in the language, you may as well use them. Use them when? Whenever some variable is going to have just a small number of possible values. Whats the advantage? It makes the listing clearer and makes it easier for the compiler to find bugs in your program. If you assign an incorrect value to an enumerated type, the compiler lets you know. Who needs that? I never make programming mistakes anyway. Oh, right.
George: Don:
CHAPTER 4 FUNCTIONS
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.
Review
Ive been using functions all along. So far, youve seen them in three different situations. First, I introduced member functions, which are one of the two fundamental components of classes, along with instance data. Second, functions appear independently in the listing, outside of any class specification. An important function in this category is main(), which is the function to which control is transferred when a C++ program starts to run. At the end of this lesson, youll see that main() can, in effect, be divided into many functions. Third, youve seen examples of C library functions such as getche() and strlen(), which are called from main() or from member functions. The programmer does not need the source code for library files; instead, object (.OBJ) or library (.LIB) files are linked to user-written object files to create the final .EXE file. Library functions require that the appropriate header file (e.g., CONIO.H for the getche() function) be included in the source file. Now lets look in more detail at the various aspects of functions.
Function Calls
A function is a section of code. It is typically called (made to execute) from a statement in another part of the 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.
Function Definitions
A function definition consists mostly of the functions code, that is, the statements that make up the function and cause it to do something. When I say function, I usually mean the function definition. The function definition starts with a line called the declarator, which specifies the function name, its return type, and the types and names of arguments. Parentheses follow the function name and enclose the arguments, if any. The functions statements, which are the function body, are enclosed in braces. Figure 4-2 shows a typical function definition.
Figure 4-2 Syntax of a function definition C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
Arguments
Arguments are the mechanism by which information may be passed from a function call to the function itself. As an example, say that somewhere in your program is a function called repchar(), whose purpose is to display a given character a certain number times. The character to be displayed and the number of times to display it are passed as arguments to the function. If you want to display 10 xs in a line, for example, you might call the function this way: repchar('x', 10); // display 10 x's Heres the definition of the function repchar(): // repchar() // displays n characters with value ch void repchar(char ch, int n) <--This line is the declarator { for(int j=0; j<n; j++) cout << ch; } This function will output the character ch exactly n times in a row. These variables are assigned whatever values appeared in the function call. If the function is executed by the function call shown above, it will display 10 xs. If it is called with the statement repchar('=', 30); it will display 30 equal signs. When data is passed as arguments in this way, the data is copied and the duplicate values are stored in separate variables in the function. These variables are named in the function declarator. This is called passing by value. Figure 4-3 shows how this looks.
Figure 4-3 Passing by value Technically, the term arguments means the values specified in the function call, whereas the term parameters is given to the variables in the function definition into which the values are copied. However, argument is often used loosely for both meanings. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 163 of 701
Return Values
A function can return a value to the statement that called it. Heres a function that converts pounds (lbs) to kilograms (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().
Figure 4-4 Returning by value C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 164 of 701
Function Declarations
In the example programs youve seen so far, all the functions were member functions of a class, the main() function itself, or library functions. However, there are many other possibilities. For one thing, main() can call other functions. Or, member functions may call other functions located inside or outside the class. When the compiler generates the machine language instructions for a function call, it needs to know the name of the function, the number and types of arguments in the function, and the functions return value. If the function was defined someplace before the function call, this isnt a problem. The compiler will have taken note of these characteristics and stored them away for reference. However, if the function definition follows the function call, the compiler wont know how to handle the call and youll get an undefined function error message. To keep this from happening (and assuming its not convenient to rearrange things so the function definition precedes all calls to it), you must declare the function before any function call. If you do this, the function can be defined anywhere. A function declaration is a single line that tells the compiler the name of the function, the number and types of arguments, and the return type. Heres how you would declare the lbstokg() function: float lbstokg(float); // function declaration or prototype The declaration looks very much like the declarator (the first line of the function definition). However, it is followed by a semicolon because it is a standalone statement. Also, you dont need the names of the arguments, only the types. Remember that a function declaration does not specify anything about what will go inside the function body. All it tells is the name of the function, its return type, and the number and types of its arguments. Function declarations are also called prototypes because they serve as a sort of nonoperative model of how the function will be called.
Arguments
Although they arent necessary, argument names can be used in the declaration. float lbstokg(float pounds); // function declaration These names need not be the same as those in the definition; in fact, they are dummy names, and the compiler ignores them (although they may be referred to in error messages). Sometimes, however, theyre helpful for human readers. For example, suppose the arguments x and y represent screen coordinates for a function that displays a dot on the screen. void draw_dot(int, int); // function declaration This declaration is fine for the compiler, but the programmer may not know which is the x coordinate and which is the y. So its helpful to write void draw_dot(int X, int Y); // function declaration
The functions follow main(), so they must be declared before they can be called. Here theyre declared outside of main(), but they could be declared inside as well, like this: void main() { void func1(); // function declarations void func2(); func1(); // call to func1() func2(); // call to func2() }
Library Functions
Perhaps you are wondering where the function declarations for library functions are located. For example, if I call getche() from somewhere in the program, how does the compiler know this functions characteristics, such as how many arguments it takes? The answer, of course, lies in the header files: They hold the function declarations for a group of related functions. By including the header file in your listing, you cause all these functions to be declared.
The Rule
I can summarize the necessity for function declarations in the following rule: If you call a function before its definition appears in the listing, then you must insert a declaration somewhere before the call. In practice, its best to place function declarations at the beginning of your source file or sometimes at the beginning of the particular function from which calls are made. (As youll see in Chapter 11, Session 7, declarations are typically placed in header files in large program functions.)
Quiz 1 1. The function _________ transfers control to the function; the function _________ is the function itself; the function __________ is surrounded by braces; and the first line of the definition is called the __________. a. definition, declarator, call, body b. definition, body, definition, call
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 166 of 701
c. call, definition, body, declarator d. call, declarator, definition, body e. call, declarator, body, definition 2. When a function returns a certain value, a. the function must use a return statement. b. the function can return other values at the same time. c. the function call must supply the return value. d. the function call expression can be treated as a constant with that value. e. the function must not be of type void. 3. A function declaration has the same relationship to a function definition that a. a class specification has to an object definition. b. an object declaration has to an object. c. a variable has to a variable declaration. d. a variable declaration has to the variable itself. e. none of the above. 4. A function declaration a. creates the code for a function. b. must be placed at the beginning of the file. c. must be placed in the same function as the call. d. must precede any calls to the function. e. tells the compiler how to create a call to the function. 5. Library function declarations a. are located in .LIB library files. b. are located in .H header files. c. are not necessary. d. must be inserted into the source file by hand. e. must not be inserted into the source file by hand.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 167 of 701
Because this session is theoretical and concerned with the big picture, it does not include any exercises.
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.
Figure 4-5 Ordinary functions versus inline functions For inline functions, there is no time wasted in calling the function because it isnt actually called.
Actually, you seldom need to define a function explicitly as inline. In the most obvious place to do this, its already done for you by the compiler, as youll see next.
// call to function
void afunc(); // definition -- NOT an inline function { } The main() function, of course, is never inline; what would it be in line with?
const char day_name[DPW][MAX] = // array of day names { "Sunday", "Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday"}; class weekday { private: int day_number; public: void inday(); void outday() { cout << day_name[day_number]; } void outnumber() { cout << (day_number + 1); } void add(int days) { day_number += days; day_number %= DPW; } }; // end weekdays class void weekday::inday() { // class of day names
// // // //
while( !gotit ) // cycle until user enters { // a correct name cout << "Enter day of week (e.g., Friday): "; cin >> tempday; for(j=0; j<DPW; j++) // compare user input { // with list of names if( stricmp(tempday, day_namhe[j])==0 ) { // if there's a match, gotit = 1; // set flag, break; // break out of for loop } } // end for day_number = j; // record what day it was } // end while } // end inday() //////////////////////////////////////////////////////////////// void main() { weekday wd; // make a weekday object cout << "What day is it?" << endl; wd.inday(); // user gives it a value cout << "You entered "; wd.outday(); // display its day name cout << "\nThat's day number "; wd.outnumber(); // display its day number wd.add(10); // add 10 days to it cout << "\nTen days later is "; wd.outday(); // display its day name } This program is the same as WEEKDAYS except inday() is defined outside the class specification. The other member functions, outnumber() and add(), which are quite short, have been kept inline.
Move em Out?
All member functions of more than a few lines should be written outside the class specification, as shown for inday() in WEEKOUT. From now on, the examples will conform to this usage. Some programmers go further and recommend defining all member functions, no matter what size, outside the class. This has the advantage of simplifying the class specification and it may be a good idea when writing function libraries. However, for small programs such as the examples in this book, defining short member functions within the class avoids some extra program lines and makes the listings easier to read.
Macros
In C, in the olden times, it was customary to use something called a macro to accomplish what inline functions accomplish in C++. For example, in C++ you might have a function that returns the smaller of two integers. int min(int a, int b) { return (a<b) ? a : b; } This function uses the conditional operator to compare its two arguments. If a is less than b, it returns a; otherwise, it returns b. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 172 of 701
A macro uses the preprocessor directive #define, with arguments, to simulate a function. The min() function, implemented as a macro, would look something like this: #define MIN(A, B) ((A<B) ? A : B) The #define directive basically causes the text on the right, ((A<B) ? A : B), to be substituted for the name on the left (MIN) wherever this name occurs in the source file. Its like a word processor doing a global search and replace, except that it also uses arguments. If you call the macro from your program like this, cout << MIN(3, 5); the statement will print out 3, the smaller of the two numbers. How does it work? The preprocessor expands this statement into cout << ((3<5) ? 3 : 5); before it goes to the compiler. Thus, a macro causes a short section of code to be generated and placed inline whenever its called. This has the same advantage inline functions doit eliminates the overhead of a function call and return. However, macros are potentially dangerous. For one thing, they dont perform type checking, so they cant tell if the types of the arguments are correct. On the whole, the compiler is better at catching mistakes than the preprocessor is. Also, macros have some built-in problems (such as the need to surround all variables with parentheses) that can generate particularly troublesome bugs. My advice, even for old-time C programmers: Dont use macros, use inline functions instead.
Quiz 2 1. Member functions, when defined within the class specification, a. are always inline. b. are inline by default, unless theyre too big or too complicated. c. are inline only if the compiler thinks its a good idea. d. are not inline by default. e. are never inline. 2. The code for an inline function a. is inserted into the program in place of each function call. b. is not generated by the compiler itself, but by the preprocessor. c. may be repeated many times throughout the program. d. occurs only once in a program. e. is merely symbolic; it is not actually executed. 3. The scope resolution operator a. joins a function name on the left and a variable name on the right. b. joins a function name on the left and a class name on the right. c. joins an inline function name and an ordinary function name.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 173 of 701
d. consists of a semicolon. e. comprises two semicolons. 4. In the WEEKDAYS program, there is a(n) __________ for inday() within the class specification, a(n) ____________of this function following the class specification, and a(n) ____________ to the function within main(). a. function body, declaration, assignment b. declaration, definition, call c. definition, member function, declaration d. assignment, call, member function e. declaration, function body, call 5. Member functions should be defined outside the class when a. they are more than a few lines long. b. they contain switches or loops. c. they contain I/O statements. d. they are short. e. they are simple. Exercise 1
Start with the STREMP program from Chapter 3, Session 4, and move both the input() and output() member functions definitions outside the employee class.
Exercise 2
Start with the CARDARAY program from Chapter 3, Session 8, and move the display() member function outside the card class. This will eliminate the warning message from the compiler about switch statements not being expanded inline.
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.
Figure 4-6 Overloaded functions 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.
class fancy_text // class displays text { private: char text[MAX_LENGTH]; // text to be displayed public: void set_text(char tx[]) // set the text { strcpy(text, tx); } void box_display(); // line of dashes void box_display(char); // line of characters void box_display(char, int); // line of n characters }; void fancy_text::box_display() // line of 40 dashes { cout << "----------------------------------------"; cout << endl << text << endl; cout << "----------------------------------------"; } // line of 40 characters void fancy_text::box_display(char ch) { int j; for(j=0; j<MAX_LENGTH; j++) cout << ch; cout << endl << text << endl; for(j=0; j<MAX_LENGTH; j++) cout << ch; } // line of n characters void fancy_text::box_display(char ch, int n) { int j; for(j=0; j<n; j++) cout << ch; cout << endl << text << endl; for(j=0; j<n; j++) cout << ch; } void main() { fancy_text ft1; ft1.set_text("Gone with the Wind"); ft1.box_display(); // display text with default lines cout << endl << endl; ft1.box_display('='); // default length and equal signs cout << endl << endl; ft1.box_display('=', 18); // equal signs; length matches text cout << endl << endl; } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 177 of 701
In main(), the program creates a fancy_text object and gives it the text Gone with the Wind. Then it displays this text using three different versions of box_display(). First the program uses the default version, which always prints lines of 40 dashes. Then it uses the version that allows the specification of the type of character to use in the lines; it chooses equal signs. Finally, it uses the version that allows it to select both the character and the length; it uses equal signs again, but matches the length to the length of the text, which is 18 characters. The output from OVERFUN looks like this: ---------------------------------------Gone with the Wind ---------------------------------------======================================== Gone with the Wind ======================================== ================== Gone with the Wind ================== Notice that Ive defined all the box_display() functions outside the class. The function declarations within the class show, with commendable brevity, the differences between the three versions of the function: void box_display(); // uses two lines of dashes void box_display(char); // uses two lines of characters void box_display(char, int); // uses two lines of n characters Remember that, even though they have the same name, these three functions are completely different as far as the compiler is concerned. The compiler has even invented separate names for each function by mangling the argument type names into the function name. (Actually, although you dont need to know this, the class name is mangled into member function names as well so the compiler can distinguish member functions that have the same name but are in different classes, such as the ubiquitous init().)
Quiz 3 1. Function overloading is desirable because a. you dont need to define as many functions. b. in a reference book thats arranged in alphabetical order, all the functions that do the same thing will be in the same place. c. one overloaded function can be made to do the work of several ordinary functions. d. when functions that do the same thing all have the same name, its clearer and easier to remember, even if they do it to different types of data. e. you dont need to make up a lot of slightly different function names. 2. In the OVERFUN program, once the code is compiled into machine language, how many versions of the box_display() function actually exist? a. 1. b. 3. c. There must always be the same number as there are function definitions.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 178 of 701
d. There must always be the same number as there are function calls. e. There must always be the same number as the number of arguments. 3. Name mangling of a member function involves a. creating a name for internal use by the compiler. b. creating a name for use by the programmer. c. incorporating the name of the object that called the function into the function name. d. incorporating the functions argument types into the function name. e. incorporating the class name into the function name. 4. Assuming the statements shown here are all in the same main(), which ones can potentially compile properly and not interfere with each other? a. intvar1 = get_number(); b. floatvar1 = get_number(); c. intvar1 = get_number(intvar2); d. floatvar1 = get_number(floatvar2); e. intvar1 = get_number(intvar2, intvar3); 5. It might be reasonable to use a set of overloaded member functions to a. perform the five basic arithmetic operations on type float. b. exit from a class in different ways. c. draw different shapes. d. display information about different kinds of employees. e. play the roles of strcpy(), strcat(), strlen(), and so on in a string class. Exercise 1
Write a set of overloaded functions (not necessarily member functions) that return the smaller of two numbers passed to them as arguments. Make versions for int, long, and float.
Exercise 2
In the HOURADD program in Chapter 2, Session 6, is a member function called addhours() This function adds a specified number of hours to an airtime object. Change the name of this function to add() and write another member function, also called add(), that uses an airtime value as an argument and adds this argument to the airtime object that called the function. Change main() to check out these overloaded functions.
A Power Example
Suppose I write a function called power() whose purpose is to raise a number of type float to a power, that is, to multiply the number by itself a given number of times. For simplicity, Ill restrict the power to whole numbers. To raise 2.0 to the power of 3, for example, I would call the function like this: answer = power(2.0, 3); Here answer should be 2.0 cubed, or 8.0. The definition for such a function might look like this: float power(float fpn, int pow) { float product = 1.0; for(int j=0; j<pow; j++) // multiply fpn by itself pow times product *= fpn; return product; } This is fine so far, but lets further suppose that I want the same function to handle a special case somewhat differently. If no second argument is specified, I want it to find the square of a number (multiply the number by itself). answer = power(9.0); // should square the number Here the answer should be 9.0 times 9.0, or 81.0. To achieve this result, I could overload power() so I have two separate functions, one that takes a single argument, which it multiplies by itself and one that takes two arguments and raises the first to the power of the second. However, theres a simpler approach. Default arguments allow you to leave out one or more arguments when you call a function. Seeing that arguments are missing, the function declaration supplies fixed values for the missing arguments. Lets see how that looks with the power() example. float power(float, int=2); // function declaration // (supplies default argument) void main() { answer = power(9.0); // call with one argument // (2.0 is supplied as 2nd arg) answer = power(2.0, 3); // call with two arguments } float power(float fpn, int pow) // function definition { // (doesn't know about defaults) float product = 1.0; for(int j=0; j<pow; j++) product *= fpn; return product; } The function declaration is used to supply the default argument. The equal sign indicates that a default argument will be used; the value following the equal sign specifies the value of this argument. The function definition is not changed at all. It doesnt know whether its receiving real arguments or default arguments; it just does what its told. In main(), the program calls power() both ways, first with one argument and then with two. Figure 4-7 shows how the definition supplies the values of the default arguments.
Figure 4-7 Default arguments I should note that there is a family of C library functions, pow(), powl(), pow10(), and so on, that raise one number to the power of another. However, being written in C, they cannot use function overloading or default arguments.
// set default values void box_display(char ch='-', int n = MAX_LENGTH); }; // line of n characters ch, or default values for n and/or ch C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 181 of 701
void fancy_text::box_display(char ch, int { int j; for(j=0; j<n; j++) // cout << ch; cout << endl << text << endl; // for(j=0; j<n; j++) // cout << ch; } void main() { fancy_text ft1; ft1.set_text("Gone with the Wind"); ft1.box_display(); cout << endl << endl;
n)
ft1.box_display('='); // default length and equal signs cout << endl << endl; ft1.box_display('=', 18); // equal signs; length matches text cout << endl << endl; } There are three ways to call box_display(): You can leave out both arguments, leave out only the second one, or supply them both. Here are examples of these three approaches. Each displays a line of characters, a line of text, and another line of characters: ft1.box_display(); // two lines of 40 dashes ft1.box_display('='); // two lines of 40 '=' characters ft1.box_display('=', 18); // two lines of 18 '=' characters The output from this program is the same as that from OVERFUN: ---------------------------------------Gone with the Wind ---------------------------------------======================================== Gone with the Wind ======================================== ================== Gone with the Wind ================== Note: Note also that the main() part of the program is identical to that of OVERFUN. Ive changed a member in the class from using overloaded functions to using default arguments, but this has not affected the interface between main() and the class.
In this case, the function will not terminate when the user presses but will continue to accept input from the keyboard until the user presses the dollar sign (). The declaration of getline() in the IOSTREAM.H header file, somewhat simplified, looks like this:
istream getline(char, int, char = '\n'); // simplified declaration The third argument is given a default value of '\n, which is the character returned when the user presses . By specifying a third argument, I can change the terminating character to anything I want.
Limitations
Only the arguments at the end of the argument list can have default arguments. If one argument has a default argument, it must be the last one. If two arguments do, they must be the last two, and so on. You cant insert a default argument in the middle of a list of normal arguments because the compiler would lose track of what arguments went where. In other words, you cant say void badfunc(int, int=2, int); // no good because the default argument is not the last one. However void okfunc(int, int=2, int=10); // ok is all right because both default arguments are at the end of the list (i.e., on the right). If you call a function such as okfunc() that has two default arguments and you leave one argument out, it must be the last one. Otherwise, the function wont be able to figure out which value matches up with which argument. You cant change the values of the default arguments midway through your program by using additional function declarations. Attempting to do so will elicit a complaint from the compiler.
When to Do What
Ive explored two ways to make one function name do the work of several. Overloaded functions are actually separate functions with the same name but different numbers and/or types of arguments. A function that has default arguments is only one function, but it can take different numbers of arguments. Overloaded functions are typically used when a similar operation must be performed on different data types. Default arguments are used in cases where a simplified or default version of a function can be used in certain circumstances. Default arguments are also useful in functions that are under development. They can serve as place holders for arguments that are not yet installed in a function definition but are planned for some time in the future. The defaults allow existing function calls to operate correctly, but allow future calls to take advantage of a revised function that uses more arguments.
Quiz 4 1. A default argument is an argument that a. can be of different types in different calls to the function. b. must always have the same value.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 183 of 701
c. can be used, or not used, in a function call. d. will cause control to be routed to different functions, depending on whether its supplied or not. e. can be supplied with a value, but not by the function call. 2. The values for nonexistent default arguments are specified in a. the function body. b. the function declaration. c. the function definition. d. the function call. e. class member data. 3. Given the following function declaration
void afunc(float, int=2, char='x');
which of the following are correct calls to the function? a. afunc(3.14159); b. afunc(19, a); c. afunc(12.2, 19, a); d. afunc(12.2, a); e. afunc(12.2, 19); 4. Which of the following are valid declarations for functions that use default arguments? a. void afunc(char, float=3.14159, int=10); b. void afunc(char=x, float, int); c. void afunc(char ch, float fpn, int intvar=77); d. void afunc(char ch=3.14159, float fpn=3.14159, int intvar); e. void afunc(char, float, int); 5. It is appropriate to use default arguments when a. you want to pass an array of data to a sorting function sort(), but the data can be of any type.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 184 of 701
b. you want a function log() that usually calculates logarithms to the base 10, but you occasionally want to specify a different base. c. you want to average the contents of int arrays of varying sizes. d. the type of the arguments will vary. e. the value of one of the arguments will often be a particular fixed value. Exercise 1
Using the getche() function to read the characters, write your own version of the cin.getline() function described in this lesson. Include the default argument for the terminating character.
Exercise 2
Revise the box_display() function in the fancy_text class so it boxes in the left and right ends of the displayed text as well as on the top and bottom, like this: |====================| | Gone with the Wind | |====================| Add a fourth argument to the function so the user can determine the character to be used for the box ends. Make this a default argument, which, if its not supplied, causes the function to use the character shown in the example output.
Midchapter Discussion
George: Estelle: Don: Overloaded functions, default arguments, what do they buy you? Is it really that hard to remember a bunch of different function names? I dont think its a major feature of the language, like classes or something. Its just a convenience. Right. Remember, with OOP weve got programmers who are class creators and other programmers who are class users. I think overloaded functions is a way to make things a little easier for the class users. If youve got a whole lot of member functions for a new class youre learning about, overloading means youve got fewer names to remember. Does everyone get the difference between function declarations and definitions? The definition is the function, and the declaration is just the instructions to the compiler about how to make a function call. Very good, George. Youre getting sharper and sharper. Well, I read the assignment, for a change. But I still dont see why you dont just put all the function definitions first. Then you wouldnt need any declarations. Lots of programmers like to put the more important functions, such as main(), first and less important ones later. But the important ones tend to call the unimportant ones. Or a program could be divided into different files. When you call a function thats in a different file, then you better have the declaration in your file or the compiler wont know how to handle it. There sure are a lot of ways to use functions. I dont see any reason to use anything but main() and member functions. Thats true in the little example programs weve seen in these sessions. But you need more flexibility in big programs. Thats why youve got to be able to call a function thats in another part of the file or in a different file. If you say so.
Estelle: George: Don: George: Estelle: Don: Estelle: George: Don: George:
manage variables. This can help save memory, make the program run faster, simplify it, and avoid variable name clashes. In this session, Ill explore storage classes and several other fundamental aspects of variable usage. Some of the material in this lesson may seem theoretical or obscure. Dont worry if you dont understand it all. You can come back to this lesson later when questions about storage classes arise in future discussions. Before you learn about storage classes, you need to firm up your understanding of two words: declaration and definition.
Automatic Variables
Automatic variables are declared inside a function, like this: void anyfunc() { int ivar; // automatic variables float fvar; } They are called automatic variables because they are automatically created when a function is called. When the function returns, its automatic variables are destroyed. For the technically minded, automatic variables are pushed onto the stack when a function first starts to execute and popped off when it returns. Thus, the lifetime of automatic variables coincides with the time the function is actually executing. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 186 of 701
Automatic variables are visible only within the function. Statements in any other function cannot access them. Thus, a variable defined as j in one function is a completely different variable than one defined as j in another function. You can use the keyword auto to specify an automatic variable, but because variables defined within a function are automatic by default, there is seldom a reason to do this. Automatic variables are not initialized by default, so unless you initialize them explicitly, they will have a random garbage value when they first come into existence. Figure 4-8 shows the visibility of variables of various storage classes. The wide arrows indicate the parts of the
Register Variables
A register variable is a specialized kind of automatic variable. Register variables require the keyword register. The idea is that the compiler will attempt to place register variables in CPU registers rather than in memory. Because registers are more quickly accessed, they should make a program with commonly used variables more efficient. A typical variable that would profit from this treatment is the innermost loop variable in a set of nested loops. You are usually limited to one or two register variables in a given function. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 187 of 701
Actually, modern compilers are so efficient at figuring outon their ownwhat variables should be register variables that its questionable whether there is much point in specifying this storage class. Ill ignore it in this book. If you have a loop in which your program spends a great deal of time, you might experiment to see if making the loop variables register speeds things up.
Blocks
Automatic variables can be defined not only within a function but also within a block, which is any section of code enclosed in braces. The body of a loop or an if statement is a block, for example. Variables defined within a block have block scope; that is, they are visible only within the block.
External Variables
As youve seen, most variables are declared either as class instance data (usually private) or as automatic variables inside functions. However, youve also seen a few examples of variables declared outside any class or function, most recently the MAX_LENGTH variable in the OVERFUN program in Session 3 in this chapter. Such variables are called external variables. Heres an example: // start of file (e.g., somename.cpp) int xvar; // external variable void afunc() { } class aclass { }; // end of file External variables are visible to all statements throughout the file from the point where the variable is declared onward. The lifetime of external variables is the life of the program. External variables, unlike automatic variables, are automatically initialized to 0, unless they are initialized explicitly.
The variable ivar is defined in file 1, declared in file 2, and not declared in file 3. This variable is thus visible in files 1 and 2, but not in 3. In any program, a variable can have only one definition. It can exist in memory in only one place, and only one statement can cause this memory to be allocated. However, a variable can be declared in many places. In the example above, ivar can be declared in any number of other files, each time using the keyword extern. You can also restrict an external variable so it is invisible in other files even if they attempt to access it using an extern declaration. To do this, use the keyword static, which doesnt really mean static in this context as much as it means restricted scope. // file #1 static int ivar; // definition // ivar can be known ONLY in this file // end of file #1 // file #2 extern int ivar; // useless declaration // end of file #2 Here the compiler will think that ivar refers to some other variable that hasnt been defined yet; when the linker encounters this reference, it will complain that ivar is undefined.
when the function is not executing. Because objects are more powerful and versatile than functions (thats why OOP was invented), it makes sense to use a class instead of a function and static variables. If you find yourself needing to use static variables in a standalone (nonmember) function, its probably time to turn the function into a class by making the static data into instance data.
Objects
Remember that objects are treated almost the same as variables in C++. (The difference is that objects must have a class specification, whereas the specifications for basic types are built into the language.) Objects can be created in any of the storage classes described in Table 4-1: automatic, external, and so on. Like variables, they can be defined or only declared. (The exception is the storage class register, which doesnt make any sense for objects because an object cannot be placed in a register.)
Quiz 5 1. Storage classes are concerned with a. which variable a value is stored in. b. which data type a value is stored in. c. a special class whose objects are variables of basic types. d. whether a variable can retain its value when a function returns. e. how long a variable stays in existence. 2. A declaration ________, whereas a definition _____________. a. may be a definition, can never be a declaration. b. specifies only a name and a data type, also allocates memory space. c. specifies a data type, specifies a name. d. specifies a name, specifies a data type. e. allocates only memory space, also specifies a name and a data type. 3. Instance data exists for the life of a. a block. b. a function. c. an object. d. a file. e. the entire program. 4. Automatic variables are visible only in the __________ in which they are declared. a. class b. function c. object d. file e. program 5. To make an external variable accessible in a file other than the one in which it is defined, a. is impossible.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 191 of 701
b. it must be defined again. c. the keyword extern must be used. d. the keyword static must be used. e. it must be declared.
Due to the largely theoretical nature of this lesson, there are no exercises.
The declaration uses the keyword static. The definition uses the class name connected to the variable name with the scope resolution operator (::). This double-barreled approach to creating a static variable makes some kind of sense if you assume that data defined inside the class specification is always instance data, duplicated for each object. To define a data item that exists only once for the entire class, you define it outside the class, although you declare it inside the class so it will be visible to other class members. The static data member can be initialized when its defined, as I showed here. If its not explicitly initialized, its automatically set to 0. Once its defined, static class data can be accessed only by class member functions; it has class scope.
Static Functions
A static member function is defined using the keyword static. Otherwise, it looks like any other member function. However, function calls to member functions are made without referring to a particular object. Instead, the function is identified by connecting it to the class name with the scope resolution operator. Heres how that looks: class aclass { private: public: static int stafunc() // function definition { // can access only static member data } }; main() { aclass::stafunc(); // function call } A static member function cannot refer to any nonstatic member data in its class. Why not? Because static functions dont know anything about the objects in a class. All they can access is static, class-specific data. You can call a static function even before youve created any objects of a class.
Count-the-Objects Example
Sometimes individual objects in a class, or nonmember functions such as main(), need to know how many objects of a class exist at any given time. A race car, for example, might want to know how many other race cars are left on the track after various crashes and engine failures. The example program creates a class of widgets (small devices of obscure purpose). Each widget is given a serial number, starting with 10,000. The class uses a static variable, total_widgets, to keep track of how many widgets have been created so far. It uses this total to generate a serial number and to place it in the instance variable widget_number when a new widget is created. A static member function is used in main() to access the total number of widgets. This allows the program to access the total before it has created any widgets and again when it has created three. Listing 4-4 shows STATIC. Listing 4-4 STATIC // static.cpp // demonstrates static data and functions #include <iostream.h> class widget { C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 194 of 701
// a widget's serial number // all widgets made so far // NOTE: declaration only
public: void init() // initialize one widget { widget_number = 10000 + total_widgets++; } int get_number() // get a widget's number { return widget_number; } static int get_total() // get total widgets { return total_widgets; } }; int widget::total_widgets = void main() { cout << "Total widgets = widget w1, w2, w3; // w1.init(); // w2.init(); w3.init(); 0; // NOTE: definition
cout << "w1=" << w1.get_number() << endl; cout << "w2=" << w2.get_number() << endl; cout << "w3=" << w3.get_number() << endl; cout << "Total widgets = " << widget::get_total() << endl; } The program calls get_total() before creating any widgets, creates three widgets, numbers them by calling init() for each one, displays each widgets number, and then calls get_total() again. Notice that the nonstatic member function init() can access the static variable total_widgets (it increments it), but static member function get_total() cannot access nonstatic (instance) data. Heres the output of the STATIC program: Total widgets = 0 w1 = 10000 w2 = 10001 w3 = 10002 Total widgets = 3 Ive noted before how clumsy it is to use a function such as init() to initialize each object to a certain value just after its created. Ill fix this in the next chapter, when you learn about constructors.
Quiz 6 1. Which of the following would be reasonable uses for static member data in a class where each object represents an elevator in a high-rise building? a. The location of each elevator. b. A request from the 17th floor to go up.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 195 of 701
c. The number of riders in an elevator. d. The number of elevators in the building. e. Pushing the button for the 17th floor while inside an elevator. 2. A static member variable in a class a. is duplicated within each object. b. does not really exist, in the sense of being defined. c. has only one copy for the entire class. d. is duplicated in each member function. e. represents something about the class as a whole. 3. Static member functions a. must be declared inside the class specification, but defined outside it. b. must be called using an object of the class. c. have only one copy for the entire class. d. can access only static data. e. can access any objects data. 4. Calling a static member function from main() requires a. the dot operator. b. the scope resolution operator. c. no operator. d. the function to be public. e. the class name. 5. In the STATIC program, a. the get_number() function can access static variables. b. the get_total() function can access static variables. c. the get_number() function can access nonstatic variables. d. the get_total() function can access nonstatic variables. e. widget_total can be assigned a new value by a statement in main().
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 196 of 701
Exercise 1
Add a price_per_widget member data variable (type float) to the widget class. Should it be static? Also add a member function that will return the total value of all widgets created to date; call it get_value(). Make this function static.
Exercise 2
Start with the elevator program from Exercise 1 in Chapter 2, Session 1. The elevator class has member functions to display the floor the elevator is on, to move it up one floor, and to move it down one floor. To this program add a static member variable that represents how many floors there are in the building. Modify the existing member functions so they use this value to check whether moving the elevator up or down will cause it to go higher than the highest floor or lower than the lowest floor (which you can assume is 1). Dont move the elevator if these conditions occur.
Swapping Integers
Suppose you have two integer variables in your program and you want to call a function that swaps (exchanges) their values. For example, suppose youve defined two variables this way: int alpha = 2; // initialize two ints int beta = 7;
Passing by Value
Now you want to call a function that will swap 7 into alpha and 2 into beta. Will the following function definition have the desired effect? void swap(int a, int b) // function swaps values??? { int temp = a; a = b; b = temp; } This function swaps the values of a and b. However, if you call this function in the usual way, like this, swap(alpha, beta); // try to swap their values hoping it will swap the values of alpha and beta, you will be disappointed. If you print out the values of alpha and beta after calling the function, you will find that they are unchanged. Why? Because normal function arguments in C++ are passed by value, as I noted. The function automatically creates completely new variables, called a and b in this example, to hold the values passed to it (2 and 7) and operates on these new variables. So when the function terminates, although its true that a has been changed to 7 and b has been changed to 2, unfortunately the variables alpha and beta, in the code that called the function, remain stubbornly unchanged.
Passing by Reference
We need a way for the function to reach back into the original variables (alpha and beta) and manipulate them instead of creating new variable. Thats what passing by reference accomplishes. Syntactically, to cause an argument to be passed by reference, simply add an ampersand (&) to the data type of the argument in the function definition (and the function declaration, if one is needed). Heres how you can rewrite the swap() function to use reference arguments: void swap(int&, int&); // function declaration, // (note ampersands) int alpha = 2; // initialize two ints int beta = 7; swap(alpha, beta); // swap their values (declaration) void swap(int& a, int& b) // function swaps values { // (note ampersands) int temp = a; a = b; b = temp; } If you print out the values of alpha and beta following the call to this version of swap(), youll find that they have indeed been exchanged: alpha is 7 and beta is 2. Note: The ampersands follow the ints in both the declaration and the definition. They tell the compiler to pass these arguments by reference. More specifically, they specify that a is a reference to alpha and that b is a reference to beta. (As you can see, ampersands should not be used in the function call itself.) Figure 4-10 shows how the function swap() operates on the variables alpha and beta in main(); the function has no variables a and b of its own because they are only aliases for alpha and beta.
Swapping Objects
Listing 4-5 shows a complete program, SWAPOBJ, that uses a swap() function to exchange two objects. Ill use the employee class, last seen in the STREMP program in Chapter 3, Session 4. Listing 4-5 SWAPOBJ // swapobj.cpp // function swaps employee objects #include <iostream.h> const int SMAX = 21; // maximum length of strings
class employee { private: char name[SMAX]; // name (20 chars max) int serial_number; public: void input() // get data from user { cout << " Enter name: "; cin >> name; cout << " Enter serial number: "; cin >> serial_number; } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 199 of 701
void output() // display employee data { cout << " Name = " << name; cout << "\n Serial number = " << serial_number; } }; void main() { void swap(employee&, employee&);
// declaration
employee e1, e2; cout << "Enter data for employee 1" << endl; e1.input(); cout << "Enter data for employee 2" << endl; e2.input(); cout << "Swapping employee data"; swap(e1, e2); // swap employees cout << "\nData for employee 1" << endl; e1.output(); cout << "\nData for employee 2" << endl; e2.output(); } // end main() void swap(employee& emp1, employee& emp2) // definition { employee temp = emp1; emp1 = emp2; emp2 = temp; } In main(), the program creates two employee objects and asks the user to supply the data for them. Then it calls the swap() function to exchange the data in the two objects. This works because the objects are passed by reference. Finally, the program displays the data. Heres some interaction with this program. (You cant use multiple-word names because I simplified the input routine in the interest of brevity.) Enter data for employee 1 Enter name: Bernard Enter serial number: 111 Enter data for employee 2 Enter name: Harrison Enter serial number: 222 Swapping employee data Data for employee 1 Name = Harrison Serial number = 222 Data for employee 2 Name = Bernard Serial number = 111 Notice that the swap() function doesnt need to be a member function of employee because it doesnt deal with private employee instance data.
Standalone References
Lets perform a little experiment to clarify what references are. So far, youve seen references used only as function arguments. References can also be used as standalone variables. (Although its not clear why you would want to do this in a real program.) C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 200 of 701
First, Ill create a variable of type int, initialized to a value: int intvar = 7; Now Ill create a reference: int& intref = intvar; // make a reference to intvar When you create a reference you must also, in the same statement, initialize it to the variable it refers to. Here I specify that intref is a reference to intvar. If you dont initialize a reference, the compiler will complain. (This initialization is handled automatically for function arguments that are references, as you saw above.) What happens if you now display the values of the original variable and the reference? cout << "\nintvar=" << intvar; // 7 cout << "\nintref=" << intref; // 7 The numbers in the comments show the output. Both statements print the same value. This isnt surprising because they are actually referring to exactly the same variable. If you change the value of the original variable and display itsand the referencescontents, youll see theyve both changed: intvar = 27; // assign value to variable cout << "\nintvar=" << intvar; // 27 cout << "\nintref=" << intref; // 27 I can also turn things around and use the reference to assign a new value to the variable: intref = 333; // assign value to reference cout << "\nintvar=" << intvar; // 333 cout << "\nintref=" << intref; // 333 This again causes the same output from both statements because both the reference and the original variable name refer to the same part of memory. After its defined and initialized, you cannot change a reference to refer to a different variable. If you try to do this, the compiler will think youre simply trying to assign the reference (and the variable it already refers to!) to the value of the new variable: int intvar2 = 3; intref = intvar2; // gives intref (really intvar) the value 3 In any case, such arcane matters are hardly worth pondering because, as I noted, the use of references is almost always restricted to passing arguments to functions and returning values from functions. In this context, references are essential to creating overloaded operators and copy constructors, which youll be meeting later on.
Quiz 7 1. A reference is
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 201 of 701
a. the contents of a variable. b. the address of a variable. c. another name for a data type. d. another name for a variable. e. a recommendation, usually laudatory. 2. When you declare a reference, a. the variable used to initialize the reference must itself already be initialized. b. the variable used to initialize the reference must itself be declared. c. it must have the same type as the variable used to initialize it. d. you use the ampersand following the data type of the reference. e. you use the ampersand following the name of the reference. 3. Passing function arguments by reference allows the function to a. receive values passed from the function call, but not alter them. b. perform input/output. c. operate on variables that are not defined within the function. d. access the variables named in the function definition. e. access the variables named in the function call. 4. If you have a reference floref to the variable flovar and you execute the statement
floref = 12.7;
you are actually a. telling floref to refer to a different variable. b. causing no change to the contents of any variable. c. assigning the value 12.7 to floref. d. assigning the value 12.7 to flovar. e. creating two different copies of 12.7 in memory, one for floref and one for flovar. 5. A standalone reference (one not used for function arguments) a. must be initialized, usually to a variable.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 202 of 701
b. must be initialized, usually to a value. c. must be initialized, usually to a function. d. is used for function arguments. e. is seldom used. Exercise 1
Write an ordinary (nonmember) function called divrem() that takes three arguments of type int. This function should divide the first argument by the second, return the quotient (the answer; which is also type int), and send the remainder back using the third argument, which is passed by reference. A call to such a function might look like this: quotient = divrem(dividend, divisor, remainder);
Exercise 2
Write a member function called move() for the employee class that, instead of exchanging two employee objects as the swap() function did in this session, moves the contents of one object to another. That is, the function will set its first argument equal to its second argument and then set the second argument to an empty employee. An empty employee has the name (the null string) and a serial number of 0. The original value in the first argument will be lost. To simplify the programming, you can have the user enter the values for the empty employee.
return data; } }; void main() { omega om1; // make an object om1.getnset() = 92; // give it a value (!) cout << "om1 = " << om1.getnset(); // get the value and display it } In main(), the getnset() member function is called twice. The second call is a normal one: The function call goes on the right side of the equal sign and it returns the value from the instance variable data, which is then displayed. However, the first call to getnset() is placed on the left side of the equal sign in the expression om1.getnset() = 92; What does this do? It assigns the value 92 to the variable data in the om1 object. Two things are necessary for this to happen. First, the function getnset() must be defined as returning by reference. This is accomplished by inserting an ampersand following the data type of the return value: int& getnset() Second, the function must return the variable data, which is the variable I want to set to a value. The statement return data; takes care of this. The arrangement is shown in Figure 4-11.
Figure 4-11 Value returned by reference The return value of 92 goes from main() to the function, the opposite direction from normal return values, which are passed back from the function to main(). The output of the program is om1 = 92 which shows that genset() can both set an object and get its value.
Cautions
Dont try to return an automatic variable (one defined within the function) by reference. Such variables cease to exist when the function terminates, so you would be assigning a value to some random location in memory. For example, dont write a member function like this: int& badret() // returns by reference { int somevar; // local variable C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 204 of 701
return somevar; // don't do this } If in main() you then said something like anObj.badret() = 1066; you would be assigning somevar a value, but somevar would have already been destroyed because the function had returned. Fortunately, the compiler wont let you get away with this.
Whats It For?
Returning by reference was created to solve a particular problem in C++: how to handle return values from overloaded operators. It probably doesnt have much use otherwise, because there are more straightforward ways to set an objects instance data to a value. However, for those cases where it does make sense, its invaluable. Ill use this technique when I explore overloaded operators in Chapter 6.
Quiz 8 1. Anything you place on the left side of the equal sign must be a variable because a. the variable shouldnt go on the right side, where constants go. b. only a variable can be given a value. c. only a constant can be given a value. d. functions are always variables. e. only variables can be equal to anything. 2. Syntactically, returning by reference involves inserting a. in the function definition an ampersand following the data type of the return value. b. in the function definition an ampersand following the name of the return value. c. in the function call an ampersand following the name of the return value. d. in the function definition a statement that returns the variable whose value is passed to the function. e. in the class instance data a variable that can be assigned a value. 3. An lvalue is a. always an object. b. always a constant. c. always a variable. d. a variable that must appear on the left side of an equal sign. e. anything that can appear on the left side of an equal sign. 4. You cant return a local variable by reference because
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 205 of 701
a. the local variable is unknown to the return statement in the function. b. the local variable is unknown to the statement calling the function. c. local variables become inaccessible when the function terminates. d. local variables cease to exist when the function terminates. e. local variables are private. 5. Returning by reference is usually used in relation to a. objects. b. arguments passed by reference. c. overloaded functions. d. overloaded operators. e. standalone member functions.
There are no exercises for this lesson because returning by reference cant be used for anything meaningful until you learn about overloaded operators.
Summary: Chapter 4
Functions are used for a variety of purposes. A function is basically a section of code that can be called from elsewhere in the program. This saves memory space. However, functions are also used to divide a program into more manageable sections. Whereas member functions in classes are a key way to organize C++ programs, standalone functions divide up parts of the program that are not parts of objects. Overloading allows several functions to have the same name. These functions are distinguished by the number and types of their arguments. Default arguments enable a single function to act as if it were several functions. Arguments not supplied by the function call are supplied by the function declaration. Storage classes determine the lifetime and visibility of variables. The lifetime is the period of time when a variable is in existence, whereas visibility (or scope) is concerned with which other parts of the program can access the variable. Storage classes include automatic, register, and local static within functions, external and static external outside of functions, and class data and functions. A reference is an alias for a variable name. References are mostly used to pass arguments to functions. In this system, an alias to a variable in the calling program is passed to the function, rather than a value. This allows the function to operate on the variables in the calling program and avoids the need to copy the arguments value, which may require much time and space if the argument is a large object. Returning values by reference is more esoteric and enables a function call to be used on the left side of the equal sign. This will be important later for overloaded operators.
End-of-Chapter Discussion
Estelle: Don: George: Estelle: I think weve spent the last half of this chapter basically learning how to fine-tune a program. Thats a good way to put it. You dont desperately need all these storage classes and references unless you want to make your program more efficient. Huh? Its all a question of trade-offs. If a variable stays around too long, it takes up too much memory. So automatic variables disappear when the function returns. But you need some variables to last longer so different functions can access them. Thats why variables can be local static, external, or external static. That way they can last the life of the program. Why not just have one life-of-the-program category? Page 206 of 701
George:
So you can avoid name clashes. Sorry, I dont follow you. You want to be able to use the same name for different variables in different places in your program. Otherwise, you could use j only for a loop variable in one function in the program. Again, the bigger the program the more useful that kind of thing is. Umm. Well, one things for sure. Count me out on references. I didnt get that at all. References are really just another way to optimize your program. With a reference, the function can manipulate the variables in the function that called it without having to make them external variables. And the point of that is that you dont have to copy reference arguments when you pass them, which is faster and saves memory. Which is especially important with objects, because they can be so big. I thought it was so the function could change variables in the code that called it. That too. What about static members? More of the same. If you didnt have static members, youd need to use external variables, and that would spread class data out all over the place, which is what classes were supposed to avoid in the first place. Really, Im so glad you guys understand all this. If you dont like that explanation about references, remember theyre going to be used later for overloaded operators. Im sure itll all be clearer then. I can hardly wait
CHAPTER 5 CONSTRUCTORS
In this chapter Im going to discuss constructors, which are special member functions used to create and initialize objects. Ill also briefly demonstrate their counterpart, destructors, which carry out clean-up tasks when an object is destroyed. Ill show constructors that take no arguments and constructors that do, and a special case: the copy constructor, which takes one argument that is a member of its own class. Along the way, youll encounter some other C++ features, such as const.
Initialization
In these programs, the use of a function such as init() was somewhat suspect. Whats wrong with using init()? First of all, its possible for the class user to forget to call the init(), thereby leaving an object uninitialized. Second, initialization should be a one-step process, just as it is when you define an external variable and its set to zero automatically. But when you use init(), you have a two-step process: creating the object and then initializing it. This may seem like a theoretical distinction. However, initializing an object automatically when its created can pay big dividends in making a program more error free and more understandable. The role of the user-written constructor, then, is to carry out any initialization thats necessary for the object. When you define an object in your program, like this: aClass anObj; // make an object C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 207 of 701
the compiler arranges for the class constructor to be called when this statement is executed.
Creation
Whether youve written an explicit constructor or not, the compiler generates the code necessary to create the object, allocate memory for it, and perform other kinds of behind-the-scenes initialization. This happens automatically, just as it does for variables of basic types such as int. Confusingly, the term constructor is often used to refer not only to the user-written initialization function, but also to this compiler-generated behind-the-scenes activity.
Destructors
Every object that is created must eventually be destroyed. Automatic objects (those created within the body of a function) are destroyed (automatically!) when the function returns. This is true of main(), just as it is with other functions. Conversely, objects declared as external or static variables are destroyed only when the program ends. The class creator can write an explicit destructor for a class. This destructor typically takes care of deallocating whatever was allocated by the constructor. Think of a destructor as the objects last will and testament. Whether the programmer writes a destructor or not, the compiler supplies a routine to deallocate the memory occupied by an object and to perform other clean-up operations when the object is destroyed. This code is often included in the meaning of the term destructor.
No Return Value
Constructors have no return value, not even void. Its hard to imagine what a constructor would return, and to whom, because its called automatically. The same is true of destructors. (Because a destructor is called to do away C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 208 of 701
with an object, its hard to imagine what message a destructor would return to the code that called it. Maybe Youll be sorry!)
What They Do
In the CONDEST example, the constructor doesnt actually initialize anything. It simply displays a message announcing that its executing. Likewise, the destructor doesnt clean up anything, it displays a message. Heres the output when you run this program: Starting main() I am the constructor I am the constructor I am the constructor Ending main() I am the destructor I am the destructor I am the destructor Notice that there are no function calls in main(), or anywhere else, to the constructor or destructor. They are called automatically. The constructor is called when each object is created. This happens three times in the statement Omega om1, om2, om3; When main() ends, by dropping through the closing brace, the three objects go out of scope and are automatically destroyed. The destructor is called for each one just before its destroyed, as can be seen from the output. Notice that, like messages from the grave, these announcements from the destructor appear after main() has terminated.
Initializing Variables
Lets look at a more realistic example, where a constructor actually performs a useful activity. Ill rewrite the STACK1 program from Chapter 3, Session 2, to use a constructor instead of the init() function. Listing 5-2 shows STACKCON. Listing 5-2 STACKCON // stackcon.cpp // class models a stack; uses a constructor #include <iostream.h> class Stack // a stack holds up to 20 ints { private: int st[20]; // integers are stored in array int top; // index of last item pushed public: Stack() : top(-1) // constructor { } void push(int var) // place an item on the stack { st[++top] = var; } int pop() // remove an item from the stack { return st[top--]; } }; void main() { Stack s1; // create a stack object // (no need to initialize it) s1.push(11); // push 3 items onto stack s1.push(12); s1.push(13); C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 209 of 701
cout << s1.pop() << endl; // pop 3 items and display them cout << s1.pop() << endl; cout << s1.pop() << endl; } Notice that I have not only eliminated the init() member function, Ive also eliminated all the calls to it that I needed in main() in STACK1. The constructor handles the initialization automatically, which is a considerable convenience.
Initialization List
The purpose of the constructor is to set the top variable in each object to -1 when the object is first created. You might expect code like this: Stack() // constructor, incorrect approach { top = -1; } This version of the constructor actually does the job, at least in this particular program. However, it is not the preferred approach to initializing variables within the object. Some variables cannot be initialized with ordinary assignment statements. As a somewhat recursive example, suppose that one of the member variables in your class is an object from some other class. This object should be initialized with a constructor, not a separate function such as init(), so you dont even have the option of initializing by calling a member function. Instead, a new syntax is used that causes actual initialization (rather than assignment) of specified variables. A colon follows the class name and the variables to be initialized are listed following the colon (separated by commas, if there is more than one). This is called the initialization list (or initializer list). The value used to initialize each
variable on the list is placed in parentheses following the name, as shown in Figure 5-1. Figure 5-1 Syntax of constructor initialization In this example, the constructors declarator initializes the variable top: C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 210 of 701
Stack() : top(-1) // creates and initializes top { } The body of the constructor is empty because all the initialization has already been taken care of on the initialization list. An empty function body is not unusual in a constructor. However, many constructors carry out other tasks in the function body. Also, some variables, such as arrays, cannot be initialized in the initialization list. I didnt define a destructor for the Stack class because the constructor doesnt allocate any system resources or do anything else that needs unusual cleaning up.
Default Constructor
A constructor that takes no arguments, such as the one in the STACKCON program, is called a default constructor. As youll see in the next session, other constructors do take arguments.
Quiz 1 1. A constructor will typically _______ when an object is created. a. allocate system resources b. free system resources c. initialize instance variables d. destroy instance variables e. cause main() to terminate 2. A constructor is preferable to a function like init() because a. it has a unique name. b. the programmer doesnt need to remember to call the constructor, as is true with init(). c. the constructor executes before the object is defined. d. some instance variables must be initialized rather than assigned a value. e. the constructor has no return value. 3. Destructors typically a. deallocate system resources. b. initialize instance variables. c. return zero if all went well. d. are called when the class specification is no longer visible. e. are executed automatically when their object is destroyed. 4. The function call to a constructor a. looks like any function call, except there is no return value.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 211 of 701
b. may take arguments, as in aclass(2, 4);. c. creates but cannot initialize an object. d. is made automatically when an object is defined. e. happens whenever an objects instance data changes. 5. The preferred syntax for initializing an instance variable, using a constructor, a. uses an assignment statement in the function body. b. uses parentheses to surround the value. c. inserts the variable name following a colon that follows the function body. d. actually assigns the variable a value, as opposed to simply initializing it. e. can be used for only one variable. Exercise 1
Start with the TIME1 program from Chapter 2, Session 5. Add a constructor to the airtime class that initializes both the hours and minutes member variables to zero. This will help avoid errors when using the class, because an object will always have a valid time value when created. Without the constructor, if you used display() without first using set(), impossible time values would be displayed, such as 14203:2491.
Exercise 2
Start with the STATIC program from Chapter 4, Session 6. Change the init() function to a constructor. (Note that you can do simple arithmetic in the expression that initializes an instance variable in the constructor.)
A Two-Argument Constructor
Remember the airtime class from the TIME1 program in Chapter 2, Session 5? (You should; its prominently featured in Exercise 1 of the last session.) Lets see how to use constructor arguments to initialize an airtime to any specified value when its created. Listing 5-3 shows TIMECON. Listing 5-3 TIMECON // timecon.cpp // class models time data type; uses constructor #include <iostream.h> class airtime { private: int hours; // 0 to 23 int minutes; // 0 to 59 public: // two-argument constructor airtime(int h, int m) : hours(h), minutes(m) { C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 212 of 701
} void display() { cout << hours << ':' << minutes; } }; void main() { airtime t1(7, 54);
// initialize t1
cout << "\nt1 = "; t1.display(); // display t1 } The output from this program is t1 = 7:54 Ive successfully initialized t1 to an airtime value. Because an airtime object has both hours and minutes components, a two-argument constructor is required. Arguments look much the same in the constructor definition as they do in any other function. The data type is followed by a name and the arguments are separated by commas: airtime(int h, int m) // looks like an ordinary function Of course, you can tell this is a constructor because theres no return type.
A No-Argument Constructor
If you dont explicitly define any constructors, the compiler automatically generates a default constructor for you, that is, a constructor that takes no arguments. This is why its called the default constructor. I relied on this automatic creation of a default constructor in the program examples in previous chapters. For example, there is no problem saying employee emp1, emp2; even if I have not explicitly defined any constructors at all for the employee class. However, once youve defined even one kind of constructor, no matter how many arguments it takes, the compiler will no longer create the default no-argument constructor for you. You must do it yourself. Suppose I rewrite main() in TIMECON this way, without changing the class: void main() { airtime t2; // can't do this // without a no-arg constructor airtime t1(7, 54); // initialize t1 cout << "\nt1 = "; t1.display(); // display t1 } This would cause an error like C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 214 of 701
Could not find a match for airtime::airtime() because there isnt any no-argument constructor to handle the creation of t2. (The airtime::airtime() format means the no-argument constructor of class airtime.) The compiler does not supply a default constructor because theres another (two-argument) constructor. However, its easy to create a no-argument constructor. It could look like this: airtime() { } Although it doesnt do anything, this constructor tells the compiler that I did not forget to write the constructor, I know what Im doing, and not to worry.
Quiz 2 1. Using a constructor with arguments (as opposed to the default constructor) allows you to create objects a. and at the same time specify what class they are. b. with their own constructors. c. whose instance data can be initialized to values supplied by the class user. d. and modify their existing instance data. e. and assign values to the programs external data variables. 2. Which of the following make(s) sense as an object definition? a. horse george; b. horse george(hands_high); c. horse george(float hands_high); d. point3d p(int x, int y, int z); e. point3d p(0, 0, 0); 3. Which is the preferred two-argument constructor for class X? a. X(int v1, float v2) { } b. X(int v1, float v2) : var1(v1), var2(v2) { } c. X(int v1, float v2) : var1(v1), var2(v2) { var1=v1; var2=v2; } d. X(int v1, float v2) { var1=v1; var2=v2; } e. X(int v1, float v2) : { var1(v1), var2(v2) } 4. Calling a constructor with arguments is implemented by a. a normal function call.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 215 of 701
b. a normal function call, but with no return value. c. a normal function call with arguments but no return value. d. the definition of an object. e. the specification of a class. 5. Which complete list of constructors, whose definitions you insert in a class, will compile properly if you create objects using the syntax
aclass anobj;
a. no-argument constructor, one-argument constructor b. one-argument constructor c. no-argument constructor, two-argument constructor d. one-argument constructor, two-argument constructor e. no constructors at all Exercise 1
Write a two-argument constructor for the employee class in the STREMP program in Chapter 3, Session 4. It should allow you to define an employee object and at the same time initialize it with appropriate values for the name and serial number. Ask yourself whether, within the constructor, there is a difference in the way you should set name and serial_number to the values passed as arguments.
Exercise 2
Start with the CARDARAY program from Chapter 3, Session 8. Change the init() function to a two-argument constructor, which should have an empty function body. Remove all the references to an array of type card in main() and test the constructor by initializing a few cards and displaying them. The initializations should look like this: card c1(jack, diamonds);
Conversions
As a barebones example, lets look at a class called typeA. This class has a constructor that takes a single argument of the basic type int. Heres how that looks: class typeA { typeA(int i) // 1-argument constructor { // convert the int value to a typeA value } }; This constructor can be used to convert a variable of type int to an object of class typeA. Remember, saying a variable is of a certain data type is the same as saying an object is of a certain class. There are two ways to write such a conversion. The most familiar way is for the typeA object to be initialized with an int value in parentheses following the name of the object: void main() { int intv = 27; // an integer with a value typeA ta(intv); // initialize typeA object with int } This causes the one-argument constructor to be called. Alternatively, you can use an equal sign instead of the parentheses: typeA ta = intv; // exactly the same as typeA ta(intv); This is completely equivalent to the syntax with parentheses. Although it looks like an assignment statement, the equal sign is not assigning anything; its initializing ta to the value of intv. It also causes the one-argument constructor to be called. A real assignment statement, ta = tb; does not cause a constructor to be called. Initialization is not the same as assignment. This is an important distinction, as youll see later.
one-argument constructor that can be used to convert an ordinary null-terminated string to an xString object. Listing 5-4 shows STRCON. Listing 5-4 STRCON // strcon.cpp // class models strings; uses constructors #include <iostream.h> #include <string.h> // for strlen(), strcpy(), etc. const int MAX = 80; // maximum length of xStrings class xString { private: char str[MAX]; // ordinary C string public: xString() // no-arg constructor { strcpy(str, ""); // make null string } xString( char s[]) // 1-arg constructor { strcpy(str, s); // initialize with string } void input() // get string from user { cin.get(str, MAX); } void display() // display string { cout << str; } void append(xString xs) // append argument string { if(strlen(str) + strlen(xs.str) < MAX-1) strcat(str, xs.str); else cout << "\nError: xString too long" << endl; } }; void main() { xString s2, s3; // make xString objects xString s1("Greetings, "); // make and initialize s1 cout << "Enter your name: "; s2.input(); // get s2 from user s1.append(s2); // append s2 to s1 s3 = s1; // set s3 to s1 s3.display(); // display s3 } This program is similar to STRCLASS. Heres some sample interaction with it: Enter your name: Kubla Khan Greetings, Kubla Khan However, STRCON uses a one-argument constructor instead of an init() function to give an xString object a string value. In the definition xString s1("Greetings, "); the C string Greetings is converted to an xString called s1. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 218 of 701
Notice that, as I mentioned earlier, I need to define a no-argument (default) constructor explicitly because I also define a one-argument constructor. The no-argument constructor simply copies the null string (a string of zero length) to its object. Its probably a good idea to use the no-argument constructor to initialize the xString. If you use the display() member function on an object thats been initialized to the null string, nothing will be printed; whereas, in the STRCLASS program, displaying an uninitialized xString would produce a garbage string value, such as x!8*tEv$#^%. Of course, the two xString constructors in STRCON have the same name. This is simply another example of function overloading. As long as the number or type of arguments is different for each one, you can have as many constructors as you want. Note: Note that you cant initialize the str instance data in the one-argument constructor: xString( char s[]) : str(s[]) { } // cant do this -// s is an array
Because youre dealing with an array, you must copy its elements one by one using the strcpy() library function: xString( char s[]) // 1-arg constructor { strcpy(str, s); // initialize with string }
{ cout << feet << "\'-" << inches << '\"'; } }; void main() { English E1(5, 2.5); cout << "\nE1 = "; E1.display(); English E2(2.0); English E3; E3 = E2; cout << "\nE3 = "; E3.display(); }
Escape Characters
The single quote () and the double quote () are special characters, used to delimit characters and string constants respectively, so they cant be displayed as normal characters without being preceded by the escape character (\). cout << feet << "\'-" << inches << '\"'; Thus, \ causes a single quote to be displayed (indicating feet) and \ causes a double quote to be displayed (indicating inches).
Quiz 3 1. The purpose of data conversion is a. to display data in different ways. b. to exercise the one-argument constructor.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 220 of 701
c. to convert a class of one type to a class of another type. d. to convert a value of one type to a value of another type. e. to convert a variable of one type to a variable of another type. 2. A one-argument constructor is the most appropriate constructor for data conversions because a. the no-argument constructor can work only with the value zero. b. you want to convert the value of a single variable into another variable. c. the two-argument constructor can be used only with feet and inches. d. it can convert an object to a basic type. e. it doesnt require a no-argument constructor to be defined. 3. Converting a C string to an xString requires a. a C string constructor. b. a C string object. c. a C string value. d. an xString constant. e. an xString variable. 4. The two-argument constructor in the English class converts a. feet and inches to English. b. feet and inches to meters. c. English to meters. d. English to English. e. English to feet and inches. 5. Converting from meters to an English distance value a. requires you to multiply the number representing the value of the English object by 3.280833. b. requires you to multiply the feet part of the English object by 3.280833. c. is more complicated than a. or b.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 221 of 701
Exercise 2
Change the one-argument constructor in the ENGLCON program in this session so that it accepts a floating-point quantity in inches rather than meters and converts it into a feet and inches English value.
}; This works, but its not in the spirit of OOP because it doesnt encapsulate the data for the class within the class specification. Ideally, the variable size should be declared within the class so it is invisible outside the class and therefore impervious to accidental alteration by other parts of the program.
class Stack { private: const int size; // legal int st[size]; // illegal--not known at compile time int top; public: Stack() : size(20), top(-1) // size initialized here { } }; The trouble is that the size of an array must be known to the compiler at compile time. Its not something the program can figure out for itself when its running. So I cant use normal instance data, whether its const or not, for an array size.
{ private: char day_name[DPW][MAX] = { "Sunday", "Monday", }; // no good }; In the particular situation of the WEEKOUT program, the array should be static, because its the same for all objects and theres no use duplicating it in each object. When an array is static, it can be initialized in its definition (not the declaration): class weekday { private: static const char day_name[DPW][MAX]; // declaration }; const char weekday::day_name[DPW][MAX] = // definition { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; This approach solves this particular problem. Ill leave it as an exercise to put this together into a working program.
Quiz 4 1. Constants such as 20 should be avoided in your program listing because a. such numbers have no specified data type. b. their lifetime is too long. c. if you change one 20 then you must change any others as well. d. the number itself isnt very informative about its purpose. e. constants are visible throughout the program. 2. External variables should not be used to supply constant values for use in class member functions because a. their visibility does not correspond to that of the class. b. their lifetime does not correspond to that of objects. c. they cant be initialized at compile time. d. they cant be initialized at all. e. they cant be made const. 3. You cant use a statement such as
const int size = 20;
a. the assignment operator cant be used to give values to data. b. a constructor must be used to initialize instance data. c. const variables cant be initialized. d. const variables must be initialized at compile time. e. const variables must be initialized outside the class. 4. Why cant you use const instance data to size an array? a. const variables can never be used to size an array. b. Array sizes must be declared outside the class. c. Arrays must be declared outside the class. d. Arrays are sized at compile time. e. Arrays arent sized until after the data is declared. 5. The enum hack a. associates a name and a value when an object is defined. b. uses the name of a value of an enum variable to name a constant. c. uses the name of an enum variable to name a constant. d. uses the value of an enum variable as a constant. e. cannot be used to size an array that is not instance data. Exercise 1
Rewrite the STRCON program from Session 3 in this chapter so that it uses a static constant value for the array size MAX.
Exercise 2
Rewrite the WEEKOUT program from Chapter 4, Session 2, so the array day_name is declared inside the class. How will you insert the day names into the array? You should make the array type static, because it is shared by all objects. This has repercussions on the variables used for the array size: DPW and MAX. Where and how should they be declared?
Midchapter Discussion
George: Don: I cant believe I spent so much time on constructors. All they do is initialize objects. It shouldnt be that much more complicated than initializing an int. Well, whereas an int just has one value, objects can have many data items and you may want to initialize different data items in different ways. Thats why C++ lets you write your own constructors: It gives you complete control over the initialization process. I can see that. Its not like basic types, where you either initialize a variable to a certain number or you dont. What I didnt get was the conversion function thing. Page 227 of 701
Estelle:
Don:
With basic types, all that conversion stuff happens automatically. The compiler knows how to convert int to float and you dont need to think about it. But if you want to convert int to airtime, the compiler cant do it because its never heard of airtime values. So you need to write a one-argument constructor. Why a constructor? Why not just a function? Good question. Well, I guess you could use any old function for conversions, but the constructor makes it easier to create a variable and initialize it to some simple value at the same time. Its really just a convenience. Isnt it a safety feature, too? It forces the class user to remember to initialize every object. Right. Thats what I meant. You know, Im not sure I see the advantage of this English class having separate numbers for feet and inches. Why not just store one number, representing fractional feet? Or fractional inches. Hmm. Well, suppose you have, like, 6 feet 4 inches. You can represent that exactly if you store both those numbers. But if all you store is 6.333333 feet, then its not a nice round number any more and you get round-off errors. Big deal. Who cares about a ten-thousandth of a foot? I guess you could do it with a single number if you wanted, but it wouldnt be as pretty. Also youd need to convert back and forth whenever there was input or output. It would be slower for some functions. I hope I never hear another word about constructors. Good luck.
Copying Variables
Youve seen numerous examples where I created an object or a variable from scratch. Where before there was no object, now suddenly there is one: sigma sigobj; // create an object of class sigma The data in the object is initialized to zero or other appropriate beginning values. This is the familiar approach to object creation, but theres another one. You can also create an object thats a copy of an existing object. Do this with variables of basic types without thinking too much about it: int alpha = 27; // create an int with value 27 int beta = alpha; // create another int that's a copy of beta With basic types, copying means making a new variable of the same type and giving it the same value. Its not too complicated because theres only one value to worry about and its always copied as is into the new object. Remember that in the two statements above, the equal signs are not assignment operators. The equal signs in these statements dont mean Im assigning a value to an existing variable, they mean Im initializing a variableas its being createdto a value. There is an important distinction between assignment and initialization.
Equivalent Syntax
As I noted, a different but equivalent notation can be used for copying one variable to another. I can also say int beta(alpha); // copy alpha to beta This has exactly the same effect as int beta = alpha; // copy alpha to beta However, the fact that there is no equal sign helps avoid confusion between assignment and initialization. This is sometimes called functional notation. (Its also used in constructors, in the initialization list, as youve seen.)
Copying Objects
You can copy objects as well as variables of basic types: C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 228 of 701
sigma sigalpha; // create sigalpha sigalpha.getdata(); // put some data in sigalpha sigma sigbeta(sigalpha); // create sigbeta by copying sigalpha Here I create sigalpha and give it some data by calling the member function getdata(). Then I copy sigalpha to sigbeta. Another way of saying this is that I create sigbeta and initialize it with sigalpha. Whatever data was placed in sigalpha by the getdata() function is presumably transferred automatically into sigbeta when its initialized. I could say that Ive cloned the sigalpha object. Figure 5-4 shows how this looks.
Youll see how to do this in a moment. First, however, I must digress briefly to show how function arguments can be const. This is important because copy constructors typically use a const argument.
Passing by Reference
However, as Ive noted, creating extra copies of an object, which may be arbitrarily large and complex, is not always a great idea. Unnecessary copies of large objects may use too much memory, and the copying process itself takes time and in some cases may introduce other problems as well. For these reasons, its often preferable to pass objects by reference (see Chapter 4, Session 7). This is the case in copy constructors, as youll see. One of the advantages (at least sometimes) of passing by reference is that the function can modify the arguments in the function call. But suppose you dont want to modify them. Maybe you want the other advantages of passing by reference, but dont want to give up the guaranteed safety of the original arguments that you get automatically when passing by value. You can, when passing by reference, reinstate the guarantee that the original arguments wont be modified. All you need to do is declare these arguments as const. Listing 5-7 shows PASSCON, which shows how that looks for arguments of basic types. Listing 5-7 PASSCON // passcon.cpp // demonstrates passing const variables class omega { private: int omdata; public: void getdata(const int& i) // guarantees not to change i { omdata = i; // this is fine // i = 44; // error: can't modify const variable } }; void main() { omega om1; int ivar = 77; // I don't want getdata() to modify ivar om1.getdata(ivar); // perfectly safe } I want to guarantee that the getdata() member function, when called from main() with ivar as a reference argument, will not modify ivar. To do this, I make the argument const in getdata(): void getdata(const int& i) // guarantees not to change argument { } Here, although ivar is passed by reference and given the alias i in the function, getdata() cant modify it, because its const. Using const for reference arguments is an important technique for avoiding program bugs. You should always make reference arguments const unless there is a specific reason for not doing so (perhaps you want to modify the original variables).
Passing by Value
Notice that there is no point in using const for arguments that are passed by value. Passing by value is already a guarantee that the original variable is safe because the original variable isnt passed at all; the function has access only to its own copy of the variable. Using const would be gilding the lily.
If you checked the contents of the new objects, om2 and om3, following their creation, you would see that they both have intvar values of 27. You might wonder, if the compiler will make copy constructors for you, why worry about them at all? In this simple example, the user-written copy constructor doesnt accomplish anything the compiler-generated (default) version would not have. However, as youll see in the next session, there are times when a smart copy constructorwhich does things the default cannot dois necessary.
Quiz 5 1. A copy constructor a. takes no arguments. b. takes one argument. c. takes an arbitrary number of arguments. d. creates a new object that later may be assigned the data of an existing object. e. creates an object initialized with the same data as an existing object. 2. The statement aclass obj2 = obj1; causes _____________ to be executed. a. no constructor b. a one-argument constructor that must be written by the user c. a one-argument constructor that must copy data d. the copy constructor e. an assignment operator 3. To prevent the possibility that a function will modify the object or variable passed to it as an argument, a. the argument may be passed by value. b. the argument may be made const and passed by reference. c. the function may be made const. d. the function may not refer to the variable.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 232 of 701
e. the function call must use the keyword const. 4. A copy constructor is always called when a. an object is initialized with another object of the same class when its created. b. an object is initialized with another object of any class when its created. c. an object is initialized with a variable of a basic type when its created. d. an object is not initialized when its created. e. an object is returned from a function by value. 5. The argument to a class copy constructor must be passed by reference because otherwise a. the copy constructor would not return. b. the integrity of the argument object could not be guaranteed. c. the integrity of the object being created could not be guaranteed. d. too many objects would be created. e. the copy constructor could not be identified by the compiler. Exercise 1
Write a copy constructor for the airtime class from Exercise 1 of Session 1 in this chapter. Have it perform a memberwise copy of all data. Write statements in main(), using two different syntax, that exercise this copy constructor. Do you really need an explicit copy constructor in this situation?
Exercise 2
One reason for an explicit copy constructor is to check that the arguments used to initialize the new object have reasonable values. Modify the copy constructor in Exercise 1 so it checks that the hours and minutes values are within appropriate ranges and displays error messages if they are not. While youre at it, modify the constructor to perform the same check.
This shows that, if you write your own copy constructor, you can pretty much do whatever you want to intialize the instance data in the new object. You can copy data directly from the old object or you can modify the data or you can generate entirely new data.
Initialization List
Remember that the strange-looking initialization list syntax in the constructor: omega(char str[]) : number(++total) <--initialization { } has almost the same effect as would a similar statement in the function body: omega(char str[]) { number = ++total; <--assignment } However, the first construction initializes number to a value, whereas the second assigns number a value.
A Variation on strcpy()
Incidentally, Ive used a new library function, strncpy(), to copy the name from the constructor argument to the object. This function is similar to strcpy(), but it includes an argument that specifies the size of the buffer being copied to. It wont permit any more characters than this to be copied, thus offering insurance against buffer overflow.
int number; public: // one-arg constructor omega(char str[]) : number(++total) { strncpy(name, str, size); cout << "\nI am the 1-arg constructor. I have " << "created object " << name << "-" << number; } // copy constructor omega(const omega& om) { strncpy(name, om.name, size); number = ++total; cout << "\nI am the copy constructor. I have " << "created object " << name << "-" << number; } }; int omega::total = 0; void main() { void func(omega); // declaration omega om1("Harriet"); // uses one-arg constructor omega om2("Nancy"); func(om1); // call the function func(om2); // call it again } void func(omega og) // argument passed by value { } This program uses the same omega class as COPYCON2. However, it adds a function called func() that is called twice from main(). This function takes one argument: an object of type omega. The function doesnt do anything and it doesnt return anything. (The fact that it doesnt do anything with its argument generates a warning message, which you can ignore.) In main(), I create two objects of type omega with the instance data names Harriet and Nancy. Each time I create an object, the one-argument constructor is invoked. After creating the objects, main() calls func() with the first object as an argument; it then calls it again with the second. Each time I call this function, the copy constructor is invoked to create a new omega object, using data from the argument passed to it. Thus the first object, Harriet-1, is cloned to Harriet-3 and the second object, Nancy-2, is cloned to Nancy-4. Heres the output from the program: I am the 1-arg constructor. I have created object Harriet-1 I am the 1-arg constructor. I have created object Nancy-2 I am the copy constructor. I have created object Harriet-3 I am the copy constructor. I have created object Nancy-4 The copy is created when the function starts to execute, as shown in Figure 5-5.
Figure 5-5 The copy constructor and passing value The new object has the same instance data name as the original (such as Harriet), because the copy constructor merely copies the name. However, the new object has a different number, because the copy constructor, using the same process as the original constructor, gives every object a unique number.
{ private: static const int size = 20; // array size char name[size]; // array for name string static int total; // total number of omegas int number; public: // one-arg constructor omega(char str[]) : number(++total) { strncpy(name, str, size); cout << "\nI am the 1-arg constructor. I have " << "created object " << name << "-" << number; } // copy constructor omega(const omega& om) { strncpy(name, om.name, size); number = ++total; cout << "\nI am the copy constructor. I have " << "created object " << name << "-" << number; } }; int omega::total = 0; void main() { omega retfunc(); // function declaration retfunc(); // call the function } omega retfunc() // return by value { omega temp("Pandora"); // uses one-arg constructor return temp; // uses copy constructor } In main(), the function call itself, retfunc(); takes on the value of the object being returned. Although I dont show it here (to avoid complicating things too much), the function call can be used, like a constant, on the right side of an equal sign: void main() { omega retfunc(); // function declaration omega om1; // create a new object om1 = retfunc(); // call the function, assign to om1 } To do this, however, I would need a no-argument constructor, which I havent shown before, and three objects would be created: om1 in main(), temp in the function retfunc(), and the unnamed object created when the function returns. The value of this unnamed object is then assigned to om1. Heres the output from the program: I am the 1-arg constructor. I have created object Pandora-1 I am the copy constructor. I have created object Pandora-2 The one-argument constructor is called when the function creates the temp object. The copy constructor is called when the function returns and creates a copy for use by the code that called the function. This is shown in Figure 56.
Quiz 6 1. The copy constructor cant modify the data in the object thats being copied from, because a. the data is declared const in the class specification. b. constructors have no return value. c. the argument to the copy constructor is passed by value. d. the argument to the copy constructor is passed by reference. e. the argument to the copy constructor is const. 2. A copy constructor written by the class creator a. must copy all data without modification from one object to another. b. must copy all data, possibly modifying it, from one object to another. c. must copy data from one object to another, modify it before copying, or create new data. d. is not necessary if all the member data is to be copied as is from one object to another. e. is necessary if there is a one-argument constructor.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 239 of 701
3. When an object is passed by value to a function, a. the function must access the objects data using member functions. b. the original object is accessed by the function. c. a copy of the object is created in the function. d. the objects copy constructor is invoked. e. the object is not copied. 4. An object constructed by a copy constructor a. is only temporary. b. lasts for the life of the program. c. may not have a name. d. is local to a member function. e. must be identical to the object from which it was copied. 5. When a function returns an object by value, a. the copy constructor is called. b. the data to be returned is copied into a temporary object. c. the function call can be used on the left side of an assignment operator. d. no additional objects are created. e. two additional objects are created. Exercise 1
Start with the program in Exercise 2 in Session 5 in this chapter, which included a constructor and a copy constructor that checked that correct values were passed to any newly created airtime object. Write a standalone function, called from main(), that takes an airtime as an argument passed by value. Check that the copy constructor works by attempting to pass bad data to the function.
Exercise 2
Write another function, similar to that in Exercise 1, but have this one return an airtime by value. Again, attempt to return bad data and verify that the copy constructor wont allow it.
Perhaps youve wondered whether its possible to use const not only with basic types but with objects of user-defined classes as well. In other words, can you say const zclass zobj; // declare a constant object of class zclass This might be useful for the same reason its useful to use const with basic types: You want to guarantee that the data in an object cannot be changed. In this section, Ill digress from my focus on constructors to examine the idea of const objects
noon.get(); which gets a new airtime value from the user and inserts it in noon. The output from the program might be noon = 12:00 Enter time(format 12:59): 1:23 noon = 1:23 Not so good: Noon should not occur early in the morning. The most obvious way to fix this might appear to involve adding a const to the definition of noon: const airtime noon(12, 0); But now every time a member function of airtime is called in main(), as in noon.display(); the compiler issues a warning along the lines of Non-const function airtime::display() called for const object. Apparently, the compiler wants me to use something called a const function.
const Functions
What is a const function? Its a member function that guarantees that it wont change the object for which its called. Specify such a function by placing the keyword const in the definition (and the declaration too, if there is one) just after the parentheses that follow the function name. Heres how that looks for the display() member function of airtime: class airtime { void display() const; // const in declaration }; void airtime::display() const // const in definition { cout << hours << ':' << minutes; } Only member functions that do not change an objects data should be made const. In this example, only display() is const. What about get()? Well, you should never use get() with a const airtime object, because get() is expressly designed to modify the object. You want an error message if you try to use get() with a const object, so dont make this function const. You might think you should put the const before the function name, rather than after it: const void display(); // not a const function but this has a different meaning: It specifies that the return value of the function is const (which doesnt make sense here, because the return type is void). In the example, I show the functions defined outside the class specification. If you define the function within the class specification, then theres only one place to put the const. However, you must use const with any declaration because const becomes part of the function name. The function void display() const is not the same as the function void display(). Constructors are never const because the creation of an object requires const to be changed
private: int hours; // 0 to 23 int minutes; // 0 to 59 public: // constructor airtime() : hours(0), minutes(0) { } // copy constructor airtime(int h, int m) : hours(h), minutes(m) { } void display() const; // declare constant function void get(); // declare non-constant function }; // end class airtime void airtime::display() const // output to screen { // can be called for const objects cout << hours << ':' << minutes; // hours = 2; // error: can't modify const object } void airtime::get() // input from user { // cannot be called for const objects char dummy; // for colon cout << "\nEnter time (format 12:59): "; cin >> hours >> dummy >> minutes; } void main() { const airtime noon(12, 0); // create noon cout << "\nnoon = "; noon.display(); // display noon // noon.get(); // warning: attempt to change noon airtime at1; // create at1 at1.get(); // OK: at1 is not a const object cout << "at1 = "; at1.display(); // display at1 } Two airtime variables are defined in main(). The first one, noon, is const, whereas the second one, at1, is not. The display() member function is const, because it does not modify its object, but the get() member function does modify its object, so its not const. You can call get() for at1 but not for noon. Heres the output from the program: noon = 12:00 Enter time (format 12:59): 1:30 at1 = 1:30 I can modify the nonconstant object at1 but not the constant noon. Attempting to call a non-const function for a const object leads to a warning from the compiler, as you saw above. However, I can call a non-const function for any object, whether const or not. Within a const function such as display(), its illegal to modify any of the class data. You can verify this by uncommenting the line hours = 2; in the display() function and recompiling. The compiler will tell you something like Cannot modify a const object in function "airtime::display() const" If the use of const with objects is to be meaningful, the class creator must have made const those member functions that are not intended to modify their object. Then if the class user attempts to call such a member function with a const object, a warning results.
Quiz 7 1. A const object is one whose a. member functions can change only non-const variables. b. member functions can change only const variables. c. const member functions cannot be called. d. instance data cannot be changed. e. instance data can only be changed by non-const member functions. 2. Which of the following are appropriate const objects? a. An object of class employee that represents the company president. b. An object of the xString class that represents the null string. c. Any object of the Stack class. d. An object of the airtime class that represents pm (times after noon). e. An object of the airtime class that represents a time entered by the user. 3. Which of these statements has (have) the correct syntax for a constant function? a. const int func(int); b. int const func(int); c. int func(const int); d. int func(int const); e. int func(int) const; 4. A nonconstant member function of a constant object a. can always modify the objects instance data. b. can modify the objects instance data if the data not const. c. guarantees not to modify the objects data. d. can always be called. e. should never be called. 5. Statements in a const member function
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 244 of 701
a. can modify a const objects data. b. can modify any objects data. c. cannot modify a const objects data. d. cannot modify any objects data. e. cannot modify their own local variables. Exercise 1
Create a const object called midnight for the airtime class (as in the CONSTA2 example in Session 7 in this chapter). Write some statements to exercise this object.
Exercise 2
Assume you want to make some const objects of the weekday class, as seen in WEEKOUT in Chapter 4, Session 2. Modify the class specification appropriately, make some const objects, and test to be sure you cant modify them.
omega(char str[]) : snumber(++total_ever) { strncpy(obname, str, size); cout << "\n 1-arg constructor creating " << obname << "-" << snumber << ". Total=" << ++total_now; } // copy constructor omega(const omega& om) : snumber(++total_ever) { strncpy(obname, om.obname, size); cout << "\n Copy constructor creating " << obname << "-" << snumber << ". Total=" << ++total_now; } ~omega() // destructor { cout << "\n Destructor destroying " << obname << "-" << snumber << ". Total=" << --total_now; } }; // end class omega int omega::total_now = 0; int omega::total_ever = 0; omega om0("Adam"); // external object //////////////////////////////////////////////////////////////// void main() { cout << "\nmain() starting"; void func(omega); // declaration omega om1("Jane"); // uses one-arg constructor omega om2("Paul"); omega om3(om2); // uses copy constructor cout << "\nmain() calling func(om1)"; func(om1); cout << "\nmain() calling func(om2)"; func(om2); cout << "\nmain() terminating"; #125; void func(omega og) // argument passed by value { cout << "\nfunc() starting"; omega om4("Mike"); // object is local to func() cout << "\nfunc() terminating"; } The output from DESTRU looks like this: 1_arg constructor creating Adam-1. Total=1 main() starting 1_arg constructor creating Jane-2. Total=2 1_arg constructor creating Paul-3. Total=3 Copy constructor creating Paul-4. Total=4 main() calling func(om1) Copy constructor creating Jane-5. Total=5 func() starting 1_arg constructor creating Mike-6. Total=6 func() terminating C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 246 of 701
Destructor destroying Mike-6. Total=5 Destructor destroying Jane-5. Total=4 main() calling func(om2) Copy constructor creating Paul-7. Total=5 func() starting 1_arg constructor creating Mike-8. Total=6 func() terminating Destructor destroying Mike-8. Total=5 Destructor destroying Paul-7. Total=4 main() terminating Destructor destroying Paul-4. Total=3 Destructor destroying Paul-3. Total=2 Destructor destroying Jane-2. Total=1 Destructor destroying Adam-1. Total=0 The indented lines are printed by the constructors and the destructor. The nonindented lines show what part of main() or func() is executing. The total shown in the last column is the value of the total_now variable. As you can see, it shows how many objects exist at a given moment. Remember that the objects name, obname, is copied without change by the copy constructor. Thats why several different objects can have the same name, such as Paul. However, each objects serial number, snumber, is set equal to the current total_ever value, so it is unique. It is only possible for there to be one number object numbered 3 in the entire program, although there can be both Paul-3 and Paul-4 objects. The distinction between numbers and names makes it easier to follow which objects are created by the one-argument constructor and which are created by the copy constructor.
Program Features
Lets examine some important points demonstrated in DESTRU.
External Variables
I use the one-argument constructor to define om0, which I initialize as Adam-1, as an external variable. The output shows that its created before main() starts to execute. External variables are destroyed after all the variables defined in main() have been destroyed and after main() itself has terminated, as can be seen in the last line of the output.
Variables in main()
Within main(), the one-argument constructor is also used to create om1 (Jane-2) and om2 (Paul-3). Then the copy constructor copies om2, creating om3 (Paul-4). All this is reflected in the output. When main() terminates, these three variables are destroyed in the opposite order they were created.
Passing by Value
As you saw in Session 6 in this chapter, passing an argument to a function by value causes the objects copy constructor to be called, implicitly, to create a copy for use by the function. This happens twice in DESTRU: the two calls from main() to func(). In the first call, Jane-5 is copied from Jane-2. In the second, Paul-7 is copied from Paul-2. I dont show an example of returning by value, but if I did, the copy constructor would create an extra copy there as well.
Local Variables
The function func() creates a local variable, om4. The first time its called, it creates Mike-6; the second time, it creates Mike-8. These variables are destroyed as soon as func() returns so, like the argument variable created by the copy constructor, they dont stay around very long.
Quiz 8 1. Static member data is __________ by a copy constructor. a. copied automatically to the new object b. ignored c. copied, but only if there are appropriate assignment statements, d. copied, but only if there are specific items on the initialization list, e. set to zero 2. In the DESTRU program, the total_ever variable _________, whereas the total_now variable _________. a. is static, is nonstatic b. is decremented when a function returns, is not decremented when a function returns c. records the number of objects created with the copy constructor, records the number of objects created with the one-argument constructor d. is not decremented by the destructor, is decremented by the destructor e. lasts for the life of the program, lasts for the life of an object 3. Variables created as a result of passing to a function by value a. have the same lifetime as the function. b. have the same lifetime as the variable named as the argument in the function call. c. are created by the one-argument constructor. d. are created by the assignment operator. e. are created by the copy constructor. 4. Instance data variables in a class a. have the same lifetime as the program. b. have the same lifetime as main(). c. have the same lifetime as the object of which they are a part. d. have the same lifetime as the class of which they are a part. e. may be created by a copy constructor.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 248 of 701
5. External variables defined at the beginning of the program are created before _________ and destroyed after ____________. a. the operating system starts execution, the operating system terminates b. each function starts execution, each function terminates c. local variables in main(), local variables in main() d. main() starts execution, main() terminates e. static instance variables, static instance variables Exercise 1
Insert some more external variables of type omega and some more local variables in func() in the DESTRU program. What can you conclude about the order in which external and local objects are created and destroyed?
Exercise 2
Change the func() function in the omega class in the DESTRU program so it returns an omega object by value. Notice what effect this has on the output. The destructor is very busy. You may want to insert getch() statements in main() to pause the program in several places, because the output becomes rather lengthy.
Summary: Chapter 5
In this chapter, Ive focused, for the most part, on constructors. A constructor is a special member function whose purpose is to initialize the instance data in an object. A no-argument constructor (called the default constructor) initializes all objects to the same value. Constructors that take arguments can initialize objects to specified values. A special constructor is the copy constructor, which creates a copy of an object. If you dont write your own copy constructor, the compiler supplies one that performs a memberwise (member-by-member) copy. Copy constructors usually pass a const value by reference. Doing this keeps the function from modifying the value named in the function call. A const object, like a const variable of a basic type, is one that cant be modified. To make this work with classes, member functions that do not modify any instance data are declared const and can be called for const objects. Member functions that are not const cannot be called for const objects.
End-of-Chapter Discussion
George: Thats just terrific. There are two ways to write a definition that initializes an object with another object, they both do the same thing, but one looks like an assignment statement. Why dont these guys get their act together? Lifes tough, George. Im impressed with copy constructors. Its all so flexible and there are so many options. Theres a copy constructor built in, which you use normally and the compiler uses it too, for stuff like passing arguments by value. But if you write your own copy constructor, then you can use it and the compiler will use it too. Its pretty amazing that the compiler executes programmer-written routines. It puts the shoe on the other foot. Theres a lot more interaction between the compiler and the programmer in C++ than there is in Pascal. Or C. Or BASIC. Its almost like you can write parts of the compiler yourself. Right. When you write a class specification youre creating your own data types and telling the compiler how to operate with them. With basic types that are built into the compiler, all thats Page 249 of 701
Estelle: Don:
Figure 6-1 Syntax of overloaded operator The dot operator, the word operator itself, and the parentheses surrounding the function argument arent necessary. They can all be removed, leaving only at3 = at1 + at2; This same syntax holds true for any binary operator x. The function operatorX() enables the operator to be used alone, with the other parts of the syntax being understood.
} // overloaded + operator airtime operator + (airtime right) { airtime temp; // make a temporary object temp.hours = hours + right.hours; // add data temp.minutes = minutes + right.minutes; if(temp.minutes >= 60) // check for carry { temp.hours++; temp.minutes -= 60; } return temp; // return temporary object } }; // end class airtime void main() { airtime at1, at2, at3; cout << "Enter first airtime: "; at1.get(); cout << "Enter second airtime: "; at2.get(); at3 = at1 + at2; // overloaded + operator // adds at2 to at1
cout << "sum = "; at3.display(); // display sum } Notice how easy it is to understand whats going on in main(). The overloaded + makes the addition look just like addition with basic types.
Arguments
The operator+() function in the airtime class works like the add() function in the TIMEADD program (see Chapter 2, Session 7). It adds the hours, adds the minutes, and checks if the minutes overflow past 59. (For brevity, Ive left out the code that checks if the hours exceed 23.) However, notice where the input operands are placed in operator+(). The airtime variable on the right of the + operator, which is called at2 in main(), becomes the single argument to operator+(). Where does at1, the variable on the left side of the + operator in main(), show up in operator+() in the class specification? Its the object for which operator+() was called. That is, its the object to which the operator+() message was sent. This message tells at1 to add itself to at2 and to return the result. Notice that there will always be one less argument to operator+() than there are operands to an operator x. Thats because one operand (the one on the left, for binary operators) is the object for which the function was called. (This rule doesnt apply to friend functions, which Ill introduce in Chapter 9.)
Return Value
In main(), the return value from the addition is assigned to at3 in the usual way. In the operator+() function, this is accomplished by returning the local airtime object, temp, by value. The result of an addition of two airtime values is another airtime value, so this must be the return value of operator+(). The temp object is used to hold the results of the calculations and is the object that is returned. Creating a temporary object as I do with temp is not very efficient. The objects constructor must be called, and its destructor must be called when the function terminates and the object goes out of scope. Also, because this object is returned by value, the copy constructor must be called when the function returns. I can make some improvements in the way things are handled, as youll see later in this chapter. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 253 of 701
// maximum length of xStrings // ordinary C string // no-arg constructor // 1-arg constructor // get string from user // display string // concatenate two strings
xString operator+(xString right) { xString temp; // temporary xString if(strlen(str) + strlen(right.str) < MAX-1) { strcpy(temp.str, str); // copy us to temp strcat(temp.str, right.str); // concatenate argument } else cout << "\nError: xString too long" << endl; return temp; // return temporary } }; void main() { xString s1("Greetings, "); xString s2, s3; cout << "Enter your name: "; s2.input();
s3 = s1 + s2 + "."; // concatenate period to s2, s2 to s1 s3.display(); // display s3 } The operator+() function creates a temporary xString object, temp, because it needs a work area and it doesnt want to modify its own object (or its argument). It first checks to be sure theres room for the concatenated C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 254 of 701
string. Then it copies its own object to temp with the strcpy() library function, concatenates the argument onto temp with strcat(), and returns temp. In main(), the program initializes the xString s1 to Greetings; gets another string, s2, from the user; concatenates them; and assigns the result to s3. Then it displays s3. Heres some sample interaction: Enter your name: George Bernard Shaw Greetings, George Bernard Shaw.
Quiz 1 1. Which of the following are valid reasons for overloading an operator? a. To make it possible to perform arithmetic on objects of user-defined classes. b. To have fun. c. To clarify the listing. d. To speed up the program. e. To make the program more memory efficient. 2. What is the name of the function that overloads the + operator for the airtime class? a. add b. airtime add c. + d. operator+ e. operator 3. The argument to an overloaded binary operator function such as operator+() a. represents the operand on the left side of the + operator. b. represents the operand on the right side of the + operator. c. is the variable to which the sum is assigned. d. is the temporary variable that is returned from the function. e. is not necessary. 4. In the ADDAIR program, the return value of operator+() is a. an airtime value.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 255 of 701
b. type void. c. an int value. d. assigned to at3 in main(). e. actually serves no purpose, because the results of the addition are placed in the object for which the function was called. 5. The named temporary object temp, defined in operator+() in the airtime class in ADDAIR, a. is used as a dummy variable and never contains valid data. b. would need to be of the widget class if you were adding two widget objects. c. is returned by reference. d. is an instance variable. e. must be constructed and destroyed whenever operator+() is called. Exercise 1
Overload the subtraction operator to subtract one airtime value from another. Write some statements in main() to exercise this function. Assume that the earlier (smaller) time will always be subtracted from the later (larger) one, so you dont need to consider negative times.
Exercise 2
It doesnt make sense to multiply one airtime value by another. After all, theres no such thing as time squared (that I know about, anyway). However, its perfectly reasonable to multiply an airtime value by an ordinary number, say of type float. This lets you calculate things like how long an airplane has been in the air since its last inspection or if it has made 36 one-way flights between Austin and Boston, each of which lasted 3 hours and 45 minutes. Overload the * operator for the airtime class so it multiplies an airtime by a float. Write some statements in main() to test it.
Relational Operators
When you compare integers with a relational operator, the answer returned is a true/false value; in C++, false is 0 and true is any other value. You can use a simple int variable to hold true/false values, but this is a little obscure. Its clearer and more self-explanatory to use an enum declaration to give names to such true/false values: enum boolean {false=0, true}; Ill use this declaration in the next example, which overloads the < operator to compare two airtime values and return a value of type boolean. The skeleton for this operator looks like this: boolean operator< (const airtime& right) { // statements } When its called for an object, this operator compares its own object with its argument, called right, and returns true if its object is less than right and false otherwise. Listing 6-3 shows COMPAIR. Listing 6-3 COMPAIR // compair.cpp // overloads the < operator #include <iostream.h> class airtime { private: int hours; // 0 to 23 int minutes; // 0 to 59 public: void display() const // output to screen { cout << hours << ':' << minutes; } void get() // input from user { char dummy; cout << "\nEnter time (format 12:59): "; cin >> hours >> dummy >> minutes; } // overloaded < operator bool operator<(const airtime& right) { if(hours < right.hours) return true; if(hours == right.hours && minutes < right.minutes) return true; return false; } }; // end class airtime void main() { airtime at1, at2; cout << "Enter first airtime: "; at1.get(); cout << "Enter second airtime: "; at2.get();
if(at1 < at2) cout << "\nfirst less than second"; else cout << "\nfirst not less than second"; } In main(), the program uses the relational operator < to compare two airtime values in the expression if(at1 < at2) The overloaded < operator makes this look like a normal comparison with basic types. As you can see by experimenting with this program, it tells you which of two airtimes is less than the other. Heres a sample interaction: Enter first airtime: Enter time (format 12:59): 3:59 Enter second airtime: Enter time (format 12:59): 4:00 first less than second
How It Works
The member function operator<() first compares the hours member for its own object and its argument object. If its own objects hours is less, then it knows its whole airtime value is less than its argument. Otherwise, it checks the minutes value. If the hours are equal and the minutes are less, then again its own object is less. In all other cases, its own object is equal to or larger than its argument. The function returns the appropriate boolean true/false value to reflect this fact.
Assignment Operators
Another important category of operators that can be overloaded is the assignment operators, which include +=, -=, *=, /=, and other more obscure operators. In the example, Ill examine +=. The major difference between normal binary operators such as + and assignment operators such as += is that assignment operators modify the object for which they are called. If you say at1 += at2; then the function operator+=() is called for the at1 object and at1 is modified (by having at2 added to it). Youre sending a message to at1 saying, Add this argument to yourself. By contrast, the message with the normal addition operator+() is add yourself to this argument and return the result. Although the primary purpose of an assignment operator is to modify the object for which it was called, its common for it to return a value as well. You want to be able to say at3 = at1 += at2; where the result of the += operation is assigned to at3 as well as at1. Assignment operators typically return a value of their own class to make this sort of chaining possible, even though this return value is not always used. The example, PLEQAIR, following a long tradition, will overload the += operator for the airtime class. Listing 6-4 shows PLEQAIR. Listing 6-4 PLEQAIR // pleqair.cpp // overloads the += operator #include <iostream.h> class airtime { C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 259 of 701
// 0 to 23 // 0 to 59
// no-arg constructor airtime() : hours(0), minutes(0) { } // 2-arg constructor airtime(int h, int m) : hours(h), minutes(m) { } void display() const // output to screen { cout << hours << ':' << minutes; } void get() // input from user { char dummy; cout << "\nEnter time (format 12:59): "; cin >> hours >> dummy >> minutes; } // overloaded += operator airtime operator += (const airtime& right) { hours += right.hours; // add argument to us minutes += right.minutes; if(minutes >= 60) // check for carry { hours++; minutes -= 60; } return airtime(hours, minutes); // return our new value } }; // end class airtime void main() { airtime at1, at2, at3; cout << "Enter first airtime: "; at1.get(); cout << "Enter second airtime: "; at2.get(); at1 += at2; cout << "\nat1+=at2 = "; at1.display(); // overloaded += operator // adds at2 to at1 // display result
at3 = at1 += at2; // do it again, use return value cout << "\nat3 = "; at3.display(); // display result } In main(), I show the assignment operator used twice: once where no value is returned and once where one is. Heres some interaction: Enter first airtime: Enter time (format 12:59): 10:30 Enter second airtime: C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 260 of 701
Enter time (format 12:59): 0:10 at1+=at2 = 10:40 at3 = 10:50 In a similar way, you can overload the other assignment operators -=, *=, /=, and so on. Overloading the assignment operator =, however, is a somewhat more complex operation that Ill discuss in Session 6 in this chapter.
Quiz 2 1. In the overloaded < operator for airtime values in COMPAIR, the operator<() function a. returns a value of type boolean. b. returns a value of type int. c. returns an airtime object. d. compares two objects passed as arguments. e. compares the object for which it was called with the object passed as an argument. 2. Why did I not construct a temporary airtime object in the operator<() function in the COMPAIR program, as I did for the operator+() function in the ADDAIR program? a. A comparison doesnt require a value to be returned, whereas addition does. b. A comparison returns a boolean, whereas addition returns an airtime. c. Constructing a temporary object would have worked just as well.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 261 of 701
d. The result of the comparison was not an object. e. I hadnt yet learned how inefficient such temporary objects were. 3. The operator +=() function in the airtime class in the PLEQAIR program a. alters an unnamed temporary object. b. alters the value of the object for which it was called. c. performs addition on two airtime objects. d. returns the value of the object that called it. e. returns the sum of two airtime objects. 4. Every time you overload the += operator for a class, you necessarily give this operator the ability to a. add an object to the object for which the operator was called. b. add the argument to operator+=() to itself. c. add two objects without modifying them. d. return the result of an addition. e. create a new numerical value. 5. Returning an unnamed temporary object from a member function using a statement such as
return classname(arg1, arg2);
causes a call to a. the two-argument constructor and the destructor, before the function terminates. b. the two-argument constructor, to make an object in main(). c. the copy constructor, when the function is called. d. the copy constructor, when the function returns. e. the two-argument constructor twice, once to create the temporary object and once to create the return value. Exercise 1
Overload the >= operator in the airtime class. Write some statements in main() to demonstrate that it works correctly.
Exercise 2
Overload the *= operator for the airtime class. Write some statements in main()to demonstrate that it works correctly.
// 0 to 23 // 0 to 59
// no-arg constructor airtime() : hours(0), minutes(0) { } // 2-arg constructor airtime(int h, int m) : hours(h), minutes(m) { } void display() const // output to screen { cout << hours << ':' << minutes; } void get() // input from user { char dummy; cout << "\nEnter time (format 12:59): "; cin >> hours >> dummy >> minutes; } airtime operator++ () { ++minutes; if(minutes >= 60) { // overloaded prefix ++ operator // increment this object
};
++hours; minutes -= 60; } // return new value return airtime(hours, minutes); } // end class airtime
void main() { airtime at1, at2; at1.get(); ++at1; cout << "\nat1="; at1.display(); // at1++;
// make two airtimes // get value for one // increment it // display result // error: postfix
at2 = ++at1; // increment again, and assign cout << "\nat2="; at2.display(); // display assigned value } The first use of ++ in main() adds 1 minute to the airtime object for which it was called, handling the carry if necessary. (Of course, you can define an overloaded operator however you want. You could increment the hours, if you wish, and leave the minutes alone, as happens when you change time zones.) Do I return the old value of the object before it was incremented, or the new value? Because its a prefix operator, which increments the operand before its used in any other calculations, I return the new value. Heres some interaction when the user enters 7:35 into the program: Enter time(format 12:59): 7:35 at1=7:36 at2=7:37 In main(), the program gets a time, at1, from the user (7:35 in this example), increments it, and displays the result (7:36). The commented-out statement shows that the compiler will issue a warning if you try to use the postfix version of an operator when you have defined only the prefix version. Next, the program increments at1 again and in the same statement assigns it to at2. Then it displays at2. Because at1 is incremented before the assignment, at2 is assigned the new value of 7:37.
// 0 to 59 // no-arg constructor
// 2-arg constructor airtime(int h, int m) : hours(h), minutes(m) { } void display() const // output to screen { cout << hours << ':' << minutes; } void get() // input from user { char dummy; cout << "\nEnter time (format 12:59): "; cin >> hours >> dummy >> minutes; } airtime operator++ () // overloaded prefix ++ operator { ++minutes; // increment this object if(minutes >= 60) { ++hours; minutes -= 60; } // return incremented value return airtime(hours, minutes); } airtime operator++ (int) { airtime temp(hours, minutes); ++minutes; if(minutes >= 60) { ++hours; minutes -= 60; } return temp; } }; // end class airtime // overloaded postfix ++ operator // save original value // increment this object
//////////////////////////////////////////////////////////////// void main() { airtime at1, at2; // make two airtimes at1.get(); // get value for one at2 = ++at1; cout << "\nat2="; at2.display(); // increment it (prefix) and assign // display result Page 265 of 701
at2 = at1++; // increment (postfix) and assign cout << "\nat1="; at1.display(); // display incremented value cout << "\nat2="; at2.display(); // display assigned value } The postfix function operator++(int) operates differently from the prefix function operator++(). The postfix version must remember the original value of its object, increment the object, and then return the original value, not the new value. This is accomplished by creating a temporary object, temp, that is initialized to the original value of the object. After the object is incremented, temp is returned. Figure 6-3 shows how this compares with the prefix version.
Figure 6-3 Prefix and postfix versions of ++ operator Heres some sample interaction with POSTFIX: Enter time (format 12:59): 7:35 at2=7:36 at1=7:37 at2=7:36 The user enters a time for at1. The program increments it with the prefix operator, assigns it to at2, and displays at2. Because at1 was incremented before being assigned, at2 reflects the new value of 7:36. Now the program increments at1 again, this time with the postfix operator, and assigns it to at2. This time, because it was assigned before being incremented, at2 retains the original value of 7:36, whereas at1 is now 7:37.
Lets look at an example using the English class. Perhaps you plan to subtract one English value from another, so you want to be able to handle negative English values. Well assume negative values are indicated by making the feet member, but not inches, negative. (There are other options: You could make inches negative, or make both feet and inches negative, or use a third member, possibly boolean, to indicate a negative value.) Listing 6-7 shows NEGENGL, which overloads both the binary subtraction operator and the unary minus. Listing 6-7 NEGENGL // negengl.cpp // overloads the unary minus for English class #include <iostream.h> class English // English distances { private: int feet; float inches; public: // no-argument constructor English() : feet(0), inches(0.0) { } // 2-argument constructor English(int f, float i) : feet(f), inches(i) { } void get() { cout << " cout << " } // user input Enter feet: "; cin >> feet; Enter inches: "; cin >> inches;
void display() // display { if(feet<0) cout << "-(" << -feet << "\'-" << inches << "\")"; else cout << feet << "\'-" << inches << '\"'; } English operator-() // negation operator { // (unary minus) return English(-feet, inches); } // subtraction operator English operator-(English right) { int tfeet = feet - right.feet; int tinches = inches - right.inches; if(tinches < 0) { tinches += 12.0; tfeet -=1; } return English(tfeet, tinches); } }; // end English class C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 267 of 701
void main() { English man(6, 2.0); English horse(7, 0.0); English difference; difference = man - horse; cout << "\ndifference = "; difference.display(); // subtraction
difference = -difference; // unary minus cout << "\n-difference = "; difference.display(); } The job of the unary minus function operator-() is to return the value of the object for which it was called, but with the sign of the feet member changed. This operator does not change the value of its own object. The subtraction function operator-(English) works like the addition functions youve seen for the airtime class. It subtracts the appropriate values, checks for a carry, and returns the result without changing either its own object or the argument. Ive changed the display() member function so it can print out negative English values. The format for this is to surround the value with parentheses and to precede the result with a minus sign, as in -(1-2), which means minus the distance one foot two inches. In main(), the program initializes two English variables and subtracts one from the other, yielding a negative result, which it places in a third English variable, difference. It displays difference, negates it with the unary minus operator, and displays it again. Heres the output from NEGENGL: difference = -(1'-2") -difference = 1'-2" The unary plus operator (as in +alpha) can be overloaded in a way similar to that of the unary minus, except that it (usually) doesnt need to do anything. Plus is the default sign, so applying it causes no change.
Quiz 3 1. A unary operator x a. is called unary because operatorX() takes only one argument. b. is called unary because x takes only one operand. c. always changes the object for which its called. d. usually returns a value. e. is normally written immediately following a variable name. 2. The prefix version of the ++ operator, overloaded as a member function of class x, probably has the declarator a. void operator++(X) b. void operator++() c. void operator++(int)
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 268 of 701
d. X operator++(int) e. X operator++() 3. The postfix and prefix versions of the ++ operator for the airtime class differ in that a. the prefix version returns its objects value after it has been incremented. b. the postfix version increments the object and then returns its value. c. the prefix operator is applied after the object is used in other calculations. d. the prefix version increments the value passed as an argument. e. the postfix version increments the value for which it was called. 4. Overloading the unary minus operator (-) for the airtime class a. could be handled in much the same way as it was in the English class. b. would lead to syntactic difficulties. c. would probably require a way to display negative airtime values. d. could never make sense, because there is no such thing as minus time. e. would be handled similarly to overloading the binary + operator. 5. The unary minus operator, overloaded as a member function for class x, a. changes the object for which it was called to the negative of its former value. b. returns a value that is the negative of the object for which it was called. c. negates its argument. d. subtracts its argument from the object for which it was called. e. can perform negation however it wants. Exercise 1
Overload the decrement (--) operator for the English class and write some statements in main() to check that it operates correctly.
Exercise 2
Overload the unary minus (-at) for the airtime class and write some statements in main() to check that it operates correctly.
floatvar = intvar; where floatvar is type float and intvar is type int, the conversion takes place invisibly, with no complaint from the compiler. The appropriate conversion routines for basic types are built in. This is not the case with classes. If I write a class and I want to convert between basic types and class objects, or between objects of different classes, then I must write the conversion routines myself. Conversions can be divided into several categories. These are
From basic types to objects From objects to basic types From objects to objects of another class
Youve already seen several examples of converting basic types to objects. This conversion is conveniently handled by the one-argument constructor, where the argument is the basic type. In Chapter 5, Session 3, you saw how ordinary strings were converted to xString objects in STRCON and how a type float quantity representing meters was converted to an English object in ENGLCON. The second category, conversion from objects to basic types, is the subject of this session. Conversion from objects of one class to objects of a different class is discussed in the next session
Figure 6-4 Casting from one type to another Type casting is a semidangerous practice. Why? For one reason, you may lose information (e.g., converting from long to int may entail the loss of significant digits). More importantly, typing, or making sure that variables interact only with variables of their own type, is an important technique in preventing errors. If you find yourself assigning a float to an int, you may be making a conceptual mistake. Casting should therefore never be done merely for the sake of expediency, to fix something that doesnt seem to work, but only when its obviously necessary. I should mention that theres another format for type casting. Besides using the so-called functional syntax (because it looks like a function), var2 = long(var1); // "functional" syntax to convert var1 to type long, you can also say var2 = (long)var1; // alternative syntax (not recommended) This second form is the old-fashioned syntax used in C. It is not the preferred approach, but occasionally it works in situations where the preferred syntax does not.
English(float meters) { float fltfeet = MTF * meters; feet = int(fltfeet); inches = 12 * (fltfeet-feet); }
// 1-argument constructor // get decimal feet // integer part is feet // remainder is inches // 2-argument constructor
English(int f, float i) : feet(f), inches(i) { } void get() { cout << " cout << " } // user input Enter feet: "; cin >> feet; Enter inches: "; cin >> inches;
void display() // display { cout << feet << "\'-" << inches << '\"'; } operator float() // convert English to float { float fracfeet = inches/12; // inches to feet fracfeet += float(feet); // add the feet return fracfeet/MTF; // convert to meters } // and return }; // end English class const float English::MTF = 3.280833; // meters to feet, // definition void main() { English engman(1.9); cout << "\nengman = "; engman.display();
float metman; metman = float(engman); // English to meters cout << "\nmetman = " << metman; // display meters } The English class includes both a one-argument constructor, to convert from float to English, and the float() function, to convert from English to float. In main(), the program converts from a float value of 1.9 meters to English when constructing the engman variable. This English value, 6-2.803, is displayed, then converted back to float in the statement metman = float(engman); // English->float conversion The program then displays the meters value, which should be the same one we started with, 1.9. Heres the output from ENGLCONV: engman = 6'-2.803" metman = 1.9
metman = engman; // also causes the English->float conversion the compiler will realize it cant assign an English value to a float value without converting it first, so it will look for a way to do this. It will find the float() member function and carry out the conversion, even though you have not explicitly asked for it. The float() function will also be invoked in initializations: float metman(engman); Here metman is not assigned a value; its initialized to a value. But because the value is English, the compiler will call the float() function automatically. (Lord Wormsley always said that English values were the best.)
A Static Constant
The MTF member variable is the conversion factor for converting meters to feet. I make this variable static because it can be used by all objects and I make it const because it should not be changed.
// maximum length of xStrings // ordinary C string // no-arg constructor // convert string to xString // get string from user // display string
{ return str; } }; void main() { xString s1("George"); xString s2; cout << "Enter your name: "; s2.input();
if( strcmp(s1, s2)==0 ) // compare them with strcmp() cout << "You're George!"; else cout << "You aren't George."; } Why would you want to convert xString objects to ordinary null-terminated C strings? Well, for one thing, theres a large library of functions that work with C strings. If I have a conversion function, I can use these library functions on xStrings. Thats what STRCONV does in main(), where it calls the strcmp() library function to compare two xString objects, s1 and s2. When the compiler sees that the arguments to strcmp() are xStrings, but that this function requires C strings, it looks for a conversion function. Finding that the char*() operator has been overloaded for the xString class, the compiler uses it to convert s1 and s2 to C strings and then calls strcmp(), which returns 0 if the strings are the same. Heres some sample interaction with STRCONV: Enter your name: Dan You aren't George. You might be wondering what the asterisk means in the member function operator char*(). It indicates a pointer, which I have not yet discussed. However, the asterisk has the same effect here as brackets ([]): It indicates (approximately) an array of type char or, in other words, a normal C string. Thus, the operator char*() function converts an xString to a string.
Quiz 4 1. Conversions from basic types to class objects a. are carried out using a constructor. b. are carried out using an overloaded operator. c. are carried out using a member function of the basic type. d. are carried out using a cast operator member of the class. e. are not possible. 2. Conversions from class objects to basic types a. are carried out using a constructor. b. are carried out using an overloaded operator. c. are carried out using a member function of the basic type. d. are carried out using a cast operator member of the class. e. are not possible. 3. In the ENGLCONV program, operator float() a. returns type English. b. returns type float. c. has no return value. d. converts from a decimal-feet value. e. converts from a meters value. 4. The declarator of a member function to convert from type airtime to type float might be a. airtime float() b. float(airtime at) c. float static_cast(airtime at) d. operator float(airtime at) e. operator float() 5. The member function operator char*() in the xString class in the STRCONV program
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 275 of 701
a. takes a C string as an argument. b. takes an xString as an argument. c. returns an xString. d. returns a C string. e. returns an array of type char*. Exercise 1
Write a member function for the airtime class that converts airtime values to type long, where the long variable represents the number of seconds since midnight (e.g., 1:05 would convert to 65). You can start with the ADDAIR program in Session 1 in this chapter. Write some statements in main() to exercise this function.
Exercise 2
Write another member function for the airtime class, but have this one convert airtime values to type float, where the float variable represents the number of hours since midnight. For example, 2:30 would convert to 2.5.
Midchapter Discussion
George: Don: Estelle: George: Don: George: Estelle: I really like overloaded operators. I havent been so excited since I got my first C program to print Hello, world. Yeah, I like them too. You can define whole new number systems. How about doing arithmetic in hexadecimal? Or making up a class of really huge numbers, like integers with hundreds of digits? I like being able to apply operators to nonarithmetic stuff, like concatenating strings. I bet you can overload the plus sign to add one file to another and the equal sign to copy files. Just type file1 = file2 at the C prompt. Wait a minute. The idea is for the class user, the programmer, to use these operators, not the end user. Nobody ever likes my ideas. Poor boobie.
A Short Example
Lets look first at a skeleton program that reduces the conversion operations to their fundamentals. This program contains two classes, alpha and beta, and includes the necessary functions to convert from alpha to beta and from beta to alpha. Both the conversion functions are located in beta. One is a constructor that takes an alpha object as an argument; the other is the operator alpha() function, which returns an alpha. Listing 6-10 shows TWOTEST. Listing 6-10 TWOTEST C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 276 of 701
// twotest.cpp // tests conversions between two classes class alpha { private: int ia; public: alpha(int i) { ia = i; } int get_ia() { return ia; } }; class beta { private: int ib; public: beta(alpha a) { ib = a.get_ia(); } operator alpha() { return alpha(ib); } };
// // // //
converts alpha to beta uses alpha get_ia() converts beta to alpha uses alpha 1-arg constr
void main() { alpha a(11); // alpha 1-arg constructor; int to alpha beta b(a); // beta 1-arg constructor; alpha to beta a = b; // beta operator alpha(); beta to alpha } Both classes contain a single data item of type int. Class alpha contains a one-argument constructor that converts an int to an alpha (or initializes an alpha with an int, to put it another way). It also contains a peek function that gives other parts of the program read-only access to its data item. Such access is necessary for the conversion function, the one-argument constructor, in beta. This one-argument constructor in beta converts an alpha to a beta. It does this by calling the get_ia() peek function in alpha to obtain alphas data and assigning this data to its own object. (Of course, a more practical class would probably need to change the data in some way.) The second member function in beta is the operator alpha(), which converts the beta for which it was called into an alpha, which it returns. It performs this conversion by calling the alpha constructor with its own int data as the argument. In this scenario, both conversion functionsthe constructor and the class operatorare in beta. However, you could also handle the conversions using similar functions in class alpha. Figure 6-5 shows the relationship of functions in the two classes.
Figure 6-5 Class-to-class conversions In main(), the program tests the conversions by initializing a beta with an alpha and setting an alpha equal to a beta. Theres no output. Notice that its important to put class alpha first in the listing. The beta class needs to know about alphas member functions, but alpha doesnt need to know anything about betas.
someone has already written the English class to handle this kind of data. One day this firm buys another company that stores data in the feet and fractions format. Now you need to modify the program to store this new kind of data in addition to the English kind, so you invent the FracFeet class. (This situation might also be a candidate for inheritance, which Ill cover in Chapter 7.) Ill use roughly the same arrangement for the conversion functions that I did in the TWOTEST program, with both conversion functions in the new FracFeet class. These will be a constructor that takes as its single argument an object of the English class and an English() function that converts its FracFeet object to an English value. Ive added two member functions, get_feet() and get_inches(), to the English class; they return the feet and inches values of an English object. These peek functions provide the only way for functions outside the English class to access its data. The English class also has a two-argument constructor taking feet and inches as arguments.
void display() // display { cout << feet << "\'-" << inches << '\"'; } // these functions needed for conversions int getfeet() // return feet { return feet; } float getinches() // return inches { return inches; } }; // end English class //////////////////////////////////////////////////////////////// class FracFeet // fractional feet class { private: C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 279 of 701
int wfeet; // whole feet int numer; // numerator int denom; // denominator public: // no-argument constructor FracFeet() : wfeet(0), numer(0), denom(1) { } FracFeet(English); // one-argument constructor // (declaration)
void get() // user input (never use 0 { // in denominator) char dummy; cout << " Enter feet: "; cin >> wfeet; cout << " Enter fraction (format 2/3): "; cin >> numer >> dummy >> denom; } void display() // display { cout << wfeet; if(numer != 0) // if numerator 0, no fraction cout << '-' << numer << '/' << denom; cout << " ft"; } operator English() // convert FracFeet to English { // inches = 12 * n / d float temp_inches = 12.0 * float(numer) / float(denom); return English( wfeet, temp_inches); } }; // end class FracFeet //////////////////////////////////////////////////////////////// FracFeet::FracFeet(English e) // one-argument constructor { // convert English to FracFeet wfeet = e.getfeet(); // feet are the same int i = int( e.getinches() ); // convert inches to integer // find fraction if(i==6) { numer=1; denom=2; } // 1/2 else if(i==2 || i==10) { numer=i/2; denom=6; } // 1/6, 5/6 else if(i==3 || i== 9) { numer=i/3; denom=4; } // 1/4, 3/4 else if(i==4 || i== 8) { numer=i/4; denom=3; } // 1/3, 2/3 else { numer=i; denom=12;} // i/12 } //////////////////////////////////////////////////////////////// void main() { FracFeet ff; English eng; cout << "\nFracFeet value\n"; ff.get(); cout << "FracFeet = "; ff.display();
cout << "\n\nEnglish value\n"; eng.get(); // get English from user cout << "English = "; eng.display();
// display English
ff = eng; // set English to FracFeet cout << "\nFracFeet = "; ff.display(); // display equivalent FracFeet } In main(), the program tests both kinds of conversions. First, a FracFeet object gets data from the user and displays itself. Then the program sets an English object equal to the FracFeet object and displays it, proving that the FracFeet to English conversion works. Second, an English object gets data from the user and displays itself. The program sets the FracFeet object equal to the English object and displays it, thus demonstrating the English to FracFeet conversion. Heres some interaction with FRACFEET: FracFeet value Enter feet: 7 Enter fraction (format 2/3): 3/4 FracFeet = 7-3/4 ft English = 7'-9" English value Enter feet: 3 Enter inches: 8 English = 3'-8" FracFeet = 3-2/3 ft Notice that the FracFeet class specification follows the English class specification in the listing. This ordering is necessary, because the conversion functions reside in FracFeet. These conversion functions must access member functions in the English class, so English must be specified first.
Quiz 5 1. Conversions between classes a. are never a good idea. b. can always be carried out, no matter what the classes are. c. normally make the most sense for classes representing numerical types. d. can usually make use of an overloaded cast operator. e. can always be handled with existing constructors. 2. To convert from class A to class B, you could use a. a constructor in class A. b. a constructor in class B. c. an operator A() in class A. d. an operator B() in class B. e. an operator B() in class A. 3. You cant exchange the order of the English and FracFeet class specifications in the FRACFEET program because a. FracFeet objects are defined earlier in main() than English objects. b. the English two-argument constructor needs to access data in FracFeet. c. the FracFeet one-argument constructor needs to access member functions in English. d. the operator English() function needs to call the English two-argument constructor. e. the operator English() function needs to call the FracFeet one-argument constructor. 4. In the FRACFEET program, inches are converted to a. feet and fractions of a foot. b. feet and inches. c. a numerator and denominator. d. a fraction of an inch. e. a fraction of a foot. 5. The one-argument constructor in FracFeet gets the values it needs from
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 282 of 701
a. peek member functions of the English class. b. a locally stored list. c. its single argument. d. the constructor in the English class. e. the operator English() function. Exercise 1
Rearrange the FRACFEET program so that the conversions are carried out in the English class rather than in the FracFeet class. Use a one-argument constructor that takes a FracFeet object as an argument and an operator FracFeet() function.
Exercise 2
Create a class called Time that stores three data items: hours, minutes, and seconds, all of type int. Include the Time class in the same program as the airtime class. (See the ADDAIR program in Session 1 in this chapter.) Create the necessary functions to convert between Time and airtime and between airtime and Time.
Figure 6-6 Operation of the overloaded assignment operator Remember that the use of an equal sign in a definition is not the same as assignment: omega beta = alpha; // construction and initialization // uses copy constructor beta = alpha; // assignment // uses assignment operator The assignment operator is not used in the first statement, only in the second.
char name[size]; static int total; const int number; public: // one-arg constructor omega(char str[]) : number(++total) // number the object { strncpy(name, str, size); // give it a name cout << "\n1-arg constructor has " << "created " << name << "-" << number; } // overloaded assignment operator void operator=(const omega& right) { cout << "\n" << right.name << "-" << right.number << " assigned to " << name << "-" << number; strncpy(name, right.name, size); // copy the name cout << ", making " << name << "-" << number; } }; int omega::total = 0; // (no objects when program starts) void main() { omega om1("Harriet"); // uses one-arg constructor omega om2("Bernard"); om2 = om1; // assignment } There are three data items in the omega class. The OAO copies only one of these, name, using the strncpy() library function. The second item, total, is static. Static data is never copied by an OAO (or a copy constructor) because it applies to the class as a whole, not to individual objects. I dont copy the number data item either, because each objects serial number is permanent from the time its first created. I reinforce this by making number a const data item. After const member data is initialized in the constructor, the compiler wont allow it to be changed. (Incidentally, it follows that when an object has a const data item, you must overload the assignment operator and copy constructor, because the compiler cant use the default memberwise copy, which would involve changing the unchangeable const variable.) In main(), the program creates two objects and then uses the assignment operator to copy one to the other. Both the constructor and the OAO display messages to tell what theyre doing. Heres the output from the program: 1-arg constructor has created Harriet-1 1-arg constructor has created Bernard-2 Harriet-1 is assigned to Bernard-2, making Harriet-2 The om2 object, which started out as Bernard-2, becomes Harriet-2; its name is changed but not its number. (This is similar to a person being able to change his or her name legally but not his or her social security number.)
} // 1-arg constructor The return statement uses the one-argument constructor to create a new temporary copy of the object for which the OAO was called, which is the object to the left of the = sign in main(). Listing 6-13, ASSIGN2, shows how this looks. Listing 6-13 ASSIGN2 // assign2.cpp // overloaded assignment operator with self-numbering objects // returns a value #include <iostream.h> #include <string.h> // for strncpy() class omega { private: enum { size=20 }; char name[size]; static int total; int number; public: // one-arg constructor omega(char str[]) : number(++total) { strncpy(name, str, size); cout << "\n1-arg constructor has " << "created " << name << "-" << number; } ~omega() { cout << "\nDestructor has " << "destroyed " << name << "-" << number; } // overloaded assignment operator omega operator=(const omega& right) { cout << "\n\n" << right.name << "-" << right.number << " assigned to " << name << "-" << number; strncpy(name, right.name, size); cout << ", making " << name << "-" << number; return omega(name); } }; int omega::total = 0; // no objects when program starts
void main() { omega om1("Harriet"); // uses one-arg constructor omega om2("Bernard"); omega om3("Damien"); om3 = om2 = om1; // chained assignment } The output from the program looks like this: 1-arg constructor has created Harriet-1 1-arg constructor has created Bernard-2 1-arg constructor has created Damien-3
Harriet-1 assigned to Bernard-2, making Harriet-2 1-arg constructor has created Harriet-4 Harriet-4 assigned to Damien-3, making Harriet-3 1-arg constructor has created Harriet-5 Destructor has destroyed Harriet-5 Destructor has destroyed Harriet-4 Destructor has destroyed Harriet-3 Destructor has destroyed Harriet-2 Destructor has destroyed Harriet-1 The first three lines are created by the first three invocations of the one-argument constructor in main(). Then the chained assignment statement is executed: om3 = om2 = om1; Each of the two assignment operators in this statement causes an assignment of the name variable and the invocation of the one-argument constructor to create the temporary variable that will be assigned. These temporary objects are Harriet-4 and Harriet-5. Both are destroyed at the end of the assignment statement. When the program ends, the three original objects with their new names (Harriet-1, Harriet-2, and Harriet-3) are destroyed as well. All the objects end up with the name Harriet because either they all have been assigned to Harriet1 or have been assigned to an object that was itself assigned to Harriet-1.
Future Improvement
In the ASSIGN2 program, the OAO is returned by value, using the one-argument constructor. This is not very efficient, because it requires the creation (and destruction) of a new object every time you use the assignment operator. Its better to be able to return by reference, which does not require the creation of a new object. But you cant return a local object by reference, because local objects are destroyed when the function returns. It would be really nice to return a reference to the object that called the OAO (return a reference to myself). How can you do that? The magic expression *this solves the problem; Ill mention it again in Session 8 at the end of this chapter. For now, keep in mind that the current version of the OAO can be improved. Another improvement to OAOs that you can make later, when you learn about pointers, is to check for selfassignment: that is, assigning an object to itself: obj1 = obj1; // uh, oh If nothing else, such a statement may indicate that the class user made a mistake; for some classes, it can cause serious internal problems. For now, assume that class users dont make this mistake.
Quiz 6 1. Overloading the assignment operator is the only way to a. chain assignment operators in a single statement, such as a=b=c=d. b. perform assignment on objects. c. assign one object to another when not all the data should be copied. d. assign an object of one type to an object of another type. e. initialize an object with another object when its created. 2. Classes in which an overloaded assignment operator might be helpful include those a. that contain pointers as member data.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 287 of 701
b. that number each object. c. with static data. d. with const data. e. that allocate system resources such as memory. 3. An overloaded assignment operator a. is used to initialize objects in statements such as alpha b = a; b. copies all the data without modification from the source object to the target object. c. copies data or does not copy it, as appropriate, from the source object to the target object. d. performs any action the class creator wants when one object is assigned to another. e. operates the same way as the default assignment operator, except for large objects. 4. Returning an object from an OAO makes it possible to a. chain assignment operators, as in a=b=c=d. b. assign an object of one type to an object of another type. c. assign one object to another even when not all the data should be copied. d. display information from within the OAO. e. exit from the OAO. 5. Returning an object by value from an OAO a. cannot be done with objects that number themselves. b. is not a perfect solution. c. is not acceptable to the compiler. d. causes a temporary object to be created. e. returns the object for which the function was called. Exercise 1
Its not really necessary, but overload the assignment operator for the airtime class.
Exercise 2
Remove the name variable from the omega class and create overloaded versions of the assignment operator and copy constructor for the new version of the class.
int& safearay::access(const int& n) // access() function { // returns by reference if( n< 0 || n>=SIZE ) { cout << "\nIndex out of bounds"; exit(1); } return arr[n]; // return the value by reference } void main() { int j; int temp; safearay sa; const int LIMIT = 20; for(j=0; j<LIMIT; j++) sa.access(j) = j*10; for(j=0; j<LIMIT; j++)
// make a safe array // amount of data // insert elements // *left* side of equals // display elements Page 289 of 701
{ temp = sa.access(j); // *right* side of equals cout << "\nElement " << j << " is " << temp; } } In main(), the statement sa1.access(j) = j*10; causes the value j*10 to be placed in array element arr[j], the return value of the function. Here the function appears on the left of the operator. The statement temp = sa.access(j); causes the value of arr[j] to be assigned to temp. Here the function appears to the right of the operator.
Figure 6-7 Syntax of the overloaded [] operator Like the access() function in ARROVER1, operator[]() must return by reference so it can be used on the left of the equal sign. Listing 6-15 shows ARROVER2. Listing 6-15 ARROVER2 // arrover2.cpp // creates safe array (index values are checked before access) // uses overloaded [] operator for put and get #include <iostream.h> #include <process.h> // for exit() class safearay { private: enum {SIZE=100}; int arr[SIZE]; public: int& operator [](int n); };
int& safearay::operator [](int n) // overloaded [] { // returns by reference if( n< 0 || n>=SIZE ) { cout << "\nIndex out of bounds"; exit(1); } return arr[n]; } void main() { int j; int temp; safearay sa; const int LIMIT = 20; for(j=0; j<LIMIT; j++) sa[j] = j*10;
// make a safe array // amount of data // insert elements // *left* side of equal sign Page 291 of 701
for(j=0; j<LIMIT; j++) // display elements { temp = sa[j]; // *right* side of equal sign cout << "\nElement " << j << " is " << temp; } } // end main() } // end program In this version of main(), I can use the natural subscript expressions sa[j] = j*10; and temp = sa[j]; for input and output to the safe array. They are used in exactly the same way as normal subscript operators are. The output from ARROVER2 is the same as that from ARROVER1. Of course, you dont need to use operator[] exactly as Ive shown here. If youre only going to read from an array, for example, theres no reason for it to return by reference. Feel free to adapt any of the overloaded operators to do exactly what you want.
Quiz 7 1. Overloading the subscript operator allows you to a. access elements of normal C++ arrays, such as intarray[10]. b. use the array name on the right side of the equal sign. c. access elements of an array object, using []. d. make an array class that, to a class user, acts like a normal C++ array. e. make an array that, internally, behaves differently than a normal C++ array. 2. In arrover1, all array objects of the safearay class hold the same amount of data because a. overloaded operators cant handle large amounts of data. b. thats how LIMIT is defined in main(). c. thats the size specified within the class. d. all objects of the same class are necessarily the same size. e. other options would require dynamic memory allocation. 3. An overloaded subscript operator should a. take one argument and return a value by reference. b. take no arguments and return a value by reference. c. take no arguments and return void. d. take no arguments and return by value. e. calculate the index of the element to be accessed.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 292 of 701
4. Returning by reference from an overloaded subscript operator means that a. you can use the operator on the left side of the equal sign. b. you can use the operator on any data type. c. no copy is made of the returned data item. d. only objects can be returned. e. only objects of the same class as the operator can be returned. 5. You might reasonably want to create an array class that a. uses an overloaded subscript operator that always returns the last item inserted in the array. b. expands automatically as you add data. c. automatically sorts data as you enter it. d. allows the user to choose the size of the array when its defined. e. models a write-only array. Exercise 1
Add a one-argument constructor to the safearay class so the user can specify the highest element that will be used in the array. (For example, on ARROVER2, only the first 20 elements are used.) Then have the overloaded subscript operator check that the index does not exceed this limit (called LIMIT in ARROVER2), rather than checking if it exceeds the total size of the array.
Exercise 2
Modify the safearay class by adding a new member function, called add(), that allows the class user to insert a value at the end of the array, where end is defined as the highest-numbered element to have been accessed. In other words, if elements 0 to 6 are already filled, then sa.add(127); will insert the value 127 into element 7. This function is convenient because the user doesnt need to supply an index number. Youll probably need a new member data item (called top?) that records the number of the highest element accessed.
Constant Arguments
I already discussed (in Chapter 4, Session 7) the desirability of passing arguments, when they are objects, by reference. Because an object may be large, passing by reference avoids the time and memory space necessary to create the copy of an object thats generated automatically when an argument is passed by value. An argument passed by reference should be passed as a const to guarantee that it wont be accidentally altered by the function. (Of course, this isnt true if the function is supposed to modify the argument.) Almost all arguments to C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 293 of 701
overloaded operators are objects and should be passed by const reference. Ive done this in most of the examples in this chapter. An argument that is a basic type such as int can be passed by value without incurring significant overhead. As Ive noted, arguments passed by value cant be modified by the function and so dont need to be const.
Constant Functions
I discussed making an entire function const in Chapter 5, Session 7. A const function guarantees not to modify the object for which it is called. You can call a const member function, but not a non-const one, for a const object. A constant function is indicated by the keyword const following the parentheses of the function name: void afunc() const // constant function { } // won't modify its object
// 0 to 23 // 0 to 59
// no-arg constructor airtime() : hours(0), minutes(0) { } // two-arg constructor airtime(int h, int m) : hours(h), minutes(m) { } void display() const // output to screen { cout << hours << ':' << minutes; } void get() // input from user { char dummy; cout << "\nEnter time (format 12:59): "; cin >> hours >> dummy >> minutes; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 294 of 701
} // overloaded + operator airtime operator + (const airtime& right) const { airtime temp; // make a temporary object temp.hours = hours + right.hours;// add data temp.minutes = minutes + right.minutes; if(temp.minutes >= 60) // check for carry { temp.hours++; temp.minutes -= 60; } return temp; // return temporary object by value } }; // end class airtime void main() { airtime at1, at2; const airtime noon(12, 0); cout << "Enter first airtime: "; at1.get(); at2 = noon + at1; // overloaded + operator // adds at1 to noon
// constant object
cout << "sum = "; at2.display(); // display sum } In main(), the airtime object noon is declared to be const. The statement at2 = noon + at1; adds another airtime value, at1, to noon. Because operator+() is a const function, its all right to call it for a const airtime. If this operator were not const, attempting to use it on a const object would elicit a compiler warning. This mechanism keeps an overloaded operator from accidentally modifying a const object for which it is called. Notice that, whether operator+() is const or not, its perfectly all right to say at2 = at1 + noon; because in this case, the operator is called for the nonconstant object at1, whereas noon is the argument to the function and is already declared const .
Return Values
When an operator produces a new value, an object must be created to hold this value. For example, in the ADDAIRCO program, operator+() adds its argument to its object and must store the sum somewhere. Usually its stored in a temporary object defined in the function. Because its temporary, this object cannot be returned by reference, but must be returned by value. For this reason, the overloaded arithmetic operators return by value, as operator+() does for the airtime class. However, things are simplified for some other kinds of operators because they can return a copy of themselves. This is true when the result of an operation is placed in the object for which the operator is called. This happens in the assignment operators and a few others.
public: // no-arg constructor airtime() : hours(0), minutes(0) { } // 2-arg constructor airtime(int h, int m) : hours(h), minutes(m) { } void display() const // output to screen { cout << hours << ':' << minutes; } void get() // input from user { char dummy; cout << "\nEnter time (format 12:59): "; cin >> hours >> dummy >> minutes; } // overloaded += operator airtime& operator += (const airtime& right) { hours += right.hours; // add argument to us minutes += right.minutes; if(minutes >= 60) // check for carry { hours++; minutes -= 60; } return *this; // return by reference } }; // end class airtime void main() { airtime at1, at2, at3; cout << "Enter first airtime: "; at1.get(); cout << "Enter second airtime: "; at2.get(); at1 += at2; // overloaded += operator // adds at2 to at1 cout << "\nat1+=at2 = "; at1.display(); // display result at3 = at1 += at2; // do it again, use return value cout << "\nat3 = "; at3.display(); // display result } The main() here is the same as in PLEQAIR. The class user wont need to change anything to use the improved operator +=() function, but the function is more efficient. Notice that operator +=() itself cannot be made const because the object for which it is called (i.e., a in a+=b) is modified. You cant call this operator for a constant object.
// overloads the ++ operator, prefix version uses *this #include <iostream.h> class airtime { private: int hours; int minutes; public:
// 0 to 23 // 0 to 59
// no-arg constructor airtime() : hours(0), minutes(0) { } // 2-arg constructor airtime(int h, int m) : hours(h), minutes(m) { } void display() const // output to screen { cout << hours << ':' << minutes; } void get() // input from user { char dummy; cout << "\nEnter time (format 12:59): "; cin >> hours >> dummy >> minutes; } airtime& operator++ () // overloaded prefix ++ operator { ++minutes; if(minutes >= 60) { ++hours; minutes -= 60; } return *this; // return our object by reference } airtime operator++ (int) // overloaded postfix ++ operator { airtime temp(hours, minutes); // save original value ++minutes; // increment this object if(minutes >= 60) { ++hours; minutes -= 60; } return temp; // return old original value } }; // end class airtime //////////////////////////////////////////////////////////////// void main() { airtime at1, at2; // make two airtimes at1.get(); // get value for one at2 = ++at1; // increment (prefix) and assign C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 298 of 701
at2 = at1++; // increment (postfix) and assign cout << "\nat1="; at1.display(); // display incremented value cout << "\nat2="; at2.display(); // display assigned value } The prefix version of ++ increments its object before its value is used so it can return its own value. But where before I used return airtime(hours, minutes); I can now say return *this; This allows me to use a reference return and to avoid copying any objects at all. As I noted earlier, the postfix version must return the value its object had before it was incremented, so theres no choice but to save this value in a temporary object and return this object after the real object has been incremented. Because a temporary object is being returned, it must be returned by value, so I cant make a corresponding improvement in the postfix ++ operator.
Quiz 8 1. Which of the following are advantages of declaring an overloaded operator to be a const function? a. It cannot modify the object for which it was called. b. Its return value cannot be modified. c. It can be called for a constant object. d. It cannot modify its argument.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 299 of 701
e. It cannot modify any objects of its class. 2. In the airtime class, the operator+() function a. adds the object that called it to its argument. b. adds its argument to the object that called it. c. should return by reference. d. returns the value of its own object. e. returns the result of the addition. 3. Within a member function, the expression *this always refers to a. the object passed as an argument to the function. b. the address of an object. c. a temporary object created within the function. d. the object that called the function. e. the object that will be returned from the function. 4. In the overloaded assignment operator +=, a. the object for which the operator is called may be modified. b. the argument to the operator may be modified. c. nothing is modified but the return value. d. the return value is the sum of the object that called the operator and its argument. e. the return value is the same as that of the object that called the operator. 5. The prefix version of the increment operator ++ a. can return by reference. b. modifies the object that called it. c. returns the value of the object that called it. d. returns a temporary object that has been incremented. e. returns a temporary object that has not been incremented. Exercise 1
Overload the >= operator for the airtime class, using the various improvements shown in this session. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 300 of 701
Exercise 2
Overload the *= operator for the English class, again using the various improvements shown in this session. Multiply by type int (how many feet and inches in five boards 2-6 long?) because it gets complicated to generate square feet, which are not the same as feet.
Summary: Chapter 6
Youve seen how the normal C++ operators, such as +, +=, <, and ++, can be made to work with class objects as well as with basic C++ types such as int and float. This involves overloading the operators so they cause different functions to be executed, depending on the data type of their operands. The advantage of operator overloading is purely visual: Source code may be easier to read and write when operators are used instead of functions such as add(). However, using equivalent functions produces the same results. It follows that overloaded operators should not be used unless they make the listing easier to read. They are not helpful when they do something that is not intuitively similar to their operation on basic types. Using the + operator to display something or, worse yet, to subtract two objects is a bad idea. With binary operators, as in a+b, the object that calls the overloaded function is to the left of the operator; the object to the right is an argument to the operator. Unary operators may appear on either side of the object that calls them: ++a and a++. Unary operators take no argument. Various techniques can be used to make overloaded operators safer and more efficient. Most arguments should be const and passed by reference, to avoid the creation of extraneous object copies. The operators themselves should be const whenever they dont modify the object that called them. The *this expression allows the value of the object that called the operator to be returned by reference, which again avoids the creation of additional copies. As you will find when you learn about friend functions, you can make an additional improvement to overloaded binary operators. Also, youll see in Chapter 10 how the << and >> operators can be overloaded to perform input and output with class objects, as they do with basic types.
End-of-Chapter Discussion
Estelle: George: Theres some pretty subtle stuff going on. I like overloaded operators, but I dont like worrying about the most efficient possible way to write them. Yeah, its a drag. Youve got to think about every little thing. Does an operator modify its own object? Does it modify its argument? Can it return the value of its own object or does it create a new value? Its really too much. Well, you dont need to worry about all that stuff if you dont want to. Pass all your arguments by value, return everything by value, dont make anything const, forget *this, and itll still work. Really? I wouldnt kid you. It just makes your program faster and smaller and safer if you do it right. Why are we talking about all this efficiency stuff now? Dont these rules apply to regular functions as well as overloaded operators? Sure. But its easier to be specific about overloaded operators because they do pretty much the same thing no matter what class theyre in. It seems like there are situations where you can do something several different ways. Like when you convert one class to another, you could use a constructor in one class or an overloaded cast operator in the other class. You could probably overload the equal operator, too. I dont think there are any rules about which approach to use. It depends on the circumstances, like which class is already written, or which class you have the source code for, or which classes have peek functions. The nice thing about C++ is you can try out stuff and the compiler will pretty much tell you if its right. Its a lot smarter than the C compiler. If you can compile it error free in C++, itll probably run.
George: Don:
Estelle: Don:
CHAPTER 7 INHERITANCE
Inheritance is the second central concept behind object-oriented programing, after classes themselves. In fact, although conceptually inheritance is secondary to the idea of classes, practically it is the driving force behind OOP. Why? Because inheritance makes reusability possible. Reusability means taking an existing class and using it in a new programming situation. By reusing classes, you can reduce the time and effort needed to develop a program, and make software more robust and reliable. If you develop software for money, you cant afford to overlook the savings made possible by reusability. Inheritance also plays a major role in OOP design. It allows you to conceptualize a complex program using a new relationship between program elements. In this chapter, Ill first discuss the conceptual underpinning of inheritance and then Ill delve into some of the slippery details, including how to handle constructors in inheritance and multiple inheritance. Along the way, youll see plenty of examples of how to use this key feature of OOP.
Reusability
To understand why inheritance is important, you need to look back at the history of reusability. Programmers have always tried to avoid writing the same code twice, or reinventing the wheel. Inheritance is the latest solution to this problem, and the most powerful.
Rewriting Code
The earliest approach to reusability was simply rewriting existing code. You have some code that works in an old program, but doesnt do quite what you want in a new project. You paste the old code into your new source file, make a few modifications to adapt it to the new environment, and youre off and running. Except that the modifications you made, in all probability, have introduced new bugs. Now you must debug the code all over again. Often youre sorry you didnt just write new code.
Function Libraries
To reduce the bugs introduced by modification of code, programmers attempted to create self-sufficient program elements in the form of functions. The hope was that functions could be written that were general enough that they could be used without modification in a variety of programming situations. Software companies gathered groups of such functions together into function libraries and sold them to other programmers. Function libraries were a step in the right direction, but, as I discussed in Chapter 1, functions dont model the real world very well, because they dont include important data. All too often, functions require modification to work in a new environment; it was common for the purveyors of function libraries to provide source code to make such modifications easier. But again, the modifications introduced bugs.
Class Libraries
A powerful new approach to reusability appears in OOP: the class library. Because a class more closely models a real-world entity, it needs less modification than functions do to adapt it to a new situation. More importantly, OOP provides a wayin effectto modify a class without changing its code. This apparent contradiction is achieved by using inheritance to derive a new class from the old one. The old class (called the base class) is not modified, but the new class (the derived class) can use all the features of the old one and additional features of its own. Figure 7-1 shows how this looks.
Old-fashioned procedural languages such as C and Pascal model this has a relationship using structures. A structure can be composed of variables and other structures. However, there is another relationship procedural languages cannot model: the kind of relationship.
Inheritance Syntax
The simplest example of inheritance requires two classes: a base class and a derived class. The base class does not need any special syntax. The derived class, on the other hand, must indicate that its derived from the base class. This is done by placing a colon after the name of the derived class, followed by a keyword such as public and then the base class name, like this: class Base { // member data and functions }; class Derv : public Base { // member data and functions }; The colon (like the arrow in inheritance diagrams) means is derived from. Thus, the class Derv is derived from the class Base and inherits all the data and functions that Base has (although I dont show any here). Ill leave an explanation of the keyword public until later. (Its called an access specifier, and the other possibilities are private and protected, but public is by far the most commonly used.) Figure 7-2 shows the syntax used in inheritance.
Figure 7-2 Derived class syntax Lets look at a more detailed example. Listing 7-1 has a Parent class, and a Child class derived from it. Listing 7-1 INHERIT // inherit.cpp // skeleton classes demonstrate inheritance #include <iostream.h> class Parent { private: float flov; // Parent's data public: void pget() // get Parent's data { cout << "\n Enter float value: "; cin >> flov; } void pdisplay() // display Parent's data { cout << "flov=" << flov; } }; class Child : public Parent { private: int intv; // Child has its own data public: void cget() // get data for Child { pget(); // call function in Parent cout << " Enter integer value: "; cin >> intv; // get Child-specific data } void cdisplay() { pdisplay(); // call function in Parent cout << ", intv=" << intv; // show Child-specific data } }; void main() { Child ch; // make a child object cout << "Requesting data for child object"; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 305 of 701
ch.cget(); // get data for it cout << "Data in child object is "; ch.cdisplay(); // display its data } The Parent class has a data item intv, along with pget()and pdisplay() member functions. The Child class has a data item flov and similar cget() and cdisplay() functions. In main(), the program creates an object of class Child, gets data for it from the user, and then displays the data. Heres how the interaction with the program looks: Requesting data for child object Enter float value: 456.789 Enter integer value: 123 Data in child object is flov=456.789, intv=123 This may seem mysterious. Although the object ch of class Child contains only an integer data item, the output shows it has successfully stored and retrieved a float item as well. How does this happen?
Inheriting Attributes
An object of a derived class inherits all the member data and functions of the base class. Thus the Child object ch contains not only a data item intv, but a data item flov as well. The ch object can also access, in addition to its own member functions cget() and cdisplay(), the member functions from Parent, which are pget() and pdisplay().
for functions that do essentially the same thing. It would be nice if both these functions had the same name, so the name would be clearer and easier to remember (among other benefits). It turns out that its perfectly all right to overload functions in base and derived classes. If I name both functions display(), well have a situation like this: class Parent { void display() { } }; class Child : public Parent { void display() { // display(); // bad: calls display() in Child Parent::display(); // good: calls display() in Parent } }; The compiler has no trouble distinguishing the two versions of display() because theyre in different classes. However, if I use the name display() in a derived class function, the compiler assumes I mean the function of that name in the derived class. That would be disastrous from within display(), because a function calling itself over and over would eventually crash the program. In the derived class, to refer to the version of the function thats in the base class, I must use the scope resolution operator and the base class name: Parent::display(); This makes it clear to the compiler which function I mean. Listing 7-2, a revised version of the INHERIT example, shows how it looks when I overload the member functions, calling both display functions display() and both input functions get(). Listing 7-2 INHERIT2 // inherit2.cpp // skeleton classes demonstrate inheritance // uses overloaded functions #include <iostream.h> class Parent { private: float flov; public: void get() { cout << "\n Enter float value: "; cin >> flov; } void display() { cout << "flov=" << flov; } }; class Child : public Parent { private: int intv; public: void get() { Parent::get(); cout << " Enter integer value: "; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 307 of 701
cin >> intv; } void display() { Parent::display(); cout << ", intv=" << intv; } }; void main() { Child ch; cout << "Requesting data for child object"; ch.get(); cout << "Data in child object is "; ch.display(); } This program operates the same way as INHERIT, but using overloaded functions makes it easier for the class user to remember the function names. Table 7-1 summarizes how overloaded and normal functions can be accessed from base and derived classes. I assume theres a basefunc() member function in the base class, a dervfunc() function in the derived class, and an overloaded func() in both classes. Table 7-1 Function accessibility from base and derived classes Function Is Member of | Overload Status | To Access Function from Base Class | To Access Function from Derived Class Base class | Different names | basefunc() | basefunc() Base class | Overloaded | func() | Base::func() Derived class | Different names | Function is unknown |dervfunc() Derived class | Overloaded | Function is unknown | func() If functions are not overloaded, the derived class can access functions in the base class using their name alone. For overloaded functions, the derived class must use the scope resolution operator to access functions in the base class. Of course, inheritance only works one way. The base class doesnt know anything about the derived class and cant access any of its members.
Quiz 1 1. Inheritance facilitates reusability because a. child objects cannot be modified. b. the base class need not be modified to derive a new class. c. programming objects are more like real-world objects. d. objects of the base class can be treated as objects of the derived class. e. derived class objects inherit only the desirable features of the base class. 2. Inheritance helps with program organization by a. allowing the use of gives birth to arrows in organization diagrams.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 308 of 701
b. modeling the has a relationship. c. exactly replicating human family trees. d. providing a new kind of relationship between objects. e. allowing a class to consist of objects of other classes. 3. If you want the functions in a derived class to access the data in a base class directly, you must a. make the base class data private. b. not make the base class data private. c. use a private base class function. d. use a nonprivate base class function. e. make the data public and call a public access function. 4. To access a public nonoverloaded base class function bafunc(), a statement in a derived class function defunc() uses the expression a. Base::bafunc(); b. Derv::bafunc(); c. bafunc(); d. defunc(); e. Im outta luck. 5. To access the public function func() in the base class, a statement in the derived class function func() uses the statement a. This is impossible. b. Base(); c. Derv(); d. func(); e. Base::func();
Due to its abstract nature, this session has no exercises.
inheritance as a basic way to relate program components. It is less relevant to the concept of reusability, which Ill focus on in the next session. Suppose I want to program a database of company employees. Its common for employees to be divided into different categories. In this particular imaginary company, Ill assume there are managers, scientists, and laborers. (Forget for the moment about secretaries, salespeople, accountants, and so on.)
Class Hierarchy
When you encounter a situation in which different categories of things can be described as kinds of something else, youre looking at a situation that can be modeled in a program as inheritance. Managers, scientists, and laborers are kinds of employees. Heres how that looks in C++ in skeleton form: class employee { ... }; class manager : public employee { ... }; class scientist : public employee { ... }; class laborer : public employee { ... }; The three classes manager, scientist, and laborer are derived from the employee class. All employees, no matter what derived class they belong to, share some common elements. For purposes of the database, lets assume that they all have a name and an ID number. In inheritance, such common elements are placed upstream, in the base class. The derived classes have their own individual characteristics. The characteristics that are important about managers are titles, such as President or Vice-President, and golf club dues. For scientists, the important data is the number of publications for which the scientist has written scholarly articles. Laborers have no distinguishing characteristics other than their name and ID number. (A database program does not always mirror the diversity of real people, with all their hopes and dreams.) Figure 7-3 shows how this looks.
Listing 7-3 EMPINH // empinh.cpp // models employee database using inheritance #include <iostream.h> class employee // employee class { private: enum { LEN=30 }; // maximum length of names char name[LEN]; // employee name unsigned long number; // employee number public: void getdata() { cout << "\n Enter last name: "; cin >> name; cout << " Enter number: "; cin >> number; } void putdata() { cout << "\n Name = " << name; cout << "\n Number = " << number; } }; class manager : public employee // management class { private: enum { LEN=40 }; // maximum length of titles char title[LEN]; // "vice-president" etc. double dues; // golf club dues public: void getdata() { employee::getdata(); cout << " Enter title: "; cin >> title; cout << " Enter golf club dues: "; cin >> dues; } void putdata() { employee::putdata(); cout << "\n Title = " << title; cout << "\n Golf club dues = " << dues; } }; class scientist : public employee // scientist class { private: int pubs; // number of publications public: void getdata() { employee::getdata(); cout << " Enter number of pubs: "; cin >> pubs; } void putdata() { employee::putdata(); cout << "\n Number of publications = " << pubs; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
} }; class laborer : public employee // laborer class { }; void main() { manager m1, m2; scientist s1; laborer l1; cout << endl; cout << "\nEnter data for manager 1"; // get data for m1.getdata(); // several employees cout << "\nEnter data for manager 2"; m2.getdata(); cout << "\nEnter data for scientist 1"; s1.getdata(); cout << "\nEnter data for laborer 1"; l1.getdata(); cout << "\nData on manager 1"; // display data for m1.putdata(); // several employees cout << "\nData on manager 2"; m2.putdata(); cout << "\nData on scientist 1"; s1.putdata(); cout << "\nData on laborer 1"; l1.putdata(); } The member functions of manager and scientist call base class functions in employee to get the employee name and number from the user and to display them. They also handle their own particular data, title, and dues for managers and number of publications for scientists. The laborer class relies completely on employee for all data and member functions.
Abstract Classes
You might think I could simply use employee objects instead of laborer objects, because they have the same data and functions. However, this violates the spirit of the programs organization. As used here, the employee class is not one from which actual objects will be instantiated. The only purpose of employee is to serve as a general class from which other classes are derived. All real employees are members of a more specific category. As an analogy, imagine you own a specific animal called Toby. If someone asked you what Toby is, you probably wouldnt say, Oh, hes a mammal. Everyone would say, Yes, but what kind of mammal? All real animals must come from a more specific category than simply mammals. (It turns out Toby is a hamster.) Mammal is a category that has no members itself, but serves only as a generalization. A class from which you dont intend to instantiate any objects, but is used only as a base class for other classes is called an abstract class. One of the advantages of using an abstract class for employee is that if you decide, sometime after the employee class hierarchy is designed, to add a data item to the laborer class, then you can modify laborer without modifying employee, which would potentially affect all employees derived classes. Abstract classes are a powerful concept in OOP, and Ill return to them later.
Enter title: President Enter golf club dues: 500000 Enter data for manager 2 Enter last name: Blakley Enter number: 222 Enter title: Vice-President Enter golf club dues: 100000 Enter data for scientist 1 Enter last name: Lee Enter number: 333 Enter number of pubs: 99 Enter data for laborer 1 Enter last name: Jones Enter number: 87647162 Then the program plays it back: Data on Manager 1 Name = Montegue Number = 111 Title = President Golf club dues = 500000 Data on Manager 2 Name = Blakley Number = 222 Title = Vice-President Golf club dues = 100000 Data on Scientist 1 Name = Lee Number = 333 Number of publications = 99 Data on Laborer 1 Name = Jones Number = 87647162
Quiz 2 1. The purpose of the EMPINH program is to demonstrate a. inheritance. b. composition. c. classes. d. the kind of relationship. e. reusability. 2. In EMPINH, the __________ class is __________ the ___________ class(es). a. scientist, the base class for, employee b. laborer, derived from, employee c. employee, composed of, manager, scientist, and laborer
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 313 of 701
d. employee, derived from, laborer e. laborer, the same as, employee 3. An object of the scientist class contains instance data representing a. the employee name, employee number, and number of publications. b. only the number of publications. c. only the employee name and employee number. d. the title and golf club dues. e. the location of the object. 4. From the manager class, you call the display() function in employee to a. display all the data on a manager. b. satisfy the formal requirements of the compiler. c. display the managers name and number. d. display the managers title and golf club dues. e. let the compiler know youre dealing with an object derived from the employee class. 5. An abstract class is a. one whose objects are identical to those of some derived class. b. one from which no objects will be instantiated. c. one that contains common elements of derived classes. d. any base class. e. any derived class. Exercise 1
Add a secretary class to the hierarchy in EMPINH. The quality unique to this class is typing speed in words per minute, an integer value. In main(), create two objects of this class and input and output appropriate data.
Exercise 2
Create a class hierarchy with a vehicle base class. Derived classes will be trucks, cars, buses, and motorcycles. All vehicles have instance data representing the vehicle identification number (type long) and the gross vehicle weight. In addition, trucks have a load capacity (in pounds, type float), cars have a body style (a string, such as sedan, sports car, or station wagon), and busses have a passenger capacity (type int). Motorcycles have no additional instance data.
Reusability
Imagine that one company (or group of programmers) developed the original Stack class. It spent many years developing and debugging the class, and the class is now a reliable programming component. Later, another programming group in another company obtains a library containing the Stack class. It is impressed with the classs speed and versatility, but the company discovers that it needs better error protection. This second group therefore derives a new class from the old one. Because it doesnt change anything in the original Stack class, the original features will continue to work well. The new class simply adds some new features; it is only these that need to be debugged.
}; //////////////////////////////////////////////////////////////// void main() { Stack2 s; // create a Stack2 object s.push(11); // push 3 items onto it s.push(12); s.push(13); cout << s.pop() << endl; // pop items and display them cout << s.pop() << endl; cout << s.pop() << endl; cout << s.pop() << endl; // oops, popped one too many }
A Smarter Object
The Stack2 class has no data of its own. Its role is to wrap the pop() and push() functions of the Stack class with its own improved pop() and push(). These improved functions start with if statements that check to be sure the class user is not popping or pushing too many items. Then they call the base class versions of pop() or push() to carry out the actual data storage and retrieval. The accessible members of a class are called its interface, because theyre what the class user interacts with. Here the interface for Stack2 is just the same as for Stack. The Stack2 class has in effect disguised itself as the Stack class. However, a Stack2 object is smarter internally. In main(), the program creates a Stack2 object and pushes three items onto it. Then, mistakenly, it pops four items off. Fortunately its using the new improved Stack2 class, which duly complains. Heres the output from STACKINH: 13 12 11 Error: stack underflow
Quiz 3 1. The purpose of the STACKINH example is to demonstrate a. inheritance. b. composition. c. the kind of relationship. d. a class hierarchy. e. reusability. 2. The class creator may make the data in the Stack class protected a. so that it cannot be accessed by functions in other classes. b. so that it can be accessed by objects of derived classes. c. if other classes will be derived from Stack. d. if Stack is derived from another class. e. to prevent other classes from being derived from Stack. 3. The interface between a class and the class user consists of a. the class member functions accessible from a derived class. b. the classs public member functions or data.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 317 of 701
c. the class specification. d. any class member functions and variable definitions. e. calls in main() to class member functions. 4. Which of the following are true? a. If the base class has a constructor, the derived class must also have one. b. If the derived class has a constructor, the base class must also have one. c. If the base class has no constructor, the derived class constructor will be called. d. If the derived class has no constructor, the base class constructor will be called. e. The compiler will look for multiple approaches to a problem. 5. The protected access specifier a. allows access to functions in the program. b. allows access to functions in the class. c. allows access to functions in derived classes. d. is used in the base class. e. is used in the derived class. Exercise 1
In Exercise 2 in Chapter 3, Session 2, I discussed a peek() function that allows the user of a stack to read the data item from the top of the stack without popping it off. Write such a function for the Stack class and then write an overloaded version for the Stack2 class that checks to be sure the stack is not empty.
Exercise 2
A queue is like a stack except that it works on a first-in-first-out (FIFO) principle instead of the last-in-first-out (LIFO) principle of a stack. Its like the line at a bank tellers window: The first person to join the tail of the line is the first person to reach the head of the line and be removed. Rewrite the Stack and Stack2 classes in the STACKINH program so they model queues instead of stacks; you can call the new classes Queue and Queue2. Youll need two index variables, head and tail, instead of top. The head index will be incremented when an item is pushed onto the queue, and the tail index will be incremented when an item is popped off. These indexes will follow each other through the array as items are pushed and popped. When either one gets to the end of the array, it must wrap around to the beginning.
Figure 7-4 Inheritance and subobjects The constructors for all the subobjects of an object are called before the constructor for the object itself. The next example demonstrates this point. Listing 7-5 shows INCONDES. Listing 7-5 INCONDES // incondes.cpp // tests constructors and destructors in inheritance #include <iostream.h> //////////////////////////////////////////////////// class Parent { public: Parent() { cout << "\n Parent constructor"; } ~Parent() { cout << "\n Parent destructor"; } }; //////////////////////////////////////////////////// class Child : public Parent { public: Child() { cout << "\n Child constructor"; } ~Child() { cout << "\n Child destructor"; } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 319 of 701
}; //////////////////////////////////////////////////// void main() { cout << "\nStarting"; Child ch; // create a Child object cout << "\nTerminating"; } This program specifies a Parent class and a Child class derived from Parent. In main(), it then creates an object of the Child class. Ive installed explicit constructors and destructors in both classes that identify themselves when they execute. Heres the output from INCONDES: Starting Parent constructor Child constructor Terminating Child destructor Parent destructor As you can see, the base class constructor is called first; then the derived class constructor is called. When the Child object goes out of scope at the end of the program, the destructors are called in reverse order: The derived object is destroyed first, then the base class object. The compiler arranges for all these constructors and destructors to be called automatically, so you dont need to worry about it. Even if you havent defined explicit constructors and destructors as I did here, the compiler will call the implicit (invisible) ones that actually create and destroy objects and subobjects. You should be aware that this activity is taking place, whether or not you write your own explicit constructors and destructors. Incidentally, an objects implicit constructor is always called before the explicit one you define. When your explicit constructor starts to execute, your object and all its subobjects already exist and have been initialized.
No Arguments
You cant use a constructor with arguments unless its explicitly defined in the specific class from which youre instantiating an object. That is, even if theres an n-argument constructor in the base class, you must still write an nargument constructor in the derived class. Listing 7-6, CONSINH1, shows how this works. Listing 7-6 CONSINH1 // consinh1.cpp // demonstrates constructors and inheritance //////////////////////////////////////////////////////////////// class Mu // base class { private: int mudata; // data public: Mu() : mudata(0) // no-arg constructor { } Mu(int m) : mudata(m) // one-arg constructor { } }; //////////////////////////////////////////////////////////////// class Nu : public Mu // derived class { // no constructors }; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 320 of 701
//////////////////////////////////////////////////////////////// void main() { Nu n1; // can use the no-arg constructor in Mu // Nu n2(77); // error: cannot use one-arg constructor } You can write the specification for class Nu, which is derived from Mu, without any constructors; the compiler doesnt complain. You can also define an object of Nu if you dont use any arguments, as seen in the first statement in main(). But you cant define a Nu object that takes arguments unless theres an appropriate constructor in Nu itself. The compiler wont substitute the constructor from the base class because it cant be sure how you want to handle the arguments.
Arguments
If you add an explicit one-argument constructor to the derived class Mu, then you must also add a no-argument constructor, just as you must with any class (assuming youre actually going to create objects with this constructor). Listing 7-7 shows the previous program, rewritten as CONSINH2, with these additional constructors. Listing 7-7 CONSINH2 // consinh2.cpp // demonstrates constructors and inheritance class Mu { private: int mudata; // data public: Mu() : mudata(0) // no-arg constructor { } Mu(int m) : mudata(m) // one-arg constructor { } }; //////////////////////////////////////////////////////////////// class Nu : public Mu { public: Nu() // no-arg constructor { } Nu(int n) : Mu(n) // one-arg constructor { } }; //////////////////////////////////////////////////////////////// void main() { Nu n1; // calls no-arg constructor Nu n2(77); // calls 1-arg constructor } In main(), the program successfully defines Nu objects using both the no-argument and the one-argument constructors. The one-argument constructor passes its argument along to the constructor in Mu using a new syntax: the initializer list.
call a constructor. Actually these operations are conceptually identical, so its not surprising they use the same syntax. In one case, a class object is initialized and in the other, a variable of a basic type is initialized, but C++ treats class objects and basic-type variables in the same way. Here the derived class constructor simply passes the argument n along to the base class constructor. It takes no action of its own, so the braces are empty. This is a common situation in derived classes: Constructors have an empty function body because all their work is done in the initialization list.
cout << "\nat0 = "; at0.display(); airtime2 at1(10, 45); // ok (2-arg constructor) cout << "\nat1 = "; at1.display(); airtime2 at2(10, 65); // user error (2-arg constructor) cout << "\nat2 = "; at2.display(); } In the airtime2 class, I create explicit no-argument and two-argument constructors. The two-argument constructor calls the two-argument constructor in the base class and then checks the values provided to it. If either hours or minutes is too large, it prints an error message and causes an exit from the program. In main(), the program creates three airtime2 objects. The first uses the no-argument constructor; the second uses the two-argument constructor with appropriate argument values; and the third uses the two-argument constructor with a minutes value thats too large. Heres the output from AIRINH: at0 = 0:00 at1 = 10:45 Error: invalid airtime value 10:65 As you can see, the airtime2 constructor has successfully detected the out-of-range minutes value for at2 and notified the user.
Quiz 4 1. Which of the following are true? a. A derived class constructor is executed before the base class constructor. b. A derived class constructor is executed after the base class constructor. c. A derived class destructor is executed before the base class destructor. d. A derived class destructor is executed after the base class destructor. e. Derived and base class constructors are executed simultaneously. 2. For a derived class constructor with arguments to call a base class constructor, it must a. make the call in the usual way from within its function body. b. finish executing before calling the base class constructor. c. use any arguments to itself in the call to the base class constructor. d. place the call to the base class constructor on its initialization list. e. make no explicit call, because the system will handle it automatically. 3. If theres a constructor with arguments in a derived class, then you must have a. at least a no-argument constructor in the base class. b. a constructor with the same number of arguments in the base class.
c. a no-argument derived class constructor (assuming youll substantiate objects without arguments). d. a no-argument base class constructor (assuming youll substantiate objects without arguments). e. instantiated objects of the base class. 4. The initializer list in a constructor typically a. initializes variables of basic types in its own class. b. calls a constructor in a base class. c. initializes variables of basic types in a base class. d. calls other constructors in its own class. e. assigns values to existing variables. 5. A derived class constructor a. need not call a base class constructor. b. can do more than simply call the base class constructor. c. must call the base class constructor that has the same number of arguments. d. creates an object of the derived class. e. creates a subobject of the base class. Exercise 1
Augment the two-argument constructor to the airtime2 class in the airinh program so it checks for values of hours and minutes that are too small (less than 0) as well as too large.
Exercise
Rewrite the STACKINH program from Session 3 in this chapter so the Stack2 class, in addition to checking for stack overflow and underflow, also provides a one-argument constructor that can create stacks of different sizes, with the value of the argument (type int) specifying the size. Actually, the array used will always be the same size, but the stack will act as if its a smaller size in that it will report stack overflow if the user tries to push too many items.
Midchapter Discussion
George: Don: Does this reusability business work in the real world? I mean, do software companies actually see any improvement in the bottom line because of inheritance? Absolutely. Although I hear it takes a while for the payoff. First the programmers have to learn C++. And then they need to write the first major application in C++. After that, they start to be much more productive, because they can reuse classes from the first project for all the projects that follow. Well, I bet it takes a while to learn. Things are getting pretty complicated, arent they? All these Page 324 of 701
Estelle:
rules about constructors, when you need them and when you dont. Who can remember all that? Ive been trying out some of this stuff at home, and... Oh, arent you the overachiever! You better not learn this stuff too fast, or youll make the rest of us look bad. ...and the surprising thing is that the compiler really helps you get it right. If you need a constructor and you dont have one, the compiler lets you know. So its a lot easier to program than you might think. Its like the compiler is on your side, for a change. Sounds like you need to experiment a lot. Absolutely. Writing code is even more important for learning C++ than it is for procedural languages. Wump on those keys! Oh, for Petes sake. You wump on the keys. Im gonna watch TV.
Access Review
Lets review what youve already learned about access. When inheritance is not involved, class member functions have access to anything in the class, whether public or private, but objects of that class have access only to public
members, as shown in Figure 7-5. Figure 7-5 Access specifiers without inheritance Once inheritance enters the picture, other access possibilities arise for derived classes. Member functions of a derived class can access public and protected members of the base class, but not private members. Objects of a derived class can access only public members of the base class (assuming its publicly derived; more on this at the end of the session). This situation is shown in Table 7-2.
Table 7-2 Inheritance and accessibility Access Specifier | Accessible from Own Class | Accessible from Derived Class | Accessible from Objects Outside Class public | yes | yes| yes protected |yes| yes| no private |yes |no |no Even though a class is derived from the base class, the situation for the base class members and objects is unchanged, because they know nothing about the derived class. Figure 7-6 shows these relationships.
Recall that an interface consists of the functions used to access something. Design your classes to provide two interfaces: a public interface for use by objects of the class and a protected interface for use by derived classes. Neither of these interfaces should have direct access to data. One advantage of keeping base class data private is that you can change it without breaking (causing the failure of) derived classes. As long as the interface remains the same, you can modify the underlying data. Although it may be a good idea to prevent derived class functions from modifying base class data, such functions must often read base class data. To provide this read-only access, you can write simple functions as I demonstrated earlier (e.g., in the get_ia() function in TWOTEST in Chapter 6, Session 5). These functions should be protected, because they are part of the protected interface used by derived classes and should not be accessible to nonderived classes.
A Stack Example
The next example shows this arrangement of private data and protected access functions. Its a modification of the STACKINH program in Session 3 in this chapter. Listing 7-9 shows STAPROFU. Listing 7-9 STAPROFU // staprofu.cpp // superior stack created using inheritance, // base class data private; uses protected member function #include <iostream.h> #include <process.h> // for exit() //////////////////////////////////////////////////////////////// class Stack // a stack holds up to SIZE ints { protected: enum {SIZE=20}; // capacity of stack private: int st[SIZE]; // integers are stored in array int top; // index of last item pushed protected: int get_top() const // return current top { return top; } public: Stack() // no-arg constructor { top = -1; } void push(int var) // place an item on the stack { st[++top] = var; } int pop() // remove an item from the stack { return st[top--]; } }; //////////////////////////////////////////////////////////////// class Stack2 : public Stack { public: void push(int var) { if(get_top() >= SIZE-1) { cout << "Error: stack overflow"; exit(-1); } Stack::push(var); // call push() in Stack class } int pop() { if(get_top() < 0) { cout << "Error: stack underflow"; exit(-1); } return Stack::pop(); // call pop() in Stack class C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 327 of 701
} }; //////////////////////////////////////////////////////////////// void main() { Stack2 s; // create a Stack2 object s.push(11); // push 3 items onto stack s.push(12); s.push(13); cout << s.pop() << endl; // pop 3 items and display them cout << s.pop() << endl; cout << s.pop() << endl; cout << s.pop() << endl; // oops, popped one too many } As you can see, the array st and the index top, which constitute the main data items in Stack, are now private. Only member functions of Stack can access them. When I derive Stack2 from Stack, Stack2s push() and pop() can call Stacks push() and pop() functions to store and retrieve data. However, in Stack2 I also need to read the value of top so I can check if the stack is full. To make this possible, I install a get_top() function in Stack. This function is protected, so derived classes can execute it; to ensure they cant use it to alter anything in Stack, its a const function. To the normal class user, Stack2 works just the way it did in STACKINH. (Note that main() looks almost the same as it did when only Stack existed.) However, in STAPROFU, Stacks data is safe from harm by class users who code incorrectly. In general, this is a better approach than making Stacks data protected. For brevity, Ill continue to show some program examples with protected data, but you should be aware that this is often a second-choice approach. Notice that Ive made the enum constant SIZE protected so it can be used by Stack2. Because SIZE must be defined before st, which is private, I use two protected sections of data in Stack.
A Graphics Example
Lets look at another example where data is made private in the base class. In this program, inheritance is used as a design element rather than for reuse. The purpose of the program is to draw shapes on the screen. To avoid becoming embroiled in the graphics library of any particular compiler, Ill simulate graphics activity by drawing shapes made of Xs on the character display. The OOP principles are the same as if I used functions that draw in graphics mode.
A Hierarchy of Shapes
The program consists of a base class, shape, and three derived classes, square, cap, and bowl. A cap is a triangle with the point facing up (a pyramid), whereas a bowl is a triangle with the point facing down. Other shapes, such as circles and polygons, would be fun, but would take us too far afield. You can create shapes of any size and position them (with certain restrictions) anywhere on the screen. Specify a shapes position and size with arguments to its constructor. For example, square(15, 2, 6); creates an X-filled square, 6 lines high and 6 characters wide, with its upper-left corner 15 columns from the left edge of the screen and 2 rows down from the last position of the cursor. The object looks something like this: XXXXXX XXXXXX XXXXXX XXXXXX XXXXXX XXXXXX Alas, in my rather crude implementation, you can put only one shape on any given row and each shape must be drawn below the previous shape. (You could fix this by storing the shapes in an array before displaying them, but again this would complicate the example.) C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 328 of 701
The Classes
The shape class contains elements that are common to all shapes. These are the position of the object on the screen, represented by the coordinates xCo and yCo, and its size, represented by size. Both position and size are measured in characters. The position has two parts: the distance from the left edge of the screen to the left edge of the object and the distance from the top of the screen to the top of the object. Size is the height of the object. Shape also contains protected member functions that permit read-only access to its private data. Because Shape is an abstract base class that will never be instantiated itself, its constructor can be protected along with these access functions. The square, cap, and bowl classes contain constructors that call the base class constructor, and member functions to draw themselves. Listing 7-10 shows the SHAPES program. Listing 7-10 SHAPES // shapes.cpp // draws shapes made from Xs on character-based display // base class data is private; uses protected access functions #include <iostream.h> //////////////////////////////////////////////////////////////// class shape { private: int xCo, yCo; // coordinates of shape int size; // size of shape protected: // read-only functions int getx() const { return xCo; } int gety() const { return yCo; } int getz() const { return size; } void down() const; // declaration // 3-arg constructor shape(int x, int y, int s) : xCo(x), yCo(y), size(s) { } }; void shape::down() const // move cursor down to top of shape { for(int y=0; y<yCo; y++) cout << endl; } //////////////////////////////////////////////////////////////// class square : public shape // square shape { public: // 3-arg constructor square(int x, int y, int s) : shape(x, y, s) { } void draw() const; // declaration }; void square::draw() const // draw a square { shape::down(); // position y at top of shape for(int y=0; y<getz(); y++)// move y down across shape { int x; for(x=1; x<getx(); x++) // space over to shape cout << ' '; for(x=0; x<getz(); x++) // draw line of Xs cout << 'X'; cout << endl; } } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 329 of 701
//////////////////////////////////////////////////////////////// class cap : public shape // cap (pyramid) shape { public: // 3-arg constructor cap(int x, int y, int s) : shape(x, y, s) { } void draw() const; // declaration }; void cap::draw() const // draw a cap { shape::down(); for(int y=0; y<getz(); y++) { int x; for(x=0; x < getx()-y+1; x++) cout << ' '; for(x=0; x<2*y+1; x++) cout << 'X'; cout << endl; } } //////////////////////////////////////////////////////////////// class bowl : public shape // bowl (inverted pyramid) shape { public: // 3-arg constructor bowl(int x, int y, int s) : shape(x, y, s) { } void draw() const; // declaration }; void bowl::draw() const // draw a bowl { shape::down(); for(int y=0; y<getz(); y++) { int x; for(x=0; x < getx()-(getz()-y)+2; x++) cout << ' '; for(x=0; x < 2*(getz()-y)-1; x++) cout << 'X'; cout << endl; } } /////////////////////////////////////////////////////////////////// void main() { bowl bw(10, 0, 3); // make a bowl bw.draw(); // draw it square sq(20, 1, 5); // make a square sq.draw(); // draw it cap cp(30, 1, 7); // make a cap cp.draw(); // draw it } In main(), I create and draw three shapes: a bowl 3 units high, a square 5 units high, and a cap 7 units high. Heres the output from SHAPES: XXXXX XXX C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 330 of 701
X XXXXX XXXXX XXXXX XXXXX XXXXX X XXX XXXXX XXXXXXX XXXXXXXXX XXXXXXXXXXX XXXXXXXXXXXXX A member function of shape, called down(), is responsible for moving the cursor down to the top of the shape to be drawn. This function is called by each of the draw() functions in the derived classes. The draw() functions for each shape move the cursor down line by line through the shape and on each line space over from the left edge of the screen to the object, and then display the appropriate number of Xs. How far to space over and the number of Xs to draw depend on the shape being drawn, its size, and its location. I wont dwell on the details of how the functions figure this out. The point of this program is the relationship between the classes and the use of public and private members in the base class.
Figure 7-7 Public and private inheritance In public inheritance, the implicationas Ive noted beforeis that an object of the B class is a kind of object of the A class. A derived class object has all the features of the base class, plus some of its own
anyfunc(bb); // beta object passed as alpha argument } As you already know, an object of a derived class can call a member function in the base class, as long as the function has public access: bb.memfunc(); However, you may be surprised to see that you can assign an object of a derived class to a variable of the base class: aa = bb; The compiler is happy with this, because bb is a kind of alpha. Similarly, you can pass a derived class object to a function that expects a base class argument: anyfunc(bb); The real payoff from this flexibility comes when you learn about pointers and virtual functions, but this brief introduction hints at the possibilities. Incidentally, it doesnt work the other way. You cant, for example, say bb = aa; because an alpha object is not a kind of beta object.
Private Inheritance
Now that youve seen how the compiler treats public inheritance, lets look at private inheritance, which looks like this: class alpha { }; class beta : private alpha { }; By substituting private for public, you are changing the entire inheritance relationship in a surprising way. (Ill ignore the possibility of using protected for the moment; its quite similar to private.) Basically, when a class is privately inherited, its objects cant access anything in the base class, no matter what access specifier was used for the base class members. This is shown on the right side of Figure 7-7. The effect is that the base class is entirely hidden from objects of the derived class. As far as derived class objects are concerned, there is no base class. (Of course, as in public inheritance, statements within derived class member functions can access protected and public members of the base class.) Thus, objects of the derived class dont see themselves as a kind of base class object; they dont know anything about the base class, even though the base class is part of the derived class. This is more like compositiona has a relationship. (I introduced composition in Session 1 in this chapter.) The derived class has an object of the base class, but objects of the derived class dont know about it. Because private inheritance is so much like composition, its usually better to use composition instead. In the fragment shown above, you would simply install an object of class alpha in class beta and forget about inheritance: class alpha { }; class beta { private: alpha obj; }; The alpha object is made private, so it is still concealed from beta objects. Composition results in a cleaner, less complicated relationship between the two classes and, as far as derived class objects are concerned, works the same way. Youll see an example of composition in Session 7 in this chapter.
// but not if it's privately derived class alpha // base class { public: void memfunc() // public member function { } }; class beta : public alpha // public derivation { }; class gamma : private alpha // private derivation { }; void main() { void anyfunc(alpha); // function takes alpha argument alpha aa; // object of type alpha beta bb; // object of type beta gamma gg; // object of type gamma bb.memfunc(); // ok gg.memfunc(); // error: 'memfunc() not accessible' anyfunc(bb); // ok anyfunc(gg); // error: 'cannot convert gamma to alpha' aa = bb; // ok aa = gg; // error: 'cannot convert gamma to alpha' } A new class gamma is privately derived from alpha. The compiler will not allow you to call an alpha member function from a gamma object, pass a gamma object to a function expecting an alpha argument, or assign a gamma object to an alpha variable.
Protected Inheritance
Protected inheritance is similar to private inheritance: theyre both like composition (the has a relationship). However, statements in member functions of a protected derived class can access public and protected members of the base class, whereasas youve seenstatements in member functions of a privately derived class cannot. Protected inheritance is not used often and is included in the language mostly for completeness.
Access Summary
Lets summarize what youve learned about accessibility and the different kinds of inheritance. Table 7-3 shows the access to base class members (usually data) that is allowed to member functions of the derived class. Private members are never accessible, and private inheritance makes all members inaccessible. Table 7-3 Access by members of the derived class Private Inheritance |Protected Inheritance |Public Inheritance Private base class data |Not accessible |Not accessible |Not accessible Protected base class data |Not accessible |Accessible |Accessible Public base class data |Not accessible |Accessible |Accessible Table 7-4 shows the access to base class members (usually functions) allowed to derived class objects that are defined outside the class specifications, as in main(). Only publicly derived public members are accessible. Table 7-4 Access by objects of the derived class Private Inheritance |Protected Inheritance |Public Inheritance
Private base class data |Not accessible |Not accessible |Not accessible Protected base class data |Not accessible |Not accessible |Not accessible Public base class data |Not accessible |Not accessible |Accessible C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
Quiz 5 1. In public inheritance, a derived class object can call, say from main(), a a. public member function of the base class. b. public member function of the derived class. c. protected member function of the base class. d. protected member function of the derived class. e. private member function of the derived class. 2. Making data protected in the base class a. is the preferred approach. b. is not acceptable to the compiler. c. makes it easy to access this data from derived class objects. d. typically requires the use of special functions to access base class data. e. can cause problems because the data can be mistakenly altered by functions in a poorly written derived class. 3. In the Stack class in the STAPROFU program, a. SIZE is protected so it can be used to define Stacks array st. b. push() and pop() are public because they are called from Stack2 objects. c. the important data is private because this is the safest approach. d. the get_top() function is necessary because push() and pop() are public. e. member functions of Stack2 can find out what top is, but cant modify it. 4. In a kind of relationship, you can a. do anything with a derived class object that you can with a base class object. b. assign a derived class object to a base class object. c. assign a base class object to a derived class object. d. call a derived class function from a base class object. e. call a base class function from a derived class object.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 335 of 701
5. When a class beta is privately derived from a class alpha, betas public member functions a. can access private data in alpha. b. can access protected data in alpha. c. can access public data in alpha. d. are visible to objects of class beta. e. are visible to objects of class alpha. Exercise 1
Add a class called line to the SHAPES program. A line object displays itself as a line of Xs, running either vertically or horizontally. (Other angles arent allowed.) Member data should include the x and y coordinates of the starting point, the line direction (down or right), and the length. Create some sample line objects in main() and display them.
Exercise 2
Start with Exercise 2 of Session 3 in this chapter. Modify the Queue class to use private data and provide the necessary access to Queue2 through protected member functions, as in the STAPROFU program in this session.
Session 6: Grandparents
As you have perhaps surmised, there can be more than two levels of inheritance. Not only can a class beta be derived from a class alpha, but a class gamma can be derived from beta, a class delta can be derived from gamma, and so on, as long as you like (or, in this example, until you run out of Greek letters). class alpha // first generation { }; class beta : public alpha // second generation { }; class gamma : public beta // third generation { }; class delta : public gamma // fourth generation { }; Everything works as you would expect. The relationship between delta and gamma is just the same as that between beta and alpha. A class has access to all its ancestors. In public inheritance, member functions of delta can access public or protected data in gamma, beta, and alpha. They cant, of course, access private members of any class except their own.
Figure 7-8 Class hierarchy in EMPGRAND Foremen oversee the laborers who operate the dangerous widget-stamping presses. Foremen are responsible for the widget production quota for their group. A foremans ability is measured by the percentage of production quotas he or she successfully meets. The quotas data item in the foreman class represents this percentage (e.g., a typical foreman meets 72.5 percent of the quotas). Listing 7-13 shows EMPGRAND. Listing 7-13 EMPGRAND // empgrand.cpp // more than two levels of inheritance // foreman derived from laborer #include <iostream.h> const int LEN = 80; // maximum length of names class employee { private: char name[LEN]; // employee name unsigned long number; // employee number public: void getdata() { cout << "\n Enter last name: ";cin >> name; cout << " Enter number: "; cin >> number; } void putdata() { cout << "\n Name = " << name; cout << "\n Number = " << number; } }; class manager : public employee // management class { private: char title[LEN]; // "vice-president" etc. double dues; // golf club dues public: void getdata() { C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 337 of 701
employee::getdata(); cout << " Enter title: "; cin >> title; cout << " Enter golf club dues: "; cin >> dues; } void putdata() { employee::putdata(); cout << "\n Title: " << title; cout << "\n Golf club dues: " << dues; } }; class scientist : public employee // scientist class { private: int pubs; // number of publications public: void getdata() { employee::getdata(); cout << " Enter number of pubs: "; cin >> pubs; } void putdata() { employee::putdata(); cout << "\n Number of publications: " << pubs; } }; class laborer : public employee // laborer class { }; class foreman : public laborer // foreman class { private: float quotas; // percent of quotas met successfully public: void getdata() { laborer::getdata(); cout << " Enter quotas: "; cin >> quotas; } void putdata() { laborer::putdata(); cout << "\n Quotas: " << quotas; } }; void main() { laborer labo; foreman fore; cout << endl; cout << "\nEnter data for laborer"; labo.getdata(); cout << "\nEnter data for foreman"; fore.getdata(); cout << endl; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 338 of 701
cout << "\nData on laborer"; labo.putdata(); cout << "\nData on foreman"; fore.putdata(); } Notice that a class hierarchy is not the same as an organization chart. A class hierarchy results from generalizing common characteristics. The more general the class, the higher it is on the chart. A laborer is a more general type of employee than a foreman, so the laborer class appears above the foreman class in the class hierarchy. On a companys organization chart, the foreman would appear above the laborer, because, on an organization chart, the employees with the most authority have the highest positions. Heres some sample interaction with EMPGRAND: Enter data for laborer Enter last name: Jones Enter number: 246137 Enter data for foreman: Enter last name: Smith Enter number: 4781 Enter quotas: 78.5 Data for laborer Name = Jones Number = 246137 Data for foreman: Name = Smith Number = 4781 Quotas = 78.5
Constructors
When there are more than two levels in the class hierarchy, handling constructors with initialization lists can become rather clumsy looking. Listing 7-14, INHCON, shows an example of such constructors. Listing 7-14 INHCON // inhcon.cpp // tests constructors with arguments, in inheritance #include <iostream.h> class Gparent { private: int intv; float flov; public: Gparent(int i, float f) : intv(i), flov(f) // initialize Gparent { } void display() { cout << intv << ", " << flov << "; "; } }; class Parent : public Gparent { private: int intv; float flov; public: Parent(int i1, float f1, int i2, float f2) : Gparent(i1, f1), // initialize Gparent intv(i2), flov(f2) // initialize Parent { } void display() C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 339 of 701
{ Gparent::display(); cout << intv << ", " << flov << "; "; } }; class Child : public Parent { private: int intv; float flov; public: Child(int i1, float f1, int i2, float f2, int i3, float f3) : Parent(i1, f1, i2, f2), // initialize Parent intv(i3), flov(f3) // initialize Child { } void display() { Parent::display(); cout << intv << ", " << flov; } }; void main() { Child ch(1, 1.1, 2, 2.2, 3, 3.3); cout << "\nData in ch = "; ch.display(); } A Child class is inherited from a Parent class, which is in turn inherited from a Gparent class. Each class has one int and one float data item. The constructor in each class takes enough arguments to initialize the data for the class and all ancestor classes. This means two arguments for the Gparent class constructor, four for Parent (which must initialize Gparent as well as itself), and six for Child (which must initialize Gparent and Parent as well as itself). Each constructor calls the constructor of its base class, which in turn calls the constructor for its base class. In main(), I create an object of type Child, initialize it to six values, and display it. Heres the output from INHCON: Data in ch = 1, 1.1; 2, 2.2; 3, 3.3 The Child object ch, the Parent subobject within the Child object, and the Gparent subobject within that Parent subobject, are all initialized before the Child constructor starts to execute. Thats why all the calls to subobject constructors appear on the initialization list: They must be initialized before the opening brace of the constructor. Thus, when your constructor starts to execute, youre guaranteed that all the subobjects youre working with are created and initialized. Incidentally, you cant skip a generation when you call an ancestor constructor in an initialization list. In the following modification of the Child constructor: Child(int i1, float f1, int i2, float f2, int i3, float f3) : Gparent(i1, f1), // error: can't initialize Gparent intv(i3), flov(f3) // initialize Child { } the call to Gparent() is illegal because the Gparent class is not the immediate base class of Child.
Summary
Let me attempt to summarize some of the access situations in multigenerational inheritance.
Private derivation makes all data in the base class, whether public, protected, or private, inaccessible to all generations of derived classes. Protected derivation makes protected and public data in the base class accessible to all generations of derived classes, no matter what kind of inheritance is used to derive subsequent generations from each other. Protected members of the base class are accessible to members of publicly derived and protectedly derived classes, and to publicly derived and protectedly derived classes of these derived classes. An object of a derived class that is defined outside the class hierarchy can access members of the base class only if the base class member is public and the derivation from the base class is public from each generation to the next. That is, if any of the derivations is not public, the member will not be accessible
Tables 7-5 and 7-6 show how this looks with three generations: a first generation base class, a second generation derived class, and a third generation derived class. Table 7-5 Access by members of third generation class C to members in first generation class A
Type of Inheritance from Class A to Class B Private Protected Public ------------------------------------------Type of Inheritance from Class B to Class C Priv Prot Pub Priv Prot Pub Priv Prot Pub As data private No No No No No No No No No As data protected No No No Yes Yes Yes Yes Yes Yes As data public No No No Yes Yes Yes Yes Yes Yes Table 7-6 Access by third generation objects to first generation members Type of Inheritance from First to Second Generation Private Protected Public --------------------------------------Type of Inheritance from Second to Third Generation Priv Prot Pub Priv Prot Pub Priv Prot Pub As functions private No No No No No No No No No As functions protected No No No No No No No No No As functions public No No No No No No No No Yes
Quiz 6 1. If a class C is derived from a class B, which is derived from a class A, all through public inheritance, then a class C member function can access a. protected and public data only in C and B.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 341 of 701
b. protected and public data only in C. c. private data in A or B. d. protected data in A or B. e. public data in A or B. 2. Assume a class C is derived from a class B, which is derived from a class A. To call a nonoverloaded member function afunc() in class A from a member function in class C, you would need to a. use the expression A::B::afunc(); b. use the expression A::afunc(); c. use the expression afunc(); d. create an object of class A, say aObj, and use the function call aObj.func(); e. create an object of class A, say aObj, and use the function call aObj.A::func(); 3. In an inheritance diagram, a. a scientist specializing in metal alloys would be shown above a laborer. b. a racing bicycle would be shown above a bicycle. c. a lion would be shown above a mammal. d. a vehicle would be shown above a car. e. a flea would be shown below a lion. 4. In an inheritance situation, when a constructor for a derived class is called, a. constructors of all subobjects, and subobjects of subobjects, are called automatically. b. only the members of the object itself are initialized, but not those of its subobjects. c. every subobject, and every subobject in a subobject, is initialized before the constructor begins to execute. d. as many objects and subobjects are created as there are ancestor classes. e. only a subobject of the base class is created. 5. If a class C is derived from a class B, which is derived from a class A, then a class C constructors a. arguments can be used to initialize only class C members.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 342 of 701
b. arguments can be used to initialize members of any ancestor class. c. initialization list may contain a call to any ancestors constructor. d. initialization list may contain a call only to the constructor in the immediate base class. e. initialization list may be used to initialize class C data members. Exercise 1
Start with the EMPGRAND program in this session and add a manager class. Managers oversee all the foremen in a particular factory. Like all employees, managers have a name and number, and like foremen they have a quotas data member, which is the average of the quotas members for all the foremen in their factory. In addition, managers have an absenteeism data member, type float, which is the average percentage of work time missed by their foremen and laborers. Situate the manager class appropriately in the inheritance diagram.
Exercise 2
From the line class in Exercise 1 in Session 5 in this chapter, derive a horizLine class and a vertLine class. The arguments to the constructors for these classes should comprise the starting coordinates of the line and its length.
Session 7: Composition
Composition is putting one object inside another object or, from the programmers viewpoint, defining an object of one class inside another class specification. Ive already mentioned composition several times in this chapter: in Session 1 and in Session 5, where I compared composition and private inheritance. Heres the composition example I used then: class alpha { }; class beta { private: alpha aObj; }; Of course, Ive used a rudimentary kind of composition every time I placed instance data of basic types such as int and char in an object. Because class objects can be considered data types, theres no conceptual difference between using class objects and variables of basic types as data in other classes. However, when I speak of composition, Im usually talking about embedding a class object in the specification for another class.
// 0 to 59
// no-arg constructor airtime() : hours(0), minutes(0) { } // two-arg constructor airtime(int h, int m) : hours(h), minutes(m) { } void display() const // output to screen { cout << hours << ':' << minutes; } void get() // input from user { char dummy; cout << "\n Enter time (format 12:59): "; cin >> hours >> dummy >> minutes; } }; // end class airtime //////////////////////////////////////////////////////////////// class flight { private: long fnumber; // flight number airtime departure; // airtime airtime arrival; // objects public: flight() : fnumber(0) // no-arg constructor { } void get() // input from user { char dummy; cout << "\nEnter flight number: "; cin >> fnumber; cout << " Departure"; departure.get(); cout << " Arrival"; arrival.get(); } void display() const // output to screen { cout << "Flight number = " << fnumber; cout << "\n Departure = "; departure.display(); cout << "\n Arrival = "; arrival.display(); } }; // end class flight //////////////////////////////////////////////////////////////// void main() { flight flarr[100]; // array holds 100 flight objects int total = 0; // number of flights in array char ch; // for 'y' or 'n' do // get data for flights { C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 344 of 701
flarr[total++].get(); cout << "Enter another flight (y/n)? "; cin >> ch; } while(ch != 'n'); for(int j=0; j<total; j++) // display data for flights { cout << endl; flarr[j].display(); } } In main(), the program lets the user enter data for as many flights as desired. It stores these flights in an array. It then displays the data for all flights stored in the array. Heres some sample interaction when two flights are entered: Enter flight number: 940 Departure Enter time (format 12:59): 22:15 Arrival Enter time (format 12:59): 23:35 Enter another flight (y/n)? y Enter flight number: 1474 Departure Enter time (format 12:59): 10:45 Arrival Enter time (format 12:59): 16:33 Enter another flight (y/n)? n Flight number = 940 Departure = 22:15 Arrival = 23:35 Flight number = 1474 Departure = 10:45 Arrival = 16:33 In the AIRSCHED program, composition seems like a natural choice; I use airtime as you would a float. I would probably not even be tempted to use inheritance to connect the airtime class with the flight class, because airtime objects are so much like variables of basic types. However, not all situations are so clear.
//////////////////////////////////////////////////////////////// class safearay { private: enum {SIZE=100}; // array size int arr[SIZE]; // ordinary array public: int& operator [](int n); // function declaration }; int& safearay::operator [](int n) // overloaded [] { // returns by reference if( n< 0 || n>=SIZE ) { cout << "\nIndex out of bounds"; exit(1); } return arr[n]; } //////////////////////////////////////////////////////////////// class Stack // stack stores ints in safe array { private: safearay st; // safe array object int top; // index of last item pushed public: Stack() // no-arg constructor { top = -1; } void push(int var) // place an item on the stack { st[++top] = var; } int pop() // remove an item from the stack { return st[top--]; } }; //////////////////////////////////////////////////////////////// void main() { Stack s; // create a Stack object s.push(11); // push 3 items onto stack s.push(12); s.push(13); cout << s.pop() << endl; // pop items and display them cout << s.pop() << endl; cout << s.pop() << endl; cout << s.pop() << endl; // woops, popped one too many } The Stack class works just the same as before, but now, even though no bounds-checking mechanism is built into Stack itself (as there was in the Stack2 class in the stackinh example), errors are caught by the safearay object. Heres some sample output, in which the class user attempts to pop one too many items: 13 12 11 Index out of bounds The safearay object within Stack spots the incorrect value of top and complains. Could I use inheritance to derive Stack from safearay? I could, but its not a great idea. If I used public inheritance, then the user of the Stack class could access member functions in the safearay class directly, leading to usage such as Stack s; s[3] = 77; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 346 of 701
where a Stack object is treated as if it were an array. This subverts the purpose of a stack, which is to restrict data access to the LIFO approach. I could make safearay invisible using private inheritance, but this still leaves an unnecessarily complex relationship between Stack and safearay. To make things simple and to minimize surprises, use composition.
Summary
It may be helpful to summarize the various characteristics of composition, as well as the three kinds of inheritance. Table 7-7 attempts to do this. Table 7-7 Characteristics of composition and inheritance Composition Private Inheritance Protected Inheritance Public Inheritance Relationship of base class to derived class Has a Has a Kind of to derived classesHas ato class user Kind of Derived class members can access base class members that are Public Public Protected Public Protected Public Protected Syntax for derived class access to base class functions Base b; b func() Base::func(); Base::func(); Base::func(); Can derived class objects access the public base class interface? No No No Yes Can derived class objects be treated as a base class object? No No No Yes Recommendation Use when derived class objects dont need to see the base class interface and will not be treated as a kind of base class object. Not recommended: base class protected data is exposed. Not recommended: base class protected data is exposed. Use when derived class members must have access to base class members. Use when derived class objects will be treated as a kind of base class object and you need to see the base class interface.
Quiz 7 1. Composition means a. private inheritance. b. member functions of one class operating on objects of another class. c. using a class specification for one class inside another class. d. using a variable of a basic type as instance data in another class. e. using a class object as instance data in another class.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 347 of 701
2. You would probably use composition to relate a. wing, tail, window, and landing_gear classes to an airplane class. b. lion, tiger, ocelot, and cheetah classes to the feline class. c. int, float, and long types to the Ship class. d. living, dining, basement, and hall classes to the room class. e. hockey, baseball, and tennis to the sport class. 3. Composition a. is a has a relationship. b. is a kind of relationship. c. is a is a relationship. d. allows you to put objects of the derived class in an array of the base class type. e. allows you to hide objects of one class from the user of another class. 4. Assume aObj is an object of class alpha and bObj is an object of class beta. When composition is used to place an alpha in beta, to access a member function afunc() of alpha from the class beta, you would say a. alpha::afunc(); b. beta::afunc(); c. aObj.afunc(); d. bObj.afunc(); e. a member function of beta must be used. 5. Objects of a class alpha, used as private instance data in a class beta, a. are named in alpha. b. are named in beta. c. are named in main(). d. can be accessed from beta. e. can be accessed from main().
Exercise 1
Assume you want to store flight objects (as in the AIRSCHED program) in a safearay object (as seen in the SAFESTAK program). Modify safearay so this is possible. (Youll see a way to avoid this kind of modification when you learn about templates in Chapter 11.) In main(), create a safearay object, get flight data from the user, and store it in the safearay. Youll need to decide what relationship to use between flight and safearay.
Exercise 2
Suppose you want to store objects of class flight in an object of class Stack, which uses the safearay class for its array. Write a program that lets you do this. Youll need to modify safearay to store flight objects. In main(), create a Stack object, get flight data from the user, store it on the stack, and then display the contents of the stack. Dont worry about why anyone would want to store flight data on a stack.
{ private: char school[LEN]; // name of school or university char degree[LEN]; // highest degree earned public: void getedu() { cout << " Enter name of school or university: "; cin >> school; cout << " Enter highest degree earned \n"; cout << " (Highschool, Bachelor's, Master's, PhD): "; cin >> degree; } void putedu() { cout << "\n School or university = " << school; cout << "\n Highest degree earned = " << degree; } }; class employee { private: char name[LEN]; // employee name unsigned long number; // employee number public: void getdata() { cout << "\n Enter last name: "; cin >> name; cout << " Enter number: "; cin >> number; } void putdata() { cout << "\n Name = " << name; cout << "\n Number = " << number; } }; class manager : public employee, public student // management { private: char title[LEN]; // "vice-president" etc. double dues; // golf club dues public: void getdata() { employee::getdata(); cout << " Enter title: "; cin >> title; cout << " Enter golf club dues: "; cin >> dues; student::getedu(); } void putdata() { employee::putdata(); cout << "\n Title = " << title; cout << "\n Golf club dues = " << dues; student::putedu(); } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 350 of 701
}; class scientist : public employee, public student // scientist { private: int pubs; // number of publications public: void getdata() { employee::getdata(); cout << " Enter number of pubs: "; cin >> pubs; student::getedu(); } void putdata() { employee::putdata(); cout << "\n Number of publications = " << pubs; student::putedu(); } }; class laborer : public employee // laborer { }; void main() { manager m1; scientist s1, s2; laborer l1; cout << endl; cout << "\nEnter data for manager 1"; // get data for m1.getdata(); // several employees cout << "\nEnter data for scientist 1"; s1.getdata(); cout << "\nEnter data for scientist 2"; s2.getdata(); cout << "\nEnter data for laborer 1"; l1.getdata(); cout << "\nData on manager 1"; // display data for m1.putdata(); // several employees cout << "\nData on scientist 1"; s1.putdata(); cout << "\nData on scientist 2"; s2.putdata(); cout << "\nData on laborer 1"; l1.putdata(); } Notice that, if there is a function with the same name in both base classes (employee and student), then in a derived class, the scope resolution operator must be used to specify which function is meant, for example, employee::getdata() or student::getdata(). Some sample interaction with the program might look like this: Enter data for manager 1 Enter last name: Webley Enter number: 111 Enter title: President Enter golf club dues: 50000 Enter name of school or university: Yale C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 351 of 701
Enter highest degree earned (Highschool, Bachelors, Masters, PhD): Bachelors Enter data for scientist 1 Enter last name: Frish Enter number:222 Enter number of pubs: 99 Enter name of school or university: MIT Enter highest degree earned (Highschool, Bachelors, Masters, PhD): PhD Enter data for scientist 2 Enter last name: Wong Enter number:333 Enter number of pubs: 204 Enter name of school or university: Stanford Enter highest degree earned (Highschool, Bachelors, Masters, PhD): Masters Enter data for laborer 1 Enter last name: Jones Enter number: 482562 Data for manager 1 Name = Webley Number = 111 Title = President Golf club dues = 50000 Name of school or university = Yale Highest degree earned (Highschool, Bachelors, Masters, PhD) = Bachelors Data for scientist 1 Name = Frish Number = 222 Number of pubs = 99 Name of school or university = MIT Highest degree earned (Highschool, Bachelors, Masters, PhD) = PhD Data for scientist 2 Name = Wong Number = 333 Number of pubs = 204 Name of school or university = Stanford Highest degree earned (Highschool, Bachelors, Masters, PhD) = Masters Data for laborer 1 Name = Jones Number = 482562
Its Controversial
Multiple inheritance is controversial. In the simple case shown in EMPMULT, multiple inheritance works as expected. In more complicated class hierarchies, however, problems may arise. One of these problems results from the following kind of inheritance situation: class Gparent { }; class Mother : public Gparent { }; class Father : public Gparent { }; class Child : public Mother, public Father { }; Both Mother and Father inherit from Gparent, and Child inherits from both Mother and Father. This forms a diamond-shaped pattern. Recall that each object created through inheritance contains a subobject of the base class. A Mother object and a Father object will contain subobjects of Gparent, and a Child object will contain subobjects of Mother and Father, so a Child object will also contain two Gparent subobjects, one inherited via Mother and one inherited via Father, as shown in Figure 7-9.
Ambiguous Subobjects
This is a strange situation. There are two subobjects when really there should be one. Suppose theres a data item in Gparent: C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 353 of 701
class Gparent { protected: int gdata; }; and you try to access this item from Child: class Child : public Mother, public Father { public: void Cfunc() { int temp = gdata; // error: ambiguous } }; The compiler will complain that the reference to gdata is ambiguous. It doesnt know which version of gdata to access: the one in the Gparent subobject in the Mother subobject or the one in the Gparent subobject in the Father subobject.
} void putdata() { employee::putdata(); cout << "\n Title = " << title; cout << "\n Golf club dues = " << dues; stu.putedu(); // send message to student object } }; The student object stu is placed in the manager class data using composition, and its member functions are called using the dot operator. The scientist class would be modified similarly. The main() part of the program would be unchanged, and any user interaction with the program would be the same as well. Including the student class by composition makes sense conceptually because, although a manager and a scientist are kinds of employees, they arent really kinds of students (although they may have been in their younger days).
Chapter 1. Which of the following are examples of multiple inheritance? a. Class B is derived twice from class A. b. Class C is derived from class B, which is derived from class A. c. Class D is derived from classes C and B, which are both derived from class A. d. Class C is derived from class B and class A. e. Class B is derived from class A. 2. Which of the following are true? a. No more than two classes can be used as base classes in multiple inheritance. b. A member function name cannot be used in more than one multiply-inherited base class. c. A comma is used to separate multiple base classes in the specification of a derived class. d. A manager object in EMPMULT could be treated as a kind of student, but a laborer object could not. e. The class creator in EMPMULT decided that no educational data was needed for the laborer class. 3. Multiple inheritance is controversial because a. its too complicated to imagine inheritance from more than one class. b. ambiguities may arise if a diamond-shaped inheritance diagram is created. c. the resulting objects would be too large. d. it doesnt make sense for an object to be a kind of A and a kind of B at the same time.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 355 of 701
e. hybrid objects are more prone to failure. 4. Virtual base classes a. solve all problems associated with multiple inheritance. b. are used as a programming convenience, but do not actually exist. c. have base classes of their own. d. specify that only one copy of a multiply-inherited subobject will be created in subsequent derived classes. e. have only one copy of each subobject, even if they have inherited from two classes, each containing that object. 5. Which of the following can often be used instead of multiple inheritance? a. single inheritance b. integration c. composition d. virtual functions e. the scope resolution operator Exercise 1
Rewrite the entire empmult program to use composition instead of multiple inheritance, following the example shown above for the manager class.
Exercise 2
Write a class DervStak that inherits from both the Stack class, from which the array has been removed, and the safearay class (see the SAFESTAK program in Session 7 in this chapter). The DervStak class should have the same interface as the Stack class in SAFESTAK.
Summary: Chapter 7
Inheritance is the creation of a new class from an existing class without modifying the existing class. The new class usually has additional features that the old class does not. The new class is a specialized version of the old class. Inheritance can be thought of as a kind of relationship, where a new class object is a kind of old class object. This is different from composition, which is a has a relationship: One class has objects of another class embedded in it. Inheritance can be used in the design of a program to specify a kind of relationship between classes. It can also be used to create improved or specialized versions of existing classes. An object of a derived class contains a subobject of the base class. This subobject contains the base class data. The constructors of derived classes, if they need to call the constructors of their base classes, do so by placing the call on the initialization list following a colon in the constructor declarator. This ensures that all subobjects in an object are initialized before the constructor starts to execute. Access specifiers determine whether class members will be accessible to derived classes. Public and protected members are accessible to functions in derived classes, but private members are not. Only public members are accessible to objects of derived classes. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 356 of 701
Inheritance itself can be public, protected, or private. Public inheritance is the normal approach. In private inheritance, derived class objects are not a kind of base class object, but, as in composition, they are a have a base class object. There can be several levels of inheritance, with the base class of one class being derived from another class. Composition, like inheritance, can be used to relate classes. Composition means using objects of one class as instance data in another class. Composition is cleaner and less complicated to use because there are fewer connections between the classes involved, but the kind of relationship, which you will discover later is important in OOP, is possible only with inheritance. Multiple inheritance means that a derived class has more than one base class. Multiple inheritance should normally be avoided and composition should be used instead.
End-of-Chapter Discussion
George: Estelle: George: Don: Correct me if Im wrong, but Id say it all boils down to two simple rules. First, always use private data in a base class, with protected functions to access it from derived classes. And second... Always use public inheritance. Exactly. Well, thats about right. Of course those are just guidelines. You may find situations where you need protected base class data, or even public data. Or where nothing works but private inheritance. But its easy to get into trouble by not following Rule 1 and Rule 2. Actually theres a third rule. Something like use composition unless you really need the kind of relationship. Its too bad multiple inheritance turns out to be so messy. It would be fun to create some really weird mixtures of classes. Like what? I dont know. What would happen if you inherited a scientist object from both an employee object and a computer object? Wouldnt you have a supersmart scientist? The mind boggles. Dont get carried away. Classes arent exact models of things in the real world; theyre just programming constructs used for managing data. Too bad. I was hoping for a little Frankenstein action, sewing different things together to make androids and monsters. Pointers are coming up in the next chapter, and lots of people think theyre the Frankenstein of C++ programming. Oh, fine, like it could get any more complicated. Im going into something simple, like astrophysics
Estelle: Don: Estelle: George: Estelle: George: Don: Estelle: Don: George:
CHAPTER 8 POINTERS
Pointers have a reputation for being hard to understand. Fortunately, only some of this reputation is deserved. In this chapter Ill introduce pointers slowly and gently and prove that, at least in concept, they really arent that tough. Ill spend the first several sessions discussing pointers in the abstract, mostly using basic types (rather than objects) as examples. Once you have a firm grasp of the fundamentals, Ill show how pointers are typically used in the C++ environment. One important use for pointers is in the dynamic allocation of memory, carried out in C++ with the keyword new and its partner delete. Ill examine this issue in several examples. Youll also learn about a very special pointer called this that points to its own object. Pointers are used to create complex structures for storing data. Toward the end of this chapter, Ill introduce examples of a memory-efficient string class, a linked list, and a kind of array that is self-sorting.
Figure 8-1 Memory addresses The addresses in the figure are numbered from the top down. To some people this seems backward (actually upside down) because they think higher numbers should be higher on the page. However, there is also logic in putting lower numbers at the top in that you read text from the top down. In any case, in whatever format it appears on the page, phrases such as higher addresses and high memory mean larger numbers, not higher on the page.
cout << endl << &var1 // print out the addresses << endl << &var2 // of these variables << endl << &var3; } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
This simple program defines three integer variables and initializes them to the values 11, 22, and 33. It then prints out the addresses of these variables. The actual addresses occupied by the variables in a program depend on many factors, such as what computer the program is running on, the size of the operating system, and whether any other programs are currently in memory. For these reasons, you probably wont get the same addresses I did when you run this program. Heres the output on my machine: 0x8f4ffff4 <--address of var1 0x8f4ffff2 <--address of var2 0x8f4ffff0 <--address of var3 Remember that the address of a variable is not the same as its contents. The contents of the three variables are 11, 22, and 33. Figure 8-2 shows the three variables in memory.
Figure 8-2 Addresses and contents of addresses The insertion operator (<<) displays the addresses in hexadecimal notation, as indicated by the prefix 0x before each number. The address of var1 is 8f4ffff4. This is the usual way to show memory addresses. If you arent familiar with the hexadecimal number system, dont worry. All you really need to know is that each variable starts at a unique address. However, you might note in the output above that each address differs from the next by exactly 2 bytes. Thats because, on my machine, integers occupy 2 bytes of memory. If I had used variables of type char, they would have adjacent addresses, because chars occupy 1 byte; if I had used type double, the addresses would have differed by 8 bytes. The addresses appear in descending order because automatic variables (those defined within a function) are stored on the stack, which grows downward (from higher to lower addresses) in memory. If I had used external variables (defined outside of any function), the variables would have had ascending addresses, because external variables are stored on the heap, which grows upward. Again, you dont need to worry too much about all this because the compiler keeps track of the details for you. Incidentally, dont confuse the address of operator &, which precedes a variable name, with the reference operator &, which follows the type name in a function prototype or definition. (References are discussed in Chapter 4.)
Pointer Variables
Addresses by themselves are rather limited. Its nice to know that you can find out where things are in memory, as I did in VARADDR, but printing out address values is not really that useful. The potential for increasing programming power requires an additional idea: variables that hold address values. Youve seen variable types that store characters, integers, and floating-point numbers. An address is a number too, and it can be stored in a variable. A variable that holds an address value is called a pointer variable, or simply a pointer. If a pointer contains the address of a variable, I can say the pointer points to the variable; hence the name. What is the data type of pointer variables? Its not the same as the variable whose address is being stored; a pointer to int is not type int. You might think a pointer data type would be called something like pointer or ptr. However, things are slightly more complicated.
ptr = &var2; // pointer points to var2 cout << endl << ptr; // print pointer value } This program defines two integer variables, var1 and var2, and initializes them to the values 11 and 22. It then prints out their addresses. So far, this is similar to the VARADDR program. The program next defines a pointer variable in the line int* ptr; To the uninitiated, this may seem a rather bizarre syntax. The asterisk means pointer to. Thus (reading from right to left, which is the proper way), the statement defines the variable ptr as a pointer to int. This is another way of saying that this variable can hold the addresses of integer variables. Whats wrong with the idea of a general-purpose pointer type that holds addresses to any data type? If I called it, for example, type pointer, I could write declarations such as pointer ptr3; The problem is that the compiler needs to know what kind of variable the pointer points to. (Youll see why when I talk about pointers and arrays later in this chapter.) The compiler designers could have come up with a bunch of new names, such as pointer_to_int and pointer_to_char, but they used a more compact notation that involves learning only one new symbol. Heres the syntax used in C++. It allows you to declare a pointer to any type, using only the type name and an asterisk: char* cptr; // pointer to char int* iptr; // pointer to int float* fptr; // pointer to float and so on. The asterisk (in this context) is shorthand for pointer or, more completely, pointer to the type on my left. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 361 of 701
Syntax Quibbles
Many programmers write pointer definitions with the asterisk closer to the variable name than to the type: char *cptr3; It doesnt matter to the compiler, but placing the asterisk next to the type helps emphasize that the asterisk is part of the type (pointer to char), and not part of the name itself. If you define more than one pointer of the same type on one line, you need to insert the type pointed to only once, but you must place an asterisk before each variable name: char* ptr1, * ptr2, * ptr3; // three variables of type char* Or you can use the asterisk-next-to-the-name approach: char *ptr1, *ptr2, *ptr3; // three variables of type char*
Figure 8-3 Changing values in ptr Heres the output of PTRVAR: 0x8f51fff4 <--address of var1 0x8f51fff2 <--address of var2 0x8f51fff4 <--ptr set to address of var1 0x8f51fff2 <--ptr set to address of var2 To summarize: A pointer can hold the address of any variable of the correct type; its a receptacle awaiting an address. However, it must be given some value or else it will point to an address you dont want it to point to, such as into program code or the operating system. Rogue pointer values can result in system crashes and are difficult to debug because the compiler gives no warning. The moral: Make sure you give every pointer variable a valid address value before using it.
Pointers to Objects
Objects are stored in memory, so pointers can point to objects just as they can to variables of basic types. For example, you can define an object and then place its address in a pointer: employee emp1; <--define object of class employee employee* ptrobj = &emp1; <--place object's address in ptrobj The ptrobj variable is of type employee*, or pointer to employee. Lets rewrite the PTRVAR program to work with objects of class employee. Listing 8-3 shows PTROBJ. Listing 8-3 PTROBJ // ptrobj.cpp // pointers to class objects C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 363 of 701
#include <iostream.h> #include <string.h> // for strcpy() //////////////////////////////////////////////////////////////// class employee // employee class { private: enum {LEN=30}; // length of name char name[LEN]; // employee name unsigned long number; // employee number public: // 2-arg constructor employee(char* na, unsigned long nu) : number(nu) { strcpy(name, na); } }; //////////////////////////////////////////////////////////////// void main() { // employee objects employee emp1(Donlevy, 123123L); employee emp2(LeBlanc, 234234L); cout << Address values; cout << endl << &emp1 << endl << &emp2; employee* ptr;
// pointer to employees
cout << \nPointer values; ptr = &emp1; // pointer points to emp1 cout << endl << ptr; // print pointer value ptr = &emp2; // pointer points to emp2 cout << endl << ptr; // print pointer value } The operation of this program is analogous to that of PTRVAR. However, you can see from the output that employee objects occupy more space than int variables: Address values 0x29072222 0x29072200 Pointer values 0x29072222 0x29072200 If you subtract 200 from 222, you get 22 hexadecimal, which is 34 decimal. This makes sense because an employee object contains a 30-character string and a 4-byte unsigned long number.
{ int var1 = 11; int var2 = 22; int* ptr; ptr = &var1; cout << endl << *ptr;
ptr = &var2; // pointer points to var2 cout << endl << *ptr; // print contents of pointer (22) } This program is very similar to PTRVAR, except that instead of printing the address value in ptr, it prints the integer value stored at the address thats stored in ptr. Heres the output: 11 22 The expression that accesses the variables var1 and var2 is *ptr, which occurs in each of the two cout statements. When the asterisk is used to the left of a variable name, as it is in the *ptr expression, it is called the indirection operator. It means the value of the variable pointed to by the variable on its right. Thus, the expression *ptr represents the value of the variable pointed to by ptr. When ptr is set to the address of var1, the expression *ptr has the value 11 because thats the value of var1. When ptr is changed to the address of var2, the expression *ptr acquires the value 22 because var2 is 22. The indirection operator is sometimes called the contents of operator, which is another way of saying the same thing. Figure 8-4 shows how this looks.
Figure 8-4 Access via pointer You can use a pointer not only to display a variables value, but to perform any operation you would perform on the variable directly. Listing 8-5 shows a program, PTRTO, that uses a pointer to assign a value to a variable and then to assign that value to another variable. Listing 8-5 PTRTO // ptrto.cpp // other access using pointers #include <iostream.h> void main() { int var1, var2; int* ptr; ptr = &var1; *ptr = 37; var2 = *ptr; cout << endl << var2; }
// two integer variables // pointer to integers // set pointer to address of var1 // same as var1=37 // same as var2=var1 // verify var2 is 37
Remember that the asterisk used as the indirection operator has a different meaning than the asterisk used to declare pointer variables. The indirection operator precedes the variable name and means value of the variable pointed to by. The asterisk used in a declaration means pointer to. int* ptr; // declaration: *ptr = 37; // indirection: Using the indirection operator to access the value stored in an address is called indirect addressing, or sometimes dereferencing, the pointer. Heres a capsule summary of what youve learned so far: int v; // defines variable v of type int int* p; // defines p as a pointer to int p = &v; // assigns address of variable v to pointer p v = 3; // assigns 3 to v *p = 3; // also assigns 3 to v The last two statements show the difference between normal or direct addressing, where you refer to a variable by name, and pointer or indirect addressing, where you refer to the same variable using a pointer that holds its address. These two approaches are vaguely analogous to delivering a letter to a friend. If you drive to the friends house and stick the letter in the mail slot, thats direct addressing. You can also write the address on the envelope and put the letter in a public mailbox. The mail personnel will read the address and see that the letter gets to the right place. Thats indirect addressing because it is directed by a third party. In the example programs Ive shown so far in this chapter, theres really no advantage to using the pointer expression to access variables because I can access them directly. Pointers come into their own when you cant access a variable, as youll see.
Pointer to void
Before I go on to show pointers at work, I should note one peculiarity of pointer data types. Ordinarily, the address that you put in a pointer must be the same type as the pointer. You cant assign the address of a float variable to a pointer to int, for example. However, there is an exception to this rule. There is a sort of general purpose pointer that can point to any data type. Its called a pointer to void, and it is defined like this: void* ptr; // ptr can point to any data type Such pointers have certain specialized uses, such as passing pointers to functions that operate on several different data types. The next example uses a pointer to void and also shows that, if you dont use void, you must be careful to assign pointers an address of the same type as the pointer. Listing 8-6 shows PTRVOID. Listing 8-6 PTRVOID // ptrvoid.cpp // pointers to type void #include <iostream.h> void main() { int intvar; float flovar; int* ptrint; float* ptrflo; void* ptrvoid; ptrint = &intvar; // ptrint = &flovar; // ptrflo = &intvar; ptrflo = &flovar; ptrvoid = &intvar; ptrvoid = &flovar;
// integer variable // float variable // define pointer to int // define pointer to float // define pointer to void // ok, int* to int* // error, float* to int* // error, int* to float* // ok, float* to float* // ok, int* to void* // ok, float* to void* Page 367 of 701
} You can assign the address of intvar to ptrint because they are both type int*, but you cant assign the address of flovar to ptrint because the first is type float* and the second is type int*. However, ptrvoid can be assigned any pointer type, such as int* or float*, because it is a pointer to void. Whenever possible you should avoid void (sorry about that). The insistence of C++ that pointers contain only addresses of a specific type is an important way of avoiding programming mistakes. Using void circumvents this safety feature. However, its occasionally important, as youll see.
Quiz 1 1. The address of a variable temp of type float is a. *temp b. &temp c. float& temp d. float* temp e. float temp& 2. If the statements
airtime at1; airtime* x = &at1;
have been executed, then a. x is an airtime object. b. the contents of x can be changed. c. &at1 is a variable. d. x is a pointer to type airtime. e. &at1 is the address of an airtime object. 3. If the statements
float fv; fv = 3.14159; float* holdaddr = &fv;
have been executed, which expressions have the value 3.14159? a. &holdaddr b. *holdaddr c. holdaddr
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 368 of 701
are executed, what will be displayed? a. The values in q and r. b. The addresses of q and r. c. The addresses of j and k. d. 123, 234. e. None of the above. 5. If these statements are executed:
employee emp1; airtime air1; void* p; employee* d = &emp1; airtime* q = &air1;
which of the following statements will compile correctly? a. p = d; b. p = &emp1; c. p = &d; d. q = &d; e. q = p;
Due to the theoretical nature of this session, there are no exercises.
not the fourth byte, as shown in Figure 8-5. Figure 8-5 Counting by integers C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 370 of 701
The C++ compiler is smart enough to take the size of the data into account when it performs arithmetic on data addresses. It knows that intarray is an array of type int because it was declared that way. When it sees the expression intarray+3, it interprets it as the address of the fourth integer in intarray, not the fourth byte. But I want the value of this fourth array element, not the address. To take the value, I use the indirection operator *. The resulting expression, when j is 3, is *(intarray+3), which is the contents of the fourth array element, or 52. Now you see why a pointer declaration must include the type of the variable pointed to. The compiler needs to know whether a pointer is a pointer to int or a pointer to double so it can perform the correct arithmetic to access elements of the array. It multiplies the index value by 2 in the case of type int, but by 8 in the case of double.
void centimize(double& v) { v *= 2.54; // v is the same as var } Here I want to convert a variable var in main() from inches to centimeters. I pass the variable by reference to the function centimize(). (Remember that the & following the data type double in the prototype for this function indicates that the argument is passed by reference.) The centimize() function multiplies the original variable by 2.54. Notice how the function refers to the variable. It simply uses the argument name v; v and var are different names for the same thing. Once it has converted var to centimeters, main() displays the result. Heres the output: var=25.4 centimeters The next example shows an equivalent situation using pointers: void main() { void centimize(double*); // prototype double var = 10.0; // var has value of 10 inches cout << endl << var= << var << inches; centimize(&var); // change var to centimeters cout << endl << var= << var << centimeters; } void centimize(double* ptrd) { *ptrd *= 2.54; // *ptrd is the same as var } The output is the same as before. The function centimize() is declared as taking an argument that is a pointer to double: void centimize(double*); // argument is pointer to double When main() calls this function, it supplies the address of a variable as the argument: centimize(&var); Remember that this is not the variable itself, as it is in passing by reference, but the variables address. Because the centimize() function is passed an address, it must use the indirection operator, *ptrd, to access the value stored at this address: *ptrd *= 2.54; // multiply the contents of ptrd by 2.54 Of course, this is the same as *ptrd = *ptrd * 2.54; // multiply the contents of ptrd by 2.54 where the standalone asterisk means multiplication. (The asterisk symbol really gets around.) Because ptrd contains the address of var, anything done to *ptrd is actually done to var. Figure 8-6 shows how changing *ptrd in the function changes var in the calling program.
Figure 8-6 Pointer passed to function Passing a pointer as an argument to a function is in some ways similar to passing a reference. They both permit the variable in the calling program to be modified by the function. However, the mechanism is different. A reference is an alias for the original variable, whereas a pointer is the address of the variable.
In centimize(), this array address is placed in the variable ptrd. To point to each element of the array in turn, I need only increment ptrd: *ptrd++ *= 2.54;
Figure 8-7 shows how the array is accessed. Figure 8-7 Accessing an array from function The output of both these examples is the same: varray[0]=25.4 centimeters varray[1]=109.474 centimeters varray[2]=243.586 centimeters varray[3]=151.638 centimeters varray[4]=221.742 centimeters Heres a syntax question: How do you know that the expression *ptrd++ increments the pointer and not the pointer contents? In other words, does the compiler interpret it as *(ptrd++), which is what I want, or as (*ptrd)++? It turns out that the asterisk * (at least when used as the indirection operator) and ++ have the same precedence. However, operators of the same precedence are distinguished in a second way: by associativity. Associativity is concerned with whether the compiler performs operations starting with an operator on the right or an operator on the left. If a group of operators have right associativity, the compiler performs the operation on the right side of the expression first, then it works its way to the left. The unary operators such as * and ++ do have right associativity, so the expression is interpreted as *(ptrd++), which increments the pointer, not what it points to. That is, the pointer is incremented first and the indirection operator is applied to the result.
Quiz 2 1. The expression sales[month], expressed in pointer notation, is a. sales[month] (the same as in array notation)
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 374 of 701
b. sales + *month. c. &sales+month. d. sales+month. e. *(sales+month). 2. If the name of an array is jules, an expression that can be made to represent the address of each element of jules is a. jules. b. jules++. c. jules+index. d. *(jules++). e. *(jules+index). 3. The essential difference between array notation and pointer notation is that a. the compiler understands array notation more completely. b. they are written differently. c. a function that an array is passed to can modify only array elements using pointer notation. d. a function that an array is passed to can modify only array elements using array notation. e. there is no difference. 4. If a value is stored in a variable, then passing it by pointer differs from passing it by reference in that you pass ______ of the variable rather than ____________. a. the contents, an alias b. the address, an alias c. the contents, the value d. the value, an alias e. the address, the value 5. If you have the following function declaration:
float rigel(float* alpha);
then, within rigel(), to refer to the float value being passed to it, you would use the expression
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 375 of 701
Exercise 2
Write a function that will take every element of an array of type float, whose address is passed to it, and, using pointer notation, average the elements and return the average. Write a main() that calls the function.
cout << endl << str2; // now str2 starts efined... In many ways, these two types of string constants are equivalent. You can print out both strings as the example shows, use them as function arguments, and so on. But there is a subtle difference: str1 is an addressthat is, a pointer constantwhereas str2 is a pointer variable. So str2 can be changed, whereas str1 cannot, as indicated in the program. Figure 8-8 shows how these two kinds of strings look in memory.
Figure 8-8 Strings as arrays and pointers I can increment str2 because it is a pointer, but once I do, it no longer points to the first character in the string. Heres the output: Defined as an array Defined as a pointer efined as a pointer <--following str2++ A string defined as a pointer is considerably more versatile than one defined as an array. The following examples make use of this flexibility.
character has the value 0, which represents false, the while loop terminates at that point. Such space-saving idioms are common in C++ (and even more common in C).
void copystr(char* dest, char* src) { while( *src ) // until null character, *dest++ = *src++; // copy chars from src to dest *dest = '\0'; // terminate dest } Here the main() part of the program calls the function copystr() to copy str1 to str2. In this function, the expression *dest++ = *src++; takes the character value at the address pointed to by src and places it in the address pointed to by dest. Both pointers are then incremented, so the next time through the loop the next character will be copied. The loop terminates when a null character is found in src; at this point, a NULL is inserted in dest and the function returns. Figure 8-9 shows how the pointers move through the strings.
Figure 8-10 Array of pointers to strings Lets see how to use pointers to solve this problem. Ill modify the code fragment from the earlier session to create an array of pointers to strings, rather than an array of strings: char* arrptrs[DAYS] = { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }; for(int j=0; j<DAYS; j++) // display every string cout << arrptrs[j] << endl; Heres the output of this fragment: Sunday Monday Tuesday Wednesday Thursday Friday Saturday C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
When strings are not part of an array, C++ places them contiguously in memory so there is no wasted space. However, there must be an array that holds pointers to the strings to find them. A string is itself an array of type char, so an array of pointers to strings is an array of pointers to char. That is the meaning of the definition of arrptrs in the code above. Now recall that a string is always represented by a single address: the address of the first character in the string. It is these addresses that are stored in the array. Figure 8-10 shows how this looks. An array of pointers is a powerful construction in C++. Ill explore many examples of this in future sessions.
// get length from user cin >> feet; cin >> inches; // display distance inches << '\'; }
// make English object // make pointer to object // access object members // with dot operator
ptreng->getdist(); // access object members ptreng->showdist(); // with -> operator } You might guess that you could use the dot (.) membership access operator with pointers as well as with objects, as in ptreng.getdist(); // won't work; ptreng is not an object but this wont work. The dot operator requires the identifier on its left to be an object. Because ptreng is a pointer to an object, we need another syntax. One approach is to dereference (get the object pointed to by) the pointer: (*ptreng).getdist(); // ok but inelegant
The expression *ptreng is an object, not a pointer, so I can use it with the dot operator. However, this syntax is a bit cumbersome. A more concise approach is furnished by the membership access operator ->, which consists of a hyphen and a greater-than sign: ptreng->getdist(); // better approach The -> operator works with pointers to objects in just the same way that the dot operator works with objects.
Quiz 3 1. Which of the following are differences between a string constant defined as (1) an array and as (2) a pointer? a. You can display (1) normally, but (2) must be dereferenced. b. You cannot increment (1) but you can increment (2). c. The data type of (1) is char[], whereas the data type of (2) is char*. d. An individual array element in (1) is a character, whereas in (2) its a pointer. e. There are no differences except for the notation. 2. If a string is passed by value as a function argument, then the function a. works with the original address of the string. b. works with a copy of the address. c. works with a pointer. d. cannot access individual characters. e. cannot access the string as a whole. 3. The loop while(*ps) can be made to terminate at the end of the string pointed to by ps because a. *ps is always 0. b. ps is the length of the string. c. *ps is 0 at the end of the string. d. \0 is false. e. ps can be made to point to each character in turn. 4. The definition
char* julio[4] = { one, two, three, four };
b. the addresses of one, two, and so on are elements of an array. c. the array julio holds pointers to characters. d. the array julio holds strings. e. the array julio holds pointers to strings. 5. If ptrobj is a pointer that points to an object, and func() is one of that objects member functions, then a. ptrobj must contain the address of an object. b. func() must be a member function of ptrobj. c. you can call func() with the syntax ptrobj->func(). d. you can call func() with the syntax ptrobj.func(). e. you cant call func() unless you know an objects name. Exercise 1
Write a homemade string function that is similar to copystr() in the example above, except that it concatenates one string to another rather than copying it. That is, if str1 is dog and str2 is fight, the execution of this function: concatstr(str1, str2); will cause str1 to become dog fight. Assume theres enough space in str1 to hold the new string. Use pointer notation throughout.
Exercise 2
Write a function that contains a list of passwords in the form of an array of pointers to strings. A string representing a potential password is passed to this function and the function returns true if the string is on the list and false otherwise. Write a main() that repeatedly gets a sample string from the user, calls this function to see if the string is on the list of approved passwords, and then displays Accepted or Denied, as appropriate.
delete[] ptr; // release ptr's memory In the expression ptr = new char[len+1]; the keyword new is followed by the type of the variables to be allocated and then, in brackets, the number of such variables. Here Im allocating variables of type char and I need len+1 of them, where len is the length of str (which I found with the strlen() library function) and the 1 creates an extra byte for the null character that terminates the string. The new operator returns a pointer that points to the beginning of the section of memory. Figure 8-11 shows the
syntax of a statement using the new operator. Figure 8-11 Syntax of the new operator When using new, remember to use brackets around the numerical size; the compiler wont object if you use parentheses, but the results will be incorrect. Figure 8-12 shows the memory obtained by new and the pointer to it.
Figure 8-12 Memory obtained by new operator In the code above, the program uses strcpy() to copy string str to the newly created memory area pointed to by ptr. Because I made this area equal in size to the length of str, the string fits exactly. The program then displays the contents of ptr. The output is ptr=Idle hands are the devil's workshop. The new operator obtains memory dynamically; that is, while the program is running. This memory is allocated from an area called the heap (or sometimes the free store). The heap is the third area of memory commonly used in C++ programs. As youve learned, automatic variables are stored in the stack and static and external variables are stored in the static storage area. C programmers will recognize that new plays a role similar to the malloc() family of library functions. However, the new approach is far superior. When you use new with objects, it not only allocates memory for the object, it also creates the object in the sense of invoking the objects constructor. This guarantees that the object is correctly initialized, which is vital for avoiding programming errors. Also, new returns a pointer to the appropriate data type, whereas malloc()s pointer must be cast to the appropriate type. You should always use new for objects, never malloc().
acquired memory, then when the function terminates, the pointer will be destroyed but the memory will continue to be owned by the program. The memory will become an orphan, taking up space that is forever inaccessible. Thus, it is always good practice to delete memory when youre through with it. Deleting the memory doesnt delete the pointer that points to it (str in the example) and doesnt change the address value in the pointer. However, this address is no longer valid; the memory it points to may be changed to something entirely different. Be careful that you dont use pointers to memory that has been deleted. The brackets following delete in the example indicate that Im deleting an array. If you create a single variable with new, you dont need the brackets when you delete it: ptr = new int; // allocate a single int variable . . . delete ptr; // no brackets following delete However, dont forget the brackets when deleting arrays of objects. Using them ensures that all the members of the array are deleted and that the destructor is called for each one. If you forget the brackets, only the first element of the array will be deleted.
cout << endl << s1=; // display string s1.display(); } The xString class has only one data item: a pointer to char, called str. This pointer will point to the ordinary string held by the xString object. However, there is no array within the object to hold the string. The string is stored elsewhere; only the pointer to it is a member of xString.
Constructor in NEWSTR
The one-argument constructor in this example takes a normal (char*) string as its argument. It obtains space in memory for this string with new and sets the member str to point to the newly obtained memory. The constructor then uses strcpy() to copy the string into this newly obtained space.
Destructor in NEWSTR
You havent seen many destructors in the examples so far, but now that Im allocating memory with new, destructors become increasingly important. If you allocate memory when you create an object, its important to deallocate the memory when the object is no longer needed. As you may recall from Chapter 5, a destructor is a routine that is called automatically when an object is destroyed. The destructor in NEWSTR looks like this: ~String() { delete[] str; } This destructor gives back to the system the memory obtained when the object was created. Objects (like other variables) are typically destroyed when the function in which they were defined terminates. This destructor ensures that memory obtained by the String object will be returned to the system. If it werent explicitly returned to the system, such memory would become inaccessible (at least until the system was rebooted) and would constitute a memory leak, which, if repeated often enough, would eventually crash the system.
{ private: int feet; float inches; public: void getdist() // get distance from user { cout << \nEnter feet: ; cin >> feet; cout << Enter inches: ; cin >> inches; } void showdist() // display distance { cout << feet << \'- << inches << '\'; } }; void main() { English* distptr; // pointer to English distptr = new English; // points to new English object distptr->getdist(); // access object members distptr->showdist(); // with -> operator delete distptr; // delete object from memory } The program creates an object of type English using the new operator and returns a pointer to it called distptr. It uses this pointer with the -> operator to access getdist() and showdist(). Heres the output of the program: Enter feet: 6 Enter inches: 4.75 6'-4.75 Note that, just before the program terminates, it explicitly deletes the English object obtained earlier with new. As I discussed, it is always good practice to ensure that anything allocated with new is eventually deallocated with delete.
cout << \n Name is: << name; } }; void main(void) { person* persPtr[100]; int n = 0; char choice; do { persPtr[n] = new person; persPtr[n]->setName(); n++; cout << Enter another (y/n)? ; cin >> choice; } while( choice=='y' );
// put persons in array // // // // make new object set person's name count new person enter another person?
// quit on 'n'
for(int j=0; j<n; j++) // print names of { // all persons cout << \nPerson number << (j+1); persPtr[j]->printName(); } while(n) // delete all persons delete persPtr[--n]; // from memory } // end main() The class person has a single data item, name, that holds a string representing a persons name. Two member functions, setName() and printName(), allow the name to be set and displayed. At the end of the program, I use a while loop to delete the objects pointed to by all the pointers in the persPtr array.
Program Operation
The main() function defines an array, persPtr, of 100 pointers to type person. In a do loop, it then uses new to create a person object. It then asks the user to enter a name and gives this name to the new person object. The pointer to the person object is stored in the array persPtr. To demonstrate how easy it is to access the objects using the pointers, main() then prints out the name data for each person object, using a simple for loop. Heres some sample interaction with program: Enter name: Stroustrup <--user enters names Enter another (y/n)? y Enter name: Ritchie Enter another (y/n)? y Enter name: Kernighan Enter another (y/n)? n Person number 1 <--program displays all names stored Name is: Stroustrup Person number 2 Name is: Ritchie Person number 3 Name is: Kernighan
persPtr[j] (or equivalently by pointer notation to be *(persPtr+j)). The elements are pointers to objects of type person. Also, as you know, to access a member of an object using a pointer, you use the -> operator. Putting this all together, we have the following syntax for getname(): persPtr[j]->getName() This statement executes the getname() function for the person object pointed to by element j of the persPtr array. (Its a good thing we dont have to write programs using English syntax.)
Quiz 4 1. The new operator a. is used to declare objects or variables. b. can create and initialize an object. c. names an object or variable. d. returns a pointer to an object or variable. e. can allocate an appropriate amount of memory for an object or variable. 2. The delete operator a. deallocates memory obtained with a variable or object definition. b. deallocates memory obtained when a variable or object is created with new. c. can destroy an object. d. can destroy a variable. e. must be used with brackets, as in delete[]. 3. If you define and initialize an xString object with a copy constructor, like this:
xString x2(x1);
then its true that a. the value of the pointer str in x1 will be copied to str in x2. b. both xString objects will refer to the same char* string. c. the char* string will be copied from x1 to x2. d. x2 is an alias for x1. e. you can change the text in x1 without affecting x2. 4. If youve created a new object using the statement
p = new X; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 390 of 701
then to access a normal member function f() in the new object, you would say a. X.f(); b. X->f(); c. p.f(); d. p->f(); e. X::f(); 5. If you have an array A of pointers to objects, then to call a member function f() for the fourth object in the array you would say a. A.f(3) b. A->f(3) c. A[3].f() d. A->f() e. A[3]->f() Exercise 1
Write a program along the lines of the PTROBJS example in this session, but make it work with airtime objects rather than with person objects. The program should allow the user to enter an arbitrary number of airtime objects. It should then display them.
Exercise 2
Create a class that obtains storage for integers. It should have a store() function to insert an int value at the next available location and a get() function to read the value in any specified location. To conserve memory, the class should use new to get memory one chunk at a time. For example, a chunk could store 10 int values. When store() is called for the 11th value, it must obtain another chunk of memory using new. To the class user, the chunks are invisible; it appears as if the integers are stored contiguously, as they would be in an array. The class, however, maintains an array of pointers to chunks and accesses any given value by figuring out what chunk its in and what number it has in the chunk.
Midchapter Discussion
I hate pointers! When I look at code that has pointers in it, my mind just goes blank. I know what you mean. All those asterisks. Actually I think the asterisks are a big part of the problem. The idea behind pointers is simple, but the notation is obscure. If theyd just used a keyword like pointer_to to define each pointer, and maybe a operator like contents_of() to get the contents of a pointer, things would be a lot more understandable for beginners. George: Why have both pointer notation and array notation for accessing array elements? Don: Theres not much point in actually accessing array elements with pointer notation. Its just educational to see that *(array+j) is the same as array[j]. The assembly language code generated by the compiler actually works with pointers, but its easier for humans to work with arrays. Estelle: Then what are pointers really for? C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 391 of 701 George: Estelle: Don:
I think the most critical use is connecting together the parts of complex data structures. Like what? Linked lists and stuff like that, which I suppose well get to later in this chapter. Weve already seen an array of pointers to objects, and the pointer returned from new is important. I dont know how you would access dynamically allocated memory without a pointer.
// the this pointer referring to data #include <iostream.h> class what { private: int alpha; public: void tester() { this->alpha = 11; cout << this->alpha; } };
void main() { what w; w.tester(); } This program simply prints out the value 11. The tester() member function accesses the variable alpha using the expression this->alpha which has exactly the same effect as referring to alpha directly. This syntax works, but there is no reason for it except to show that this does indeed point to w, the object that invoked the member function tester().
{ data = d; } void display() // display data { cout << data; } alpha& operator = (alpha& a) // overloaded = operator { data = a.data; // (not done automatically) cout << \nAssignment operator invoked; return *this; // return ref to this alpha } }; void main() { alpha a1(37); alpha a2, a3; a3 = a2 = a1; cout << \na2=; a2.display(); // invoke overloaded = // display a2
cout << \na3=; a3.display(); // display a3 } In this program, I declare the overloaded = operator as alpha& operator = (alpha& a) which returns by reference. The last statement in this function is return *this; Because this is a pointer to the object that invoked the function, *this is that object itself, and the statement returns this object. Heres the output of ASSIGN3: Assignment operator invoked Assignment operator invoked a2=37 a3=37 Each time the equal sign is encountered in a3 = a2 = a1; the overloaded operator=() function is called, which displays Assignment operator invoked. The three objects all end up with the same value, as shown by the output. Returning by reference from member functions and overloaded operators is a powerful and commonly used approach that avoids the creation of unnecessary objects. This makes your program smaller, faster, and less prone to errors.
const Variables
Youve seen that if you want an ordinary (nonpointer) variable to be unchanged for the duration of the program, you make it const. For example, following the definition const int var1 = 123; any attempt to modify var1 will elicit an error message from the compiler. It will have the value 123 throughout the program.
const int* const r = &a; // constant pointer to constant int ++r; // error: can't modify a const r ++(*r); // error: can't modify a const a If const is placed before the data type (as in the definition of p), the result is a pointer to a constant variable. If const is placed after the data type (as in the definition of q), the result is a constant pointer to a variable. In the first case, you cant change the value in the variable the pointer points to; in the second, you cant change the address in the pointer. Using const in both places (as in the definition of r) means that neither the pointer nor the variable it points to can be modified. You should use const whenever possible. If a pointer value should never change, make the pointer const. If the pointer should never be used to change what it points to, make it a pointer to const. All this may not seem vital in this example, because the variable a itself isnt const. Why worry about modifying it with a pointer if it can be modified directly? Things become more realistic when you have a variable thats already const and you want to define a pointer to it: const int b = 99; // const variable int* r = &b; // error: can't convert const to non-const const int* s = &b; // ok int* const t = &b; // error: can't convert const to non-const The compiler protects you from writing statements that could lead to the value of a const variable being changed, even if the change takes place via a pointer. You cant assign the address of a const variable such as b to a pointer unless its defined as a pointer to a constant variable (as is pointer s in the example). This maintains constness because you cant use such a pointer to modify the original const variable: ++(*s); // error:can't modify a const
Quiz 5 1. The this pointer a. must never be accessed directly by the programmer. b. must be initialized by the programmer. c. is different in different versions of the same member function that are invoked by different objects. d. is different in different member functions called by the same object. e. points to the object that invoked a member function. 2. When you return *this from a function, a. the return type of the function must be the same class as the object that invoked the function.
b. the return type of the function must be a pointer to the same class as the object that invoked the function. c. you can usually return by reference. d. you cannot return by value. e. the object that invoked the function is destroyed. 3. By using const appropriately, you can define a pointer that prevents any change to a. the variable the pointer points to. b. the contents of the pointer. c. the address of the pointer. d. the address of what the pointer points to. e. the contents of what the pointer points to. 4. If a function is declared as
void f(const int* arg1);
a. it cant change the contents of arg1. b. it cant change the value of the variable whose address is in arg1. c. it cant access the contents of arg1. d. it cant access the value of the variable whose address is in arg1. e. the compiler will flag an error. 5. If a function is declared as
const int* f();
a. it can return a pointer to a constant variable. b. it cannot return a pointer to a nonconstant variable. c. the return value cant be changed. d. the value pointed to by the return value cant be changed. e. the return value must be assigned to a const variable.
Due to its theoretical nature, this session contains no exercises.
Figure 8-13 Replicating strings Instead of having each xString object contain its own string, I could arrange for each xString object to contain only a pointer to a string. Now, if I assign one xString object to another, I need only copy the pointer from one object to another. Both pointers will point to the same string. This saves space because only a single copy of the string itself needs
to be stored in memory. Figure 8-14 shows how this looks. Figure 8-14 Replicating pointers to strings However, if I use this system, I need to be careful when I destroy an xString object. If an xStrings destructor uses delete to free the memory occupied by the string, and if there are other objects with pointers pointing to the same string, then these other objects will be left with pointers pointing to memory that no longer holds the string they think it does; they become dangling pointers. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 398 of 701
To use pointers to strings in xString objects successfully, I need a way to keep track of how many xString objects point to a particular string so I can avoid deleting the string until the last xString that points to it is itself deleted. In the following section, youll see an example program, STRIMEM, that shows one way to do this.
xString object contains a pointer to the appropriate strCount object. Figure 8-15 shows the arrangement. Figure 8-15 xString and strCount classes A strCount object manages the char* string. Its constructor uses new to create the string, and its destructor uses delete to delete it. To provide xString objects with access to the count and the string within their strCount object, I supply strCount with a number of short member functions: getstr(), getCount(), incCount(), and decCount().Listing 8-14 shows STRIMEM. Listing 8-14 STRIMEM // strimem.cpp C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 399 of 701
// memory-saving xString class // the this pointer in overloaded assignment #include <iostream.h> #include <string.h> // for strcpy(), etc //////////////////////////////////////////////////////////////// class strCount // keep track of number { // of unique strings private: int count; // number of xStrings char* str; // pointer to string public: strCount(const char* const s) // one-arg constructor { int length = strlen(s); // length of string argument str = new char[length+1]; // get memory for string strcpy(str, s); // copy argument to it count=1; // start count at 1 } ~strCount() // destructor { delete[] str; } char* getstr() // get string { return str; } int getCount() // get count { return count; } void incCount() // increment count { ++count; } void decCount() // decrement count { --count; } }; // end class strCount //////////////////////////////////////////////////////////////// class xString // xString class { private: strCount* psc; // pointer to strCount public: xString() // no-arg constructor { psc = new strCount(NULL); } xString(const char* const s) // 1-arg constructor { psc = new strCount(s); } xString(const xString& S) // copy constructor { cout << \nCOPY CONSTRUCTOR; psc = S.psc; psc->incCount(); } ~xString() // destructor { if(psc->getCount()==1) // if we are its last user, delete psc; // delete our strCount else // otherwise, psc->decCount(); // decrement its count } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 400 of 701
void display() // display the xString { cout << psc->getstr(); // print string cout << (addr= << psc << ); // print address } // assignment operator xString& operator = (const xString& S) { cout << \nASSIGNMENT; if(psc->getCount()==1) // if we are its last user, delete psc; // delete our strCount else // otherwise, psc->decCount(); // decrement its count psc = S.psc; // use argument's strCount psc->incCount(); // increment count return *this; // return this object } }; // end class xString //////////////////////////////////////////////////////////////// void main() { // 1-arg constructor xString s1 = When the fox preaches, look to your geese.; cout << \ns1=; s1.display(); // display s1 xString s2; // define s2 s2 = s1; // set equal to s1 cout << \ns2=; s2.display(); // display s2 xString s3(s1); // initialize s3 to s1 cout << \ns3=; s3.display(); // display s3 } In the main() part of STRIMEM, the program defines an xString object, s1, to contain the proverb When the fox preaches, look to your geese. It defines another xString s2 and sets it equal to s1; then it defines s3 and initializes it to s1. Setting s2 equal to s1 invokes the overloaded assignment operator, and initializing s3 to s1 invokes the overloaded copy constructor. The program displays all three xStrings and the address of the strCount object pointed to by each objects psc pointer to show that they all refer to the same strCount object (and hence the same char* string). Heres the output from STRIMEM: s1=When the fox preaches, look to your geese. (addr=0x8f510e3e) ASSIGNMENT s2=When the fox preaches, look to your geese. (addr=0x8f510e3e) COPY CONSTRUCTOR s3=When the fox preaches, look to your geese. (addr=0x8f510e3e) Notice how short and simple main() is. Once youve written the classes, using them is easy. The duties of the xString class are divided between the xString and strCount classes. Lets see what they do.
Quiz 6 1. The advantage of the xString class in the STRIMEM program, compared with earlier versions of xString, is that a. memory is saved. b. the + operator is overloaded. c. you can set one xString object equal to another.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 404 of 701
d. objects can be initialized more quickly. e. if you set one object equal to another, you can delete one without invalidating the other. 2. When you set one xString object from STRIMEM equal to another, as in
s2 = s1;
a. s1 is copied memberwise into s2. b. a char* string is copied from s1 to s2. c. a pointer is copied from s1 to s2. d. the pointer-to-strCount in s2 is changed. e. the count in the strCount pointed to by s1 is changed. 3. The count stored in objects of the strCount class represents a. how many xString objects the strCount object points to. b. how many char* strings the strCount object points to. c. the number of xString objects in the program. d. the number of xString objects using that strCount. e. how many times the corresponding xString object has been referenced. 4. The const in the argument in the copy constructor declarator for xString in STRIMEM means that a. the object for which the copy constructor is invoked cant be modified. b. the object for which the copy constructor is invoked will point to the same strCount as the argument to the copy constructor. c. the newly initialized object is const. d. the copy constructor cant modify the original object. e. the copy constructor cant modify the new object. 5. The fact that the overloaded = operator uses a reference return for the object that invoked it means that a. the = operator can be chained. b. no extra xString objects are created if the = operator used, whether it assigns the return value to a variable or not. c. no extra xString objects are created if the = operator is chained.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 405 of 701
d. the = operator can be used to set one xString equal to another. e. the copy constructor can use the same code as the = operator. Exercise 1
Create a member function of xString, called mid(), that returns a new xString that is a substring from the middle part of the xString that invoked the function The first argument to this function is the character number where the substring starts (starting at 0 on the left), and the second argument is the number of characters in the substring. For example, if s2 is dogs and chickens, then the statement s1 = s2.mid(5, 3) will set s1 to and.
Exercise 2
Overload the + operator in the xString class so you can concatenate xString objects. For example, if s1 is Harry, s2 is , and s3 is Brown and you execute name = s1 + s2 + s3; then name should have the value Harry Brown.
A Chain of Pointers
The linked list provides a more flexible storage system in that it doesnt use arrays at all. Instead, space for each data item is obtained as needed with new and each item is connected, or linked, to the next item using a pointer. The individual items dont need to be located contiguously in memory as array elements are; they can be scattered anywhere theres room for them. In the example, the entire linked list is an object of class linklist. The individual data items, or links, are represented by structures of type link. (I could have used class objects for the links, but theres no point because they dont need member functions.) Each such structure contains an integerrepresenting the objects single data itemand a pointer to the next link. The linklist object itself stores a pointer to the link at the head of the list; this is its only data item. The arrangement is shown in Figure 8-18.
Figure 8-18 A linked list The linklist class contains member functions that allow the class user to add a link to the list and to display the data in all the links. Of course, in a real program there would be much more data than a single int stored in each link. A link might contain a complete personnel file or the inventory data on a car part, for example. Listing 8-15 shows LINKLIST. Listing 8-15 LINKLIST // linklist.cpp // linked list #include <iostream.h> struct link { int data; link* next; }; class linklist { private: link* first; public: linklist() { first = NULL; } void additem(int d); void display(); }; void linklist::additem(int d) { link* newlink = new link; newlink->data = d; newlink->next = first; first = newlink; } void linklist::display() { link* current = first; while( current != NULL ) // one element of list // data item // pointer to next link
// a list of links
// pointer to first link // // // // no-argument constructor no first link yet add data item (one link) display all links
// add data item // // // // make a new link give it data it points to next link now first points to this
// display all links // set ptr to first link // quit on last link Page 407 of 701
void main() { linklist li; // make linked list li.additem(25); // add four items to list li.additem(36); li.additem(49); li.additem(64); li.display(); // display entire list } The linklist class has only one member data item: the pointer to the start of the list. When the list is first created, the constructor initializes this pointer, first, to NULL. The NULL constant is defined in the mem.h header file (which is #included in the iostream.h file) to be 0. This value serves as a signal that a pointer does not hold a valid address. In the program, a link whose next member has a value of NULL is assumed to be at the end of the list.
Self-Containing Classes
I should note a possible pitfall in the use of self-referential classes and structures. The link structure in LINKLIST contained a pointer to the same kind of structure. You can do the same with classes: class sampleclass { sampleclass* ptr; // this is fine }; However, although a class can contain a pointer to an object of its own type, it cannot contain an object of its own type: class sampleclass { sampleclass obj; // can't do this }; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 409 of 701
Containers
The linklist class is an example of a container class. Container classes are used for storing (containing) data. In Chapter 12 Ill investigate the Standard Template Library (STL), which includes such containers as vectors, lists, sets, and deques. The linked list stores integers and only integers. Most commercial container classes (including the STL) allow containers to store any kind of object or basic type by implementing the container as a template. Youll learn more about templates in Chapter 11.
Quiz 7 1. Which of the following are advantages of a linked list as a data-storage device? a. You can quickly access an item with specified characteristics. b. The size of the data storage area can expand and contract dynamically. c. You can quickly insert a new item. d. You can quickly delete an existing item with specified characteristics. e. The first item entered is the first to be removed. 2. The end of the linked list is signaled by a. the current link number equaling the total number of links. b. a 0 value in the data item in any link. c. a NULL value in the next variable of the last link. d. a NULL value in the first variable in linklist. e. a pointer pointing back to first. 3. To add a new link to the linked list, using the scheme in INKLIST, a member function must a. ask the programs user to enter the data that will be stored in the link. b. change first so it points to the new link.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 410 of 701
c. leave the value of first unchanged. d. set the next pointer in the new link so it points to the last link. e. set the next pointer in the new link so it has whatever value was previously in first. 4. If a member function of linklists purpose is to delete a link that held specific data, this function would need to a. step through the list, looking for the link to be deleted. b. create a temporary link with a NULL pointer. c. use delete to delete a link. d. put the address of the deleted link in first. e. rearrange the pointer in the previous link to bypass the deleted link. 5. Suppose you define a linklist object, as specified in the LINKLIST program above, as an automatic variable in a standalone (nonmember) function f(). Then, within f(), you add numerous links to the list. Which of the following will happen when f() terminates? a. The linklist object will continue to occupy memory. b. The first pointer will point to an invalid address. c. The links will continue to occupy memory. d. The data in the links can be accessed by calling f() again and creating a new linklist object. e. The list can be accessed from some other function. Exercise 1
Write a destroy_list() member function for the linklist class. It should follow the chain from link to link, deleting each link as it goes, and set first to NULL.
Exercise 2
Write a remove_item() member function for the linklist class. It should chain through the list, looking for a link with a data item that matches a value supplied by the programs user. When remove_item() finds this link, it should delete it and rearrange the pointers so the previous link points to the next link. (That is, if you delete link 6, then the pointer in link 5 should end up pointing to link 7.)
Figure 8-20 Insertion into a SortedArray Listing 8-16 shows SORTEMPS. Listing 8-16 SORTEMPS // sortemps.cpp // sorted array class holds sorted employee objects #include <iostream.h> //////////////////////////////////////////////////////////////// class employee // employee class { private: enum { LEN=30 }; // maximum length of names char name[LEN]; // employee name unsigned long number; // employee number public: void getdata() // get data from user { cout << \n Name: ; cin >> name; cout << Number: ; cin >> number; } void putdata() const // display data { C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 412 of 701
cout << \n Name = << name; cout << \n Number = << number; } unsigned long get_number() const // return emp number { return number; } }; //////////////////////////////////////////////////////////////// class SortedArray { private: enum {SIZE=100}; employee* arr[SIZE]; // array of pointers to emps int total; // number of emps in array public: SortedArray() : total(0) // no-arg constructor { } employee* operator[](int) const; // declarations void insert(employee*); }; // read data from element n employee* SortedArray::operator[](int n) const { return arr[n]; } // insert in sorted order void SortedArray::insert(employee* data) { int j = 0; while(j < total && // find correct place data->get_number() > arr[j]->get_number() ) j++; for(int k=total; k>j; k--) // move higher elements up arr[k] = arr[k-1]; arr[j] = data; // insert new data total++; // now it has one more element } //////////////////////////////////////////////////////////////// void main() { SortedArray sa; // a sorted array employee* ptr; // utility pointer char answer; // 'y' or 'n' int total = 0; // number of employees in array do { ptr = new employee; // make an employee cout << Enter data for employee << ++total << : ; ptr->getdata(); // get employee data from user sa.insert(ptr); // put employee in sorted array cout << Do another (y/n)? ; cin >> answer; } while(answer != 'n'); cout << \nCONTENTS SORTED BY NUMBER << endl; for(int j=0; j<total; j++) // display data in order { cout << \nEmployee << (j+1); sa[j]->putdata(); C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 413 of 701
} } Heres some sample interaction with SORTEMPS in which the user enters data for five employees: Enter data for employee 1: Name: Wesley Number: 456 Do another (y/n)? y Enter data for employee 2: Name: McDonald Number: 234 Do another (y/n)? y Enter data for employee 3: Name: O'Hara Number: 345 Do another (y/n)? y Enter data for employee 4: Name: Chang Number: 123 Do another (y/n)? y Enter data for employee 5: Name: Aaron Number: 567 Do another (y/n)? n CONTENTS SORTED BY NUMBER Employee 1 Name = Chang Number = 123 Employee 2 Name = McDonald Number = 234 Employee 3 Name = Wesley Number = 345 Employee 4 Name = O'Hara Number = 456 Employee 5 Name = Aaron Number = 567 The program has three parts: the employee class, the SortedArray class, and main().
the SortedArray itself, rather than the user, determines where each element will be inserted. If the user could insert an object at an arbitrary location, the order could be disrupted. To insert an element in the SortedArray, the user calls the insert() member function. This functions only argument is a pointer to the object to be inserted. First, insert() examines the employee objects that are pointed to by the pointers in its array. The pointers are arranged in order of increasing employee numbers in the objects they point to. When insert() finds an object with a higher employee number than the one its trying to insert, it knows it should insert the new object just before this one. But first, insert() must move all the pointers that have higher employee numbers up one space to create room for the new element. This is handled by the for loop. Finally, a pointer to the new object is inserted in the newly vacated element.
In main()
The main() program creates a sorted array; then, in a do loop, it repeatedly creates employee objects, using new, and asks the user to fill them with data. The address of each employee object is then inserted into the sorted array. When the user is done inserting employees, the contents of the entire sorted array are displayed. Because they were inserted in sorted order, its easy to display them in sorted order by accessing each element in turn.
Figure 8-21 A binary search In the SortedArray of employee objects, you first examine the object in the middle of the array. If the employee number youre looking for is larger than that contained in this middle element, you can forget the bottom half of the array and concentrate on the top half. Or, if the number youre looking for is less than the middle element, you can concentrate on the bottom half. You repeat this process, dividing the relevant half into quarters, and the relevant quarter into eighths, and so on, until youve narrowed the search to a single element. If this element doesnt have a matching number, the search fails. Listing 8-17 shows FINDEMP. Listing 8-17 FINDEMP // findemp.cpp // sorted array class holds sorted employee objects // finds employee with binary search #include <iostream.h> #include <string.h> //////////////////////////////////////////////////////////////// C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 416 of 701
class employee { private: enum { LEN=30 }; char name[LEN]; unsigned long number; public:
// employee class
// 2-arg constructor employee(char* na, unsigned long nu) : number(nu) { strcpy(name, na); } void putdata() const // display data { cout << \n Name = << name; cout << \n Number = << number; } // return employee number unsigned long get_number() const { return number; } }; //////////////////////////////////////////////////////////////// class SortedArray { private: enum {SIZE=100}; // size of array employee* arr[SIZE]; // define array int total; // number of objects in array public: SortedArray() : total(0) { } employee* operator[](int) const; void insert(employee*); employee* search(unsigned long); }; // read data from element n employee* SortedArray::operator[](int n) const { return arr[n]; } // insert in sorted order void SortedArray::insert(employee* data) { int j = 0; while(j < total && // find correct place data->get_number() > arr[j]->get_number() ) j++; for(int k=total; k>j; k--) // move higher elements up arr[k] = arr[k-1]; arr[j] = data; // insert new data total++; // now it has one more element } // binary search for employee with specified employee number employee* SortedArray::search(unsigned long num_to_find) { int lower = 0; // range of index numbers int upper = total; // to be searched C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 417 of 701
int index;
while(upper > lower) if(upper-lower > 1) index = lower + (upper-lower)/2; else index = upper = lower;
employee* ptr = arr[index]; // get ptr to emp unsigned long num = ptr->get_number(); // get emp number if(num == num_to_find) // if exactly right, return ptr; // return pointer to emp if(num < num_to_find) // if emp number too low, lower = index; // move lower bound up else // if too high, upper = index; // move upper bound down } return NULL; // no match } //////////////////////////////////////////////////////////////// void main() { int j; SortedArray sa; // a sorted array unsigned long number; // number to find employee* pemp; // ptr to found employee const int LIMIT = 10; // number of employees employee emparr[LIMIT] = // array of employees { employee(Webley, 468L), // (note use of constructors employee(Suzuki, 672L), // for initialization) employee(Smith, 371L), employee(Gonzalez, 274L), employee(Wong, 431L), employee(LeMonde, 789L), employee(Weinstein, 147L), employee(DeCarlo, 223L), employee(Nguyen, 390L), employee(O'Grady, 573L) }; for(j=0; j<LIMIT; j++) sa.insert(emparr+j); // insert address of each // employee into SortedArray
for(j=0; j<LIMIT; j++) // display data in order { cout << \nEmployee << (j+1); sa[j]->putdata(); } cout << \n\nEnter employee number to search for: ; cin >> number; pemp = sa.search(number); // search for employee if(pemp != NULL) { cout << \nEmployee with that number is; pemp->putdata(); } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
Quiz 8 1. Suppose pointers to person objects are placed in a sorted array according to a characteristic called height, with lower height values occupying lower array indexes. This is a good arrangement if a. the height of many person objects is not known when they are inserted in the array. b. no two objects have the same value for height. c. the array index occupied by an object must reflect the order in which it was inserted. d. single objects will be retrieved using their height characteristic. e. all the objects in the array will be accessed in order, from shortest to tallest.
2. Storing pointers to objects in an array, rather than storing the objects themselves, has the advantage that a. objects stored in an array take up less space than those allocated with new. b. an object with a given index can be accessed more quickly. c. the objects can be sorted more easily. d. its quicker to organize storage for a new object. e. an object with a specified characteristic can be accessed more quickly.
SortedArray
3. In the FINDEMP program, when you insert a pointer to a new employee object into a object,
a. you must examine every array element with an employee number less than the one youre going to insert. b. the total member of SortedArray is incremented. c. the pointer goes at the smallest empty index. d. you must move the pointers to all objects that have employee numbers greater than that of the new object. e. you must move all the pointers that have index values greater than that of the new object. 4. In a binary search of a sorted array, a. each comparison divides the range to be searched into thirds. b. from an efficiency standpoint, it doesnt really matter whether the array contains objects or pointers to objects. c. if the array has 32 elements, youll need to do about 6 (or possibly fewer) comparisons. d. from an efficiency standpoint, it doesnt really matter whether the array is sorted or not. e. the array should be sorted using the same characteristic thats being searched for. 5. To initialize three objects of class X, which require a one-argument constructor and are elements in an array A, you might say a. X A[SIZE] = { 121, 232, 343 }; b. X A[SIZE] = { X(121), X(232), X(343) }; c. X A[SIZE] = { A(121), A(232), A(343) }; d. X A[SIZE] = X( 121, 232, 343 );
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 420 of 701
e. The objects can be initialized only after the array is defined Exercise 1
Change the insert() member function in SortedArray so it uses a binary search to find where to insert a new element, rather than simply stepping through all the elements from the beginning.
Exercise 2
Change the SortedArray class in FINDEMP so the array is sorted according to employee name rather than number. The names should be in alphabetical order. You can use the strcmp() library function to compare two names.
Summary: Chapter 8
In this chapter Ive introduced pointers and shown some typical uses for them. A pointer is a variable that holds the memory address of some other variable. Pointers are a way of connecting parts of a program: A pointer in one part of a program points to another part of the program. For example, the new operator returns a pointer to a section of memory. Links in a linked list contain a pointer to the next link. An array of pointers allows access to any object whose pointer is in the array, and so on. The this pointer, generated by the compiler, is automatically defined within most member functions and always points to the object that invoked the function. For example, if you say objA.func(); then when func() is executed, it will contain a this pointer to objA. Its good to use const wherever possible to avoid mistakenly changing values that shouldnt be changed. A pointer can be const in two different ways: The variable pointed to by the pointer may be const or the address in the pointer, specifying what variable is pointed to, may be const.
End-Of-Chapter Discussion
Estelle: Don: George: Estelle: Don: Estelle: The this pointer is just as cute as can be. It is useful for returning objects. Why is it such a big deal to keep from making extra copies of objects? Objects can be a lot bigger than basic type variables, so copying them takes time and memory. And there can be weird side effects to making copies. Some programs really care how many objects there are and get confused if there are extra ones. Thats why its nice to pass and return objects by reference, and thats why you need to return *this, so you have a more or less permanent object to return by reference. If you dont mind making a copy, you can make a temporary object and return it by value. Putting const everywhere sure clutters up the listing. I never use it; it makes things look too messy. Well, in small programs you can get away with not using const. But when projects get big and hairy and complicated, you really want the compiler to help you find errors. The more you use const, the more the compiler can stop you from trying to change a constant. Absolutely. But if const bothers you, it probably doesnt hurt to write the code first without it and then go back and put it in later after youve conceptualized everything. All of a sudden were spending all our time talking about container classes. Whats that got to do with C++? You need to know about data storage structures whether youre using a procedural language or OOP. But in C++, containers make really good examples. You can make them into objects and put other objects in them. And you learn a lot about pointers when you write a container class. Thats for sure. But dont worry too much if you dont follow all the details of the containers. Its the stuff youd normally learn in a Data Structures and Algorithms class in computer science. In the real world, youll probably buy a set of containers from someone or use the ones that come Page 421 of 701
George: Estelle:
Estelle: Don:
George:
with your compiler. In the meantime, just try to follow the OOP aspects of containers. Yeah, well, Ill get to that as soon as I figure out what all these asterisks mean
Polymorphism
In real life, there is often a collection of different kinds of things that, given identical instructions, should take different actions. Take students, for example. There may be many kinds of students in a university: language students, premed students, computer science majors, athletes. Suppose you are the dean of this university and you want to send a directive to all students: Fill out your registration forms! Different kinds of students have different registration forms because theyre in different departments. But as the dean, you dont need to send a different message to each group (Fill out the premed registration form to the premed students; Fill out the engineering registration form to engineers; and so on). One message works for everyone because everyone knows how to fill out his or her own particular version of the registration form. Polymorphism means taking many shapes. The deans single instruction is polymorphic because it looks different to different kinds of students. To the premed student, its an instruction to fill out the premed form, whereas to the English major, its an instruction to fill out the liberal arts form. Typically, polymorphism occurs in classes that are related by inheritance. In C++, polymorphism means that a call to a member function will cause a different function to be executed depending on the type of object that invokes the function. This sounds a little like function overloading, but polymorphism is a different, and much more powerful, mechanism. One difference between overloading and polymorphism has to do with which function to execute when the choice is made. With function overloading, the choice is made by the compiler. With polymorphism, its made while the program is running. Function overloading is merely a convenience for a class user, whereas polymorphism affects the entire architecture of a program. This is all rather abstract, so lets start with some short programs that show parts of the situation, and put everything together later. In practice, virtual functions are associated with pointers to objects, so lets see how they work together.
class Base // base class { public: void show() // normal function { cout << \nBase; } }; class Derv1 : public Base // derived class 1 { public: void show() { cout << \nDerv1; } }; class Derv2 : public Base // derived class 2 { public: void show() { cout << \nDerv2; } }; void main() { Derv1 dv1; // object of derived class 1 Derv2 dv2; // object of derived class 2 Base* ptr; // pointer to base class ptr = &dv1; ptr->show(); // put address of dv1 in pointer // execute show()
ptr = &dv2; // put address of dv2 in pointer ptr->show(); // execute show() } The Derv1 and Derv2 classes are derived from class Base. All three classes have a member function show(). In main(), the program creates objects of class Derv1 and Derv2 and a pointer to class Base. Then it puts the address of a derived class object in the base class pointer in the line ptr = &dv1; // derived class address in base class pointer Remember that its perfectly all right to assign an address of one type (Derv1) to a pointer of another (Base), because pointers to objects of a derived class are type compatible with pointers to objects of the base class. Now the question is, when you execute the next statement ptr->show(); what function is called? Is it Base::show() or Derv1::show()? I can ask the same question after I put the address of an object of class Derv2 in the pointer and again try to execute its show() function: ptr = &dv2; // put address of dv2 in pointer ptr->show(); Which of the show() functions is called here: Base::show() or Derv2::show()? The output from the program answers these questions: Base Base The function in the base class is executed in both cases. The compiler ignores the contents of the pointer ptr and chooses the member function that matches the type of the pointer, as shown in Figure 9-1.
ptr = &dv2; // put address of dv2 in pointer ptr->show(); // execute show() } The output of this program is Derv1 Derv2 Now the member functions of the derived classes, not the base class, are executed. I change the contents of ptr from the address of Derv1 to that of Derv2, and the particular instance of show() that is executed also changes. So the same function call, ptr->show(); executes different functions, depending on the contents of ptr. The compiler selects the function based on the contents of the pointer ptr, not on the type of the pointer, as in NOTVIRT. This is polymorphism at work. Ive
made show() polymorphic by designating it virtual. Figure 9-2 shows how this looks. Figure 9-2 Virtual pointer access
Late Binding
The astute reader may wonder how the compiler knows what function to compile. In NOTVIRT, the compiler has no problem with the expression ptr->show(); It always compiles a call to the show() function in the base class. But in VIRT, the compiler doesnt know what class the contents of ptr may be a pointer to. It could be the address of an object of the Derv1 class or the Derv2 class. Which version of draw() does the compiler call? In fact, at the time its compiling the program, the compiler doesnt know what to do, so it arranges for the decision to be deferred until the program is running. At runtime, when the function call is executed, code that the compiler placed in the program finds out the type of the object whose address is in ptr and calls the appropriate show() function: Derv1::show() or Derv2::show(), depending on the class of the object. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 425 of 701
Selecting a function at runtime is called late binding or dynamic binding. (Binding means connecting the function call to the function.) Connecting to functions in the normal way, during compilation, is called early binding or static binding. Late binding requires a small amount of overhead (the call to the function might take something like 10 percent longer) but provides an enormous increase in power and flexibility, as youll see.
How It Works
You now know the bare bones of using virtual functions, but sometimes its nice to understand a little of what goes on behind the scenes. Remember that, stored in memory, a normal objectthat is, one with no virtual functionscontains only its own data, nothing else. When a member function is called for such an object, the compiler passes to the function the address of the object that invoked it. This address is available to the function in the this pointer, which the function uses (usually invisibly) to access the objects data. The address in this is generated by the compiler every time a member function is called; its not stored in the object and does not take up space in memory. The this pointer is the only connection thats necessary between an object and its normal member functions. With virtual functions, things are more complicated. When a derived class with virtual functions is specified, the compiler creates a tablean arrayof function addresses called the virtual table. (It is named something like vtbl or vtable, depending on the compiler.) In the VIRT example, the Derv1 and Derv2 classes each have their own virtual table. There is an entry in each virtual table for every virtual function in the class. Because they are both derived from Base, Derv1 and Derv2 have the same virtual functions. The virtual table arrays for Derv1 and Derv2 can therefore contain entries for the same function names, arranged in the same order. However, the addresses in the table are different for the two classes. Figure 9-3 shows this arrangement. It also shows additional functions in each class, func1() and func2(), to make it clear that the virtual tables each have many entries (pointers to the appropriate member functions).
Figure 9-3 Virtual function linkage Every object of Derv1 or Derv2, when its constructed, contains an extra pointer in addition to its own data. This is true of any object of a class that has one or more virtual functions. This pointer, called something like vptr or vpointer, contains the address of the class virtual table. Thus, objects of classes with virtual functions are slightly larger than normal objects. Including the address to a class specific vtbl in an objects vptr amounts to allowing an object to know what class it is. In the example, when a virtual function is called for an object of Derv1 or Derv2, the compiler, instead of specifying what function will be called, creates code that will first look at the objects vptr and then uses this to access the appropriate member function address in the class vtable. Thus, for virtual functions, the object itself determines what function is called, rather than the compiler. The VIRT example is not very realistic in that the pointers to objects used with virtual functions are not usually stored in standalone variables, but in arrays. The next example shows a more typical situation.
// virtual function
// derived class 1
// derived class 2
// array of pointers to Base // object of derived class 1 // object of derived class 2 // put address of dv1 in array // put address of dv2 in array
for(int j=0; j<2; j++) // for all array elements, arrBase[j]->show(); // execute show() } Ive put the addresses of the objects dv1 and dv2 into the array arrBase, which is of type Base. As in the VIRT program, the compiler uses the type of the pointers stored in the array, not the type of the array, to figure out which version of show() to call. The output is Derv1 Derv2 Notice how easy it is for the for loop in main() to call show() for any number of objects. This is the heart of polymorphism: A single function call can invoke many different functions, depending on the type of object doing the calling.
Base arrObjs[2]; Derv1 dv1; Derv2 dv2; arrObjs[0] = dv1; arrObjs[1] = dv2;
// array holds base class objects // object of derived class 1 // object of derived class 2 // put dv1 object in array // put dv2 object in array
for(j=0; j<2; j++) // for all array elements, arrObjs[j].show(); // execute show() } Although the compiler will let you put a derived class object in a base class array, the results probably wont be what you hoped for. The output from this version of main() is Base Base Why arent the derived class versions of show() executed? Well, for one thing, objects of a derived class may not be the same size as objects of the base class. Theyre usually bigger (although not in this example) because they include additional data that makes them a kind of base class object. Thus, they dont fit in the base class array. If you try to stick a derived class object in a base class array, the parts of the object that are specific to the derived class will be sliced off, leaving you with a base class object. Even if theres no size difference, the compiler regards derived class objects as base class objects. The moral is, never put derived class objects in base class variables; theyll forget what they are. Pointers, on the other hand, are the same size no matter what class they point to, so you dont lose any information putting a derived class address in an array of base class pointers. The moral: To use polymorphism, invoke virtual functions with pointers to objects (or with references, which are pointers in disguise), but not with objects.
Quiz 1 1. If you put the address of a derived class object into a variable whose type is a pointer to the base class, then the object, when accessed using this pointer, a. continues to act like a derived class object. b. acts like a base class object. c. will cause the compiler to issue an error message. d. continues to act like a derived class object if virtual functions are called. e.can determine which of several virtual functions to call. 2. Virtual functions are appropriate when a. you dont know, at the time you write the program, what class to use. b. pointers (or references) to objects are used to access the object. c. the function bodies are empty. d. objects of different types should respond to the same message in different ways. e. objects of the base class will never be invoked. 3. Late binding
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 429 of 701
a. concerns connecting a function call to a function. b. relies on an object to know what its own member functions are. c. happens at compile time. d. happens at link time. e. happens at runtime. 4. If you have an array of pointers to objects called arr_of_ptrs_to_objs, and this array is of type alpha*, and there is a class beta derived from alpha, and alpha and beta have a virtual function called mayday(), then which of the following statements are reasonable? a. alpha->mayday(); b. beta->mayday(); c. arr_of_ptrs_to_objs[3]->mayday(); d. beta* arr_of_ptrs_to_objs[77]; e. alpha arr_of_ptrs_to_objs[77]; 5. When you put a derived class object in a base class variable a. it continues to act like a derived class object. b. it starts to act like a base class object. c. the compiler issues an error message. d. only the objects virtual functions can be executed. e. only the objects nonvirtual functions can be executed.
Due to its theoretical nature, this session contains no exercises
A Personnel Example
This example models the kinds of people one finds in a school or university. The major categories are students and teachers. Noticing that these categories have some features in common, Ill arrange for the student and teacher classes to be derived from a person class. In addition, other people may be connected with the university who are neither students nor teachers (administrators, alumni, consultants, and so on). Ill assume I dont want to bother creating specific classes for these miscellaneous categories, so Ill lump them all into the person class. For simplicity, the only data in the person class is a name. One can imagine the other variables that might be appropriate to include as well, such as address, telephone number, and social security number. The student class C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 430 of 701
includes the students grade point average (GPA), and the teacher class includes the number of scholarly papers the teacher has published. All the classes contain getData() and putData() functions for basic input and output. They also contain a function called isOutstanding(), which makes it easy for the school administrators to create a list of outstanding students and teachers for the venerable Awards Day ceremony. Listing 9-4 shows VIRTPERS. Listing 9-4 VIRTPERS // virtpers.cpp // virtual functions with person class #include <iostream.h> class person // person class { protected: char name[40]; public: virtual void getData() { cout << Enter name: ; cin >> name; } virtual void putData() { cout << \nName = << name; } virtual void isOutstanding() { } // note: empty function body }; // student class class student : public person { private: float gpa; // grade point average public: void getData() // get student data from user { person::getData(); cout << Enter student's GPA: ; cin >> gpa; } void putData() { person::putData(); cout << GPA = << gpa; } void isOutstanding() { if (gpa > 3.5) cout << (This person is outstanding); } }; // teacher class class teacher : public person { private: int numPubs; // number of papers published public: void getData() // get teacher data from user { person::getData(); cout << Enter number of teacher's publications: ; cin >> numPubs; } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 431 of 701
void putData() { person::putData(); cout << Publications = << numPubs; } void isOutstanding() { if(numPubs > 100) cout << (This person is outstanding); } }; void main(void) { person* persPtr[100]; // list of pointers to persons int n = 0; // number of persons on list char choice; // 'p', 's', etc. do { cout << Enter person, student or teacher (p/s/t): ; cin >> choice; if(choice=='s') // put new student persPtr[n] = new student; // in array else if(choice=='t') // put new teacher persPtr[n] = new teacher; // in array else // put new person persPtr[n] = new person; // in array persPtr[n++]->getData(); // get data for person cout << Enter another (y/n)? ; // do another person? cin >> choice; } while( choice=='y' ); // cycle until not 'y' for(int j=0; j<n; j++) { // print names of all persPtr[j]->putData(); // persons, and persPtr[j]->isOutstanding(); // say if outstanding } } // end main()
Enter student's GPA: 2.4 Enter another (y/n)? y Enter person, student or teacher (p/s/t): t Enter name: Shipley Enter number of teacher's publications: 714 Enter another (y/n)? y Enter person, student or teacher (p/s/t): t Enter name: Wainright Enter number of teacher's publications: 13 Enter another (y/n)? n Name = Bob Name = Timmy GPA = 1.2 Name = Brenda GPA = 3.9 (This person is outstanding) Name = Sandy GPA = 2.4 Name = Shipley Publications = 714 (This person is outstanding) Name = Wainright Publications = 13
The Classes
The person class contains a single data item, a string representing the persons name. The student and teacher classes add new data items to the person base class. The student class contains a variable gpa of type float, which represents the students GPA. The teacher class contains a variable numPubs of type int, representing the number of papers the teacher has published. Students with a GPA over 3.5 and teachers who have published more than 100 papers are considered outstanding. (Ill refrain from comment on the desirability of these criteria for judging educational excellence.)
Virtual Functions
In the person class, all three functions, getData(), putData(), and isOutstanding(), are declared virtual. This is necessary because functions with these names exist in the derived classes as well, and objects of these derived classes will be accessed using pointers. The only way for the program to know what kind of object the pointer in persPtr[j] points to, in an expression such as persPtr[j]->putData(); is if the putData() function is virtual. If its declared virtual, the version of putData() appropriate to the class of object pointed to by persPtr[j] will be executed.
A Graphics Example
Lets explore another example of virtual functions, this one a graphics example derived from the shapes program from Chapter 7, Session 5. Classes representing a number of specific shapes (cap, bowl, and square) are derived from a general shape class. One easy way to display a complex picture made out of such shape objects is to put pointers to the different shapes in an array and then draw all the shapes using a simple for loop: for(int j=0; j<N; j++) sharray[j]->draw(); The VIRTSHAP (Listing 9-5) program makes this possible by declaring the draw() function to be virtual in the shape class. Listing 9-5 VIRTSHAP // virtshap.cpp // draws shapes made from Xs on character-based display // uses virtual draw() function #include <iostream.h> //////////////////////////////////////////////////////////////// class shape { private: int xCo, yCo; // coordinates of shape int size; // size of shape protected: // read-only functions int getx() const { return xCo; } int gety() const { return yCo; } int getz() const { return size; } void down() const; // declaration public: // 3-arg constructor shape(int x, int y, int s) : xCo(x), yCo(y), size(s) { } virtual void draw() const { cout << Error: base virtual << endl; } }; void shape::down() const // move cursor down to top of shape { for(int y=0; y<yCo; y++) cout << endl; } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 434 of 701
//////////////////////////////////////////////////////////////// class square : public shape // square shape { public: // 3-arg constructor square(int x, int y, int s) : shape(x, y, s) { } void draw() const; // declaration }; void square::draw() const // draw a square { shape::down(); // position y at top of shape for(int y=0; y<getz(); y++) // move y down across shape { int x; for(x=1; x<getx(); x++) // space over to shape cout << ' '; for(x=0; x<getz(); x++) // draw line of Xs cout << 'X'; cout << endl; } } //////////////////////////////////////////////////////////////// class cap : public shape // cap (pyramid) shape { public: // 3-arg constructor cap(int x, int y, int s) : shape(x, y, s) { } void draw() const; // declaration }; void cap::draw() const // draw a cap { shape::down(); for(int y=0; y<getz(); y++) { int x; for(x=0; x < getx()-y+1; x++) cout << ' '; for(x=0; x<2*y+1; x++) cout << 'X'; cout << endl; } } //////////////////////////////////////////////////////////////// class bowl : public shape // bowl (inverted pyramid) shape { public: // 3-arg constructor bowl(int x, int y, int s) : shape(x, y, s) { } void draw() const; // declaration }; void bowl::draw() const // draw a bowl { shape::down(); for(int y=0; y<getz(); y++) { int x; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 435 of 701
for(x=0; x < getx()-(getz()-y)+2; x++) cout << ' '; for(x=0; x < 2*(getz()-y)-1; x++) cout << 'X'; cout << endl; } } /////////////////////////////////////////////////////////////////// void main() { const int N = 3; // number of shapes shape* sharray[N]; // array of pointers to shapes bowl bw(10, 0, 3); // make a bowl square sq(20, 1, 5); // make a square cap cp(30, 1, 7); // make a cap sharray[0] = sharray[1] = sharray[2] = cout << endl for(int j=0; &bw; &sq; &cp; << endl; j<N; j++) // put their addresses in array
sharray[j]->draw(); } The class specifiers in VIRTSHAP are similar to those in SHAPES, except that the draw() function in the shape class has been made into a virtual function. In main(), the program sets up an array, ptrarr, of pointers to shapes. Next, it creates three objects, one each of the bowl, square, and cap classes, and places their addresses in the array. Now its easy to draw all three shapes using the for loop. Figure 9-4 shows the output from VIRTSHAP (its the same as that from SHAPES):
Figure 9-4 Output of the VIRTSHAP program This is a powerful approach to combining graphics elements, especially when a large number of objects need to be grouped together and drawn as a unit.
Quiz 2 1. The getData() function in the person class in VIRTPERS needs to be virtual because a. it is called from within member functions of derived classes. b. it will be invoked by base class objects. c. it will be invoked by pointers to derived class objects. d. it appears in both base and derived classes. e. it is used only in derived classes, not in the base class. 2. Which of the following are true statements about the VIRTPERS program? a. An array of pointers to person objects is more useful than an array of person objects. b. The isOutstanding() function would not need to be virtual if only person objects, not student or teacher objects, were outstanding. c. The isOutstanding() function cannot be accessed from main() without using pointers. d. Because person objects are never outstanding, you can remove the isOutstanding() function from the person class without altering the operation of the program.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 437 of 701
e. Because person objects are never outstanding, you can remove the isOutstanding() function from the person class without altering the operation of the program, provided that you make the function virtual in the student and teacher classes. 3. If the main() part of the VIRTPERS example did nothing but put person, student, and teacher objects in an array of type person and access them using statements such as
persArr[2].getData();
then a. everything would work just as it does in VIRTPERS. b. you could not access a students GPA or a teachers number of publications. c. there would be error messages from the compiler. d. there would be no need for virtual functions. e. none of the above. 4. The error message in the body of the draw() function in the shape class in VIRTSHAP a. will never be displayed if shape objects are always accessed using pointers. b. is not the most effective solution to keeping the user from using base class objects. c. is not necessary because its impossible to create a shape object. d. is not necessary because its impossible to create a pointer to a shape object. e. will be displayed at compile time, if at all. 5. The constructor in the shapes class in VIRTSHAP is not virtual because a. constructors can never be virtual. b. there are no constructors in the derived classes. c. the constructors in the derived classes call the constructor in the base class. d. you dont need to specify what kind of object youre creating when you create it. e. constructors have no return type. Exercise 1
Add an erase() member function to all the appropriate classes in the VIRTSHAP program. It should erase its shape by drawing over the shape with spaces (the character) wherever there were X characters before.
Exercise 2
Add a move() member function to all the appropriate classes in the VIRTSHAP program. It should be able to move a shape by erasing it with erase() (see Exercise 1), incrementing or decrementing one of the shapes coordinates and then redrawing it with draw(). Use this function to animate a shape; that is, to cause it to appear to move across the screen.
Passing References
As a simple example of using polymorphism to decouple user code from classes, imagine a base class with many derived classes. Suppose that main() calls a global (nonmember) function, func(), that will do something to objects of all the derived classes. If func() needed to worry about handling many different kinds of objects in different ways, its code would become very complicated. However, using references and virtual functions, I can employ the same version of func() for many different classes. When an object is passed to a function by reference, the syntax makes it appear that the object itself is being passed, but actually a pointer to the object is passed instead. As youve seen, pointers are always the same size no matter what they point to, so theres no loss of information if a reference to a derived class object is passed to a function that is expecting a reference to a base class object. In the example, there is a base class Base and two derived classes, Derv1 and Derv2. Statements in main() call func(), using reference arguments to pass Derv1 and Derv2 objects. Notice that the same func() handles both Derv1 and Derv2 objects. Listing 9-6 shows VIRTREF. Listing 9-6 VIRTREF // virtref.cpp // tests virtual functions and passing objects by reference #include <iostream.h> class Base { public: virtual void speak() { cout << \nBase speaks; } }; class Derv1 : public Base C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 439 of 701
{ public: void speak() { cout << \nDerv1 speaks; } }; class Derv2 : public Base { public: void speak() { cout << \nDerv2 speaks; } }; void main() { void func(Base&); Derv1 d1; Derv2 d2; func(d1); func(d2); }
// // // // //
prototype create derived class create derived class pass d1 by reference pass d2 by reference
void func(Base& obj) // (note reference argument) { obj.speak(); } The func() function tells the object passed to it to call its speak() member function, which displays what class of object it is. Heres the output from VIRTREF: Derv1 speaks Derv2 speaks Because the argument to func() is passed by reference, and because the speak() member function is virtual, func() doesnt need to know what kind of object its sending the speak() message to. It knows the object is derived from Base, but thats all it needs to know. The object itself takes care of figuring out which version of speak() to call, as demonstrated by the output. By contrast, suppose polymorphism could not be used and func() had to be aware of what kind of object it was working with. Either it would need something like a big switch statement to handle the different classes (and a way to figure out what class an object was) or main() would need to select one of many different versions of func(), depending on the class. Each of these solutions is unnecessarily complicated. By leaving an objects response to a message up to the object itself, you can make the user code independent of the actual class being used, with a resulting simplification in the code. One advantage of this approach is that the same user code will work with classes that havent even been invented. For example, in VIRTREF, if a Derv3 class is added to the class hierarchy, func() will be happy to work with it, just as it does with Derv1 and Derv2. Of course, this decoupling effect works only if you use reference (or pointer) arguments. If you pass a derived class object itself to a function, it will be sliced down to the size of a base class object, just as it would be if you assigned it to a base class variable. Also, you must use virtual functions. If speak() were not virtual, the output of VIRTREF would be Base speaks Base speaks In C++, polymorphism depends on virtual functions. Although theres not much gain by using polymorphism in this short example, in a larger program, with many derived classes and many functions in the class-user code, it would be much more efficient for these functions to be written in terms of a single base class rather than a multiplicity of derived classes.
Passing Pointers
The same decoupling effect is achieved whether you pass references to objects or pointers to objects. Ill rewrite the VIRTREF program to use pointers instead of references. Listing 9-7 shows VIRTPTR. Listing 9-7 VIRTPTR // virtptr.cpp // tests virtual functions and passing objects by pointer #include <iostream.h> class Base { public: virtual void speak() { cout << \nBase speaks; } }; class Derv1 : public Base { public: void speak() { cout << \nDerv1 speaks; } }; class Derv2 : public Base { public: void speak() { cout << \nDerv2 speaks; } }; void main() { void func(Base*); // prototype (note reference argument) Derv1 d1; // create derived class object d1 Derv2 d2; // create derived class object d2 func(&d1); // pass address of d1 to func() func(&d2); // pass address of d2 to func() } void func(Base* ptr) { ptr->speak(); } The classes are identical to those in VIRTREF, but main() passes addresses of objects, rather than references to them, to func() and uses the -> operator to access speak(). Otherwise, the program works the same, and again the output is Derv1 Derv2 Typically, references are appropriate when objects are created through definitions so their names are known, whereas pointers are used when objects are created with new and only pointers to them are available. References are safer than pointers. The value of a pointer can be changed by the programmer, possibly inadvertently, whereas a reference, once initialized, cant be changed.
to add a person to the database, to display all the persons on the database, or to exit the program. The user can add as many persons as seems desirable and display them all at any time. To implement this increased capability, Ill divide the class-user part of the program into several functions. A switch statement in main() calls the appropriate function, depending on which key the user presses. There are two of these functions: getPerson() gets data from the user about a person to be added to the database and displayPerson() displays a persons data. The classes are the same as in virtpers. Listing 9-8 shows PERSFUNC. Listing 9-8 PERSFUNC // persfunc.cpp // passing pointers to objects that use virtual functions #include <iostream.h> #include <process.h> // for exit() //////////////////////////////////////////////////////////////// class person // person class { protected: char name[40]; public: virtual void getData() { cout << Enter name: ; cin >> name; } virtual void putData() { cout << \nName = << name; } virtual void isOutstanding() { } // note: empty function body }; //////////////////////////////////////////////////////////////// class student : public person // student class { private: float gpa; // grade point average public: void getData() // get student data from user { person::getData(); cout << Enter student's GPA: ; cin >> gpa; } void putData() { person::putData(); cout << GPA = << gpa; } void isOutstanding() { if (gpa > 3.5) cout << (This person is outstanding); } }; //////////////////////////////////////////////////////////////// class teacher : public person // teacher class { private: int numPubs; // number of papers published public: void getData() // get teacher data from user C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 442 of 701
{ person::getData(); cout << Enter number of teacher's publications: ; cin >> numPubs; } void putData() { person::putData(); cout << Publications = << numPubs; } void isOutstanding() { if(numPubs > 100) cout << (This person is outstanding); } }; //////////////////////////////////////////////////////////////// void main(void) { person* persPtr[100]; // list of pointers to persons int n = 0; // number of persons on list char choice; int j; person* getPerson(); // prototypes void displayPerson(person*); while(1) // cycle until exit { cout << endl << 'a' to add new person << endl << 'd' to display all persons << endl << 'x' to exit program << endl << Enter selection: ; cin >> choice; switch(choice) { case 'a': persPtr[n++] = getPerson(); break; case 'd': for(j=0; j<n; j++) displayPerson( persPtr[j] ); break; case 'x': for(j=0; j<n; j++) delete persPtr[j]; // delete all person objects exit(0); break; default: cout << \nNo such selection; } // end switch } // end while } // end main() //////////////////////////////////////////////////////////////// person* getPerson() // function returns a person { person* tp; // pointer to person C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 443 of 701
char choice; cout << Enter person, student or teacher (p/s/t): ; cin >> choice; if(choice=='s') // put new student tp = new student; // in array else if(choice=='t') // put new teacher tp = new teacher; // in array else // put new person tp = new person; // in array tp->getData(); // get data for person return tp; // return pointer to person } // end getPerson()' //////////////////////////////////////////////////////////////// void displayPerson(person* pp) // function displays a person { pp->putData(); // display data, and pp->isOutstanding(); // say if outstanding } // end displayPerson() Heres some sample interaction with the program in which the user adds a person, two students, and a teacher to the database and then displays all four. 'a' to add new person, 'd' to display all persons 'x' to exit program Enter selection: a Enter person, student or teacher (p/s/t): p Enter name: Harriet 'a' to add new person, 'd' to display all persons 'x' to exit program Enter selection: a Enter person, student or teacher (p/s/t): s Enter name: Steve Enter student's GPA: 2.5 'a' to add new person, 'd' to display all persons 'x' to exit program Enter selection: a Enter person, student or teacher (p/s/t): s Enter name: Francine Enter student's GPA: 3.9 'a' to add new person, 'd' to display all persons 'x' to exit program Enter selection: a Enter person, student or teacher (p/s/t): t Enter name: Marshall Enter number of teacher's publications: 50 'a' to add new person, 'd' to display all persons 'x' to exit program Enter selection: d C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
Name = Harriet Name = Steve GPA = 2.5 Name = Francine GPA = 3.9 (This person is outstanding) Name = Marshall Publications = 50 You can continue to add persons and display the contents of the database as long as you like (at least until you exceed the size of the persPtr array). When you select x, the program terminates. Note that there is no mention at all in main() and displayPerson() of any class other than person. The displayPerson() function takes a pointer to person as an argument, and the getPerson() function returns a pointer to a person. The only place student and teacher objects are explicitly mentioned in the user code is in getPerson(), where an object of the type requested by the user is created with new. This isolation of the user code from specific classes makes these functions surprisingly immune to any changes in the class hierarchy. Even if the class creators issued a new revision of the classes, adding new derived classes to the program, say class administrator : public person { }; and class football_coach : public person { }; you would not need to make any changes at all to main() or displayPerson(), and you would need to add only a few lines to getPerson().
Quiz 3 1. In C++, polymorphism a. requires virtual functions. b. allows increased separation of classes and class-user code. c. requires inheritance. d. allows increased separation of code in one class and code in another class. e. requires pointers or references to objects. 2. To use polymorphism, when an object is passed to a function by reference, a. the object must be a base class object. b. the object must be a derived class object. c. the functions argument type must be a reference to the base class. d. the functions argument type must be a reference to the objects class. e. statements within the function will affect an object created within the function. 3. To use polymorphism to decouple specific classes from the class-user code, a. the classes to be decoupled must be base classes. b. the classes to be decoupled must be derived classes.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 445 of 701
c. requires a class hierarchy with virtual functions in the base class. d. can result in significantly simpler code for the class user. e. doesnt make sense. 4. If you derive a class called gradStudent from the student class in PERSFUNC, then a. main(), getPerson(), and displayPerson() will require extensive rewriting. b. the compiler will issue an error message because virtual functions cant be used with more than one level of inheritance. c. youll need to add some lines to the switch statement in main(). d. youll need to add some lines to the getPerson() function. e. no change is necessary to the user code. 5. You cant pass a pointer to a derived class object to a function whose argument type is a pointer to a base class object because a. the parts of the object specific to the derived class will be sliced off. b. the function will treat the object as a base class object. c. polymorphic functions must operate on base class pointers. d. the data in the object thats specific to the derived class will be inaccessible. e. none of the above. Exercise 1
Add an administrator class to the PERSFUNC program. It should be derived from person. What distinguishes an administrator object from other person objects is a member variable called salary, which is a floating-point number with values from $75,000 on up. Administrators with a salary value greater than $200,000 are considered to be outstanding. Make the necessary changes to the user code so that administrator objects can be added to the database.
Exercise 2
Add an option to the PERSFUNC program that allows the user to search the database for a specific person. This option can be accessed with the letter s, for search. The user will be prompted to enter the name of the person to be found. An appropriate function, called findPerson(), will be called from main() with this name as an argument and will carry out the search, displaying any matching names. To the extent possible, use polymorphism for this function.
Abstract Classes
In the VIRTPERS program in Session 2, I instantiated base class person objects as well as derived class student and teacher objects. In the VIRTSHAP program, on the other hand, I dont instantiate any base class shape objects, but only derived class cap, bowl, and square objects. In VIRTPERS, I assume that a generic person object will be useful in the program, whereas in VIRTSHAP, I assume that the shape class exists only as a starting point for deriving other classes and that it doesnt make sense to instantiate a generic shape object (which would not, after all, even have a specific shape). I can call the shape class in VIRTSHAP an abstract class, which means that no actual objects will be derived from it. Abstract classes arise in many situations. A factory can make a sportscar or a truck or an ambulance, but it cant make a generic vehicle. The factory must know the details about what kind of vehicle to make before it can actually make one. Similarly, youll see sparrows, wrens, and robins flying around, but you wont see any generic birds. Some situations require an abstract base class, whereas others dont. More often than not, using abstract base classes is a good idea. Theres a certain clarity about treating the base class as something special, something that doesnt need to worry about having its own objects. In the VIRTPERS program, for example, it might be better to make person an abstract class and derive a miscellaneous class from it for all the person objects that arent teachers or students. Actually, the shape class in VIRTSHAP is an abstract class only in the eyes of humans. The compiler is ignorant of our decision to make it an abstract class, and would not complain if we said shape s3(5, 6, 7); // legal but doesn't make sense I have arranged for the draw() function in shape to display an error message: class Base // base class { public: virtual void show() // virtual function { cout << \nError: base version of show(); } }; This keeps me from trying to draw a shape, but its not a complete solution. I can still create shape objects, even if I cant draw them; and, if I do try to draw a shape, I wont discover the error until the message is displayed at runtime.
A Short Example
The first example of a pure virtual function is adapted from the VIRT program in Session 1. Listing 9-9 shows VIRTPURE. Listing 9-9 VIRTPURE // virtpure.cpp // pure virtual function #include <iostream.h> class Base // base class { public: virtual void show() = 0; };
class Derv1 : public Base { public: void show() { cout << \nDerv1; } }; class Derv2 : public Base { public: void show() { cout << \nDerv2; } }; void main() { Derv1 dv1; Derv2 dv2;
// derived class 1
// derived class 2
// Base ba; // Error: cannot create instance of // abstract base class } Now the virtual function is declared as virtual void show() = 0; // pure virtual function The equal sign here has nothing to do with assignment; the value 0 is not assigned to anything. The =0 syntax is simply how you tell the compiler that a function will be purethat is, will have no body. You might wonder, if you can remove the body of the virtual show() function in the base class, why you cant remove the function altogether. That would be even cleaner, but it doesnt work. Without a virtual function show() in the base class, statements such as Base list[3]; list[0] = new Derv1; list[0]->show(); // can't do this would not be valid because the base class version of show() would always be executed.
shape(int x, int y, int s) : xCo(x), yCo(y), size(s) { } virtual void draw() const = 0; // pure virtual function }; void shape::down() const // move cursor down to top of shape { for(int y=0; y<yCo; y++) cout << endl; } //////////////////////////////////////////////////////////////// class square : public shape // square shape { public: // 3-arg constructor square(int x, int y, int s) : shape(x, y, s) { } void draw() const; // declaration }; void square::draw() const // draw a square { shape::down(); // position y at top of shape for(int y=0; y<getz(); y++) // move y down across shape { int x; for(x=1; x<getx(); x++) // space over to shape cout << ' '; for(x=0; x<getz(); x++) // draw line of Xs cout << 'X'; cout << endl; } } //////////////////////////////////////////////////////////////// class cap : public shape // cap (pyramid) shape { public: // 3-arg constructor cap(int x, int y, int s) : shape(x, y, s) { } void draw() const; // declaration }; void cap::draw() const // draw a cap { shape::down(); for(int y=0; y<getz(); y++) { int x; for(x=0; x < getx()-y+1; x++) cout << ' '; for(x=0; x<2*y+1; x++) cout << 'X'; cout << endl; } } //////////////////////////////////////////////////////////////// class bowl : public shape // bowl (inverted pyramid) shape C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 449 of 701
{ public: // 3-arg constructor bowl(int x, int y, int s) : shape(x, y, s) { } void draw() const; // declaration }; void bowl::draw() const // draw a bowl { shape::down(); for(int y=0; y<getz(); y++) { int x; for(x=0; x < getx()-(getz()-y)+2; x++) cout << ' '; for(x=0; x < 2*(getz()-y)-1; x++) cout << 'X'; cout << endl; } } /////////////////////////////////////////////////////////////////// void main() { // shape x(1, 2, 3); const int N = 3; // error: can't instantiate abstract object // number of shapes // array of pointers to shapes shape* sharray[N] = { &bowl(10, 0, 3), &square(20, 1, 5), &cap(30, 1, 7) }; cout << endl << endl; for(int j=0; j<N; j++) sharray[j]->draw(); } // start two lines down // display all three shapes
require virtual functions in the base class. Usually at least one such function doesnt do anything in the base class. We call such functions pure virtual functions and let the compiler know about them with the notation =0.
Virtual Destructors
If you use any virtual functions in a class, pure or otherwise, you will probably want to make the destructor for that class virtual. Why? To understand the problem, examine Listing 9-11 which features a base class and a derived class, both with nonvirtual destructors. Its called NOVIDEST. Listing 9-11 NOVIDEST // novidest.cpp // non-virtual function used as base class destructor #include <iostream.h> class Base { public: ~Base() { cout << \nBase destructor; } }; class Derv : public Base { public: ~Derv() { cout << \Derv destructor; } }; void main() { Base* pb = new Derv; delete pb; // output is Base Destructor cout << \nProgram terminates; }
the pointer is the address of a derived class object. Thus in NOVIDEST, the Derv class destructor is never called. This could be a problem if this destructor did something important.
#include <string.h> // for strlen(), strcpy() //////////////////////////////////////////////////////////////// class person // person class { protected: char* nameptr; public: person(char* np) // 1-arg constructor { int length = strlen(np); // find length of name nameptr = new char[length+1]; // allocate memory strcpy(nameptr, np); // put name in memory } virtual ~person() = 0 // destructor { cout << \nperson Destructor; if(nameptr != NULL) // if it has it been used, delete[] nameptr; // delete name } virtual void putData() { cout << \nName = << nameptr; } }; // end person class //////////////////////////////////////////////////////////////// class gradStudent : public person // gradStudent class { private: char* topicptr; // ptr to thesis topic public: gradStudent(char* n, char* t) : // 2-arg constructor person(n), topicptr(NULL) { int length = strlen(t); // find length of topic topicptr = new char[length+1]; // allocate memory strcpy(topicptr, t); // put topic in memory } ~gradStudent() // destructor { cout << \ngradStudent destructor; if(topicptr != NULL) // if it has it been used, delete[] topicptr; // delete thesis topic } virtual void putData() { person::putData(); cout << \n Thesis topic = << topicptr; } }; // end gradStudent class //////////////////////////////////////////////////////////////// void main(void) { int j; const int total = 3; person* persPtr[3]; // list of pointers to persons char name[40]; // temporary storage char topic[80]; for(j=0; j<total; j++) // get data, make gradStudents C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 453 of 701
{ cout << \nEnter name: ; cin >> name; cout << Enter thesis topic: ; cin >> topic; persPtr[j] = new gradStudent(name, topic); } for(j=0; j<total; j++) // display gradStudents persPtr[j]->putData(); for(j=0; j<total; j++) // delete gradStudents delete persPtr[j]; } // end main() The constructors in person and gradStudent obtain memory with new for the grad students name and thesis topic, respectively. Their destructors delete this memory. Both destructors are called for each object, as shown by the output, so all allocated memory is guaranteed to be freed. Heres some sample interaction with destpers. (Ive used the unsophisticated stream operator cin>> for text input, so only one-word strings are acceptable.) Enter name: Wiffington Enter thesis topic: SomeAspectsOfGastropodBehavior Enter name: Brown Enter thesis topic: SocksWornByRevolutionaryGenerals Enter name: Pennybrook Enter thesis topic: ColorsThatEnhanceMoodOfValleyGirls Name = Wiffington Thesis topic = SomeAspectsOfGastropodBehavior Name = Brown Thesis topic = SocksWornByRevolutionaryGenerals Name = Pennybrook Thesis topic = ColorsThatEnhanceMoodOfValleyGirls gradStudent destructor person destructor gradStudent destructor person destructor gradStudent destructor person destructor
a. may represent a generic category such as geographical locations or trees. b. tells the compiler what it is by including a virtual function. c. tells the compiler what it is by including a pure virtual function. d. tells the compiler what it is with the keyword abstract. e. is one from which no objects will be instantiated. 2. Usually, a pure virtual function a. has no function body. b. will never be called. c. will be called only to delete an object. d. tells the compiler what it is, using the notation =0. e. is defined only in derived classes. 3. Which of the following statements are true? a. Its acceptable to use a pure virtual function in an abstract class because you will always invoke abstract objects using pointers. b. A pure virtual function guarantees that no object can be instantiated from the class in which its defined. c. A pure virtual function is useful whether the class in which its defined is used as a base class or not. d. Its all right to use a function with no body in an abstract class because you never execute any of the functions in an abstract class. e. If you have one or more pure virtual functions in a class, the class is abstract. 4. If a base class destructor is not virtual, then a. it cant be called. b. it cant be called when accessed through a pointer. c. destructors in derived classes cant be called. d. destructors in derived classes cant be called when accessed through a pointer to the base class. e. it cannot have a function body.
5. Which of the following make it more likely that you would use a virtual destructor in a particular class? a. The class is used as a base class. b. The class contains no virtual functions. c. Objects of derived classes will be deleted using a variable whose type is a pointer to the base class. d. Objects of the base class will be deleted using a variable whose type is a pointer to the derived class. e. Destructors in the base or derived classes are necessary to deallocate system resources. Exercise 1
Start with the program from Exercise 1 of Session 2 in this chapter which added an erase() function to the shape class in VIRTSHAP. Make this erase() function into a pure virtual function and make any necessary changes to the rest of the program to accommodate this change.
Exercise 2
Consider the EMPINH program in Chapter 7, Session 2. Retrofit the classes in this program with virtual functions or pure virtual functions as appropriate. Fix things so its impossible to instantiate an employee object. Write a main() that fills an array of type employee* with pointers to different kinds of employees whose data is supplied by the user and then displays all the employee data.
Midchapter Discussion
George: Whats all the fuss about virtual functions? It looks to me like theyre useful only in this one particular situation, where you use base class pointers to refer to derived class objects. If I just avoid that situation, then I dont need to worry about virtual functions at all. I must admit that thought crossed my mind too. But arent virtual functions one of the three pillars of OOP, or something? So they must be important. I think the point is that using base class pointers to derived class objects gives you so much power that youll want to use them all the time. Or at least when you have derived classes. But thats probably most of the time. It looks like inheritance isnt just a way of organizing classes. Its also a stepping stone to polymorphism. So what does polymorphism buy you? I mean, it sounds like something the vice squad should investigate. With polymorphism, one statement can draw any shape, one statement can display data for any kind of person, one global function can operate on many different kinds of objects. Polymorphism lets the class user stop worrying about how something will be done and concentrate on what will be done. To draw an object, all you do is send it a message saying Draw yourself. You dont even need to know what kind of object it is. As long as its stored in a base class array or something like that. Right. You do need to set things up a little. Well, Ive decided I dont need to understand virtual functions. Because youre never going to use them? No, because Im joining the foreign legion. Poor fellow, hes polymorphically impaired.
A Simple Example
Fortunately, most recent C++ compilers include a special function, typeid(), that allows you to find the type (or class, which is the same thing) of an object. This is called Runtime Type Identification, or RTTI. Listing 9-14, TYPEID, shows how typeid() works. Listing 9-14 TYPEID // typeid.cpp // demonstrates typeid() function #include <iostream.h> #include <typeinfo.h> // for typeid() class ClassA { }; class ClassB { }; void main() { ClassA ObjA; ClassB ObjB; if( typeid(ObjA) == typeid(ClassA) ) cout << \nObjA is an object of ClassA; else cout << \nObjA is not a member of ClassA; if( typeid(ObjB) == typeid(ClassA) ) cout << \nObjB is an object of ClassA; else cout << \nObjB is not an object of ClassA; } Heres the output from the program: ObjA is an object of ClassA ObjB is not an object of ClassA You need to include the TYPINFO.H header file. You can use either an object name or a class name as an operand for typeid(), so its easy to see if an object is from a particular class. The return value from typeid() is a pointer that is useful for comparison purposes, as with the == and != operators. The typeid() function also works with basic types such as int and float.
Suppose, for example, that I want to modify the VIRTPERS program from Session 2 in this chapter so it indicates what kind of person object (student, teacher, or person) is being displayed. Listing 9-15 shows RTTIPERS, which does exactly that. Listing 9-15 RTTIPERS // rttipers.cpp // runtime type identification with person class #include <iostream.h> #include <typeinfo.h> // for typeid() class person // person class { protected: char name[40]; public: virtual void getData() { cout << Enter name: ; cin >> name; } virtual void putData() { cout << Name= << name; } }; // student class class student : public person { private: float gpa; // grade point average public: void getData() // get student data from user { person::getData(); cout << Enter student's GPA: ; cin >> gpa; } void putData() { person::putData(); cout << GPA= << gpa; } }; // teacher class class teacher : public person { private: int numPubs; // number of papers published public: void getData() // get teacher data from user { person::getData(); cout << Enter number of teacher's publications: ; cin >> numPubs; } void putData() { person::putData(); cout << Publications= << numPubs; } }; void main(void) { C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 458 of 701
person* persPtr[100]; // list of pointers to persons int n = 0; // number of persons on list char choice; // 'p', 's', etc. do { cout << Enter person, student or teacher (p/s/t): ; cin >> choice; if(choice=='s') // put new student persPtr[n] = new student; // in array else if(choice=='t') // put new teacher persPtr[n] = new teacher; // in array else // put new person persPtr[n] = new person; // in array persPtr[n++]->getData(); // get data for person cout << Enter another (y/n)? ; // do another person? cin >> choice; } while( choice=='y' ); // cycle until not 'y' for(int j=0; j<n; j++) { // display class name if( typeid(*persPtr[j]) == typeid(student) ) cout << \nStudent, ; else if( typeid(*persPtr[j]) == typeid(teacher) ) cout << \nTeacher, ; else if( typeid(*persPtr[j]) == typeid(person) ) cout << \nPerson, ; else cout << \nError: unknown type; persPtr[j]->putData(); // display name } // end for for(int j=0; j<n; j++) // delete all objects delete persPtr[j]; } // end main() Heres some typical interaction with the program. The user enters data for four persons, and the program then displays this data, including the class of the person. Enter person, student or teacher (p/s/t): p Enter name: Smith Enter another (y/n)? y Enter person, student or teacher (p/s/t): p Enter name: Johnson Enter another (y/n)? y Enter person, student or teacher (p/s/t): s Enter name: Harrison Enter student's GPA: 3.3 Enter another (y/n)? y Enter person, student or teacher (p/s/t): t Enter name: McConnel Enter number of teacher's publications: 104 Enter another (y/n)? n Person, Name=Smith Person, Name=Johnson Student, Name=Harrison GPA=3.3 Teacher, Name=McConnel Publications=104 As you can see, RTTI can save you the trouble of storing, say, a string specifying the class of each object. Instead, you can find out from the object itself what it is (although you do need to know all the possibilities in advance). C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 459 of 701
Quiz 5 1. RTTI a. takes place at compile time. b. is seldom useful because you always know what class something is. c. is applicable to base class objects. d. is applicable to derived class objects. e. is an acronym for Random True Type Identification. 2. In the TYPEID program, a. the type ID of objects is compared with the type ID of classes. b. comparison operators are required. c. the argument to typeid() is a pointer. d. the argument to typeid() is a string. e. the header file TYPEID.H must be included. 3. In the RTTIPERS program, a. the class creator arranges for type information to be stored in each object. b. the class user arranges for type information to be stored in each object. c. the compiler arranges for type information to be stored in each object. d. typeid() uses contextual analysis of the source code to determine the type of an object. e. typeid() and a comparison operator determine the type of an object. 4. Using RTTI in the RTTIPERS program a. gives extra information to the end user. b. could be avoided, but at the expense of object size. c. isnt necessary if you use virtual functions. d. isnt necessary if you instantiate only objects of the base class. e. makes all objects slightly larger. 5. The return value from typeid() is a. a pointer to the object used as an argument.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 460 of 701
b. for classes with virtual functions, possibly the address of the class vtable. c. useful for comparison with other typeid() calls. d. a pointer to a string representing the class name. e. useful only with abstract classes. Exercise 1
Start with the EMPINH program from Chapter 7, Session 2. Change main() so that when members of the various derived classes are instantiated, pointers to them are placed in an array of type employee*. Assume you cant change the existing class hierarchy. Use RTTI in main() to display the type of each object whose pointer is stored in the array at the same time you invoke putdata() to display the objects data.
0 to 0 to 0, 1 0) :
{ cout << hours << ':' << minutes; } void get() // input from user { char dummy; cin >> hours >> dummy >> minutes; } // overloaded + operator airtime operator + (airtime right) { airtime temp; // make a temporary object temp.hours = hours + right.hours; // add data temp.minutes = minutes + right.minutes; if(temp.minutes >= 60) // check for carry { temp.hours++; temp.minutes -= 60; } return temp; // return temporary object by value } }; // end class airtime void main() { airtime at1, at2; cout << Enter an airtime: ; at1.get(); at2 = at1 + 3; // add integer to airtime cout << airtime + 3 = ; at2.display(); // display sum // at2 = 3 + at1; // error: illegal structure operation
} The constructor in this program handles three situations: no arguments, one argument, or two arguments. (I could also have used three separate constructors.) In its role as a one-argument constructor, the constructor is used by the compiler to convert the 3 in main() into an airtime(3, 0) value, which is then added to whatever airtime value the user entered for at1. Heres some sample interaction: Enter an airtime: 6:45 airtime + 3 = 9:45 The operator+() function, with a little help from the constructor, has no problem when a nonobject (an integer) appears on the right side of the + operator.
Member functions must be invoked by an object of their class, and 3 is not an airtime object. So although you can add an integer to an airtime, you cant add an airtime to an integer. This is a serious inconsistency for class users.
at2 = at1 + 3; // add integer to airtime cout << airtime + 3 = ; at2.display(); // display sum at2 = 3 + at1; // add airtime to integer cout << \n3 + airtime = ; at2.display(); // display sum } A function declaration (the last statement in the airtime class) makes operator+() a friend: friend airtime operator+(airtime, airtime); This declaration can be placed anywhere in the class; it doesnt matter, at least to the compiler, if it goes in the public or the private section. However, it belongs conceptually in the public section because its part of the public interface to the class. That is, any class user can invoke the friend function; its not accessible only to class members. Therefore, friend function declarations are commonly placed in the public section. As you can see, the operator+() function (which appears just after the airtime class in the listing) is not a member function of airtime; if it were, its declarator would be airtime airtime::operator + (airtime left, airtime right) Nevertheless, it can access hours and minutes, which are private data members of airtime. It can do this because it has declared a friend within the class. The operator+() friend function takes two arguments, called left and right. Because its a standalone function and not a class member, its not invoked by an object; its simply called from main() like any other global function. Both objects to be added are therefore available as arguments. As a general rule, the friend version of a function always takes one more argument than the member version. Internally, the friend operator+() function in AIRFRI is similar to the member version in AIRNOFRI, except that it refers to the data in the airtime arguments as left.hours and right.hours, whereas the member version uses hours and right.hours. The function returns the sum as a third airtime value. If either argument, whether on the left or right, is an integer, the compiler will use the one-argument constructor to convert the integer to an airtime and then add the two airtimes. Statements in main() show both situations, and the compiler has no problem with either one.
#include <iostream.h> class English // English class { private: int feet; float inches; public: English(int ft, float in) // 2-arg constructor { feet = ft; inches = in; } void showdist() // display { cout << feet << \'- << inches << '\'; } float square(); // member function declaration }; float English::square() // return square of { // this English object float fltfeet = feet + inches/12; // convert to float float feetsqrd = fltfeet * fltfeet; // find the square return feetsqrd; // return square feet } void main() { English dist(3, 6.0); float sqft = dist.square();
// 1-arg constructor (3'-6") // return square of dist // display distance and square cout << "\nDistance = "; dist.showdist(); cout << "\nSquare = " << sqft << " square feet"; } The main() part of the program creates an English distance value, squares it, and prints out the result. The output shows the original distance and the square: Distance = 3-6 Square = 12.25 square feet That is, if theres a table 3-6 on each side, it has an area of 12.25 square feet. In main(), I use the statement sqft = dist.square(); to find the square of dist and assign it to sqft. This works all right, but if you want to work with English objects using the same syntax that you use with ordinary numbers, you would probably prefer a functional notation: sqft = square(dist); You can achieve this effect by making square() a friend of the English class, as shown in FRISQ (Listing 919). Listing 9-19 FRISQ // frisq.cpp // friend square() function for English class #include <iostream.h> class English // English class { private: int feet; float inches; public: English(int ft, float in) // 2-arg constructor { feet = ft; inches = in; } void showdist() // display { cout << feet << "\'-" << inches << '\"'; }
// friend function
float square(English d) // return square of { // the argument float fltfeet = d.feet + d.inches/12; // convert to float float feetsqrd = fltfeet * fltfeet; // find the square return feetsqrd; // return square feet } void main() { English dist(3, 6.0); // two-arg constructor (3'-6") float sqft = square(dist); // return square of dist
// display distance and square cout << "\nDistance = "; dist.showdist(); cout << "\nSquare = " << sqft << " square feet"; } Now you can use the more intuitive float sqft = square(dist); Although, as a member function in MISQ, square() takes no arguments, it takes one as a friend function in FRISQ. Again, the friend version of a function always requires one more argument than the member version.
Friends as Bridges
Heres another situation in which friend functions might come in handy. Imagine that you want a function to operate on objects of two different classes. Perhaps the function will take objects of the two classes as arguments and operate on their private data. If the two classes are inherited from the same base class, then you may be able to put the function in the base class. But what if the classes are unrelated? Listing 9-20 shows a simple example, BRIDGE, that shows how friend functions can act as a bridge between two classes. Listing 9-20 BRIDGE // bridge.cpp // friend functions #include <iostream.h> class beta; // needed for frifunc declaration class alpha { private: int data; public: alpha() { data = 3; } // no-arg constructor friend int frifunc(alpha, beta); // friend function }; class beta { private: int data; public: beta() { data = 7; } // no-arg constructor friend int frifunc(alpha, beta); // friend function }; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 466 of 701
int frifunc(alpha a, beta b) // function definition { return( a.data + b.data ); } void main() { alpha aa; beta bb; cout << frifunc(aa, bb); // call the function } In this program, the two classes are alpha and beta. The constructors in these classes initialize their single data items to fixed values (3 in alpha and 7 in beta). I want the function frifunc() to have access to both these private data members, so I make it a friend function. Its declared with the friend keyword in both classes: friend int frifunc(alpha, beta); An object of each class is passed as an argument to the function frifunc() and accesses the private data member of both classes through these arguments. The function doesnt do much: It adds the data items and returns the sum. The main() program calls this function and prints the result. A minor point: Remember that a class cant be referred to until it has been declared. Class beta is referred to in the declaration of the function frifunc() in class alpha, so beta must be declared before alpha. Hence the declaration class beta; at the beginning of the program.
Quiz 6 1. A friend function a. must be a member function of the class that declares it a friend. b. must be invoked by the class that declares it a friend. c. must be invoked by an object of the class that declares it a friend. d. can access the private data of the class that declares it a friend. e. can access the nonpublic data of any class derived from the class that declares it a friend. 2. Typically, the private class data accessed by a friend function a. is in an object created by the friend function. b. is in the object that invoked the friend function. c. is in an object sent to the friend function as an argument. d. must be static class data. e. is in an object of a different class than that in which the function is declared to be a friend. 3. Friend functions are helpful in functional notation because they a. use parentheses following the function name.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 467 of 701
b. take no arguments. c. arent related to a specific class. d. operate on an object passed as an argument. e. allow other functions to access their class data. 4. Which of the following statements are true? a. Any function can use a friend function to access the private data of the class with which the friend function is associated. b. For a function to be a friend, the keyword friend must appear in its definition. c. A friend function must be declared in the class whose friend it will be. d. A friend function is specified by its location in the listing. e. Friends are especially useful for overloaded operators. 5. Which of the following statements are true? a. A function can be a friend of more than one class. b. A class can be a friend of a function. c. More than one function can be a friend of a class. d. Member functions from different classes could use a friend function to exchange private data between the classes. e. A friend function can be called in a statement in main(). Exercise 1
Write an operator-() function that subtracts two airtime values. Assume a larger airtime will never be subtracted from a smaller. Make it possible for an integer, representing hours, to be subtracted from an airtime and for an airtime to be subtracted from an integer.
Exercise 2
Create a function for the English class that returns the square of an English value, in square feet, type float. Write a main() that tests this function.
Interclass Communication
Suppose you have two classes, alpha and beta, that are closely associated with each other. In fact, they are so closely associated that one class needs to access the others private data directly (without using public access functions). You dont want to make the data public because then anyone could alter it by mistake. Also, neither class is a kind of the C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 468 of 701
other, so you dont want to relate them using inheritance. How do you arrange for one class to access anothers private members? The answer is to use friend classes, but theres more to it than that. In interclass communication, it makes a difference which class specification appears first in the listing. You cant refer to members of a class that hasnt been specified yet because the compiler wont know anything about them. If alpha is specified before beta, then its easy for member functions of beta to refer to private members of alpha, but harder for functions in alpha to access private data in beta. Ill start with an easy case.
class alpha { public: void afunc(); // function declaration }; // (definition must follow beta) class beta { private: friend class alpha; // so alpha can access beta data int bdata; // beta's data }; void alpha::afunc() // alpha's function { beta objB; // create beta object objB.bdata = 3; // access its private data }; I declare afunc() in alpha, but define it later, following the beta specification. The compiler therefore knows what beta looks like when it compiles afunc() and, because alpha is a friend of beta, can handle statements that access betas private data. Again, notice that this data must be in an actual object.
Listing 9-23 INTERC3 // interc3.cpp // interclass communication using pointers and friend classes #include <iostream.h> //////////////////////////////////////////////////////////////// class alpha // an alpha is associated with { // several betas private: friend class beta; // for beta access to alpha data beta* bptr1; // pointers to betas beta* bptr2; int adata; public: alpha(); // constructor (defined after beta) void afunc(); // function (defined after beta) }; //////////////////////////////////////////////////////////////// class beta // several betas are { // associated with an alpha private: friend class alpha; // for alpha access to beta data C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 470 of 701
alpha* aptr; // pointer to "our" alpha int bdata; // data // note: constructor is private beta(alpha* ap) : aptr(ap) // 1-arg construtor { } // initializes pointer to alpha public: void bfunc() { aptr->adata = 3; // access private alpha data } }; //////////////////////////////////////////////////////////////// alpha::alpha() // alpha constructor { // (must be defined after beta) bptr1 = new beta(this); // make betas bptr2 = new beta(this); } void alpha::afunc() // alpha function { // (must be defined after beta) bptr1->bdata = 4; // accesses private beta data bptr2->bdata = 5; } //////////////////////////////////////////////////////////////// void main() { alpha objA; objA.afunc(); } I again use the trick of defining alphas functions after the beta specification because both the alpha constructor and afunc() require access to beta data. You can see that alphas function afunc() accesses the bdata in both beta objects and that betas function bfunc() accesses the adata in the alpha object. How are all these objects created? Lets assume that the class user creates only alpha objects. If the user could create beta objects, the two-beta-one-alpha arrangement could be violated. To ensure that the user cant create a beta, we make the beta constructor private, so only friends of beta can make betas. When an alpha object is created, its constructor creates two beta objects. It does this by calling the beta constructor and passing it its own this pointer. Thus, every beta can locate the alpha that its associated with. Because alphas constructor uses new to create its two beta objects, it can access them by pointer as well. Everyone knows where everyone else is.
Operation of FRIHORSE
When the program, FRIHORSE, is started, it asks the user to supply the races distance and the number of horses that will run in it. The classic unit of distance for horse racing (at least in English-speaking countries) is the furlong, which is 1/8 of a mile. Typical races are 6, 8, 10, or 12 furlongs. You can specify from 1 to 10 horses. The program draws vertical start and finish lines and lines corresponding to furlongs. Each horse is represented by a rectangle with a number in the middle. Figure 9-5 shows the screen with a race in progress.
#include <time.h> // for randomize() const int CPF = 5; // screen columns/furlong //////////////////////////////////////////////////////////////// class track { private: friend class horse; // let horses access data horse* hptr; // pointer to horse memory int total; // total number of horses int count; // horses created so far int track_length; // track length in furlongs float elapsed_time; // time since start of race public: track(float, int); // 2-arg constructor void track_tick(); // time tick; entire track ~track(); // destructor }; // end track class //////////////////////////////////////////////////////////////// class horse { private: friend class track; // let track access functions track* ptr_track; // pointer to track int horse_number; // this horse's number float finish_time; // this horse's finish time float distance_run; // distance since start // note: private member functions horse() : distance_run(0.0) // construct a horse { } void horse_init(track* pt) // initialize a horse { ptr_track = pt; horse_number = (ptr_track->count)++; } void horse_tick(); // time tick for one horse }; // end class horse //////////////////////////////////////////////////////////////// void horse::horse_tick() // for each horse { // display horse & number gotoxy( 1 + int(distance_run * CPF), 2 + horse_number*2 ); cout << " \xDB" << horse_number << "\xDB"; if(distance_run < ptr_track->track_length + 1.0/CPF) { if( random(3) % 3 ) // skip about 1 of 3 ticks distance_run += 0.2; // advance 0.2 furlongs finish_time = ptr_track->elapsed_time; // update finish time } else { // display finish time int mins = int(finish_time)/60; int secs = int(finish_time) - mins*60; cout << " Time=" << mins << ":" << secs; } } //////////////////////////////////////////////////////////////// // two-arg constructor C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 473 of 701
track::track(float l, int t) : track_length(l), total(t), count(0), elapsed_time(0.0) { randomize(); // initialize random numbers clrscr(); // clear screen // display track for(int f=0; f<=track_length; f++) // for each furlong for(int r=1; r<=total*2 + 1; r++) // for each screen row { gotoxy(f*CPF + 5, r); if(f==0 || f==track_length) cout << '\xDE'; // draw start or finish line else cout << '\xB3'; // draw furlong marker } // create horses hptr = new horse[total]; // get memory for all horses for(int j=0; j<total; j++) // initialize each horse (hptr+j)->horse_init(this); // with track pointer } void track::track_tick() { elapsed_time += 1.75; // update time for(int j=0; j<total; j++) // for each horse, (hptr+j)->horse_tick(); // update horse } track::~track() // destructor { delete hptr; // delete memory for horses } //////////////////////////////////////////////////////////////// void main() { float length; int nhorses; cout << "\nEnter track length (furlongs, 6 to 12): "; cin >> length; cout << "\nEnter number of horses (1 to 10): "; cin >> nhorses; track t(length, nhorses); // create track and horses while( !kbhit() ) // exit on keypress { t.track_tick(); // move and display all horses C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 474 of 701
delay(500); } t.~track(); }
Keeping Time
Simulation programs usually involve an activity taking place over a period of time. To model the passage of time, such programs typically energize themselves at fixed intervals. In the FRIHORSE program, the main() program uses a while loop to call a track function, tick_track(), repeatedly; the time tick is sent to the track as a whole. The tick_track() function then makes a series of calls, one for each horse, to the horse function horse_tick() (which should not be confused with the insect that carries equine fever). This function then redraws each horse in its new position.
Quiz 7 1. For all the members of a class B to be able to access the private data of a previously defined (and unrelated) class A, a. class A must be defined as a friend. b. member function definitions of B must follow the class specification for A in the listing. c. the member functions of class B must be defined outside the class. d. within A, B must be declared to be a friend of A. e. within B, A must be declared to be a friend of B. 2. For the members of a class A to be able to access the private data of a class B, whose specification follows A in the listing, a. class B must be defined as a friend. b. member function definitions of A must follow the class specification for B, in the listing. c. the member functions of class A must be defined outside the class. d. within A, B must be declared to be a friend of A. e. within B, A must be declared to be a friend of B. 3. Which of the following statements, describing the INTERC3 program, are true? a. An alpha object knows where related beta objects are because it created them with new.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 475 of 701
b. The pointer to an alpha object, defined within beta, is used by alpha to access private beta data. c. Member functions of beta must be defined following the specification for alpha. d. The friend relationship is unnecessary when each class contains pointers to objects of the other. e. The beta objects know where their related alpha object is because they are friends of alpha. 4. Which of the following statements, concerning the FRIHORSE program, are true? a. The track class does not need to know where the horse objects are stored in memory. b. The new operator is called once to create each horse object. c. The horse objects find out what time it is by accessing private data in track. d. The horse objects find out how far theyve gone by accessing private data in track. e. The track class does not need to be a friend of the horse class. 5. In the FRIHORSE program, a. a single call to the horse_tick() function moves all the horses. b. the horse class creates the track. c. the track class contains data specific to each horse. d. the track class communicates with the horses using a pointer. e. the horse objects are stored contiguously in memory. Exercise 1
Modify the INTERC3 program so that each alpha object is associated with 100 beta objects. Store pointers to the beta objects in an array in alpha. As before, each class should be able to access the others private data.
Exercise 2
Add a third class gamma to the INTERC3 program. Arrange for each alpha object to be associated with one beta object and one gamma object. Organize things so that alpha and beta objects can access one anothers private data, alpha and gamma can do the same, but there is no such communication between beta and gamma.
Nested Classes
You can place one class specification inside another: C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 476 of 701
class alpha { private: class beta; { }; }; Here, beta is said to be nested inside alpha. Whats the advantage of this? For one thing, it can help mirror the conceptual relationship of the classes. If beta is used only by alpha and will never be accessed or instantiated outside alpha, then it makes sense to place it completely within alpha. Theres another advantage to nested classes. Remember that normal class names and member names have global scope; that is, they are visible throughout the entire program. However, in this example, because beta (along with its members, if it had any) is hidden inside alpha, betas member names can be used for other purposes elsewhere in the program without fear of conflict. Such name clashes can be a problem in large programs with thousands of names. Its always good practice to minimize the number of names in the global name space. (Another solution to name clashes is a recently adopted C++ feature called namespaces, which Ill describe in Chapter 11, Session 7.)
// beta is a friend of alpha // (must follow beta) public: void afunc() { beta objB; objB.bdata = 2; // access private beta data objB.bfunc(); C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 477 of 701
};
void main() { alpha objA; objA.afunc(); } The member function afunc() in alpha can access the private bdata in beta, and bfunc() in beta can access the private adata in alpha. As in the examples in the last session, access must be to variables in actual objects. Notice too that the statement friend class alpha::beta; must follow the specification for beta within the alpha specification. It must also use the complete name of beta, which, because it is nested within alpha, is alpha::beta. Within beta, alpha is made a friend in the usual way. Because beta is hidden in alpha, you cant write something like void main() { alpha::beta objB; // Error: can't make a beta object } The beta class is unknown outside of alpha.
horse_number = (ptr_track->count)++; } void horse_tick(); // time tick for one horse }; // end class horse ////////////////////////////////////////////////////////// horse* hptr; // pointer to horse memory // (must follow horse class) int total; // total number of horses int count; // horses created so far int track_length; // track length in furlongs float elapsed_time; // time since start of race friend class track::horse; public: track(float, int); // two-arg constructor void track_tick(); // time tick for entire track ~track() // destructor { delete hptr; // delete horse memory } }; // end track class //////////////////////////////////////////////////////////////// // 2-arg constructor track::track(float l, int t) : track_length(l), total(t), count(0), elapsed_time(0.0) { randomize(); // initialize random numbers clrscr(); // clear screen // display track for(int f=0; f<=track_length; f++) // for each furlong for(int r=1; r<=total*2 + 1; r++) // for each screen row { gotoxy(f*CPF + 5, r); if(f==0 || f==track_length) cout << '\xDE'; // draw start or finish line else cout << '\xB3'; // draw furlong marker } // create horses hptr = new horse[total]; // get memory for all horses for(int j=0; j<total; j++) // initialize each horse (hptr+j)->horse_init(this); // with track pointer C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 479 of 701
} void track::track_tick() { elapsed_time += 1.75; // update time for(int j=0; j<total; j++) // for each horse, (hptr+j)->horse_tick(); // update horse } void track::horse::horse_tick() // for each horse { // display horse & number gotoxy( 1 + int(distance_run * CPF), 2 + horse_number*2 ); cout << " \xDB" << horse_number << "\xDB"; if(distance_run < ptr_track->track_length + 1.0/CPF) { if( random(3) % 3 ) // skip about 1 of 3 ticks distance_run += 0.2; // advance 0.2 furlongs finish_time = ptr_track->elapsed_time; // update finish time } else { // display finish time int mins = int(finish_time)/60; int secs = int(finish_time) - mins*60; cout << " Time=" << mins << ":" << secs; } } //////////////////////////////////////////////////////////////// void main() { float length; int nhorses; cout << "\nEnter track length (furlongs): "; cin >> length; cout << "\nEnter number of horses (1 to 10): "; cin >> nhorses; track t(length, nhorses); // create track and horses while( !kbhit() ) // exit on keypress { t.track_tick(); // move and display all horses delay(500); // wait 1/2 second } t.~track(); // delete horses }
Because horse is nested within track, the class user, represented by main(), cant make the potential mistake of instantiating a horse object: void main() { horse h; // Error: undefined class ... } This is true even though the member functions of horse, including the constructor, are public.
distance_run = 0.0; // haven't moved yet } void horse_tick(); // time tick for one horse public: static void init_track(float l, int t); // initialize track static void track_tick(); // time tick for entire track static void kill_track() // delete all the horses { delete hptr; } }; horse* horse::hptr; // define static (track) vars int horse::total; int horse::count = 0; int horse::track_length; float horse::elapsed_time = 0.0; void horse::init_track(float l, int t) // static (track) function { total = t; // set number of horses track_length = l; // set track length randomize(); // initialize random numbers clrscr(); // clear screen // display track for(int f=0; f<=track_length; f++) // for each furlong for(int r=1; r<=total*2 + 1; r++) // for each screen row { gotoxy(f*CPF + 5, r); if(f==0 || f==track_length) cout << '\xDE'; // draw start or finish line else cout << '\xB3'; // draw furlong marker } hptr = new horse[total]; // get memory for all horses } void horse::track_tick() // static (track) function { elapsed_time += 1.75; // update time for(int j=0; j<total; j++) // for each horse, (hptr+j)->horse_tick(); // update horse } void horse::horse_tick() // for each horse { // display horse & number gotoxy( 1 + int(distance_run * CPF), 2 + horse_number*2 ); cout << " \xDB" << horse_number << "\xDB"; if(distance_run < track_length + 1.0/CPF) // until finish, { if( random(3) % 3 ) // skip about 1 of 3 ticks distance_run += 0.2; // advance 0.2 furlongs finish_time = elapsed_time; // update finish time } else { // display finish time int mins = int(finish_time)/60; int secs = int(finish_time) - mins*60; cout << " Time=" << mins << ":" << secs; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 482 of 701
} } void main() { float length; int nhorses; cout << "\nEnter track length (furlongs): "; cin >> length; cout << "\nEnter number of horses (1 to 10): "; cin >> nhorses; // initialize track and horses horse::init_track(length, nhorses); while( !kbhit() ) // exit on keypress { horse::track_tick(); // move and display all horses delay(500); // wait 1/2 second } horse::kill_track(); // delete horses from memory } Of the three approaches to the horse race program, FRIHORSE in Session 7, and NESHORSE and STAHORSE in this session, which is best? The track is an entity, so it may make more sense to represent it with a class rather than with static functions in the horse class. Also, horse objects should only be created by a track object, so nesting horse inside track is conceptually more appropriate. Thus, NESHORSE seems to be the winner. But a slightly different situation might profit from the static data approach. Look at each programming situation individually and maybe try different approaches to see which is best.
Quiz 8 1. When a class B is nested within a class A, a. member functions of B can always access the private data in A. b. member functions of A can always access the private data in B. c. every object of class A automatically contains one object of class B. d. the names used for B and its members are not visible outside A. e. class A can access class B members only if B is in the public part of A. 2. Which of the following are true? a. If you nest the same specification for a class C within both A and B, which are otherwise unrelated classes, then every object of C, created in A, is automatically accessible to B. b. If you nest a class C within a class B, which is itself nested within a class A, then every object of C, created in B, is automatically accessible to A. c. If you nest a class B within a class A, then within A you must refer to bdata, a member of B, as A::B::bdata. d. If you nest a class B within the public part of a class A, then outside of A (e.g., in main()) you must refer to the B class as A::B.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 483 of 701
e. If you nest a class B within a class A, then to declare that B is a friend of A, you must use the name B::A. 3. In the NESHORSE program, statements in main() cant access horse objects. Which of the following prevents this? a. The horse class is nested within the track class. b. The horse class is nested within the private part of the track class. c. The horse constructor is private. d. All horse objects are created in the track class. e. All horse objects are created in the private part of the track class. 4. Static data a. is defined within a class but declared outside the class. b. can be modified only by static functions. c. represents data common to all objects of a class. d. is global data that is visible to any function, including main(). e. continues to exist even if all objects of a class are destroyed. 5. In the STAHORSE program, a. there are no horse objects, which are instead represented by static data and functions. b. static data and functions in the horse class play the same role as the track class in the FRIHORSE example. c. because its a nested class, the function init_track() must be accessed from main() using the full name horse::init_track(). d. the constructor for the horse class is private so horses cant be instantiated from main(). e. the track class is a static friend of horse. Exercise 1
Rewrite the NESTED program so that each alpha object is associated with two beta objects, which it creates. Have these alpha and beta objects access each others private data using pointers. Make the access possible using friends. In main(), create an alpha object.
Exercise 2
Rewrite the NESTED program so that class beta is represented by static data and functions in alpha. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 484 of 701
Summary: Chapter 9
In this chapter Ive focused on two major topics: virtual functions and friends. Virtual functions are the mechanism that C++ uses to implement polymorphism. In a general sense, polymorphism means that one thing takes different forms; in C++ it means that one function call causes different functions to be executed, depending on the class of the object that made the call. Virtual functions are used in the context of addresses of derived class objects stored in base class pointers. These pointers may be array elements or function arguments. If a member function is declared virtual in a base class, then, when the function is invoked by a base class pointer, the compiler will examine the type of object whose address is stored in the pointer, rather than the type of the pointer, to determine which function to call. This allows a very simple and intuitive way to perform a series of function calls to related objects or to create a global function that can work with objects of many related classes. Pure virtual functions are denoted with the =0 notation. They are associated with an abstract base class, that is, a class from which no objects will be instantiated and that serves only as a common ancestor of other classes. If a class has one or more pure virtual functions, it is defined as an abstract class because the compiler will not allow objects to be instantiated from it. A friend function is a global function (or a member function of a class) that is granted access to the nonpublic data of a class. The class grants this access by declaring the function to be a friend. Friend functions are useful in overloaded operators when the variable on the left side of an operator is not a class member. They are also useful when functional notation, rather than member access syntax (the . or -> operators), is desired for objects. If an entire class is declared to be a friend of a second class, it has access to all the member functions and data of the second class, whether they are public or private. This provides a method for closely related classes to communicate. Actually, only objects of such classes communicate with each other; classes cant access each others data in the abstract (unless the data is static). A class can be nested within another class. When this happens, the inside class is known to the outside class, but is hidden from the outside world. Communication between objects of nested classes requires the same use of friends as does communication between unrelated classes.
End-of-Chapter Discussion
Estelle: There dont seem to be any rules for how to design object-oriented programs. How are you supposed to know whether one class should be derived from another, or should be its friend, or should not be related at all? Or maybe two classes should be merged into one class, or one of the classes should be made into static data inside the other. Or we should all go back to procedural programming! I guess you learn by experience. And trial and error. Youve got to experiment to get the best program design. Theres definitely more to getting comfortable with OOP than with procedural programming. But there are shortcuts. I had this revelation. I dont need to use virtual functions because I can figure out the class of an object using RTTI and then use a switch statement, or maybe an else if ladder, to figure out what function to call. Thats very clever, but I think youve just defeated the whole purpose of virtual functions. Youve expanded a one-statement function call into all those decisions. And if someone adds a new derived class or something, youll need to add new code to your switch statement. Oops. I see what you mean. Maybe theres something in this virtual function business after all. Theres hope for you yet, George.
Advantages of Streams
Old-fashioned C programmers may wonder what advantages there are to using the stream classes for I/O instead of traditional C functions such as printf() and scanf() andfor filesfprintf(), fscanf(), and so on. One reason is that the stream classes are less prone to errors. If youve ever used a %d formatting character when you should have used a %f in printf(), youll appreciate this. There are no such formatting characters in streams, because each object already knows how to display itself. This removes a major source of program bugs. Second, you can overload existing operators and functions, such as the insertion (<<) and extraction (>>) operators, to work with classes you create. This makes your classes work in the same way as the built-in types, which again makes programming easier and more error free (not to mention more aesthetically satisfying). You may wonder if C++ stream I/O is important if you plan to program in an environment with a Graphics User Interface (GUI) such as Windows, where direct text output to the screen is not used. Yes, iostreams are still important because they are the best way to write data to files and to format data in memory for later use in dialog boxes and other GUI elements.
Figure 10-1 Stream class hierarchy The classes used for input and output to the video display and keyboard are declared in the header file IOSTREAM.H, which I routinely included in my examples in previous chapters. The classes used specifically for disk file I/O are declared in the file FSTREAM.H. Figure 10-1 shows which classes are in each of these two header files. (Also, some manipulators are declared in IOMANIP.H and in-memory classes are declared in STRSTREA.H.) You may find it educational to print out the contents of these header files and trace the relationships among the various classes. Many questions about streams can be answered by studying their class and constant declarations. Theyre in your compilers INCLUDE subdirectory. As you can see in Figure 10-1, the ios class is the base class for the iostream hierarchy. It contains many constants and member functions common to input and output operations of all kinds. Some of these, such as the showpoint and fixed formatting flags, youve seen already. The ios class also contains a pointer to the streambuf class, which contains the actual memory buffer into which data is read or written and the low-level routines for handling this data. Ordinarily, you dont need to worry about the streambuf class, which is referenced automatically by other classes, but sometimes access to this buffer is helpful. The istream and ostream classes are derived from ios and are dedicated to input and output, respectively. The istream class contains such member functions as get(), getline(), read(), and the extraction (>>) operators, whereas ostream contains put() and write() and the insertion (<<) operators. The iostream class is derived from both istream and ostream by multiple inheritance. Classes derived from the iostream class can be used with devices, such as disk files, that may be opened for both input and output at the same time. Three classesistream_withassign, ostream_withassign, and iostream_withassignare inherited from istream, ostream, and iostream, respectively. They add assignment operators to these classes so that cin, cout, and so on can be assigned to other streams. (Youll see what this means when I talk about redirection.) C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 487 of 701
The summary of stream classes in this session contains considerable information. You may want to skim this session now and return to it later when you need to know how to perform a particular stream-related activity.
Formatting Flags
Formatting flags are a set of enum definitions in ios. They act as on/off switches that specify choices for various aspects of input and output format and operation. I wont provide a detailed discussion of each flag because youve already seen some of them in use and others are more or less self-explanatory. Some Ill discuss later in this chapter. Table 10-1 presents a complete list of the formatting flags. Table 10-1 ios formatting flags Flag |Meaning skipws |Skip (ignore) whitespace on input. left |Left adjust output [12.34 ]. right |Right adjust output [ 12.34]. internal |Use padding between sign or base indicator and number [+12.34]. dec |Convert to decimal. oct |Convert to octal. hex |Convert to hexadecimal. Showbase| Use base indicator on output (0 for octal, 0x for hex). showpoint |Show decimal point on output. uppercase |Use uppercase X, E, and hex output letters ABCDEF (the default is lowercase). showpos |Display + before positive integers. scientific |Use exponential format on floating-point output [9.1234E2]. fixed |Use fixed format on floating-point output [912.34]. unitbuf |Flush all streams after insertion. stdio |Flush stdout, stderror after insertion.
There are several ways to set the formatting flags, and different flags can be set in different ways. Because they are members of the ios class, flags must usually be preceded by the name ios and the scope-resolution operator (e.g., ios::skipws). All the flags can be set using the setf() and unsetf() ios member functions. For example, cout.setf(ios::left); // left justify output text cout >> "This text is left-justified"; cout.unsetf(ios::left); // return to default (right justified) Many formatting flags can be set using manipulators, so lets look at them now.
Manipulators
Manipulators are formatting instructions inserted directly into a stream. Youve seen examples before, such as the manipulator endl, which sends a new line to the stream and flushes it: cout << "To each his own." << endl; Ive also used the setiosflags() manipulator: cout << setiosflags(ios::fixed) // use fixed decimal point << setiosflags(ios::showpoint) // always show decimal point << var; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 488 of 701
As these examples demonstrate, manipulators come in two flavors: those that take an argument and those that dont. Table 10-2 summarizes the no-argument manipulators. Table 10-2 No-argument ios manipulators Manipulator |Purpose ws |Turn on whitespace skipping on input. dec |Convert to decimal. Oct| Convert to octal. hex| Convert to hexadecimal. endl |Insert new line and flush the output stream. ends |Insert null character to terminate an output string. flush |Flush the output stream. lock Lock file handle. unlock |Unlock file handle. You insert these manipulators directly into the stream. For example, to output var in hexadecimal format, you can say cout << hex << var; The states set by no-argument manipulators remain in effect until the stream is destroyed, so you can, for example, output many numbers in hex format with only one insertion of the hex manipulator. Table 10-3 summarizes the manipulators that take arguments. You need the IOMANIP.H header file for these functions. Table 10-3 ios manipulators with arguments Manipulator |Argument |Purpose setw() |field width (int) |Set field width for output. setfill() |fill character (int) |Set fill character for output (default is a space). setprecision() |precision (int) |Set precision (number of digits displayed). setiosflags() |formatting flags (long) |Set specified flags. resetiosflags()|formatting flags (long) |Clear specified flags. Manipulators that take arguments affect only the next item in the stream. For example, if you use setw to set the width of the field in which one number is displayed, youll need to use it again for the next number.
Functions
The ios class contains a number of functions that you can use to set the formatting flags and perform other tasks. Table 10-4 shows most of these functions, except those that deal with errors, which Ill examine in the next session. Table 10-4 ios functions Function |Purpose ch = fill(); Return the fill character (fills unused part of field; default is space). fill(ch); Set the fill character. p = precision() Get the precision (number of digits displayed for floating point). precision(p); Set the precision. w = width(); Get the current field width (in characters). width(w); Set the current field width. setf(flags); Set specified formatting flags (e.g., ios::left). unsetf(flags); Unset specified formatting flags. setf(flags, field); First clear field, then set flags. These functions are called for specific stream objects using the normal dot operator. For example, to set the field width to 14, you can say C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 489 of 701
cout.width(14); Similarly, the following statement sets the fill character to an asterisk (as for check printing): cout.fill('*'); You can use several functions to manipulate the ios formatting flags directly. For example, to set left justification, use cout.setf(ios::left); To restore right justification, use cout.unsetf(ios::left); A two-argument version of setf() uses the second argument to reset all the flags of a particular type or field. Then the flag specified in the first argument is set. This makes it easier to reset the relevant flags before setting a new one. Table 10-5 shows the arrangement. Table 10-5 Two-argument version of setf() First argument: flags to set | Second argument; field to clear dec, oct, hex | basefield left, right, internal |adjustfield scientific, fixed | floatfield For example, cout.setf(ios::left, ios::adjustfield); clears all the flags dealing with text justification and then sets the left flag for left-justified output. By using the techniques shown here with the formatting flags, you can usually figure out a way to format I/O not only for the keyboard and display, but, as youll see later in this chapter, for files as well.
Figure 10-2 Input and output Table 10-6 lists the functions youll most commonly use from the istream class. Table 10-6 istream functions Function | Purpose >> Formatted extraction for all basic (and overloaded) types. get(ch); Extract one character into ch. get(str) Extract characters into array str, until \0. get(str, MAX) Extract up to MAX characters into array. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 490 of 701
get(str, DELIM) Extract characters into array str until specified delimiter (typically \n). Leave delimiting char in stream. get(str, MAX, DELIM) Extract characters into array str until MAX characters or the DELIM character. Leave delimiting char in stream. getline(str, MAX, DELIM) Extract characters into array str until MAX characters or the DELIM character. Extract delimiting character. putback(ch) Insert last character read back into input stream. ignore(MAX, DELIM) Extract and discard up to MAX characters until (and including) the specified delimiter (typically \n). peek(ch) Read one character, leave it in stream. count = gcount() Return number of characters read by a (immediately preceding) call to get(), getline(), or read(). read(str, MAX) For files. Extract up to MAX characters into str until EOF. seekg(position) Sets distance (in bytes) of file pointer from start of file. seekg(position, seek_dir) Sets distance (in bytes) of file pointer from specified place in file: seek_dir can be ios::beg, ios::cur, ios::end. position = tellg(pos) Return position (in bytes) of file pointer from start of file.
Youve seen some of these functions, such as get(), before. Most of them operate on the cin object, representing the keyboard as well as disk files. However, the last four deal specifically with disk files. Youll see how they work later in this chapter.
Why do you need separate copyable and uncopyable stream classes? In general, its not a good idea to copy stream class objects. Each such object is associated with a particular streambuf object, which includes an area in memory to hold the objects actual data. If you copy the stream object, it causes confusion if you also copy the streambuf object. However, in a few cases its important to be able to copy a stream object, as in the case of redirection of the predefined objects cout and cin. (Ill discuss redirection later in this chapter.) Accordingly, the istream, ostream, and iostream classes are made uncopyable (by making their overloaded copy constructors and assignment operators private), whereas the _withassign classes derived from them can be copied.
The cerr object is often used for error messages and program diagnostics. Output sent to cerr is displayed immediately, rather than being buffered, as output sent to cout is. Also, output to cerr cannot be redirected. For these reasons, you have a better chance of seeing a final output message from cerr if your program dies prematurely. Another object, clog, is similar to cerr in that it is not redirected, but its output is buffered, whereas cerrs is not.
1. Which of the following are advantages of C++ iostreams over the old C language printf() and scanf() approach? a. Most people are already familiar with printf() and scanf(). b. Iostreams allow the same interface for user-created classes as for built-in types. c. With iostreams, you dont need to learn weird formatting codes such as %d and %f. d. With iostreams, errors are more likely to be caught at compile time rather than at runtime. e. You dont need to specify the type of data being input or output because the compiler can figure it out. 2. The iostream class hierarchy a. has no user-accessible member functions. b. has as a base class the iostream class. c. can handle the keyboard and display, but not files. d. relates various classes from which objects, representing data streams, can be instantiated. e. uses multiple inheritance.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 492 of 701
3. ios formatting flags a. work only with output. b. can be set using manipulators. c. can be set using ios member functions. d. include fill(). e. include fixed. 4. Manipulators a. include endl. b. include fill(). c. are inserted directly into a stream. d. are called with the dot operator, like any member function. e. cannot take arguments. 5. The ostream class a. is used to instantiate the cout object. b. is used to instantiate the cin object. c. is used to derive the ostream_withassign class. d. contains a member function that inserts a character into a stream. e. is directly derived from the iostream class.
Due to its theoretical nature, this session contains no exercises.
Stream Errors
So far, Ive used a rather straightforward approach to input and output in this book, using statements of the form cout << "Good morning"; and cin >> var; However, as you may have discovered, this approach assumes that nothing will go wrong during the I/O process. This isnt always the case, especially on input. What happens if a user enters the string nine instead of the integer 9, or pushes without entering anything? What happens if theres a hardware failure? Ill explore such problems in this session. Many of the techniques youll see here are applicable to file I/O as well.
Error-Status Bits
The stream error-status bits are an ios enum member that report errors that occurred in an input or output operation. Theyre summarized in Table 10-9. Table 10-9 Error-status bits Name |Meaning goodbit |No errors (no bits set, value = 0). Eofbit| Reached end of file. Failbit| Operation failed (user error, premature EOF). badbit |Invalid operation (no associated streambuf). hardfail |Unrecoverable error. Figure 10-3 shows the position of these bits in the error-status byte.
Figure 10-3 Error-status byte Various ios functions can be used to read (and even set) these error bits, as summarized in Table 10-10. Table 10-10 Functions for error bits Function |Purpose int = eof(); Returns true if EOF bit set. int = fail(); Returns true if fail bit or bad bit or hard-fail bit set. int = bad(); Returns true if bad bit or hard-fail bit set. int = good(); Returns true if everything OK; no bits set. clear(int=0); With no argument, clears all error bits; otherwise sets specified bits, as in clear(ios::failbit).
Inputting Numbers
Lets see how to handle errors when inputting numbers. This approach applies to numbers read both from the keyboard and from disk, as youll see later. The idea is to check the value of goodbit, signal an error if its not true, and give the user another chance to enter the correct input. while(1) // cycle until input OK { cout << "\nEnter an integer: "; cin >> i; if( cin.good() ) // if no errors { cin.ignore(10, '\n'); // remove newline break; // exit loop } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 494 of 701
cin.clear(); // clear the error bits cout << "Incorrect input"; cin.ignore(10, '\n'); // remove newline } cout << "integer is " << i; // error-free integer The most common error this scheme detects when reading keyboard input is the user typing nondigits (such as nine instead of 9). This causes the failbit to be set. However, it also detects system-related failures that are more common with disk files. Floating-point numbers (float, double, and long double) can be analyzed for errors in the same way as integers.
No-Input Input
Whitespace characters, such as , and \n, are normally ignored (skipped) when inputting numbers. This can have some undesirable side effects. For example, users, prompted to enter a number, may simply press the key without typing any digits. (Perhaps they think that this will enter 0, or perhaps they are simply confused.) In the code shown above, as well as the simple statement
cin >> i; pressing causes the cursor to drop down to the next line while the stream continues to wait for the number. Whats wrong with the cursor dropping to the next line? First, inexperienced users, seeing no acknowledgment when they press , may assume the computer is broken. Second, pressing repeatedly normally causes the cursor to drop lower and lower until the entire screen begins to scroll upward. This is all right in teletype-style interaction where the program and the user simply type at each other. In text-based graphics programs, however, scrolling the screen disarranges and eventually obliterates the display. Thus its important to be able to tell the input stream not to ignore whitespace. This is done by clearing the skipws flag: cout << "\nEnter an integer: "; cin.unsetf(ios::skipws); // don't ignore whitespace cin >> i; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 495 of 701
Now if the user types without any digits, failbit will be set and an error will be generated. The program can then tell the user what to do or reposition the cursor so the screen does not scroll.
Error-Free Distances
Lets look at a program in which user input to the English Distance class is checked for errors. This program accepts Distance values in feet and inches from the user and displays them. If the user commits an entry error, the program rejects the input with an appropriate explanation to the user and prompts for new input. The program is very simple except that the member function getdist() has been expanded to handle errors. Parts of this new code follow the approach of the fragment shown previously. Ive also added some statements to ensure that the user does not enter a floating-point number for feet. This is important because, whereas the feet value is an integer, the inches value is floating point, and the user could easily become confused. Ordinarily, if its expecting an integer, the extraction operator simply terminates without signaling an error when it sees a decimal point. The program wants to know about such an error, so it reads the feet value as a string instead of an int. It then examines the string with a homemade function isint(), which returns true if the string proves to be an int. To pass the int test, the string must contain only digits and they must evaluate to a number between 32,768 and 32,767 (the range of type int). If the string passes the int test, the program converts it to an actual int with the library function atoi(). The inches value is an integer. The program checks its range, which should be 0 or greater and less than 12.0. The program also checks it for ios error bits. Most commonly, the fail bit will be set because the user typed nondigits instead of an integer. Listing 10-1 shows ENGLERR. Listing 10-1 ENGLERR // englerr.cpp // input checking with English Distance class #include <iostream.h> #include <string.h> // for strchr() #include <stdlib.h> // for atoi() int isint(char*); const int IGN = 10; class Distance { private: int feet; float inches; public: // prototype // characters to ignore // English Distance class
Distance() // constructor (no args) { feet = 0; inches = 0.0; } Distance(int ft, float in) // constructor (two args) { feet = ft; inches = in; } void showdist() // display distance { cout << feet << "\'-" << inches << '\"'; } void getdist(); // get length from user }; void Distance::getdist() { char instr[80]; // get length from user // for input string
while(1) // cycle until feet are right { cout << "\n\nEnter feet: "; cin.unsetf(ios::skipws); // do not skip white space cin >> instr; // get feet as a string if( isint(instr) ) // is it an integer? { // yes cin.ignore(IGN, '\n'); // eat chars, including newline feet = atoi(instr); // convert to integer break; // break out of 'while' } // no, not an integer cin.ignore(IGN, '\n'); // eat chars, including newline cout << "Feet must be an integer\n"; // start again } // end while feet while(1) // { cout << "Enter inches: "; cin.unsetf(ios::skipws); // cin >> inches; // if(inches>=12.0 || inches<0.0) { cout << "Inches must be between cin.clear(ios::failbit); // } if( cin.good() ) // { // cin.ignore(IGN, '\n'); // break; // cycle until inches are right
0.0 and 11.99\n"; "artificially" set fail bit check for cin failure (most commonly a non-digit) eat the newline input is OK, exit 'while'
} cin.clear(); // error; clear the error state cin.ignore(IGN, '\n'); // eat chars, including newline cout << "Incorrect inches input\n"; // start again } // end while inches } int isint(char* str) // return true if the string { // represents type int int slen = strlen(str); // get length if( slen==0 || slen > 5) // if no input, or too long return 0; // not an int for(int j=0; j<slen; j++) // check each character // if not digit or minus C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 497 of 701
if( (str[j] < '0' || str[j] > '9') return 0; // long n = atol(str); // if( n< -32768L || n>32767L ) // is it return 0; // return 1; // }
&& str[j] != '-' ) string is not an int convert to long int out of int range? if so, not an int it is an int
void main() { Distance d; // make a Distance object char ans; do { d.getdist(); // get its value from user cout << "\nDistance = "; d.showdist(); // display it cout << "\nDo another (y/n)? "; cin >> ans; cin.ignore(IGN, '\n'); // eat chars, including newline } while(ans != 'n'); // cycle until 'n' } Ive used another dodge here: setting an error-state flag manually. I do this because I want to ensure that the inches value is greater than 0 but less than 12.0. If it isnt, I turn on the failbit with the statement cin.clear(ios::failbit); // set failbit When the program checks for errors with cin.good(), it will find the failbit set and signal that the input is incorrect.
All-Character Input
Another approach to error handling is to read all input, even numbers, as strings. Your code can then parse the string, character by character, to determine whether the user has typed something reasonable. If so, it can be converted to the appropriate kind of number. The details of this approach depend on the specific input you expect, so I wont show an example; if youre trying to write the most bullet-proof code possible, this is probably the best way to go. Of course, even when reading characters, you should still check for errors using the error-status bits.
Quiz 2 1. The error-status bits a. can be read but not altered by user code. b. can be used to determine if end-of-file has occurred. c. are part of the iostream class. d. can be checked using functions such as fail() and bad(). e. are all set if good() returns false. 2. The ignore(MAX, DELIM) member function of istream a. always reads exactly MAX characters. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
Page 498 of 701
b. reads up to MAX characters or until a DELIM character is encountered, whichever comes first. c. is used to format output. d. causes the same effect as setting the ios::skipws flag. e. gets rid of unwanted input. 3. The clear() function a. is a friend of the iostream class. b. is a member of the iostream class. c. can be used to set the goodbit. d. can be used to clear all the error bits at once. e. can be used to clear the entire display screen. 4. In the ENGLERR program, the user cannot enter a. a floating-point value for feet. b. an integer value for inches. c. a value for feet thats greater than 32,767. d. an inches value greater than or equal to 11.99. e. letters in place of numbers. 5. The safest way to handle user input is a. to convert numbers to character strings after reading them. b. to not worry about the error-status bits. c. to handle things exactly as in the englerr program. d. to read all input as strings of characters and then to analyze them. e. to read only integers. Exercise 1
Rewrite get() the member function of the airtime class in the addair program (from Chapter 6, Session 1) so that it incorporates the same kind of input error checking that is found in ENGLERR. Notify the user if the hours value is not between 0 and 23 and the minutes value is not between 0 and 59.
Exercise 2
Rewrite the get() function referred to in Exercise 1 so user input is read as a string and then is parsed and converted into hours and minutes values.
// // // // //
// create ofstream object // insert (write) data // needs space between numbers
<< str2; } Here the program defines an object called outfile to be a member of the ofstream class. At the same time, it initializes the object to the file name FDATA.TXT. This initialization sets aside various resources for the file, and accesses or opens the file of that name on the disk. If the file doesnt exist, it is created. If it does exist, it is truncated and the new data replaces the old. The outfile object acts much as cout did in previous programs, so the insertion operator (<<) is used to output variables of any basic type to the file. This works because the insertion operator is appropriately overloaded in ostream, from which ofstream is derived. When the program terminates, the outfile object goes out of scope. This calls its destructor, which closes the file, so you dont need to close the file explicitly. There are several potential formatting glitches. First, you must separate numbers (such as 77 and 6.02) with nonnumeric characters. Because numbers are stored as a sequence of characters rather than as a fixed-length field, this is the only way the extraction operator will know, when the data is read back from the file, where one number stops and the next one begins. Second, strings must be separated with whitespace for the same reason. This implies that strings cannot contain imbedded blanks. In this example, I use the space character ( ) for both kinds of delimiters. Characters need no delimiters, because they have a fixed length. You can verify that FORMATO has indeed written the data by examining the FDATA.TXT file with any text-based editor.
Reading Data
Any program can read the file generated by FORMATo by using an ifstream object that is initialized to the name of the file. The file is automatically opened when the object is created. The program can then read from it using the extraction (>>) operator. Listing 10-3 shows the FORMATI program, which reads the data back in from the FDATA.TXT file. Listing 10-3 FORMATI // formati.cpp // reads formatted output from a file, using >> #include <fstream.h> const int MAX = 80; void main() { char ch; int j; double d; char str1[MAX]; char str2[MAX]; ifstream infile("fdata.txt");
// empty variables
// create ifstream object // extract (read) data from it infile >> ch >> j >> d >> str1 >> str2;
cout << ch << endl // display the data << j << endl << d << endl << str1 << endl << str2 << endl; } Here the ifstream object, which I name infile, acts much the way cin did in previous programs. Provided I have formatted the data correctly when inserting it into the file, theres no trouble extracting it, storing it in the appropriate variables, and displaying their contents. The programs output looks like this: x 77 6.02 Kafka C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 501 of 701
Proust Note that the numbers are converted back to their binary representations for storage in the program. That is, the 77 is stored in the variable j as a type int, not as two characters, and the 6.02 is stored as a double.
outfile << outfile << outfile << outfile << } When you run the program, the lines of text (from Samuel Taylor Coleridges The Rime of the Ancient Mariner) are written to a file. Each one is specifically terminated with a newline (\n) character. To extract the strings from the file, the program creates an ifstream and reads from it one line at a time using the getline() function, which is a member of istream. This function reads characters, including whitespace, until it encounters the \n character and places the resulting string in the buffer supplied as an argument. The maximum size of the buffer is given as the second argument. The contents of the buffer are displayed after each line. Listing 10-5 shows ILINE. Listing 10-5 ILINE // iline.cpp // file input with strings #include <fstream.h> // for file functions void main() { const int MAX = 80; // size of buffer char buffer[MAX]; // character buffer ifstream infile("TEST.TXT"); // create file for input while( infile ) // until end-of-file { infile.getline(buffer, MAX); // read a line of text cout << buffer; // display it } } The output of ILINE to the screen is the same as the data written to the TEST.TXT file by OLINE: the four-line Coleridge stanza. The program has no way of knowing in advance how many strings are in the file, so it continues to read one string at a time until it encounters an EOF. Incidentally, dont use this program to read random text files. It requires all the text lines to terminate with the \n character, and if you encounter a file in which this is not the case, the program may hang.
// create file for output // send text to file "I fear thee, ancient Mariner!\n"; "I fear thy skinny hand\n"; "And thou art long, and lank, and brown,\n"; "As is the ribbed sea sand.\n";
Detecting End-of-File
As you have seen, objects derived from ios contain error-status bits that can be checked to determine the results of operations. When you read a file little by little, as we do in OLINE, you will eventually encounter an end-of-file condition. The EOF is a signal sent to the program from the hardware when there is no more data to read. In ILINE, I could have used the following construction to check for this: while( !infile.eof() ) // until eof encountered However, checking specifically for an eofbit means that I wont detect the other error bits, such as the failbit and badbit, which may also occur, although more rarely. To do this, I could change the loop condition: while( infile.good() ) // until any error encountered But even more simply, I can test the stream directly, as I do in OLINE: while( infile ) // until any error encountered Any stream object, such as infile, has a value that can be tested for the usual error conditions, including EOF. If any such condition is true, the object returns a zero value. If everything is going well, the object returns a nonzero value. This value is actually a pointer, but the address returned has no significance except to be tested for a zero or nonzero value.
Character I/O
The put() and get() functions, which are members of ostream and istream, respectively, can be used to output and input single characters. Listing 10-6 shows, OCHAR, which outputs a string one character at a time. Listing 10-6 OCHAR // ochar.cpp // file output with characters #include <fstream.h> // for file functions #include <string.h> // for strlen() void main() { char str[] = "Time is a great teacher, but unfortunately " "it kills all its pupils. Berlioz"; ofstream outfile("TEST.TXT"); // create file for output for(int j=0; j<strlen(str); j++) // for each character, outfile.put(str[j]); // write it to file } In this program, an ofstream object is created as it was in OLINE. The length of the string is found using the strlen() function and the characters are output using put() in a for loop. The aphorism by Hector Berlioz (a 19th-century composer of operas and program music) is written to the file TEST.TXT. I can read this file back in and display it using the ICHAR program (Listing 10-7). Listing 10-7 ICHAR // ichar.cpp // file input with characters #include <fstream.h> // for file functions void main() { char ch; ifstream infile("TEST.TXT"); while( infile ) { infile.get(ch); cout << ch; } }
// character to read // create file for input // read until EOF // read character // display it
This program uses the get() function and continues reading until the EOF is reached. Each character read from the file is displayed using cout, so the entire aphorism appears on the screen.
Binary I/O
You can write a few numbers to disk using formatted I/O, but if youre storing a large amount of numerical data, its more efficient to use binary I/O in which numbers are stored as they are in the computers RAM memory rather than as strings of characters. In binary I/O an integer is always stored in 2 bytes, whereas its text version might be 12345, requiring 5 bytes. Similarly, a float is always stored in 4 bytes, whereas its formatted version might be 6.02314e13, requiring 10 bytes. The next example shows how an array of integers is written to disk and then read back into memory using binary format. I use two new functions: write(), a member of ofstream, and read(), a member of ifstream. These functions think about data in terms of bytes (type char). They dont care how the data is formatted, they simply transfer a buffer full of bytes from and to a disk file. The parameters to write() and read() are the address of the data buffer and its length. The address must be cast to type char, and the length is the length in bytes (characters), not the number of data items in the buffer. Listing 10-9 shows BINIO. Listing 10-9 BINIO // binio.cpp // binary input and output with integers #include <fstream.h> // for file streams const int MAX = 100; int buff[MAX]; void main() { int j; for(j=0; j<MAX; j++) buff[j] = j; // number of ints // buffer for integers
fill buffer with data (0, 1, 2, ...) create output stream write to it ); must close it
for(j=0; j<MAX; j++) // erase buffer buff[j] = 0; // create input stream ifstream is("edata.dat", ios::binary); C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 504 of 701
// read from it is.read( (char*)buff, MAX*sizeof(int) ); for(j=0; j<MAX; j++) // check data if( buff[j] != j ) { cerr << "\nData is incorrect"; return; } cout << "\nData is correct"; } You must use the ios::binary argument in the second paramter to write() and read() when working with binary data. This is because the default, text mode, takes some liberties with the data. For example, in text mode, the \n character is expanded into 2 bytesa carriage return and a linefeedbefore being stored to disk. This makes a formatted text file more readable for simple text editors, but causes confusion when applied to binary data because every byte that happens to have the ASCII value 10 is translated into 2 bytes. So far in the examples, there has been no need to close streams explicitly because they are closed automatically when they go out of scope; this invokes their destructors and closes the associated file. However, in BINIO, because both the output stream os and the input stream is are associated with the same file, EDATA.DAT, the first stream must be closed before the second is opened. I use the close() member function for this.
Object I/O
Because C++ is an object-oriented language, its reasonable to wonder how objects can be written to and read from disk. The next examples show the process. The person class, used in several previous examples (e.g., the VIRTPERS program in Chapter 9, Session 2) supplies the objects.
// create a person // get data for person // create ofstream object ofstream outfile("PERSON.DAT", ios::binary); outfile.write( (char*)&pers, sizeof(pers) ); // write to it } Page 505 of 701
The getData() member function of person is called to prompt the user for information, which it places in the pers object. Heres some sample interaction: Enter name: Coleridge Enter age: 62 The contents of the pers object are then written to disk using the write() function. I use the sizeof operator to find the size of the pers object.
void main(void) { person pers; // create person variable ifstream infile("PERSON.DAT", ios::binary); // create stream infile.read( (char*)&pers, sizeof(pers) ); // read stream pers.showData(); // display person } The output from IPERS reflects whatever data the OPERS program placed in the PERSON.DATfile: Name: Coleridge Age: 62
objects other data. If you change a classs member functions, this number changes as well. If you write an object of one class to a file and then read it back into an object of a class that has identical data but different member functions, youll encounter big trouble if you try to use virtual functions on the object. The moral: Make sure a class that reads an object is identical to the class that wrote it.
// create person object // create input/output file // open for append file.open("PERSON.DAT", ios::app | ios::out | ios::in | ios::binary ); do
// data from user to file { cout << "\nEnter person's data:"; pers.getData(); // get one person's data // write to file file.write( (char*)&pers, sizeof(pers) ); cout << "Enter another person (y/n)? "; cin >> ch; } while(ch=='y'); // quit on 'n' file.seekg(0); // reset to start of file // read first person file.read( (char*)&pers, sizeof(pers) ); Page 507 of 701
while( !file.eof() ) // quit on EOF { cout << "\nPerson:"; // display person pers.showData(); file.read( (char*)&pers, sizeof(pers) ); // read another } // person } Heres some sample interaction with DISKFUN. The output shown assumes that the program has been run before and that two person objects have already been written to the file. Enter person's data: Enter name: McKinley Enter age: 22 Enter another person (y/n)? n Person: Name: Whitney Age: 20 Person: Name: Rainier Age 21 Person: Name: McKinley Age: 22 One additional object is added to the file and the entire contents, consisting of three objects, are then displayed.
In DISKFUN, I use app because I want to preserve whatever was in the file before; whatever I write to the file will be added at the end of the existing contents. I use in and out because I want to perform both input and output on the file, and I use binary because Im writing binary objects. The vertical bars between the flags cause the bits representing these flags to be combined (ORed) together into a single integer so that several flags can apply simultaneously. I write one person object at a time to the file using the write() function. When Ive finished writing, I want to read the entire file. Before doing this, I must reset the files current position. I do this with the seekg() function (which Ill examine in the next session, on file pointers). It ensures Ill start reading at the beginning of the file. Then, in a while loop, I repeatedly read a person object from the file and display it on the screen. This continues until Ive read all the person objectsa state that I discover using the eof() function, which returns the state of the ios::eofbit.
Quiz 3 1. To perform stream I/O with disk files in C++, you should a. open and close files as in procedural languages. b. create objects that are associated with specific files. c. include the IOSTREAM.H header file. d. use classes derived from ios. e. use C language library functions to read and write data. 2. In formatted file I/O, a. you read data directly into individual variables. b. an integer requires the same number of bytes in the file as it does when converted to a string. c. an integer requires the same number of bytes in the file as it does in RAM. d. a carriage return is stored as 1 byte. e. you must explicitly write an EOF character at the end of every file. 3. In binary file I/O, a. you use the iostream::binary mode bit. b. writing each variable requires a separate call to write(). c. an integer requires the same number of bytes in the file as it does when converted to a string. d. an integer requires the same number of bytes in the file as it does in RAM. e. the stream objects think of the data as a buffer of bytes. 4. When using streams to write objects to a disk file,
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 509 of 701
a. you must use the iostream::out flag. b. each objects data and member functions are stored on the disk. c. you should use formatted I/O. d. you can write to an object of the fstream class. e. objects should be read back into variables of the identical class from which they were written. 5. Which of the following statements, concerning stream I/O with disk files, are true? a. Objects cannot be stored on disk as objects; their data must be stored as individual variables. b. For an object of a user-defined class to be written to disk, the class must contain member functions for this purpose. c. You can write to a stream object, and then, without closing it, read from it. d. A mode bit can be used with open() to specify that an attempt to open a nonexistent file will fail. e. You cannot send output to a file without destroying any data already in the file. Exercise 1
Start with the airtime class (as in the ADDAIR program from Chapter 6, Session 1) and write a main() that asks for an indefinite number of airtime values from the user and writes them all, with a single statement, to a disk file. Destroy whatever was in the file before.
Exercise 2
Add code to the above example to read the contents of the file and display it.
Reacting to Errors
The next program shows how such errors are most conveniently handled. All disk operations are checked after they are performed. If an error has occurred, a message is printed and the program terminates. Ive used the technique, discussed earlier, of checking the return value from the object itself to determine its error status. The program opens an output stream object, writes an entire array of integers to it with a single call to write(), and closes the object. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 510 of 701
Then it opens an input stream object and reads the array of integers with a call to read(). Listing 10-13 shows REWERR. Listing 10-13 REWERR // rewerr.cpp // handles errors during input and output #include <fstream.h> #include <process.h> const int MAX = 1000; int buff[MAX]; void main() { int j; for(j=0; j<MAX; j++) buff[j] = j; ofstream os; // for file streams // for exit()
// create output stream // open it os.open("a:edata.dat", ios::trunc | ios::binary); if(!os) { cerr << "\nCould not open output file"; exit(1); } cout << "\nWriting..."; // write buffer to it os.write( (char*)buff, MAX*sizeof(int) ); if(!os) { cerr << "\nCould not write to file"; exit(1); } os.close(); // must close it for(j=0; j<MAX; j++) buff[j] = 0; ifstream is; is.open("a:edata.dat", ios::binary); if(!is) // clear buffer
{ cerr << "\nCould not open input file"; exit(1); } cout << "\nReading..."; // read file is.read( (char*)buff, MAX*sizeof(int) ); if(!is) { cerr << "\nCould not read from file"; exit(1); } for(j=0; j<MAX; j++) // check data if( buff[j] != j ) { cerr << "\nData is incorrect"; exit(1); } cout << "\nData is correct"; }
Analyzing Errors
In the REWERR example, I determined whether an error occurred in an I/O operation by examining the return value of the entire stream object. if(!is) // error occurred
Here, is returns a pointer value if everything went well, but 0 if it didnt. This is the shotgun approach to errors: No matter what the error is, its detected in the same way and the same action is taken. However, its also possible, using the ios error-status bits, to find out more specific information about a file I/O error. Youve already seen some of these status bits at work in screen and keyboard I/O. The next example, FERRORS (Listing 10-14), shows how they can be used in file I/O. Listing 10-14 FERRORS // ferrors.cpp // checks for errors opening file #include <fstream.h> // for file functions void main() { ifstream file; file.open("GROUP.DAT", ios::nocreate); if( !file ) cout << "\nCan't open GROUP.DAT"; else cout << "\nFile opened successfully."; cout << "\nfile = " << file; cout << "\nError state = " << file.rdstate(); cout << "\ngood() = " << file.good(); cout << "\neof() = " << file.eof(); cout << "\nfail() = " << file.fail(); cout << "\nbad() = " << file.bad(); file.close(); } This program first checks the value of the object file. If its value is zero, the file probably could not be opened because it didnt exist. Heres the output from ferrors when thats the case: Can't open GROUP.DAT file = 0x1c730000 Error state = 4 good() = 0 eof() = 0 fail() = 4 bad() = 4 The error state returned by rdstate() is 4. This is the bit that indicates the file doesnt exist; its set to 1. The other bits are all set to 0. The good() function returns 1 (true) only when no bits are set, so it returns 0 (false). Im not at EOF, so eof() returns 0. The fail() and bad() functions return nonzero because an error occurred. In a serious program, some or all of these functions should be used after every I/O operation to ensure that things have gone as expected.
File Pointers
Each file object has associated with it two integer values called the get pointer and the put pointer. These are also called the current get position and the current put position, orif its clear which one is meantsimply the current position. These values specify the byte number in the file where writing or reading will take place. (The term pointer in this context should not be confused with C++ pointers used as address variables.) Youll often want to start reading an existing file at the beginning and continue until the end. When writing, you may want to start at the beginning, deleting any existing contents, or at the end, in which case you can open the file with the ios::app mode specifier. These are the default actions, so no manipulation of the file pointers is necessary. However, there are times when you must take control of the file pointers yourself so that you can read from or write to an arbitrary location in the file. The seekg() and tellg() functions allow you to set and examine the get pointer, and the seekp() and tellp() functions perform the same actions on the put pointer.
Figure 10-4 shows how this looks. Figure 10-4 The seekg() function with one argument
Figure 10-5 The seekg() function with two arguments Listing 10-15 shows an example that uses the two-argument version of seekg() to find a particular person object in the PERSON.DAT file already created with DISKFUN and to display the data for that particular person. Listing 10-15 SEEKG // seekg.cpp // seeks particular person in file #include <fstream.h> // class person // { protected: char name[40]; // int age; // public: void showData(void) // { cout << "\n Name: " << name; cout << "\n Age: " << age; } }; for file streams class of persons
void main(void) { person pers; // create person object ifstream infile; // create input file infile.open("PERSON.DAT", ios::binary); // open file infile.seekg(0, ios::end); // go to 0 bytes from end Page 514 of 701
int endposition = infile.tellg(); // find where we are int n = endposition / sizeof(person); // number of persons cout << "\nThere are " << n << " persons in file"; cout << "\nEnter person number: "; cin >> n; int position = (n-1) * sizeof(person); // number times size infile.seekg(position); // bytes from begin // read one person infile.read( (char*)&pers, sizeof(pers) ); pers.showData(); // display the person } Heres the output from the program, assuming that the PERSON.DAT file is the same as that just accessed in the DISKFUN example: There are 3 persons in file Enter person number: 2 Name: Rainier Age: 21 For the user, I number the items starting at 1, although the program starts numbering at 0, so person 2 is the second person of the three in the file. The first thing the program does is figure out how many persons are in the file. The first step is to position the get pointer at the end of the file with the statement infile.seekg(0, ios::end);
Quiz 4 1. If an error occurs when you use open() on an ofstream object called ofile, then a. its just an EOF, so you can always go ahead and write to the file. b. the ofile object has not been created. c. the file associated with ofile has not been opened. d. ofile will be true. e. ofile will be false. 2. If an error occurs when you use read() on an ifstream object called ifile, then a. it could be an EOF. b. you can discover the type of error by examining the value of ifile.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 515 of 701
c. you must immediately destroy the ifile object. d. ifile will be true. e. ifile will be false. 3. Which of the following statements, concerning member functions of ifstream, are true? a. The rdstate()function returns the error-status byte. b. The ferror() function reports the error status. c. The good() function returns 0 if an error occurred. d. No function exists to tell if an EOF occurred. e. No function exists to tell if the diskette door is open. 4. Which of these statements are true? a. The file pointer value handled by seekg() and similar functions is an address. b. The tellg() function tells the disk drive where to position the file pointer. c. You need to know how your data is formatted in a file to make any use of seekg() and tellg(). d. The two-argument version of seekg() can use the ios::beg constant. e. You must always start reading at the beginning of a file. 5. The seekg() member function a. can be used with one argument to position the get pointer to a specified number of bytes from the end of the file. b. can be used to read the position of the get pointer. c. can position the get pointer relative to its previous location. d. works with binary files. e. works with formatted files. Exercise 1
Rewrite the DISKFUN example from Session 3 so that every file operation is checked for errors.
Exercise 2
Write a variation of the SEEKG program in this session that allows the user to overwrite a specified record in the PERSON.DAT file with new data for a single person. You can use the seekp() member function. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 516 of 701
MidChapter Discussion
George: Well, I already know C, so Im just going to go on using printf() and scanf() and all that stuff. It works and I dont want to waste my time learning all these new member functions and this weird class structure. What about the errors you get when you use the wrong formatting character, like %f with a integer? You can blow up the program. If you use iostreams, you cant make that mistake. I dont make those mistakes any more. Of course not. But Ive got a feeling youll regret that approach, even aside from the errors. Wait until we hear about overloading the insertion and extraction operators. I can wait.
}; void person::diskIn(int pn) // read { // from ifstream infile; // make infile.open("PERSON.DAT", ios::binary); infile.seekg( pn*sizeof(person) ); infile.read( (char*)this, sizeof(*this) ); } void person::diskOut() { ofstream outfile;
person number pn file stream // open it // move file ptr // read one person
// make stream // open it outfile.open("PERSON.DAT", ios::app | ios::binary); outfile.write( (char*)this, sizeof(*this) ); // write to it }
int person::diskCount() // return number of persons { // in file ifstream infile; infile.open("PERSON.DAT", ios::binary); infile.seekg(0, ios::end); // go to 0 bytes from end // calculate number of persons return infile.tellg() / sizeof(person); } void main(void) { person p; // char ch; do // { cout << "\nEnter data for person:"; p.getData(); // p.diskOut(); // cout << "Do another (y/n)? "; cin >> ch; } while(ch=='y'); //
int n = person::diskCount(); // how many persons in file? cout << "\nThere are " << n << " persons in file"; for(int j=0; j<n; j++) // for each one, { cout << "\nPerson #" << (j+1); p.diskIn(j); // read person from disk p.showData(); // display person } } There shouldnt be too many surprises here: Youve seen most of the elements of this program before. It operates in the same way as the DISKFUN program. Notice, however, that all the details of disk operation are invisible to main(), having been hidden away in the person class. I dont know in advance where the data is that Im going to read and write because each object is in a different place in memory. However, the this pointer always tells me where I am when Im in a member function. In the read() C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 518 of 701
and write() stream functions, the address of the object to be read or written is this and its size is sizeof(*this). Heres some output, assuming there were already two persons in the file when the program was started: Enter data for person: Enter name: Acheson Enter age: 63 Enter another (y/n)? y Enter data for person: Enter name: Dulles Enter age: 72 Enter another (y/n)? n Person #1 Name: Stimson Age: 45 Person #2 Name: Hull Age: 58 Person #3 Name: Acheson Age: 63 Person #4 Name: Dulles Age: 72 If you want the user to be able to specify the file name used by the class, instead of hard-wiring it into the member functions as I do here, you could create a static member variable (say, char fileName[]) and a static function to set it. Or, you might want to associate each object with a different file using a nonstatic function.
Static Functions
One way to write many objects at once is to use a static member function, which applies to the class as a whole rather than to each object. This function can write all the objects at once. How will such a function know where all the objects are? It can access an array of pointers to the objects, which can be stored as static data. As each object is created, a pointer to it is stored in this array. A static data member also keeps track of how many objects have been created. The static write function can open the file; then, in a loop, it can go through the array, writing each object in turn; finally, it can close the file.
Suppose I have an array of pointers (call it arrap[]) to objects of type employee. These pointers can point to objects of the three derived classes. (See the VIRTPERS program in Chapter 9, Session 2 for an example of an array of pointers to objects of derived classes.) I know that if Im using virtual functions, I can make statements such as arrap[j]->putdata(); The version of the putdata() function that matches the object pointed to by the pointer will be used rather than the function in the base class. But can I also use the sizeof() function to return the size of a pointer argument? That is, can I say ouf.write( (char*)arrap[j], sizeof(*arrap[j]) ); // no good No, because sizeof() isnt a virtual function. It doesnt know that it needs to consider the type of object pointed to rather than the type of the pointer. It will always return the size of a base class object.
enum employee_type {tmanager, tscientist, tlaborer}; class employee // employee class { private: char name[LEN]; // employee name unsigned long number; // employee number static int n; // current number of employees static employee* arrap[]; // array of ptrs to emps public: virtual void getdata() { cout << "\n Enter last name: "; cin >> name; cout << " Enter number: "; cin >> number; } virtual void putdata() { cout << "\n Name: " << name; cout << "\n Number: " << number; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 520 of 701
} virtual employee_type get_type(); // get type static void add(); // add an employee static void display(); // display all employees static void read(); // read from disk file static void write(); // write to disk file static void destroy(); // delete objects from memory }; // static variables int employee::n; // current number of employees employee* employee::arrap[MAXEM]; // array of ptrs to emps // manager class class manager : public employee { private: char title[LEN]; // "vice-president" etc. double dues; // golf club dues public: void getdata() { employee::getdata(); cout << " Enter title: "; cin >> title; cout << " Enter golf club dues: "; cin >> dues; } void putdata() { employee::putdata(); cout << "\n Title: " << title; cout << "\n Golf club dues: " << dues; } }; // scientist class class scientist : public employee { private: int pubs; // number of publications public: void getdata() { employee::getdata(); cout << " Enter number of pubs: "; cin >> pubs; } void putdata() { employee::putdata(); cout << "\n Number of publications: " << pubs; } }; // laborer class class laborer : public employee { }; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
// add employee to list in memory void employee::add() { cout << "\n'm' to add a manager" "\n's' to add a scientist" "\n'l' to add a laborer" "\nType selection: "; switch( getche() ) { // create specified employee type case 'm': arrap[n] = new manager; break; case 's': arrap[n] = new scientist; break; case 'l': arrap[n] = new laborer; break; default: cout << "\nUnknown employee type"; return; } arrap[n++]->getdata(); // get employee data from user } // display all employees void employee::display() { for(int j=0; j<n; j++) { cout << '\n' << (j+1); // display number switch( arrap[j]->get_type() ) // display type { case tmanager: cout << ". Type: Manager"; break; case tscientist: cout << ". Type: Scientist"; break; case tlaborer: cout << ". Type: Laborer"; break; default: cout << ". Unknown type"; return; } arrap[j]->putdata(); // display employee data } } // return the type of this object employee_type employee::get_type() { if( typeid(*this) == typeid(manager) ) return tmanager; else if( typeid(*this)==typeid(scientist) ) return tscientist; else if( typeid(*this)==typeid(laborer) ) return tlaborer; else { cout << "\nBad employee type"; exit(1); } return tmanager; } // write all current memory objects to file void employee::write() { int size; cout << "\nWriting " << n << " employees."; ofstream ouf; // open ofstream in binary employee_type etype; // type of each employee object ouf.open("EMPLOY.DAT", ios::trunc | ios::binary); C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 522 of 701
if(!ouf) { cout << "\nCan't open file"; return; } for(int j=0; j<n; j++) // for every employee object { // get it's type etype = arrap[j]->get_type(); // write type to file ouf.write( (char*)&etype, sizeof(etype) ); switch(etype) // find its size { case tmanager: size=sizeof(manager); break; case tscientist: size=sizeof(scientist); break; case tlaborer: size=sizeof(laborer); break; } // write employee object to file ouf.write( (char*)(arrap[j]), size ); if(!ouf) { cout << "\nCan't write to file"; return; } } } // read data for all employees from file into memory void employee::read() { int size; // size of employee object employee_type etype; // type of employee ifstream inf; // open ifstream in binary inf.open("EMPLOY.DAT", ios::binary); if(!inf) { cout << "\nCan't open file"; return; } n = 0; // no employees in memory yet while(1) { // read type of next employee inf.read( (char*)&etype, sizeof(etype) ); if( inf.eof() ) // quit loop on eof break; if(!inf) // error reading type { cout << "\nCan't read type from file"; return; } switch(etype) { // make new employee case tmanager: // of correct type arrap[n] = new manager; size=sizeof(manager); break; case tscientist: arrap[n] = new scientist; size=sizeof(scientist); break; case tlaborer: arrap[n] = new laborer; size=sizeof(laborer); break; default: cout << "\nUnknown type in file"; return; } // read data from file into it inf.read( (char*)arrap[n], size ); if(!inf) // error but not eof { cout << "\nCan't read data from file"; return; } n++; // count employee } // end while C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 523 of 701
cout << "\nReading " << n << " employees"; } // delete memory allocated for employees void employee::destroy() { for(int j=0; j<n; j++) delete arrap[j]; } void main() { while(1) { cout << "\n'a' -- add data for an employee" "\n'd' -- display data for all employees" "\n'w' -- write all employee data to file" "\n'r' -- read all employee data from file" "\n'x' -- exit" "\nType selection: "; switch( getche() ) { case 'a': // add an employee to list employee::add(); break; case 'd': // display all employees employee::display(); break; case 'w': // write employees to file employee::write(); break; case 'r': // read all employees from file employee::read(); break; case 'x': // exit program employee::destroy(); return; default: cout << "\nUnknown command"; } } // end while }
aPtr_to_Obj = (aClass*)someArray; // don't do this However, an array of type char, even if it contains an objects data, is not an object. Attempts to use the array, or a pointer to the array, as if it points to an object will lead to trouble. There are only two legitimate ways to create an object. You can define it explicitly at compile time: aClass anObj; // ok or you can create it with new at runtime, and assign its location to a pointer: aPtr_to_Obj = new aClass; // ok When you create an object properly, its constructor is invoked. This is necessary even if you have not defined a constructor and are using the default constructor. An object is more than an area of memory with data in it; it is also a set of member functions, some of which, like a default constructor, you dont even see.
Enter number: 3333 'a' -- add data for an employee 'd' -- display data for all employees 'w' -- write all employee data to file 'r' -- read all employee data from file 'x' -- exit Type selection: w Writing 3 employees 'a' -- add data for an employee 'd' -- display data for all employees 'w' -- write all employee data to file 'r' -- read all employee data from file 'x' -- exit Type selection: r Reading 3 employees 'a' -- add data for an employee 'd' -- display data for all employees 'w' -- write all employee data to file 'r' -- read all employee data from file 'x' -- exit Type selection: d 1. Type: Manager Name: Johnson Title: President Golf club dues: 20000 2. Type: Scientist Name: Faraday Number: 2222 Number of publications: 99 3. Type: Laborer Name: Smith Number: 3333 Of course, you can also exit the program after writing the data to disk. When you start it up again, you can read the file back in and all the data will reappear. It would be easy to add functions to this program to delete an employee, retrieve data for a single employee from the file, search the file for employees with particular characteristics, and so forth.
Quiz 5 1. In the REWOBJ program, how does each person object identify itself so it can insert itself in the same place in memory when it is read back from the disk? a. Theres a table of pointers to all person objects so each one knows where it came from and where it should go. b. The diskIn() and diskOut() functions operate on generic person objects so these objects dont need to identify themselves. c. The data for each person object, stored in the disk file, includes the this pointer value for the object so it can be restored to the same spot in memory.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 526 of 701
d. The data for each person object, stored in the disk file, includes an index number for the object so it can be restored to the same spot in memory. e. Objects dont need to go back into the same place in memory. 2. The person objects in rewobj are arranged in the person.dat file a. by employee name. b. by employee number. c. in the same order as in the table of this pointers. d. randomly. e. in the order they were entered by the user. 3. In the EMPL_IO program, how does the class employee know what objects of the class exist? a. The class doesnt need to know anything about its objects. b. Each object contains a code number that indicates if it exists. c. The associated company class contains a table of employee objects. d. The employee class contains, in static data, a table of pointers to all existing employee objects. e. It examines its own this pointer. 4. In the EMPL_IO program, the object type is discovered by _______ when writing to the disk file and by _________ when reading from the disk file. a. values of the this pointer; array index values b. typeid(); code numbers c. code numbers; array index values d. array index values; typeid() e. All objects are of the same type so there is no need for such determination. 5. If you read the data for an object of class alpha from a disk file into an array of type char, a. you can then identify the type of the object using the typeid() function on the array. b. the section of the array occupied by the data becomes an object of class alpha. c. all will be well if you know the type of the object before accessing the array.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 527 of 701
d. you should then create an empty object of class alpha and copy the data into it. e. the compiler will signal an error. Exercise 1
Apply the technique of REWOBJ, where objects read and write themselves, to the airtime class, as seen in the ADDAIR example in Chapter 6, Session 1. That is, create diskIn() and diskOut() member functions for airtime. In main(), get data for many airtime values from the user, write them to disk, read them back in, and display them.
Exercise 2
Write a function for the EMPL_IO program that will search the file for an employee with a given employee number and display all the employees information.
} ostream& operator << (ostream& s, const English& d) { s << d.feet << "\'-" << d.inches << '\"'; return s; } // display // distance // using // overloaded // << operator
void main() { English dist1, dist2; // define English objects cout << "\nEnter two English values:"; cin >> dist1 >> dist2; // get values from user English dist3(11, 6.25); // define, initialize dist3 // display distances cout << "\ndist1 = " << dist1 << "\ndist2 = " << dist2; cout << "\ndist3 = " << dist3; } This program asks the user for two distance values and then displays these values and another value (11-6.25) that was initialized in the program. Heres some sample interaction: Enter feet: 10 Enter inches: 3.5 Enter feet: 12 Enter inches: 6 dist1 = 10'-3.5" dist2 = 12'-6" dist3 = 11'-6.25" Notice in main() how convenient and natural it is to treat English objects like any other data type, using statements such as cin >> dist1 >> dist2; and cout << "\ndist1=" << dist1 << "\ndist2=" << dist2; The operator<<() and operator>>() functions must be friends of the English class, because the istream and ostream objects appear on the left side of the operator. (See the discussion of friend functions in Chapter 9, Session 6.) They return, by reference, an object of istream (for >>) or ostream (for <<). These return values permit chaining so that more than one value can be input or output in a single statement. The operators take two arguments, both passed by reference. The first is an object of istream (for >>; often this is cin) or of ostream (for <<; often this is cout). The second argument is the object to be displayed, an object of class English in this example. The fact that the stream and the English object are passed by reference allows them to be modified by the function. The >> operator takes input from the stream specified in the first argument and copies it into the member data of the object specified by the second argument. The << operator copies the data from the object specified by the second argument and sends it into the stream specified by the first argument. You can overload the insertion and extraction operators for other classes using this same approach. Notice the const before the second argument of the overloaded << operator. It guarantees that outputting an English value wont alter it.
Listing 10-19 ENGLIO2 // englio2.cpp // overloaded << and >> operators work with files #include <fstream.h> class English // English English class { private: int feet; float inches; public: English() // constructor (no args) { feet = 0; inches = 0.0; } English(int ft, float in) // constructor (two args) { feet = ft; inches = in; } friend istream& operator >> (istream& s, English& d); friend ostream& operator << (ostream& s, const English& d); }; istream& operator >> (istream& s, English& d) // get distance { // from file or char dummy; // for ('), (-), and (") // keyboard // with s >> d.feet >> dummy >> dummy >> d.inches >> dummy; return s; // overloaded } // >> operator ostream& operator << (ostream& s, const English& d) // send { // to file or s << d.feet << "\'-" << d.inches << '\"'; // screen with return s; // overloaded } // << operator void main() { char ch; English dist1; ofstream ofile; // create and open ofile.open("DIST.DAT"); // output stream do { cout << "\nEnter English: "; cin >> dist1; // get distance from user ofile << dist1; // write it to output str cout << "Do another (y/n)? "; cin >> ch; } while(ch != 'n'); ofile.close(); // close output stream ifstream ifile; // create and open ifile.open("DIST.DAT"); // input stream cout << "\nContents of disk file is:"; while(1) { ifile >> dist1; // read dist from stream if( ifile.eof() ) // quit on EOF break; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 530 of 701
// display distance
} Ive made minimal changes to the overloaded operators themselves. The >> operator no longer prompts for input because it doesnt make sense to prompt a file. When getting input from the keyboard, I assume the user knows exactly how to enter a feet and inches value, including the various punctuation marks. (Type an integer value for feet, a quote, a hyphen, a float value for inches, and a double quote: 19-2.5.) The << operator is unchanged. The program asks for input from the user, writing each English value to the file as its obtained. When the user is finished with input, the program reads and displays all the values from the file. Heres some sample interaction: Enter Distance: 3'-4.5" Do another (y/n)? y Enter Distance: 7'-11.25" Do another (y/n)? y Enter Distance: 11'-6" Do another (y/n)? n Contents of disk file is: Distance = 3'-4.5" Distance = 7'-11.25" Distance = 11'-6" The distances are stored character by character to the file. In this example, the contents of the file would be 3'-4.5"7'-11.25"11'-6" If the user fails to enter the distances with the correct punctuation, the distances wont be written to the file correctly and the file wont be readable for the << operator. In a real program, error checking on input would be essential.
cin.ignore(10, '\n'); } void putData() // display data on screen { cout << "\n Name = " << name; cout << "\n Age = " << age; } friend istream& operator >> (istream& s, person& d); friend ostream& operator << (ostream& s, person& d); void persin(istream& s) // read file into ourself { s.read( (char*)this, sizeof(*this) ); } void persout(ostream& s) // write our data to file { s.write( (char*)this, sizeof(*this) ); } }; // istream& operator >> (istream& s, person& { d.persin(s); return s; } // ostream& operator << (ostream& s, person& { d.persout(s); return s; } get data from disk d)
void main(void) { // create 4 persons person pers1, pers2, pers3, pers4; cout << "\nPerson 1"; pers1.getData(); // get data for pers1 cout << "\nPerson 2"; pers2.getData(); // get data for pers2 // create output stream ofstream outfile("PERSON.DAT", ios::binary); outfile << pers1 << pers2; // write to file outfile.close(); // create input stream ifstream infile("PERSON.DAT", ios::binary); infile >> pers3 >> pers4; // read from file into cout << "\nPerson 3"; // pers3 and pers4 pers3.putData(); // display new objects cout << "\nPerson 4"; pers4.putData(); } I had to overcome a slight glitch in the overloaded << and >> operators. As youve seen, these operators must be friend functions because a stream object instead of a person is used as the left-hand argument. However, because Im using binary format, it is most convenient to access the person objects data directly from its location in memory, which is handled with the this pointer. Unfortunately, the this pointer is not accessible in friend C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 532 of 701
functions. So, from within the overloaded << and >> operators, I call other functions that are person member functions: persin() and persout(), which do the actual writing and reading. In main(), the program obtains data for two person objects from the user, writes to disk, reads it back into two different person objects, and displays their contents. Heres some sample interaction: Person 1 Enter name: George Harrison Enter age: 33 Person 2 Enter name: Emily Dickinson Enter age: 25 Person 3 Name = George Harrison Age = 33 Person 4 Name = Emily Dickenson Age = 25 As youve seen, chaining is possible with the overloaded operators so that a single statement in main() writes two person objects to the disk file: outfile << pers1 << pers2; and a single statement reads data from the file into two other person objects: infile >> pers3 >> pers4;
Quiz 6 1. Which of these statements are true? a. Overloading the << and >> operators means authorizing the compiler to create new versions of these operators to work with a user-written class. b. You cannot overload the << and >> operators to perform input and output with disk files. c. You could use functions to do most things overloaded operators do. d. If the user-written object was passed by value in the overloaded >> operator, no data could be placed in it. e. If the stream was passed by value in the overloaded << operator, no data could be written to it. 2. To overload the << and >> operators to work with cout and cin, you will probably a. redefine cout and cin. b. write statements within operator<<() and operator>>() to input and output basic variable types. c. make these operators member functions of a stream. d. make these operators member functions of the class whose objects you want to input and output. e. use friend functions.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 533 of 701
3. Although the overloaded >> and << operators are defined to work with ostream and istream objects, they can also work with ofstream and ifstream objects, as in the ENGLIO program, because a. ofstream and ifstream are different names for istream and ostream. b. ofstream and ifstream are derived from for istream and ostream. c. the overloaded >> and << operators work with any stream class. d. the overloaded >> and << operators are virtual functions. e. the overloaded >> and << operators are friend functions. 4. To permit multiple uses of the << or >> operators in a single statement (chaining), a. these operators must return appropriate stream objects. b. only objects instantiated from streams can be input or output. c. only basic types can be input and output. d. these operators must return an object of their own class. e. these operators must return by reference. 5. When overloading the << and >> operators to work with binary files, a. there will be runtime errors. b. the compiler will report an error. c. the read() and write() functions of istream and ostream can be used. d. you must copy the objects data into a buffer before writing it to disk. e. the data on the disk is stored in the same format as the data in the object in memory. Exercise 1
Write overloaded << and >> operators, for use with cout and cin, for the airtime class. Write a main() similar to that in ENGLIO to exercise these operators.
Exercise 2
Extend the airtime operators of Exercise 1 so they work in binary mode for files. Write a main() similar to that in PERSIO to exercise these operators.
call the GUI function with the string as an argument. (C programmers remember using the sprintf() function in similar situations.) There are, of course, many other situations where in-memory formatting is convenient.
// buffer in memory // make output memory object // data variables // for output to memory
<< endl // output data to << endl // memory object ostr1 << endl ostr2 << endl // end the buffer with '\0' // display the memory buffer // new variables for input
cout << membuff; char dummy[20]; int ij; double id; char istr1[20]; char istr2[20]; istrstream imem(membuff, SIZE);
// make input memory object // extract data imem >> dummy >> ij >> dummy >> id // into new variables >> dummy >> istr1>> dummy >> istr2; // display variables cout << "\nij=" << ij << "\nid=" << id << "\nistr1=" << istr1 << "\nistr2=" << istr2; }
j= 77\nd= 890.12\nstr1= Kafka\nstr2= Freud\n\0 You can format the text using manipulators, just as you do for cout. The manipulator ends inserts a \0 character at the end of the string. (Dont forget this manipulator; the string must be zero terminated, and this doesnt happen automatically.) The program now displays the contents of the buffer as a string of type char: cout << membuff; Heres the resulting output: oj= 77 od= 890.12 ostr1= Kafka ostr2= Freud In this example, the program displays the contents of the buffer only to show what the buffer looks like. Ordinarily, you would have a more sophisticated use for this formatted text, such as passing the address of the buffer to a GUI function for display in a dialog box.
Universality
To focus attention on the techniques of using in-memory formatting, I have shown standalone main() programs in the examples in this session. If youre writing an object-oriented program, you will probably want to place the routines that handle in-memory I/O in member functions so that objects can read and write themselves to memory (see the REWOBJ example in Session 5 in this chapter). If you handle things correctly when you do this, you can use the same member functions to read and write objects to memory that you use to read and write them to disk files. Overload the << and >> operators to work with disk files. Then let the name of the file object (whether istream or istrstream, or ostream or ostrstream) be passed as an argument to the function, rather than hard-wired into the function, as I show in REWOBJ.
File Pointers
Incidentally, you may be interested to know that you can use file pointers, such as those handled with seekg() and tellp(), with in-memory objects such as istrstream and ostrstream, just as you can with file objects.
defined, fill it with data and it will expand to hold what you put in. As you continue to add data, the object will continue to expand. Listing 10-22 shows AUTOSTR. Listing 10-22 AUTOSTR // autostr.cpp // writes formatted data into memory; uses dynamic ostrstream #include <strstrea.h> // for ostrstream class void main() { ostrstream omem;
// create dynamic stream object // insert numbers // into ostrstream object // terminate with '\0'
omem << "j=" << 111 << endl << "k=" << 2.3456 << endl; omem << ends;
char* p = omem.str(); // get address of buffer cout << p; // display it } I create a dynamic ostrstream object (note the lack of arguments) and put data in it: the integer 111 and the floating-point value 2.3456. To display its contents, I use the str() member function to return a pointer (type char*) to the section of memory containing the data: char* p = omem.str(); This pointer can then be sent on to another function to display the data or access it for other purposes. In the program, I insert the pointer in cout: cout << p; The output looks like this: j=111 k=2.3456 Typically, the pointer value is sent to a GUI function to display the data. This use of the dynamic ostrstream object works nicely, relieving you of the work of setting up a buffer of your own and worrying about how big to make it. If you use the approach shownputting data into the object using a single statement, then getting the value of a pointer to the data, and never changing the contents or accessing the data againthen everything works as expected. However, going much beyond this approach can lead you quickly into a realm akin to black magic. Why is this? Remember that dynamic ostrstream objects, rather than relying on a user-supplied buffer, must dynamically allocate memory to store their data. When you put data in them, they find memory for just that amount of data. If you then add more data, they may need to move the data to a different place in memory to accommodate the increased buffer size. But if youve already found out where the data is using str(), the object can no longer move the data. It therefore freezes itself. Once its frozen, you cant add more data. Also, you are responsible for releasing its memory yourself, using delete. All things considered, its probably not worth the hassle to go beyond the simple approach shown here.
Quiz 7 1. In-memory formatting means a. rearranging your computers physical memory. b. rearranging the data in memory to eliminate unused spaces. c. storing (or retrieving) numerical and other data as a string in memory. d. creating an object of type ostream.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 537 of 701
e. creating an object of type strstream. 2. A regular (nondynamic) ostrstream object a. is used to store data in a disk file. b. is told by the code that creates it how much data it can hold. c. can store int and float values in the same format as memory variables. d. must be associated with a data buffer located elsewhere in the program. e. creates its own data buffer. 3. An istrstream object a. allocates its own data buffer. b. writes data to either a disk file or the screen display. c. reads data from either a disk file or the keyboard. d. takes formatted data from memory and stores it in variables. e. takes data from variables and stores it as formatted data in memory. 4. A dynamic ostrstream object a. is called dynamic because it destroys its data automatically when it goes out of scope. b. uses the member function str() to store data. c. allocates its own memory buffer for the data stored in it. d. cannot be deleted. e. must be given a limit on buffer size. 5. Unless you really know what youre doing, you shouldnt use multiple data inputs and outputs with dynamic ostrstream objects because a. actually, this is OK. b. finding out where the data is stored freezes it. c. an object might move its data after you find out where it is. d. all pointers are relative. e. dynamic data is changing too fast to read.
Exercise 1
Adapt the REWOBJ example from Session 5 of this chapter so the person class works with in-memory formatting as well as with disk files. As noted earlier, this is conveniently done by overloading the << and >> operators and specifying the type of object to be read or written to as an argument passed to the member functions.
Exercise 2
Start with Exercise 1 of Session 5 in this chapter, which added diskIn() and diskOut() member functions to the airtime class. Rewrite this example so it works with in-memory formatting as well as with disk files, using a technique similar to that in Exercise 1.
Command-Line Arguments
If you use DOS programs, you are probably familiar with command-line arguments, used when invoking a program from the DOS C prompt. (Actually, you can also use command-line arguments when you invoke a program using the Windows Run command. However, they are not commonly used in Windows.) Command-line arguments are typically used to pass the name of a data file to an application at the same time you start the application. For example, you can invoke a word processor application and the document it will work on at the same time: C>wordproc afile.doc Here, afile.doc is a command-line argument. How can you get a C++ program to read command-line arguments? Listing 10-23 shows an example, COMLINE, that reads and displays as many command-line arguments as you care to type. Multiple arguments are separated by spaces. Listing 10-23 COMLINE // comline.cpp // demonstrates command-line arguments #include <iostream.h> void main(int argc, char* argv[] ) { cout << "\nargc = " << argc; for(int j=0; j<argc; j++) cout << "\nArgument " << j << " = " << argv[j]; } Heres some sample interaction with the program where the user types three command-line arguments: C>comline uno dos tres argc = 4 Argument 0 = C:\CPP\CHAP14\COMLINE.EXE Argument 1 = uno Argument 2 = dos Argument 3 = tres To read command-line arguments, the main() function (dont forget its a function!) must itself be given two arguments. The first, argc (for argument count), represents the total number of command-line arguments. The first command-line argument is always the path name of the current program. The remaining command-line arguments are those typed by the user; they are delimited by the space character. In the example above, they are uno, dos, and tres. The system stores the command-line arguments as strings in memory and creates an array of pointers to these strings. In the example, the array is called argv (for argument values). Individual strings are accessed through the appropriate pointer, so the first string (the path name) is argv[0], the second (uno in this example) is C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 539 of 701
argv[1], and so on. comline accesses the arguments in turn and prints them out in a for loop that uses argc, the number of command-line arguments, as its upper limit. You dont actually need to use the particular names argc and argv as arguments to main(), but they are so common that any other names would cause consternation to everyone but the compiler. Listing 10-24 shows a program that uses a command-line argument for something useful. It displays the contents of a text file whose name is supplied by the user on the command line. Thus it imitates the DOS command Type. Listing 10-24 OTYPE // otype.cpp // imitates TYPE command #include <fstream.h> // for file functions #include <process.h> // for exit() void main(int argc, char* argv[] ) { if( argc != 2 ) { cerr << "\nFormat: otype filename"; exit(-1); } char ch; // character to read ifstream infile; // create file for input infile.open( argv[1] ); // open file if( !infile ) // check for errors { cerr << "\nCan't open " << argv[1]; exit(-1); } while( infile.get(ch) != 0 ) // read a character cout << ch; // display the character } This program first checks to see if the user has entered the correct number of command-line arguments. Remember that the path name of OTYPE.EXE itself is always the first command-line argument. The second argument is the name of the file to be displayed, which the user should have entered when invoking the program: C>otype ichar.cpp Thus, the total number of command-line arguments should equal two. If it doesnt, the user probably doesnt understand how to use the program, and the program sends an error message via cerr to clarify matters. If the number of arguments is correct, the program tries to open the file whose name is the second command-line argument (argv[1]). If the file cant be opened, the program signals an error. Finally, in a while loop, the program reads the file character by character and writes it to the screen. A value of 0 for the character signals an EOF. This is another way to check for EOF. You can also use the value of the file object itself, as Ive done before: while( infile ) { infile.get(ch); cout << ch; } You could also replace this entire while loop with the statement cout << infile.rdbuf(); as you saw in the ICHAR2 program in Session 3 in this chapter.
Printer Output
In DOS, its fairly easy to send data to the printer. DOS predefines a number of special file names for hardware devices. These make it possible to treat the devices as if they were files. Table 10-12 shows these names. Table 10-12 Predefined hardware file names C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 540 of 701
Name |Device con |Console (keyboard and screen) aux or com1 |First serial port com2| Second serial port prn or lpt1 |First parallel printer lpt2 |Second parallel printer lpt3 |Third parallel printer nul |Dummy (nonexistent) device
In most systems, the printer is connected to the first parallel port, so the file name for the printer is PRN or LPT1. (You can substitute the appropriate name if your system is configured differently.) The following program, EZPRINT (Listing 10-25), sends a string and a number to the printer, using formatted output with the insertion operator. Listing 10-25 EZPRINT // ezprint.cpp // demonstrates simple output to printer #include <fstream.h> // for file streams
void main(void) { char* s1 = "\nToday's winning number is "; int n1 = 17982; ofstream outfile; // make a file outfile.open("PRN"); // open it for the printer outfile << s1 << n1 << endl; // send data to printer outfile << '\x0D'; // formfeed for page eject } You can send any amount of formatted output to the printer this way. The \x0D character causes the page to eject from most printers. This technique works only for DOS programs, so you must use a DOS target when you create EZPRINT (not an EasyWin or similar Windows-related target). Sending output to the printer from a Windows program is beyond the scope of this book. The next example, OPRINT (Listing 10-26), prints the contents of a disk file, specified on the command line, to the printer. It uses the character-by-character approach to this data transfer. Listing 10-26 OPRINT // oprint.cpp // imitates print command #include <fstream.h> // for file functions #include <process.h> // for exit() void main(int argc, char* argv[] ) { if( argc != 2 ) { cerr << "\nFormat: oprint filename"; exit(-1); } char ch; // character to read ifstream infile; // create file for input infile.open( argv[1] ); // open file if( !infile ) // check for errors C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
{ cerr << "\nCan't open " << argv[1]; exit(-1); } ofstream outfile; // make file outfile.open("PRN"); // open it for printer while( infile.get(ch) != 0 ) // read a character outfile.put(ch); // write character to printer } You can use this program to print any text file, such as any of your .CPP source files. It acts much the same as the DOS Print command. Like the OTYPE example, this program checks for the correct number of command-line arguments and for a successful opening of the specified file.
Redirection
Note: The programs in this section should be executed from a DOS box in Windows or from DOS itself. You need to see a DOS prompt when invoking the program so you can enter additional information besides the program name. In DOS, its possible to read and write to files using only the objects cout and cin. These predefined objects normally represent the display and the keyboard, but they can be redirected by the user to represent disk files. Redirection is a technique, originally imported from UNIX into DOS, that allows the user additional flexibility in the way programs are used. Redirection is supplied by DOS, not by C++, but C++ supports it, so its interesting to see how to use it. As an example, consider the program redir (Listing 10-27). Listing 10-27 REDIR // redir.cpp // demonstrates redirection // syntax: redir <source >destination #include <iostream.h> #include <iomanip.h> // for resetiosflags() void main() { char ch; while( !cin.eof() ) // quit on EOF { cin >> resetiosflags(ios::skipws) // keep whitespace >> ch; // read from std input cout << ch; // send to std output } } Before exploring how this program can be used for redirection, I should note that you must reset the skipws flag when getting input from cin. This is necessary because cin normally skips over whitespace, which includes spaces, new lines, and EOFs. We want to read all these characters.
C>redir Truth is the trial of itself, <--entered by the user Truth is the trial of itself, <--echoed by the program And needs no other touch; And needs no other touch; And purer than the purest gold, And purer than the purest gold, Refine it ne'er so much. Refine it ne'er so much. ^Z
The user enters the key combination (or the function key) to terminate the program. This character is interpreted by the program as an EOF. Using redirection gives the program additional capabilities. It can take input from the keyboard and write it to a disk file, display text from a disk file on the screen, or copy one file to another.
Redirecting Output
To redirect a programs output from the screen to a file, use the > operator. This is a DOS operator; it has nothing to do with C++ comparison or overloading. Heres how to invoke redir to take keyboard input and redirect it to a file called sample.txt: C>redir >sample.txt If you would avoid suspicion, <--entered by user don't lace your shoes in a melon field. <--entered by user ^Z <--entered by user The text you type will be written to sample.txt. You can check this file with the DOS command Type to be sure it contains this text.
Redirecting Input
To redirect a programs input so it comes from a file and not the keyboard, use the < operator. Heres how to invoke redir to take data from the file sample.txt and use it as input to the program, which then displays it: C>redir <sample.txt If you would avoid suspicion, <--displayed by program don't lace your shoes in a melon field. <--displayed by program
Quiz 8 1. Command-line arguments a. are separated by whitespace characters. b. are separated by the space character. c. cannot be accessed from an application. d. take place between generals and admirals. e. are stored as strings in memory. 2. For command-line arguments, argc is _______ and argv is ___________. a. the number of arguments typed by the user; a pointer to an array of pointers b. 1 plus the number of arguments typed by the user; a pointer to an array c. what the user types; what the operating system supplies d. always 2; always a file name e. the name of the application; a list of arguments 3. In C++, in the DOS environment, a printer a. can be treated like a write-only file with a specific name. b. needs to be opened like any other file. c. can easily receive and print graphics images. d. is usually connected to COM or LPT1. e. cannot be told to eject a page. 4. Using DOS redirection a. you can redirect program output that would normally go to the printer to a disk file. b. you can redirect program output that would normally go to the screen to a disk file.
c. you can redirect program output that would normally go to a program to a formatted memory buffer. d. you can redirect a file that would normally go to a program to the screen. e. seldom occurs in Windows. 5. For the program prog to read data from file1.doc and write it to file2.doc, you would write a. prog <file1.doc >file2.doc b. file1.doc >prog >file2.doc c. file1.doc <prog <file2.doc d. file2.doc >prog >file1.doc e. file2.doc <prog <file1.doc Exercise 1
Write a member function that performs printer output for the airtime class.
Exercise 2
Revise the persio program in Session 6 so that the name of the file that person objects are written to or read from is supplied by the user on the command line when the program is first invoked, rather than always being person.dat.
Summary: Chapter 10
In this chapter Ive covered a variety of topics connected with input/output in C++. I started with an overview of the stream class hierarchy and then looked at how errors are handled in streams. You saw how disk file I/O is performed using the stream classes, how file pointers determine where data is accessed within a file, and how file errors are handled. Next I showed how member functions can incorporate file-handling capabilities into user-written classes. I showed how to overload the << and >> operators to simplify I/O for class objects and how a section of memory can be treated as a disk file to simplify formatting. Finally, I covered three DOS-related capabilities: Redirection allows program output to be redirected from the screen to a file or input to be redirected from the keyboard to a file; printer output can be handled easily in DOS by treating the printer as a file with a special name; and, command-line arguments allow the user to specify information, such as a file to be opened, when a program is invoked.
End-of-Chapter Discussion
Estelle: George: Don: Estelle: Don: George: Estelle: George: This whole chapter doesnt seem relevant to OOP. Its just about files in old-fashioned procedural situations. Well, thank heavens! I dont mind a break from OOP. But its pretty obvious how to apply all this file-related stuff to OOP. You mean just put file-handling routines in member functions? Exactly. If an object needs to write itself to a file or read its data from a file, you just create a member function to do it. Then you can call that function from main() or wherever. Anyway, iostreams are C++ classes. Thats already pretty OOP. Well, thats true, but Im just using the iostream classes. Im not writing them. Thats all right with me. Page 545 of 701
Estelle: George: Estelle: George: Don: George: Don: Estelle: Don: George: Estelle:
It does show how little you need to know about OOP to be a class user, as opposed to a class creator. Thats one of the strengths of OOP: Once a class is written, its easy to use. WOUMT. I beg your pardon? Its an acronym. Write Once, Use Many Times. Isnt that how classes are supposed to work? Thats good, George. I like it. What bothers me is that I still dont know how to do I/O in Windows. They dont teach that in this book. Its complicated. The point here is just to learn the fundamentals of OOP, thats why we use such simple I/O. Good luck if you want to learn Windows and C++ at the same time. You mean I dont know everything there is to know already? Im going to drop this programming stuff and go to barber school. Come on, George. Nothing worth doing is ever easy.
cout << "\nabs(" << dub2 << ")=" << abs(dub2); // abs(double) } Heres the output of the program: abs(5)=5 abs(-6)=6 abs(70000)=70000 abs(-80000)=80000 abs(9.95)=9.95 abs(-10.15)=10.15 As you can see, the abs() function now works with all three of the data types (int, long, and double) that I use as arguments. It will work on other basic types as well, and it will even work on user-defined data types, provided the less-than operator (<) and the unary minus (-) are appropriately overloaded. Heres how to specify the abs() function so it works with multiple data types: template <class T> // function template T abs(T n) { return (n<0) ? -n : n; } This entire syntax, with a first line starting with the keyword template and the function definition following, is called a function template. How does this new way of writing abs() give it such amazing flexibility?
double. Of course, the compiler is smart enough to generate only one version of abs() for each data type. Thus, even though there are two calls to the int version of the function, the code for this version appears only once in the executable code. Notice that the amount of RAM used by the program is the same whether I use the template approach or write three separate functions. What Ive saved is having to type three separate functions into the source file. This makes the listing shorter and easier to understand. Also, if I want to change the way the function works, I need make the change in only one place in the listing instead of three. The compiler decides how to compile the function based entirely on the data type used in the function calls argument (or arguments). The functions return type doesnt enter into this decision. This is similar to the way the compiler decides which of several overloaded functions to call. A function template isnt really a function, because it does not actually cause program code to be placed in memory. Instead it is a pattern, or blueprint, for making many functions. This fits right into the philosophy of OOP. Its similar to the way a class isnt anything concrete (such as program code in memory), but a blueprint for making many similar objects.
in in in in
Here I name the template argument atype. It appears in two of the functions arguments: as the type of a pointer to the array and as the type of the item to be matched. The third function argument, the array size, is always type int; its not a template argument. Heres the output of the program: 5 in chrArray: index=2 6 in intArray: index=-1 11 in lonArray: index=4 4 in dubArray: index=-1 The compiler generates four versions of the function, one for each type used to call it. It finds a 5 at index 2 in the character array it does not find a 6 in the integer array, and so on. I should note that some programmers put the template keyword and the function declarator on the same line: template<class atype> int find(atype* array, atype value, int size) { // function body } Of course, the compiler is happy enough with this format, but I find it more forbidding and less clear than the multiline approach.
What Works?
How do you know whether you can instantiate a template function for a particular data type? For example, could you use the find() function from TEMPFIND to find a string (type char*) in an array of strings? To see if this is possible, check the operators used in the function. If they all work on the data type, then you can probably use it. The find() function, however, compares two variables using the equal-equal (==) operator. You cant use this operator with strings; you must use the strcmp() library function. Thus, find() wont work on char* strings. (However, it would work on a user-defined string class in which you overloaded the == operator.)
Quiz 1 1. Template functions allow a. one function stored in memory to handle multiple data types. b. one function in the source file to handle multiple data types. c. user-defined types to be treated as ordinary types. d. user-defined types to be used as arguments to functions. e. one name to represent many data types within a functions definition in the source code. 2. Concerning this code:
template <class X> X max(X n1, X n2) { return (n1 > n2) ? n1 : n2; }
which (if any) of the following statements are correct? a. n1 and n2 are data types.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 552 of 701
b. The keyword class means that youre defining a new class. c. X represents a data type. d. The keyword template means that you can use any numerical value for X. e. The longest data type name will be returned. 3. If in your program you make the following four calls to the template function abs() (as it was declared in the TEMPABS example), how many instantiations of this function will be placed in memory?
abs(intvar1); abs(intvar2); abs(floatvar1); abs(floatvar2);
a. 1 b. 2 c. 3 d. 4 e. none 4. A function template ___________, whereas a template function ___________ . a. does not use a specific type, does use a specific type b. does use a specific type, does not use a specific type c. does not exist in memory, exists in memory d. exists in memory, does not exist in memory e. cannot be called, can be called 5. To instantiate a template function tempfunc<>() using a particular user-defined data type (class) TYPE as a template argument, TYPE must a. overload the assignment operator (=). b. overload the less-than operator (<). c. contain the same data variables as those used in tempfunc<>(). d. supply the same member functions as tempfunc<>(). e. supply a member function to handle any operation used in tempfunc<>().
Exercise 1
Write a template function that returns the largest of three numbers (of the same type) passed to it as arguments.
Exercise 2
Write a template function that searches through an array of any type, counting how many elements have a specific value.
// size of array
// stack: array of any type // number of top of stack // constructor Page 554 of 701
void { Type { };
push(Type var) // put number on stack st[++top] = var; } pop() // take number off stack return st[top--]; }
s1.push(1111.1); // push 3 floats, pop 3 floats s1.push(2222.2); s1.push(3333.3); cout << "1: " << s1.pop() << endl; cout << "2: " << s1.pop() << endl; cout << "3: " << s1.pop() << endl; Stack<long> s2; // s2 is object of class Stack<long>
s2.push(123123123L); // push 3 longs, pop 3 longs s2.push(234234234L); s2.push(345345345L); cout << "1: " << s2.pop() << endl; cout << "2: " << s2.pop() << endl; cout << "3: " << s2.pop() << endl; } Here the the class Stack is presented as a template class. The approach is similar to that used in function templates. The template keyword signals that the entire class will be a template: template <class Type> class Stack { // data and member functions using template argument 'Type' }; A template argument, named Type in this example, is then used (instead of a fixed data type such as int) every place in the class specification where there is a reference to the type of the array st. There are three of these: the definition of st, the argument type of the push() function, and the return type of the pop() function. Class templates differ from function templates in the way they are instantiated. To create an actual function from a function template, call it using arguments of a specific type. Classes, however, are instantiated by defining an object using the template argument: Stack<float> s1; This creates an object, s1, that is a stack that stores numbers of type float. The compiler provides space in memory for this objects data, using type float wherever the template argument Type appears in the class specification. It also provides space for the member functions (if these have not already been placed in memory by another object of type Stack<float>). These member functions also operate exclusively on type float. Figure 11-2 shows how a class template and definitions of specific objects cause these objects to be placed in memory.
Figure 11-2 A class template Creating a Stack object that stores objects of a different type, as in Stack<long> s2; creates not only a different space for data, but also a new set of member functions that operate on type long. Note that the name of the type of s1 consists of the class name Stack plus the template argument: Stack<float>. This distinguishes it from other classes that might be created from the same template, such as Stack<int> or Stack<long>. The program exercises the s1 and s2 stacks by pushing and popping three values on each one and displaying each popped value. Heres the output of TEMPSTAK: 1: 3333.3 // float stack 2: 2222.2 3: 1111.1 1: 345345345 // long stack 2: 234234234 3: 123123123 In this example, the template approach gives me two classes for the price of one. I could instantiate class objects for other numerical types with just a single line of code.
// implements stack class as a template // member functions are defined outside the class #include <iostream.h> const int MAX = 100; template <class Type> class Stack { private: Type st[MAX]; int top; public: Stack(); void push(Type var); Type pop(); };
// stack: array of any type // number of top of stack // constructor // put number on stack // take number off stack
template<class Type> Stack<Type>::Stack() // constructor { top = -1; } template<class Type> void Stack<Type>::push(Type var) // put number on stack { st[++top] = var; } template<class Type> Type Stack<Type>::pop() { return st[top???]; } void main() { Stack<float> s1;
s1.push(1111.1); // push 3 floats, pop 3 floats s1.push(2222.2); s1.push(3333.3); cout << 1: << s1.pop() << endl; cout << 2: << s1.pop() << endl; cout << 3: << s1.pop() << endl; Stack<long> s2; // s2 is object of class Stack<long> push 3 longs, pop 3 longs
s2.push(123123123L); // s2.push(234234234L); s2.push(345345345L); cout << 1: << s2.pop() cout << 2: << s2.pop() cout << 3: << s2.pop() }
The expression template<class Type> must precede not only the class definition, but each externally defined member function as well. Heres how the push() function looks: template<class Type> void Stack<Type>::push(Type var) { st[++top] = var; } The name Stack<Type> has been used to identify the class of which push() is a member. In a normal nontemplate member function, the name Stack alone would suffice: void Stack::push(int var) // Stack() as a non-template function { st[++top] = var; } However, for a function template, you need the template argument as well: Stack<Type>. Thus you see that the name of the template class is expressed differently in different contexts. Within the class specification, its simply the name itself: Stack. For externally defined member functions, its the class name plus the template argument name: Stack<Type>. When you define actual objects for storing a specific data type, its the class name plus this specific type: Stack<float> (or whatever). class Stack // Stack class specifier { }; void Stack<Type>::push(Type var) { } // push() definition
Stack<float> s1; // object of type Stack<float> You must exercise considerable care to use the correct name in the correct context. Its easy to forget to add the <Type> or <float> to the Stack. The compiler hates it when you get this wrong. Although its not demonstrated in this example, the syntax when a member function returns a value of its own class can create some problems for you. Suppose you define a class Int that provides some augmentation for the int class, such as checking for overflow. Suppose further that you make this a template class so it can be instantiated using either type int or type long. If you use an external definition for a member function xfunc() of this class that returns type Int, you will need to use Int<Type> for the return type as well as preceding the scope resolution operator: Int<Type> Int<Type>::xfunc(Int arg) { } The class name used as a type of a function argument, on the other hand, doesnt need to include the <Type> designation.
template<class TYPE> // class linklist<TYPE> class linklist // a list of links // within this class definition 'linklist' means linklist<TYPE> { private: link<TYPE>* first; // pointer to first link public: linklist() // no-argument constructor { first = NULL; } // no first link void additem(TYPE d); // add data item (one link) void display(); // display all links ~linklist(); // destructor }; template<class TYPE> void linklist<TYPE>::additem(TYPE d) // { link<TYPE>* newlink = new link<TYPE>; newlink->data = d; // newlink->next = first; // first = newlink; // } template<class TYPE> void linklist<TYPE>::display() { link<TYPE>* current = first; while( current != NULL ) { cout << endl << current->data; current = current->next; } } template<class TYPE> linklist<TYPE>::~linklist() { while( first != NULL ) { link<TYPE>* temp = first; first = temp->next; delete temp; } }
add data item // make a new link give it data it points to next link now first points to this
// display all links // set ptr to first link // quit on last link // print data // move to next link
// destructor // deletes all links // quit on last link // 'temp' to current link // 'first' to next link // delete current link
void main() { linklist<double> ld; // ld is object of class linklist<double> ld.additem(151.5); // add three doubles to list ld ld.additem(262.6); ld.additem(373.7); C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 559 of 701
lch.additem('a'); // add three chars to list lch lch.additem('b'); lch.additem('c'); lch.display(); // display entire list lch } In main(), the program defines two linked lists: one to hold numbers of type double and one to hold characters of type char. It then exercises the lists by placing three items on each one with the additem() member function, and displaying all the items with the display() member function. Heres the output of TEMPLIST: 373.7 262.6 151.5 c b a Both the linklist class and the link structure make use of the template argument TYPE to stand for any type. (Well, not really any type; Ill discuss later what types can actually be stored.) Thus, not only linklist but also link must be templates, preceded by the line template<class TYPE> Notice that its not just a class thats turned into a template. Any other programming constructs that use a variable data type must also be turned into templates, as the link structure is here. As before, you must pay attention to how the class (and, in this program, a structure as well) is named in different parts of the program. Within its own specification, you can use the name of the class or structure alone: linklist and link. In external member functions, you must use the class or structure name and the template argument: linklist<TYPE>. When you actually define objects of type linklist, you must use the specific data type that the list is to store: linklist<double> ld; // defines object ld of class linklist<double>
Listing 11-6 TEMLIST2 // temlist2.cpp // implements linked list as a template // demonstrates list used with employee class #include <iostream.h> //////////////////////////////////////////////////////////////// // the employee class //////////////////////////////////////////////////////////////// const int LEN = 80; // maximum length of names class employee // employee class { private: char name[LEN]; // employee name unsigned long number; // employee number public: friend istream& operator >> (istream& s, employee& e); friend ostream& operator << (ostream& s, employee& e); }; istream& operator >> (istream& s, employee& e) { cout << \n Enter last name: ; cin >> e.name; cout << Enter number: ; cin >> e.number; return s; } ostream& operator << (ostream& s, employee& e) { cout << \n Name: << e.name; cout << \n Number: << e.number; return s; } //////////////////////////////////////////////////////////////// // the linked list template //////////////////////////////////////////////////////////////// template<class TYPE> // struct link<TYPE>" struct link // one element of list { TYPE data; // data item link* next; // pointer to next link }; template<class TYPE> // class linklist<TYPE>" class linklist // a list of links { private: link<TYPE>* first; // pointer to first link public: linklist() // no-argument constructor { first = NULL; } // no first link void additem(TYPE d); // add data item (one link) void display(); // display all links ~linklist(); // destructor C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 561 of 701
}; template<class TYPE> void linklist<TYPE>::additem(TYPE d) { link<TYPE>* newlink = new link<TYPE>; newlink->data = d; newlink->next = first; first = newlink; } template<class TYPE> void linklist<TYPE>::display() { link<TYPE>* current = first; while( current != NULL ) { cout << endl << current->data; current = current->next; } }
// add data item // // // // make a new link give it data it points to next link now first points to this
// display all links // set ptr to first link // quit on last link // display data // move to next link
template<class TYPE> // destructor linklist<TYPE>::~linklist() // deletes all links { while( first != NULL ) // quit on last link { link<TYPE>* temp = first; // 'temp' to current link first = temp->next; // 'first' to next link delete temp; // delete current link } } //////////////////////////////////////////////////////////////// // main() creates a linked list of employees //////////////////////////////////////////////////////////////// void main() {// lemp is object of linklist<employee> lemp; // class linklist<employee>" employee emptemp; // temporary employee storage char ans; // user's response (`y' or `n') do { cin >> emptemp; // get employee data from user lemp.additem(emptemp); // add it to linked list `lemp' cout << \nAdd another (y/n)? ; cin >> ans; } while(ans != `n'); // when user is done, lemp.display(); // display entire linked list } In main(), the program instantiates a linked list called lemp. Then, in a loop, it asks the user to input data for an employee, and adds that employee object to the list. When the user terminates the loop, the program displays all the employee data. Heres some sample interaction: Enter last name: Mendez Enter number: 1233 Add another(y/n)? y C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 562 of 701
Enter last name: Smith Enter number: 2344 Add another(y/n)? y Enter last name: Chang Enter number: 3455 Add another(y/n)? n Name: Chang Number: 3455 Name: Smith Number: 2344 Name: Mendez Number: 1233 Notice that the linklist class does not need to be modified in any way to store objects of type employee. This is the beauty of template classes: They will work not only with basic types, but with user-defined types as well.
Quiz 2 1. A template class clatem allows a. a member function of clatem to have a variable number of arguments. b. clatem to use data types that arent known until compile time. c. clatem to use data types that arent known until runtime. d. data types within clatem to be determined when objects of that class are specified. e. objects of clatem to act as if they were of several different data types. 2. Typically, an object instantiated from a templatized container class temclass<T> a. stores objects of a single type. b. stores objects of different types. c. stores objects (not pointers) that can be of different types, provided these types are all derived from a single type. d. shares in-memory member functions with all other objects of temclass<T>.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 563 of 701
e. shares in-memory member functions with all other objects of (say) temclass<float>. 3. Assuming a specification for a template class temclass<T>, memory space is allocated a. for member data and functions when any object of class temclass<T> is defined. b. for member data when any object of class temclass<float> is defined. c. for member data when the first object of class temclass<float> is defined. d. for member functions when any object of class temclass<float> is defined. e. for member functions when the first object of class temclass<float> is defined. 4. If the specification for a templatized container class has the form
template <class Type> class ConClass {...};
then the type name of objects of this container class might be a. ConClass in the declarator of an externally defined member function of this class. b. ConClass<Type> in the declarator of an externally defined member function. c. ConClass<float> in an application that uses the container. d. ConClass<float> in the declarator of an inline member function. e. ConClass<Type> in the declarator of an inline member function. 5. When you use a templatized container class CON to store objects of a user-defined class UDC, a. there must be member functions in UDC to carry out any operation demanded by CON of the objects stored in it. b. class CON must overload the assignment (=) operator. c. class UDC must overload the assignment (=) operator. d. class CON must overload the << operator. e. class UDC must overload the << operator. Exercise 1
Modify the TEMPSTAK program from this session so the Stack class stores objects of type employee.
Exercise 2
Write a program that stores airtime values in a container of type linklist<TYPE>, as seen in the TEMLIST2 example in this session.
Session 3: Exceptions
Exceptions provide a systematic, object-oriented approach to handling runtime errors generated by C++ classes. To qualify as an exception, such errors must occur as a result of some action taken within a program and they must be ones the program itself can discover. For example, a constructor in a user-written string class might generate an exception if the application tries to initialize an object with a string thats too long. Similarly, a program can check if a file was opened or written too successfully and generate an exception if it was not.
Not all runtime errors can be handled by the exception mechanism. For instance, some error situations are not detected by the program but by the operating system, which may then terminate the application. Examples are stack overflow, the user pressing the key combination, or a hardware divide-by-zero error.
The exception mechanism was designed to minimize these difficulties and to provide a consistent, easy-toimplement approach to error handling, one that supports the concepts of OOP. All errors can be handled in one place and the normal code is not interlaced with error-handling statements.
Exception Syntax
Imagine an application that creates and interacts with objects of a certain class. Ordinarily, the applications calls to the class member functions cause no problems. Sometimes, however, the application makes a mistake, causing an error to be detected in a member function. This member function then informs the application that an error has occurred. When exceptions are used, this is called throwing an exception. In the application, a separate section of code is installed to handle the error. This code is called an exception handler or catch block: it catches the exceptions thrown by the member function. Any code in the application that uses objects of the class is enclosed in a try block. Errors generated in the try block will be caught in the catch block. Code that doesnt interact with the class need not be in a try block.. Figure 11-3 shows the arrangement.
Figure 11-3 The exception mechanism The exception mechanism uses three new C++ keywords: throw, catch, and try. Also, the class creator will probably want to create a new kind of entity called an exception class. The next program, XSYNTAX (Listing 117), demonstrates these features of the exception mechanism. It is not a working program, only a skeleton to show the syntax. Listing 11-7 XSYNTAX // xsyntax.cpp // not a working program class AClass // a class { public: class AnError // exception class { }; void Func() // a member function { if( /* error condition */ ) throw AnError(); // throw exception } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 566 of 701
}; void main() // application { try // try block { AClass obj1; // interact with AClass objects obj1.Func(); // may cause error } catch(AClass::AnError) // exception handler { // (catch block) // tell user about error, etc. } } This code starts with a class called AClass, which represents any class in which errors might occur. An exception class, AnError, is specified in the public part of AClass. The member functions of AClass check for errors. If they find one, they throw an exception, using the keyword throw followed by the constructor for the error class: throw AnError(); // 'throw' followed by constructor for AnError class In the main() part of the program, I enclose any statements that interact with AClass in a try block. If any of these statements causes an error to be detected in an AClass member function, an exception will be thrown and control will go to the catch block that immediately follows the try block.
// array of integers // index of top of stack // exception class for Stack // note: empty class body // constructor
void push(int var) { if(top >= MAX-1) // if stack full, throw Range(); // throw exception st[++top] = var; // put number on stack } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 567 of 701
int pop() { if(top < 0) // if stack empty, throw Range(); // throw exception return st[top]; // take number off stack } }; void main() { Stack s1; try { s1.push(11); s1.push(22); s1.push(33); // s1.push(44); // oops: stack full cout << "1: " << s1.pop() << endl; cout << "2: " << s1.pop() << endl; cout << "3: " << s1.pop() << endl; cout << "4: " << s1.pop() << endl; // oops: stack empty } catch(Stack::Range) // exception handler { cout << "Stack Full or Empty" << endl; } cout << "Arrive here after catch (or normal exit)" << endl; } Lets examine four features of this program that deal with exceptions. In the class specification, there is an exception class. There are also statements that throw exceptions. In the application (the main() part of the program), there is a block of code that may cause exceptions (the try block) and a block of code to handle the exception (the catch block).
Throwing an Exception
An exception occurs in the Stack class if the application tries to pop a value when the stack is empty or tries to push a value when the stack is full. To let the application know that it has made such a mistake when manipulating a Stack object, the member functions of the Stack class check for these conditions using if statements and throw an exception if they occur. In XSTAK, the exception is thrown in two places, both using the statement throw Range(); The Range() part of this statement invokes the (implicit) constructor for the Range class, which creates an object of this class. The throw part of the statement transfers program control to the exception handler (which Ill examine in a moment).
1. Code is executing normally outside a try block. 2. Control enters the try block. 3. A statement in the try block causes an error in a member function. 4. The member function throws an exception. 5. Control transfers to the exception handler (catch block) following the try block.
Thats all there is to it. Notice how clean the resulting code is. Any of the statements in the try block could cause an exception, but I dont need to worry about checking a return value for each one because the try-throw-catch arrangement handles them all automatically. In the XSTAK example, Ive deliberately created two statements that cause exceptions. The first, s1.push(44); // pushes too many items causes an exception if you remove the comment symbol preceding it and the second, cout << "4: " << s1.pop() << endl; // pops item from empty stack causes an exception if the first statement is commented out. Try it each way. In both cases, the same error message will be displayed: Stack Full or Empty This is the bare bones of the exception process. In the next session, Ill look at some enhancements.
b. throw c. exception d. attempt e. catch 2. Exceptions a. provide a systematic way to handle errors detected in class member functions. b. were created to handle exceptionally large values. c. were created so a single class can handle different types of data. d. handle situations detected by the operating system. e. are not errors, only a different way to handle certain data values. 3. Which is the correct ordering of the following events? 1. Control goes to catch block. 2. Exception is thrown. 3. Error occurs in try block. 4. Error is detected by a member function. a. 2, 1, 3, 4 b. 3, 4, 2, 1 c. 2, 3, 4, 1 d. 2, 3, 1, 4 e. 3, 1, 4, 2 4. An object of an exception class a. is created when an error occurs. b. is created when an error is detected. c. is really an object of the class in which the error occurred. d. is said to be thrown when an exception occurs. e. is typically created within a member function. 5. Which of the following statements are true?
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 570 of 701
a. Catch and try blocks have the same syntax as classes. b. The try block must immediately follow the catch block. c. An exception class is typically declared within the class whose objects will cause the exception. d. Try blocks are identified by the name of an exception class. e. Catch blocks are identified by the name of an exception class. Exercise 1
Start with the ARROVER1 program from Chapter 6, Session 7. Modify the safearay class so that an exception is thrown if the user attempts to use an out-of-bounds index number. Revise main() so it uses try and catch blocks.
Exercise 2
Modify the airtime class from the ADDAIR program in Chapter 6, Session 1, so its get() member function throws an exception if the user enters a minutes value greater than 59. Revise main() to use try and catch blocks.
Multiple Exceptions
You can design a class to throw as many exceptions as you want. To show how this works, Ill modify the XSTAK program from the last session to throw separate exceptions for attempting to push data on a full stack and attempting to pop data from an empty stack. Listing 11-9 shows XSTAK2. Listing 11-9 XSTAK2 // xstak2.cpp // demonstrates two exception handlers #include <iostream.h> const int MAX = 3; class Stack { private: int st[MAX]; int top; public: class Full { }; class Empty { }; Stack() { top = -1; } void push(int var) { if(top >= MAX-1) throw Full(); st[++top] = var; } int pop()
// stack: array of integers // index of top of stack // exception class // exception class // constructor
{ if(top < 0) throw Empty(); return st[top--]; } }; void main() { Stack s1; try { s1.push(11); s1.push(22); s1.push(33); // s1.push(44); // oops: stack full cout << "1: " << s1.pop() << endl; cout << "2: " << s1.pop() << endl; cout << "3: " << s1.pop() << endl; cout << "4: " << s1.pop() << endl; // oops: stack empty } catch(Stack::Full) { cout << "Stack Full" << endl; } catch(Stack::Empty) { cout << "Stack Empty" << endl; } } In XSTAK2, I specify two exception classes: class Full { }; class Empty { }; The statement throw Full(); is executed if the application calls push() when the stack is already full, and throw Empty(); is executed if pop() is called when the stack is empty. A separate catch block is used for each exception: try { // code that operates on Stack objects } catch(Stack::Full) { // code to handle Full exception } catch(Stack::Empty) { // code to handle Empty exception } All the catch blocks used with a particular try block must immediately follow the try block. In this case, each catch block simply prints a message: Stack Full or Stack Empty. Only one catch block is activated for a given exception. This group of catch blocks, or catch ladder, operates a little like a switch statement, with only the appropriate section of code being executed. When an exception has been handled, control passes to the statement C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 572 of 701 // if stack empty, // throw Empty exception
following all the catch blocks. (Unlike a switch statement, an exception doesnt require you to end each catch block with a break.)
// exception class
Distance() // constructor (no args) { feet = 0; inches = 0.0; } Distance(int ft, float in) // constructor (two args) { if(in >= 12.0) // if inches too big, throw InchesEx(); // throw exception feet = ft; inches = in; } void getdist() { cout << "\nEnter feet: "; cout << "Enter inches: "; if(inches >= 12.0) throw InchesEx(); } // get length from user cin cin // // >> feet; >> inches; if inches too big, throw exception
void showdist() // display distance { cout << feet << "\'-" << inches << '\"'; } }; void main() { try { Distance dist1(17, 3.5); Distance dist2; dist2.getdist();
// // // //
2-arg constructor no-arg constructor get distance from user display distances Page 573 of 701
cout << "\ndist1 = "; dist1.showdist(); cout << "\ndist2 = "; dist2.showdist(); } catch(Distance::InchesEx) // catch exceptions { cout << "\nInitialization error: " "inches value is too large."; } } I install an exception class called InchesEx in the Distance class. Then, whenever the user attempts to initialize the inches data to a value greater than or equal to 12.0, I throw the exception. This happens in two places: in the two-argument constructor, where the programmer may make an error supplying initial values, and in the getdist() function, where the user may enter an incorrect value at the Enter inches prompt. In main(), all interaction with Distance objects is enclosed in a try block, and the catch block displays an error message. In a more sophisticated program, of course, you might want to handle a user error (as opposed to a programmer error) differently. It would be more user friendly to go back to the beginning of the try block and give the user a chance to enter another distance value.
// exception class
// for name of routine // for faulty inches value // 2-arg constructor // store string Page 574 of 701
iValue = in; } };
Distance() // no-arg constructor { feet = 0; inches = 0.0; } Distance(int ft, float in) // 2-arg constructor { if(in >= 12.0) throw InchesEx("2-arg constructor", in); feet = ft; inches = in; } void getdist() // get length from user { cout << "\nEnter feet: "; cin >> feet; cout << "Enter inches: "; cin >> inches; if(inches >= 12.0) throw InchesEx("getdist() function", inches); } void showdist() // display distance { cout << feet << "\'-" << inches << '\"'; } }; void main() { try { Distance dist1(17, 3.5); Distance dist2; dist2.getdist();
cout << "\ndist1 = "; cout << "\ndist2 = "; } catch(Distance::InchesEx ix) // exception handler { cout << "\nInitialization error in " << ix.origin << ".\n Inches value of " << ix.iValue << " is too large."; } } There are three parts to the operation of passing data when throwing an exception: specifying the data members and a constructor for the exception class, initializing this constructor when I throw an exception, and accessing the objects data when I catch the exception. Lets look at these in turn.
// 2-arg constructor // no-arg constructor // get value // display distances dist1.showdist(); dist2.showdist();
char origin[80]; float iValue; InchesEx(char* or, float in) { strcpy(origin, or); iValue = in; }
// for name of routine // for faulty inches value // 2-arg constructor // put string in object // put inches value in object
}; There are public variables for a string, which will hold the name of the member function being called, and a type float for the faulty inches value.
void main() { const unsigned int MAX = 60000; // memory size (chars) char* ptr; // pointer to memory unsigned int j; // loop variable try { ptr = new char[MAX]; // allocate memory // other statements that use 'new' } catch(xalloc) // exception handler { cout << "\nxalloc exception: can't allocation memory."; exit(1); } for(j=0; j<MAX; j++) // fill memory with data *(ptr+j) = j%128; for(j=0; j<MAX; j++) // check data if(*(ptr+j) != j%128) { cout << "\nData error"; exit(1); } delete[] ptr; // release memory cout << "\nMemory use is successful."; } Ill need to include the EXCEPT.H file, which contains the specification for the xalloc class (among others). Then I put all statements that use new in a try block. The catch block that follows handles the exception, usually by displaying an error message and terminating the program. I can get this program to throw the xalloc exception by tinkering with the value of MAX. I cant allocate an entire data segment of 65,536 bytes, so as I increase MAX beyond 60,000 bytes, Ill eventually cause the xalloc exception to be thrown, which will print the error message and cause the program to terminate: xalloc exception: can't allocate memory The program includes for loops to fill the memory with data and verify that its correct. Note that these statements dont need to be in the try block because they dont use new.
Exception Notes
Ive shown only the simplest and most common approach to using exceptions. I wont go into further detail, but I will conclude with a few thoughts about exception usage.
Termination Oriented
After you catch an exception, youll probably want to terminate your application. The exception mechanism gives you a chance to indicate the source of the error to the user and to perform any necessary clean-up chores before terminating. It also makes clean up easier by executing the destructors for objects created in the try block. This C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 577 of 701
allows you to release system resources, such as memory, that such objects may be using. (DOS programs are supposed to release system resources automatically when they terminate, but Windows programs may not.) However, in some cases you may not want to terminate your program. It is also possible to try to recover from the error. Perhaps your program can figure out what caused the error and correct it, or the user can be asked to input different data. When this is the case, the try and catch blocks are typically embedded in a loop, so control can be returned to the beginning of the try block (which the exception mechanism has attempted to restore to its initial state). If there is no exception handler that matches the exception thrown, the program is unceremoniously terminated.
Function Nesting
The statement that causes an exception need not be located directly in the try block; it can also be in a function that is called by a statement in the try block (or in a function called by a function that is called by a statement in the try block, and so on). So you need to install a try block only on the programs upper level. Lower-level functions need not be so encumbered, provided they are called directly or indirectly by functions in the try block.
Quiz 4 1. When you use multiple exceptions in a class, a. you must create several exception classes. b. there must be one try block for each exception. c. the exception class must take multiple arguments. d. the catch blocks must be contiguous. e. there can be no more than two exceptions. 2. Arguments in exceptions a. require member data in the exception class. b. are implemented using arguments to exception-object constructors. c. require the catch block to instantiate an object of the exception class. d. can provide additional information about an exception for the programs user. e. require a member function to catch exception objects with arguments. 3. In the Distance class in the XDIST2 program, a. multiple exceptions are used. b. an exception will be thrown if the value of feet is greater than 12.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 578 of 701
c. exceptions with multiple arguments are used. d. a member function prints an error message when an exception is detected. e. the class creator wrote code to detect the error. 4. Which of the following statements are correct? a. If there is no catch block corresponding to an exception that is thrown, then nothing happens. b. An exception will be handled normally even if it occurs in a function called from within the try block. c. If a class uses exceptions with arguments, then in the catch block the argument values can be obtained using the dot operator. d. Following an exception, you can arrange to return automatically to the statement in the try block following the one that caused the exception. e. If a class uses exceptions with arguments, then the values of these arguments must be specified in the try block. 5. The xalloc class a. lets you know exactly how much memory you obtained. b. handles exceptions caused by new. c. in a class that uses new, requires the class creator to write code that throws an exception. d. in a class that uses new, requires a class user to write code that throws an exception. e. requires statements that use new to appear only in class member functions. Exercise 1
Modify the exception mechanism in the airtime class, as described in Exercise 2 of Session 3, so that its get() member function throws an exception not only if the user enters a minutes value greater than 59, but also if the user enters negative values for either hours or minutes. Use three separate exceptions. Revise main() to handle the additional exceptions.
Exercise 2
Modify the exception mechanism in the airtime class from Exercise 1 so that all exceptions supply the offending value of minutes or hours. Revise main() to display this data for the user.
Midchapter Discussion
Estelle: George: Estelle: George: The whole idea of template classes makes me uneasy. Because you dont understand it? Its not that so much. Theyve taken this sort of very general idea of making a data type into a variable that you can plug specific types into, and all they use it for is container classes. So? Page 579 of 701
Estelle: Don:
I dont know, I like to see more general purpose solutions to problems. Its like they put in a special keyword to add the number 27. Template classes just do one thing. I know what you mean. But after all, container classes are pretty important. They probably developed templates for container classes, but decided to make them as general as possible just in case something else turned up. So theres something else you can do with template classes? Well, not that I know of, but people are coming up with new stuff all the time. Ill bet there are plenty of neat things you can do with templates. What I want to know is, do people really use exceptions? It seems like a lot of baggage with all these try blocks and exception classes and everything. Youd think thered be an easier way. Its not that bad. It makes more sense in larger programs, where a big chunk of code can throw all kinds of exceptions. It really simplifies things just to drop all that code into a try block instead of messing it up with lines and lines of error-handling code.
Explicit Casts
The ANSI/ISO C++ draft has introduced a new way to perform casts (explicitly changing a quantity from one type to another). One motivation for a new approach to casts is that old-style casts were hard to locate in a large listing during the debugging process. If you have statements that use the old C style cast, such as intvar = (int)longvar; or the newer C++ functional style cast, such as intvar = int(longvar); its hard to find them in your listing because theres no specific keyword to search for. Casts are a frequent source of bugs because they break the C++ typing system, but if you cant find them, theyre hard to fix. Four new C++ keywords solve this problem: static_cast, dynamic_cast, const_cast, and reinterpret_cast allow explicit casts tailored for different situations. The format of these casts makes use of template notation to specify the target data type. Casts are often a mistake and should be used with great restraint. However, sometimes theyre the only efficient way to solve a problem.
Static Casts
The static cast is used in many situations where old-style casts were previously used. It allows you to change any basic type to any other basic type, even if information is potentially lost, as it might be going from a long to an int, for example. Heres how such a cast looks with the old C++ notation: intvar = long(longvar); // convert long to int With an explicit cast, you would write it like this: intvar = static_cast<int>(longvar); // convert long to int The destination type (the type to be converted to) is supplied as a template argument to static_cast. Of course, such casts are dangerous and should be used only when absolutely necessary and when youre sure no harmful effects will result from losing information. The static cast also allows you to convert pointers-to-base-classes to pointers-to-derived-classes, and vice versa. The STA_CASTexample (Listing 11-13) shows a variety of conversion situations. Listing 11-13 STA_CAST // sta_cast.cpp // demonstrates static casts #include <iostream.h> C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 580 of 701
class Base { }; class Derv : public Base { }; void main() { int intvar = 27; long longvar = 12341234L;
// base class
// derived class
longvar = intvar; // ok intvar = static_cast<int>(longvar); // ok, and more explicit intvar = longvar; // compiler warning intvar = int(longvar); // ok (but dangerous) intvar = static_cast<int>(longvar); // ok (but dangerous) Base base; Base* bptr; Derv derv; Derv* dptr; bptr = &derv; bptr = static_cast<Base*>(&derv); // base-class object // base-class pointer // derived-class object // derived-class pointer // ok, upcast // ok, and more explicit
// dptr = &base; // compiler error, downcast dptr = (Derv*)&base; // ok (but dangerous) dptr = static_cast<Derv*>(&base); // ok (but dangerous) } Its normal to convert the addresses of derived class objects to base class pointers. The static cast makes this a more overt and easily identified activity. Going the other way is dangerous, but possible if you really want to. (Theres no output from this example.)
Dynamic Casts
The dynamic cast is actually an alternative way of performing Runtime Type Identification (RTTI), described in Chapter 9, Session 5. Its useful if, for example, youre stepping through a base class array looking for objects of a specific derived class type (e.g., all the pointers to scientist objects in an array of type pointer to employee). This cast returns 0 if the object is not the desired type or a pointer to the object if it is the desired type. Dynamic casts use the same format as static casts, but with the dynamic_cast keyword. They are normally used in the context of pointers to derived class types. Listing 11-14 shows DYN_CAST. Listing 11-14 DYN_CAST // dyn_cast.cpp // demonstrates dynamic casts #include <iostream.h> class Base { public: void virtual dummy() { } }; class Derv1 : public Base { }; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 581 of 701
class Derv2 : public Base { }; void main() { Base* bptrs[10]; Derv1 derv1; Derv2 derv2; bptrs[0] = &derv1; bptrs[1] = &derv2; Derv1* d1ptr; Derv2* d2ptr;
// these casts return valid addresses d1ptr = dynamic_cast<Derv1*>(bptrs[0]); // there is a derv1 cout << "\nd1ptr = " << d1ptr; d2ptr = dynamic_cast<Derv2*>(bptrs[1]); cout << "\nd2ptr = " << d2ptr; // in bptrs[0] // there is a derv2 // in bptrs[1]
// these casts return 0 d1ptr = dynamic_cast<Derv1*>(bptrs[1]); // no derv1 cout << "\nd1ptr = " << d1ptr; // in bptrs[1] d2ptr = dynamic_cast<Derv2*>(bptrs[0]); // no derv2 cout << "\nd2ptr = " << d2ptr; // in bptrs[0] } Notice that, for dynamic casts to work, the base class must be polymorphic; that is, it must contain at least one virtual function. Element 0 of the bptrs array (an array of pointers to base class objects) contains a pointer to a Derv1 object, and element 1 contains a pointer to a Derv2 object. The first dynamic cast looks for a pointer to a Derv1 object in element 0 and a pointer to a Derv2 object in element 1. It finds both of them and returns their addresses. The second two dynamic casts look in the reversed locations and, not finding the requested types, return 0. Heres the output from DYN_CAST: d1ptr = 0x3bcf2434 d2ptr = 0x3bcf2432 d1ptr = 0x00000000 d2ptr = 0x00000000 Normally, when you step through an array like this, you let the virtual function mechanism handle the selection of the correct function for the correct object. Sometimes, however, as when you cant modify the class (because someone else wrote it and you dont have the source code), you may need dynamic_cast (or the typeid approach to RTTI) to respond differently to different types of objects. Dynamic casts, besides leading to potential program bugs, also impose additional overhead, another reason for avoiding them unless theyre really necessary.
Const Casts
Youve seen various examples of const variables, which, once initialized, cannot be modified. On rare occasions, its useful to modify variables even though they are const. The const cast allows you to cast away constness (another C++ phrase with vaguely religious overtones). You cant use const_cast directly on variables, only on pointers or references to variables. Probably its most useful when a pointer or reference to a const variable is passed as an argument to function that needs to modify the variable. Listing 11-15, CON_CAST, shows how this cast is used. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 582 of 701
Listing 11-15 CON_CAST // con_cast.cpp // demonstrates const casts #include <iostream.h> void main() { int var = 27; const int constvar = 33; var = constvar; // constvar = var; int* ptr = &var; // int* ptr = &constvar; const int* ptr_c = &constvar; // ok // error, modifies const // ok // error, pointer not const // ok, pointer to const
// ptr = ptr_c; // error, pointer not const ptr = const_cast<int*>(ptr_c); // ok, casts away const } Of course, you can always assign the value of a const variable to a non-const variable. However, you cant normally assign a value to a const variable. constvar = var; // no good To overcome this limitation, first create a pointer to the object. This must be a pointer to const, as shown in the line const int* ptr_c = &constvar; // ok, pointer to const Remember that a constant pointer to a nonconstant variable isnt the same as a pointer to a constant: int* const c_ptr = &constvar; // bad, const pointer to non-const Normally, you cant take a pointer to a const variable and assign it to a pointer to a nonconstant variable because that would allow the constant variable to be modified: ptr = ptr_c; // no good However, using a const_cast, this becomes legitimate: ptr = const_cast<int*>(ptr_c); Such a cast should be used only in unusual situations as it violates the whole purpose of using const variables and thus removes a significant safety net from your program.
Reinterpret Casts
The reinterpret cast is the most powerful, and therefore the most dangerous, of the explicit casts. It can change pretty much anything into anything else: pointers into integers, floating-point numbers into bit patterns, and so on. Its useful in emergencies when nothing else works, but in general you probably shouldnt use this cast.
Heres another example. If youre writing code that interfaces with hardware devices, you may need to think about bytes. You might then declare a new type byte: typedef unsigned char byte; Now you can declare variables of type byte: byte high, low; Notice that theres no problem using typedef even if the type to be renamed consists of multiple words, such as unsigned int.
// use object like a function double answer = multiply(20002, 30003); cout << setprecision(10) << answer << endl; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 585 of 701
} Class alpha has nothing in it but a function consisting of the overloaded function operator. This operator takes two arguments, both type int, and multiplies them together, returning a type double. The output from the program is 600120006 Of course, you dont need a function for so simple an operation, but the example does demonstrate how arguments and return values are used with the overloaded function operator. Once Ive defined an object of class alpha, I can use this object as if it were a function. The compiler interprets a reference to multiply() as a call to the overloaded function operator in the multiply object. The multiply object is called a function object. Function objects have several uses. One is that you can create a function that has a separate version for every process that calls it. (Every process using multiply() creates its own alpha object.) This is important in a multitasking environment if the function contains data that it should remember between calls (like a count of how many times its been called or the last random number it generated). Problems can arise if several processes call the function and interfere with this data. However, if each process creates its own function object, it will have its own version of the data. A function object is also useful when you need to specify a function as an argument to another function that specifies an object for the argument. Youll see examples of this in Chapter 12.
Quiz 5 1. A static cast a. can be used to convert a double to a float. b. is necessary to convert a float to a double. c. can be used to convert a pointer to a derived class object to a pointer to a base class object. d. permits casts that cant be accomplished with the implicit-style casts (that use parentheses). e. makes it easier to find instances of casts in your source file. 2. Dynamic casts a. are usually applied to pointers of base class types. b. work only with polymorphic classes. c. perform tasks that cant be accomplished with old implicit-style casts. d. are another way to throw exceptions. e. are another way to provide runtime type identification. 3. A const cast a. may be used to cast away constness. b. can change a variable of type const int to type int. c. works only with pointers.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 586 of 701
d. works only with classes. e. works only with polymorphic classes. 4. The typedef specifier a. changes the meaning of basic type names such as float and int. b. can be used to shorten type names. c. could be used to give a name such as George to the type array of pointers to int. d. changes a variable of one type to a variable of another type. e. is a notational convenience. 5. Overloading the function operator a. requires a class with an overloaded constructor. b. requires a class with an overloaded [] operator. c. requires a class with an overloaded {} operator (braces). d. usually makes use of a constructor that takes arguments. e. allows you to create objects that act syntactically like functions. Exercise 1
Revise the VIRTSHAP program from Chapter 9, Session 2, to use dynamic casts when storing the addresses of derived class objects in the base class array.
Exercise 2
Use a const cast to make it possible for the user to change the object const noon in the CONSTA2 program in Chapter 5, Session 7.
Header Files
Vendors have implemented the standard string class in somewhat different ways. The examples Ill show in this section work with the Borland implementation of the string class and should work with other implementations as well. An exception to this is the name of the header file that contains the definitions of the various classes. This file might be C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 587 of 701
called STRING (with no .H), BSTRING.H, CSTRING.H, or some other name, depending on the compiler vendor. In my listings, Ill use the Borland version, CSTRING.H, but you should substitute the appropriate variation if necessary. You may also need to specify a namespace (see Appendixes B and C).
// // // //
string s5; // default constructor cout << "Before assignment, length of s5 = " << s5.length() << endl; s5 = s1; // assignment cout << "After assignment, length of s5 = " << s5.length() << endl << endl; string s6('-', 47); cout << "Length of s6 = " << s6.length() << endl; s6[0] = 'X'; s6[46] = 'X'; cout << for(int cout cout << "s6 = "; j=0; j<47; j++) << s6[j]; endl; // fixed size string
// array notation
// array notation
// concatenation string s7 = s1 + " (Motto of the Roman Empire)"; cout << "s7 = " << s7 << endl << endl; cout << "s1[3]=" << s1[3] << endl; } The << and >> operators are overloaded for input and output. The length() member function returns the number of characters in the current string object. (Current means the object for which the member function was called.) The overloaded [] operator allows you to access an individual character within a string if you know its position. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 588 of 701
The = operator works as you would expect, setting one string object equal to another, and the + operator concatenates two strings. Heres the output from STRING1: s1 = IN HOC SIGNO VINCES s2 = ------------------s3 = SIGNO VINCES s4 = VINCES Before assignment, length of s5 = 0 After assignment, length of s5 = 19 Length of s6 = 47 s6 = X---------------------------------------------X s7 = IN HOC SIGNO VINCES (Motto of the Roman Empire) s1[3]=H In the last statement of the program, which generates the last line of output, I show that the [] operator is overloaded to allow access to individual characters in the string using the same syntax as with char* strings. Although I dont show it here, the [] operator can also be used on the left side of an equal sign to assign values to individual characters.
Member Functions
A variety of member functions allows sophisticated manipulation of string objects. In the next example, STRING2 (Listing 11-18), Ill demonstrate a half-dozen of these functions. Listing 11-18 STRING2 // string2.cpp // demonstrates insert(), remove(), find(), etc #include <cstring.h> #include <iostream.h> void main() { string s1("Don told Estelle he would get the ring."); cout << "s1 = " << s1 << endl; s1.insert( 26, "not "); cout << "s1 = " << s1 << endl; s1.remove(26, 4); s1.replace(9, 7, "Pam", 3); cout << "s1 = " << s1 << endl; // insert "not " before "get"
int loc1 = s1.find("Pam"); // find "Pam" cout << "Pam is at = " << loc1 << endl; // find first whitespace char // following loc1 int loc2 = s1.find_first_of(" \t\n", loc1); // make substring "Pam" string s2 = s1.substr(loc1, loc2-loc1); cout << "Hi, " << s2 << endl; } Heres the output from STRING2: s1 = Don told Estelle he would get the ring. s1 = Don told Estelle he would not get the ring. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 589 of 701
s1 = Don told Pam he would get the ring. Pam is at 9 Hi, Pam
// call the function // display return value cout << "main is displaying: " << s2 << endl; char char_arr[80]; int len = s1.length(); // array of type char* // length of string
int n = s1.copy(char_arr, len, 0); // copy s1 to char_arr C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 590 of 701
char_arr[len] = '\0'; // end the char* string cout << "characters copied = " << n << endl; cout << "char_arr = " << char_arr << endl; const char* ptr = s1.c_str(); cout << "ptr = " << ptr << endl; } // pointer to char* // points to hidden array
string func(string s) // function { cout << "func is displaying argument: " << s << endl; return string("return value from func\n"); } The func() function is passed a string, which it displays. It then returns another string, which main() displays just to show that everything works as expected.
int j = 0; while(j < SZ+1) // check each name in array { int len = arr[j].length(); int lex_comp = new_name.compare( arr[j], 0, len ); if(lex_comp > 0) // if new one greater, j++; // go on to next name else// otherwise, { for(int k=SZ-1; k>=j; k--) // move all names that are arr[k+1] = arr[k]; // above this one arr[j] = new_name; // insert the new name break; // break out of while } // end else } // end while if(j == SZ+1) // if new name follows arr[10] = new_name; // all the others for(j=0; j<SZ+1; j++) cout << arr[j] << endl; } // end main // display all the names
Return value < 0: The current string follows the str argument alphabetically. Return value = 0: The current string is equal to str. Return value > 0: The current string precedes str alphabetically.
The program compares the new name with each of the array elements. If the new name is greater than the array element, the next element is examined. If not, the new name should be inserted at that point in the array and all the string objects from that point on up are moved to make this possible. Heres some interaction with STRING4 when the user enters Dilbert (the capital D is necessary): Enter a name: Dilbert Adam Bob Clair Dilbert Doug Emily Frank Gail Harry Ian Joe The new name has been inserted into the array at the appropriate place. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 592 of 701
Quiz 6 1. When working with the string class from the standard C++ library, a. you treat pointers to char as a data type. b. you treat sequences of type char as a data type. c. you must define arrays to allocate memory for objects. d. the = and < operators are overloaded. e. you use C library functions such as strlen() and strcmp(). 2. Which of these statements are true? a. A constructor allows you to create a string object initialized to part of a char* string. b. You cannot create a string object with no characters. c. A member function of string allows you to replace part of a string object with a char* string. d. The << and >> operators can be used the same way as they are with char* strings. e. The find_first_of() member function finds the first value in a specified range of values. 3. If you pass a string object as an argument to a function, the functions prototype might be a. void func(char); b. void func(char*); c. void func(char[]); d. void func(string); e. void func(string*); 4. To convert a. a string object to a char* string, you can use the copy() member function. b. a string object to a char* string, you can use the c_str() member function. c. a char* string to a string object, you can use the copy() member function.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 593 of 701
d. a char* string to a string object, you can use the c_str() member function. e. a char* string to a string object, you can use a constructor. 5. Which of these statements are true? a. You can initialize a string array with a list of char* strings, just as you can a char* array. b. The compare() member function compares the lengths of string objects. c. To assign one string object to another, you typically use the strcpy() function. d. The overloaded < operator compares the lengths of string objects. e. The expression George is a string object. Exercise 1
Using appropriate string member functions, write a program that allows the user to enter a word and then displays all possible anagrams (arrangements of letters) of that word. For example, if the user enters cat, the program should display
Class Libraries
In traditional procedure-oriented languages, it has long been customary for software vendors to furnish libraries of functions. Other programmers then combine these libraries with their own custom-written routines to create an application for the end user. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 594 of 701
Libraries provide ready-made functions for a wide variety of fields. For instance, a vendor might supply a library of functions for handling statistics calculations or for advanced memory management. Libraries that provide the functions necessary to create graphics user interface (GUI) programs are popular. Because C++ is organized around classes rather than functions, its not surprising that libraries for C++ programs consist of classes. What may be surprising is how superior a class library is to an old-fashioned function library. Because classes encapsulate both data and functions, and because they more closely model objects in real life, the interface between a class library and the application that makes use of it can be much cleaner than that provided by a function library. For these reasons, class libraries assume a more important role in C++ programming than function libraries do in traditional programming. A class library can take over a greater portion of the programming burden. An applications programmer, if the right class library is available, may find that only a minimal amount of programming is necessary to create a final product. Also, as more and more class libraries are created, the chances of finding one that solves your particular programming problem continues to increase. A class library often includes two components: public and private.
Public Components
To use a class library, the applications programmer needs to access various declarations, including class declarations. These declarations can be thought of as the public part of the library and are usually furnished in source code form as a header file with the .H extension. This file is typically combined with the clients source code using an include statement. The declarations in such a header file need to be public for several reasons. First, its a convenience to the client to see the actual definitions rather than to read a description of them. More important, the clients program will need to declare objects based on these classes and call on member functions from these objects. Only by declaring the classes in the source file is this possible.
Private Components
On the other hand, the inner workings of the member functions of the various classes dont need to be known by the client. The developers of the class library, like any other software developer, dont want to release source code if they can help it because the code might be illegally modified or pirated. Member functionsexcept for short inline functionsare therefore usually distributed in object form as .OBJ files or as library (.LIB) files. Figure 11-4 shows how the various files are related in a multifile system.
Figure 11-4 Files in a multifile application In the next session Ill show a program organized according to these principles. The program introduces a class of very large numbers. (By very large, I mean integers with an almost unlimited number of digits.)
Header Files
The header file THEIRS.H is easily incorporated into your own source file, MINE.CPP, with an include statement: #include "THEIRS.H" Quotes rather than angle brackets around the file name tell the compiler to look for the file first in the current directory rather than in the default include directory.
Directory
Make sure all the component files, THEIRS.OBJ, THEIRS.H, and MINE.CPP, are in the same directory. In fact, you will probably want to create a separate directory for the project to avoid confusion. This isnt necessary, but its the simplest approach.
Multiple Files
For handling the details of development using multiple source files, see Appendix C for the Borland C++ compiler and Appendix D for Microsoft Visual C++. Generally, you proceed in the same way as when developing a program with a single source file (MINE.CPP) and then add any additional files to the project (THEIRS.OBJ). Once youve done this, the compiler takes care of compiling any source files and linking the resulting .OBJ files together. You can run the resulting .EXE file (MINE.EXE) in the usual way.
Namespaces
The larger a program is, the greater the danger of name clashes, that is, a single name being usedinadvertentlyto refer to several different things. This is a particular problem in multifile programs, where files may have been created by different programmers or different companies. For example, suppose a class alpha is defined in a file A: // file A class alpha { }; In another file, which will eventually be linked with file A, there is also a specification for a class that unbeknownst to the creators of file Ahas been given the same name: // file B class alpha { }; The resulting name clash will cause linker errors. Programmers might then try to fix the problem by renaming one of the alpha classes. However, theres an easier way. A recent revision to the draft ANSI C++ standard introduces a new solution to this problem: namespaces. A namespace is a section of a program specified by the keyword namespace and enclosed in brackets. A name used within a namespace does not have global visibility; instead, its scope is limited to the namespace.
Declaring Namespaces
In the A and B files shown on the previous page, the creators of the two files, George and Harry, could limit the visibility of the class alpha specifications by placing each one in its own namespace: // file A namespace NS_George { class alpha { }; } and // file B namespace NS_Harry { class alpha C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 597 of 701
{ }; } I show the namespaces in different files because they will frequently be used that way, but of course they could be in the same file as well. Theres no necessary connection between files and namespaces.
Quiz 7 1. Which of the following are reasons for using multiple files? a. Member functions can be separated from member data.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 598 of 701
b. Each group of programmers may work on a separate file. c. Classes can be placed in different files than the code that uses the classes. d. Class specifications can be placed in a different file than member function code. e. The code in each file can have a different functionality. 2. In the highest level of program organization, private components of a class library a. take the form of .CPP or .H files. b. take the form of .LIB or .OBJ files. c. do not need to be accessed by the class user. d. do not need to be accessed by the application user. e. are differentiated from public components by the keywords private and public. 3. To combine a header file with your project, use a. an #include statement. b. the editor. c. the compiler. d. the linker. e. the .EXE file. 4. To combine several .OBJ files in your project, use a. an #include statement. b. the editor. c. the compiler. d. the linker. e. the .EXE file. 5. A namespace a. is created with the keyword namespace. b. ensures functions are visible throughout a program. c. limits the scope of global names. d. limits the scope of automatic variables.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 599 of 701
Exercise 2
Apply the same approach as in the previous exercise to make the LINKLIST program of Chapter 8, Session 7, into a multifile program consisting of files LINKLIST.H, LINKLIST.CPP (which becomes LINKLIST.OBJ), and LINK_APP.CPP. Make sure the project compiles and runs correctly.
Numbers as Strings
The verylong class stores numbers as objects of the standard string class described in Session 6 in this chapter. The string object contains the digits of the number stored as ASCII characters. By representing numbers as strings, I can make them as long as I want, although arithmetic operations will be slower. There are two data members in the verylong class: a string object to hold the string of digits and an int to tell how long the string is. This length data isnt strictly necessary, but it saves using the length() member function to find the length of string objects over and over. The digits in the string are stored in reverse order, with the least significant digit stored first. This simplifies various operations on the string. Figure 11-5 shows a number stored as a string.
class verylong { private: string vlstr; // verylong number as a string int vlen; // length of verylong string verylong multdigit(const int) const; // prototypes for verylong mult10(const verylong) const; // private functions public: verylong() : vlen(0) // no-arg constructor { } verylong(string& const s) : // one-arg constructor vlstr(s), vlen( s.length() ) // for string { } verylong(const unsigned long n) // one-arg constructor { // for long int char temp[SZ]; // utility char* string ltoa(n, temp, 10); // convert n to char* strrev(temp); // reverse char* string vlstr = temp; // convert to string obj vlen = vlstr.length(); // find length } void putvl() const; // display verylong void getvl(); // get verylong from user // add verylongs verylong operator + (const verylong) const; // multiply verylongs verylong operator * (const verylong) const; }; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 601 of 701
The + and * operators are overloaded to provide addition and multiplication of verylong numbers. In addition to these public functions and the two data members, there are two private member functions. One multiplies a verylong number by a single decimal digit and one multiplies a verylong number by 10. These routines are used internally by the multiplication routine. There are three constructors. One sets a verylong variable to 0 by setting its length, vlen, to 0. The second initializes a verylong to a string (which must already be in reverse order), and the third initializes it to a long int value. The putvl() member function displays a verylong and the getvl gets a verylong value from the user. You can type as many digits as you like, up to 200. Note that there is no error checking in this routine; if you type a nondigit, the results are unpredictable. You can perform addition and multiplication on verylongs using expressions such as alpha = beta * gamma + delta; just as you would with variables of basic types.
// add verylongs verylong verylong::operator + (const verylong v) const { int j; char temp[SZ]; // utility char* string // find longest number int maxlen = (vlen > v.vlen) ? vlen : v.vlen; int carry = 0; // set to 1 if sum >= 10 for(j = 0; j<maxlen; j++) // for each position { int int int if(
d1 = (j > vlen-1) ? 0 : vlstr[j]-'0'; // get digit d2 = (j > v.vlen-1) ? 0 : v.vlstr[j]-'0'; // get digit digitsum = d1 + d2 + carry; // add digits digitsum >= 10 ) // if there's a carry, { digitsum -= 10; carry=1; } // decrease sum by 10, else // set carry to 1 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 602 of 701
carry = 0; temp[j] = digitsum + '0'; } if(carry==1) temp[j++] = '1'; temp[j] = '\0'; return verylong(temp); }
// otherwise carry is 0 // insert char in string // // // // if carry at end, last digit is 1 terminate string return temp verylong
// multiply verylongs verylong verylong::operator * (const verylong v) const { verylong pprod; // product of one digit verylong tempsum; // running total for(int j=0; j<v.vlen; j++) // for each digit in arg { int digit = v.vlstr[j] - '0'; // get the digit pprod = multdigit(digit); // multiply this by digit for(int k=0; k<j; k++) // multiply result by pprod = mult10(pprod); // power of 10 tempsum = tempsum + pprod; // add product to total } return tempsum; // return total of prods } // multiply argument by 10 verylong verylong::mult10(const verylong v) const { char temp[SZ]; for(int j=v.vlen-1; j>=0; j--) // move digits one temp[j+1] = v.vlstr[j]; // position higher temp[0] = '0'; // put zero on low end temp[v.vlen+1] = '\0'; // terminate string return verylong(temp); // return result } // multiply this verylong verylong verylong::multdigit(const int d2) const // by digit { // in argument int j; char temp[SZ]; int carry = 0; for(j = 0; j<vlen; j++) // for each position
{ // in this verylong int d1 = vlstr[j]-'0'; // get digit from this int digitprod = d1 * d2; // multiply by that digit digitprod += carry; // add old carry if( digitprod >= 10 ) // if there's a new carry, { carry = digitprod/10; // carry is high digit digitprod -= carry*10; // result is low digit } else carry = 0; // otherwise carry is 0 temp[j] = digitprod+'0'; // insert char in string } C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 603 of 701
if(carry != 0)// if carry at end, temp[j++] = carry+'0'; // it's last digit temp[j] = '\0'; // terminate string return verylong(temp); // return verylong } The putvl() member function converts the vlstr object to a normal C++ char* string and dislays it. The getvl() member function reads a normal char* string from the keyboard, reverses it, and converts it to the string object vlstr. Both functions use the strrev() library function, which reverses a char* string, so the digits of the verylong are stored in reverse order but displayed normally. The operator+() function adds two verylongs and leaves the result in a third verylong. It does this by considering digits one at a time. It adds digit 0 from both numbers, storing a carry if necessary. Then it adds the digits in position 1, adding the carry if necessary. It continues until it has added all the digits in the larger of the two numbers. If the numbers are different lengths, the nonexistent digits in the shorter number are set to 0 before being added. Figure 11-6 shows the process.
Figure 11-6 Adding verylong numbers Multiplication uses the operator*() function. This function performs multiplication by multiplying the multiplicand (the top number when you write it by hand) by each separate digit in the multiplier (the bottom number). It calls the multdigit() routine to do this. The results are then multiplied by 10 an appropriate number of times to shift the result to match the position of the digit, using the mult10() function. The results of these separate calculations are then added together using the operator+() function.
Listing 11-22 VL_APP // vl_app.cpp // calculates factorials of larger numbers using verylong class #include "verylong.h" // verylong header file void main() { unsigned long numb, j; verylong fact = 1; // initialize verylong cout << "\n\nEnter number: "; cin >> numb; // input a long int for(j=numb; j>0; j--) // factorial is numb * fact = fact * j; // numb-1 * numb-2 * cout << "Factoral is "; // numb-3 and so on fact.putvl(); // display factorial } In this program, fact is a verylong variable. The other variables, numb and j, dont need to be verylongs because they dont get so big. To calculate the factorial of 200, for example, numb and j require only three digits, whereas fact requires 158. Notice how, in the expression fact = fact * j; the long variable j is automatically converted to verylong, using the one-argument constructor, before the multiplication is carried out. Heres the output when I ask the program to find the factorial of 100: Enter number: 100 Factoral is 9332621544394415268169923885626670049071596826438162 1468592963895217599993229915608941463976156518286253697920827223 758251185210916864000000000000000000000000 Try that using type long variables! Surprisingly, the routines are fairly fast; this program executes in a fraction of a second.
Exercise 1
Figure out something you would like to make into a class library (A new data type to represent dollars-and-cents values? A new container class based on a queue? Airplanes in an air-traffic control simulator?). Implement this class library as separate .Hand .OBJ files. Write a separate .CPP file that makes use of the class library. Use a template approach if appropriate. Install exceptions to catch any errors.
Summary: Chapter 11
Ive covered many disparate topics in this chapter. Template functions can operate on different kinds of data, with the data type supplied when the function is instantiated rather than when its written. Template classes are usually used for container classes and allow containers (data structures) to store different types of data, with the type determined when an object of the class is instantiated. Exceptions are a systematic approach to handling errors that occur in class member functions. An exception class is specified within the class in question, and exceptions are thrown using the throw keyword when errors are C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 605 of 701
detected. In the application using the class, code that may generate errors is installed in a try block and code that handles an exception is installed in a catch block. Explicit casts provide an alternative form of runtime type identification, allow const variables to be modified, and are more easily located with a source code editor than implicit casts. The typedef specifier gives a data type a new name. It can be used to give a potentially long and complicated type a shorter name, and for other purposes. The string class thats now being made a part of the standard C++ library makes it far more convenient to work with text strings. Objects of the string class handle their own memory allocation, so users no longer need to set aside array space to hold strings. The string class also supplies many member functions to make string manipulation easier. In the real world, C++ programs are divided into different files. This helps separate the work of different programmers, puts different kinds of functionality in different files, and allows the distribution of proprietary software without giving users access to the source code. I showed an example of a class library for a very long numbers data type in multifile form.
End-of-Chapter Discussion
George: Estelle: George: Don: Estelle: George: Don: Estelle: Don: Estelle: George: Don: George: Don: George: What a hodgepodge! We covered everything but the kitchen sink in this chapter. You should be glad we didnt spend whole chapters on typedef and static casts and everything. That would have been really fun. Are we really going to use this stuff? It seems pretty far out. Even if you dont use it, you should recognize it when you see it. Practically every listing I see uses typedef. Not so much the explicit casts; I think theyre too new. The standard string class is pretty neat. Well its about time they did something with strings in C++. Imagine having to deal with arrays of characters instead of a real string type. Yes, no more worrying about always having enough memory for some string. Let the class handle it. The string class header files in my compiler arent anything like what they talked about in Session 6, though. They said that would change. The string class is pretty new, and when they wrote this book, it wasnt clear what compiler vendors were really going to call them. I guess thats the trouble with being on the cutting edge. Do you think anyone would really use that verylong number class? Maybe a mathematician. There arent even member functions to let you subtract and divide! But youll have them written by next week, right? Oh, no problem. Ill whipem off in no time cause Im some kind of a genius programmer.
Containers
A container is a way to store data, whether the data consists of built-in types such as int and float or of class objects. The STL makes available seven basic kinds of containers; three more are derived from the basic kinds. In addition, you can create your own containers based on the basic kinds. You may wonder why you need so many kinds of containers. Why not use C++ arrays in all data storage situations? The answer is efficiency. An array is awkward or slow in many situations. Containers in the STL fall into two categories: sequence and associative. The sequence containers are vector, list, and deque. The associative containers are set, multiset, map, and multimap. In addition, several containers are called abstract data types, which are specialized versions of other containers. These are stack, queue, and priority_queue. Ill look at these categories in turn.
Sequence Containers
A sequence container stores a set of elements that you can visualize as a line, like houses on a street. Each element is related to the other elements by its position along the line. Each element (except at the ends) is preceded by one specific element and followed by another. An ordinary C++ array is an example of a sequence container. One problem with a C++ array is that you must specify its size at compile time, that is, in the source code. Unfortunately, you usually dont know when you write the program how much data will be stored in the array. So you must specify an array large enough to hold what you guess is the maximum amount of data. When the program runs, you will either waste space in memory by not filling the array or elicit an error message (or even blow up the program) by running out of space. The STL provides the vector container to avoid these difficulties. Heres another problem with arrays. Say youre storing employee records and youve arranged them in alphabetical order by the employees last name. If you now want to insert a new employee whose name starts with, say, L, then you must move all the employees from M to Z to make room. This can be very time-consuming. The STL provides the list container, which is based on the idea of a linked list, to solve this problem. Recall from the LINKLIST example in Chapter 8, Session 7, that a new item can easily be inserted in a linked list by rearranging several pointers. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 608 of 701
The third sequence container is the deque, which can be thought of as a combination of a stack and a queue. A stack, as you may recall from previous examples, works on a last-in-first-out (LIFO) principle. Both input and output take place on the top of the stack. A queue, on the other hand, uses a first-in-first-out (FIFO) arrangement: Data goes in at the front and comes out at the back, like a line of customers in a bank. A deque combines these approaches so you can insert or delete data from either end. The word deque is derived from Double-Ended QUEue. Its a versatile mechanism thats not only useful in its own right, but can be used as the basis for stacks and queues, as youll see later. Table 12-1 summarizes the characteristics of the STL sequence containers. It includes the ordinary C++ array for comparison. Table 12-1 Basic sequence containers Container Characteristic Advantages and Disadvantages Ordinary C++ array Fixed size Quick random access (by index number). Slow to insert or erase in the middle. Size cannot be changed at runtime. Vector Relocating, expandable array Quick random access (by index number). Slow to insert or erase in the middle. Quick to insert or erase at end. List Doubly linked list Quick to insert or delete at any location. Quick access to both ends. Slow random access. Deque Like vector, but can be accessed at either end Quick random access (using index number). Slow to insert or erase in the middle. Quick to insert or erase (push and pop) at either the beginning or the end.
Instantiating an STL container object is easy. First, you must include an appropriate header file. Then you use the template format with the kind of objects to be stored as the parameter. Examples might be vector<int> avect; // create a vector of ints or list<airtime> departure_list; // create a list of airtimes Notice that theres no need to specify the size of STL containers. The containers themselves take care of all memory allocation.
Associative Containers
An associative container is not sequential; instead it uses keys to access data. The keys, typically numbers or stings, are used automatically by the container to arrange the stored elements in a specific order. Its like an ordinary English dictionary in which you access data by looking up words arranged in alphabetical order. You start with a key value (e.g., the word aardvark, to use the dictionary example), and the container converts this key to the elements location in memory. If you know the key, its quick to access the associated value. There are two kinds of associative containers in the STL: maps and sets. A map associates a key (e.g., the word youre looking up) with a value (the definition of the word). The value can be any kind of object. A set is similar to a map, but it stores only the keys; there are no associated values. Its like a list of words without the definitions. The map and set containers allow only one key of a given value to be stored. This makes sense in, say, a phone book where you can assume that multiple people dont have the same number. On the other hand, the multimap and multiset containers allow multiple keys. In an English dictionary, there might be several entries for the word set, for example. Table 12-2 summarizes the associative containers available in the STL. Table 12-2 Basic associative containers Container Characteristics Advantages and Disadvantages Map Associates key with element Only one key of each value allowed Quick random access (by key). Inefficient if keys not evenly distributed. Multimap Associates key with element Multiple key values allowed Quick random access (by key). Inefficient if keys not evenly distributed. Set Stores only the keys themselves Only one key of each value allowed Quick random access (by key). Inefficient if keys not evenly distributed. Multiset Stores only the keys themselves Multiple key values allowed Quick random access (by key). Inefficient if keys not evenly distributed.
Creating associative containers is just like creating sequential ones: map<int> IntMap; // create a map of ints or multiset<employee> machinists; // create a multiset of employees C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 609 of 701
Member Functions
Algorithms are the heavy hitters of the STL, carrying out complex operations such as sorting and searching. However, containers also need member functions to perform simpler tasks that are specific to a particular type of container. Table 12-3 shows some frequently used member functions whose name and purpose (not the actual implementation) are common to all the container classes. Table 12-3 Some member functions common to all containers Name Purpose size() Returns the number of items in the container. empty() Returns true if container is empty. max_size() Returns size of the largest possible container. begin() Returns an iterator to the start of the container for iterating forward through the container. end() Returns an iterator to the past-the-end location in the container, used to end forward iteration. rbegin() Returns a reverse iterator to the end of the container for iterating backward through the container. rend() Returns a reverse iterator to the beginning of the container, used to end backward iteration.
Many other member functions appear only in certain containers or certain categories of containers. Youll learn more about these as I go along. Appendix B includes a table showing all the STL member functions and which ones exist for which containers.
You use a template within a template to instantiate an ADT. For example, heres a stack object that holds type int, instantiated from the deque class: stack< deque<int> > astak; A detail to note about this format is that you must insert a space between the two closing angle brackets. You cant write stack<deque<int>> astak; // syntax error because the compiler will interpret the >> as an operator.
Algorithms
An algorithm is a function that does something to the items in a container (or containers). As I noted, algorithms in the STL are not member functions or even friends of container classes, as they are in most other container libraries, but standalone template functions. You can use them with built-in C++ arrays or with container classes you create yourself (provided the class includes certain basic functions). Table 12-5 shows a few representative algorithms. Ill examine others as I go along. Appendix B contains a table listing all the currently available STL algorithms. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 610 of 701
Table 12-5 Some typical STL algorithms Algorithm Purpose find Returns first element equivalent to a specified value. count Counts the number of elements that have a specified value. equal Compares the contents of two containers and returns true if all corresponding elements are equal. search Looks for a sequence of values in one container that correspond with the same sequence in another container. copy Copies a sequence of values from one container to another (or to a different location in the same container). swap Exchanges a value in one location with a value in another. iter_swap Exchanges a sequence of values in one location with a sequence of values in another location. fill Copies a value into a sequence of locations. sort Sorts the values in a container according to a specified ordering. merge Combines two sorted ranges of elements to make a larger sorted range. accumulate Returns the sum of the elements in a given range. for_each Executes a specified function for each element in a container.
Suppose you create an array of type int, with data in it: int arr[8] = {42, 31, 7, 80, 2, 26, 19, 75}; You can then use the STL sort() algorithm to sort this array by saying sort(arr, arr+8); where arr is the address of the beginning of the array and arr+8 is the past-the-end address (one item past the end of the array).
Iterators
Iterators are pointer-like entities used to access individual data items (which are usually called elements) in a container. Often they are used to move sequentially from element to element, a process called iterating through the container. You can increment an iterator with the ++ operator so it points to the next element, and you can dereference it with the * operator to obtain the value of the element it points to. Like a pointer to an array element, some types of iterators can store (or remember) the location of a specific container element. In the STL, an iterator is represented by an object of an iterator class. Different classes of iterators must be used with different types of container. There are three major classes of iterators: forward, bidirectional, and random access. A forward iterator can move only forward through the container one item at a time. Its ++ operator accomplishes this. It cant move backward and it cant be set to an arbitrary location in the middle of the container. A bidirectional iterator can move backward as well as forward, so both its ++ and -- operators are defined. A random-access iterator, in addition to moving backward and forward, can jump to an arbitrary location. You can tell it to access location 27, for example. There are also two specialized kinds of iterators. An input iterator can point to an input device (cin or a file) to read sequential data items into a container, and an output iterator can point to an output device (cout or a file) and write elements from a container to the device. Although the values of forward, bidirectional, and random-access iterators can be stored (so they can be used later), the values of input and output iterators cannot be. This makes sense: The first three iterators point to memory locations, whereas input and output iterators point to I/O devices for which stored pointer values have no meaning. Table 12-6 shows the characteristics of these different kinds of iterators. Table 12-6 Iterator characteristics Iterator Read/Write Iterator Can Be Saved Direction Access Random access Read and write Yes Forward and back Random Bidirectional Read and write Yes Forward and back Linear Forward Read and write Yes Forward only Linear Output Write only No Forward only Linear Input Read only No Forward only Linear
First, its sometimes hard to find errors because the compiler reports them as being deep in a header file when they are really in the class users code. You may need to resort to brute force methods such as commenting out one line of your code at a time to find the culprit. Precompilation of header files, which speeds up compilation dramatically on compilers that offer it, may cause problems with the STL. If things dont seem to be working, try turning off precompiled headers. The STL generates lots of compiler warnings from deep within the STL header files. Conversion may lose significant digits is a favorite. These appear to be harmless and can be ignored or turned off. These minor complaints aside, the STL is a surprisingly robust and versatile system. Errors tend to be caught at compile time rather than at runtime. The different algorithms and containers present a very consistent interface; what works with one container or algorithm will usually work with another (assuming its used appropriately). This quick overview probably leaves you with more questions than answers. The following sessions should provide enough specific details of STL operation to make things clearer.
Quiz 1 1. An STL container can be used to a. hold int values. b. hold objects of class employee. c. store elements in a way that makes them quickly accessible. d. compile C++ programs. e. organize the way objects are stored in memory. 2. Which of the following are STL sequence containers? a. multiset b. stack c. deque d. map e. list 3. Which of the following are STL associative containers? a. multiset b. stack c. deque d. map e. list 4. An algorithm is a. a standalone function that operates on containers.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 612 of 701
b. a link between member functions and containers. c. a friend function of a container class. d. a member function of a container class. e. a kind of pointer to objects in a container. 5. An iterator a. acts something like a function. b. cannot step backward through a container. c. always executes the same algorithm over and over. d. points to a specific object in a container. e. is the STL version of a for loop.
Because of its generality, this session contains no exercises.
Session 2: Algorithms
The STL algorithms perform operations on collections of data. These algorithms were designed to work with STL containers, but one of the nice things about them is that you can apply them to ordinary C++ arrays. This may save you considerable work when programming arrays. It also offers an easy way to learn about algorithms, unencumbered with containers. In this session, Ill examine how some representative algorithms are actually used. (Remember that all the algorithms are listed in Appendix B.)
Header Files
In this program, Ive included the header file ALGO.H. In the version of the STL Im using, this file contains the declarations of all the algorithms. Other header files are used for containers. If youre using a version of the STL from a different supplier, you may need to include a different header file. Some implementations use only one header file for all STL operations, whereas others use many different files but call them by slightly different names, such as ALGORTHM.H. or ALGORITHM (without the .H). You may also need to insert a using namespace directive in your code. See Appendixes C and D for more on this issue.
Ranges
The first two parameters to find() specify the range of elements to be examined. These values are specified by iterators. In this example, I use normal C++ pointer values, which are a special case of iterators. The first parameter is the iterator of (or in this case, the pointer to) the first value to be examined. The second parameter is the iterator of the location one past the last element to be examined. Because there are 8 elements, this value is the first value plus 8. This is called a past-the-end value; it points to the element just past the end of the range to be examined. This syntax is reminiscent of the normal C++ idiom in a for loop: for(int j=0; j<8; j++) // from 0 to 7 { if(arr[j] == 33) { cout << "First object with value 33 found at offset " << j << endl; break; } } In the FIND example, the find() algorithm saves you the trouble of writing this for loop. In more complicated situations, algorithms may save you from writing far more complicated code.
// must initialize // count number of 33's // add result to n cout << "There are " << n << " 33's in arr." << endl; } The output is There are 3 33's in arr. You might expect count() to return the count, but adding the count to an existing number may be useful if youve already counted something and want to add the new count to the old one.
for(int j=0; j<8; j++) // display sorted array cout << arr[j] << ' '; } The output from the program is -30, -17, 0, 2, 25, 45, 55 Ill look at some variations of this algorithm later.
Function Objects
Some algorithms can take something called a function object as an argument. A function object looks, to the user, much like a template function. However, its actually an object of a template class that has a single member function: the overloaded () operator. This sounds mysterious, but its easy to use. Suppose you want to sort an array of numbers into descending instead of ascending order. The SORTEMP program (Listing 12-6) shows how to do this. Listing 12-6 SORTEMP // sortemp.cpp // sorts array of floats in backwards order, // uses greater<>() function object #include <iostream.h> #include <algo.h> // array of floats float fdata[] = { 19.2, 87.4, 33.6, 55.0, 11.5, 42.2 }; void main() { // sort the floats sort( fdata, fdata+6, greater<float>() ); for(int j=0; j<6; j++) // display sorted floats cout << fdata[j] << endl; } The array of float values is sorted using the greater<>() function object. Heres the output: 87.4 55 42.2 33.6 19.1 11.5 Besides comparisons, there are function objects for arithmetical and logical operations. Ill look at function objects more closely in Session 8. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 616 of 701
bool alpha_comp(char* s1, char* s2) // returns true if s1<s2 { return ( strcmp(s1, s2)<0 ) ? true : false; } The third argument to the sort() algorithm is the address of the alpha_comp() function, which compares two char* strings and returns true or false, depending on whether the first is lexicographically (i.e., alphabetically) less than the second. The output from this program is what you would expect: Bob Don Estelle George Mike Penny Actually, you dont need to write your own function objects to handle text. If you use the string class from the standard library, you can use built-in function objects such as less<>() and greater<>() instead.
Boolean Type
The return value of the user-written function (alpha_comp() in this example) should be type bool. This type is specified in the BOOL.H header file (which is included in ALGO.H). On my system, type bool is #defined to be type int, true is 1, and false is 0; other systems vary but work the same way.
defined, you can create an equality function that works with the find_if() algorithm to find elements with any arbitrary characteristic. Listing 12-8 uses char* strings. The find_if() algorithm is supplied with a user-written isDon() function to find the first string in an array of strings that has the value Don. Listing 12-8 FIND_IF // find_if.cpp // searches array of strings for first name that matches "Don" #include <iostream.h> #include <string.h> // for strcmp() #include <algo.h> // array of strings char* names[] = { "George", "Estelle", "Don", "Mike", "Bob" }; bool isDon(char*); // prototype void main() { char** ptr; // find the first string "Don" ptr = find_if( names, names+5, isDon ); if(ptr==names+5) // display results cout << "Don is not on the list"; else cout << "Don is element " << (ptr-names) << " on the list."; } bool isDon(char* name) // returns true if name=="Don" { return ( strcmp(name, "Don") ) ? false : true; } Because Don is indeed one of the names in the array, the output from the program is Don is element 2 on the list. The address of the function isDon() is the third argument to find_if(), whereas the first and second arguments are, as usual, the first and the past-the-end addresses of the array. The find_if() algorithm applies the isDon() function to every element in the range. If isDon() returns true for any element, then find_if() returns the value of that elements pointer (iterator). Otherwise, it returns a pointer to the past-the-end address of the array. Various other algorithms, such as count(), replace(), and remove(), have _if versions.
float inches[] = { 3.5, 6.2, 1.0, 12.75, 4.33 }; void in_to_cm(float); // prototype // output as centimeters for_each(inches, inches+5, in_to_cm); } void in_to_cm(float in) // convert and display as centimeters { cout << (in * 2.54) << ' '; } The output looks like this: 8.89 15.748 2.54 32.385 10.9982
// return result } The output is the same as that from the FOR_EACH program. Ive showed just a few of the algorithms in the STL.There are many others, but what Ive shown here should give you an idea of the kinds of algorithms that are available and how to use them.
Quiz 2 1. Algorithms can a. be used only on STL containers. b. obtain information about elements in a container.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 619 of 701
c. change the order of elements in a container. d. create and destroy containers. e. create and destroy the objects stored in containers. 2. The find() algorithm a. finds matching sequences of elements in two containers. b. finds a container that matches a specified container. c. takes two iterators as its first two arguments. d. takes two container elements as its first two arguments. e. finds an element in a container that matches a specified value. 3. A range is a. all the elements between two specified elements. b. all the elements in a container. c. a large, flat container dotted with buffalo. d. often supplied to an algorithm by two iterator values. e. all the values an element in a container can have. 4. Which of the following are true? a. Type bool is used as the return value of all algorithms to indicate success or failure. b. Type bool is appropriate for variables whose values are restricted to true and false. c. A user-written function can substitute for a function object, which is supplied to some algorithms. d. A function object can customize the behavior of an algorithm. e. The sort() algorithm can sort the elements in a container into an order based on the < operator overloaded for the class of the elements. 5. A user-written function whose address is supplied as the third argument to the for_each() algorithm a. must always return a bool value. b. copies the elements of one container into a different container. c. must be a member of the container class.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 620 of 701
d. will be applied to each element in a range. e. must take container elements as its first two arguments. Exercise 1
Write a program that applies the sort() algorithm to an array of names entered by the user and displays the result.
Exercise 2
Write a program that applies the sort() algorithm to an array of names entered by the user and displays the result. Employ a user-written function to compare the names alphabetically.
Vectors
You can think of vectors as smart arrays. They manage storage allocation for you, expanding and contracting the size of the vector as you insert or erase data. You can use vectors much like arrays, accessing elements with the [] operator. Such random access is very fast with vectors. Its also fast to add (or push) a new data item onto the end (the back) of the vector. When this happens, the vectors size is automatically increased to hold the new item.
for(int j=0; j<v.size(); j++) // display vector contents cout << v[j] << ' '; } I use the vectors default (no-argument) constructor to create a vector v. As with all STL containers, the template format is used to specify the type of variable the container will hold; in this case, type int. I dont specify the containers size, so it starts off at 0.
The push_back() member function inserts the value of its argument at the back of the vector. (The back is where the element with the highest index number is.) The front of a vector (where the element with index 0 is), unlike that of a list or queue, is not accessible. Here I push the values 10, 11, 12, and 13, so that v[0] contains 10, v[1] contains 11, v[2] contains 12, and v[3] contains 13. Once a vector has some data in it, this data can be accessedboth read and written tousing the overloaded [] operator, just as if it were in an array. I use this operator to change the first element from 10 to 20 and the last element from 13 to 23. Heres the output from VECTOR: 20 11 12 23 Note that although I can access data thats already there, I cant user the [] operator in a way that would involve changing the size of the vector, as I can with push_back(). For example, trying to read or write to v[27] wont work because the vector has only four members. The size() member function returns the number of elements currently in the container, which in VECTOR is 4. I use this value in the for loop to print out the values of the elements in the container. Another member function, max_size() (which I dont demonstrate here), returns the maximum size to which a container can be expanded. This number depends on the type of data being stored in the container (the bigger the elements, the fewer of them you can store), the type of container, and the operating system. For example, on my system, max_size() returns 32,767 for a vector type int.
// an array of floats // initialize vector to array // empty vector of size 4 // swap contents of v1 and v2
while( !v2.empty() ) // until vector is empty, { cout << v2.back() << ' '; // display the last element v2.pop_back(); // remove the last element } // output: 4.4 3.3 2.2 1.1 } Ive used two new vector constructors in this program. The first initializes the vector v1 with the values of a normal C++ array passed to it as an argument. The arguments to this constructor are pointers to the start of the array and to the element one past the end. The second constructor sets v2 to an initial size of 4, but does not supply any initial values. Both vectors hold type float. The swap() member function exchanges all the data in one vector with all the data in another, keeping the elements in the same order. In this program, only garbage data is in v2, so its swapped with the data in v1. I display v2 to show it now contains the data that was in v1. The output is 4.4, 3.3, 2.2, 1.1 The back() member function returns the value of the last element in the vector. I display this value with cout. The pop_back() member function removes the last element in the vector. Thus, each time through the loop there is a different last element. (Its a little surprising that pop_back() does not simultaneously return the value of the last element and remove it from the vector, as youve seen in previous examples with stacks, but it doesnt, so back() must be used as well.) C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 622 of 701
Some member functions, such as swap(), also exist as algorithms. When this is the case, the member function version is usually provided because its more efficient for that particular container than the algorithm version. Sometimes you can use the algorithm as well, for example, to swap elements in two different kinds of containers.
// erase element 2
cout << "\nAfter erasure: "; for(j=0; j<v.size(); j++) // display all elements cout << v[j] << ' '; } The insert() member function (at least this version of it) takes two arguments: the place where an element will be inserted in a container and the value of the element. I add 2 to the begin() member function to specify element 2 (the third element) in the vector. The elements from the insertion point to the end of the container are moved upward to make room and the size of the container is increased by 1. The erase() member function removes the element at the specified location. The elements above the deletion point are moved downward and the size of the container is decreased by 1. Heres the output from VECTINS: Before insertion: 100 110 120 130 After insertion: 100 110 115 120 130 After erasure: 100 110 120 130
Lists
An STL list container is a doubly linked list in which each element contains a pointer not only to the next element but also to the preceding one. The container stores the address of both the front (first) and the back (last) elements, which makes for fast access to both ends of the list.
// list.cpp // demonstrates push_front(), front(), pop_front() #include <iostream.h> #include <list.h> void main() { list<int> ilist; ilist.push_back(30); ilist.push_back(40); ilist.push_front(20); ilist.push_front(10); int size = ilist.size();
// number of items
for(int j=0; j<size; j++) { cout << ilist.front() << ' '; // read item from front ilist.pop_front(); // pop item off front } } The push_front(), pop_front(), and front() member functions are similar to push_back(), pop_back(), and back(), which youve already seen at work with vectors. Note that you cant use random access for list elements because such access is too slow; that is, the [] operator is not defined for lists. If it were, this operator would need to traverse along the list, counting elements as it went, until it reached the correct one, a time-consuming operation. If you need random access, you should use a vector or a deque. Lists are appropriate when you make frequent insertions and deletions in the middle of the list. Lists are not efficient for vectors and deques because all the elements above the insertion or deletion point must be moved. However, its quick for lists because only a few pointers need to be changed to insert or delete a new item. The insert() and erase() member functions are used for list insertion and deletion, but they require the use of iterators, so Ill postpone a discussion of these functions.
} The first list is in backward order, so I return it to normal sorted order using the reverse() member function. (Its quick to reverse a list container because both ends are accessible.) This is necessary because the second member function, merge(), operates on two lists and requires both of them to be in sorted order. Following the reversal, the two lists are 10, 20, 30, 40 15, 20, 25, 30, 35 Now the merge() function merges list2 into list1, keeping everything sorted and expanding list1 to hold the new items. The resulting content of list1 is 10, 15, 20, 20, 25, 30, 30, 35, 40 Finally, I apply the unique() member function to list1. This function finds adjacent pairs of elements with the same value and removes all but the first. The contents of list1 are then displayed. The output of LISTPLUS is 10, 15, 20, 25, 30, 35, 40 To display the contents of the list, I use the front() and pop_front() member functions in a for loop. Each element, from front to back, is displayed and then popped off the list. The process of displaying the list destroys it. This may not always be what you want, but at the moment its the only way you have learned to access successive list elements. Iterators, described in the next session, will solve this problem.
Deques
A deque is a variation of a vector. Like a vector, it supports random access using the [] operator. However, unlike a vector (but like a list), a deque can be accessed at the front as well as the back. Its a sort of double-ended vector, supporting push_front(), pop_front(), and front(). Memory is allocated differently for vectors and queues. A vector always occupies a contiguous region of memory. If a vector grows too large, it may need to be moved to a new location where it will fit. A deque, on the other hand, can be stored in several noncontiguous areas; it is segmented. A member function, capacity(), returns the largest number of elements a vector can store without being moved, but capacity() isnt defined for deques because deques dont need to be moved. Listing 12-16 shows DEQUE. Listing 12-16 DEQUE // deque.cpp // demonstrates push_back(), push_front(), front() #include <iostream.h> #include <deque.h> void main() { deque<int> deq; deq.push_back(30); deq.push_back(40); deq.push_back(50); deq.push_front(20); deq.push_front(10); deq[2] = 33; // push items on back
for(int j=0; j<deq.size(); j++) cout << deq[j] << ' '; // display items } Youve already seen examples of push_back(), push_front(), and operator []. They work the same for deques as for other containers. The output of this program is 10 20 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 625 of 701
Figure 12-2 shows some important member functions for the three sequential containers.
Quiz 3 1. A vector is an appropriate container if you a. want to insert lots of new elements at arbitrary locations in the vector. b. dont know at runtime how many elements the vector will hold. c. want to insert new elements, but always at the front of the container. d. are given an index number and you want to access the corresponding element quickly.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 626 of 701
e. are given an element value and you want to access the corresponding element quickly. 2. Which of the following statements are true? a. The pop_back() member function returns the value of the element at the back of the container. b. The back() member function returns the value of the element at the back of the container. c. The pop_back() member function removes the element at the back of the container. d. The back() member function removes the element at the back of the container. e. The swap() member function interchanges two adjacent elements in a container. 3. If you define a vector v with the default constructor, define another vector w with a oneargument constructor to a size of 11, and insert 3 elements into each of these vectors with push_back(), then a. size() will return 0 for v and 3 for w. b. size() will return 3 for v and 3 for w. c. size() will return 3 for v and 14 for w. d. max_size() will return two values that differ by 11. e. max_size() will return the same value for both vectors. 4. Which of the following are true? a. The reverse() member function reverses pairs of adjacent elements. b. The merge() member function merges pairs of adjacent elements from one container into another container. c. The unique() member function returns the first of a sequence of elements that have the same value. d. The push_front() member function is not defined for lists. e. The push_front() member function is not defined for deques. 5. In a deque, a. data can be quickly inserted or deleted at any arbitrary location. b. data can be inserted or deleted at any arbitrary location, but the process is relatively slow. c. data can be quickly inserted or deleted at either end.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 627 of 701
d. data can be inserted or deleted at either end, but the process is relatively slow. e. elements always occupy contiguous locations in memory. Exercise 1
Write a program that applies reverse(), merge(), and unique() to vectors.
Exercise 2
Write a program that uses insert(), erase(), and swap() on a deque.
Session 4: Iterators
Iterators may seem a bit mysterious, yet they are central to the operation of the STL. In this session, Ill first discuss the twin roles played by iterators: as smart pointers and as a connection between algorithms and containers. Then Ill show some examples of their use.
SmartPointer sptr = start_address; for(int j=0; j<SIZE; j++) cout << *sptr++; }
Whose Responsibility?
Assuming I need a class of smart pointers, whose responsibility is it to create this class? As a class user, I certainly dont want the added complexity of writing the code myself. On the other hand, theres a problem with making smart pointers members of the container class. I may need many pointers in a container, and it would be complicated for a container object itself to store these values. How many should be included? Should every container include, say, three pointers to its own elements? Or should it maintain a table of such pointers? The application should be able to create such a pointer whenever it needs one without such restrictions or complexity. The approach chosen by the STL is to make smart pointers, called iterators, into a completely separate class (actually a family of templatized classes). The class user creates iterators by defining them to be objects of such classes.
Iterators as an Interface
Besides acting as smart pointers to items in containers, iterators serve another important purpose in the STL. They determine which algorithms can be used with which containers. Why is this necessary? In some theoretical sense, you should be able to apply every algorithm to every container. In fact, many algorithms will work with all the STL containers. However, some algorithms are inefficient (i.e., slow) when used with some containers. The sort() algorithm, for example, needs random access to the container its trying to sort; otherwise, it would need to iterate through the container to find each element before moving it, a time-consuming approach. Similarly, to be efficient, the reverse() algorithm needs to iterate backward as well as forward through a container. Iterators provide a surprisingly elegant way to match appropriate algorithms with containers. As I noted, you can think of an iterator as a cable, like the cable used to connect your computer and printer. One end of the cable plugs into a container and the other plugs into an algorithm. However, not all cables plug into all containers, and not all cables plug into all algorithms. If you try to use an algorithm thats too powerful for a given container type, then you wont be able to find a cable (an iterator) to connect them. If you try it, you will receive a compiler error alerting you to the problem. How many kinds of iterators (cables) do you need to make this scheme work? As it turns out, only five types are necessary. Figure 12-3 shows these five categories, arranged from bottom to top in order of increasing sophistication, except that input and output are equally unsophisticated. (This is not an inheritance diagram.)
Figure 12-3 Iterator categories If an algorithm needs only to step through a container in a forward direction, reading (but not writing to) one item after another, it can use an input iterator to connect itself to the container. If it steps through the container in a forward direction but writes to the container instead of reading from it, it can use an output iterator. If it steps along in the forward direction and both reads and writes, it must use a forward iterator. If it must be able to step both forward and back, it must use a bidirectional iterator. And if it must access any item in the container instantly, without stepping along to it, it must use a random-access iterator. Table 12-7 shows which operations each iterator supports. Table 12-7 Capabilities of different iterator categories Iterator Step Forward Read Write Step Back Random Access ++ value=*i *i=value -- [n] Random-access iterator x x x x x Bidirectional iterator x x x x Forward iterator x x x Output iterator x x Input iterator x x
As you can see, all the iterators support the ++ operator for stepping forward through the container. The input iterator can also use the * operator on the right side of the equal sign (but not on the left): value = *iter; The output iterator can use the * operator only on the right: *iter = value; The forward iterator handles both input and output and the bidirectional iterator can be decremented as well as incremented. The random-access iterator can use the [] operator (as well as simple arithmetic operators such as + and -) to access any element quickly. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 630 of 701
An algorithm can always use an iterator with more capability than it needs. If it needs a forward iterator, for example, its all right to plug it into a bidirectional iterator or a random-access iterator.
How does the STL enforce the use of the correct iterator for a given container? When you define an iterator, you must specify what kind of container it will be used for. For example, if youve defined a list holding elements of type int, list<int> iList; // list of ints then to define an iterator to this list you say list<int>::iterator it; // iterator to list-of-ints When you do this, the STL automatically makes this iterator a bidirectional iterator because thats what a list requires. An iterator to a vector or a deque, on the other hand, is automatically created as a random-access iterator. This automatic selection process is implemented, deep in ITERATOR.H and other header files, by having an iterator class for a specific container be derived (inherited) from a more general iterator class thats appropriate to a specific container. Thus, the iterators to vectors and deques are derived from the random_access_iterator class, whereas iterators to lists are derived from the bidirectional_iterator class. You now see how containers are matched to their end of the fanciful iterator cables. A cable doesnt actually plug into a container; it is (figuratively speaking) hard-wired to it, like the cord on a toaster. Vectors and deques are always wired to random-access cables, whereas lists (and all the associative containers, which youll encounter in Session 6) are always wired to bidirectional cables.
Again, although each algorithm requires an iterator with a certain level of capability, a more powerful iterator will also work. The replace() algorithm requires a forward iterator, but it will work with a bidirectional or a randomaccess iterator as well. Now imagine that algorithms have connectors with pins sticking out, as shown in Figure 12-4. Those requiring random-access iterators have five pins, those requiring bidirectional iterators have four pins, those requiring forward iterators have three pins, and so on. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 631 of 701
Figure 12-4 Iterators connecting containers and algorithms The algorithm end of an iterator (a cable) has a connector with a certain number of holes.You can plug a five-hole iterator into a five-pin algorithm, and you can also plug it into an algorithm with four or fewer pins. However, you cant plug a four-hole (bidirectional) iterator into a five-pin (random-access) algorithm. So vectors and deques, with random-access iterators, can be plugged into any algorithm, whereas lists and associative containers, with only fourhole bidirectional iterators, can be plugged into less powerful algorithms.
Iterators at Work
Using iterators is considerably simpler than talking about them. Youve already seen several examples of one of the more common uses where iterator values are returned by a containers begin() and end() member functions.
Ive disguised the fact that these functions return iterator values by treating them as if they were pointers. Now lets see how actual iterators are used with these and other functions.
Data Access
In containers that provide random-access iterators (vector and queue), its easy to iterate through the container using the [] operator. Containers such as lists, which dont support random access, require a different approach. In previous examples, Ive used a destructive readout to display the contents of a list by popping off the items one by one, as in the LIST and LISTPLUS examples in Session 3. A more practical approach is to define an iterator for the container. The LISTOUT program (Listing 12-17) shows how that might look. Listing 12-17 LISTOUT // listout.cpp // iterator and for loop for output #include <iostream.h> #include <list.h> void main() { int arr[] = { 2, 4, 6, 8 }; // array of ints list<int> iList(arr, arr+4); // list initialized to array list<int>::iterator it; // iterator to list-of-ints for(it = iList.begin(); it != iList.end(); it++) cout << *it << ' '; } The program simply displays the contents of the iList container. The output is 2 4 6 8 I define an iterator of type list<int> to match the container type. As with a pointer variable, I must give an iterator a value before using it. In the for loop, I initialize it to iList.begin(), the start of the container. I can increment it with the ++ operator so it steps through the elements in a container, and I can dereference it with the * operator to obtain the value of each element it points to. I can also compare it for equality using the != operator so I can exit the loop when it reaches the end of the container at iList.end(). An equivalent approach, using a while loop instead of a for loop, might be it = iList.begin(); while( it != iList.end() ) cout << *it++ << ' '; The *it++ syntax is the same as it would be for a pointer.
Data Insertion
I can use similar code to place data into existing elements in a container, as shown in LISTFILL (Listing 12-18). Listing 12-18 LISTFILL // listfill.cpp // uses iterator to fill list with data #include <iostream.h> #include <list.h> void main() { list<int> iList(5); list<int>::iterator it; int data = 0;
// fill list with data for(it = iList.begin(); it != iList.end(); it++) *it = data += 2; // display list 2 for(it = iList.begin(); it != iList.end(); it++) cout << *it << ' '; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
} The first loop fills the container with the int values 2, 4, 6, 8, 10, showing that the overloaded * operator works on the left side of the equal sign as well as on the right. The second loop displays these values.
// fill list with data for(it = iList.begin(); it != iList.end(); it++) *it = data += 2; // look for number 8 it = find(iList.begin(), iList.end(), 8); if( it != iList.end() ) cout << "\nFound 8"; else cout << "\nDid not find 8."; } As an algorithm, find() takes three arguments. The first two are iterator values specifying the range to be searched and the third is the value to be found. Here I fill the container with the same 2, 4, 6, 8, 10 values as in the last example. Then I use the find() algorithm to look for the number 8. If find() returns iList.end(), I know its reached the end of the container without finding a match. Otherwise, it must have located an item with the value 8. Here the output is Found 8 Can I use the value of the iterator to tell where in the container the 8 is located? You might think the offset of the matching item from the beginning of the container could be calculated from (it - iList.begin()). However, this is not a legal operation on the iterators used for lists. A list iterator is only a bidirectional iterator, so I cant perform arithmetic on it. I can do arithmetic on random-access iterators, such as those used with vectors and queues. Thus, if I were searching a vector v rather than a list iList, I could rewrite the last part of ITERFIND like this: it = find(v.begin(), v.end(), 8); if( it != v.end() ) cout << "\nFound 8 at location " << (it-v.begin() ); else cout << "\nDid not find 8."; The output would be Found 8 at location 3 Listing 12-20 shows another example in which an algorithm uses iterators as arguments. This one uses the copy() algorithm with a vector. The user specifies a range of locations to be copied from one vector to another, and the program copies them. Iterators specify this range. Listing 12-20 ITERCOPY // itercopy.cpp C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 634 of 701
// uses iterators for copy() algorithm #include <iostream.h> #include <vector.h> #include <algo.h> void main() { int beginRange, endRange; int arr[] = { 11, 13, 15, 17, 19, 21, 23, 25, 27, 29 }; vector<int> v1(arr, arr+10); // initialized vector vector<int> v2(10); // uninitialized vector cout << "Enter range to be copied (example: 2 5): "; cin >> beginRange >> endRange; vector<int>::iterator it1 = v1.begin() + beginRange; vector<int>::iterator it2 = v1.begin() + endRange; vector<int>::iterator it3; // copy range from v1 to v2 it3 = copy( it1, it2, v2.begin() ); // (it3 -> last item copied) it1 = v2.begin(); // iterate through range while(it1 != it3) // in v2, displaying values cout << *it1++ << ' '; } Some interaction with this program is Enter range to be copied (example: 2 5): 3 6 17 19 21 I dont want to display the entire contents of v2, only the range of items copied. Fortunately, copy() returns an iterator that points to the last item (actually one past the last item) that was copied to the destination container, v2 in this case. The program uses this value in the while loop to display only the items copied.
Quiz 4 1. Which of the following are true? a. An iterator is an object of an appropriate container class. b. An iterator indicating a specific container element is a class, not an object. c. An iterator class is derived from an appropriate container class. d. An iterator class for a specific container category (such as a list) is derived from a more general iterator class that is appropriate for that container (such as bidirectional_iterator). e. An iterator class is templatized. 2. An iterator a. knows how big the objects in its container are.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 635 of 701
b. can figure out where the objects in its container are located in memory. c. will usually point to an object in its container. d. can always move forward or backward through its container. e. requires the objects in its container to be located contiguously in memory. 3. Which of these statements are true? a. Iterators prevent the use of algorithms with containers that dont support that algorithms task. b. An algorithm requiring random-access iterators will work with a list. c. An algorithm requiring bidirectional iterators will work with a vector. d. You can always use an iterator thats less powerful than a container requires. e. You can always use an iterator thats more powerful than an algorithm requires. 4. If iter is an iterator variable that is used in a while loop, then a. before entering the loop, iter should probably have been initialized to point to some element in a container. b. iter can point to ordinary C++ array elements. c. iter cannot be decremented. d. the expression *iter++ will obtain the value of the element pointed to by iter. e. the expression *iter++ will cause iter to point to the next element. 5. The copy() algorithm returns a. the number of elements copied. b. an iterator to an element in the range copied from. c. an iterator to an element in the range copied to. d. an iterator to the last item copied. e. void. Exercise 1
Write a program that allows you to copy any range in a container to any other location in the same container where it will fit. Have the user specify the range and the location where it will be copied. Can you copy to a point inside the range? Try using copy_backward() instead of copy().
Exercise 2
Write a program that will find not only the first element in a container that has a specified value, but all elements in the container that have that value.
Midchapter Discussion
George: Estelle: Don: Estelle: Don: Just when I thought I was getting a grip on C++, along comes all this new stuff. Its like learning another language! Right, with all these containers and dozens and dozens of algorithms and member functions. It doesnt seem fair. But its very powerful. If you ever need to store data in anything but an array, the STL will save you a huge amount of trouble. But have you looked in the STL header files? Some of that stuff is seriously complicated. Well, you dont really need to understand whats going on in those header files. I mean, it builds character to wade through them, but the beauty of OOP is that you can use classes without understanding them. All you need to understand is the user interface. You mean the functions and stuff that go with a container. Exactly. The thing I dont understand is why we need iterators. To be smart pointers and to keep you from using the wrong algorithm with the wrong container. I know, but isnt there a simpler way? Well, maybe you could just use pointers. But then youd need to handle all the memory management stuff for a container by yourself, so youd know where it was. OK, I can see that. But where did they get the idea to use iterators to connect algorithms to containers? That way you can make the same algorithm work with many kinds of containers. I think the STL is the first set of container classes that uses iterators that way. Earlier container classes used member functions to do all the work, so there was a lot of duplication. If you had 10 containers and they each had 20 member functions, that was 200 member functions you had to worry about. In the STL, theres only one version of each algorithm. Pretty clever. Ill say.
Estelle: Don:
Iterator Adapters
The STL provides three variations on the normal iterator. These are the reverse iterator, the insert iterator, and the raw storage iterator. The reverse iterator allows you to iterate backward through a container. The insert iterator changes the behavior of various algorithms, such as copy() and merge(), so they insert data into a container rather than overwriting existing data. The raw storage iterator allows output iterators to store data in uninitialized memory, but its used in specialized situations and Ill ignore it here.
Reverse Iterators
Suppose you want to iterate backward through a container from the end to the beginning. You might think you could say something like list<int>::iterator it; // normal iterator it = iList.end(); // start at end while( it != iList.begin() ) // go to beginning cout << *it-- << ' '; // decrement iterator but unfortunately this doesnt work. For one thing, the range will be wrong (from n to 1, instead of from n-1 to 0). To iterate backward, you must use a reverse iterator. The ITEREV program (Listing 12-21) shows an example where a reverse iterator is used to display the contents of a list in reverse order. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 637 of 701
Listing 12-21 ITEREV // iterev.cpp // demonstrates reverse iterator #include <iostream.h> #include <list.h> void main() { int arr[] = { 2, 4, 6, 8, 10 }; list<int> iList(arr, arr+5);
list<int>::reverse_iterator revit;
revit = iList.rbegin(); // iterate backwards while( revit != iList.rend() ) // through list, cout << *revit++ << ' '; // displaying output } The output of this program is 10 8 6 4 2 You must use the member functions rbegin() and rend() when you use a reverse iterator. (But dont try to use them with a normal forward iterator.) Confusingly, youre starting at the end of the container, but the member function is called rbegin(). Also, you must increment the iterator. Dont try to decrement a reverse iterator; revit-- doesnt do what you want. With a reverse_iterator, always go from rbegin() to rend() using the increment operator.
Insert Iterators
Some algorithms, such as copy(), overwrite the existing contents (if any) of the destination container. The COPYDEQ program (Listing 12-22), which copies from one deque to another, provides an example. Listing 12-22 COPYDEQ // copydeq.cpp // demonstrates normal copy with queues #include <iostream.h> #include <deque.h> #include <algo.h> void main() { int arr1[] = { 1, 3, 5, 7, 9 }; deque<int> d1(arr1, arr1+5); int arr2[] = { 2, 4, 6, 8, 10 }; deque<int> d2(arr2, arr2+5);
// initialize d1
// initialize d2
// copy d1 to d2 copy( d1.begin(), d1.end(), d2.begin() ); for(int j=0; j<d2.size(); j++) // display d2 cout << d2[j] << ' '; } The output of this program is 1 3 5 7 9 The contents of d1 have been written over the contents of d2, so when I display d2, theres no trace of its former (even-numbered) contents. Usually this behavior is what you want. Sometimes, however, youd rather have copy() insert new elements into a container along with the old ones than overwrite the old ones. You can cause this behavior by using an insert iterator. There are three flavors of this iterator: C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 638 of 701
back_inserter inserts new items at the end front_inserter inserts new items at the beginning inserter inserts new items at a specified location
The DINSITER program (Listing 12-23) shows how to use a back inserter. Listing 12-23 DINSITER // dinsiter.cpp // demonstrates insert iterators with queues #include <iostream.h> #include <deque.h> #include <algo.h> void main() { int arr1[] = { 1, 3, 5, 7, 9 }; deque<int> d1(arr1, arr1+5); int arr2[] = {2, 4, 6}; deque<int> d2(arr2, arr2+3);
// initialize d1
// initialize d2
copy( d1.begin(), d1.end(), back_inserter(d2) ); cout << "\nd2: "; // display d2 for(int j=0; j<d2.size(); j++) cout << d2[j] << ' '; } The back inserter uses the containers push_back() member function to insert the new items at the end of the target container d2, following the existing items. The source container d1 is unchanged. The output of the program, which displays the new contents of d2, is 2 4 6 1 3 5 7 9 If I specified a front inserter instead, copy( d1.begin(), d1.end(), front_inserter(d2) ) then the new items would be inserted into the front of the container. The underlying mechanism is the containers push_front() member function, which pushes the items one at a time, effectively reversing their order. The output would be 9 7 5 3 1 2 4 6 I can also insert the new items starting at any arbitrary element by using the inserter version of the insert iterator. For example, to insert the new items at the beginning of d2, I would say copy( d1.begin(), d1.end(), inserter(d2, d2.begin() ); The first argument to inserter is the container to be copied into and the second is an iterator pointing to the location where copying should begin. Because inserter uses the containers insert() member function, the order of the elements is not reversed. The output resulting from this statement would be 1 3 5 7 9 2 4 6 By changing the second argument to inserter, I could cause the new data to be inserted anywhere in d2. Note that a front_inserter cant be used with a vector, because vectors dont have a push_front() member function; they can be accessed only at the end.
Stream Iterators
Stream iterators allow you to treat files and I/O devices (such as cin and cout) as if they were iterators. This makes it easy to use files and I/O devices as arguments to algorithms. (This is another demonstration of the versatility of using iterators to link algorithms and containers.) C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 639 of 701
The major purpose of the input and output iterator categories is to support these stream iterator classes. Input and output iterators make it possible for appropriate algorithms to be used directly on input and output streams. Stream iterators are actually objects of classes that are templatized for different types of input or output. There are two stream iterators: ostream_iterator and istream_iterator. Lets look at them in turn.
Theres no displayable output from FOUTITER, but you can examine the file ITER.DAT with an editor to see that it contains the data, which should be 11 21 31 41 51
list
// copy from cin to fList copy( cin_iter, end_of_stream, fList.begin() ); cout << endl; // display fList ostream_iterator<float> ositer(cout, "--"); copy(fList.begin(), fList.end(), ositer); } Some interaction with INITER is Enter 5 floating-point numbers: 1.1 2.2 3.3 4.4 5.5 1.1--2.2--3.3--4.4--5.5-Notice that for copy(), because the data coming from cin is the source and not the destination, I must specify both the beginning and the end of the range of data to be copied. The beginning is an istream_iterator connected to cin, which I define as cin_iter using the one-argument constructor. But what about the end of the range? The no-argument (default) constructor to istream_iterator plays a special role here. It always creates an istream_iterator object that represents the end of the stream.
How does the user generate this end-of-stream value when inputting data? By typing the key combination, which transmits the end-of-file character normally used for streams. (Pressing wont end the file, although it will delimit the numbers.) I use an ostream_iterator to display the contents of the list, although of course there are many other ways to do this. At least on my compiler, I must perform any display output, such as the Enter 5 floating-point numbers prompt, not only before using the istream iterator, but even before defining it. As soon as this iterator is defined, it locks up the display waiting for input. Listing 12-27, FINITER, uses a file instead of cin as input to the copy() algorithm. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 641 of 701
Listing 12-27 FINITER // finiter.cpp // demonstrates istream_iterator with files #include <fstream.h> #include <list.h> #include <algo.h> void main() { list<int> iList; ifstream infile("ITER.DAT");
// empty list // create input file object // (ITER.DAT must already exist) // istream iterators istream_iterator<int, ptrdiff_t> file_iter(infile); // file istream_iterator<int, ptrdiff_t> end_of_stream; // eos // copy from infile to iList copy( file_iter, end_of_stream, back_inserter(iList) ); cout << endl; // display iList ostream_iterator<int> ositer(cout, "--"); copy(iList.begin(), iList.end(), ositer); } The output from FINITER is 11--21--31--31--41--51-I define an ifstream object to represent the ITER.DAT file, which must already exist and contain data. (The FOUTITER program, if you ran it, will have generated this file.) Instead of using cout, as in the istream iterator in the INITER example, I use the ifstream object named infile. The end-of-stream object is the same. Ive made another change in this program: It uses a back_inserter to insert data into iList. This makes it possible to define iList as an empty container instead of one with a specified size. This often makes sense when reading input because you may not know how many items will be entered.
Quiz 5 1. To use a reverse_iterator, you should a. begin by initializing it to end(). b. begin by initializing it to rend(). c. begin by initializing it to rbegin(). d. increment it to move backward through the container. e. decrement it to move backward through the container. 2. Which of the following statements are true? a. The back_inserter iterator always causes the new elements to be inserted following the existing ones. b. The inserter iterator always causes the newly inserted elements to be placed before the existing ones.
c. Insert iterators are normally used as arguments to the member functions insert(), push_front(), and push_back(). d. If you want to overwrite existing data, you should use the front_inserter iterator. e. Before using an insert iterator, you must write a statement to define it. 3. Stream iterators a. provide a handy way to perform I/O on container elements. b. are random-access iterators. c. allow you to treat the display and keyboard devices as if they were iterators. d. allow you to treat files as if they were iterators. e. cannot be used for algorithm arguments that require random-access iterators. 4. Which of the following statements are largely correct? a. The first argument to an ostream_iterator must be an ostream object. b. The first argument to an ostream_iterator represents an output device or file. c. An ostream_iterator is a forward_iterator. d. The second argument to an ostream_iterator specifies the EOL (end-of-line) character. e. The second argument to an ostream_iterator specifies the EOS (end-of-stream) character. 5. Which of the following are accurate statements? a. When using an istream_iterator with copy(), the end of the range to be copied from is indicated by the end() member function. b. When using an istream_iterator with copy(), the end of the range to be copied from is indicated by an istream_iterator object defined with the default constructor. c. An istream_iterator is an input_iterator. d. The second argument to an istream_iterator specifies the EOL (end-of-line) character. e. The second argument to an istream_iterator specifies the EOS (end-of-stream) character. Exercise 1
Write a program that copies one file to another using the copy() algorithm and stream iterators. The user should supply both source and destination file names to the program. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 643 of 701
Exercise 2
Write a program in which the merge() algorithm combines the contents of two containers and sends the results to cout using a stream iterator.
Figure 12-5 A set of string objects Listing 12-28, SET, shows a set that stores objects of class string. Again, I use the current Borland header file, CSTRING.H, to invoke the string class. Depending on your compiler, you may need to change this header file name to STRING, BSTRING.H, or some other name (see Appendixes C and D). Listing 12-28 SET // set.cpp // set stores string objects #include <iostream.h> #include <set.h> #include <cstring.h> void main() { // array of string objects string names[] = {"Juanita", "Robert", "Mary", "Amanda", "Marie"}; // initialize set to array set<string, less<string> > nameSet(names, names+5); // iterator to set set<string, less<string> >::iterator iter; nameSet.insert("Yvette"); // insert some more names nameSet.insert("Larry"); nameSet.insert("Robert"); // no effect; already in set nameSet.insert("Barry"); nameSet.erase("Mary"); // erase a name // display size of set cout << "\nSize=" << nameSet.size() << endl; iter = nameSet.begin(); // display members of set while( iter != nameSet.end() ) cout << *iter++ << '\n'; string searchName; // get name from user C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
cout << "\nEnter name to search for: "; cin >> searchName; // find matching name in set iter = nameSet.find(searchName); if( iter == nameSet.end() ) cout << "The name " << searchName << " is NOT in the set."; else cout << "The name " << *iter << " IS in the set."; } To define a set, I specify the type of objects to be stored (in this case, class string) and the function object that will be used to order the members of the set. Here I use less<>() applied to string objects. As you can see, a set has an interface similar to other STL containers. I can initialize a set to an array and insert new members into a set with the insert() member function. To display the set, I can iterate through it. To find a particular entry in the set, I use the find() member function. (Sequential containers use find() in its algorithm version.) Heres some sample interaction with SET, where the user enters George as the name to be searched for: Size = 7 Amanda Barry Juanita Larry Marie Robert Yvette Enter name to search for: George The name George is NOT in the set. Of course, the speed advantage of searching an associative container isnt apparent until you have many more entries than in this example. Lets look at an important pair of member functions available only with associative containers. Listing 12-29, SETRANGE, shows the use of lower_bound() and upper_bound(). Listing 12-29 SETRANGE // setrange.cpp // tests ranges within a set #include <iostream.h> #include <set.h> #include <cstring.h> void main() { // set of string objects set<string, less<string> > organic; // iterator to set set<string, less<string> >::iterator iter; organic.insert("Curine"); // insert organic compounds organic.insert("Xanthine"); organic.insert("Curarine"); organic.insert("Melamine"); organic.insert("Cyanimide"); organic.insert("Phenol"); organic.insert("Aphrodine"); organic.insert("Imidazole"); organic.insert("Cinchonine"); C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 646 of 701
organic.insert("Palmitamide"); organic.insert("Cyanimide"); iter = organic.begin(); // display set while( iter != organic.end() ) cout << *iter++ << '\n'; string lower, upper; // display entries in range cout << "\nEnter range (example C Czz): "; cin >> lower >> upper; iter = organic.lower_bound(lower); while( iter != organic.upper_bound(upper) ) cout << *iter++ << '\n'; } The program first displays an entire set of organic compounds. The user is then prompted to type in a pair of key values, and the program will display those keys that lie within this range. Heres some sample interaction: Aphrodine Cinchonine Curarine Curine Cyanimide Imidazole Melamine Palmitamide Phenol Xanthine Enter range (example C Czz): Aaa Curb Aphrodine Cinchonine Curarine The lower_bound() member function takes an argument that is a value of the same type as the key. It returns an iterator to the first entry that is not less than this argument (where the meaning of less is determined by the function object used in the sets definition). The upper_bound() function returns an iterator to the first entry that is greater than its argument. Together, these functions allow you to access a specified range of values.
// definition (C string) // key (string object) // shorten type name typedef map< string, string, less<string> > map_type; map_type diction; // define a dictionary // insert sample entries diction.insert( map_type::value_type("cat", "A small furry animal that chases mice.") );
diction.insert( map_type::value_type("dog", "A large hairy animal that chases sticks.") ); while(true) // get entries from user { cout << "\nEnter word (or \"done\"): "; cin >> word; if(word == "done") break; cout << "Enter definition: "; cin.get(def, SIZE); // (reads embedded blanks) diction.insert( map_type::value_type(word, def) ); } map_type::iterator iter; // make an iterator iter = diction.begin(); // set to beginning of dictionary cout << endl << endl; while( iter != diction.end() ) { // display "word -- definition" cout << (*iter).first << " -- " << (*iter).second << endl; ++iter; } } The key values cat and dog and their definitions are already installed in the map by the program, but the user is encouraged to enter other word-definition pairs. Heres some sample interaction with the program when the user defines a snail: Enter word (or "done"): snail Enter definition: A small shelled animal that eats gardens. Enter word (or "done"): done cat -- A small furry animal that chases mice. dog -- A large hairy animal that chases sticks. snail -- A small shelled animal that eats gardens. In the MAP program, the user cant insert definitions for cat or dog because these keys are already in the container and a map allows only one key with a given value. A multimap would allow the same key value to appear more than once. The expression for the data type of the map container map< string, string, less<string> > is rather unwieldy to write. Because this expression appears several times in the listing, I use a typedef to condense it into the more manageable expression map_type: typedef map< string, string, less<string> > map_type; typedefs are typically used in this way when dealing with the templatized STL containers, especially maps and sets.
Pairs
The argument to the insert() function, map_type::value_type(word, def), may look a little ponderous. It arises because maps and multimaps actually hold objects of type pair. The class pair is itself templatized so its objects can contain two objects of two other classes. You can make your own pair objects: pair<string, string> dictopair = make_pair("cat", "A cat is a ....."); In the MAP.H header file, value_type is defined as a pair of values: typedef pair<const Key, T> value_type;
This makes it convenient to supply values to insert(), as is done in this example and similar functions. Notice that the char* strings such as cat are converted automatically to string objects to match the type expected by value_type. The names of the two members of pair are (appropriately) first and second. These members are used in the while loop to display the contents of the map: cout << (*iter).first << " -- " << (*iter).second << endl; Roughly speaking, when you combine values to put them into a map, you combine them with value_type, but to separate them when you take them out again, you use first and second.
The [ ] Operator
You can use the find() or lower_bound()/upper_bound() functions to search for a particular entry in a map, just as you can in sets. However, you can also use the [] operator. Ill demonstrate this with a map that holds pairs consisting of numbers and nautical expressions. In the 18th century, ships carried signal books listing hundreds of flag messages. To send a message, one ship would hoist flags indicating a certain number; another ship, perhaps several miles away, would read the flags with telescopes and then use the number to look up the message in its own flag book. The example program stores pairs consisting of flag numbers and their corresponding messages. If 18thcentury ships had been equipped with computers, the program would have made looking up the message a much faster process. Listing 12-31 shows MAPBRACK. Listing 12-31 MAPBRACK // mapbrack.cpp // demonstrates operator [] used for maps #include <iostream.h> #include <map.h> void main() { typedef map< long, char*, less<int> > map_type; map_type flaglist; // map holding flag messages long code_number; flaglist.insert( map_type::value_type(14072, "I are listing sharply and will soon founder.") ); flaglist.insert( map_type::value_type(12023, "The enemy is within sight and approaching rapidly.") ); flaglist.insert( map_type::value_type(16067, "I have dispatches. Prepare to receive our longboat.") ); flaglist.insert( map_type::value_type(13045, "Fall in line astern of me.") ); flaglist.insert( map_type::value_type(19092, "Stand off. This coast is rocky and uncharted.") ); while(true) // get code number from user { cout << "\n\nEnter flag code number (0 to terminate): "; cin >> code_number; if( !code_number ) break; cout << "Message is:" << endl; cout << flaglist[code_number]; // access value with key } } Some sample interaction with the program might be Enter flag code number (0 to terminate): 16067 Message is: I have dispatches. Prepare to receive our longboat. Enter flag code number (0 to terminate): 0 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
The expression flaglist[code_number]; uses the key (the code number) in place of the index and returns the associated map value, in this case a char* string, which is then displayed. The overloaded [] operator provides an intuitively clear way to access entries in a map.
Quiz 6 1. In an associative container, a. values are stored in sorted order. b. keys are stored in sorted order. c. sorting is always in alphabetical or numerical order. d. you must use the sort() algorithm to keep the contents sorted. e. you cant use iterators. 2. When defining a set, you must specify a. the underlying container that implements the set (vector, list, or deque). b. the data type of the keys. c. the data type of the values. d. a certain function object. e. the comparison that will be used to specify the order of the elements. 3. Which of the following statements are true? a. In a set, the insert() member function inserts a key in sorted order. b. In a multimap, the insert() member function does nothing if its argument is the same as an existing element in the container. c. If you initialize an associative container using an array, the order of the elements will necessarily be preserved. d. A map can have two or more elements with the same key value. e. The erase() member function wont remove an element from a set because if it did, the ordering would not be preserved.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 651 of 701
4. A map a. is like a set, but can store multiple key values. b. can be thought of as a French dictionary. c. can use an iterator to access elements. d. stores pairs, each of which must hold an index number and an object. e. can use the [] operator to access elements. 5. In a map or multimap, the argument to the insert() member function a. requires that the data type of the container be specified. b. requires the typedef value_type. c. is an iterator to a key value. d. is a key-value pair. e. is either a key or a value. Exercise 1
Write a program that merges two maps into a single map.
Exercise 2
Write a program that creates a frequency table of the words in a text file. You can use a map where the words are the keys and the number of times the word appears in the file are the associated values. The program should obtain the name of the file from the user and display the frequency table.
string lastName; string firstName; long phoneNumber; public: // default constructor person() : lastName("blank"), firstName("blank"), phoneNumber(0L) { } // 3-arg constructor person(string lana, string fina, long pho) : lastName(lana), firstName(fina), phoneNumber(pho) { } friend bool operator<(const person&, const person&); friend bool operator==(const person&, const person&); void display() const // display person's data { cout << endl << lastName << ",\t" << firstName << "\t\tPhone: " << phoneNumber; } }; // operator < for person class bool operator<(const person& p1, const person& p2) { if(p1.lastName == p2.lastName) return (p1.firstName < p2.firstName) ? true : false; return (p1.lastName < p2.lastName) ? true : false; } // operator == for person class bool operator==(const person& p1, const person& p2) { return (p1.lastName == p2.lastName && p1.firstName == p2.firstName ) ? true : false; } //////////////////////////////////////////////////////////////// void main() { // create person objects person pers1("Deauville", "William", 8435150); person pers2("McDonald", "Stacey", 3327563); person pers3("Bartoski", "Peter", 6946473); person pers4("KuangThu", "Bruce", 4157300); person pers5("Wellington", "John", 9207404); person pers6("McDonald", "Amanda", 8435150); person pers7("Fredericks", "Roger", 7049982); person pers8("McDonald", "Stacey", 7764987); // multiset of persons multiset< person, less<person> > persSet; // iterator to a multiset of persons multiset<person, less<person> >::iterator iter; persSet.insert(pers1); // put persons in multiset persSet.insert(pers2); persSet.insert(pers3); persSet.insert(pers4); persSet.insert(pers5); persSet.insert(pers6); persSet.insert(pers7); persSet.insert(pers8); cout << "\nNumber of entries = " << persSet.size(); C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
iter = persSet.begin(); // display contents of multiset while( iter != persSet.end() ) (*iter++).display(); // get last and first name string searchLastName, searchFirstName; cout << "\n\nEnter last name of person to search for: "; cin >> searchLastName; cout << "Enter first name: "; cin >> searchFirstName; // create person with this name person searchPerson(searchLastName, searchFirstName, 0); // get count of such persons int cntPersons = persSet.count(searchPerson); cout << "Number of persons with this name = " << cntPersons; // display all matches iter = persSet.lower_bound(searchPerson); while( iter != persSet.upper_bound(searchPerson) ) (*iter++).display(); } // end main()
Ordering
The overloaded < operator specifies the way the elements in the set will be ordered. In SETPERS, I define this operator to order the last name of the person and, if the last names are the same, to order the first names. (I could have used the phone numbers to provide ordering or I could have ordered on any other data item included in the class.) Heres some interaction with SETPERS. The program first displays the entire list (this would not be practical on a real database with a large number of elements). Because they are stored in a multiset, the elements are ordered automatically. Then, at the prompt, the user enters the name McDonald, Stacey (last name first). There are two persons on the list with this particular name, so they are both displayed. Number of entries = 8 Bartoski, Peter phone: 6946473 Deauville, William phone: 8435150 Fredericks, Roger phone: 7049982 KuangThu, Bruce phone: 4157300 McDonald, Amanda phone: 8435150 McDonald, Stacey phone: 3327563 McDonald, Stacey phone: 7764987 Wellington, John phone: 9207404 Enter last name of person to search for: McDonald Enter first name: Stacey Number of persons with this name = 2 McDonald, Stacey phone: 3327563 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 654 of 701
McDonald,
Stacey
phone: 7764987
return (p1.lastName < p2.lastName) ? true : false; } // overloaded == for person class bool operator==(const person& p1, const person& p2) { return (p1.lastName == p2.lastName && p1.firstName == p2.firstName ) ? true : false; } //////////////////////////////////////////////////////////////// void main() { list<person> persList; // list of persons // iterators to a list of persons list<person>::iterator iter1; list<person>::iterator iter2; // put persons in list persList.push_back( person("Deauville", "William", 8435150) ); persList.push_back( person("McDonald", "Stacey", 3327563) ); persList.push_back( person("Bartoski", "Peter", 6946473) ); persList.push_back( person("KuangThu", "Bruce", 4157300) ); persList.push_back( person("Wellington", "John", 9207404) ); persList.push_back( person("McDonald", "Amanda", 8435150) ); persList.push_back( person("Fredericks", "Roger", 7049982) ); persList.push_back( person("McDonald", "Stacey", 7764987) ); cout << "\nNumber of entries = " << persList.size(); iter1 = persList.begin(); while( iter1 != persList.end() ) (*iter1++).display(); // display contents of list
// find person or persons with specified name (last and first) string searchLastName, searchFirstName; cout << "\n\nEnter last name of person to search for: "; cin >> searchLastName; cout << "Enter first name: "; cin >> searchFirstName; // make a person with that name person searchPerson(searchLastName, searchFirstName, 0L); // search for first match of names iter1 = find(persList.begin(), persList.end(), searchPerson); if( iter1 != persList.end() ) { cout << "Person(s) with that name is(are)"; do { (*iter1).display(); // display matches iter2 = ++iter1; // search for other matches iter1 = find(iter2, persList.end(), searchPerson); } while( iter1 != persList.end() ); } else cout << "There is no person with that name."; // find person or persons with specified phone number cout << "\n\nEnter phone number (format 1234567): "; C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
// iterate through list bool found_one = false; for(iter1=persList.begin(); iter1 != persList.end(); ++iter1) { if( sNumber == (*iter1).get_phone() ) // compare numbers { if( !found_one ) { cout << "Person(s) with that phone number is(are)"; found_one = true; } (*iter1).display(); // display the match } } // end for if( !found_one ) cout << "There is no person with that phone number"; } // end main()
Enter phone number (format 1234567): 8435150 Person(s) with that number is(are) Deauville, William phone: 8435150 McDonald, Amanda phone: 8435150 The program has found two people with the same phone number.
// 0 to 23 // 0 to 59
// default constructor airtime() : hours(0), minutes(0) { } // 2-arg constructor airtime(int h, int m) : hours(h), minutes(m) { } void display() const // output to screen { cout << hours << ':' << minutes; } void get() // input from user { char dummy; cout << "\nEnter airtime (format 12:59): "; cin >> hours >> dummy >> minutes; } // overloaded + operator airtime operator + (airtime right) const { // add members int temph = hours + right.hours; int tempm = minutes + right.minutes; if(tempm >= 60) // check for carry { temph++; tempm -= 60; } // return sum return airtime(temph, tempm); } // overloaded < operator bool operator < (const airtime& at2) const C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 658 of 701
{ if(hours < at2.hours) return true; if(hours == at2.hours && minutes < at2.minutes) return true; return false; } // overloaded == operator bool operator == (const airtime& at2) const { if(hours == at2.hours && minutes == at2.minutes) return true; return false; } }; // end class airtime void main() { char answer; airtime temp, sum; list<airtime> airlist; // list and iterator list<airtime>::iterator iter; do { temp.get(); // get airtime from user airlist.push_back(temp); cout << "Enter another (y/n)? "; cin >> answer; } while (answer != 'n'); iter = airlist.begin(); // iterate through list while( iter != airlist.end() ) sum = sum + *iter++; // add this airtime to sum cout << "\nsum = "; sum.display(); // display sum } The airtime values are entered by the user. Once all these values have been obtained and stored on the list, the program iterates through the list, adding each value to an airtime object called sum. Finally, sum is displayed. (Youll see a more sophisticated way to do this in the next session.) Heres some interaction with LISTAIR: Enter airtime (format 12:59): 1:22 Enter another (y/n)? y Enter airtime (format 12:59): 0:19 Enter another (y/n)? y Enter airtime (format 12:59): 2:13 Enter another (y/n)? y Enter airtime (format 12:59): 1:45 Enter another (y/n)? n sum = 5:39 In this session, Ive concentrated on searching a container for specified elements using algorithms such as find(). However, you can apply any of the STL algorithms to objects of user-defined classes placed in STL arrays: You can C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 659 of 701
sort them, merge them, copy them, and so on. As you can see, its quite simple to store objects of almost any class in an STL container and manipulate these objects with STL algorithms.
Quiz 7 1. Which of the following are differences between storing variables of basic types (such as int) in a container and storing objects of user-created classes? a. Certain container member functions dont work when containers hold objects of usercreated classes. b. You must give values to basic variables before placing them in a container, but this isnt necessary for objects of user-created classes. c. Certain member functions must be supplied for the user-created classes. d. Random-access iterators dont work for user-created class objects. e. Objects of user-created classes must be defined after the class is created. 2. If you are going to store objects of a class T in an STL container, then T a. will probably require a default constructor. b. will definitely need a one-argument constructor. c. will not require an overloaded == operator unless it uses the equal function object. d. will definitely need an overloaded < operator. e. must include a pointer of type T* as a member to store its own location. 3. If you overload the < operator for a class T whose objects you are going to store in an STL container, then this operator a. will probably base the comparison on one or more member data items in T. b. will call on a function object to carry out the actual comparison. c. if the container is a list, will be used by find(). d. if the container is a map, will be used to order the keys (not the values). e. if the container is a map, will be used to order the values (not the keys). 4. In which of the following containers might it make sense, under the appropriate circumstances, to store objects of the person class (as seen in the examples in this section)? a. A deque. b. A vector.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 660 of 701
c. A multiset. d. A map in which social security numbers are used as the key and person objects as the values. e. A map in which person objects are used both as the key and the value. 5. Which of these statements are reasonable? a. When you create a fictitious object to use as an argument to the find(), count(), or lower_bound() functions, this object must have valid data for all its members. b. Container member functions have direct access to the private data of objects stored in the container. c. If you overload an arithmetic operator (such as + or -) for a class whose objects you are going to store in an STL container, then you can use a dereferenced container iterator as the argument on the right of the operator, but not on the left. d. If iter is a container iterator and display() is a member function of the objects stored in the container, you can use iter->display() as a shorthand version of (*iter).display(). e. The count() member function can return the number of elements in a container that have a specified value. Exercise 1
Create a map of person objects (based on the person class in the LISTPERS example) using social security numbers (SSN) as keys. The user should be able to add or delete persons or search for a person with a given SSN. Provide the ability to search for persons with a particular name or telephone number as well.
Exercise 2
Create a flight_information class that holds the information necessary for displaying flight information on an airport Departures monitor. The information should include the airline and the destination city (string objects), the scheduled and current departure times (airtime values), and the gate number (an integer). Make a set to hold flight_information objects, sorted by destination and, for each destination, by scheduled departure time. The user should be able to add new information and display the sorted information.
There are function objects for arithmetic operations, comparisons, and logical operations. The SORTEMP example in Session 2 showed how to use a comparison function object, greater<>(). Lets look at an example where an arithmetic function object might come in handy. The example (a variation of the LISTAIR example in Session 7) shows how the plus<>() function object can be used to add all the airtime values in a container. Listing 12-35 shows PLUSAIR. Listing 12-35 PLUSAIR // plusair.cpp // uses accumulate() algorithm and plus() function object #include <iostream.h> #include <list.h> #include <algo.h> class airtime { private: int hours; int minutes; public:
// 0 to 23 // 0 to 59
// default constructor airtime() : hours(0), minutes(0) { } // 2-arg constructor airtime(int h, int m) : hours(h), minutes(m) { } void display() const // output to screen { cout << hours << ':' << minutes; } void get() // input from user { char dummy; cout << "\nEnter airtime (format 12:59): "; cin >> hours >> dummy >> minutes; } // overloaded + operator airtime operator + (const airtime right) const { // add members int temph = hours + right.hours; int tempm = minutes + right.minutes; if(tempm >= 60) // check for carry C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 662 of 701
{ temph++; tempm -= 60; } return airtime(temph, tempm); // return sum } // overloaded < operator bool operator < (const airtime& at2) const { if(hours < at2.hours) return true; if(hours == at2.hours && minutes < at2.minutes) return true; return false; } // overloaded == operator bool operator == (const airtime& at2) const { if(hours == at2.hours && minutes == at2.minutes) return true; return false; } }; // end class airtime void main() { char answer; airtime temp, sum; list<airtime> airlist; // list and iterator list<airtime>::iterator iter; do { // get airtimes from user temp.get(); airlist.push_back(temp); cout << "Enter another (y/n)? "; cin >> answer; } while (answer != 'n'); // sum all the airtimes sum = accumulate( airlist.begin(), airlist.end(), airtime(0, 0), plus<airtime>() ); cout << "\nsum = "; sum.display(); // display sum } This program features the accumulate() algorithm, which returns the sum of all the elements in a range. The four arguments to accumulate() are the iterators of the first and last elements in the range, the initial value of the sum (usually 0 or its equivalent), and the operation to be applied to the elements. In this example I add them, but I could subtract them, multiply them, or perform other operations using different function objects. Heres some interaction with PLUSAIR: Enter airtime (format 12:59) : 3:45 Enter another (y/n)? y Enter airtime (format 12:59) : 5:10 Enter another (y/n)? y Enter airtime (format 12:59) : 2:25 Enter another (y/n)? y Enter airtime (format 12:59) : 0:55 C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 663 of 701
Enter another (y/n)? n sum = 12:15 The accumulate() algorithm is not only easier and clearer than iterating through the container myself to add the elements, its also (unless I put a lot of work into my code) more efficient. (Another version of accumulate() can perform any operation I define, rather than summing the data.) The plus<>() function object requires that the + operator be overloaded for the airtime class. (This operator should be a const function because thats what the plus<>() function object expects.) The other arithmetic function objects work in a similar way. The logical function objects such as logical_and<>() can be used on objects of classes for which these operations make sense (e.g., type bool variables).
<< "\t\tPhone: " << phoneNumber; } long get_phone() const // return phone number { return phoneNumber; } }; // overloaded < for person class bool operator<(const person& p1, const person& p2) { if(p1.lastName == p2.lastName) return (p1.firstName < p2.firstName) ? true : false; return (p1.lastName < p2.lastName) ? true : false; } // overloaded == for person class bool operator==(const person& p1, const person& p2) { return (p1.lastName == p2.lastName && p1.firstName == p2.firstName ) ? true : false; } // function object to compare person's phone number class phoneLess : binary_function<person, person, bool> { public: bool operator() (const person& p1, const person& p2) const { return p1.get_phone() < p2.get_phone(); } }; //////////////////////////////////////////////////////////////// void main() { // a multiset of persons multiset< person, less<person> > persSet; // put persons in set persSet.insert( person("Deauville", "William", 8435150) ); persSet.insert( person("McDonald", "Stacey", 3327563) ); persSet.insert( person("Bartoski", "Peter", 6946473) ); persSet.insert( person("KuangThu", "Bruce", 4157300) ); persSet.insert( person("Wellington", "John", 9207404) ); persSet.insert( person("McDonald", "Amanda", 8435150) ); persSet.insert( person("Fredericks", "Roger", 7049982) ); persSet.insert( person("McDonald", "Stacey", 7764987) ); // iterator to multiset multiset< person, less<person> >::iterator iterset; cout << "\nPersons sorted by name:"; iterset = persSet.begin(); // display contents of set while( iterset != persSet.end() ) (*iterset++).display(); // an empty vector of persons vector<person> persVect( persSet.size() ); // copy from set to vector copy( persSet.begin(), persSet.end(), persVect.begin() ); // sort vector by phone number sort( persVect.begin(), persVect.end(), phoneLess() ); C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 665 of 701
// iterator to a vector of persons vector<person>::iterator itervect; cout << "\n\nPersons sorted by phone number:"; itervect = persVect.begin(); // display contents of vector while( itervect != persVect.end() ) (*itervect++).display(); } // end main() The program starts out by putting the usual person objects in a set and displaying its contents to show ordering by name. Then the set is copied into a vector and the vector is sorted using the lessPhone() function object in the line sort( persVect.begin(), persVect.end(), phoneLess() ); Heres the output of SORTPERS: Persons sorted by name: Bartoski, Peter phone: 6946473 Deauville, William phone: 8435150 Fredericks, Roger phone: 7049982 KuangThu, Bruce phone: 4157300 McDonald, Amanda phone: 8435150 McDonald, Stacey phone: 3327563 McDonald, Stacey phone: 7764987 Wellington, John phone: 9207404 Persons sorted by phone number: McDonald, Stacey phone: 3327563 KuangThu, Bruce phone: 4157300 Bartoski, Peter phone: 6946473 Fredericks, Roger phone: 7049982 McDonald, Stacey phone: 7764987 Deauville, William phone: 8435150 McDonald, Amanda phone: 8435150 Wellington, John phone: 9207404 The phoneLess class is defined this way: class phoneLess : binary_function<person, person, bool> { public: bool operator() (const person& p1, const person& p2) const { return p1.get_phone() < p2.get_phone(); } You could alternatively express this class as a struct, which makes it unnecessary to use the public designation (because everything in a struct is public by default). However, I prefer to use class to emphasize that Im dealing with a class and not an old-fashioned C style struct. Instead of comparing names, as the built-in less<>() function object would have, the overloaded () operator in the phoneLess class compares the phone numbers of the two person objects passed as arguments. It returns true if the first phone number is less than the second. The sort() algorithm uses this relationship to sort the vector. The phoneLess class is derived from the binary_function class (actually a struct), which is defined in FUNCTION.H. The binary_function class specifies that two arguments must be supplied to the function object. This simplifies the syntax used to write the overloaded operator() function. (Theres also a unary_function class, from which you can derive function objects taking one argument.)
an appropriate function object. However to do this, you need to learn about a new concept: binding, in which a value is bound to one function object to make another function object. The FUNCPERS example (Listing 12-37) shows how a function object called phoneEqual() can find all entries with a given phone number. Listing 12-37 FUNCPERS // funcpers.cpp // demonstrates function objects with person class #include <iostream.h> #include <list.h> #include <algo.h> #include <cstring.h> class person { private: string lastName; string firstName; long phoneNumber; public: person() : // default constructor lastName("blank"), firstName("blank"), phoneNumber(0L) { } // 3-arg constructor person(string lana, string fina, long pho) : lastName(lana), firstName(fina), phoneNumber(pho) { } friend bool operator<(const person&, const person&); friend bool operator==(const person&, const person&); void display() const { cout << endl << lastName << ",\t" << firstName << "\t\tPhone: " << phoneNumber; } long get_phone() const // return person's phone number { return phoneNumber; } }; // overloaded < for person class bool operator<(const person& p1, const person& p2) { if(p1.lastName == p2.lastName) return (p1.firstName < p2.firstName) ? true : false; return (p1.lastName < p2.lastName) ? true : false; } // overloaded == for person class bool operator==(const person& p1, const person& p2) { return (p1.lastName == p2.lastName && p1.firstName == p2.firstName ) ? true : false; } // function object to compare person's phone number class phoneEqual : binary_function<person, long, bool> { public: C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy
bool operator() (const person& p, const long& n) const { return (p.get_phone()==n) ? true : false; } }; //////////////////////////////////////////////////////////////// void main() { list<person> persList; // list of persons
list<person>::iterator iter1; list<person>::iterator iter2; persList.push_back( persList.push_back( persList.push_back( persList.push_back( persList.push_back( persList.push_back( persList.push_back( persList.push_back(
// iterators to a // list of persons // put persons in list person("Deauville", "William", 8435150) ); person("McDonald", "Stacey", 3327563) ); person("Bartoski", "Peter", 6946473) ); person("KuangThu", "Bruce", 4157300) ); person("Wellington", "John", 9207404) ); person("McDonald", "Amanda", 8435150) ); person("Fredericks", "Roger", 7049982) ); person("McDonald", "Stacey", 7764987) );
// find person or persons with specified phone number cout << "\n\nEnter phone number (format 1234567): "; long sNumber; // get search number cin >> sNumber; // search for first match iter1 = find_if( persList.begin(), persList.end(), bind2nd(phoneEqual(), sNumber) ); // func obj if( iter1 != persList.end() ) { cout << "\nPerson(s) with that phone number is(are)"; do { (*iter1).display(); // display match iter2 = ++iter1; // search for another match iter1 = find_if( iter2, persList.end(), bind2nd(phoneEqual(), sNumber) ); } while( iter1 != persList.end() ); } else cout << "There is no person with that phone number."; } // end main() The phoneEqual() function object is similar to the phoneLess() function object in the SORTPERS example. However, it is invoked somewhat differently by the find_if() algorithm. The third argument to find_if() requires a function object. This function object, phoneEqual(), expects find_if() to supply two arguments to it: a person object and a long value representing the phone number. As find_if() iterates through the persList container checking each person object in turn, it repeatedly calls phoneEqual(). It knows which person object to pass to phoneEqual() because it knows where it is in the iteration process. But where does find_if() get the phone number to supply to phoneEqual()? I cant supply this number directly to the function object as an argument, like this iter1 = find_if( persList.begin(), C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 668 of 701
persList.end(), phoneEqual(sNumber) ); // can't do this because find_if() must supply all the values to phoneEqual itself. Instead, I must bind the phone number and the function object together to make another function object. The bind2nd binder is used for this purpose: iter1 = find_if( persList.begin(), persList.end(), bind2nd(phoneEqual(), sNumber) ); You can use either of two binders, bind1st or bind2nd, depending on whether the value you supply is the first or second argument to the function object. In phoneEqual(), its the second; the person object is the first. Heres some interaction with FUNCPERS: Enter phone number (format 1234567): 9207474 Person(s) with that phone number is(are) Wellington, John Phone: 9207404 If there are multiple instances of the same phone number, the program will find and display all of them.
Quiz 8 1. Function objects can be used to a. customize the find() algorithm so it uses a different notion of equality than the normal overloaded == operator. b. sort the elements of a container using a different criteria than the normal overloaded < operator. c. customize the copy() algorithm so it sorts the objects it is copying. d. multiply all the elements of a container together, using the accumulate() algorithm. e. customize the find_if() algorithm to find all person objects with a specified first name. 2. You can use a predefined function object to a. provide a lexicographical comparison of two objects. b. negate two objects. c. find if one object is greater than or equal to another. d. subtract one object from another. e. set one object equal to another. 3. To use the minus<>() function object on objects of class T, you must a. overload the accumulate() algorithm to work with class T. b. provide objects of class T as arguments to this function object. c. write your own code for this function object.
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 669 of 701
d. overload the (<) operator for class T. e. overload the (-) operator for class T. 4. To make a custom function object that is useful for objects of class T in a container of class C, you must a. create a class. b. overload operator() for class T. c. overload operator() for class C. d. derive a class from T. e. wrap a function in a class. 5. If you need to provide a value v to a custom function object that is used as an argument to an algorithm that will operate on objects of class T stored in a container of class C, then you must a. use v as the first argument to the function object. b. use v as the second argument to the function object. c. bind v to the function object. d. bind v to an object of class T. e. derive the function object from the binary_function class. Exercise 1
Write a program that counts how many objects of type person, stored in an STL container, have the same first name. Use count_if() and a custom function object.
Exercise 2
The remove() algorithm removes all elements in a container that have a certain value or satisfy a condition specified by a function object. Write a program that allows the user to remove all the person objects from a container if they have a last name specified by the user. Use a function object to compare last names.
Summary: Chapter 12
This chapter has presented a quick and dirty introduction to the STL. Ive touched on the major topics, and you should have acquired enough information to begin using the STL in a useful way. For a fuller understanding of the STL, I strongly recommend that you avail yourself of a complete text on the topic. Youve learned that the STL consists of three main components: containers, algorithms, and iterators. Containers are divided into two groups: sequential and associative. Sequential containers are the vector, list, and deque. Associative containers are the set and map and the closely related multiset and multimap. Algorithms carry out operations on containers such as sorting, copying, and searching. Iterators act like pointers to container elements and provide connections between algorithms and containers. Not all algorithms are appropriate for all containers. Iterators are used to ensure that algorithms and containers are appropriately matched. Iterators are defined for specific kinds of containers and used as arguments to algorithms. If the containers iterators dont match the algorithm, a compiler error results. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 670 of 701
Input and output iterators connect directly to I/O streams, thus allowing data to be piped directly between I/O devices and containers. Specialized iterators allow backward iteration and can also change the behavior of some algorithms so they insert data rather than overwriting existing data. Algorithms are standalone functions that can work on many different containers. In addition, each container has its own specific member functions. In some cases, the same function is available as both an algorithm and a member function. STL containers and algorithms will work with objects of any class, provided certain member functions, such as the < operator, are overloaded for that class. The behavior of certain algorithms such as find_if() can be customized using function objects. A function object is instantiated from a class containing only an () operator.
End-of-Chapter Discussion
George: Estelle: George: Don: George: Estelle: George: Estelle: OK, thats it. Theres no way Ill ever understand function objects. How to use them? Or how they really work? How they work. Whats in all the header files. You dont need to understand how a class works to use it. Thats the beauty of C++. But Id really like to feel I know what you mean, George, but I agree with Don. As a user, I could really get to like the STL. Well, youll be surprised to know Im with you on this one. It isnt that hard to use. But Ive got to admit I was skeptical at first. You, George? Thats hard to believe.
Session 2
1. c 2. b 3. e 4. a, c, e 5. a, b, c, e
Session 3
1. c 2. b, c, e 3. b 4. d 5. e
Session 4
1. a 2. b 3. a 4. b 5. b, c, e
Session 5
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 672 of 701
1. d 2. c, d 3. a, c, d 4. a, b, d 5. b
Session 6
1. d 2. a, e 3. b, c, d 4. b 5. b, d
Session 7
1. c, d, e 2. b, e 3. b 4. b, d 5. c
Session 8
1. a, b, e 2. b, c, d 3. a 4. d 5. c, d
Chapter 2 Session 1
1. a, d 2. c 3. c, d, e
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 673 of 701
4. b, c, d 5. a, d
Session 2
1. a, e 2. b 3. a, c 4. d, e 5. d
Session 3
1. b, c, e 2. a 3. b 4. c 5. c, d
Session 4
1. b, d 2. b, d 3. e 4. b, d 5. d
Session 5
1. e 2. b, c, d 3. c 4. a, b, d 5. c
Session 6
1. d, e
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 674 of 701
2. b, e 3. a, b, c 4. e 5. a, b
Session 7
1. a, b 2. c, d, e 3. b 4. b, e 5. a, d, e
Session 8
1. a, c 2. b, c, e 3. c, e 4. b 5. b
Chapter 3 Session 1
1. b 2. e 3. e 4. a, e 5. b
Session 2
1. c 2. c 3. a, d, e 4. c, e
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 675 of 701
5. c, d
Session 3
1. d 2. b 3. a 4. a, c, e 5. b
Session 4
1. a 2. b, e 3. b, c, d 4. c, e 5. a
Session 5
1. b, c, e 2. d 3. a, b 4. a, b, c 5. b, e
Session 6
1. e 2. b, d 3. b, c, e 4. b, c 5. a, c
Session 7
1. a, d 2. c, d
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 676 of 701
3. b, c, e 4. b, d 5. b, e
Session 8
1. b, c, e 2. c, d 3. d, e 4. a, b, c 5. a, d, e
Chapter 4 Session 1
1. c 2. a, d, e 3. d 4. d, e 5. b
Session 2
1. b, c 2. a, c 3. e 4. b 5. a, b
Session 3
1. b, d, e 2. b, c 3. a, d, e 4. c, d, e 5. c, d
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 677 of 701
Session 4
1. c 2. b 3. a, c, e 4. a, c 5. b, e
Session 5
1. d, e 2. b 3. c 4. b 5. c, e
Session 6
1. b, d 2. c, e 3. b, c, d 4. b, d, e 5. a, b, c
Session 7
1. a, d 2. b, c, d 3. e 4. c, d 5. a, e
Session 8
1. b 2. a 3. e
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 678 of 701
4. d 5. c
Chapter 5 Session 1
1. a, c 2. b, d 3. a, e 4. b, d 5. b
Session 2
1. c 2. a, b, e 3. b 4. d 5. a, c
Session 3
1. d 2. b 3. c, e 4. a 5. c, e
Session 4
1. c, d 2. a, b 3. b 4. d 5. b, d
Session 5
1. b, e
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 679 of 701
2. d 3. a, b 4. a, e 5. a, d
Session 6
1. e 2. d 3. c, d 4. c 5. a, b
Session 7
1. d 2. b 3. e 4. e 5. c
Session 8
1. b 2. d 3. a, e 4. c, e 5. c, d
Chapter 6 Session 1
1. a, b, c 2. d 3. b 4. a, d
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 680 of 701
5. b, e
Session 2
1. a, e 2. b, d 3. b, c, d, e 4. a 5. b
Session 3
1. b, d 2. e 3. a 4. a, c, e 5. b, e
Session 4
1. a 2. d 3. b 4. e 5. b, d
Session 5
1. c, d 2. b, e 3. c, d 4. c, e 5. a, c
Session 6
1. c 2. a, b, d, e
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 681 of 701
3. c, d 4. a 5. b, d
Session 7
1. c, d, e 2. c, e 3. a 4. a, c 5. b, c, d
Session 8
1. a, c 2. b, e 3. d 4. a, d, e 5. a, b
Chapter 7 Session 1
1. b, c 2. d 3. b 4. c 5. e
Session 2
1. a 2. b 3. a 4. c 5. b
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 682 of 701
Session 3
1. a, c, e 2. b, c 3. b 4. c 5.
Session 4
1. b, c 2. d 3. c 4. a, b 5. a, b, d, e
Session 5
1. a, b 2. c, e 3. c, e 4. a, b, e 5. b, c, d
Session 6
1. d, e 2. c 3. d 4. a, c 5. d, e
Session 7
1. b, e 2. a, c 3. a, e
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 683 of 701
4. c 5. b, d
Session 8
1. c, d 2. c, d, e 3. b 4. d 5. c
Chapter 8 Session 1
1. b 2. b, d, e 3. b 4. e 5. a, b
Session 2
1. e 2. c 3. b 4. b 5. a
Session 3
1. b, c 2. a 3. c, d, e 4. b, c 5. a, c
Session 4
1. b, d, e
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 684 of 701
2. b, c, d 3. a, b 4. d 5. e
Session 5
1. c, e 2. a, c 3. a, b, e 4. b 5. d, e
Session 6
1. a, e 2. c, d, e 3. d 4. d 5. b
Session 7
1. b, c 2. c 3. a, b, e 4. a, c, e 5. b, c
Session 8
1. b, d, e 2. c 3. a, b, d 4. c, e 5. b
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 685 of 701
Chapter 9 Session 1
1. d 2. b, d 3. a, b, e 4. c 5. b
Session 2
1. c 2. a, b 3. d, e 4. b 5. a
Session 3
1. a, b, c, e 2. b, c 3. b, c, d 4. d 5. e
Session 4
1. a, c, e 2. a, b, d 3. b, e 4. d 5. a, c, e
Session 5
1. c, d 2. a, b
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 686 of 701
3. e 4. a, b, d 5. c
Session 6
1. d 2. c 3. d 4. c, e 5. a, c, d, e
Session 7
1. d 2. b, c, e 3. a, c 4. c 5. d, e
Session 8
1. d 2. d, e 3. b 4. c, e 5. b, d
Chapter 10 Session 1
1. b, c, d, e 2. d, e 3. b, c, e 4. a, c 5. c, d
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 687 of 701
Session 2
1. b, d 2. b, e 3. c, d 4. a, c, e 5. d
Session 3
1. b, d 2. a, b, d 3. d, e 4. d, e 5. b, c, d
Session 4
1. c, e 2. a, e 3. c, e 4. c, d 5. d, e
Session 5
1. b, e 2. e 3. d 4. b 5. d
Session 6
1. c, d, e 2. b, e 3. b
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 688 of 701
4. a, e 5. c, e
Session 7
1. c, e 2. b, c, d 3. d 4. c 5. b
Session 8
1. b, e 2. b 3. a 4. b, e 5. a
Chapter 11 Session 1
1. b, e 2. c 3. b 4. a, c, e 5. e
Session 2
1. b, d 2. a, e 3. b, e 4. b, c 5. a
Session 3
1. b, e
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 689 of 701
2. a 3. b 4. b, d, e 5. c, e
Session 4
1. a, d 2. a, b, d 3. c, e 4. b, c 5. b
Session 5
1. a, c, e 2. b, c, e 3. a, c 4. b, c, e 5. e
Session 6
1. b, d 2. a, c, d 3. d 4. a, b, e 5. a
Session 7
1. b, c, d, e 2. b, c, d 3. a 4. d 5. a, c, e
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 690 of 701
Session 8
(There is no quiz for Session 8. Everyone receives a perfect score.)
Chapter 12 Session 1
1. a, b, c, e 2. c, e 3. a, c 4. a, b, e 5. d
Session 2
1. b, c 2. c, e 3. a, d 4. b, c, d, e 5. d
Session 3
1. b, d 2. b, c 3. c, e 4. e 5. b, c
Session 4
1. d, e 2. a, b, c 3. a, c, e 4. a, b, d, e 5. e
Session 5
1. c, d
C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 691 of 701
2. a 3. a, c 4. a, b 5. b, c
Session 6
1. b 2. b, d, e 3. a 4. b, c 5. a, b, d
Session 7
1. c 2. a, d 3. a, d 4. a, b, c, d, e 5. e
Session 8
1. b, d, e 2. c, d 3. d, e 4. a, e 5. c, e
Algorithms
Table B-1 shows the algorithms available in the STL. The descriptions in this table offer a quick and condensed explanation of what the algorithms do; they are not intended to be serious mathematical definitions. For definitive explanations, with more information including the exact data types to use for arguments and return values, consult the Stepanov and Lee document. The first column gives the function name, the second explains the purpose of the algorithm, and the third specifies the arguments. Return values are not systematically specified. Some are mentioned in the Purpose column and many are either obvious or not vital to using the algorithm. In the Arguments column, the names first, last, first1, last1, first2, last2, first3, and middle represent iterators to specific places in a container. Names with numbers (like first1) are used to distinguish multiple containers. The name first1, last1 delimits range 1, and first2, last2 delimits range 2. The arguments function, predicate, op, and comp are function objects. The arguments value, old, new, a, b, and init are values of the objects stored in a container. These values are ordered or compared based on the < or == operator or the comp function object. The argument n is an integer. In the Purpose column, movable iterators are indicated by iter, iter1, and iter2. When iter1 and iter2 are used together, they are assumed to move together step by step through their respective containers (or possibly two different ranges in the same container). Table B-1 Algorithms available in the STL Name Purpose Arguments Nonmutating Sequence Operations for_each Applies function to each object. first, last, function find Returns iterator to first object equal to value. first, last, value find_if Returns iterator to first object for which predicate is true. first, last, predicate adjacent_find Returns iterator to first adjacent pair of objects that are equal. first, last adjacent_find Returns iterator to first adjacent pair of objects that satisfy predicate. first, last, predicate count Adds to n the number of objects equal to value. first, last, value, n count_if Adds to n the number of objects satisfying predicate. first, last, predicate, n mismatch Returns first nonequal pair of corresponding objects in two ranges. first1, last1, first2 mismatch Returns first pair of corresponding objects in two ranges that dont satisfy predicate. first1, last1, first2, predicate equal Returns true if corresponding objects in two ranges are all equal. first1, last1, first2 equal Returns true if corresponding objects in two ranges all satisfy predicate. first1, last1, first2, predicate search Checks if second range is contained within the first. Returns start of match, or last1 if no match. first1, last1, first2, last2 search Checks if second range is contained within the first, where equality is determined by predicate. Returns start of match, or last1 if no match. first1, last1, first2, last2, predicate Mutating Sequence Operations copy Copies objects from range 1 to range 2. first1, last1, first2 copy_backward Copies objects from range 1 to range 2, inserting them backwards, from last2 to first2. first1, last1, first2 swap Interchanges two objects. a, b iter_swap Interchanges objects pointed to by two iterators. iter1, iter2 swap_ranges Interchanges corresponding elements in two ranges. first1, last1, first2 transform Transforms objects in range 1 into new objects in range 2 by applying operator. first1, last1, first2, operator transform Combines objects in range 1 and range 2 into new objects in range 3 by applying operator. first1, last1, first2, first3, operator replace Replaces all objects equal to C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 693 of 701
old with objects equal to new. first, last, old, new replace_if Replaces all objects that satisfy predicate with objects equal to new. first, last, predicate, new replace_copy Copies from range 1 to range 2, replacing all objects equal to old with objects equal to new. first1, last1, first2, old, new. replace_copy_if Copies from range 1 to range 2, replacing all objects that satisfy predicate with objects equal to new. first1, last1, first2, predicate, new fill Assigns value to all objects in range. first, last, value fill_n Assigns value to all objects from first to first+n. first, n, value generate Fills range with values generated by successive calls to function gen. first, last, gen generate_n Fills from first to first+n with values generated by successive calls to function gen. first, n, gen remove Removes from range any objects equal to value. first, last, value remove_if Removes from range any objects that satisfy predicate. first, last, predicate remove_copy Copies objects, except those equal to value, from range 1 to range 2. first1, last1, first2, value remove_copy_if Copies objects, except those satisfying pred, from range 1 to range 2. first1, last1, first2, pred unique Eliminates all but the first object from any consecutive sequence of equal objects. first, last unique Eliminates all but the first object from any consecutive sequence of objects satisfying predicate. first, last, predicate unique_copy Copies objects from range 1 to range 2, except only the first object from any consecutive sequence of equal objects is copied. first1, last1, first2 unique_copy Copies objects from range 1 to range 2, except only the first object from any consecutive sequence of objects satisfying predicate is copied. first1, last1, first2, predicate reverse Reverses the sequence of objects in range. first, last reverse_copy Copies range 1 to range 2, reversing the sequence of objects. first1, last1, first2 rotate Rotates sequence of objects around iterator middle. first, last, middle rotate_copy Copies objects from range 1 to range 2, rotating the sequence around iterator middle. first1, middle1, last1, first2 random_shuffle Randomly shuffles objects in range. first, last random_shuffle Randomly shuffles objects in range, using randomnumber function rand. first, last, rand partition Moves all objects that satisfy predicate so they precede those that do not satisfy it. first, last, predicate stable_partition Moves all objects that satisfy predicate so they precede those that do not, and also preserves relative ordering in the two groups. first, last, predicate Sorting and Related Operations sort Sorts objects in range. first, last sort Sorts elements in range, using comp as comparison function. first, last, comp stable_sort Sorts objects in range and maintains order of equal elements. first, last stable_sort Sorts elements in range using comp as comparison function and maintains order of equal elements. first, last, comp partial_sort Sorts all objects in range and places as many sorted values as will fit between first and middle. Order of objects between middle and last is undefined. first, middle, last partial_sort Sorts all objects in range and places as many sorted values as will fit between first and middle. Order of objects between middle and last is undefined. Uses predicate to define ordering. first, middle, last, predicate partial_sort_copy Same as partial_sort (first, middle, last), but places resulting sequence in range 2. first1, last1, first2, last2 partial_sort_copy Same as partial_sort (first, middle, last, predicate), but places resulting sequence in range 2. first1, last1, first2, last2, comp nth_element Places the nth object in the position it would occupy if the whole range were sorted. first, nth, last nth_element Places the nth object in the position it would occupy if the whole range were sorted using comp for comparisons. first, nth, last, comp lower_bound Returns iterator to first position into which value could be inserted without violating the ordering. first, last, value lower_bound Returns iterator to first position into which value could be inserted without violating an ordering based on comp. first, last, value, comp upper_bound Returns iterator to last position into which value could be inserted without violating the ordering. first, last, value upper_bound Returns iterator to last position into which value could be inserted without violating an ordering based on comp. first, last, value, comp equal_range Returns a pair containing the lower bound and upper bound between which value could be inserted without violating the ordering. first, last, value equal_range Returns a pair containing the lower bound and upper bound between which value could be inserted without violating an ordering based on comp. first, last, value, comp binary_search Returns true if value is in the range. first, last, value, comp binary_search Returns true if value is in the range, where the C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 694 of 701
ordering is determined by comp. first, last, value, comp merge Merges sorted ranges 1 and 2 into sorted range 3. first1, last1, first2, last2, first3 merge Merges sorted ranges 1 and 2 into sorted range 3, where the ordering is determined by comp. first1, last1, first2, last2, first3, comp inplace_merge Merges two consecutive sorted ranges first, middle and middle, lastinto first, last. first, middle, last inplace_merge Merges two consecutive sorted rangesfirst, middle and middle, lastinto first, last, where the ordering is based on comp. first, middle, last, comp includes Returns true if every object in the range first2, last2 is also in the range first1, last. (Sets and multisets only.) first1, last1, first2, last2 includes Returns true if every object in the range first2-last2 is also in the range first1-last1, where ordering is based on comp. (Sets and multisets only.) first1, last1, first2, last2, comp set_union Constructs sorted union of elements of ranges 1 and 2. (Sets and multisets only.) first1, last1, first2, last2, first3 set_union Constructs sorted union of elements of ranges 1 and 2, where the ordering is based on comp. (Sets and multisets only.) first1, last1, first2, last2, first3, comp set_intersection Constructs sorted intersection of elements of ranges 1 and 2. (Sets and multisets only.) first1, last1, first2, last2, first3 set_intersection Constructs sorted intersection of elements of ranges 1 and 2, where the ordering is based on comp. (Sets and multisets only.) first1, last1, first2, last2, first3, comp set_difference Constructs sorted difference of elements of ranges 1 and 2. (Sets and multisets only.) first1, last1, first2, last2, first3 set_difference Constructs sorted difference of elements of ranges 1 and 2, where the ordering is based on comp. (Sets and multisets only.) first1, last1, first2, last2, first3, comp set_symmetric_ difference Constructs sorted symmetric difference of elements of ranges 1 and 2. (Sets and multisets only.) first1, last1, first2, last2, first3 set_symmetric_ difference Constructs sorted difference of elements of ranges 1 and 2, where the ordering is based on comp. (Sets and multisets only.) first1, last1, first2, last2, first3, comp push_heap Places value from last1 into resulting heap in range first, last. first, last push_heap Places value from last1 into resulting heap in range first, last, based on ordering determined by comp. first, last, comp pop_heap Swaps the values in first and last1; makes range first, last1 into a heap. first, last pop_heap Swaps the values in first and last1; makes range first, last1 into a heap, based on ordering determined by comp. first, last, comp make_heap Constructs a heap out of the range first, last. first, last make_heap Constructs a heap out of the range first, last, based on the ordering determined by comp. first, last, comp sort_heap Sorts the elements in the heap first, last. first, last sort_heap Sorts the elements in the heap first, last, based on the ordering determined by comp. first, last, comp min Returns the smaller of two objects. a, b min Returns the smaller of two objects, where the ordering is determined by comp. a, b, comp max Returns the larger of two objects. a, b max Returns the larger of two objects, where the ordering is determined by comp. a, b, comp max_element Returns an iterator to the largest object in the range. first, last max_element Returns an iterator to the largest object in the range, with an ordering determined by comp. first, last, comp min_element Returns an iterator to the smallest object in the range. first, last min_element Returns an iterator to the smallest object in the range, with an ordering determined by comp. first, last, comp lexico-graphical_ compare Returns true if the sequence in range 1 comes before the sequence in range 2 alphabetically. first1, last1, first2, last2 lexico-graphical_ compare Returns true if the sequence in range 1 comes before the sequence in range 2 alphabetically, based on ordering determined by comp. first1, last1, first2, last2, comp next_permutation Performs one permutation on the sequence in the range. first, last next_permutation Performs one permutation on the sequence in the range, where the ordering is determined by comp. first, last, comp prev_permutation Performs one reverse permutation on the sequence in the range. first, last prev_permutation Performs one reverse permutation on the sequence in the range, where the ordering is determined by comp. first, last, comp Generalized Numeric Operations accumulate Sequentially applies init = init + *iter to each object in the range. first, last, init accumulate Sequentially applies init = op(init, *iter) to each object in the range. first, last, init, op inner_product Sequentially applies init = init + (*iter1) * (*iter2) to C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 695 of 701
corresponding values from ranges 1 and 2. first1, last1, first2, init inner_product Sequentially applies init = op1( init, op2(*iter1, *iter2) ) to corresponding values from ranges 1 and 2. first1, last1, first2, init, op1, op2 partial_sum Adds values from start of range 1 to current iterator, and places the sums in corresponding iterator in range 2. first1, last1, first2 *iter2 = sum(*first1, *(first1+1), *(first1+2),...*iter1) partial_sum Sequentially applies op to objects between first1 and current iterator in range 1, and places results in corresponding iterator in range 2. first1, last1, first2, op answer = *first; for(iter=first+1; iter != iter1; iter++) op(answer, *iter) *iter2 = answer; adjacent_difference Subtracts adjacent objects in range 1 and places differences in range 2. first1, last1, first2 *iter2 = *(iter1+1) *iter1; adjacent_difference Sequentially applies op to adjacent objects in range 1 and places results in range 2. first1, last1, first2, op *iter2 = op(*(iter1+1), *iter1);
Member Functions
The same names are used for member functions that have similar purposes in the different containers. However, no container class includes all the available member functions. Table B-2 is intended to show which member functions are available for each container. Explanations of the functions are not given, either because they are more or less self-evident, or because they are explained in the text. Table B-2 Member functions vector list deque set multi-set map multi-map stack queue priority_ queue tree operator == x x x x x x x x x x operator < x x x x x x x x x x operator = x x x x x x x x operator[ ] x x x operator * x x x operator () x x operator + x operator - x operator ++ x x x operator -- x x x operator += x operator -= x begin x x x x x x x x end x x x x x x x x rbegin x x x x x x x x rend x x x x x x x x capacity x empty x x x x x x x x x x x size x x x x x x x x x x x max_size x x x x x x x x front x x x x back x x x x push_front x x push_back x x x pop_front x x pop_back x x x swap x x x x x x x x insert x x x x x x x x erase x x x x x x x x key_comp x x x x x value_comp x x x x find x x x x x count x x x x x lower_bound x x x x x upper_bound x x x x x equal_range x x x x x top x x push x x x pop x x x reserve x splice x remove x unique x merge x reverse x sort x rotate_left x rotate_right x
Iterators
Table B-3 lists the type of iterator required by each algorithm. Table B-3 Type of iterator required by algorithm Input Output Forward Bidirectional Random access for_each x find x find_if x adjacent_find x count x count_if x mismatch x equal x search x copy x x copy_backward x x iter_swap x swap_ranges x transform x x replace x replace_if x replace_copy x x fill x fill_n x generate x generate_n x remove x remove_if x remove_copy x x remove_copy_if x x unique x unique_copy x x reverse x reverse_copy x x rotate x rotate_copy x x random_shuffle x partition x stable_partition x sort x stable_sort x partial_sort x partial_sort_copy x x nth_element x lower_bound x upper_bound x equal_range x binary_search x merge x x inplace_merge x includes x set_union x x set_intersection x x set_difference x x set_symmetric_difference x x pop_heap x push_heap x make_heap x sort_heap x max_element x min_element x lexicographical_ comparison x next_permutation x prev_permutation x accumulate x inner_product x partial_sum x x adjacent_difference x x
Compiler Installation
Run the SETUP.EXE program in the MSDEV directory on the CD-ROM. You can select a typical installation if you have plenty of disk space, but if you want to install only the components necessary to develop the program in this book and similar console applications (which will be described in a moment), then you should select Custom installation. This cuts the hard disk space that you need for Visual C++ roughly in half, from almost 80MB to about 35MB. Well assume that you accept the directory name suggested by the installation program: C:\MSDEV\. Within this directory, the compiler and linker will be installed in a BIN subdirectory, header files will go in INCLUDE, library files will go in LIB, and so on.
Console Applications
The programs in this book will all compile to an application type that Microsoft calls a Console Application. This is an MS-DOS character-based program in which a user and a program communicate with each other by displaying text, each line below the last. Of course such applications are not graphics-based, as full-scale Windows applications are. However, the text-only approach vastly simplifies the programming, and allows us to focus on the C++ language rather than the peculiarities of Windows programming. Our discussion of the installation procedure will assume youll be developing only Console Applications.
Components
After youve selected Custom Installation, the SETUP program will present a dialog with a list of items, each with a check box. Heres how to handle each item on the list. In some cases the check boxes cant be altered directly; you need to click on the item to elicit a further list of check-boxed items. Developer Studio This must be checked. As we noted, its the development environment youll be using. Visual C++ Build Tools You should check this one too. It includes the compiler and linker youll use to transform your source file into an executable file. OLE Controls Uncheck this one; we wont be worrying about Object Linking and Embedding in this book. Runtime Libraries Runtime libraries are files that contain all the functions your program will need to perform I/O, math, data conversion, string handling, and many other things that are not built into the C++ language. Youll need one such library to write C++ programs. Click on this item and then on a button called Details to bring up a further series of four items: Static Library, Shared Library, Static Library Not Multithreaded, and Source Code. Check only one of these: Static Library Not Multithreaded. Uncheck the others. For the programs in this book you dont need multithreading, shared libraries, or source code. Foundation Class Libraries Click on this item to get to the next level of items, and then uncheck all of them. You dont need any of the foundation classes. Database Options Here, too, go to the next level of items, and uncheck all of them. You dont need any database options. Tools Go to the next level of items here as well, and uncheck all of them. You dont need any of these tools, which are for graphical Windows development. C++ Interactive Course by Robert Lafore, Pdf by: Venkat Reddy Page 697 of 701
Books Online This choice lets you copy all the documentation from the CD to your hard disk. Uncheck it, unless you have lots of disk space. Youll find the documentation is readily accessible from the CD (as long as its in the drive, of course).
This will prevent the program from terminating before you have time to read the output.
Multifile Programs
Creating a program with multiple source (.CPP) files requires repeating the same steps described above for each file. Place all the source files in the folder created for the Project Workspace. Use Insert Files Into Project from the Insert menu to associate each source file with the project. Compile each source file separately. Then link them all together by selecting Build from the Build menu. Actually, the appropriate files will be compiled automatically if you simply select Build, although this makes it less clear what steps are involved in the built process. To see what source files are used in a project, flip to the FileView tab in the Project Workspace window. Youll see not only the .CPP files, but the .H files, shown as a dependency.
DOS Targets
The FRIHORSE, NESHORSE, and STAHORSE programs in Chapter 9 were designed to work with the Borland C++ compiler. They contain calls to the Borland-specific library functions clrscr(), delay(), gotoxy(), randomize(), and random(). To work with other compilers, youll need to find substitutes for these functions. The file MSOFT.H, available in the APP_C directory on your CD-ROM, supplies such substitute functions. These functions have the same names as the Borland-specific functions, but internally they call functions that are supplied with Microsoft Visual C++ (versions 4.0 and later). To make it possible to compile the horse programs with the Microsoft compiler, place MSOFT.H in the same directory as your source file, and insert the directive #include "msoft.h" at the beginning of your source file. (Use quotes, not angle brackets.) This will cause calls to the Borland functions to be translated into calls to appropriate Microsoft functions.
Debugging
Even when your program compiles and links correctly, it still may not run the way you expected. The Developer Studio includes built-in debugging features that make it easy to track down errors. These are available from the Debug menu or using special keys.
Single-Stepping
The most fundamental debugging activity is single-stepping, or executing one program line at a time. There are two ways to do this. You can either step into functions called by program statements, executing all the statements within the function; or you can step over functions, treating a function call like a single statement. The key single-steps over functions, while the key single-steps into them.
Single-stepping is a useful way to see where your program is going. Sometimes youll be surprised to see that it wanders off course; this may solve some problems all by itself. You should probably avoid stepping into library functions, which are complicated and probably dont contain bugs anyway. If your program requires input from the user, youll need to switch to the program window from the Taskbar, and enter the necessary input.