9: Classes and Objects

What We Will Cover


Continuations

Cybersession Schedule

Ensuring Registration Priority for Next Semester

Higher priority registration helps you get the classes you want. To improve your registration priority:

  1. Begin an education plan in WebAdvisor's Student Planning tool
  2. Declare an active program/major
  3. Have good academic standing

Homework Questions?

Questions from last class?

9.1: Reviewing Functions

Learner Outcomes

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

  • Define and call functions
  • Describe the difference between parameters and arguments
  • Identify the scope of a variable
  • Write function declarations (prototypes)
  • Describe function style requirements
  • Design a program with functions

9.1.1: Defining and Calling Functions

  • As we add more code to programs, main() becomes too long to easily understand
  • The solution is to break up the long sequences of code into shorter sections using functions
  • Functions are a way to organize programs into modules
  • With functions, we can potentially reduce the size of our code by collecting duplicate code into one place
  • In addition, we can reuse well-written functions in other programs, saving us from rewriting code

Defining Functions

  • When we reuse a block of code in various parts of a program, we should put the code into a function
  • To define a function, we use the following syntax:
    returnType functionName(parameter1, ..., parametern) {
        statements
    }
    
  • Where:
    • returnType: the data type of the value returned
    • functionName: the name you make up for the method
    • parameterx: the input values of the function, if any
    • statements: the statements to execute when calling the function
  • If the function does not return a value, we use the return type: void
  • Otherwise, we specify the type of data we want to return
  • As an example, we wrote the function:
    int add(int a, int b) {
        int sum = a + b;
        return sum;
    }
    
  • The add() function returns an integer value as shown by the return value: int
  • Functions returning a value use a return statement
    return sum;
    
  • A return statements ends a function call and returns a value specified after the word return
  • For a void function, we code nothing after the word return, like
    return;
    
  • Return statements are optional for void functions
  • A void function returns automatically after reaching its last line

Function Calls

  • When we define a function, the code does not execute automatically
  • To execute the code, we must call a functions call like:
    int sum = add(32, 10);
    
  • When we call a function, we specify its name and provide arguments for each parameter
  • The flow of the program execution stops and jumps to the called function
  • When the called function finishes executing it returns
  • The calling code continues by executing the next operation

Check Yourself

  1. The following code snippet displays the value ________.
    int fun() {
        return 12;
    }
    // other code here
    cout << fun();
    
  2. Which of the following is a function definition and which is a function call?
    1. add(32, 10);
    2. int add(int a, int b) { /* statements */ }
  3. The best return type for a function with the following return statement is ________.
    return 42.3;
    

9.1.2: Parameters and Scope

  • When we define a function, we want it to be reusable
  • To make a function reusable, we first abstract important characteristics and discard the unnecessary
  • Then we generalize the function by defining parameters
  • A parameter is a variable that receives information during a function call
  • To identify their special ability, parameters are declared inside the parenthesis () of a function definition
    int add(int a, int b) { /* statements */ }
    
  • When we call a function, we supply an argument for each parameter
    add(32, 10)
    
  • Parameter variables hold the argument values when we call a function

    function parameters

  • In the above, the values of num1 and num2 are copied to the parameters a and b respectively

Scope and Variables

  • Scope is the term used to define where a program can access a variable
  • A variable declared inside a pair of curly braces is local to that block of code
  • Blocks begin at an opening brace ({) and end at a closing brace (})
  • Because of scope, we can use variables with the same name in different functions
  • However, even if the name is the same the variable is a different memory location

Parameter Passing Mechanisms

  • C++ can pass arguments to parameters in the following ways:
  • Because value parameters send a copy of an arguments value, change to parameters have no effect on the argument
    int mystery(int param)
    
  • However, reference parameters send a variable address and thus changes to the parameter changes the value of the argument variable
    int mystery(int& param)
    
  • The following animation shows the effect of call-by-reference versus call-by-value
  • Call-by-value is like calling on a phone to get something done
  • Call-by-reference is like going somewhere to get something done
  • When you return from call-by-reference you can bring something back like a cup of coffee
call-by-reference vs. call-by-value
Image source: penjee.com

