Section Contents
Topics should be read in order presented.
After clicking on a topic below, press "BACK" on your browser to return to Section Contents. Or, instead of jumping to topics and returning back to Section Contents, begin, rather, with the topic introduction to classes as it appears immediately following Section Contents, and then advance sequentially through the remainder of the topics.
introduction to classes:
The keyword struct is used to create a user-defined data type. It is designed to model the properties of a programming entity whose purpose is defined by the name of the struct. Those properties take the form of the variables contained within the struct. Within the previous section on new features of C++, as you may recall, we spent time studying a few programs that made use of structs. structs contain properties. A function, on the other hand, is a module of code designed to perform a specific task. The procedure a function carries out is specified by the name of the function. A struct's name is a description of what its properties (variables) represent. Let us continue. A function performs its task by manipulating variables. A function's manipulation of the variables given to it, in turn, can be thought of as being the behavior of the function.
A class, moving along, could be considered to be a fusing-together of a struct and a function. Every class one will ever come across will be found to be divisible into 2 main components: properties (variables), and the behaviors (functions) paired along with those properties. The variables contained within a class are called data members. The functions contained within a class, on the other hand, are called member functions. It is the job of the member functions to act upon the data members in the manner specified by the programmer. As with a struct, the name given to a class could be thought of as a means of expressing the underlying concept that the class represents. The programmer, furthermore, should put forth effort into giving a class a name that effectively describes the purpose of the class.
structs can be thought of as the nouns in a program: nouns in the sense that structs do not actually "do" anything - they are simply programmer-specified collections of variables. Functions, in turn, could be thought of as the verbs in a program: verbs in the sense that functions perform specific actions - tasks that are based upon the purpose the programmer has for the function. classes, as with structs, are most usually nouns - the "things" or entities active within a program: the name of a class is most often not an action or a task. However, at times, it is not impossible for a class to be given a name that is a verb (although this is less frequently the case). In general, though, it is advisable that the programmer give a class a name that is a noun. Note: like a class, a struct can contain functions. However, to make our study of classes easier we will assume that a struct holds only properties (variables).
The data members of a class describe the properties of a class - the properties that specify what kind of a 'noun' the class is. There can be as many or as little data members as desired, based upon the requirements of a class to participate in the program being written. However, take note, classes contain tasks (member functions). Almost always, a class will contain multiple, if not several, member functions. Member functions, quite simply, tell a class what to do with its data members. What is the end result of a completed class? Before us would lie a programming entity as real as a real-world, daily-life entity - real in the sense that all real-world, daily-life entities share what a class possesses: properties and behaviors.
classes are the most important, most fundamental aspect of programming in C++. They make possible a special type of programming called object-oriented programming. This type of programming takes over where regular C programming leaves off. C, you see, involves a type of programming called procedural programming. There is nothing actually "wrong" with procedural programming, take note - but object-oriented programming is far superior. Programming using a procedural programming language is called "structured" programming. "Structured" programming is used to describe a style of programming in which a program makes decisions in a specific manner. A program that makes decisions in this manner is verb-oriented - the program must perform actions and tasks appropriately, in the manner that is in the correct order and sequence. Object-oriented programming, on the other hand, is noun-oriented.
Object-oriented programming (or "OOP") does not discard the use of "structured" programming, but in fact requires its use: "structured" programming is a very real part of OOP. In another sense, OOP takes over where "structured" programming leaves off. The majority of this tutorial on C++ is centered around exactly how OOP works. We will learn, in this present section and in sections to come, how OOP works by discussing the 3 features of OOP that make it what it is: classes, inheritance, and polymorphism. Do not be alarmed if any of these terms seem odd or confusing as of the moment: the concepts behind these terms will be made clearer as we confront them one by one. Moving along: in the current section here and now we will be studying the first (of the three) powerful features of object-oriented programming (just mentioned) that make object-oriented programming ("OOP") what it is. This first (of the three) features of object-oriented programming is known as encapsulation (which, quite clearly, can be referred to in a somewhat simpler fashion using the term classes, without any loss of meaning). OOP can be seen as superior to "structured" programming alone in terms of how code and variables are organized. Programming in C++ using classes, you see, has a very direct, noticable effect upon how easy programs are to update, modify, and maintain, because OOP, as stated, is noun-oriented. This will be made clearer as we continue onward.
What would be a good summary of how encapsulation works? It separates the inner, hidden details of a class from its external, outermost expression. Quite clearly, the "inner, hidden details" of a class can change, without affecting the "external, outermost expression" of the class. Without doubt, as you will agree, this is a powerful programming feature: a programmer can modify his class without altering how it appears to those using it. All one need concern himself with, you see, is the member functions of a class. What these member functions do upon being called lies entirely in the hands the programmer who created the class. Furthermore, and finally, grouping variables with the code that acts upon them makes it much easier for the programmer to "debug" - work through the errors of - the program he is writing.
What makes grouping data members with the member functions that act upon them ("encapsulation") important? It makes possible what is called a "software brick". A "software brick" is a collection of information that is completely self-contained. Understanding exactly how a "software brick" (class) is "self-contained", so to speak, does not require of us to go far beyond what we've just discussed so far on the topic of classes. Because the data members of a class are confined within the boundaries of that class, no member functions external to the class will ever refer to those data members. Without doubt, this makes things much easier for the programmer to write his class (as well as for other programmers who may later make use of the class for their own purposes). Equally so, because the member functions of a class are confined within the boundaries of that class, no data members external to the class will ever require the presence of those member functions.
The collection of information that makes up a "software brick" (class) could also be considered to be "self-contained" in the sense that each "software brick" is centered around the one unique special goal for which the "brick" is responsible for achieving. Without doubt, assigning a single designated programming purpose to a software "brick" that the "brick" is meant to provide makes it possible for the programmer to "piece together" such "bricks" in any way the programmer requires in order to construct a complete program.
What does this mean? It means that the creator of a class can write his class independently of the classes being written by other programmers. This is a powerful feature of object-oriented programming. For example: a programmer can focus his full attention upon the class he is responsible for writing, without "worrying" about what goes on within the classes that other programmers are working on. Quite clearly, the use of "encapsulation" when writing a program makes possible the use of "teams", so to speak, where which each "team" is handed the one class for which the team is considered responsible. In conclusion: once a "software brick" (class) has been completed, it can be pieced together with other completed software "bricks" in any possible way that a program could require it to be, and the combined network of "bricks" in the program will fit together nicely.
Another way of thinking of the concept of encapsulation is the idea of what is called a "black box". A class could be thought of as being a "black box". What does this mean? Those using a class are unaware of the inner workings of that class: exactly what goes on inside of and within a class cannot be seen by those external to the class. All that those making use of a class need concern themselves with is the service that the class offers. Having been made familiar with this "service", one making use of a class need only "request" this service of the class, and the class will provide a "response", even though the one requesting the service of the class is not aware of the inner, hidden details of how the class responds to the request. In what way is this favorable? All that one making use of a class need focus upon is what he wants the class to do for him. Upon presenting this request to the class, one making use of that class is guaranteed a response suitable to the request. In this sense, input is given to the "black box", the input is processed within the "black box", and the result is returned from the black box as output.
How does encapsulation make programs much easier to update and modify (as well as easier to understand)? Because the programmer knows precisely what variables it is that the member functions refer to, he can, possessing that knowledge, more easily assign meaning to the class. This grouping of functions and variables, finally, makes possible the writing of programs that are both potent and powerful - programs in which the programmer need not closely examine the parts of a program in order to understand their basic purpose.
Let us discuss yet another advantage to using classes. Assume that one is attempting to modify or update a program written in a procedural programming language. A problem can arise: when any given module of code within the program is changed, that change can greatly affect modules of code in other parts of the program. (A similar problem can happen when variables are added or taken away.) Changing a single module of code, therefore, can result in a chain-reaction of unintended programming difficulty - creating a monotonous, tangled web of programming confusion. To add to what was stated above, for example, an altered module of code may end up referring to variables that are no longer there (or to new variables that the module of code does not know what to do with); removing variables from or adding variables to a module of code may result in code that won't work. With the use of classes, you see, code is "paired" with the variables it manipulates - solving the problems just discussed.
How, then, do classes work? Using a class involves, first of all, providing a class definition. A class definition, take note, is very similar to a struct definition. In what ways, you may ask, is a class definition different from a struct definition? A struct contains only variables (properties). A class, on the other hand, contains both variables (properties) and functions (behaviors). As you may recall, the variables contained within a class are called data members, and the functions contained within a class are called member functions. Displayed below is an example of a very simple class definition - a class we will call class Pet.
a simple class:
class Pet { public: Pet(); // constructor. void PerformTrick(); // task member function. ~Pet(); // destructor. private: char *trick; // name of trick. };
One of the primary purposes of a class definition, as can be seen above, is to declare the data members of a class. Moving along: focus your attention, if you will, upon the declaration of the member function PerformTrick( ). When a member function is declared within a class definition, most usually it is only the function prototype of the member function that is included, as can be seen above. The body of the member function is then placed in a separate file. At times, however, if the body of a member function is very small (or the member function possesses an empty body), it is a common practice to simply include the body of the member function within the class definition file.
Recall, if you will, from C, that all struct definitions require a semicolon ( ; ) to be placed immediately following them. The same is true for all class definitions: all class definitions must be followed immediately by a semicolon.
public and private keywords:
The use of the public and private keywords when setting up a class serves a very special purpose. Using these keywords when setting up a class allows the development of a "firewall", so to speak, that controls the flow of information into and out from a class. Why produce such a "firewall"? The member functions within the public field of a class definition (in our case the member function PerformTrick( ) ) are intended to be accessed by a program from outside of their class. The purpose of the public keyword, then, is to designate which members of a class are available to those external to the class. Just how this works may be unclear at the moment, but we will address the concept in material to come. The data member 'trick' in the private field of the definition of class Pet displayed above, however, cannot be accessed from outside of its class (which is the intention of the private keyword). The purpose of the private keyword, then, is to designate which members of a class cannot be reached by those external to the class. The only way to manipulate the data member 'trick', therefore, is through the public member function PerformTrick( ). Let us discuss why one would use public and private in this manner.
Producing a "firewall" in between the inside and outside of a class allows those using the class to be able to perform a task offered by the class, without worrying about the inner details of how the class does what it does. How would one define the term "firewall"? In computer terms, a "firewall" is "a program closely examining and controlling all input and output to and from a source and a destination" - such as an Internet firewall program. This "firewall" around a class is called encapsulation: those external to a class must first pass through the public member functions of a class in order to reach the private data members of that class. The purpose of the private keyword, then, once again, is to forbid those external to a class access to the data members of the class.
Using a "firewall" in this fashion ensures that only the member functions of a class can manipulate the data members of that class. Those external to a class, therefore, as stated, must first pass through the public member functions of a class in order to reach the private data members of that class. The data members of a class "belong" to the member functions of that class - and hence the member functions of a class should be the only means by which the data members of a class are manipulated.
Displayed below, once again, is the class definition of class Pet:
class Pet { public: Pet(); // constructor. void PerformTrick(); // task member function. ~Pet(); // destructor. private: char *trick; // name of trick. };
the constructor and destructor:
The constructor and destructor of a class were designed to prepare a class for use, on the one hand, and then, on the other hand, to take care of things when the class is done being used. The constructor prepares the class for use. The destructor, in turn, takes care of things when the class is done being used. There is really nothing difficult or complex about understanding how the constructor and destructor of a class work.
The constructor of a class always possesses the name of the class to which it belongs. Furthermore, as you can see in the class Pet definition, it has no return type (not even void). Although a constructor can receive parameters, for simplicity we will assume that it does not. What is the purpose of a constructor? A constructor's purpose is to initialize the values of the data members of the class to which that constructor belongs. It is designed to prepare the data members of a class for use. In addition, the constructor of a class should be made use of to ensure that every data member of the class has a valid value (as opposed to the "garbage" value that an uninitialized variable possesses). The constructor of a class is called when a class is brought into being in the form of a class variable. Exactly what a "class variable" is and what it means for a class to be "brought into being" will be discussed shortly.
The destructor of a class, like the name of a constructor, possesses the name of the class it belongs to: the only difference is that the name of a destructor is always preceded by a 'tilde' ( ~ ). A destructor, like a constructor, has no return type (not even void). (Equally so, neither does a destructor ever receive any parameters.) What is the purpose of a destructor? A destructor's main purpose is to use the delete operator to free all memory allocated by means of new during the time that the destructor's class was being used. The use of new to "set aside" space in memory as needed, followed by the use of delete to "give back" that space in memory, makes it possible for a program to perform what we've come to know as dynamic memory allocation. Dynamic memory allocation using new and delete, as you may recall, was discussed in the previous section on new features of C++. Finally: in general, all class deallocation activity (performed upon data members of a class using delete) should be reserved specifically for the destructor of that class.
A couple of paragraphs earlier we learned that the constructor of a class is called "when a class is brought into being in the form of a class variable". What is a "class variable" and what does it mean for a class to be "brought into being"? As we will learn, to put a class to use, we "bring it into being" in the form of a class variable. As with all variables in C and C++, it is possible to declare class variables in program code. In this sense, it is by declaring a class variable that we bring a class into being. Although the idea behind declaring class variables may seem unclear at the moment, an example of a class variable being declared could be Pet pet. The declaration of a class variable could be thought of as being the "beginning" of that variable's "lifetime" - the moment at which the class variable is created.
The declaration of a "class variable" (possibly after the opening brace - { - of a function) could be thought of as the "birth" of the variable - the "beginning" of its "lifetime". Equally so, the "death" of the variable occurs when the program reaches the closing brace - } - of the function. This is also called "going out of scope". Such a pairing of opening and closing braces (which often takes the form of the braces that define the body of a function) also defines, along the lines of our current topic of study, what is called the "visibility" of a variable (or class variable). The "visibility" of a variable, in a nutshell, refers to where in a program a variable can be accessed. The concept of the "lifetime" of a variable is of importance here in that when the lifetime of a class variable reaches its completion, the destructor of that class is called. Let us focus our attention once again upon the topic of dynamic memory allocation.
In review: make note, firstly, if you will, that the practice of dynamic memory allocation requires the presence of a pointer variable; this makes perfect sense given that both dynamic memory allocation and pointer variables operate in terms of computer addresses in one way or another. The purpose of the pointer variable, quite clearly, is to store the location in memory of the allocated memory so that the memory can be accessed later on in the program. Let us continue. Through the skilled use of dynamic memory allocation using new and delete, a program has the potential to possess unlimited memory throughout the duration of a program's execution: if space is "set aside" for memory using new for only as long as the memory need be used, and if the space is afterward "given back", using delete, when the memory is done being used, the program will never run out of space with which it can perform further dynamic memory allocation. Furthermore: for a destructor to "clean up" after a class once the class is done being used is not limited to the practice of applying dynamic memory allocation (using new and delete) within a class.
To better understand this, assume, if you will, that within the constructor of a class, a file had been opened so that the class can write to or read from the file. Just as for every usage of new, as we've learned, there should always be a "matching" usage of delete, the same applies to files: the programmer having written a statement, within the constructor of a class, that opens a file so that the file can be later accessed, the programmer would then go about "cleaning up", within the destructor of the class, after the file that had been opened, by means of writing a statement that closes the file.
As you will agree, opening a file within the constructor of a class is similar in every way to the constructor of a class preparing a data member for use by means of new. Equally so, closing such a file within the destructor of a class could be considered quite similar to the destructor of a class using delete upon a data member to take care of that data member when it is done being used. Opening a file within the constructor of a class, you see, prepares the file for use. Closing a file within the destructor of a class, on the other hand, takes care of the file after it is done being used.
As we've just learned, opening and closing a file is not the only way, other than that of dynamic memory allocation management, in which a constructor and destructor can be used together. Many other possibilities other than managing dynamic memory allocation, quite clearly, exist as well.
There always exists the possibility that the constructor of a class, declared within the class definition file of a class, goes unused. Equally so, a class may not make use of dynamic memory allocation (by means of new) during their lifetime and hence, have no need for a destructor declared within the class definition of a class. This gives the programmer 2 options.
The first option would be to give a declared constructor (or destructor) of a class an empty body: an opening - { - and closing - } - brace with nothing in between. Such braces are necessary if a program is to successfully compile.
The second option would be for the programmer to simply not declare the unused constructor (or destructor) within the definition of a class: if the programmer makes this choice, the compiler, quite simply, "generates" an empty body (opening and closing braces with nothing in between) for the constructor or destructor of the class. "Generated" constructors and destructors do not appear in the definition of the class to which they belong (and hence neither do their braces): they are not visible to the programmer. Consisting only of empty braces, they "do nothing", and exist solely for the purpose of acting as a kind of "substitute" for an undeclared constructor or destructor.
Note: above on the topic of the constructor and destructor, we were introduced to the term "class variable", and this term will be used again in material to come. What does this term mean? What must be made clear, firstly, is that the term "class variable" does not refer to what we have come to call by the name data member. Data members refer to the properties of a class - the variables declared private within a class definition, which are acted upon by the public member functions of the class. A class variable, rather, could be best described as a "bringing into being" of a class.
To better understand what is meant by a "bringing into being" of a class, let us go back to something we are already familiar with: structs. Given a struct named struct StructVar (which gets its name from the term "struct variable"), a good example of a "bringing into being" of such a struct (in the form of a struct variable) could be StructVar var. Make note here that you may have noticed in the struct variable declaration just shown (StructVar var), the keyword "struct" is not placed in front of the 'structure tag' (the structure tag in this case being StructVar). This is so, quite clearly, because, as of the present moment, we are dealing with C++, not C: in C, as you may recall from the previous section on new features of C++, the presence of the keyword "struct" is required for all struct variable declarations; an example of the C style of declaring a struct variable, therefore, would be struct StructVar var. In C++, on the other hand, only the structure tag is necessary, as in StructVar var.
Having briefly talked about the idea behind struct variables, let us return to our discussion on class variables. Given a class named class ClassVar (which gets its name from the term "class variable"), a good example of a "bringing into being" of such a class (in the form of a class variable) could be ClassVar var. That is all we need know for now. The details behind how classes are "brought into being" will be discussed later in this section and in sections to come. As of the moment, what must be clearly understood is that the terms "class variable" and "data member", as stated, possess two separate meanings. Make note here that in the class variable declaration ClassVar var, as with all class variable declarations, the keyword "class" is not to be placed before the name of the class (as in the statement class ClassVar var); all that is required concerning the class variable declaration, rather, is the name of the class by itself, as in ClassVar var.
Displayed below are the bodies of the constructor and destructor of class Pet. Note: the member functions below, as well as member functions to come, make use of a few predefined <string.h> functions.
Pet::Pet() // class Pet constructor. { int length; // length of string. length = strlen("flip"); // allocate memory trick = new char[length + 1]; // for trick. strcpy(trick, "flip"); // } Pet::~Pet() // class Pet destructor. { delete [] trick; // deallocate memory. }
Notice, if you will, the presence of the empty square brackets ([]) in between the delete operator and the name of the pointer variable trick inside of the destructor of class Pet. This notation is necessary when using delete to "give back" the memory "taken" by an array allocated by means of new. Upon further observing the destructor of class Pet, it is not hard to see that delete is using the pointer variable trick to access the array. As you can see by examining the usage of new inside of the constructor of class Pet, the array being deallocated inside of the destructor of class Pet is a char array. In the previous section on new features of C++, as you may recall, we were introduced to the concept of applying delete to arrays allocated by means of new.
Let us now study the constructor of class Pet in greater detail. The constructor of class Pet, as stated above, makes use of a few predefined <string.h> functions. As you may recall from C, a string is simply a collection of text characters. In this sense, a char array could be considered to be a string. The predefined function strlen( ), as you may further recall from C, returns the length, in terms of text characters, of the string that the function takes as a parameter. The predefined function strcpy( ), equally so, copies a source string into a destination string. In the above usage of strcpy( ), then, the string "flip" is being copied into the memory allocated using the pointer variable trick. As can be seen in the expression "flip" in the code shown above and as we will learn later in this section, it is possible to designate the contents of a string by enclosing the characters that make up the string in double quotes.
Let us further examine the constructor of class Pet. As you can see in the constructor of class Pet, the pointer variable trick is being used to allocate memory for the char array by means of new. Within the square brackets (the value within the square brackets being what designates the number of characters in the char array), as you can also see, 1 is being added onto the variable length (the value held by length being the number of text characters in the string "flip"). The purpose of this extra character, quite clearly, is to make room for what is called the "null character". The "null character", so to speak, specifies where in memory a string "stops".
the binary scope resolution operator:
You may have noticed the double-colon symbol (::) in the middle of the member function body names above. This is called the binary scope resolution operator. Its purpose is to tell a program which class a member function body belongs to. The syntax for using the binary scope resolution operator is as follows:
return_type class_name::member_function_name (parameters)
One important use of the binary scope resolution operator is to designate the classes to which 2 member functions of equivalent names belong (those member functions being contained within 2 different classes). The binary scope resolution operator is used when specifying the body of a member function (and not as a function prototype within the definition of a class).
class scope:
In the body of the constructor and destructor of class Pet displayed above, you may have noticed something strange: the char pointer trick, class Pet's data member, was accessed by these functions without even being passed to the functions. How is that possible? This is an example of what is called class scope. The idea behind class scope is simple: all member functions of a class have complete access to the data members of that class. This feature not only makes using classes easy but gives power to the programmer.
more on classes:
Having been introduced to the constructor and destuctor of class Pet, displayed below is the body of the remaining class Pet member function:
void Pet::PerformTrick() { cout << "The pet performed a " << trick << " as a trick." << endl; }
This member function is an example of one of the 5 types of member functions a class can possess (that is, according to my own style of explaining C++): task member functions, set (assignment) member functions, get (access) member functions, boolean (predicate) member functions, and utility member functions. PerformTrick( ) is a task member function. Later in the text we will discuss in detail exactly how a task member function works. Included in the discussion, for that matter, will be a full explanation of how the remaining 4 types of possible class member functions carry out their designated purposes.
A task member function will always be declared public. Usually, its purpose is to manipulate the data members of its class in some way. Using a task member function allows a programmer to be able to tell a class how to behave in a certain way. One can perform some sort of designated activity offered by the class (that is centered around the fundamental purpose that the class is meant to serve), without worrying about the inner details of how the class does what it does. A task member function represents a behavior of the class. Such a behavior, in turn, most usually alters the properties (data members) in some way, as determined by the programmer. All task member functions, being declared public, are accessed from outside of their class. When a task member function is used, what it does within the class is hidden from those outside of the class requesting the services of the class that the member function offers. This allows one to relate to the class by means of its 'external interface', rather than worry about the 'inner details' that would otherwise complicate the matter.
Displayed below is a simple program demonstrating the use of class Pet as defined above:
void main() { Pet pet; // object created. pet.PerformTrick(); // message sent to class Pet. }
OUTPUT:
The pet performed a flip as a trick.As you can see, the above program is very short and to the point. At the beginning of the program a class variable (of class Pet) is declared. Upon the class variable being declared, the constructor of class Pet is called. The declaration is identical to the declaration of a struct variable. Upon examining the declaration, one can see that only the class name is required to declare the class variable: there is no need for the statement class Pet pet. Furthermore, class variables operate in a way very similar to how struct variables operate. However, C++ provides a very specific term for a bringing into being of a class variable: in C++, class variables are called objects. It is through an object of a class that we gain access to the public member functions of that class. Observe the next, and final, line of code. We see that the class Pet object is used to access its PerformTrick( ) member function. Observe further, if you will, that the class Pet object accesses its member function in the same way that a struct accesses its own variables: by means of the "dot operator" ( . ). It is through an object of a class, once again, that the public member functions of that class are accessed.
The statement pet.PerformTrick( ) is the equivalent of a function call. In C++, to access a member function through an object of its class is known as sending that class a "message". As you may recall, PerformTrick( ) is a task member function. To better understand how the above PerformTrick( ) "message" to class Pet works, recall the "firewall" concept mentioned earlier. To send class Pet a "message" is the equivalent of accessing that class from the outside (through an object of that class). Sending a "message" to a task member function of a class (such as PerformTrick( ) ) may or may not, based upon the intentions of the programmer, manipulate the data members of that class in some way. Just how class Pet carries out the "message" sent by PerformTrick( ) is hidden from those accessing the class from the outside. As stated earlier, this is encapsulation: an active "firewall" in which one need only concern himself with the behaviors (member functions) - and not the properties (data members) - of a class. This is the whole reason that the private keyword was invented: to deny access to the properties (data members) of a class to those external to the class. From outside of a class, one's only means of reaching the data members of that class is through its member functions.
"What if", one may ask, "we wanted to access the data members of a class through an object of that class?" This is not allowed! Why not? This is the purpose of the private keyword: to forbid any and all data members from being directly accessed from outside of a class (that is, through an object of that class), as in pet.trick. Up until now, we have been informed that data members should always be declared private. For example, the statement pet.trick (accessing a data member using the dot operator) will not work if trick is declared a private member of class Pet. To be able to directly access a data member using an object of its class - along with the dot operator - would be to ruin the most fundamental aspects represented by encapsulation.
Furthermore, on a separate topic: at the very moment the class Pet object pet is created (declared) in the program above, the program immediately allocates space in memory necessary to hold the object. The memory having been allocated, the program then calls the constructor of class Pet. As you may recall from earlier in this section on the topic of the constructor and destructor, this is the beginning of the "lifetime" of the object. When main( ) ends, hence ending the "lifetime" of the class Pet object, the destructor of class Pet is called. Make note here that the calling of the destructor of a class is not all that takes place upon the ending of an object's lifetime. After the destructor of a class has been called (upon the ending of an object's lifetime), the object itself is deallocated. Make further note that for a variable (which includes objects) to be "deallocated" is also known as that variable being "destroyed".
a complete class:
We are now ready to cover the 5 types of class member functions that a class can possess: task member functions, set (assignment) member functions, get (access) member functions, boolean (predicate) member functions, and utility member functions. We will, next of all, introduce into the situation more of what we have set down already concerning class Pet. New member functions and data members will be added to class Pet, of course making it more complex. Upon covering the 5 types of class member functions, however, our study on classes and encapsulation will be complete.
Within the previous section on new features of C++, we were introduced to the concept of logical operations. A "logical expression", in review, is an argument in program code that can be either true or false. A possible example of a "logical expression", in further review, could be if (a = = b). The purpose of this statement, as you may recall from C, is to determine if the variable a and the variable b both contain the same value. If the expression (a = = b) is true, the expression will generate a 1. If the expression (a = = b) is false, the expression will generate a 0. As the means of designating the 1 or 0 that a logical expression generates, the int data type was used. It was then reasoned that the int data type was not actually created for the purpose of dealing with arguments that can be either true or false, and what we needed was an entirely new data type dedicated to handling logical operations. The code shown below does just that: below, a data type is created that is specifically designed for managing true / false conditions within a program.
The code shown below creates the bool data type. This data type gets its name from the term "boolean". What makes the term "boolean" of importance here is that the complete class Pet program to come (that we have yet to encounter) makes use of what is called a boolean member function. Just what a "boolean" member function is and does will be discussed shortly.
#define bool int // creates the 'bool' data type - // 'bool' is the equivalent of 'int'. const bool true = 1; // set the constant 'true'. const bool false = 0; // set the constant 'false'.
Of importance here is the statement #define bool int. Whenever the compiler, during the compile process, encounters the term "bool" within the code of a program that uses this #define statement, the compiler "replaces", so to speak, that occurrence of "bool" with "int". It is for such a "replacement" of one programming term for another that #define statements are commonly used. What does this accomplish? What must be fully understood, firstly, is that we are assuming that no "bool" data type actually exists. However, the "int" data type (int being a built-in data type of C and C++) is a data type that, in the eyes of the compiler, does exist. Moving along: that the compiler interprets all occurrences of "bool" in program code as being "int" (int being a built-in data type that the compiler is familiar with) is given.
To those reading the code of such a program, however, the code does not first pass through the compiler on its way to the reader: no such "replacement" of "bool" with "int" takes place. This makes the program easier to understand, you see, to those examining it: as we've learned, when the compiler encounters "bool" in program code, the compiler thinks that "bool" is equivalent to "int" (int being a built-in data type of C and C++ that the compiler is familiar with). When the reader comes across "bool" in program code, on the other hand, "bool" is a very real data type: there is no compiler to "replace" "bool" with "int", as stated, and hence, "bool" remains as it is. The statement #define bool int, in summary and in a manner of speaking, "fools" the compiler, so to speak, into thinking that "bool" is an actual existing data type. When the reader encounters "bool" in program code, in conclusion, he is to assume that "bool" is a data type that is just as real as a built-in data type such as char or float.
Also of importance in the code shown above is the usage of the const keyword. When const is used in a variable declaration, the variable must be initialized when it is declared. Once a variable declared using const has been initialized, the value of the variable cannot be altered afterward. Variables are declared using const, then, when the value held by the variable will not change throughout the duration of a program. This is indeed the case with the variables true and false: the variable true will always hold the value 1; the variable false will always hold the value 0.
Note: as you may recall from reading the introduction to this C++ tutorial, this tutorial is written based upon the standard set down by Microsoft Visual C++ 1.52. As was stated, this version of C++ could be considered to be very old. It was further stated that the reason for basing this tutorial upon such an "outdated" version of C++ was to make the material easier to understand: many of the aspects and features of newer, more up to date versions of C++ are advanced. The point to be expressed here, then, is that the reason it was necessary to create a bool data type in this tutorial (as well as to declare the variables const bool true and const bool false) is that Microsoft Visual C++ 1.52 has no bool data type (nor are true and false part of that version of C++). Why is this important? As it would happen, more recent versions of C++ DO have a bool data type (and also possess true and false as keywords). How are we to react, then, in response to this situation? Rather than let this minor issue get in the way of our understanding of this tutorial, we should allow it to teach us that C++ is in fact an ever-evolving language.
Displayed below is the complete, fully-developed class definition of class Pet:
class Pet { public: Pet(); // constructor. void PerformTrick(); // task member function. void SetTrickNum (int); // assignment member function. int GetTrickNum(); // access member function. bool TrickIsComplete(); // boolean member function. ~Pet(); // destructor. private: void ChooseTrick(); // utility member function. int trick_number; // number of given trick. char *pet_trick1; // the trick "flip". char *pet_trick2; // the trick "jump". char *pet_trick3; // the trick "spin". char cur_trick[10]; // name of the chosen trick. bool trick_complete; // boolean data member. };
Do not be alarmed if anything in the above class definition of class Pet appears out of the ordinary: it contains material that we have not yet covered. The purpose of the above class definition, then, is to provide an introduction to what we need to know ahead of time, so that we will be better prepared when the time comes to discuss the contents of the class definition in greater detail. As before, you can see that a class definition contains both the member functions and the data members of a class.
To start off, let us observe the bodies of the constructor and destructor of class Pet, displayed below. The body of each of the two functions is self-explanatory, with comments added to document the code.
Pet::Pet() { int length; // length of current string. length = strlen("flip"); // allocate memory for pet_trick1 = new char[length + 1]; // first trick. strcpy(pet_trick1, "flip"); length = strlen("jump"); // allocate memory for pet_trick2 = new char[length + 1]; // second trick. strcpy(pet_trick2, "jump"); length = strlen("spin"); // allocate memory for pet_trick3 = new char[length + 1]; // third trick. strcpy(pet_trick3, "spin"); strcpy(cur_trick, NULL); // assign string non-garbage value. trick_number = 0; // give a default value. trick_complete = false; // trick is not done yet. } Pet::~Pet() { delete [] pet_trick1; // deallocate first trick. delete [] pet_trick2; // deallocate second trick. delete [] pet_trick3; // deallocate third trick. }
the 5 types of member functions:
The 5 types of class member functions, once again, that a class can possess, are task member functions, set (assignment) member functions, get (access) member functions, boolean (predicate) member functions, and utility member functions. Having been introduced to the 5 types of class member functions, it is now time to go about discussing and explaining each type of member function.
task member functions:
PerformTrick( ) is the task member function of class Pet. As you may recall, we have already covered the topic of task member functions. By sending a "message" to a class in the form of a task member function (performed through an object of that class), one can bring about some sort of designated activity offered by the class (that is centered around the fundamental purpose that the class is meant to serve), without worrying about the inner details of how the class does what it does. A task member function represents a behavior of a class - a behavior that typically manipulates the properties (data members) of a class in some way. Task member functions are always accessed from the outside of a class - that is, through a "message" sent by an object of that class. Given a class Pet object named pet, an example of the PerformTrick( ) message being sent to class Pet through that object could be pet.PerformTrick( ). As we've learned, the internal workings of how a task member function does what it does are hidden, so to speak, from those outside of the class requesting the services of the class that the member function offers: those outside of the class can relate to the class by means of its 'external interface', rather than worry about the 'inner details' that would otherwise complicate the matter. This goes along the lines of the "firewall" concept of encapsulation discussed earlier.
utility member functions:
Displayed once again below is the body of the PerformTrick( ) member function, with additions to suit the new needs of class Pet. Directly below the code of PerformTrick( ), furthermore, is the body of the utility member function ChooseTrick( ). As the first means of understanding utility member functions, concentrate, if you will, on the definition of ChooseTrick( ) in the class definition of class Pet displayed earlier. Take note that the placement of ChooseTrick( ) in the class definition of class Pet may seem odd - odd in that ChooseTrick( ) is a member function that happens to be declared private!
A utility member function, like a task member function, performs some sort of designated activity - it "does" something. However, because it is declared private, it cannot be accessed from outside of its class (that is, as a "message" performed through a class object). Like a task member function, a utility member function may manipulate the properties (data members) of its class in some way. What sets a utility member function apart from a task member function, though, is that a utility member function is typically only involved in the inner, hidden details of how the class does what it does. Although a utility member function, like a task member function, "does" something, what it does is to be considered unimportant to those accessing a class from the outside (as a "message" performed through a class object). To send class Pet the message pet.ChooseTrick( ) is essentially impossible, take note, because ChooseTrick( ) is declared private.
Displayed below are the bodies of task member function PerformTrick( ) and of utility member function ChooseTrick( ):
void Pet::PerformTrick() { ChooseTrick(); // utility member function. if ( (trick_number >= 1) && (trick_number <= 3) ) { cout << "The pet performed a " << cur_trick << " as a trick." << endl; trick_complete = true; // boolean data member } // assignment. } void Pet::ChooseTrick() { switch (trick_number) { case 1 : strcpy(cur_trick, pet_trick1); // copy first break; // trick. case 2 : strcpy(cur_trick, pet_trick2); // copy second break; // trick. case 3 : strcpy(cur_trick, pet_trick3); // copy third break; // trick. } }
Take a look, if you will, at the first statement within the body of PerformTrick( ). It is a call to utility member function ChooseTrick( ). What does the utility member function ChooseTrick( ) do? Let us examine ChooseTrick( ) more closely. Upon first viewing ChooseTrick( ), you may ask the question, "where did the variable 'trick_number' in the switch( ) statement come from?" Recall, if you will, the topic presented earlier on class scope: all member functions of a class automatically have direct access to all data members of that class. Equally so, all member functions of a class automatically have direct access to all other member functions of that class (that is, within and inside of a class, no external class object (such as pet) and dot operator ( . ) is required in order to reach another member function within the same class). This is demonstrated, quite clearly, in the call to ChooseTrick( ) performed from within PerformTrick( ).
Let us continue. Having learned that it is by means of class scope that 'trick_number' can be made use of within ChooseTrick( ), from where, one may ask, does trick_number (the argument taken by the switch( ) statement) receive its value? The answer is that the value of 'trick_number' is obtained from outside of class Pet, by means of a "message" sent through a class Pet object. This "message", in the complete class Pet program to come, sends class Pet a number from 1 to 3 (that number being the user's response to the menu prompting the user as to the pet trick to be performed). The details of how such a message works will be discussed shortly. How does the rest of ChooseTrick( ) work? Based upon the value of trick_number, strcpy( ) is used, within the chosen 'case' option of the switch( ) statement, to copy the contents of one of the 3 already-existing 'pet_trick' strings into cur_trick - the name of the chosen trick.
Once cur_trick holds the appropriate contents (the contents of cur_trick being dependent upon the value held by trick_number), the program leaves ChooseTrick( ) and comes back to PerformTrick( ). The remaining portion of PerformTrick( ) consists of an if( ) statement. This if( ) statement checks the value of trick_number (obtained, as we will learn, by means of a message sent to class Pet through a class Pet object) to see if the user had inputted a number that corresponds to one of the three tricks presented. This is self-explanatory. If the user had correctly inputted a valid number (a number from 1 to 3), this would mean that ChooseTrick( ) would have copied one of the three 'pet_trick' strings into cur_trick: cout is used, within the body of the if( ) statement of PerformTrick( ), to display the trick (cur_trick) that the pet had performed. Afterward, within the body of the if( ) statement, the program manipulates the boolean data member 'trick_complete' (by setting it to the constant value true) to specify that the pet has in fact finished performing a trick. We will see how data member trick_complete affects those outside of class Pet once we arrive at the topic on boolean member functions. Upon the program encountering the if( ) statement within PerformTrick( ), and trick_number does not contain a valid value (a number from 1 to 3), the body of the if( ) statement will be skipped and the program will exit PerformTrick( ).
set (assignment) and get (access) member functions:
By now the concept of a "firewall" should be clearly and fully understood. As we've learned, all data members should be declared as private. This prevents data members from being accessed from outside of their class (by means of a class object and the dot operator), an example of which would be pet.trick_number. This is necessary in order for the practice of proper encapsulation, which should also be clearly and fully understood. As you may recall from past discussion, the primary way that data members are manipulated from the outside of a class is through public task member functions. Let us continue. We will now introduce two new types of public member functions into the situation: "set" (assignment) and "get" (access) member functions. What purposes, you may ask, do these functions serve? "Set" and "get" member functions allow us to assign values to, and access the values of, the data members of a class, without violating the rules behind encapsulation, nor the "firewall" principle we are familiar with.
This is because "set" and "get" member functions are themselves firewalls. It's really quite simple. "Set" member functions allow data members to be assigned values, and at the same time allow data members to be free of the risk of being altered from the outside of a class. "Get" member functions, in turn, allow the values of data members to be accessed, without the danger of any data members of a class being directly manipulated. Like task member functions, "set" and "get" member functions are always declared public. To use a "set" or "get" member function of a class in a program, access that function through a "message" to its class (through a class object and the dot operator). Displayed below are the bodies of the "set" member function and the "get" member function of class Pet:
void Pet::SetTrickNum (int trick_num) { trick_number = trick_num; } int Pet::GetTrickNum() { return trick_number; }
These functions are quite self-explanatory. To use a "set" member function to assign a data member a value, pass the function a parameter (by means of a message sent through a class object) containing the value you wish to assign that data member to. Given a class Pet object named pet, an example of the SetTrickNum( ) message being sent to class Pet through that object (from the environment external to class Pet) could be pet.SetTrickNum (trick_num). The primary purpose of a "set" member function, take note, is to send a value into a class from outside of that class (through the "firewall" of the class); within the body of such a "set" member function, as things go, the data member to be assigned a value is given the contents of the received parameter. As a rule, the name of the received parameter of a "set" member function should be modified, if necessary, so that it is not equivalent to the name of the data member being assigned to the value of that received parameter (quite simply, so that the undesired occurrence of assigning a data member to itself does not occur, as in trick_number = trick_number). As you can see in the body of SetTrickNum( ) displayed above, finally, the assignment of the data member trick_number to the parameter received by SetTrickNum( ) takes place.
Let us continue. A "get" member function's purpose, next of all, is to send the value of a data member out through the "firewall" of a class, into the external environment outside of the class. Most usually, a "get" member function does not receive parameters. Its goal is to return a value - the value of the data member to which access is being provided. This can be observed above in the form of the body of GetTrickNum( ): return trick_number. Given a class Pet object named pet, an example of the GetTrickNum( ) message being sent to class Pet through that object (that returns the value of the data member trick_number) could be trick_num = pet.GetTrickNum( ). In the complete class Pet program to come we will witness the actual usage of these "set" and "get" member functions.
Let us further discuss "set" (assignment) and "get" (access) member functions. In order for these concepts to be grasped as they should, it must be realized that there exists 2 types of "environments", so to speak, concerning classes: the environment inside of a class, and the environment outside of a class. This brings us back to the idea of a "firewall" that has been the topic of discussion. In review: in C++, the keywords public and private were created to control the flow of information into and out from a class. Because the data members of a class are declared private, data members of a class cannot be reached by those external to that class (using a class object and the dot operator, as in pet.trick_number). We then learned that most usually, but not always, the member functions of a class are declared public. Because such class member functions are declared public, they are available from outside of their class (using a class object and the dot operator, as in pet.PerformTrick( ) ).
Task member functions, as we discussed earlier, as with all public member functions, are meant to enforce the practice of encapsulation: those external to a class must first pass through the public member functions of a class in order to reach the private data members of that class. We then learned that the goal of a "task" member function, in addition to enforcing the practice of encapsulation, is to perform some sort of designated activity offered by the class that is centered around the fundamental purpose that the class is meant to serve. The inner details of how a "task" member function does what it does are hidden, as stated, from those requesting the services of the class that the member function offers. A task member function represents a behavior of a class - a behavior that typically acts upon the properties (data members) of a class in some way.
"Set" (assignment) and "get" (access) member functions, in their own way, also act upon the properties (data members) of a class. However, "set" and "get" member functions do not actually "do" anything (a designated activity), as is the case with a "task" member function. Their purpose is to make available (to those external to a class) data members that would be otherwise inaccessible. Let us continue. Along the lines of encapsulation, as you may recall, it is only through the public member functions of a class that those external to a class can manipulate private data members. This should be clearly understood. Given that "set" and "get" member functions are themselves public, one may reason, in what way is using "set" and "get" member functions any different than simply choosing to declare the data members of a class as public? In other words: what makes the use of "set" and "get" member functions favorable? This brings us back to the concept of the "firewall" to which we have already been made familiar.
"Set" (assignment) and "get" (access) member functions exist for the purpose of maintaining the private status of the data members of a class, in that their use is the alternative to making data members public. The whole fundamental purpose for which the private keyword was created, you see, was to enforce the practice of encapsulation. It is in this sense that the private keyword is a "friend" of the programmer: its use will "remind" the programmer that the rules of encapsulation are being broken if the programmer attempts to directly access a data member (as in pet.trick_number). This brings us back to the idea of making data members public. To make the data members of a class public, as we've learned, is to violate the fundamental principle of encapsulation and of the concept of the "firewall" that has been emphasized.
Let us assume, then, that the class Pet data member trick_number had been declared public. Having been declared public, the data member itself can be accessed (through a class object and the dot operator), as in pet.trick_number. Since the data member itself can be accessed, the data member can be directly modified, as in pet.trick_number = 1. In what way is the use of "set" and "get" member functions preferable (in comparison to directly modifying a public data member)? In a nutshell: when "set" and "get" member functions are used to maintain a "firewall" for a data member in between the environments inside and outside of a class, it is not the data member itself that those external to the class are accessing.
Rather, when information concerning a data member is sent into and out from a class (using "set" and "get" member functions), what is actually being passed through the firewall is a COPY of a VALUE. For example, a call to a "set" member function such as pet.SetTrickNum (trick_num) sends a COPY of the VALUE held by the variable trick_num into class Pet (in the form of trick_num being passed as a parameter). A "get" member function, such as GetTrickNum( ), on the other hand, whose body consists of the statement return trick_number, sends, upon being called, a COPY of the VALUE held by the data member trick_number out from class Pet (in the form of a return statement). In both situations, the data member trick_number is not directly accessed.
boolean (predicate) member functions:
Displayed below is the body of the "boolean" member function TrickIsComplete( ) of class Pet:
bool Pet::TrickIsComplete() { if (trick_complete == true) return true; else if (trick_complete == false) return false; }
What does "boolean" mean? "Boolean" is the proper term for describing a concept that we had discussed in detail within the previous section on new features of C++: the concept of an expression that can be either true or false. As you may recall from the piece of code shown earlier, bool is the data type that we had created for performing boolean operations. As you may further recall from the piece of code shown earlier, 2 const variables (variables whose values will not change throughout the duration of a program) of the bool data type were declared and initialized. These two variables were created, quite clearly, for the purpose of making the use of the bool data type in a program possible. These two variables, named appropriately, are true and false. The use of these two variables in program code, as you will agree, makes things clearer to those reading a program. For example, the term true in program code is more meaningful than a 1, and the term false in program code is more meaningful than a 0.
Moving along: boolean member functions are separate from task member functions in that boolean member functions do not actually "do" anything: they simply return a value related to a certain true / false condition of a class, nothing more. (Boolean member functions are also called 'predicate' member functions.) "Boolean" member functions affirm or deny a logical argument, and can be a powerful tool in evaluating true / false aspects of a class. How does TrickIsComplete( ) work within class Pet? Whether TrickIsComplete( ), when called, returns true or returns false, is directly dependent upon the contents, at that point in time, of the bool (boolean) data member trick_complete of class Pet. If trick_complete holds true, TrickIsComplete( ) returns true. If trick_complete holds false, TrickIsComplete( ) returns false. It's that simple.
Most usually, calls to boolean member functions appear within if( ) statements. Given a class Pet object named pet, an example of the TrickIsComplete( ) message being sent to class Pet through that object (as part of an if( ) statement) could be if (pet.TrickIsComplete( ) = = true).
Displayed below is a full, complete program demonstrating the use of class Pet. Given everything we've covered, understanding the code should not be that difficult.
a complete class Pet program:
#include "pet.h" void main() { Pet pet; // class Pet object declared. int trick_num; // inputted trick #. int trick_done_num; // received trick #. cout << "Enter number of the trick to be performed:" << endl; cout << " 1 - flip" << endl; // menu of tricks cout << " 2 - jump" << endl; // # 1-3. cout << " 3 - spin" << endl; cin >> trick_num; pet.SetTrickNum (trick_num); // assignment message. pet.PerformTrick(); // task message. if (pet.TrickIsComplete() == true) // boolean message. { trick_done_num = pet.GetTrickNum(); // access message. cout << "Trick number " << trick_done_num << " was performed." << endl; } else if (pet.TrickIsComplete() == false) // boolean message. cout << "No trick performed." << endl; }
OUTPUT:
number 1 chosen from menu:
The pet performed a flip as a trick.
Trick number 1 was performed.
number 2 chosen from menu:
The pet performed a jump as a trick.
Trick number 2 was performed.
number 3 chosen from menu:
The pet performed a spin as a trick.
Trick number 3 was performed.
Notice, if you will, the #include statement above main( ). This statement prepares the program to be able to use class Pet. Worth mentioning is that because this class definition file is surrounded by double quotes, it refers to a class created by a programmer (that class in this case being class Pet). The other possibility is that such a file be surrounded by angle brackets, as in <stdio.h>. Angle brackets mean that a program is using a file within the C / C++ library of prewritten functions.
In the first line of code in the above program, a class Pet object is declared. Upon this object being declared, the constructor of class Pet is called. Next is the declaration of the variable that is used to input, from the user, the number of the trick to be performed. Directly afterward, the variable that is used to obtain the number of the chosen trick is declared; this variable, as we will see, will receive its value from a class Pet "get" (access) member function. Next, the user is prompted to input the number of the desired trick, out of a menu of 3 choices. The remainder of main( ) consists of "messages" sent to class Pet through the class Pet object and the dot operator.
Immediately after the user has inputted the number of the desired trick, the "set" (assignment) member function SetTrickNum( ) is used to transfer the value inputted by the user into class Pet (by means of a message sent to class Pet). class Pet having received that value, the next line of code in main( ) is another message sent to class Pet: a function call to PerformTrick( ) (the task member function of class Pet) is performed. Inside the body of PerformTrick( ) and within class Pet, ChooseTrick( ) - a utility member function - is called, whose purpose is to copy the appropriate string into cur_trick (the name of the chosen trick). Take note here that the string copied into cur_trick is directly dependent upon the value that trick_number had received from the message pet.SetTrickNum (trick_num) sent from within main( ).
Next, inside of PerformTrick( ), there is a test, by means of an if( ) statement, to determine if trick_number holds a valid value (a number from 1 to 3). If trick_number does hold a valid value, that would mean that ChooseTrick( ) would have copied the correct string into cur_trick: cout is used, within PerformTrick( ), to output to the user the name of the pet trick (cur_trick) that had been performed, and finally, trick_complete is set to true. Let us direct our attention back to main( ).
In the two if( ) statements within main( ) that follow the call to PerformTrick( ), a predicate (boolean) member function - TrickIsComplete( ) - is called (in the form of a message sent to class Pet) to determine whether or not a trick had been performed. Assuming a trick was performed (TrickIsComplete( ) returns true), the first of the two if( ) statements is chosen. Take note here that what TrickIsComplete( ) returns (true or false) is directly dependent upon the value held by trick_complete (within and inside of class Pet) at that point in time.
Inside the body of the first if( ) statement, another message is sent to class Pet: an access member function - GetTrickNum( ) - is called. GetTrickNum( ), in being called, returns the current value of the data member trick_number out from within class Pet, and based upon what was returned, cout is used, within main( ), to inform the user as to the number of the trick that had been performed. If a trick was not performed (TrickIsComplete( ) returns false), the second of the two if( ) statements is chosen, in which cout is used, within main( ), to express to the user the fact that no trick was performed.
After the program goes through the two if( ) statements in main( ), the program reaches the closing brace - } - of main( ), ending the lifetime of the class Pet object. As we've learned, the ending of the lifetime of a class object, firstly, brings about the calling of the destructor of the object. Second and finally, the object is deallocated altogether.
to previous section |
to table of contents |
to next section |
Comments, questions, feedback: jsfsite@yahoo.com |