What We Will Cover
Continuations
Homework Questions?
Questions from last class?
An instance variable is hidden (shadowed) in a function when
- The instance variable has the same name as the function.
- The instance variable has the same name as a local variable in the function.
- The instance variable has the same name as the class.
- The instance variable has the same name as the file.
^ top
10.1: Working With Multiple Objects
Objectives
At the end of the lesson the student will be able to:
- Code classes that use other objects
- Describe how to pass objects to methods
- Code arrays of objects
|
^ top
10.1.1: Objects as Instance Variables
- Object-oriented programs use one or more objects to perform their tasks
- Objects can be part of the data for other classes
private:
Product product;
Different applications can use the same classes to create objects
For Example
- Following example uses the
Product class
- Note how an object of the
Product class is stored as part of the ProductOrder class
- Also note how the
Product class is initialized
- When using another class, you do not need to know the inner workings of the other class
- You only need to know the publicly available functions
#include <iostream>
using namespace std;
#include "Product.cpp"
class ProductOrder {
public:
ProductOrder(string name, double price,
int prodQuantity);
Product getProduct() { return prod; }
double getQuantity() { return quantity; }
double getTotal();
void showData();
private:
Product prod;
int quantity;
};
ProductOrder::ProductOrder(string name, double price,
int prodQuantity): prod(name, price) {
quantity = prodQuantity;
}
double ProductOrder::getTotal() {
return quantity * prod.getPrice();
}
void ProductOrder::showData() {
cout << "Name: " << prod.getName()
<< "\nPrice: "
<< prod.getPrice()
<< "\nQuantity: " << quantity
<< "\nTotal Amount: "
<< getTotal() << "\n";
}
- Note that the
getTotal() function does not simply return a value
- Instead, it calculates the total by using data stored in the class
- Uses the
getPrice() function to extract data from the Product class
^ top
10.1.2: Another Example Application
productorderapp.cpp is another driver application
- Has a main function that starts an application
#include <iostream>
using namespace std;
#include "ProductOrder.cpp"
// For testing class Product
int main() {
char choice = 'Y';
string name;
double price;
int qty;
while ('Y' == choice || 'y' == choice) {
cout << "Enter a product name: ";
cin >> name;
cout << "Enter the price for a "
<< name << ": ";
cin >> price;
cout << "Enter a quantity of "
<< name << ": ";
cin >> qty;
ProductOrder po(name, price, qty);
cout << "\nYou entered:\n";
po.showData();
cout << "\nEnter another product order? ";
cin >> choice;
}
return 0;
}
- Note that we have two applications sharing the same class
Product
- This is an example of code reuse
^ top
10.1.3: Passing Objects to Functions
- Objects can be passed to functions as arguments
- Usually objects are passed by reference
bool Product::isEqual(Product& p) {
bool equal = false;
if (p.name == name && p.price == price ) {
equal = true;
}
return equal;
}
We can test this function:
Product milk("Milk", 3.95);
Product bread("Bread", 1.99);
cout << "Testing product equality: "
<< milk.isEqual(bread) << endl;
^ top
10.1.4: Accessing Private Variables
- One important note about
private variables
- Any object of a particular class can access the private variables of another object of that class
bool Product::isEqual(Product& p) {
bool equal = false;
if (p.name == name && p.price == price ) {
equal = true;
}
return equal;
}
Note that p.name accesses the private variable of the p object
^ top
10.1.5: Returning Objects from Functions
- Class types can be returned from functions
Product makeProduct(string name, double price) {
Product newProd(name, price);
return newProd;
}
In main we use the function:
Product myProd = makeProduct("Cheese", 3.45);
cout << myProd.getName() << endl;
Usually more efficient to return by reference
Will learn how when covering pointers
^ top
10.1.6: Arrays of Objects
- Any type can be made into an array, including class types
Product prods[10];
In the following, array elements are objects
You can call methods of the objects stored in each array element
#include <iostream>
#include "product.cpp"
using namespace std;
const int NUM_PRODS = 3;
int main(void) {
Product store[] = {
Product("Milk", 3.95),
Product("Bread", 1.09),
Product("Cheese", 4.59)
};
for (int num = 0; num < NUM_PRODS; num++) {
Product prod = store[num];
cout << prod.getName() << " @ ";
cout << store[num].getPrice();
cout << endl;
}
return 0;
}
Notes on the Code
- Code that accesses each individual array element
Product prod = store[num];
Code that calls a function on each array element
cout << store[num].getPrice();
^ top
10.1.7: Summary
- Object-oriented programs use one or more objects to perform their tasks
- Objects can be part of the data for other classes
private:
Product product;
Different applications can use the same classes to create objects
A class may use another class as a type for a member variable
Classes can be function parameters
- Objects of the class type are passed as arguments
- Usually passed by reference for efficiency
Objects can be returned from functions as well
^ top
Exercise 10.1
- Start a text file named exercise10.txt.
- Prepare the exercise header as described in the HowTo on submitting exercises
- Label this exercise: Exercise 10.1
- Submit all exercises for today's lesson in one file unless instructed otherwise
- Complete the following and record the answers to any questions in exercise10.txt.
Specifications
- Trace the
ProductOrderApp program by listing the object names and method calls in the order of their occurrence:
objName.methodName()
If an object is not instantiated, use the class name instead. For example, the first method few method calls are:
main()
po.ProductOrder()
- Record your sequential listing in your exercise10.txt file.
- In addition, record answers to the following questions:
Q1: In ProductOrder, what is the number of the line that instantiates the Product object?
Q2: In productorderapp.cpp, what is the number of the line that calls the constructor of ProductOrder?
Q3: How many objects are created when the following line of code is executed?
ProductOrder po(name, price, qty);
^ top
10.2: ADTs and Separate Compilation
Objectives
At the end of the lesson the student will be able to:
- Describe how to make a class an abstract data type
- Describe how to separate the interface from the implementation
- Use separate compilation
|
^ top
10.2.1: Designing Classes as ADTs
- Data types, such as an
int, has certain values that can be stored
- To be useful, you must be able to operate on these values
- How the operation occurs, we do not need to know to use them
- We want to keep these observation in mind when we design classes
- Ideally, we want to design our classes as abstract data types (ADTs)
- We keep details of the operation from the programmers using the class
- Our public interface (functions) are all another programmer needs to know to use our class
- Designing in this way provides for more modularity and easier changes
Ensuring Classes are ADT's
- The key to designing a good class is to separate the implementation from the interface
- How to use the class is presented to the programmer in detail
- How the class is implemented is hidden
- We start the process by making all member variables private members
- We make public the member functions a programmer needs to use the class
- We then fully specify, using block comments, how to use each public function
- In addition, we usually make helper functions private members
- Our next step is to hide the actual code from the public function declarations (prototypes)
- C++ allows you to place the interface and the implementation in separate files
- Each part can be compiled separately
- Known as separate compilation
- In C++, this provides the best way to ensure a class is an ADT
- However, the declarations of private members are still exposed
- For a simple program with one class and a driver, you end up with 3 files
^ top
10.2.2: Example of Separate Compilation
- Separate compilation starts by placing class declaration in a separate file
- For example, we can save our
Product class declaration in a separate file
#ifndef PRODUCT_H
#define PRODUCT_H
#include <string>
using namespace std;
class Product {
public:
// Constructors
Product();
Product(string newName, double newPrice);
// Instance functions
string getName() { return name; }
double getPrice() { return price; }
void setName(string newName);
void setPrice(double newPrice);
private:
// Instance variables
string name;
double price;
};
#endif
We name the file: product.h
The remained of the class definition stays in the product.cpp file
However, the product.cpp definitions still need the declarations
Thus, we include the product.h file in product.cpp
#include "product.h"
// no-arg constructor
Product::Product() {
name = "Unknown";
price = 0.0;
}
Product::Product(string newName, double newPrice) {
setName(newName);
setPrice(newPrice);
}
void Product::setName(string newName) {
if (newName.length() == 0) {
name = "Unknown";
} else {
name = newName;
}
}
void Product::setPrice(double newPrice) {
if (newPrice > 0.0) {
price = newPrice;
} else {
price = 0.0;
}
}
The main() function is the starting point of any application
Most applications use more than one object
Thus, you usually place main() in a separate file
We name the file depending on the application
In this case, productapp.cpp seems appropriate
#include <iostream>
using namespace std;
#include "product.h"
// For testing class Product
int main() {
char choice = 'Y';
string name;
double price;
while ('Y' == choice || 'y' == choice) {
cout << "Enter a product name: ";
cin >> name;
cout << "Enter the price for a "
<< name << ": ";
cin >> price;
Product prod(name, price);
cout << "You entered:"
<< "\n Name: " << prod.getName()
<< "\n Price: " << prod.getPrice()
<< endl;
cout << "Enter another product? ";
cin >> choice;
}
return 0;
}
To use the Product class, all we need to access is the class declaration
We gain access to the declaration by including the product.h file
Compiling
- Because we have multiple
.cpp files, compiling is a two-step process:
- Compile the all the
.cpp files into object files
- Link all the objects files together into an executable file
- We compile the class and the application into object files:
g++ -c product.cpp
g++ -c productapp.cpp
Then we link both files together into the application
g++ -o productapp productapp.o product.o
We then run the application in the usual way:
productapp
^ top
10.2.3: Instructions for Separate Compilation
- Separate the interface from the implementation
- Place the class declaration into a
classname.h file
- Place
#ifndef...#enddef around the definition in the classname.h file
- Please the class implementation into a
classname.cpp file
- Code a
#include "classname.h" directive in the classname.cpp file
- Usually placed after the core library includes
- Place the
main function in a separate file
- Place the application main function into an
appname.cpp file
- Code a
#include "classname.h" directive in the appname.cpp file
- Compile the class and the driver into object files
g++ -c classname.cpp
g++ -c appname.cpp
- Link both files together into the application
g++ -o appname appname.o classname.o
^ top
10.2.4: Makefiles
- It quickly becomes tedious to recompile code with multiple source files
- You can use a program named
make to automatically recompile your files
- However, you must create a file named
Makefile with instructions for the make program
For Example
# simple makefile
# define target dependencies and files
productapp: productapp.o product.o
g++ -o productapp productapp.o product.o
# define how each object file is to be built
productapp.o: productapp.cpp product.h
g++ -c productapp.cpp -W -Wall --pedantic
product.o: product.cpp product.h
g++ -c product.cpp -W -Wall --pedantic
# clean up
clean:
rm -f productapp.exe *.o
Note that the large blank areas before a command is a tab character
To use a Makefile, you type make at the command line
Most people write a Makefile by modifying an existing one
Important Information
- Lines starting with a hash mark (#) are comments and are ignored
- Rules define which files depend on others and take the form:
targetfile : sourcefiles
<tab>commands you normally type
Note the tab character, which is required prior to defining the commands
Further Information
^ top
10.2.5: Summary
- All classes should be coded as ADTs
- C++ allows you to place the interface and the implementation in separate files
- Separate compilation helps to ensure your classes are ADTs
- You can write a
Makefile for easy recompiling
^ top
Exercise 10.2
- Label this exercise: Exercise 10.2
- Submit all exercises for today's lesson in one file unless instructed otherwise
- Complete the following and record the answers to any questions in exercise10.txt.
Specifications
- Apply the separate compilation process to the
MyRectangle class shown below and compile the files.
Q1: What is the name of the file that contains the class declaration?
Q2: What is the name of the file that contains the main() function?
- Submit your updated
MyRectangle class files along with exercise10.txt.
#include <iostream>
using namespace std;
class MyRectangle {
public:
MyRectangle(double length, double width);
void showData();
private:
double length;
double width;
};
MyRectangle::MyRectangle(double newLength, double newWidth) {
length = newLength;
width = newWidth;
}
void MyRectangle::showData() {
cout << "length: " << length
<< "\nwidth: " << width
<< endl;
}
// For testing
int main() {
MyRectangle rec(3.0, 5.0);
rec.showData();
return 0;
}
^ top
10.3: Developing Classes
Objectives
At the end of the lesson the student will be able to:
- Describe the software development process
- Write function stubs
- Develop drivers for testing classes
|
^ top
10.3.1: About Software Development
- Roughly four main steps to developing software:
- Analysis: explore what is wanted and develop an initial plans
- Design: class design and algorithm development
- Implementation: coding and testing classes
- Deployment: final documentation and submitting the code
- Software development is an iterative process
- Often need to go back and repeat steps as you discover new information
- In this section, we discuss the design and implementation steps
Further Information
- Many different methodologies proposed for developing software
- Which one to use depends on your company and project
- Some of the most popular today:
^ top
10.3.2: Designing Classes
- The organization of classes used in a program is known as its architecture
- Classes are often organized into three main categories
- User interface classes
- Problem-domain classes
- Database classes
- This is known as a three-tier architecture
- Most of the work in this course is centers around the problem-domain classes
- User interfaces are usually graphical, which is beyond the scope of this course
- Database classes are beyond the scope of this course as well
Designing Problem-Domain Classes
- Identify the classes needed for the program
- Often spelled out in the specifications
- If not, then read the specification looking for nouns
- For example: customers, addresses, products, sales
- Nouns become the classes of the program
- Identify the functions needed for each class
- Often spelled out in the specifications
- If not, read the specifications and identify the actions that need to occur
- Usually identified by the verbs in the specification
- These actions become the functions for the class
- At his point, you are ready to implement the first iteration of your classes
^ top
10.3.3: First Step
- As a first step, write a class declaration with function and variable declarations
- For example:
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass();
int getValue() { return value; }
int setValue(int newValue) { value = newValue; }
bool isCorrect();
void performCalculation();
private:
int value;
};
int main(void) {
MyClass mc;
}
For get and set functions, just code a simple function definition in the class declaration
int getValue() { return value; }
int setValue(int newValue) { value = newValue; }
You can compile a class declaration to check for errors
$ g++ myclass.cpp -c
^ top
10.3.4: Initial Implementation
- When you first implement functions, it often makes sense to use stubs
- For the constructors, just assign default values
MyClass::MyClass() {
value = 0;
}
If a function does not have to return anything, you can code an empty function
void MyClass::performCalcuation() { }
If you want to see when a function is called, include a print statement
void MyClass::performCalcuation() {
cout << "Executing performCalcuation\n";
}
If the function needs to return a value, just return a known value
bool MyClass::isCorrect() {
return true;
}
You can return later and write the complete function at a later time
Keep stubs so simple that you are confident they perform correctly
If stubs get too complex, it is better to just write the function
You can compile the first implementation to check for errors
$ g++ myclass.cpp -c
Once the class is "stubbed out" you should develop a driver for testing
^ top
10.3.5: Drivers and Testing
- Driver programs allow testing of each function
- Once a function is tested, it can be used in the driver program to test other functions
- Drivers allow you to complete each function of your class one at a time
- You can start with drivers in the same class
- Later move the driver into a separate file so you can use the class in your main application
For Example
- Following is an example of a main function to drive the tests for the stubs shown above
- You add to the tests as you develop your class
- If you make changes, you can rerun the tests to verify no new errors were introduced
int main() {
cout << "Calling the constructor\n";
MyClass mc;
cout << "Calling the get and set functions\n";
mc.setValue(2);
if (mc.getValue() != 2) cout << "Error\n";
cout << "Calling performCalcuation\n";
mc.performCalculation();
cout << "Verifying the results\n";
if (!mc.isCorrect()) cout << "Error\n";
return 0;
}
^ top
10.3.6: Testing and Iterating
- We now have a complete first iteration
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass();
int getValue() { return value; }
int setValue(int newValue) { value = newValue; }
bool isCorrect();
void performCalculation();
private:
int value;
};
MyClass::MyClass() {
value = 0;
}
bool MyClass::isCorrect() {
return true;
}
void MyClass::performCalculation() {
cout << "Executing performCalcuation\n";
}
int main(void) {
cout << "Calling the constructor\n";
MyClass mc;
cout << "Calling the get and set functions\n";
mc.setValue(2);
if (mc.getValue() != 2) cout << "Error\n";
cout << "Calling performCalcuation\n";
mc.performCalculation();
cout << "Verifying the results\n";
if (!mc.isCorrect()) cout << "Error\n";
return 0;
}
Now we can compile and link the code to check for errors
$ g++ myclass.cpp
Then run the tests
$ ./a
Calling the constructor
Calling the get and set functions
Calling performCalcuation
Executing performCalcuation
Verifying the results
Subsequent Iterations
- Now implement each function, or part of a function, and retest
- Make whatever changes are needed as your design evolves
- Modify your tests to reflect your design
- When you start using multiple classes, move to separate compilation
^ top
10.3.7: Summary
- There are roughly four main steps to developing software:
- Analysis: explore what is wanted and develop an initial plans
- Design: class design and algorithm development
- Implementation: coding and testing classes
- Deployment: final documentation and submitting the code
- Software development is an iterative process
- You often need to go back and repeat steps as you discover new information
- Once you identify your classes and functions, you are ready to code your first iteration
- Write a class declaration with function and variable declarations
- When you first implement functions, it often makes sense to use stubs
- You can return later and write the complete function at a later time
- Once the class is "stubbed out" you should develop a driver for testing
- Keep the tests up to date as you change the program
^ top
Exercise 10.3
- Label this exercise: Exercise 10.3
- Submit all exercises for today's lesson in one file unless instructed otherwise
- Complete the following and record the answers to any questions in exercise10.txt.
Specifications
- Spend 10 minutes reviewing the specification for A10: Storing Objects.
We will review the specification and design the classes during this exercise.
- Record a description of the design in your
exercise10.txt file.
- After our design discussion, and as time permits, develop a first implementation of your new classes.
^ top
Wrap Up
^ top
Home
| WebCT
| Announcements
| Day Schedule
| Eve Schedule
Course info
| Help
| FAQ's
| HowTo's
| Links
Last Updated: November 16 2004 @19:10:00
|