LABORATORY 8 Getting classy Objectives Objects are the basic unit of programming in object-oriented languages like C++. The C++ construct for defining new types of objects is the class. A class is the “blueprint” from which an object can be created. This laboratory examines the basic mechanisms of class types by inspecting several different classes. Key Concepts _ class construct Access specification Information hiding _ Constructors _ Encapsulation _ Inspectors _ Data members _ Mutators _ Facilitators _ _ GETTING STARTED Copy the following files to your directory, you will use them for this lab. Circle.h, Circle.o, Circle.cpp Complex.h, Complex.o, complex.cpp BankAccount.h, BankAccount.o, BankAccount.cpp, banking.cpp Circle Class Objects are the fundamental units of programming in object-oriented languages such as C++. Objects are models of information and are implemented as software packages that contain or encapsulate both attributes and behavior. A data abstraction is a representation of information and the operations to be performed on it. A fundamental type is a type that the programming language provides. For example, in C++, the type int and the operations on it are part of the language definition. That is, all C++ compilers must provide int objects. In addition to the fundamental types, C++ provides several mechanisms to define other types. These other types are called programmer- or user-defined types. The class construct is the most important mechanism for defining new types. With this construct, software engineers can define encapsulated objects. When defining a class, we typically use the information-hiding principle: All interaction with an object should be constrained to use a well defined interface that allows underlying object implementation details to be safely ignored. By following the information-hiding principle, we can create classes that tend to be reliable and easily reused. To consider the basics of creating a new object type using the class construct, we first examine the declaration of a programmer-defined—Circle. Whenever one is designing something, it helps to know how the thing will be used. Our goal in designing the class Circle is to create an object that is both easy to construct and simple to use. The first thing to do when creating a class-type object is to determine the attributes of the object. For circle objects, the necessary attributes are, its center of course, its radius. In C++, the attributes of a class-type object are referred to as the data members. The following are the declarations of the data members of Circle: float x; float y; float radius; The second step when creating a class-type object is to determine the messages the object can receive and the operations that can be performed on the object. This portion of an object is called its behavior component. The behavior component of a class-type object is a collection of member functions that provide the ability to send messages to the object requesting it to perform some action. These member functions provide the public interface to achieve the desired behavior; other member functions are often present to assist the member functions that are part of the public interface. For a circle class, we can divide the messages that a circle needs to handle into three sets. One set of messages directs a Circle object to return an attribute’s value. The second set of messages directs a Circle object to change the value of an attribute. The third set of messages directs a Circle object to perform a service. Messages that return the value of an attribute are called inspectors. For maximum flexibility, our definition of Circle includes inspectors for all of the data members. Inspectors will have the qualifier const after their parameter list. The qualifier indicates that the member function does not modify the object. An inspector name usually begins with Get. Messages that change or set an attribute are called mutators. Again, to make the Circle as flexible as possible, we will have mutators for the attributes that control the size and location of a Circle. There are class member functions that is neither an inspector nor a mutator. The getArea messages tells the circle to display its area. getArea() is an example of a facilitator. A facilitator performs a service. In addition to the class member functions that process messages, a class definition will also specify class constructors. A constructor is a member function that initializes an object of that class. A constructor has the same name as the class. The appropriate constructor is invoked automatically when a Circle object is defined. The following Circle constructor expects an initial value for each of the data members. Unlike other functions, a constructor does not have a return type. Circle(float cx, float cy, float rad); Besides listing the various members, the definition of a class also indicates which parts of a program can use the members. There are three kinds of access permissions: public, private, and protected. The general rule is A public member has unrestricted access. A private member can be accessed only by other members of the same class. A protected member does not have unrestricted access. It can be accessed only by other members of the same class or classes derived from this class. _ Open the file Circle.h to see the class definition for Circle. It should resemble the following: #ifndef _CIRCLE_H #define _CIRCLE_H class Circle { public: Circle(); Circle(float cx, float cy, float r); void Set_x(float cx); void Set_y(float cy); void Set_radius(float r); float Get_x(); float Get_y(); float Get_radius(); float Get_Area(); float Get_Perimeter(); bool Overlap(Circle mycircle); private: float x; float y; float radius; }; #endif Examine the code. Explain to your laboratory instructor the purpose of the preprocessor directives ifndef, define, and endif. (upload your answer to webct) If you are unsure about any aspects of the definition of class Circle, consult your laboratory instructor before proceeding _ Modify the program mycircle.cpp the program by defining a second Circle object yourcircle. Have yourcircle be different in size and position from the Circle object already defined in the program. Make a second call getArea() to also display the area of yourcircle. TO COMPILE your program you will need Circle.o – this is the object code for circle.cpp – for now you do NOT need to change anything in Circle.cpp (the implementation file) or in Circle.h (the declaration file). Compile as follows %g++ Circle.o mycircle.cpp –Wall –pedantic –ansi –o circles %./circles The member function overlap checks if 2 circles overlap – call this function using mycircle and yourcircle to see if they overlap. Recompile and test your program. Upload your modified program to WebCT. When defining a class, we give the public section first because the members in this section can be used by anyone. We give the protected section (if it exists) next. The private section comes last. Open the file circle.cpp. Observe that the class name and the scope resolution operator precede the member function name. Note that the scope resolution operator is a double colon (::). The member functions must be identified this way so that the compiler can tell that they belong to a class. The first function defined in the file is a Circle constructor. A constructor for a class is invoked when an object of that class is defined. To verify that the Circle constructor is invoked when a Circle object is defined, add an insertion statement in Circle’s constructor that inserts the following message to the stream cout: _ Circle constructor called. You may wonder why the constructor uses the mutators to set the values of the data members when the constructor could access the data members directly. The key idea is that all access to the data members should be through the same interface (i.e., member function). Thus a client user of a class changes the data members the same way a public member function of the class changes them. Using this strategy means that we can change the representation of a data member without having to modify every class member function that changes the value of the data member. Since all access to the data member is through the mutator, we need to change only the implementation of the mutator. This concept is probably fuzzy because you have not yet seen enough code to appreciate this very important design strategy. However, as you develop additional classes and modify existing classes, you will see that always using a mutator to change a data member value (whether the entity doing the changing is a client user of the class or a member function of the class) makes software more flexible, easier to maintain, and easier to modify. Using Classes Complex Numbers Open the File Complex.h. This contains the class interface for a class Complex which represents complex number. Open the program called complex_numbers.cpp, you will need to complete the implementation. You need to begin by declaring two Complex object, then accepts values for the real and imaginary parts from the user and sets up the numbers. Then the program should calculates (using the member functions) and output the sum of the two complex numbers and the difference between them (subtract the second number from the first one). To compile this program you will also need Complex.o and simply compile using %g++ Complex.o complex_numbers.cpp –o complex –Wall –ansi –pedantic %./complex Upload your modified program to WebCT. Banking Open the file BankAccount.h. This contains the class interface for a class BankAccount. It looks like this: //Program to demonstrates the class BankAccount. #ifndef _BANKACCOUNT_H #define _BANKACOUNT_H #include <iostream> using namespace std; //Class for a bank account: class BankAccount { public: BankAccount(); BankAccount(int dollars, int cents, double rate); //Postcondition: The account balance has been set to $dollars.cents; //The interest rate has been set to rate percent. BankAccount(int dollars, double rate); //Postcondition: The account balance has been set to $dollars.00. //The interest rate has been set to rate percent. void set(int dollars, int cents, double rate); //Postcondition:The account balance has been set to $dollars.cents; //The interest rate has been set to rate percent. void set(int dollars, double rate); //Postcondition: The account balance has been set to $dollars.00. //The interest rate has been set to rate percent. void update( ); //Postcondition: One year of simple interest has been //added to the account balance. double get_balance( ); //Returns the current account balance. double get_rate( ); //Returns the current account interest rate as a percent. void output(ostream& outs); //Precondition: If outs is a file output stream, then //outs has already been connected to a file. //Postcondition: Account balance and interest rate have //been written to the stream outs. private: double balance; double interest_rate; double fraction(double percent); //Converts a percent to a fraction. //For example, fraction(50.3) returns 0.503. }; #endif Open the program banking.cpp. There are several tasks for you to complete, using the member functions declared in the interface. Once complete compile and run the program using %g++ BankAccount.o banking.cpp –Wall –pedantic –ansi –o banking %./banking Next open BankAccount.cpp – examine each member function and ensure you know what is going on, if you have any questions discuss this with your lab instructor. Finishing up. There are some questions on WebCT you will need to answer in ADDITION to uploading your modified programs (just the .cpps) with some sample runs. The files you need to upload are the updated mycircle.cpp complex.cpp banking.cpp Thank your Lab Instructor!