8: Classes

What We Will Cover


Continuations

Midterm Questions

Homework Questions?

  • Nothing

Questions from last class?

8.1: Creating and Using Structures

Learner Outcomes

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

  • Code structures containing multiple values
  • Use structures in a program

8.1.1: Structuring Data

  • Sometimes you want to keep data together in a group
  • For instance, suppose we were programming an inventory system for a grocery store
  • We want to keep various information about each product in our computer like that shown in the following table
  • How would we store the data for each product in our program?
Name Price
Milk 3.95
Bread 2.99
Cheese 3.95

8.1.2: Creating Structures

  • Using individual variables to store groups of data quickly becomes cumbersome
  • One early attempt to group related data in C was called a "struct" (short for structure)
  • A struct is a data type that allows us to gather multiple variables into a single type
  • Because a struct is part of the C language, it is included in C++
  • Classes are a more powerful tool for organizing data, which we will learn about later
  • However, since you will see the struct type used in programs, you need to know about them
  • The general syntax for defining a struct is:
    struct structName {
        dataType1 variableName1;
        dataType2 variableName2;
        ...
    };
    
  • Where:
    • structName: the name you make up for the structure
    • dataTypex: the data type of each variable
    • variableNamex: the name you make up for each variable
  • As an example, we can define our struct to hold product information like this:
    struct Product {
        string name;
        double price;
    };
    
  • The grouping of data into a struct is known as aggregation
  • In the following program, we combine all the data for a milk into a single struct
  • Note how the encapsulated data is access using dot notation
  • Also note that structures are usually placed outside of any function definitions

Example Program Using a struct to Group Related Data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

struct Product {
    string name;
    double price;
};

int main(void) {
    Product milk;

    milk.name = "Milk";
    milk.price = 3.95;

    cout << "Product data:"
         << "\nName: " << milk.name
         << "\nPrice: " << milk.price
         << endl;
}

Declaring a struct Variable

  • Notice how we create a variable of the struct we defined:
    Product milk;
    
  • The struct name becomes a new data type
  • After the struct name, we write a name for our new struct variable

Check Yourself

  1. The keyword struct is short for ________.
  2. True or false: a struct can contain multiple variables.
  3. Grouping of data is known as ________.
  4. Enter the code to declare a struct of type Person named "ed".

    answer

8.1.3: Member Variables and Assignment

  • The variables inside a struct are known as member variables
    struct Product {
        string name;
        double price;
    };
    
  • To access each member variable, we use dot notation
  • The dot '.' operator separates the struct name from the member variable name:
    milk.name = "Milk";
    milk.price = 3.95;
    
  • This allows us to access each member variable
  • We can use dot notation for both assigning and reading variables

Shortcut Assignment

  • You may find it tedious to assign values to a struct one variable at a time
  • As an option, you can assign all the values to a structure at once:
    Product milk = { "Milk", 3.95 };

Input and Output

  • Another way to enter data into a struct variable is to use cin
  • For example:
    cout << "Enter the price: ";
    cin >> milk.price;
    cout << "You entered: " << milk.price
    
  • As shown, you can output member variables as well

Check Yourself

  1. Variables inside a struct are known as ________ variables.
  2. To access a variable inside a struct, we use dot notation, where the left side of the dot is the ________ name and the right side of the dot is the ________ name.
  3. For the following struct, ________ correctly assigns a value to the variable "firstName".
    struct Person {
        string firstName;
        int age;
    }
    
    1. Person ed = { "Ed", 39 };
    2. Person ed;
      ed.firstName = "Ed";
      ed.age = 39;
    3. Person ed;
      cin >> ed.firstName >> ed.age;
    4. all of these

8.1.4: Structures and Functions

  • Structures can be function parameters, either value parameters or reference parameters
  • However, passing by reference is more efficient
  • In addition, we can return structures from functions
  • The following example program uses structures for parameters and return types
  • Note that we create three different products using the single struct

Example Program Using struct's with Functions

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;

struct Product {
    string name;
    double price;
};

// Read product data from a user
Product readProduct();

// Display product data to the screen
void print(Product& prod);

int main(void) {
    Product milk;
    milk.name = "Milk";
    milk.price = 3.95;

    Product bread = { "Bread", 2.99 };
    Product cheese = readProduct();

    cout << "Data for product #1:";
    print(milk);
    cout << "Data for product #2:";
    print(bread);
    cout << "Data for product #3:";
    print(cheese);
}

Product readProduct() {
    Product prod;
    cout << "Enter a name: ";
    getline(cin, prod.name);
    cout << "Enter a price: ";
    cin >> prod.price;
    return prod;
}

void print(Product& prod) {
     cout << "\nName: " << prod.name
     << "\nPrice: " << prod.price
     << "\n\n";
}

Check Yourself

  1. True or false: you can write code to pass a struct variable to a function.
  2. True or false: a call-by-reference parameter is more efficient than a call-by-value parameter for a struct.
  3. True or false: C++ does not allow a function to return a struct.

Exercise 8.1

In this exercise we look at how to use a structure. Make certain you follow each step exactly to prevent problems in this and future exercises.

