What We Will Cover
Continuations
Homework Questions?
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
9.1: 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
9.1.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 observations 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
9.1.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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#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 function definitions still need the declarations
- Thus, we include the
product.h file in product.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#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
9.1.3: Instructions for Separate Compilation
- Separate the interface from the implementation
- Place the class declaration into a
classname.h file
- Place
#ifndef...#endif around the definition in the classname.h file
#ifndef PRODUCT_H
#define PRODUCT_H
// code goes here
#endif
- Place the class implementation into a file named
classname.cpp
- 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 a file named
appname.cpp
- Code a
#include "classname.h" directive in the appname.cpp file
#include "product.h"
- 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
9.1.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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 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
9.1.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 9.1
In this exercise we separate files for compilation and create a Makefile to make the compilation easier.
Specifications
- Start a text file named exercise9.txt.
- Prepare the exercise header as described in the HowTo on submitting exercises
- Label this exercise: Exercise 9.1
- Apply the separate compilation process to the
MyRectangle class shown below and compile the files.
- Write a
Makefile that automatically compiles the files when you type make at the command line.
- Write answers to the following questions in
exercise9.txt.
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 and Makefile along with exercise9.txt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#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
9.2: 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
9.2.1: About Multiple Objects
- In OOP, only the simplest programs use a single object
- More common is to have several objects work together in your program
- Each object performs one specialized task
- In this section we look at how use multiple objects in a program
Product Class
- Before we start, lets recall our
Product class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
#include <iostream>
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);
void show();
private:
// Instance variables
string name;
double price;
};
// 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;
}
}
void Product::show() {
cout << name << " has a price of $"
<< price << endl;
}
|
^ top
9.2.2: Objects as Instance Variables
- Different applications can use existing classes to create objects
- For example, the following class uses a
Product object in the class definition
private:
Product product;
Note how the Product class is initialized in the constructor
ProductOrder::ProductOrder(string name, double price,
int prodQuantity): prod(name, price) {
What else do you notice that is new about the following code?
Example Class with an Object Instance Variable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
#include <iostream>
using namespace std;
#include "Product.cpp"
class ProductOrder {
public:
ProductOrder();
ProductOrder(string name, double price,
int prodQuantity);
Product getProduct() { return prod; }
double getQuantity() { return quantity; }
void setProduct(Product& newProduct);
void setQuantity(int newQuantity);
double getTotal();
void showData();
private:
Product prod;
int quantity;
};
ProductOrder::ProductOrder() {
quantity = 0;
}
ProductOrder::ProductOrder(string name, double price,
int prodQuantity): prod(name, price) {
quantity = prodQuantity;
}
void ProductOrder::setProduct(Product& newProduct) {
prod = newProduct;
}
void ProductOrder::setQuantity(int newQuantity) {
quantity = newQuantity;
}
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 variables of both the
prod object and the ProductOrder class
^ top
9.2.3: Storing Objects Within Objects
- When an object is created as part of another object, room for both objects are allocated in memory
- This is shown in the following diagram

- Both objects exist, but one object is nested within another
^ top
9.2.4: Another Example Application
productorderapp.cpp is another driver application
- It has a
main() function so we can compile and link the application
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#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
productapp.cpp: from lesson 8.3.3
productorderapp.cpp: shown above
- This is an example of code reuse
^ top
9.2.5: Passing Objects to Functions
- Objects can be passed to functions as arguments
- Usually we pass objects by reference
- We saw an example of passing objects to functions in the
ProductOrder class:
void ProductOrder::setProduct(Product& newProduct) {
prod = newProduct;
}
We can test this by changing ProductOrderApp:
//ProductOrder po(name, price, qty);
Product prod(name, price);
ProductOrder po;
po.setProduct(prod);
po.setQuantity(qty);
^ top
9.2.6: Returning Objects from Functions
- Class types can be returned from functions
- We saw this as well in
ProductOrder:
Product getProduct() { return prod; }
We can make a more interesting example function
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
We will learn how when covering pointers
^ top
9.2.7: Summary
- Object-oriented programs often use one or more objects to perform their tasks
- In addition, objects can be part of the data for other classes
private:
Product product;
Different applications can use the same classes to create objects
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
Check Yourself
- How do you code an object as an instance variable of another class?
- How do you code a function with an object parameter?
- How do you code a function to return an object?
^ top
Exercise 9.2
In this exercise we explore the flow of control when one class is part of another class.
Specifications
- Label this exercise: Exercise 9.2
- Copy the following 3 files into TextPad and set them up so you can view the line numbers
- Trace the
ProductOrderApp program by listing the file names and range of line numbers in the order of their occurrence:
ProductOrderApp: 7-22
ProductOrder: 26-27
etc.
Note that you do only need to list a range of line numbers that start and end with each function call. Everyone should know how to trace if-else statements and loops by now, so you do not need to list individual line numbers.
- Record your program-flow listing in your exercise9.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
9.3: Developing Object-Oriented Programs
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
9.3.1: About Software Development
- There are roughly four main steps to developing software commercially:
- Analysis: explore what is wanted and develop initial plans
- Design: Develop algorithms and design classes
- Implementation: Code and test classes
- Deployment: Write the documentation and distribute the code
- Software development is an iterative process
- You 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
- People have proposed many different methodologies for developing software
- Which one to use depends on your company and project
- Some of the most popular processes used today include:
^ top
9.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 require a database, which is beyond the scope of this course as well
Designing Problem-Domain Classes
- First you identify the classes needed for the program
- A good design concept is that objects are things with responsibilities
- Sometimes these things are 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
- You may want to aggregate individual objects into a single class
- For instance player A and player B should be defined as a single Player class
- Next you decide on the data that each class needs to store
- Data can be simple things like numbers and strings
- Data can be other objects as well
- Finally, you determine the functions needed for each class
- Read the specifications and identify the actions that need to occur
- Then you develop algorithms to solve the problems
- The algorithms identify the functions needed for the objects to interact
- Once you have a design, you are ready to write the first iteration of your classes
^ top
9.3.3: Design Example
- As an example of designing with objects, lets design a simplified version of a game called "two up"
- A "two up" game has two coins -- hence the "two" part of the name
- Both coins are tossed at the same time in a circular area
- Tosses are done using a flat piece of wood called a "kip"
- If both coins show heads, then you win
- If both coins show tails then you lose
- If you get one of each, then you neither win nor lose
- The person who controls the game is called a "Boss"
- Supplies the coins for the game
- Designates who tosses the coins
- Collects and pays out the bets
- More information: Two-up
Program Design
- Now that we understand the game, we need to design our program objects
- What are the objects in the game?
- We name our classes after each type of object
- We want a simple single-user game so we limit our design to only a few basic classes
- If we added more features, we probably would add more classes
- Once we have decided on our classes, we decide what data each class stores
- Once we identify the data, we determine what functions we need for each class
- The functions should operate on the data such that we can play the game
^ top
9.3.4: Implementing the Design: First Step
- As a first step, we write a class declaration for each object
- For instance, for a class
Coin we might have a class declaration like:
class Coin {
public:
Coin();
void toss();
char getValue() { return value; }
private:
char value;
};
After we declare the class we need to write function definitions
When you first define functions, it often makes sense to use stubs
When you write a stub, it is a good idea to print messages to help you follow the flow of the code
For a no-parameter constructor you just code a message and assign default values
For instance:
Coin::Coin() {
cout << "Called Coin constructor\n";
value = 'H';
}
If a function does not have to return anything, you can just code a message like:
void Coin::toss() {
cout << "Called toss()\n";
}
If a function needs to return a value, you just code a default return value
When "stubbing out" the code, it often makes sense to put all the classes in one file
Later, when the classes are more developed, you can split them into multiple files
In addition to the classes, we need a main() function to compile and test the code
After we stub out each function definition, we compile our code and fix any syntax errors
Our stubbed-out code might look like the following
Example First Pass Code for Twoup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
class Coin {
public:
Coin();
void toss();
char getValue() { return value; }
private:
char value;
};
Coin::Coin() {
cout << "Called Coin constructor\n";
value = 'H';
}
void Coin::toss() {
cout << "Called toss()\n";
}
class Boss {
public:
Boss();
void kip();
char getValue1() { return coin1.getValue(); }
char getValue2() { return coin2.getValue(); }
private:
Coin coin1;
Coin coin2;
};
Boss::Boss() {
cout << "Called Boss constructor\n";
}
void Boss::kip() {
cout << "Called kip()\n";
}
#include <iostream>
using namespace std;
int main() {
}
|
^ top
9.3.5: Drivers and Testing
- After the first implementation is complete we need to test the class functions
- To test, we use the
main() function as a test "driver"
- Our test is to instantiate each of the object types and call every function
- Thus we might write a
main() function like:
int main() {
Coin penny;
penny.toss();
cout << penny.getValue() << endl;
Boss twoup;
twoup.kip();
cout << twoup.getValue1() << endl;
cout << twoup.getValue2() << endl;
}
Then we compile and run the code, correcting any errors we find
^ top
9.3.6: Iterating and Testing
- After our driver is in place we develop some of the functions
- For instance, we might develop the
toss() function
- We can use the
rand() function to generate a random toss
- For example:
void Coin::toss() {
cout << "Called toss()\n";
if (rand() % 2 == 0) {
value = 'H';
} else {
value = 'T';
}
cout << "Set value=" << value << endl;
}
Every time we add to or change a function, we make sure our driver tests the new features of the function
Subsequent Iterations
- We now implement each function, or part of a function, and retest
- Once the functions work well, we add a main loop to the
main() function
- For example:
char answer = 'Y';
while ('Y' == answer || 'y' == answer) {
twoup.kip();
cout << "You tossed: " << twoup.getValue1()
<< " and " << twoup.getValue2() << endl;
cout << "\nTry again? (Y/y to continue) ";
cin >> answer;
}
cout << "\nGame over\n";
We continue to add features to our game until we meet specifications fully
Along the way, we divide the code into separate files as needed
Once everything is working satisfactorily, we remove all the print statements and document the code
When the code is fully documented, we retest one more time and deploy the program
^ top
9.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
- When finished, complete the documentation, retest and deploy the code
^ top
Exercise 9.3
In this exercise we prepare an initial design for the next assignment.
Specifications
- Spend 10 minutes reviewing the specification for A9: Paradise Roller.
We will review the specification and design the classes during this exercise.
- Record a description of the design in your
exercise9.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 03 2005 @15:22:00
|