Check Yourself

  1. The following code displays the value ________.
    int fun(int param) {
        param = 12;
        return param;
    }
    // main code here
    int x = 42;
    fun(x);
    cout << x;
    
  2. The following code displays the value ________.
    int fun(int& param) {
        param = 12;
        return param;
    }
    // main code here
    int x = 42;
    fun(x);
    cout << x;
    
  3. The following code displays the value ________.
    int fun(int& param) {
        param = 12;
        return param;
    }
    // main code here
    int x = 42;
    cout << fun(x);
    

Exercise 9.1a: Returning Multiple Values (6m)

In this exercise we write a function that returns two values.

Specifications

  1. Copy the following program into a text editor, save it as coffee.cpp, and then compile and run the starter program to make sure you copied it correctly.
    #include <iostream>
    using namespace std;
    
    // Define function here
    
    int main() {
    
        return 0;
    }
    
  2. Before main(), add the following function to fill a cup of coffee:
    void fillCup(double cup, string sweetener) {
        cup = 10;
        sweetener = "sugar";
        cout << "Coffee amount: " << cup << " oz.\n";
        cout << "Sweetener: " << sweetener << endl;
    }
    
  3. Compile your code to make sure you copied it correctly.
  4. Inside the main() function, add these statements:
        double cup = 0;
        string sweetener;
        fillCup(cup, sweetener);
        if (cup == 0) {
            cout << "Where's my coffee?\n";
        } else {
            cout << cup << "oz. coffee and " << sweetener << " too!\n";
            cout << "Time to shine!\n";
        }
    
  5. Compile and run your code. What do you see when you run the code? (click here to verify) Click to show answer
  6. Update your code to call fillCup() by reference for BOTH parameters.
  7. After changing to call-by-reference, what do you see when you run the code? (click here to verify) Click to show answer
  8. Save your coffee.cpp file to submit to Canvas as part of assignment 9.

When finished, verify your code with a classmate. Then please help those around you as needed.

Exercise Questions

  1. What was the effect of changing from call-by-value to call-by reference?
  2. How many values did you return from the fillCup() function?

9.1.3: Prototypes, Style and Design

  • C++ allows you to declare functions without defining them
  • Function declarations (prototypes) have the function heading without the function body
  • The general syntax for declaring a function is:
    returnType functionName(parameter1, ..., parametern);
    
  • For example:
    double square(double number);
  • Prototypes end with a semicolon (;) rather than curly braces
  • By declaring prototypes before main() you can define the functions after main()

Function Style

  • Functions have style requirements we must follow
  • For instance, we must have a block comment before the name
  • Functions have naming conventions similar to variable names: camel case or underbars
  • When making up a name for a function, use verbs since functions perform an action
  • We indent functions statements inside curly braces, like elsewhere in our code

Function Design

  • We write functions in our code for these reasons:
    1. To organize our code into modules
    2. To reduce repeated code
    3. To allow for code reuse in another program
  • To develop a program around functions we use stepwise refinement (aka top-down design or functional decomposition)
  • With stepwise refinement we create our programs or algorithms with the process:
    1. Describe general tasks
    2. Break each task into smaller tasks as needed
    3. Continue to refine in successive steps until the whole program is fully defined
  • Eventually, the tasks are so trivial they are easy to code
  • General tasks and common code are organized into functions

Exercise 9.1b: Designing a Program Using Functions (20m)

Part A (5m)
  1. Design a program in psuedocode using stepwise refinement that compares the price of two products. Record your results on either paper or a text file.
  2. First list the top-level steps of the program which operates in this order:
    1. Ask the user for the name and price of two products
    2. Prints the name and price of both products
    3. Compares the price of the two products and states which product has a higher price.
  3. After the general steps, specify a separate function for each of the high-level tasks:
    1. Ask the user for the name and price of a single product
    2. Print the name and price of a single product
    3. Compare the price of the two products and state which product has a higher price.
  4. List the subtasks for each of these functions as needed to make translation to C++ straightforward
Part B (3m) Click to show answer
  1. Compare your design with another student.
  2. Record the reviewer's name in your design like:
    Reviewed by: Emma Programmer
  3. Improve your design based on reviewing another student's design.