Specifications

  1. Copy the following program into a text editor, save it as rectstruct.cpp, and then compile the starter code to make sure you copied it correctly.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    #include <iostream>
    using namespace std;
    
    struct Rectangle {
        // Put member variables here
    
    };
    
    // Read structure data from the keyboard
    void read(Rectangle& rect);
    
    // Print structure data to the console
    void print(Rectangle& rect);
    
    int main() {
        // Place statements to create a struct here
    
        // Place statements to call functions here
    
        return 0;
    }
    
    // Define the functions here
    
  2. Add member variables length and width of type int in the Rectangle struct area.

    For more information see section 8.1.2: Creating Structures.

  3. Using the specified prototype, write a read() function that prompts the user for the length and width member variables. Place the function definitions after main(). In addition, add code to the main() function to create a Rectangle structure and call the read() function. For example:
    Rectangle spongeBob;
    read(spongeBob);
    
    When your program runs, the display should look like this:
    Enter the rectangle length: 10
    Enter the rectangle width: 12
    

    The numbers after the colons are entered by the user. For more information see section 8.1.4: Structures and Functions.

  4. Using the specified prototype, write a print() function that displays the length and width variables contained inside the struct with an "x" between them. In addition, add code to the main() function to call the print() function after the call to the read() function. For example:
    print(spongeBob);
    
    When your program runs, the display should look like this:
    Enter the rectangle length: 10
    Enter the rectangle width: 12
    10 x 12
    

    If your code does not work correctly, compare it to the listing shown below.

  5. Submit your final program source code to Blackboard as part of assignment 8.

Listing of rectstruct.cpp

Listing of rectstruct.cpp

As time permits, be prepared to answer the Check Yourself questions in the section: 8.1.5: Summary.

8.1.5: Summary

  • Structures contains multiple values of possibly different types
  • Structure definitions are usually placed outside of any function definition
  • Member variables can be used just as any other variable of the same type
  • Structures can be arguments in function calls
  • Structures can be the type of a value returned by a function

Check Yourself

  1. What is a structure used for? (8.1.2)
  2. Given a struct named Foo, what code do you write to declare a variable of the struct? (8.1.2)
  3. What is a member variable? (8.1.3)
  4. How many member variables can a structure contain? (8.1.3)
  5. Given the following struct and struct variable, what code do you write to disply the member variable price to the console? (8.1.3)
    struct Product {
        string name;
        double price;
    };
    ...
    Product cheese;
    
  6. For the previous struct and struct variable, what code do you write to assign a new value to the member variable name? (8.1.3)
  7. When you pass a struct variable to a function, should you normally use value parameters or reference parameters? (8.1.4)

8.2: Coding Classes to Create Objects

Learner Outcomes

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

  • Discuss the concept of encapsulation and information hiding
  • Describe the reasons for separating the interface from the implementation
  • Write class declarations that define interfaces
  • Code member variables and member functions
  • Design and implement accessor and mutator member functions
  • Write simple no-argument constructors
  • Declare a class and use it to create an object
  • Call a member function of an object to perform a task

8.2.1: Problems With Structures

  • While useful, structures have some inherent limitations and problems
  • For example, consider the following struct to store the time of day:
    struct Time {
        int hours;
        int minutes;
        int seconds;
    };
    
  • Suppose we use this struct to track the liftoff of the space shuttle:
    Time liftoff = {19, 30, 45};
  • Unfortunately, we find a problem and liftoff is delayed six (6) hours
  • We let the user enter this delay into our program:
    int hoursDelayed;
    cout << "Enter the hours of delay: ";
    cin >> hoursDelayed;
    
    liftoff.hours = liftoff.hours + hoursDelayed;
    
  • What is the new time of liftoff?
  • As we can see, structures offer no guarantee that our data will be valid

Example Program Showing a struct Problem

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

struct Time {
    int hours;
    int minutes;
    int seconds;
};

void print(Time t);

int main() {
    Time liftoff = {19, 30, 45};
    print(liftoff);

    int hoursDelayed;
    cout << "Enter the hours of delay: ";
    cin >> hoursDelayed;

    liftoff.hours = liftoff.hours + hoursDelayed;
    print(liftoff);

    return 0;
}

void print(Time t) {
    cout << t.hours << ":"
         << t.minutes << ":"
         << t.seconds << endl;
}

Check Yourself

  1. True or false: for the following struct, any possible value of the member variables are valid.
    struct Time {
        int hours;
        int minutes;
        int seconds;
    };
    
  2. A valid value for the member variable "minutes" is in the range of zero (0) to ________.
  3. True or false: a C-style struct cannot guarantee member-variable data is valid.

8.2.2: Encapsulation and Information Hiding

  • The struct problem shown above is just one of many issues that occur in large programs
  • As programs become larger, we need to change the way we work on them
  • Large programs usually have teams of people who work on them like:
    • One or two software engineers may work on the user interface
    • Another two or three may work on the logic of the application and various features
    • Another may work on data storage and retrieval
  • When many programmers work together, they need to take special care with the data
  • The data type of an application may need to change as the program develops
  • For example, we may start a program by keeping money in a variable of type double
  • Later we may decide that two int variables are a better choice (dollars and cents)
  • Any part of the program that used the double variable would have to change

The Prescription for Larger Programs

  • To make larger programs more manageable, we use classes
  • A class is like a struct but with added features to help protect program data
  • Two important features of classes are:
    • Encapsulation
    • Information hiding
  • These two concepts are closely intertwined
  • You cannot have information hiding without encapsulation
  • However, you can have encapsulation without information hiding, as we saw with structures
  • Sometimes you will see a reference to one term when the writer means both terms

