11: Multiple Classes

What We Will Cover


Continuations

Questions from last class?

Homework Questions?

11.1: Working with Classes and Objects

Objectives

At the end of the lesson the student will be able to:

  • Separate classes from applications
  • Pass objects to functions
  • Return objects from functions

11.1.1: Review of Classes and Objects

  • Previously we discussed how to code classes to create objects
  • Classes are grouping of related variables and functions in one container
  • We code classes to "hide" data so we are free to make changes independent of other parts of a program
  • Data (variables) are hidden using the private access modifier:
    private:
        string name;
        double price;
    
  • To access private data, we code public functions like:
    public:
        string getName() const;
        double getPrice() const;
        void setName(string newName);
        void setPrice(double newPrice);
        void print() const;
    
  • These public functions are the interface to our class
  • To create objects from the class, we construct an object like:
    Product milk;
  • When the object is created, memory is allocated for the class variables
  • However, the memory is uninitialized
  • We can use the set functions to assign the memory values:
    milk.setName("Low fat milk");
    milk.setPrice(3.95);
    
  • However, this is cumbersome and provides no guarantee that the programmer using our class will completely initialize the object data
  • The solution is to code constructor functions
  • A constructor is always called whenever an object is created from a class
  • A default constructor must set the member variables to default values:
    Product::Product() {
        name = "none";
        price = 0.0;
    }
    
  • Even though we should always code a default constructor, it is convenient to code other constructors like:
    Product::Product(string newName, double newPrice) {
        setName(newName);
        setPrice(newPrice);
    }
    
  • This lets us construct an object and initialize data members at the same time:
    Product milk("Low fat milk", 3.95);
    
  • When we are done, we have a modular, reusable grouping of variables and functions
  • In this section we look at some ways we can make use of these modules

11.1.2: Separating Classes from the main() Function

  • When you work with classes and objects, you usually specify a class in one file and write code to use the class in another file
  • This creates a more modular set of classes and allows you to reuse classes in other programs
  • Recall the #include directive:
    #include <iostream>
  • It turns out we can include our own files into other program files
  • Syntax:
    #include "myfile.cpp"
  • For example:
    #include "product.cpp"