Part C (10m)
  1. Copy the following program into a text editor, save it as funplus.cpp, and then compile and run the starter program to make sure you copied it correctly.
    #include <iostream>
    using namespace std;
    
    int main() {
        // Enter your code here
    
        return 0;
    }
    
  2. Take your algorithm from Part B and translate it to C++ code, keeping the algorithm steps as comments in your program.
  3. Test your code with values like those shown below.
  4. Save your funplus.cpp source code file to submit as part of assignment 9.
  5. When finished, verify your code with a classmate. Then please help those around you as needed.

Your completed program should operate like the following.

For the first product:
Enter the product name: Milk
Enter the price of Milk: 3.95
For the second product:
Enter the product name: Bread
Enter the price of Bread: 2.99
You entered:
Milk 3.95
Bread 2.99
Milk has a higher price.

Completed Program

When finished, compare your source code by clicking here. Your code does NOT need to be the same but comparison is helpful for learning once you have attempted the program yourself. Click to show answer

9.2: Introduction to Object-Oriented Programming

Learner Outcomes

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

  • Describe objects and classes
  • 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

9.2.1: About Objects and Classes

  • We have learned to structure programs using functions
  • While this is a good practice, experience shows it does not go far enough
  • As programs become larger, it becomes difficult to maintain a large collection of functions
  • To overcome this problem, computer scientists developed object-oriented programming (OOP)
  • In object-oriented programming code is centered around objects instead of functions
  • For example, consider a complex object like a car:
  • Do we think of a car as a collection of parts?
  • Or do we think of a car as an object?

Moving to Object-Oriented Programming

  • It turns out we have been programming with objects throughout the course
  • Some pre-made objects we have programmed with include:
    • cin
    • cout
  • In addition, we have made our own objects with statements like:
    string str = "Hello World!";
    
  • In the line above, str is the object
  • We can tell these are objects because of statements like these:
    cin.clear();
    cout.unsetf(ios_base::floatfield);
    string str = "Hello World!";
    int x = str.length();
    
  • What do you notice about these commands?

Some Object-Oriented Concepts

  • Most often functions work on a certain type or group of data
  • For example, a program about cars may have variables for names and prices
  • Functions for a car work with these variables
  • Thus we group, or encapsulate, the variables for names and prices with their functions
  • When we encapsulate related variables and functions in C++, we call these groupings objects
  • Some object terminology:
    • member variables: variables grouped inside an object (aka data members)
    • member functions: functions grouped inside an object that work with data members

Designing Objects

  • When we design objects in C++, we do not design a single object
  • Instead we design a class for creating one or more objects
  • A class is a a user defined type that provides a template for creating objects
  • A class specifies the components and operations of an object
  • We discuss how to declare a class for creating objects in the following section

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 built from the template
  • By analogy, a class is like the factory that makes cars
  • We need to build the car in the factory before we can use the object

    House construction from blueprints

  • Other analogies may help understanding:
    • Blueprint and house: a class is a blueprint and the house is made following the blueprint
    • Dog and Fido: a class is like the concept of a dog and objects are individual animals like Fido.
    • 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.
    • Stencil and drawings: a class is like stencil and objects are the drawings made from the stencil.
    • 3D printers and objects: a class is like a 3D printer program and objects are the items printed
  • For the above analogies, which is the class and which is the object?

Check Yourself

  1. A bundle or group of related variables and functions in known as a(n) ________.
  2. When we design objects in C++, we write a(n) ________.
  3. For the following code, the item that is not an object is ________.
    1. cin
    2. cout
    3. int
    4. string str

9.2.2: Specifying the Programming Interface

  • Let us look at how to develop classes in C++
  • To develop a class we start by defining a class using the syntax shown below
  • Within the class declaration, we both:
    • Declare (prototype) the member functions that specify the behavior
    • Define the data members to hold the object's data
  • After the class declaration we implement the member functions by writing the statements for the body of the functions

Class Definition Syntax

  • The general syntax for defining a class is:
    class ClassName {
    public:
        function declarations
    private:
        data members
    };
    
  • Where:
    • ClassName: the name you make up for the class
    • function declarations: function prototypes for member functions
    • data members: definitions of member variables

Example Class Definition

  • The following is an example class definition:
    class Product {
    public:
        void read();
        void print() const;
    private:
        string name;
        double price;
    };
    
  • The class definition describes the programming interface for using the class
  • An interface describes how other parts of the program can interact with objects of our class
  • The interface consists of all the public members we define in a class
  • Members declared private are restricted and not part of the interface
  • For example, the above interface declares public member functions:
    • read(): read data from the console and store it in the object
    • print(): print the information about a product
  • The member variables are declared inside the class
  • However, all the member variables are "hidden" with the keyword private