Encapsulation

Encapsulation: bundling of data and the functions operating on that data

  • We saw an example of encapsulation when we looked at structures
  • Encapsulation allowed us to group related variables in one container (capsule)
  • If the variables in the grouping are logically related, we can more easily keep track of what is happening with the data
  • For example, we can keep all the data about a product inside one container
  • In addition to data, classes extend encapsulation to include member functions
  • Member functions are functions that work with the member variables
  • Thus, functions like print() are grouped into the class that describes a product

Information Hiding

Information hiding: hiding the parts of the design that are most likely to change (a.k.a. data hiding)

  • As programs develop, developers change them frequently
  • Even after a program is first completed and released, successful programs are updated with new features
  • With large programs, making a change in one area can cause problems in other areas
  • By hiding the parts of a program most likely to change, a programmer can make implementing changes much easier
  • The ability to change parts of a program without affecting other parts becomes more important as programs grow larger
  • We will look at how to hide parts of classes in the following sections

Check Yourself

  1. True or false: the type, name or quantity of variables rarely change as a program develops.
  2. A ________ is like a struct but adds the ability to hide information.
  3. True or false: classes add the ability to encapsulate functions as well as variables (data).

8.2.3: Declaring Classes

  • Let us look at how to define classes so we can make use of encapsulation and information hiding
  • We first declare a class like we declared a struct, but using the keyword class instead of struct
  • For instance, we declared the following structure to store data for a product:
    struct Product {
        string name;
        double price;
    };
    
  • However, a class has additional features as shown in the following class declaration:
    class Product {
    public:
        Product();
        void read();
        void print();
    private:
        string name;
        double price;
    };
    
  • Notice the similarities and differences between the two declarations

Defining the Programming Interface

  • The class declaration describes the programming interface for using the class
  • An interface describes how other parts of the program can interact with our class
  • The interface consists of all the member functions we want to define for our class
  • For example, the above interface declares member functions to:
    • Product(): construct a new product object
    • read(): read data from the console and store it in the object
    • print(): print the information about a product
  • The first member function, Product(), is known as a constructor
  • The purpose of a constructor is to initialize the member variables
  • The member variables are declared inside the class, like we did with structures

Class Declaration Syntax

  • The general syntax for declaring a class is:
    class ClassName {
    public:
        constructor declarations
        function declarations
    private:
        data fields
    };
    
  • Where:
    • ClassName: the name you make up for the class
    • constructor declarations: function prototypes for constructors (optional)
    • function declarations: function prototypes for member functions
    • data fields: declarations of member variables

Private Members and Information Hiding

  • Note the keywords public and private
  • These keywords are known as access modifiers (a.k.a. visibility modifiers or permission labels)
  • The keyword public means that the following members are "available to the public" (other parts of the program)
  • The keyword private means that only constructors and member functions can access private members
  • An access modifier is how we implement information hiding in the class container
  • Setting private accessibility for all data is good design practice
  • This protects data by forcing indirect access only through public functions
  • We can then add code to the public functions to ensure that the data cannot accidentally be put into an incorrect state
  • In general, you declare most class functions public
  • However, if you want to prevent a function from being used outside a class, declare it private
  • The example program above a class declaration compiles but does not do anything useful
  • We will discuss the other pieces we must add to make the program do something useful in the following sections

Example Program with a Class Declaration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

class Product {
public:
    Product();
    void read();
    void print() const;
private:
    string name;
    double price;
};

// For testing
int main() {
    // Create objects here

    return 0;
}

Programming Style: Class Naming Conventions

  • Use nouns to name classes -- they represent objects
  • Start all class names with a capital letter
  • The capital letter differentiates class names from variable names

Check Yourself

  1. True or false: a class is like a C-style structure but has more features.
  2. A class declaration describes describes the programming ________ for using the class.
  3. To allow only member functions and constructors of an object to access a member variable, use the keyword ________.
  4. True or false: good programming practice is to set the accessibility of all member variables to private.

8.2.4: Member Functions

  • To make use of our class, we must define member functions
  • Each member function prototype listed in the class interface must be implemented
  • We define member functions like we define other functions
  • The only difference is we must put ClassName:: in front of the function name
    • Where ClassName is the name of our class
  • The ClassName:: prefix makes it clear to the compiler that we are defining a member function for that class
  • For example, we could define the read() function like this:
    void Product::read() {
        cout << "Enter the name of the product: ";
        cin >> ws;
        getline(cin, name);
        cout << "Enter the price for a " << name << ": ";
        cin >> price;
        if (price < 0) {
            cout << "Error: negative price!\n"
                 << "Setting price to 0.\n";
            price = 0;
        }
    }
    
  • We may have other read() functions and we must tell the compiler this one belongs to a particular class

Mutator Functions

  • The read() function is a mutator function - a function that modifies one or more member variables of an object
  • When the value of a member variable changes, we say its state has changed
  • After a mutator function runs, the state of the class changes (mutates)
  • In this case, the read() function changes the values of all the member variables
  • Note the use of the conditional statement
  • Mutator functions must leave the object in a valid state
  • Most grocery stores would consider a negative price to be an error

Accessor Functions

  • Another type of function is the accessor function
  • An accessor function queries the object for some information without changing it
  • As an example of an accessor function, we define print() as follows:
    void Product::print() const {
        cout << name << "\t" << price << endl;
    }
    
  • The print() function displays information about a product without changing the the value of any member variable
  • Because the function does not change any variable, we add the keyword const
  • Whenever we design an accessor function, we should add the keyword const
  • The const keyword tells that compiler, and other programmers, that the function does not change the object