Programs and the main() Function

  • In C++, each program can have only one main() function
  • Usually, the main() function is coded in a file separate from all the other classes and functions
  • Then any functions or classes needed by main are added using the #include preprocessor directive
  • To demonstrate, we remove the main() function from our Product class as shown below
  • Note that without a main() function we can no longer compile the code
  • Trying to compile causes a linker error because every program must have a single main() function:
    ...undefined reference to `_WinMain@16'
    collect2: ld returned 1 exit status
    
  • We will show how to compile and link this code in the next section

Class Product Without a main() Function

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
52
#include <iostream>
using namespace std;

class Product {
public:
    Product();
    Product(string newName, double newPrice);
    string getName() const;
    double getPrice() const;
    void setName(string newName);
    void setPrice(double newPrice);
    void print() const;
private:
    string name;
    double price;
};

Product::Product() {
    name = "none";
    price = 0.0;
}

Product::Product(string newName, double newPrice) {
    setName(newName);
    setPrice(newPrice);
}

string Product::getName() const {
    return name;
}

double Product::getPrice() const {
    return price;
}

void Product::setName(string newName) {
    name = newName;
}

void Product::setPrice(double newPrice) {
    if (newPrice > 0.0) {
        price = newPrice;
    } else {
        cout << "Error: negative price!\n"
             << "Setting price to 0.\n";
        price = 0.0;
    }
}

void Product::print() const {
    cout <<  name << " @ " << price << endl;
}

11.1.3: Including a Class in an Application

  • Now let us look at how to create an application by including the separate product.cpp file
  • The following program is called productapp.cpp
  • It consists of a main() function that includes the Product class using the #include directive:
    #include "product.cpp"
  • Notice that standard library includes are placed before our custom includes

Program productapp.cpp Including a File

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
#include <iostream>
#include <vector>
using namespace std;

#include "product.cpp"

// For testing class Product
int main() {
    vector<Product> store;
    char repeat = 'Y';
    while ('Y' == repeat || 'y' == repeat) {
        cout << "Enter product data:\n";
        string name;
        cout << "Product name: ";
        cin >> name;
        double price;
        cout << "Price for a " << name << ": ";
        cin >> price;

        Product temp(name, price);
        store.push_back(temp);
        cout << "You entered:"
             << "\n   Name: " << temp.getName()
             << "\n   Price: " << temp.getPrice()
             << endl;

        cout << "Enter another product? (y/n): ";
        cin >> repeat;
    }

    cout << "\nAll your products:\n";
    for (unsigned i = 0; i < store.size(); i++) {
        store[i].print();
    }

    return 0;
}

11.1.4: Returning Objects from Functions

  • Objects can be returned from functions
  • We can make a simple non-member function to demonstrate this technique:
    Product makeProduct() {
        string name;
        cout << "Enter a product name: ";
        cin >> name;
        double price;
        cout << "Enter the price for a "
             << name << ": ";
        cin >> price;
        Product newProd(name, price);
        return newProd;
    }
    
  • We revise our productapp.cpp file to add this function as shown below

Revised productapp.cpp Returning an Object

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
#include <iostream>
#include <vector>
using namespace std;

#include "product.cpp"

// Function that returns an object
Product makeProduct();

// For testing class Product
int main() {
    vector<Product> store;
    char repeat = 'Y';
    while ('Y' == repeat || 'y' == repeat) {
        cout << "Enter product data:\n";
        Product temp = makeProduct();
        store.push_back(temp);
        cout << "You entered:"
             << "\n   Name: " << temp.getName()
             << "\n   Price: " << temp.getPrice()
             << endl;

        cout << "Enter another product? (y/n): ";
        cin >> repeat;
    }

    cout << "\nAll your products:\n";
    for (unsigned i = 0; i < store.size(); i++) {
        store[i].print();
    }

    return 0;
}

Product makeProduct() {
    string name;
    cout << "Product name: ";
    cin >> name;
    double price;
    cout << "Price for a " << name << ": ";
    cin >> price;
    Product newProd(name, price);
    return newProd;
}

11.1.5: Passing Objects to Functions

  • Class types can be function parameters and we can pass objects to functions
  • We can pass objects by value or by reference
  • However, usually we pass objects by reference because it requires less work for the computer
  • As an example, let us write a function to compare the price of two products
  • One way we can write the function is as a non-member function
  • For example:
    bool isHigherPrice(Product& prod1, Product& prod2) {
        if (prod1.getPrice() > prod2.getPrice()) {
            return true;
        }
        return false;
    }
    
  • To call the function we use two explicit parameters like:
    if (isHigherPrice(prod1, prod2)) {
        cout << prod1.getName() << " costs more\n";
    } else {
        cout << prod2.getName() << " costs more\n";
    }
    
  • We revise our productapp.cpp file to add this function as shown below

Revised productapp.cpp with Class Parameters

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
#include <iostream>
using namespace std;

#include "product.cpp"

// Function with Product parameters
bool isHigherPrice(Product& prod1, Product& prod2);

// Function that returns an object
Product makeProduct();

// For testing class Product
int main() {
    cout << "Enter the first product:\n";
    Product prod1 = makeProduct();
    cout << "Enter the second product:\n";
    Product prod2 = makeProduct();
    if (isHigherPrice(prod1, prod2)) {
        cout << prod1.getName() << " costs more\n";
    } else {
        cout << prod2.getName() << " costs more\n";
    }

    return 0;
}

bool isHigherPrice(Product& prod1, Product& prod2) {
    if (prod1.getPrice() > prod2.getPrice()) {
        return true;
    }
    return false;
}

Product makeProduct() {
    string name;
    cout << "Product name: ";
    cin >> name;
    double price;
    cout << "Price for a "
         << name << ": ";
    cin >> price;
    Product newProd(name, price);
    return newProd;
}

11.1.6: Comparing Member Functions with Non-member Functions

  • We looked at comparing two Product objects using a non-member function:
    bool isHigherPrice(Product& prod1, Product& prod2) {
        if (prod1.getPrice() > prod2.getPrice()) {
            return true;
        }
        return false;
    }
    
  • Another way is to write the comparison as a member function:
    bool Product::isHigherPrice(Product& prod2) const {
        if (getPrice() > prod2.getPrice()) {
            return true;
        }
        return false;
    }
    
  • Note the difference in the parameter lists
  • To call the non-member function we use two explicit parameters:
    if (isHigherPrice(prod1, prod2)) {
        cout << prod1.getName() << " costs more\n";
    } else {
        cout << prod2.getName() << " costs more\n";
    }
    
  • To call the member function we use dot notation and one explicit parameter:
    if (prod1.isHigherPrice(prod2)) {
        cout << prod1.getName() << " costs more\n";
    } else {
        cout << prod2.getName() << " costs more\n";
    }
    
  • By using dot notation, the object name supplies an implicit parameter to the function
  • The implicit parameter identifies which object data to access for comparison against the object specified by the parameter
  • Note that we could have written the member function as:
    bool Product::isHigherPrice(Product& prod2) const {
        if (price > prod2.price) {
            return true;
        }
        return false;
    }
    
  • We do not need to use function calls because member functions can access private member variables directly

When to Write Member and NonMember Functions

  • Which solution is better: member or nonmember functions?
  • It depends on the ownership of the class
  • If you own the class, you should implement useful operations as member functions
  • If you are using a class supplied by someone else, you should write a nonmember function rather than changing the class
  • The author of the class may improve it and give you a new version
  • It would be a nuisance to have to add your modifications every time you received a new version of the class

Exercise 11.1

In this exercise we look at how to separate the class code from the main() function.

Specifications

  1. Copy the following program into a text editor, save it as rectangleclass.cpp, and then compile and run the starter program to make sure you copied it correctly.
    #include <iostream>
    using namespace std;
    
    class Rectangle {
    public:
        Rectangle();
        Rectangle(double newLength, double newWidth);
        void print();
    private:
        double length;
        double width;
    };
    
    Rectangle::Rectangle() {
        length = 0;
        width = 0;
    }
    
    Rectangle::Rectangle(double newLength, double newWidth) {
        length = newLength;
        width = newWidth;
    }
    
    void Rectangle::print() {
        cout << length << " long x " << width << " wide\n";
    }
    
    // For testing
    int main() {
        Rectangle rec;
        Rectangle rec3x5(3.0, 5.0);
        cout << "Printing rec: ";
        rec.print();
        cout << "Printing rec3x5: ";
        rec3x5.print();
    
        return 0;
    }
    
  2. Start a new file named rectanglemain.cpp and move the main() function to this file, deleting it from my rectangleclass.cpp. Try compiling rectanglemain.cpp and notice that it will NOT compile at this time.

    For more information see section 11.1.2: Separating Classes from the main() Function.

  3. Now we need to include the Rectangle class in the main application by adding the following code at the top of the file:
    #include "rectangleclass.cpp"
    

    For more information see section 11.1.3: Including a Class in an Application.

  4. Add a member function to the Rectangle class using the following prototype:
    bool isBiggerThan(Rectangle& rec2);
    

    The function returns true if the object calling the function has a larger area than the object passed to the function. For more information on member functions see section 11.1.6: Comparing Member Functions with Non-member Functions.

  5. To test the new member function, add the following code to main() after constructing the objects but before the return statement:
    if (rec.isBiggerThan(rec3x5)) {
        cout << "rec is bigger\n";
    } else {
        cout << "rec3x5 is bigger\n";
    }
    
  6. Compile and run your program and verify the screen output looks like:
    Printing rec: 0 long x 0 wide
    Printing rec3x5: 3 long x 5 wide
    rec3x5 is bigger
    

    If there are any problems, ask a class mate or the instructor for help.

  7. Submit both of your program source code files to Blackboard as part of your project.

Check Yourself

As time permits, be prepared to answer these questions. You can find more information by following the links after the question.

  1. Why should you code classes in files separate from a main() function? (11.1.2)
  2. How do you add code from a separate file into the file that has a main() function? (11.1.3)
  3. How do you code a function to return an object? (11.1.4)
  4. How do you code a function with an object parameter? (11.1.5)
  5. What is the difference between a member and non-member function? (11.1.6)
  6. When should you write member functions and when should you write nonmember functions? (11.1.6)

11.1.7: Summary

  • In this sections we looked at some techniques for working with an object
  • We started by placing the Product class definition into a file separate from the main() function
  • The reason was to make the class more modular and allow it to be used in other applications
  • Then we looked at specifying a class type as a return type of a function
  • In addition, we looked at specifying a function with an object parameter
  • There are two approaches to functions with object parameters: member and non-member functions
  • We use member functions if we own the class
  • Otherwise we use non-member functions

11.2: Working with Multiple Objects

Objectives

At the end of the lesson the student will be able to:

  • Code classes that use other classes
  • Code arrays of objects

11.2.1: About Multiple Objects

  • In object-oriented programming, only the simplest programs use a single object
  • More commonly, we have several objects working together in your program
  • Each object performs one specialized task
  • For instance, one object may contain the data for a product
  • Another object may store a list of products and provide functions to manage the list
  • In this section we look at how use multiple objects in a program
  • Before we start, lets recall our Product class

Class Product Without a main() Function

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
52
#include <iostream>
using namespace std;

class Product {
public:
    Product();
    Product(string newName, double newPrice);
    string getName() const;
    double getPrice() const;
    void setName(string newName);
    void setPrice(double newPrice);
    void print() const;
private:
    string name;
    double price;
};

Product::Product() {
    name = "none";
    price = 0.0;
}

Product::Product(string newName, double newPrice) {
    setName(newName);
    setPrice(newPrice);
}

string Product::getName() const {
    return name;
}

double Product::getPrice() const {
    return price;
}

void Product::setName(string newName) {
    name = newName;
}

void Product::setPrice(double newPrice) {
    if (newPrice > 0.0) {
        price = newPrice;
    } else {
        cout << "Error: negative price!\n"
             << "Setting price to 0.\n";
        price = 0.0;
    }
}

void Product::print() const {
    cout <<  name << " @ " << price << endl;
}

11.2.2: Objects as Member Variables

  • Different applications can reuse existing classes to store data
  • For example, we can use a Product object in the class definition:
    private:
        Product prod;
    
  • Note how the Product class is initialized in the constructor:
    prod = Product(name, price);
    
  • We have to use the full definition syntax rather than the shortcut syntax
  • The reason is that defining a Product variable allocates the memory for a Product object
    Product prod;
  • The Product variables are initialized using the default constructor
  • If we want to assign a different object to that space, we must construct the object and then copy it using the assignment operator (=)
    prod = Product(name, price);
    
  • 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:

    nested objects

  • Both objects exist, but one object is nested within another
  • The following class has a Product object as a member variable
  • What else do you notice that is new about the following code?

Example Class with an Object Member 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
51
52
53
54
55
56
57
58
#include <iostream>
using namespace std;

#include "product.cpp"

class ProductOrder {
public:
    ProductOrder();
    ProductOrder(string name, double price, int qty);
    Product getProduct();
    double getQuantity();
    void setProduct(Product& newProduct);
    void setQuantity(int newQuantity);
    double getTotal();
    void print();
private:
    Product prod;
    int quantity;
};

ProductOrder::ProductOrder() {
    quantity = 0;
}

ProductOrder::ProductOrder(string name, double price,
        int qty) {
    prod = Product(name, price);
    quantity = qty;
}

Product ProductOrder::getProduct() {
    return prod;
}

double ProductOrder::getQuantity() {
    return quantity;
}

void ProductOrder::setProduct(Product& newProduct) {
    prod = newProduct;
}

void ProductOrder::setQuantity(int newQuantity) {
    quantity = newQuantity;
}

double ProductOrder::getTotal() {
    return quantity * prod.getPrice();
}

void ProductOrder::print() {
    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

11.2.3: Another Example Application

  • Let us consider another example application: productorderapp.cpp
  • 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
32
33
34
35
36
37
38
39
#include <iostream>
#include <vector>
using namespace std;

#include "productorder.cpp"

// For testing class ProductOrder
int main() {
    vector<ProductOrder> orders;
    string name;
    double price;
    int qty;
    char repeat = 'Y';
    while ('Y' == repeat || 'y' == repeat) {
        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.print();
        orders.push_back(po);

        cout << "\nEnter another product order? ";
        cin >> repeat;
    }

    cout << "\nAll your orders:\n";
    for (unsigned i = 0; i < orders.size(); i++) {
        orders[i].print();
    }

    return 0;
}
  • Note that we now have two applications sharing the same class: Product
    • productapp.cpp: from lesson 11.1.3
    • productorderapp.cpp: shown above
  • This is an example of code reuse

11.2.4: Vectors in Container Classes

  • A common object oriented design pattern is the container class
  • A container class is a class that is capable of storing other objects
  • We did this previously with the ProductOrder class:
    private:
        Product prod;
    
  • Another way to store objects in a container class is to use a vector
  • The following class Store contains a vector of Product objects:
    vector<Product> list;
  • To work with the objects in the list, you write member functions such as addProduct() or listProducts() which are shown below
  • You add products to the container using a function like addProduct():
    Store store;
    store.addProduct();
    
  • To list products in the container, you call a function like listProducts():
    store.listProducts();
    
  • You can view an example application for the Store class in the listing below

Class Store with a Vector of Product Objects

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
#include <iostream>
#include <vector>
using namespace std;

#include "product.cpp"

class Store {
public:
    Store() { }
    void addProduct();
    void listProducts() const;
private:
    vector<Product> list;
    // can add other member variables too
};

void Store::addProduct() {
    cout << "Enter the name of the product: ";
    cin >> ws; // remove leading whitespace
    string name;
    getline(cin, name);
    cout << "Enter the price for a " << name << ": ";
    double price;
    cin >> price;
    Product temp(name, price);
    list.push_back(temp);
}

void Store::listProducts() const {
    for (unsigned num = 0; num < list.size(); num++) {
        Product temp = list[num];
        temp.print();
    }
}

Example Application for the Store 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
#include <iostream>
using namespace std;

// Custom includes after standard libraries
#include "store.cpp"

// Application for class Store
int main() {
    Store store;
    cout << "Welcome to the Virtual Store!\n";

    int choice = 1;
    while (choice != 0) {
        cout << "\n0. Exit program\n"
             << "1. Report inventory\n"
             << "2. Add a new product\n"
             << "Choice (0-2): ";
        cin >> choice;
        cin.ignore(1000, '\n');
        if (choice == 1) {
            cout << "\nAvailable products:\n";
            store.listProducts();
        } else if (choice == 2) {
            store.addProduct();
        } else if (choice != 0) {
            cout << "\nInvalid choice!\n";
        }
    }
    cout << "\nGoodbye!\n";

    return 0;
}

Why Container Classes?

  • The reason for putting a vector, or array, into a container class is the same reason you put any variable into a class: encapsulation and data hiding
  • The encapsulation puts everything into one package, which makes using the vector more convenient
  • You can just call a function for an answer or computation without having to worry about details
  • The data hiding provides two benefits:
    1. You can be sure that a programmer using your class will not misuse the vector in some way
    2. You can change your mind about the vector, or even decide later to use something besides a vector, such as an array or a more advanced data type. If you make a change, it will not affect any other part of a program using the container class.

Exercise 11.2

In this exercise we explore the flow of control when one class is part of another class.

Specifications

  1. Copy the following 3 files into TextPad, or another text editor, and set them up so you can view the line numbers
  2. Create a file named trace.txt
  3. In the trace.txt file, list the file name and line number of the application in the order the lines are processed like this:
    ProductOrderApp: 8, 9, 10 ... more tracing here
    ProductOrder: 25 & 26, 27 ... more tracing here
    ... more files and tracing here
    

    Do not bother to list lines containing only a closing curly brace (}) of a function definition.

  4. Assume that the user enters one product only.
  5. Submit the trace.txt file to Blackboard as part of your project.

Check Yourself

As time permits, be prepared to answer these questions. You can find more information by following the links after the question.

  1. Why do object-oriented programs usually have multiple classes? (11.2.1)
  2. True or false? A class can define another class type as a member variable. (11.2.2)
  3. To include the class Foo as a member variable of class Bar, what code do you write? (11.2.2)
  4. To initialize the Foo object in the constructor of a Bar class, what code do you write? (11.2.2)
  5. What is a container class? (11.2.4)

11.2.5: Summary

  • Object-oriented programs usually use more than one class
  • One way is for class types to be part of the data for other classes:
    class ProductOrder {
    // ... more code here
    private:
        Product product;
        int quantity;
    };
    
  • When an object is constructed as part of another object, room for both objects are allocated in memory

    nested objects

  • Since we write classes to be modular, different applications can use the same classes
  • A common object oriented design pattern is the container class
  • A container class is a class that is capable of storing other objects
  • You can store objects on a vector like:
    private:
        vector<Product> list;
    
  • To work with the objects in the list, you write member functions such as addProduct() or listProducts()
  • Then an application constructs an object of the container class and calls the member functions:
    Store store;
    store.addProduct();
    

Wrap Up

Due Next:
A10-Storing Information (5/7/09)
Sampler Project (5/28/09)
  • When class is over, please shut down your computer
  • You may complete unfinished exercises at the end of the class or at any time before the next class.
Home | Blackboard | Announcements | Day Schedule | Eve Schedule
Course info | Help | FAQ's | HowTo's | Links
Last Updated: May 09 2009 @11:38:24