Private Members and Information Hiding

  • Note the keywords public and private
  • These keywords are known as access modifiers
  • The keyword public means that the following members have no restrictions and are "available to the public" (other parts of the program)
  • The keyword private restricts access to only member functions
  • An access modifier is how we implement information hiding in a class
  • Information hiding is restricting access to certain data and code
  • By restricting access we are able to make changes to the "hidden" parts of the program without affecting the rest of the program
  • Setting private accessibility for all data is good design practice
  • This protects data by forcing indirect access only through public functions
  • We then add code to the public functions to ensure that the data cannot accidentally be put into an incorrect state
  • In general, we declare most class functions public
  • The example program below contains a class definition and 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 Definition

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

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

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

    return 0;
}

Camel Case

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
  • Spell class names using CamelCase where each "word" starts with an uppercase letter
  • What would be an appropriate class name for the picture? (answer)

Try It: Declare a Class (4m)

  1. Copy the following program into a text editor, save it as car.cpp, and then compile and run the starter program to make sure you copied it correctly.
    #include <iostream>
    using namespace std;
    
    // Declare class here
    
    int main() {
        // Construct objects here
    
        return 0;
    }
    
  2. Declare a class named Car, and then compile your code to check for syntax errors.

    Remember the semicolon after the closing curly brace.

  3. Declare private member variables for a name, price and mpg (miles per gallon) with the appropriate data types.
  4. Declare two public prototypes for functions named read() and print() with void return types and no parameters.
  5. Compile and run the modified program to make sure you made the changes correctly.
  6. Save your source code file as we will be adding to it in the next Try It.

When finished, please help those around you.

Check Yourself

  1. The public parts of a class definition describes what operations can be performed on the class and is known as the programming ________.
  2. To allow only member functions and constructors of an object to access a member variable, use the keyword ________.
  3. True or false: good programming practice is to set the accessibility of all member variables to private.
  4. True or false: the main reason to set all member variables to private is to allow changes to how the class data is stored.
  5. As a matter of style, class names should be spelled using ________.

9.2.3: Data Members and Objects

  • An object stores its data in data members
  • Data members are the variables declared inside a class
  • Recall our example class definition:
    class Product {
    public:
        void read();
        void print() const;
    private:
        string name;
        double price;
    };
    
  • The two member variables are name and price

Constructing an Object

  • After defining a class, we construct one or more objects
  • Objects are then a particular instance of a class, meaning an object has particular values
  • The syntax for constructing an object 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 and another named bread:
    Product milk;
    Product bread = Product();
    
  • When we construct a Product object the program allocates memory like:

    space for name
    space for price

  • Memory is allocated for both variables next to each other in memory
  • The amount of memory allocated depends on the data type of each member variable
  • Since the data members are declared private, we cannot access the variables directly
    cout << milk.name << endl; // Error -- use member function instead
    
  • Instead, we must call member functions which we cover in the next section

Dot Notation

  • Notice that when we access a member of an object we use "dot" notation
  • Recall that we used dot notation with both strings and cin like:
    string str = "Hello";
    cout << str.length() << endl;
    cout << str.substr(0, 4) << endl;
    cin.clear();
    cin.ignore(1000, '\n');
    
  • When constructing our own objects, we use dot notation as well
  • The dot separates the object name from the member name

Try It: Construct an Object (2m)

  1. Open your source code file from the last Try It.
  2. In the main() function, define a Car object (Car class variable) like:
    int main() {
        Car tesla;
    
        return 0;
    }
    
  3. Compile and run your code to verify it works correctly.

    If you have questions or problems, ask a classmate or the instructor for help.

  4. Save your source code file as we will be adding to it in the next Try It.
  5. What does defining and compiling a Car variable tell us about class and objects?

When finished, please help those around you.

Check Yourself

  1. Of the following, the code that correctly constructs an object for the class Person is ________.
    1. Person ed;
    2. Person ed();
    3. string ed;
    4. ed is a Person;
  2. True or false: when an object is constructed, C++ sets aside memory for all the member variables automatically.
  3. True or false: every object has separate memory space for its member variables.