Member Function definition Syntax

  • The general syntax for defining a function declared inside a class is:

    returnType ClassName::functionName(parameter1, ..., parametern) [const] {
        statements }

  • Where:
    • returnType: the type of the value returned
    • ClassName: the name of the class
    • functionName: the name you make up for the function
    • parameterx: the input values, if any
    • statements: the statements to execute when the function is called
  • Note that the square brackets indicate that const is optional

Function Naming Conventions

  • As mentioned before, use verbs for function names since they perform an action
  • Also start function names with a lower case letter
  • Not required by syntax, but is a convention that professional programmers follow

Check Yourself

  1. A function declared inside a class is known as a ________ function.
  2. Of the following, ________ is a valid first line of a member function definition.
    1. void print()
    2. void Person::print()
    3. Person::print()
    4. Person::void print()
  3. A function that can modify an object is known as a(n)________ function.
  4. A C++ standard for accessor functions is to include the keyword ________ in the function header so the compiler will ensure no changes are made to member variables.

8.2.5: Coding Constructors

  • Recall that the first member function of our interface is called a constructor:
    class Product {
    public:
        Product();
        void read();
        void print();
    private:
        string name;
        double price;
    };
    
  • The purpose of a constructor is to initialize all the member variables
  • Constructors are a special type of function and thus are coded like a function
  • However, there are slight syntax differences:
    • Constructors always have the same name as the class
    • Constructors do not have a return type
  • As an example, we define our Product constructor like this:
    Product::Product() {
        name = "Unknown";
        price = 0.0;
    }
    
  • Note that the constructor does its job of initializing all the class variables
  • Most of the time we initialize default values to zero
  • However, as you can see, you can choose to use other appropriate values as well
  • The following program with a class declaration and member functions compiles and runs
  • We will discuss more about using classes to create objects in the following section

Example Program with a Class Defined

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:
    Product();
    void read();
    void print() const;
private:
    string name;
    double price;
};

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

void Product::read() {
    cout << "Enter the name of the product: ";
    cin >> ws;
    getline(cin, name);
    cout << "Enter the price for a " << name << ": ";
    cin >> price;
    if (price < 0) {
        cout << "Error: negative price!\n"
             << "Setting price to 0.\n";
        price = 0;
    }
}

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

// For testing
int main() {
    Product milk;
    Product bread;

    cout << "Enter the milk product data\n";
    milk.read();

    cout << "\nEnter the bread product data\n";
    bread.read();

    milk.print();
    bread.print();

    return 0;
}

Check Yourself

  1. True or false: when a variable is not assigned a value in C++, its value is automatically set to zero (0).
  2. True or false: the purpose of a constructor is to initialize all the member variables.
  3. True or false: a constructor has the same name as the class.
  4. True or false: the return type of a constructor is always void.

8.2.6: Constructing Objects From Classes

  • After defining a class, we construct one or more objects
  • The syntax for constructing an object with no arguments is:
    ClassName objectName;
    or
    ClassName objectName = ClassName();
    
  • Where:
    • ClassName: the name of the class type
    • objectName: the variable of the class type
  • For example, to construct a Product object named milk:
    Product milk;
    
  • When an object is created, memory is allocated for the class variables:
    private:
        string name;
        double price;
    
  • The constructor gets called after the memory is allocated, which then initializes the values to a known state:
    Product::Product() {
        name = "Unknown";
        price = 0.0;
    }
    
  • Once an object is constructed, we can call its member functions
  • For instance:
    milk.read();
    milk.print();
    
  • From a single class we can construct many objects:
    Product milk;
    Product bread;
    Product cheese;
    
  • Each of these objects have their own memory space

The Difference Between Classes and Objects

  • What is the difference between a class and an object?
  • A class is a template for building modules
  • An object is one of the modules build from the template
  • By analogy, a class is like the design of a house (blueprints)
  • You need to construct the house from the design before you can use the object:

    House construction from blueprints

  • Other analogies may help understanding:
    • Cookie cutter and cookies: a class is like a cookie cutter and objects are like cookies
    • Mold and Pots: a class is like a mold and objects are the clay pots coming out of the mold.
    • Dog and Fido: a class is like the concept of a dog and objects are individual animals like Fido.
    • Stencil and drawings: a class is like stencil and objects are the drawings made from the stencil.
  • For the above analogies, which is the class and which is the object?
  • In the next exercise we put these ideas into practice to create a class and then construct objects from the class template

Check Yourself

  1. Of the following, ________ correctly constructs an object for the class Person.
    1. Person ed;
    2. Person ed();
    3. string ed;
    4. ed is a Person;
  2. A constructor with no parameters is known as a ________ constructor because if assigns default values to member variables.
  3. A class is a ________ for building objects.
  4. True or false: when an object is constructed, C++ sets aside memory for all the member variables automatically.
  5. True or false: every object has separate memory space for its member variables.

Exercise 8.2

In this exercise we write our first class. We start by converting the struct from the previous exercise to a class. Make certain you follow each step exactly and in order to prevent problems.

