On to C++
Table of Contents
- 1. How This Book Teaches You The Language.
- 2. How To Compile And Run A Simple Program.
- 3. How To Declare Variable
- 4. How To Write Arithmetic Expressions.
- 5. How To Write Statements that Read Information From Your Keyboard.
- 6. How-to Define Simple Functions.
- 7. How-to Benefit From Procedure Abstraction.
- 8. How-to Work with Local and Global Variables.
- 9. How-to Create Classes and Objects.
- 10. How-to Define Member Functions.
- 11. How-to Define Constructor Member Functions.
- 12. How-to Define Reader and Writer Member Functions.
- 13. How-to Benefit From Data Abstraction.
- 14. How-to Protect Member Variables From Harmful Reference.
- 15. How-to Define Classes That Inherit Variables And Functions.
- 16. How-to Design Classes And Class Hierarchies.
- 17. How-to Perform Tests Using Numerical Predicates.
- 18. How-to Write One-Way And Two-Way Conditional Statements.
- 19. How-to Combine Boolean Expressions.
- 20. How-to Write Iteration Statements.
- 21. How-to Process Data In Files.
- 22. How-to Write Recursive Functions.
- 23. How-to Solve Definition Ordering Problems With Function Prototypes.
- 24. How-to Work With Arrays Of Numbers.
- 25. How-to Work With Arrays Of Class Objects.
- 26. How-to Create File Streams For Input And Output.
- 27. How-to create New Class Objects At Run Time.
- 28. How-to Store Pointers To Class Objects.
- 29. How-to Write Programs That Find Member Functions At Run Time.
- 30. How-to Write Multiway Conditional Statements.
- 31. How-to Use Enumerations To Improve Readability.
- 32. How-to Write Constructors That Call Other Constructors.
- 33. How-to Write Member Functions That Call Other Members Functions.
- 34. How-to Use Protected And Private Variables And Functions.
- 35. How-to Use Protected And Private Class Derivations.
- 36. How-To Write Functions That Return Character Strings.
- 37. How-To Use Call-By-Reference Parameters.
- 38. How-To Overload The Output Operator.
- 39. How-To Produce Tabular Displays
- 40. How-To Write Statements That Read Character Strings.
- 41. How-To Test String Characters.
- 42. How-To Deposit Character Strings Into Class Objects.
- 43. How-To Reclaim Memory With Delete And Destructors.
- 44. How-To Prevent Object Copying.
- 45. How-To Organize A Multiple-File Program.
- 46. How-To Compile A Multiple-File Program.
- 47. How-To Implement Lists.
- 48. How-To Make One Class A Friend Of Another Class.
- 49. How-To Reuse Class Definitions Using Templates.
- 50. How-To Iterate Over Lists Using Iteration Class Object.
1 How This Book Teaches You The Language.
Why you should learn C++. Object-Oriented programming languages. Procedure-oriented programming languages. Programming cliches.
2 How To Compile And Run A Simple Program.
29
Conventions on file extensions for C++
source code vary: some programmers prefer -and some compilers require- C
, cc
, or cpp
.
30
…e.g., the name of the program that compiles and links may be CC
, g++
, or cxx
, just to mention a three popular examples. So, for a ~/fooDir/foo.cpp
we compile:
$ g++ foo.cpp -o foo $ ./foo
2.1 Edit.
I run into issues when compiling the script which appears on the handbook (pg. 23 section 84).
[txarly@ntxarly c++lab]$ g++ -o volume volume.cpp volume.cpp:1:22: fatal error: iostream.h: No such file or directory
Some goggling by and reading this post I found out I had to correct the script as shown below, note '<iostream>
' and 'using namespace std;
'
#include <iostream> using namespace std; main() { int height, width, length; cout << "Please, type three integers. " <<endl; cin >> height; cin >> width; cin >> length; cout << "The volume of a " << height << " by " << width << " by " << length << " box car is " << height * width * length << endl; }
3 How To Declare Variable
4 How To Write Arithmetic Expressions.
5 How To Write Statements that Read Information From Your Keyboard.
The >>
when used with the cin
'input-place' specification, the input operator picks up a value for a variable by watching what you type on your keyboard.
#include <iostream.h> main() { int height, width, length; cout << "Please, type three integers. " <<endl; cin >> height; cin >> width; cin >> length; cout << "The volume of a " << height << " by " << width << " by " << length << " box car is " << height * width * length << endl; }
6 How-to Define Simple Functions.
7 How-to Benefit From Procedure Abstraction.
8 How-to Work with Local and Global Variables.
9 How-to Create Classes and Objects.
10 How-to Define Member Functions.
11 How-to Define Constructor Member Functions.
12 How-to Define Reader and Writer Member Functions.
13 How-to Benefit From Data Abstraction.
14 How-to Protect Member Variables From Harmful Reference.
15 How-to Define Classes That Inherit Variables And Functions.
16 How-to Design Classes And Class Hierarchies.
17 How-to Perform Tests Using Numerical Predicates.
18 How-to Write One-Way And Two-Way Conditional Statements.
19 How-to Combine Boolean Expressions.
20 How-to Write Iteration Statements.
21 How-to Process Data In Files.
The following program computes box-car volumes as long as you type numbers; it stops when you type the control-d key-chord.
#include <iostream.h> double box_volume(double h, double w, double l){return h * w * l;} main(){ double height, width, depth; while (cin >> height >> width >> depth) cout << "The volume of a " << height << " by " << width << " by " << depth << " box car is " << box_volume (height, width, depth) << endl; cout << "You appear to have finished." << endl; }
Also, you can redirect input from your keyboard to a file. e.g. a file named test-data:
10.5 9.5 40.0 12.6 8.6 50.0
So, to call the function you need only to type, assuming you have called you program box_car
and you have prepared a test file named test-data
:
$ box_car < test-data
22 How-to Write Recursive Functions.
An example better than words..
#include <iostream> using namespace std; // Define recursive.. int recursive_power_of_2 (int n) { if (n == 0) return 1; else return 2 * recursive_power_of_2 (n - 1); } //test main () { cout << "2 to the 0th power is " << recursive_power_of_2 (0) << endl << "2 to the 1st power is " << recursive_power_of_2 (1) << endl << "2 to the 2nd power is " << recursive_power_of_2 (2) << endl << "2 to the 3rd power is " << recursive_power_of_2 (3) << endl; }
23 How-to Solve Definition Ordering Problems With Function Prototypes.
As learned in seg 349
the number of rabbits after more than 1 month is the sum of the number at the end of the previous month and the month before that, as shown next:
int rabbits (int n) { if (n == 0 || n == 1) return 1; else return previous_month (n) + penultimate_month (n); }
#include <iostream> using namespace std; //Function prototype for rabbits function int rabbits (int); //Function definitions requiring rabbits function prototype: int previous_month (int n) {return rabbits (n - 1);} int penultimate_month (int n) {return rabbits (n -2);} //Function definition for rabbits function: int rabbits (int n) { if (n == 0 || n == 1) return 1; else return previous_month (n) + penultimate_month (n); } //Test rabbits function main () { cout << "At the end of the month 1, there is " << rabbits (1) << endl << "At the end of the month 2, there are " << rabbits (2) << endl << "At the end of the month 3, there are " << rabbits (3) << endl << "At the end of the month 4, there are " << rabbits (4) << endl << "At the end of the month 5, there are " << rabbits (5) << endl; }
Each of the three cooperating functions can initiate a chain of calls that ends in a call to itself. Thus, the cooperating functions exhibit indirect, rather than direct, recursion.
24 How-to Work With Arrays Of Numbers.
To define a global array
int distances[5];
Then the following expressions, for example, inserts an integer into the place indexed by the value of counter
.
distances[counter] = 57;
The following is a program in which an array of five integers is defined, data are wired in via five assignments, and the data are accessed in an output statement:
#include <iostream> using namespace std; // Define global integer array: int distances[5]; main () { // Wire in sample data distances[0] = 57; distances[1] = 72; distances[2] = 94; distances[3] = 22; distances[4] = 35; //Display cout << "The total distance traveled is " << distances[0] + distances[1] + distances[2] + distances[3] + distances[4] << endl; }
//Result The total distance traveled is 280
24.1 PS.
Here I had to take an interesting break reading up on certain Rich Hickey's tracked performing curve which I do look against as the learning route me myself am trough (which this very notepad is proof of). Actually afterwards some LISP literacy (followed a quite hard EMACS learning curve) I picked up some of Python, R, HTML, CSS, & am into a rebuilt coming back to C++ though so better understood now as it's under a LISP-building-view approach.
25 How-to Work With Arrays Of Class Objects.
As in the following example:
#include <iostream> use namespace std; const double pi = 3.14159; //Define the cylinder class class cylinder { public: double radious, length; double volume () {return pi * radious * radious * length;} }; //Define cylinder array: cylinder oil_tanks[100]; main() { //Declare various variables: int limit, counter; double sum = 0.0; //Read numbers and write them into array: for (limit = 0; cin >> oil_tanks[limit].radious >> oil_tanks[limit].length; ++limit) ; //Compute volume: for (counter = 0; counter < limit; ++counter) sum = sum + oil_tanks[counter].volume (); //Display sum: cout << "The total volume in the " << limit << " storage tanks is " << sum << endl; }
26 How-to Create File Streams For Input And Output.
In this section we learn how to access information in files directly, so that we don't have to depend on input-output redirection to get information in and out of files.
A stream is a sequence of data objects. In the context of file input and output, cout
is the name of a stream that flows from your program to your screen, and cin
is the name of a stream that flows from your keyboard to your program.
#include <fstream> ifstream cilinder_stream ("test.data"); //of course, the file specification may include a file directory path: ifstream cilinder_stream ("/usr/phw/cpp/test.data"); //the following fileopening statement write down on to a file, rather: ofstream volume_stream ("test.result")
The following program, fills an array with cylinder radio and lengths from a file named test.data, and then writes the sum of the cylinders' volumes into a file named test.result.
#include <iostream> #include <fstream> cons double pi = 3.14159; //Define the cylinder class: class cylinder { public: double radius, length; double volume () {return pi * radius * radius * length;} }; //Define cylinder array: cylinder oil_tanks[100]; main () { //Declare variables: double sum = 0.0; //Connect an input stream to the file named test.data: ifstream cylinder_stream ("test.data"); //Connect an output stream to the file named test.result: ofstream volume_stream ("test.result"); //Read numbers from the test.data file: for (limit = 0; cylinder_stream >> oil_tanks[limit].radius >> oil_tanks[limit].length; ++limit); //Compute volume: for (counter = 0; counter < limit; ++counter) sum = sum + oil_tanks[counter].volume (); //Write sum into the test result file: volume_stream << "The total volume in the " << limit << " storage tanks is " << sum << endl; }
27 How-to create New Class Objects At Run Time.
28 How-to Store Pointers To Class Objects.
You may find yourself creating ridiculously large global arrays just to be sure that you don't run out of space. Such arrays can waste a lot of memory, particularly if they hold large class of objects. Fortunately, you now know that you can arrange for the C++
compiler to allocate only pointers at compile time, deferring the allocation of memory for objects until run time.
// recall you define an array of cylinder objects with a definition // that looks like a var definition with a bracketed number added: cylinder oil_tanks[100]; // And you can define an array of pointers to cylinders objects by // adding an asterisk before the array name: cylinder *oil_tank_pointers[100];
29 How-to Write Programs That Find Member Functions At Run Time.
456
.. you must somehow arrange for your compiled program to determine which [..] function to use, rather than having the C++
compiler decide in advance, at compile time.
[…] to mark a member function for selection at run time, rather than at compile time, you preface its definition with virtual
. Such function is said to be a virtual member function
(it won be available, hence, only virtual, at compile time)
class railroad_car { public: //Constructor: railroad_car () {} // Virtual display function: virtual void display_short_name () {}; }
458
Until you are completely comfortable with virtual functions, you may find the phrase multiversion function for which the version to be called is determined by object class at run time whenever you see the phrase 'virtual function'
30 How-to Write Multiway Conditional Statements.
You know how to use if
statements when you want a program to decide which of two alternative statements to execute. In this section, you learn how to use switch
statements when you want your program to decide which of many alternative statements to execute.
switch (integer-producing expression) { case integer constant 1: statement for integer 1 break; case integer constant 2: statement for integer 2 break; ... default: default statements }
The version of the analyze_train
shown in segment 460
contains a sequence of if-else
statements that determines what to do for any given value of the type_code
variable:
if (type_code == 0) train[n] = new engine; else if (type_code == 1) train[n] = new box_car; else if (type_code == 2) train[n] = new tank_car; else if (type_code == 3) train[n] = new caboose; // Most C++ programmers use a switch statement instead: switch (type_code) { case 0: train[n] = new engine; break; case 1: train[n] = new box_car; break; case 2: train[n] = new tank_car; break; case 3: train[n] = new caboose; break; }
31 How-to Use Enumerations To Improve Readability.
..making programs easier to understand by replacing integer codes by mnemonic symbols.
For example, you could define integer constants as follows near the beginning of your program:
const int eng_code = 0; const int box_code = 1; const int tnk_code = 2; const int cab_code = 3; // Although more experienced c++ programmers would be more likely to use // an enumeration statement: enum {eng_code, box_code, tnk_code, cab_code}; // so the switch statement could gain clarity by being writen this way: switch (type_code) { case eng_code: train[n] = new engine; break; case box_code: train[n] = new box_car; break; case tnk_code: train[n] = new tank_car; break; case cab_code: train[n] = new caboose; break; }
32 How-to Write Constructors That Call Other Constructors.
We should back to secc 11
where we learned that constructors are member functions that you use to construct class objects.
At this point
So that we could build construct the function as in the following
box_car() : box (10.5, 9.5, 40.0){}
Now let's compare this with the previous member-variable-assigning version
box_car () { height = 10.5; width = 9.5; length = 40.0; }
Also, if wish, we could arrange for explicit calls to more than one constructor. We simply separate the calls by commas, as in the following example:
box_car () : box (10.5, 9.5, 40.0), railroad_car (arguments) {}
And, the way of doing this is (e.g) as follows:
class box : public container { public: double height, width, length; // Default constructor: box () {} // Argument-bearing constructor: box (double h, double w, double l) { height = h; width = w; length = l; } // Other member function: double volume () {return height * width * length;} };
33 How-to Write Member Functions That Call Other Members Functions.
As said in the previous case, this issue is faced by recurring to a kind of 'template', a built-in one abstraction..
..to understand this (I think) better starting by the end.. (segment 509
) (Though it doesn't work for me (I've had to give it up for now))
34 How-to Use Protected And Private Variables And Functions.
To improve your understanding of the public, protected, and private parts of a class definition, contemplate the following diagram.
I have no included syntax details because it's quite close the case and would become a some tricky chapter. I find this will come out easily when on the writing task.
35 How-to Use Protected And Private Class Derivations.
36 How-To Write Functions That Return Character Strings.
#include <iostream> using namespace std; const double pi = 3.14159; class box { public: double height, width, length; // Default constructor: box ( ) { } // Argument-bearing constructor: box (double h, double w, double l) { height = h; width = w; length = l; } // Volume member function: double volume ( ) {return height * width * length;} }; // ...Cylinder definition goes here class cylinder { public: double radius, length; // Default constructor: cylinder ( ) { } // Other member functions // Argument-bearing constructor: double volume ( ) {return pi * radius * radius * length;} }; class railroad_car { public: railroad_car ( ) { } virtual char* short_name ( ) {return "rrc";} virtual double capacity ( ) {return 0.0;} }; class box_car : public railroad_car, public box { public: box_car ( ) : box (10.5, 9.5, 40.0) { } virtual char* short_name ( ) {return "box";} virtual double capacity ( ) {return volume ( );} }; // Tank car definition goes here class tank_car : public railroad_car, public cylinder { public: tank_car ( ) : cylinder (3.5, 40.0) { } virtual char* short_name ( ) {return "tnk";} virtual double capacity ( ) {return volume ( );} }; //..Engine & class engine : public railroad_car { public: engine ( ) { } virtual char* short_name ( ) {return "eng";} }; // ..Caboose definition goes here class caboose : public railroad_car { public: caboose ( ) { } virtual char* short_name ( ) {return "cab";} }; // Define railroad car pointer array: railroad_car *train[100]; // Declare enumeration constants, needed in switch statement: enum {eng_code, box_code, tnk_code, cab_code}; main ( ) { int n, car_count, type_code; for (car_count = 0; cin >> type_code; ++car_count) switch (type_code) { case eng_code: train[car_count] = new engine; break; case box_code: train[car_count] = new box_car; break; case tnk_code: train[car_count] = new tank_car; break; case cab_code: train[car_count] = new caboose; break; } //Display report. for (n = 0; n < car_count; ++n) // Display shortname & capacity & terminate the line: cout << train[n] -> short_name ( ) << " " << train[n] -> capacity ( ) << endl; } // Sample Data 0 1 1 2 3 // Result eng 0 box 3990 box 3990 tnk 1539.38 cab 0
I cannot see where it's wrong.. so, I decided post for help on the c++ g+ community and include the entire script as following:
well, unfolding out some context (as I think I got it) as whenever a string appears in an expression the value of that string is a pointer to the first element in the correspondent character array (and a final null character, which the array is filled with, as end-of-string marker) the idea is playing out with the potential of writing functions that return character strings. Or, as we could e.g. declare a variable whose value is a string by: 'char *variable name = character string;' & using 'char* function name' if we wanted a function to return a string. Lets say e.g. we wanted control to in an only (main) place a calling for returns which would be provided by local methods (although there would be a lot uses for this functionality (apparently)). e.g. make of this 2 following: virtual void display_capacity ( ) { } virtual void display_capacity ( ) {cout << volume ( );} more versatile by writing them this way: virtual double capacity( ){return 0.0;} virtual double capacity( ){return volume( );} The whole exercise as I could manage to complete summarizing with this chap some other previous can be seen here: [1] http://pastebin.com/Q362CVpy
37 How-To Use Call-By-Reference Parameters.
There are occasions when you have to hand a class object to a member function as an ordinary argument, rather than via the class-pointer operator or the class-member operator.
e.g. you might think that this function would work properly in the analyze_train
program if called as follows:
// DEFECTIVE attempt to define ordinary_capacity_function! double ordinary_capacity_function (railroad_car r) { return r.capacity ( ); } for (n = 0; n < car_count; ++n) { // Display short name and capacity and terminate the line: cout << train[n] -> short_name ( ) << " " << ordinary_capacity_function (*train[n]) << endl; --------- } ^ | Dereferenced array pointer identifies --* a chunk of memory that holds a box car, tank car, engine, or caboose
Perhaps unexpectedly, incorporating this version of ordinary_capacity_function
into the analyze_train
program leads to the following result:
eng 0 box 0 box 0 tnk 0 cab 0
To tell C++ that the parameter is to be a call-by-reference
parameter, we add an ampersand &
to the parameter's type indicator:
| v double ordinary_capacity_function (railroad_car& r) { return r.capacity ( ); }
With this one-character amendment, the ordinary_capacity_function
works fine. The analyze_train
program displays the following report, as it should:
eng 0 box 2880 box 2880 tnk 1130.97 cab 0
38 How-To Overload The Output Operator.
So figure out by target this chap is about the following statement:
Supose that you decide that you want to decorate the capacity report —the one displayed by the analyze_train
program— with a simple iconic train, such as the following:
[eng]-[box]-[box]-[tnk]-[cab]
39 How-To Produce Tabular Displays
Basically is about using the printf
function rather than the output operator.
40 How-To Write Statements That Read Character Strings.
As learned in seg. 36
now is about obtaining character strings from a file.
char input_buffer[100];
getline(name of character array, maximum characters to be read)
41 How-To Test String Characters.
It's about hoe to extract characters from strings, and how to use such characters to determine what to do next.
Obtaining e.g. the type-indicating character from the input_buffer
character array is easy:
input_buffer[4] //If data were stored in file e.g. as 'TPW-E-783' with the above statement we'd obtain 'E'
So:
char extract_car_code(char *input_buffer){...}
Could evolve to:
char extract_car_code(char *input_buffer){ return input_buffer[4]; }
So I could alter the switch
function to read from file by:
switch (extract_car_code (input_buffer)) { case 'E': train[n] = new engine; break; ... } //and still obtain de code and reporting e.g. for 'TPW-E-783' 'E' and so on..
42 How-To Deposit Character Strings Into Class Objects.
In the previous two sections, we learned how to obtain character strings from a file, how to extract characters from character strings, and how to test characters. In this section, we learn how to move a character string from a temporary buffer to a permanent location inside a class object using functions provided by c++
string handling library.
- As content of
input_buffer
is being continuously used, first we should e.g. create a character array to store the value (the char string) we want to work with, as in e.gnew char[10]
- Then, by using
#include <string>
& call e.g. forstrlen(input_buffer)
function, we could:- create a new array making sure to add
1
, to account for thenull
character as in:new char[strlen(input_buffer) + 1]
- create a new array making sure to add
- Next, using
strcpy
(for string copy) from string library function we must arrange to copy that content into the new character just been created:strcpy (e-g-serial_number, input_buffer);
43 How-To Reclaim Memory With Delete And Destructors.
After reading the chapter I indeed wondered why C++
does not reclaim memory automatically whenever a pointer is reassigned to a new object created by the new operator. The rationale is that chunks of memory accessed trough a pointer are often accessible through more than one pointer. Accordingly, C++
cannot assume that reassignment of a single pointer makes a chunk of memory inaccessible.
So, as this seems to have features of being a routine:
- We can use a
while
loop, e.g.:while (1) {...}
- As seen below we make the
railroad_car
destructor virtual, by which we tellC++
to treat all the lower-level destructors as virtual. Thus, a virtual destructor exposes destructors in derived classes (see segment 649).
Another way to prevent leaks is to keep track of the number of objects that you have created by having constructors and destructors increment and decrement an object counter. You could arrange for a global variable to be the object counter, better using what is called a static member variable.
Normal member variables are duplicated once for each class object. Static member variables are not —there is just one static member variable for the entire class in which the static member variable is defined.
#include <iostream> #include <fstream> #include <string> const double pi = 3.14159; // Box and cylinder class definitions go here class railroad_car { public: char *serial_number; // Constructors: railroad_car ( ) {++counter;} railroad_car (char *input_buffer) { ++counter; serial_number = new char[strlen(input_buffer) + 1]; strcpy (serial_number, input_buffer); } // Destructor: virtual ~railroad_car ( ) { cout << "Destroying a railroad car; " << --counter << " remain." << endl; delete [ ] serial_number; } // Other: virtual char* short_name ( ) {return "rrc";} virtual double capacity ( ) {return 0.0;} private: static int counter; }; int railroad_car::counter = 0; // Other railroad-car class definitions go here railroad_car *train[100]; char input_buffer[100]; enum {eng_code = 'E', box_code = 'B', tnk_code = 'T', cab_code = 'C'}; char extract_car_code (char *input_buffer) {return input_buffer[4];} void do_analysis (ifstream& car_stream) { int n, car_count; for (car_count = 0; car_stream >> input_buffer; ++car_count) { delete train[car_count]; switch (extract_car_code (input_buffer)) { case eng_code: train[car_count] = new engine (input_buffer); break; case box_code: train[car_count] = new box_car (input_buffer); break; // Entries for tank cars and cabooses } } car_stream.close ( ); for (n = 0; n < car_count; ++n) cout << train[n] -> serial_number << " " << train[n] -> short_name ( ) << " " << train[n] -> capacity ( ) << endl; } main ( ) { // Read file name and call analysis function repeatedly: while (1) { cout << "Please type the name of a train file." << endl << ": "; cin >> input_buffer; // Open the input file: ifstream car_stream (input_buffer); // Give up if the input file does not exist: if (!car_stream) {cout << "No such file." << endl; return 0;} // Analyze: do_analysis (car_stream); // Close: car_stream.close ( ); } }
44 How-To Prevent Object Copying.
Here more about memory management in general, and about copy constructors in particular.
45 How-To Organize A Multiple-File Program.
In section 16
you learned about the organisation of class hierarchies. In this section, you learn about the organisation of files.
Suppose, for the sake of illustration, that you decide to divide the analize_train
program into three files:
trains.cxx
, containers
& cars
; then, we can use #include
declarations to incorporate the text in the containers
file and the cars
file back into the trains.cxx
file:
#include "containers" #include "cars"
Note that, when a file specification is surrounded by double-quotation marks, the C++
compiler attempts to find the file in the current directory —which is presumably the same one that contains the source code.
With the header files properly included, we can use the C++
compiler to compile each source-code file separately, producing object code. The object code files, by convention, have o
extensions: trains.o
, containers.o
, cars.o
. …When you make changes in a big, multiple-file program, you need to recompile only the altered files; & leave unaltered files alone.
PS. I should skip going further into this section (though I got the idea) because of the info I have about the *.h
files (or libraries) (I still don't know what updated is this standard..)
46 How-To Compile A Multiple-File Program.
When you build big programs, with many header and source-code files, you eventually lose track of what depends on what. Accordingly, most operating systems that support C++
provide what is called the make
utility.
When you compile a multiple-file program using the make
utility, the file describing dependencies is called a makefile
. Following section 45
dependency would be reflected e.g. as:
analyze_train: trains.o containers.o cars.o trains.o: trains.cxx cointainers.h cars.h containers.o: containers.cxx containers.h cars.o: cars.cxx cars.h containers.h
47 How-To Implement Lists.
In this section, we learn about storing objects in lists. Why?
- The implementation of lists demonstrates that we can use
C++
classes to implement programming-language features, as well as to represent domain-specific concepts. - List manipulation is a useful tool: When we use list, instead of an ordinary array, we do not need to anticipate how many objects we will need to store, so we can conserve the space that would be wasted by safe worst-case estimates.
- Lists manipulation also provides a compelling context for the introduction of other
C++
concepts as class friends and template functions.
To do it we basically could include in the class definition itself a pointer to the next class object, excepting for the final one which always will carry a pointer to 0
(known as the NULL
pointer). This is what we can call a list but, made of internal pointers. However this design has at least two issues:
- We must add a new member variable to the class definition that should define the objects we want to tie together & which accessing to may have not get included or implementation could be so buggy.
- We may want to include particular class objects in more than one list, which would make so complicated when adding internal pointers for each such list.
So, we also can try a completely new approach. We can create a new class, the link class
. So that objects in this new class have two pointers; one pointing to the next link object, and the other pointing to a class member object. Advantages?.
- We don't have to alter the class definition of the objects we want to tie together.
- We can include class member objects in as many lists as we like without further class definition.
- We can include objects that belong to the class member we are on sub-classes.
Now, so that designing to the header & link classes, we need to answer the following questions:
- What member variables we do need for the pointers involved?
- What member functions we do need to create lists, to add new lists elements, and to accessing elements?
- Which member variables & member functions should be protected, and which public (though for now we will defer this question and make member variables & member functions all public)
Next, we need a way to create an empty list-one that has not link objects. Or a header constructor that explicitly assigns first_link_pointer
to the NULL
pointer, e.g.:
class header { public: link *first_link_pointer; header () { first_link_pointer = 0; } ... }; // or class header { public: link *first_link_pointer; header () { first_link_pointer = NULL; } ... };
We also need a function for adding an element to a list (which in its basic mode makes all additions to the front of the list & using the add
member function) as in:
A header variable | | Name of the element-adding function | | train.add (pointer to a class member object);
being add
a member function of the header
class it does add a pointer to a new class member object provided as an ordinary argument, & must have a parameter that is declared to be a pointer to that given class member object.
class header { public: link *first_link_pointer; header () { first_link_pointer = NULL; } // e.g. with railroad_car class seen this book troughout void add (railroad_car *new_element) { ... } ... };
Note, then, that part of the work done by add
is actually done by the two-argument constructor for the link
class.
class link { public: link *next_link_pointer; // e.g. railroad_car class railroad_car *element_pointer; link (railroad_car *e, link *l) { element_pointer = e; next_link_pointer = l; } };
So, the link
constructor is called when a new link object is created inside of the add
member function.
class header { public: link *first_link_pointer; header () { first_link_pointer = NULL; } // e.g. with railroad_car class seen this book troughout void add (railroad_car *new_element) { first_link_pointer = new link (new_element, first_link_pointer); ... } ... };
Note that add
reroutes the pointer in the header object to point to the new link.
At this point we can construct an empty list and add elements to an existing list, but we cannot get at a list's elements. We would need the following:
- A pointer,
current_link_pointer
class header { public: link *first_link_pointer; link *current_link_pointer; header ( ) { first_link_pointer = NULL; current_link_pointer = first_link_pointer; }
- A function,
advance
, that advancescurrent_link_pointer
from one link to the next.
void advance ( ) { current_link_pointer = current_link_pointer -> next_link_pointer; }
- A function
access
, that obtains a pointer to a, e.g.,railroad_car
object from the link pointed at bycurrent_link_pointer
railroad_car* access ( ) { return current_link_pointer -> element_pointer; }
- A function
endp
, for end predicate, that determines whether thecurrent_link_pointer
's value isNULL
This endp
predicate is easy to define, because, when we work on a pointer with the negation operation, the result is 0
of type int
, if the pointer is the NULL
pointer; otherwise, the result is 1
, of type int
, so:
int endp ( ) { return ! current_link_pointer; }
- A function,
reset
, that assigns the value ofcurrent_link_pointer
to the value offirst_link_pointer
void reset ( ) { current_link_pointer = first_link_pointer; }
An overview to which is done so far:
class header { public: link *first_link_pointer; link *current_link_pointer; header ( ) { first_link_pointer = NULL; current_link_pointer = first_link_pointer; } void add (railroad_car *new_element) { first_link_pointer = new link (new_element, first_link_pointer); current_link_pointer = first_link_pointer; } void advance ( ) { current_link_pointer = current_link_pointer -> next_link_pointer; } railroad_car* access ( ) { return current_link_pointer -> element_pointer; } int endp ( ) { return ! current_link_pointer; } void reset ( ) { current_link_pointer = first_link_pointer; } };
And the whole exercise to:
#include #include const double pi = 3.14159; // Box, cylinder, and railroad-car class definitions go here // Define list classes: class link { public: link *next_link_pointer; railroad_car *element_pointer; link (railroad_car *e, link *l) { element_pointer = e; next_link_pointer = l; } }; class header { public: link *first_link_pointer; link *current_link_pointer; header ( ) { first_link_pointer = NULL; current_link_pointer = first_link_pointer; } void add (railroad_car *new_element) { first_link_pointer = new link (new_element, first_link_pointer); current_link_pointer = first_link_pointer; } void advance ( ) { current_link_pointer = current_link_pointer -> next_link_pointer; } railroad_car* access ( ) { return current_link_pointer -> element_pointer; } int endp ( ) { return ! current_link_pointer; } void reset ( ) { current_link_pointer = first_link_pointer; } }; // Define list header: header train; char input_buffer[100]; enum {eng_code = 'E', box_code = 'B', tnk_code = 'T', cab_code = 'C'}; char extract_car_code (char *input_buffer) {return input_buffer[4];} main ( ) { // No initialization or increment expressions: for (; cin >> input_buffer;) switch (extract_car_code (input_buffer)) { case eng_code: train.add (new engine (input_buffer)); break; case box_code: train.add (new box_car (input_buffer)); break; case tnk_code: train.add (new tank_car (input_buffer)); break; case cab_code: train.add (new caboose (input_buffer)); break; } train.reset ( ); // No initialization; increment expression advances list: for (; !train.endp ( ) ; train.advance ( )) // Display number, short name, and capacity and terminate the line: cout << train.access ( ) -> serial_number << " " << train.access ( ) -> short_name ( ) << " " << train.access ( ) -> capacity ( ) << endl; } --- Data --- TPW-E-783 PPU-B-422 NYC-B-988 NYC-T-988 TPW-C-271 --- Result --- TPW-C-271 cab 0 NYC-T-988 tnk 1539.38 NYC-B-988 box 3990 PPU-B-422 box 3990 TPW-E-783 eng 0
48 How-To Make One Class A Friend Of Another Class.
In section 47 we learned about the definition of two classes, header
and link
& that all member variables and member functions in those two classes were in the public portion of the class definition, where they are too freely accessible.
Of course, add
, advance
, access
, endp
, reset
and the header
constructor are used in the train program and therefore should be in the public portion of the header class. In this section, we learn how to make all other member variables and member functions inaccessible, except through those six functions, which constitute the public interface. Along the way we learn about friends and why they are needed.
Well, happen that moving member variables & the link class constructor to the private would make them inaccessible & (or put them in the protected portion of the class definition) make it the header
class a subclass of the link
class would create more problems than help. This later e.g. would make header
objects to carry around element_pointer
& next_link_pointer
member variables as excess baggage.
So, there being a C++
mechanism whereby the link
class can grant complete access privileges to the member functions of the header
class. We just grab it & We do this by adding a friend declaration
to the definition of the link
class.
class link { friend class header; private: link *next_link_pointer; railroad_car *element_pointer; link (railroad_car *e, link *l) { element_pointer = e; next_link_pointer = l; } };
49 How-To Reuse Class Definitions Using Templates.
In the section 47
, we learned how to use the header
and link
classes to make e.g. a list of railroad cars. In this section we will use the template mechanism, a general feature of c++
with wide applicability to package and reuse so, once we have had defined template classes for list, it's easy to create new lists.
As a step forward understanding c++
mechanism we have just extracted a template from the header
and link
classes shown in segment 763
class link { friend class header; private: link *next_link_pointer; railroad_car *element_pointer; link (railroad_car *e, link *l) { element_pointer = e; next_link_pointer = l; } }; class header { public: header ( ) { first_link_pointer = NULL; current_link_pointer = first_link_pointer; } void add (railroad_car *new_element) { first_link_pointer = new link (new_element, first_link_pointer); current_link_pointer = first_link_pointer; } void advance ( ) { current_link_pointer = current_link_pointer -> next_link_pointer; } railroad_car* access ( ) { return current_link_pointer -> element_pointer; } int endp ( ) { return ! current_link_pointer; } void reset ( ) { current_link_pointer = first_link_pointer; } private: link *first_link_pointer; link *current_link_pointer; }; //////// class link { friend class header; private: link *next_link_pointer; / *element_pointer; link (/ *e, link *l) { element_pointer = e; next_link_pointer = l; } }; class header { public: header ( ) { first_link_pointer = NULL; current_link_pointer = first_link_pointer; } void add (/ *new_element) { first_link_pointer = new link (new_element, first_link_pointer); current_link_pointer = first_link_pointer; } void advance ( ) { current_link_pointer = current_link_pointer -> next_link_pointer; } /* access ( ) { return current_link_pointer -> element_pointer; } int endp ( ) { return ! current_link_pointer; } void reset ( ) { current_link_pointer = first_link_pointer; } private: link *first_link_pointer; link *current_link_pointer; };
49.1 Template, simple syntax…
template <class link_parameter> class link { ... }; template <class header_parameter> class header { ... };
Here, then, is the analyze_train
program, newly revised to make use of the generic template class:
#include #include const double pi = 3.14159; // Box, cylinder, and railroad-car class definitions go here // Define list classes: template class link { friend class header; private: link *next_link_pointer; link_parameter *element_pointer; link (link_parameter *e, link *l) { element_pointer = e; next_link_pointer = l; } }; template class header { public: header ( ) { first_link_pointer = NULL; current_link_pointer = first_link_pointer; } void add (header_parameter *new_element) { first_link_pointer = new link (new_element, first_link_pointer); current_link_pointer = first_link_pointer; } void advance ( ) { current_link_pointer = current_link_pointer -> next_link_pointer; } header_parameter* access ( ) { return current_link_pointer -> element_pointer; } int endp ( ) { return ! current_link_pointer; } void reset ( ) { current_link_pointer = first_link_pointer; } private: link *first_link_pointer; link *current_link_pointer; }; // Define list header: header train; char input_buffer[100]; enum {eng_code = 'E', box_code = 'B', tnk_code = 'T', cab_code = 'C'}; char extract_car_code (char *input_buffer) {return input_buffer[4];} main ( ) { // Train list is constructed here train.reset ( ); // No initialization; increment expression advances list: for (; !train.endp ( ) ; train.advance ( )) // Display number, short name, and capacity and terminate the line: cout << train.access ( ) -> serial_number << " " << train.access ( ) -> short_name ( ) << " " << train.access ( ) -> capacity ( ) << endl; } --- Data --- TPW-E-783 PPU-B-422 NYC-B-988 NYC-T-988 TPW-C-271 --- Result --- TPW-C-271 cab 0 NYC-T-988 tnk 1539.38 NYC-B-988 box 3990 PPU-B-422 box 3990 TPW-E-783 eng 0
50 How-To Iterate Over Lists Using Iteration Class Object.
Now, what if we ever need to have more than one traversal going simultaneously (the reset
function and changes in lists (as defined traversing a list alters that list because of the changes made to the current_link_pointer
) concurrency is not necessarily stated).
So, in this section, we learn about iteration
classes, which allow us to separate list construction from list traversal, thereby enabling multiple simultaneous traversals.
So we will need:
- Firstly, define to the
iterator
class to be a template class
template class iterator { ... private: link* current__link_pointer; link* first_link_pointer; };
- Next, we need member functions to
access
the elements, then anadvance
function so going fromcurrent_link_pointer
to the next link object, then, test ifcurrent_link_pointer
is theNULL
pointer, andreset
thecurrent_link_pointer
. So basically we could borrow all these member functions from the definition of the header template class.
template class iterator { public: ... iterator_parameter* access ( ) { return current_link_pointer -> element_pointer; } void advance ( ) { current_link_pointer = current_link_pointer -> next_link_pointer; } int endp ( ) { return ! current_link_pointer; } void reset ( ) { current_link_pointer = first_link_pointer; } private: link* current_link_pointer; link* first_link_pointer; };
- Then, on defining the
iterator
constructor:
iterator (header<iterator_parameter>& header) { first_link_pointer = header.first_link_pointer; current_link_pointer = first_link_pointer; }
- Next, we must ensure that the
iterator
member function have access to theheader
andlink
member variables by declaring theiterator
class to be a friend of both theheader
class and thelink
class.
template <class link_parameter> class link { friend class iterator<link_parameter>; friend class header<link_parameter>; ... }; template <class parameter> class header { friend class iterator<parameter>; .... };
Now, in the following program,
#include #include const double pi = 3.14159; // Container, railroad-car, and list definitions go here template class iterator { public: iterator (header& header) { first_link_pointer = header.first_link_pointer; current_link_pointer = first_link_pointer; } iterator_parameter* access ( ) { return current_link_pointer -> element_pointer; } void advance ( ) { current_link_pointer = current_link_pointer -> next_link_pointer; } int endp ( ) { return ! current_link_pointer; } void reset ( ) { current_link_pointer = first_link_pointer; } private: link* current_link_pointer; link* first_link_pointer; }; // Define list header: header train; char input_buffer[100]; enum {eng_code = 'E', box_code = 'B', tnk_code = 'T', cab_code = 'C'}; char extract_car_code (char *input_buffer) {return input_buffer[4];} main ( ) { // Collect train elements as usual // Define and initialize iterator class object: //Seg; 785: the expression 'railroad_car', provided as template argument in the expression // 'iterator<railroad_car> train_iterator(train)' //causes the iterator class to be instantiated as to deal with //railroad_cars. The expression 'train' provides the new iterator with //access to a particular list of railroad cars. iterator <railroad_car> train_iterator (train); // Iterate: train_iterator.reset ( ); for (; !train_iterator.endp ( ) ; train_iterator.advance ( )) // Display number, short name, and capacity and terminate the line: cout << train_iterator.access ( ) -> serial_number << " " << train_iterator.access ( ) -> short_name ( ) << " " << train_iterator.access ( ) -> capacity ( ) << endl; } --- Data --- TPW-E-783 PPU-B-422 NYC-B-988 NYC-T-988 TPW-C-271 --- Result --- TPW-C-271 cab 0 NYC-T-988 tnk 1539.38 NYC-B-988 box 3990 PPU-B-422 box 3990 TPW-E-783 eng 0