9.2.4: Member Functions

  • To define behaviors for a class, we must implement member functions
  • We declare (prototype) member functions inside the class like:
    class Product {
    public:
        void read();
        void print() const;
    //... other code omitted
    };
    
  • Each member function prototype declared in the class interface must be implemented

Member Function definition Syntax

  • The general syntax for implementing 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
  • We implement member functions like we implement 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 tells the compiler we are defining a member function for that class
  • For example, we could implement 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
  • The compiler will match the function declaration (prototype) in the class with the function definition (implementation)
  • Notice that our function tests the data as it is entered to verify it is correct
  • Verification of data is one benefit of hiding member variables
  • Where are the variables name and price declared? (answer)

Mutator Functions

  • The read() function above is a mutator function - a function that modifies one or more member variables
  • 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
  • As an example, if an objects starts with member variables in the following state:

    name = "none"
    price = 0

  • If the user enters "Milk" and 3.95 when the read() function is called, the member variables would contain:

    name = "Milk"
    price = 3.95

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 << " @ " << price << endl;
    }
    
  • The print() function displays information about a product without changing 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 to make sure that the function does not change the object

Member Functions Calling Member Functions

  • When a member function calls another on the same object, we do not use dot notation
  • Instead we simply use the name of the function
  • As an example, suppose at the end of read() we decided to call print()
  • We would call print() so the user can confirm what data was entered
  • For example, we could implement the added code 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;
        }
        cout << "You entered: ";
        print();
    }
    
  • Notice that we did not use dot notation to call print()
  • We only need dot notation when calling a member function from a non-member function like main()

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
  • These are the same function naming rules we have discussed previously

Try It: Add Member Functions (4m)

  1. Open your source code file from the last Try It.
  2. After the class definition, add the implementation of the mutator function read() that asks for the name, price and mpg of a car.

    Compile your code to verify you wrote it correctly.

  3. After the class definition, add the definition of the accessor function print() that prints the name, price and mpg of a car object.

    Compile your code to verify you wrote it correctly.

  4. Change the main() function to call the member functions like:
    int main() {
        Car tesla;
        tesla.read();
        tesla.print();
    
        return 0;
    }
    
  5. Compile and run your code to verify it works correctly.

    If you have questions or problems, ask a classmate or the instructor for help.

  6. You program should work like this:
    Enter the name of the car: Civic
    Enter the price for a Civic: 19500
    Enter the MPG for a Civic: 31
    Civic @ 19500 with MPG: 31
    
  7. Save your source code file as we will be adding to it in the next Try It.

When finished, please help those around you.

Check Yourself

  1. A function declared inside a class is known as a ________ function.
  2. A valid first line of a member function definition is ________.
    1. void print()
    2. void Person::print() const
    3. Person::print()
    4. Person::void print() const
  3. A function that can modify an object is known as a(n) ________ function.
  4. A C++ standard for accessor functions is to include in the function header the keyword ________.
  5. True or false: member variables can always be accessed by member functions, even when declared private.

9.2.5: Coding Constructors

  • A constructor is a member function that initializes the data members of an object
  • The constructor is automatically called whenever an object is created
  • We add a constructor so we can ensure that all data members are properly set before other member functions act on an object
  • To understand the importance of the constructor consider the following code to test our Product class:
    int main() {
        Product milk;
        milk.print();
    
        return 0;
    }
    
  • When we run the code, we may see something like the following printed:
     @ 6.86242e-308
    
  • The problem is cause by uninitialized member variables
  • The solution to uninitialized member variables is to code a constructor

Adding a Constructor

  • We add a constructor function to our interface as shown in bold:
    class Product {
    public:
        Product();
        void read();
        void print() const;
    private:
        string name;
        double price;
    };
    
  • Constructors are a special type of function and thus are coded like a function
  • However, there are some syntax differences:
    • Constructors always have the same name as the class
    • Constructors do not have a return type, not even void
  • Continuing our example, we implement our Product constructor like this:
    Product::Product() {
        name = "none";
        price = 0.0;
    }
    
  • Notice that the constructor does its job of initializing the class variables
  • Most of the time we initialize default values to zero
  • However, as we can see, we can choose to use other appropriate values as well
  • The following program has an added constructor