Specifications

  1. Without destroying your file, save the rectstruct.cpp file from the last exercise as rectangle.cpp. Compile your code to make sure you saved it correctly.

    We will use the struct as a starting point for exploring classes.

  2. Change the word struct to class in the rectangle.cpp file.
    class Rectangle
    Try to recompile the file and notice that your get error messages like:
    int Rectangle::length' is private

    By default all members of a class are private, which means they cannot be accessed from outside the container of the class. Thus statements like the following are not allowed:

    cin >> rect.length;
  3. One way to allow the class to compile is to add the word public: before the member variables. Make the change shown below and then compile and run the code:
    class Rectangle {
    public: // add this line
        int length;
        int width;
    };
    

    Setting class member variables public lets the code compile, but breaks the information hiding of the class. We need a different way to work with the member variables of the class.

  4. The correct way to work with member variables of the class is through a set of public functions known as the interface. The first step in creating an interface is to move the function prototypes for read() and print() inside the class after the keyword public: and then to add the keyword private: after the function prototypes. When finished, your class should look like so far:
    class Rectangle {
    public:
        // Read data from the keyboard
        void read(Rectangle& rect);
    
        // Print data to the console
        void print(Rectangle& rect);
    private:
        int length;
        int width;
    };
    

    Notice that we now have both member variables and member functions inside our class. The ability to encapsulate both functions and variables is one of the strengths of classes. The public member functions provide the interface to the class and the member variables are "hidden" from the rest of the program. For more information see section 8.2.3: Defining Classes.

  5. Our code will not compile because we still need to correct the function definitions. We need to specify that the read() and print() function definitions are part of the class. Add the class name and scope resolution operator in front of the function names of the function definitions (the part with the curly braces) like:
    void Rectangle::read(Rectangle& rect) {
    void Rectangle::print(Rectangle& rect) {
    

    Adding the class name tells the compiler that the function definitions match the prototypes declared inside the class Rectangle. For more information see section 8.2.4: Member Functions.

  6. We still need to make changes in main() for our code to compile and run. Since the functions moved inside the class, we need to specify an object for the class. Add the object name before the calls to the read() and print() functions like:
    Rectangle spongeBob;
    spongeBob.read(spongeBob);
    spongeBob.print(spongeBob);
    

    Compile and run your program to verify all the changes you have made in the last few steps are correct.

  7. We still need to make changes to our class member functions. In the read() and print() functions, remove the object parameter names from before the member variable names so the functions now look like:
    void Rectangle::read(Rectangle& rect) {
        cout << "Enter rectangle length: ";
        cin >> length;
        cout << "Enter rectangle width: ";
        cin >> width;
    }
    
    void Rectangle::print(Rectangle& rect) {
        cout << length << " x "
             << width << endl;
    }
    

    Note that you may get a compiler warning that your code has unused parameters. The object parameter names are no longer necessary because the member functions can access any member of the class.

  8. Since the parameters are no longer needed, we can remove them from all the functions and function calls throughout our program. Remove all the Rectangle& rect parameters in your program. Also remove the object argument from the read() and print() function calls in main(). When you are finished, main() should look like:
    int main() {
        Rectangle spongeBob;
        spongeBob.read();
        spongeBob.print();
    
        return 0;
    }
    

    Compile and run your program to verify you made all the changes correctly.

  9. One problem remains with our program: when the Rectangle object is constructed the length and width variables are not initialized. You may be able to see this problem by commenting out the read() function call in main() like the following and then compiling and running the program.
    int main() {
        Rectangle spongeBob;
    //    spongeBob.read();
        spongeBob.print();
    
        return 0;
    }
    

    When you run the program you may see 0 x 0 on the screen or you may see another garbage value like 1628430752 x 4262800. As with all uninitialized variables in C++, you can be easily fooled when your program works sometimes but mysteriously fails other times.

  10. You may be tempted to try and correct the problem by assigning an initial value to the member variables like:
    private:
        int length = 0; // No!
        int width = 0;  // No!
    

    However, C++ will not allow you to assign a value to member variables inside a class declaration. The solution is to use a constructor function.

  11. Let us add a constructor to our Rectangle class. In the class declaration, add the following as the first statements after the keyword public:
    public:
        // Default constructor
        Rectangle();
    
    Then after the end of the main() function and before the other member function definitions, add the constructor definition:
    Rectangle::Rectangle() {
        length = 0;
        width = 0;
    }
    

    For more information see section 8.2.5: Coding Constructors.

  12. Compile and run your program to verify you made all the changes correctly.

    With the constructor defined, we can be sure that our class member variables are valid even if the print() function is called before the read() function.

  13. Update your main() function to include the call to read() before the call to print(). Your main() function should look like:
    int main() {
        Rectangle spongeBob;
        spongeBob.read();
        spongeBob.print();
    
        return 0;
    }
    

    Compile and run your program to verify you made all the changes correctly. If you have any problems, verify your code against the program listing below.

  14. Submit your final program source code to Blackboard as part of assignment 8.

Listing of rectangle.cpp

Listing of rectangle.cpp

As time permits, be prepared to answer the Check Yourself questions in the section: 8.2.7: Summary.

8.2.7: Summary

  • While useful, structures have some inherent limitations and problems
  • It is easy to make a mistake and set variable data to incorrect values
  • OOP addresses the problem of data protection through encapsulation and information hiding
  • Encapsulation and information hiding allow you to change the inner workings of a class without affecting other parts of a program
  • The ability to change portions of a program without affecting other parts becomes more important as programs grow larger
  • Member variables store the data of an object
  • You declare and use member variables like other variables except they are encapsulated inside a class
  • Member functions are the operations that can occur on the object
  • Access modifiers control access to variables and functions:
    • private: can only be accessed by member functions of this class
    • public: can be accessed any function
  • To implement information hiding, you declare variables in the private section of a class
  • For this course, use private for all variables and public for most functions
  • Functions in the public section of a class provide the operations that an object can perform
  • Objects are constructed from classes
  • You create an instance of a class (an object) using code like the following:
    Product milk;
    milk.read();
    milk.print();
    

Check Yourself

  1. What problem can you run into using a struct? (8.2.1)
  2. What is meant by the terms "encapsulation" and "information hiding"? (8.2.2)
  3. What keyword identifies the interface of a class? (8.2.2)
  4. What is meant by the term declaration of a class? (8.2.3)
  5. What is a member function? (8.2.4)
  6. Where are member functions declared? (8.2.4)
  7. Where are member functions normally defined? (8.2.4)
  8. What feature identifies the definition of a member function? (8.2.4)
  9. What is the difference between an accessor and a mutator function? (8.2.4)
  10. What is a constructor and what is its purpose? (8.2.5)
  11. How is the syntax of a constructor different than other member functions? (8.2.5)
  12. Given a class named Foo, what code do you write to construct an object of the class? (8.2.6)
  13. For the object declared in the previous question, what code do you write to call a public member function named bar()? (8.2.6)

8.3: Improving Our Class

Learner Outcomes

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

  • Write constructors with parameters
  • Overload constructors
  • Construct objects from classes with overloaded constructors
  • Discuss when to use accessor and mutator functions
  • Describe how to prevent "shadowing"

8.3.1: Constructors with Parameters

  • Recall that the purpose of a constructor is to initialize all member variables for an object to a valid state
  • Whenever we create an object, we need to assign specific values to the member variables
  • So far, we have been assigning specific values using a read() member function
  • However, sometimes we want to create objects and assign them initial values without getting user input
  • For instance, we may want to predefine some objects or assign them values from another source such as a file or database
  • To accomplish this, we can write a constructor with parameters
  • For instance, we could add the following constructor prototype to the interface of our Product class:
    Product(string newName, double newPrice);
    
  • Also, we could define our constructor like this:
    Product::Product(string newName, double newPrice) {
        name = newName;
        price = newPrice;
        if (price < 0) {
            cout << "Error: negative price!\n"
                 << "Setting price to 0.\n";
            price = 0;
        }
    }
    
  • Note the use of the conditional statement
  • Part of the job of a constructor is to make certain that the member variables are set to a valid state
  • Setting a price less than zero would be incorrect for most businesses

Check Yourself

  1. True or false: The purpose of a constructor is to initialize all the member variables for an object.
  2. True or false: constructors may have parameters, just like functions.
  3. True or false: to assign a value to an object variable, you can use an assignment statement in the body of a constructor.

8.3.2: Multiple Constructors

  • We can write a class with more than one constructor
  • All constructors have the same name as the class, but have different parameter lists
  • For example, we could define three different constructors in our Product interface:
    public:
        // Constructors
        Product();
        Product(string newName);
        Product(string newName, double newPrice);
    
  • The technique of defining multiple constructors is known as constructor overloading
  • Having multiple constructors allows us to create an object in different ways
  • By allowing different ways of creating an object, we make our classes more flexible and easier to reuse

Example: Default Constructor (No Parameters)

  • Constructors that can be called with no arguments are known as default constructors
  • Calling a default constructor assigns default values to the member variables
    Product::Product() {
        name = "Unknown";
        price = 0.0;
    }
    

Example: Constructor Definition with one Parameter

  • If you want to accept only some arguments, code a constructor appropriately:
    Product::Product(string newName) {
        name = newName;
        price = 0.0;
    }
    

Example: Constructor Definition with two Parameters

  • If you want to accept arguments for all member variables, code a constructor with parameters for each variable:
    Product::Product(string newName, double newPrice) {
        name = newName;
        price = newPrice;
        if (price < 0) {
            cout << "Error: negative price!\n"
                 << "Setting price to 0.\n";
            price = 0;
        }
    }
    

Implicit Default Constructors

  • If the programmer does not define any constructor, then the compiler supplies an empty default constructor like the following:
    Product::Product() { }
    
  • However, if the programmer defines any constructor, the compiler does not supply a default constructor

Check Yourself

  1. You can define ________ construcors for a class.
    1. 1
    2. 2
    3. 3
    4. as many as you need
  2. A constructor with no parameters is known as a ________ constructor because it sets the member variables to default values.
  3. True or false: if you do not write any constructors for a class, the compiler will add one to the compiled code.

8.3.3: Constructing Objects from Overloaded Constructors

  • If a class has more than one constructor, our program must decide which constructor to call
  • The way that C++ resolves which constructor to call is by matching the arguments to the parameter list
  • Constructor matching is done based on the number and types of the parameters
  • Also, the matching only works if the order is correct
  • For example, assume our class has the following three constructors:
    Product();
    Product(string newName);
    Product(string newName, double newPrice);
    
  • Creating the following objects calls the indicated constructor:
    Product milk;                     // first
    Product bread("Rye Bread");       // second
    Product cheese("Cheddar", 6.75);  // third
    
  • Note that names play no role in matching, only the number and type in the correct order
  • Also note that your can have regular functions with the same name as long as their parameters are different

Avoiding the No-Parameter Trap

  • Notice the difference in the number of parenthesis between the first object created and the others
  • The first statement calls the no-parameter constructor
    Product milk;
  • Other statements call a different constructor
  • It is an error to call the no-parameter constructor like this:
    Product milk2(); // wrong
  • However, the compiler will not catch the error!
  • It is legal syntax to declare a function like milk2() with no parameters
  • Thus, you will not see the error until you try to use the function prototype as an object
    milk2.print();  // causes error

Implicit Type Conversion

  • If there is no exact match for the parameters, C++ will look for compatible types
  • For instance, we can create the following object:
    Product gouda("Gouda Cheese", 5);
  • Since '5' is an int, it does not exactly match the double parameter of the constructor
  • However, C++ will automatically convert an int to a double and call the appropriate constructor
  • C++ only knows how to convert certain types, mostly the numeric types
  • Thus, C++ does not automatically convert something like a string to a double
  • Thus, you cannot create a Product object using:
    Product gouda("Gouda Cheese", "5"); // wrong

Check Yourself

  1. C++ determines which constructor to call by matching the ________ of the parameters in order.
  2. Of the following, ________ is NOT a correct way to construct an object of class Person.
    1. Person ed;
    2. Person ed();
    3. Person ed("Ed");
    4. Person ed("Ed", 39);
  3. True or false: the following is a correct way to construct an object of class Person.
    Person ed = Person();
    

8.3.4: Accessing Member Variables

  • Only member functions of a class are allowed to access the private member variables of objects of that class
  • For example, if we tried to access a member variable from main():
    Product milk;
    milk.name = "Chocolate milk"; // error
    
  • To access a private member variable, you must use public accessor and mutator functions

Set Functions

  • The simplest mutator function is one that changes a single value
  • A common naming convention is to use the name of the value with the word set prepended
  • Thus to set the price of a Product, we would add the following to our interface:
    void setPrice(double newPrice);
  • Mutator functions should verify the value is correct before setting it
  • For example, since price should not be a negative value, we define our function like:
    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;
        }
    }
    