Example Program with a Constructor Added

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 = "none";
    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;
}

In-class Member Initializers

  • Starting with C++-11, we can initialize values in the class
  • Thus we could assign initial values Like:
    private:
        string name = "none";
        double price = 0;;
    
  • Thus in-class member initialization saves us some typing
  • However, there are still good uses for constructors as we will see the the next section

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.

More Information

Exercise 9.2: Creating a Class (5m)

In this exercise we add a constructor to our exercise.

Specifications

  1. Complete the Try It activities we explored throughout the section including:
    1. Try It: Declare a Class
    2. Try It: Construct an Object
    3. Try It: Add Member Functions
  2. Open your source code file from the last Try It.
  3. In main(), add a call to the member function print() function before the call to the member function read(), like:
    int main() {
        Car tesla;
        tesla.print();
        tesla.read();
        tesla.print();
    
        return 0;
    }
  4. Compile and run your program and notice the output of member function print() before calling read(). It should look something like:
     @ 2.32211e-322 with MPG: 6.89746e-308
    Enter the name of the car: Civic
    Enter the price for a Civic: 19500
    Enter the MPG for a Civic: 31
    Civic @ 19500 with MPG: 31
    

    User input is shown in italics for the example only, NOT your program. Notice that before calling read(), the print() function shows garbage output. After calling read(), the output is correct. Why do you think the first call to print() produces garbage output? Click to show answer

  5. Add a constructor declaration to the class specification. Your class structure should now look like:
    class Car {
    public:
        Car();
        void read();
        void print() const;
    
    private:
        string name;
        double price;
        double mpg;
    };
    
  6. After the class definition,, add the following constructor definition.
    Car::Car() {
        name = "none";
        price = 0;
        mpg = 0;
    }
    
  7. Compile and run your program and notice the output of member function print() before calling read(). It should look like:
    none @ 0 with MPG: 0
    Enter the name of the car: Civic
    Enter the price for a Civic: 19500
    Enter the MPG for a Civic: 31
    Civic @ 19500 with MPG: 31
    

    User input is shown in italics for the example only, NOT in your program. Notice that calling the print() function before read() now shows the default values assigned by the constructor.

  8. Save your source code file to submit as part of assignment 9.

When finished, please help those around you.

9.2.6: Summary

  • We have learned to structure programs using functions
  • While this is a good practice, experience shows it does not go far enough
  • As programs become larger, it becomes difficult to maintain a large collection of functions
  • To overcome this problem, computer scientists developed object-oriented programming (OOP)
  • In object-oriented programming code is centered around objects instead of functions
  • We encapsulate variables and functions into an object
  • The variables inside an object are known as member variables (aka data members)
  • The functions inside an object are known as member functions
  • When we design objects in C++, we do not design a single object
  • Instead we design a class for creating many objects

Defining a Class

  • The following is an example of a class definition
    class Product {
    public:
        void read();
        void print() const;
    private:
        string name;
        double price;
    };
    
  • The class definition describes the programming interface for using the class
  • An interface describes how other parts of the program can interact with objects of our class
  • The interface consists of all the public members we define in a class
  • Members declared private are restricted and not part of the interface
  • For example, the above interface declares public member functions:
    • read(): read data from the console and store it in the object
    • print(): print the information about a product
  • The member variables are declared inside the class as well
  • However, all the member variables are "hidden" with the keyword private
  • By hiding parts of a class, we are able to make changes to the class without affecting other parts of a program
  • For this course, use private for all variables and public for most functions

Constructing an Object

  • After defining a class, we can construct one or more objects
  • Objects are then a particular instance of a class, meaning an object has particular values
  • For example, to construct a Product object named milk:
    Product milk;
    Product bread = Product();
    
  • When we construct an object the program automatically allocates memory for all data members
  • For our example Product object the program allocates memory like:

    space for name
    space for price

Member Functions

  • To define behaviors for a class, we must implement member functions
  • We declare (prototype) member functions inside the class like:
    class Product {
    public:
        void read();
        void print() const;
    //... other code omitted
    };
    
  • Each member function prototype declared 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
  • The ClassName:: prefix tells the compiler that we are defining a member function for that class
  • For example:
    void Product::read() {
        // function code here
    }
    
  • There are two types of member functions in a class:
    • mutator function: a function that modifies one or more member variables
    • accessor function: queries the object for some information without changing the value

Constructor Functions

  • A constructor is a member function that initializes the data members of an object
  • The constructor is called automatically whenever an object is created
  • We add a constructor so we can ensure that all data members are properly set before other member functions act on an object
  • The following class definition shows a constructor function prototype in bold:
    class Product {
    public:
        Product();
        void read();
        void print() const;
    private:
        string name;
        double price;
    };
    
  • Constructors are a special type of function and thus are coded like a function
  • However, there are some syntax differences:
    • Constructors always have the same name as the class
    • Constructors do not have a return type, not even void
  • For our example, we might implement our Product constructor like this:
    Product::Product() {
        name = "none";
        price = 0.0;
    }
    
  • Notice that the constructor does its job of initializing the class variables

9.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"

9.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: we can assign a value to a member variable when we declare it, like:
    class Product {
    // ... other code omitted
    private:
        price = 0.0;
    
  3. A constructor with no parameters is known as a ________ constructor because if assigns default values to member variables.
  4. A class is a ________ for building objects.
  5. True or false: constructors cannot have parameters, unlike functions.
  6. True or false: constructors may assign a parameter to a member variable.

9.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. For any class, you can define ________ constructors.
    1. 1
    2. 2
    3. 3
    4. as many as you need
  2. A constructor with no parameters that sets the member variables to default values is known as a ________ constructor.
  3. True or false: if you do not write any constructors for a class, the compiler will add one to the compiled code.

9.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
    
  • Names play no role in matching, only the data type in the correct order
  • We 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
  • Another way to construct an object with a no-parameter constructor is to use the alternate form we discussed in section 9.2.5
  • For example, to construct a Product object by calling the default constructor:
    Product milk = Product();
    

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

Try It: Multiple Constructors and Their Objects (5m)

  1. Open your source code file from the last exercise.

    If you did not save the file then start with this file and save it as car.cpp. Then compile and run the program to make sure you saved it correctly.

  2. Add a constructor declaration with a three parameters to the class specification like the following:
    Car(string newName, double newPrice, double newMpg);
    
  3. After the class definition, add a constructor definition (implementation) for the above constructor.

    If you have questions or problems, ask a classmate or the instructor for help.

  4. In main(), construct an object using the overloaded constructor and then call print() as follows:
    cout << "Other cars for comparison:\n";
    Car corolla("Toyota Corolla", 18500, 30);
    corolla.print();
    
  5. Compile and run your program and verify you see the car information printed like:
    none @ 0 with MPG: 0
    Enter the name of the car: Civic
    Enter the price for a Civic: 19500
    Enter the MPG for a Civic: 31
    Civic @ 19500 with MPG: 31
    Other cars for comparison:
    Toyota Corolla @ 18500 with MPG: 30
    

    User input is shown in italics for the example only, NOT in your program. If you have problems, ask a classmate or the instructor for help.

  6. Identify which constructor is called for each object and add a comment in your source code where the object is created.
  7. Save your car.cpp file as we will add to it in the next exercise.

When finished please help those around you.

Check Yourself

  1. C++ determines which constructor to call by matching the order of the parameter ________.
  2. Of the following, the wrong way to construct an object of class Person is ________.
    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();
    

9.3.4: Accessing Member Variables

  • Only member functions of a class are allowed to access the private member variables
  • For example, if we tried to access a member variable from main():
    Product milk;
    milk.name = "Chocolate milk"; // Error -- must call member function
    
  • How can we change the value of private (hidden) member variables from main()? Click to show answer
  • To work with a private member variable, we must use public accessor or mutator function

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;
        }
    }
    
  • To call a set-function from main() we write code like:
    Product milk;
    milk.setPrice(3.99);
    

Get Functions

  • We often need to know the value of private variables
  • For this we 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 value with the word get prepended
  • For example:
    double Product::getPrice() const {
        return price;
    }
    
  • Remember that accessor functions should have the keyword const as shown above
  • To call a get-function from main() we write code like:
    Product cheese("Cheddar", 6.75);
    double priceCheese = cheese.getPrice();
    cout << priceCheese << endl;
    

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
#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;
}

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

void Product::setPrice(double newPrice) {
    price = newPrice;
}