Get Functions

  • You often need to know the value of private variables
  • You use public functions to get the value of private data
  • Called get functions (a.k.a. accessor functions)
  • A common naming convention is to use the name of the variable with the word get prepended
  • For example:
    double Product::getPrice() const {
        return price;
    }
    

Example Class with Get and Set Functions

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <iostream>
using namespace std;

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

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

Product::Product(string newName) {
    name = newName;
    price = 0.0;
}

Product::Product(string newName, double newPrice) {
    name = newName;
    price = newPrice;
    if (price < 0) {
        cout << "Error: negative price!\n"
             << "Setting price to 0.\n";
        price = 0;
    }
}

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

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::read() {
    cout << "Enter the name of the product: ";
    cin >> ws;
    getline(cin, name);
    cout << "Enter the price for a " << name << ": ";
    cin >> price;
    if (price < 0) {
        cout << "Error: negative price!\n"
             << "Setting price to 0.\n";
        price = 0;
    }
}

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

// For testing
int main() {
    Product milk;
    Product bread("Rye Bread");
    bread.setPrice(2.99);
    Product cheese("Cheddar", 6.75);

    cout << "Enter the milk product data\n";
    milk.read();

    milk.print();
    bread.print();
    cheese.print();

    double price = milk.getPrice();
    cout << "The current price of milk is $"
         << price << ".\n";
    cout << "Enter the new price: ";
    double newPrice = 0;
    cin >> newPrice;
    milk.setPrice(newPrice);
    milk.print();

    return 0;
}