void Product::read() {
    cout << "Enter the name of the product: ";
    cin >> ws;
    getline(cin, name);
    cout << "Enter the price for a " << name << ": ";
    cin >> price;
}

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 << endl;
    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 variable needs an accessor function
  • 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: we should declare all member variables private. Why?
  2. 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 value being returned.
  3. True or false: every member variable must have a get- and set-function.

9.3.5: Local Variables, Parameters and Shadowing

  • Shadowing occurs 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
  • We should not use the same name for a class member variable as a local variable or parameter
  • Remember that a parameter is a local variable, just initialized in a special way
  • Thus you should use a different name for a parameter and a member variable
  • In the following, which lines contain local shadowing 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 9.3: Building A Better Class (15m)

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

Specifications

  1. Open your source code file from the last Try It.
  2. Add another constructor prototype with a two parameters to the class definition like the following:
    Car(string name, double price);
    
  3. Implement the new constructor assigning the two parameters to name and price and 10 to mpg.
  4. In main(), construct an object using the two-parameter constructor and then call print() as follows:
    cout << "Other car comparisons:\n";
    Car corolla("Toyota Corolla", 18500, 30); // 3-param constructor
    corolla.print();
    Car junker("Junker", 350); // 2-param constructor
    junker.print();
    
  5. Compile and run your program and verify you see the car information printed like:
    none @ 0 with MPG: 0
    Enter the name of the car: Civic
    Enter the price for a Civic: 19500
    Enter the MPG for a Civic: 31
    Civic @ 19500 with MPG: 31
    Other cars for comparison:
    Toyota Corolla @ 18500 with MPG: 30
    Junker @ 350 with MPG: 10
    

    User input is shown in italics for the example only, NOT in your program. If you have problems, ask a classmate or the instructor for help.

  6. If you see output like the following for the junker, then you have a problem with shadowing.
     @ 1.25384e+224 with MPG: 10
    
    Correct the shadowing problem by changing the names of the parameters in the two-parameter constructor.
  7. Write an accessor and mutator function for each member variable following the "standard" get and set naming protocol. For example, here are the prototypes for the name variable:
    string getName();
    void setName(string newName);
    

    Write the same type of functions for member variables price and mpg. For more information see sections 9.3.4: Accessing Member Variables.

  8. 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 mpg:
    cout << "Testing get and set functions\n";
    Car tester("Calamity", 1000, 5);
    cout << "The mpg of tester is: "
         << tester.getMpg() << endl;
    tester.setMpg(25);
    cout << "Tester's MPG is now: ";
    tester.print();
    

    You must add statements like this for member variables name and price as well.

  9. Submit your exercise source code as part of assignment 9.

When finished please help those around you.

9.3.6: Summary

  • In this section we looked at more class features and pitfalls

Overloaded Constructors

  • 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
    

Get and Set Functions

  • We can use accessor and mutator functions to get and set member variables
  • For accessor functions that get a single values, we write a function named with the word get prepended to the value returned
  • For example, to get the price of a Product object we would write a function like:
    double Product::getPrice() {
        return price;
    }
    
  • If we decide we need to allow our interface to change a single value, we write a function named with the word set prepended to the value changed
  • For example, to set the price of a Product object we would write a function like:
    void Product::setPrice(double newPrice) {
        price = newPrice;
    }
    

Shadowing

  • Local variables and parameters with same name as a class variable hide the class variable
    • Known as "shadowing"
  • We 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? (9.3.1)
  2. How many constructors can you define for each class? (9.3.2)
  3. When does a compiler automatically include a constructor? (9.3.2)
  4. How do you call a particular constructor when there are many of them? (9.3.3)
  5. How does the syntax for calling a default constructor differ from calling constructors that have parameters? Why? (9.3.3)
  6. What types of functions can access private member variables? (9.3.4)
  7. Since member variables should be declared private, how do you access them outside the member functions of the class? (9.3.4)
  8. Should you declare accessor and mutator functions for all class member variables? Why or why not? (9.3.4)
  9. What is meant by the term "shadowing"? (9.3.5)
  10. How do you avoid "shadowing"? (9.3.5)

Wrap Up

Due Next:
A9-Getting Classy (4/5/17)
  • 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.
Last Updated: April 07 2017 @17:55:20