Designing Accessor and Mutator Functions

  • Not every member variables needs accessor functions
  • Also, not every variable needs a mutator function
  • In addition, not every get-function needs a matching set-function
  • Remember that the implementation details are hidden
  • Just because a class has member functions prepended with get or set does not necessarily explain how the class is designed
  • For example, a Time class may have the following interface:
    class Time {
    public:
       Time(int hour, int min, int sec);
       Time();
    
       int getHours() const;
       int getMinutes() const;
       int getSeconds() const;
       int secondsFrom(Time t) const;
       void addSeconds(int s);
    
    private:
       int timeInSecs;
    };
    
  • Despite using hours, minutes and seconds in the public interface, all time values are stored as seconds:
    Time::Time(int hour, int min, int sec) {
        timeInSecs = 60 * 60 * hour + 60 * min + sec;
    }
    
  • Then the accessor functions simply convert the seconds to hours, minutes and seconds as needed
    int Time::getMinutes() const {
        return (timeInSecs / 60) % 60;
    }
    

Check Yourself

  1. True or false: the usual naming convention for get or set functions is to use the word "get" or "set" followed by the name of the variable.
  2. True or false: every member variable must have a get and set function.

8.3.5: Local Variables, Parameters and Shadowing

  • Shadowing is when a local variable or parameter has the same name as a member variable of a class
  • Local variables and parameters with same name as a member variable hide the member variable
  • Any use of the variable's name will refer to the local variable or parameter
  • This is the source of many an elusive bug
  • You should not use the same name for a local variable or parameter and a class member variable
  • Remember that a parameter is like a local variable but initialized in a special way
  • Thus you should use a different name for a parameter and a member variable
  • Which lines contain local shadowing in the following program and how do you correct the problem?

Example of Shadowing

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

class MyRectangle {
public:
    MyRectangle();
    MyRectangle(double length, double width);
    void print();
private:
    double length;
    double width;
};

MyRectangle::MyRectangle() {
    length = 0;
    width = 0;
}

MyRectangle::MyRectangle(double length, double width) {
    length = length;
    width = width;
}

void MyRectangle::print() {
    cout << "length: " << length << endl
         << "width: " << width << endl;
}

// For testing
int main() {
    MyRectangle rec;
    MyRectangle rec3x5(3.0, 5.0);
    rec.print();
    rec3x5.print();

    return 0;
}

What the Shadow Knows

  • We correct shadowing by making sure a parameter name is different than a member variable name
  • For example:
    MyRectangle::MyRectangle(double newLength,
            double newWidth) {
        length = newLength;
        width = newWidth;
    }
    
  • In the above code, we changed the name of the parameters
  • The parameter that was length is now newLength
  • Also the parameter that was width is now newWidth

Exercise 8.3

In this exercise we explore the use of more class features.

Specifications

  1. Without destroying your file, save a copy of your rectangle.cpp file from the last exercise as rectangle2.cpp. Compile your code to make sure you saved it correctly.

    We will be enhancing this program during this exercise.

  2. Add a second constructor that lets you set the both the length and width member variables from the parameters by adding the following constructor prototype to the class declaration:
    // Overloaded constructor
    Rectangle(int newLength, int newWidth);
    

    In addition, you will need to add the definition of this overloaded constructor after the end of default constructor definition and before the other member function definitions:

    Listing of Rectangle constructor

    For more information see sections 8.3.1: Constructors with Parameters and 8.3.2: Multiple Constructors.

  3. In your main() function, create at least two objects, one using each constructor. For example:
    Rectangle big(100, 300);
    

    After adding your new constructor, compile and run your program to verify your work. For more information see sections 8.3.3: Creating Objects from Overloaded Constructors.

  4. Write an accessor and mutator function for a member variable following the "standard" get and set naming protocol. For example, here are the prototypes for the length variable:
    int getLength();
    void setLength(int newLength);
    

    For more information see sections 8.3.4: Accessing Member Variables.

  5. In main(), call all the get and set functions at least one time. For example, the following calls the get and set functions for member variable length:
    cout << "Testing get and set functions\n";
    Rectangle big(100, 300);
    cout << "The length of big is: "
         << big.getLength() << endl;
    big.setLength(50);
    cout << "Rectangle big is now: ";
    big.print();
    

    You must add statements like this for member variable width as well.

  6. To explore shadowing, add the following member function prototype to your class declaration:
    void setSize(int length, int width);
    
    In addition, add the following function definition to your program:
    void Rectangle::setSize(int length, int width) {
        length = length;
        width = width;
    }
    

    This function definition has a shadowing problem which you must find and correct. For more information see sections 8.3.6: Local Variables, Parameters and Shadowing.

  7. In main(), call the setSize() function to test your fix to the shadowing problem using code like the following:
    cout << "Testing shadowing\n";
    Rectangle shadow;
    shadow.setSize(7, 14);
    shadow.print();
    

    If you do not see that the shadow rectangle is 7 x 14, then you have not corrected the problem. Ask your classmates or the instructor for help if needed.

  8. Submit your final program source code to Blackboard as part of assignment 8.

As time permits, be prepared to answer the Check Yourself questions in the section: 8.3.6: Summary.

8.3.6: Summary

  • Constructors are used to initialize all the member variables of a class
  • We can code a constructor with or without parameters
  • A constructor without parameters is known as a default constructor
  • We can use constructors with parameters to assign values to the member variables using the parameters
  • We can write a class with more than one constructor
  • If the class has more than one constructor, the constructor matching the argument list is used
  • For example, if our class has the following three constructors:
    Product();
    Product(string newName);
    Product(string newName, double newPrice);
    
  • The following call the constructors in order
    Product milk;
    Product bread("Bread");
    Product cheese("Cheddar", 6.75);
    
  • You must use care when calling a no-parameter constructor:
    • Do NOT use parenthesis
    Product milk2(); // wrong (but compiles)
    Product milk3;   // right
    
  • Local variables and parameters with same name as a class variable hide the class variable
    • Known as "shadowing"
  • You should not use the same name for a local variable and a class member variable

Check Yourself

  1. What is the purpose of a constructor? (8.3.1)
  2. How many constructors can you define for each class? (8.3.2)
  3. When does a compiler automatically include a constructor? (8.3.2)
  4. How do you call a particular constructor when there are many of them? (8.3.3)
  5. How does the syntax for calling a default constructor differ from calling constructors that have parameters? Why? (8.3.3)
  6. What types of functions can access private member variables? (8.3.4)
  7. Since member variables should be declared private, how do you access them outside the member functions of the class? (8.3.4)
  8. Should you declare accessor and mutator functions for all class member variables? Why or why not? (8.3.4)
  9. What is meant by the term "shadowing"? (8.3.5)
  10. How do you avoid "shadowing"? (8.3.5)

Wrap Up

Due Next:
A7-Postal Bar Codes (4/5/12)
A8-Getting Classy (4/19/12)
  • 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 | Day Schedule | Eve Schedule
Syllabus | Help | FAQ's | HowTo's | Links
Last Updated: April 29 2012 @21:40:53