Courses/CS 2124/Lab Manual/Classes and Objects

From A-State Computer Science Wiki
Jump to: navigation, search

Classes and Objects and Our Development Toolchain

Introduction

The purpose of this lab is to provide a bridge from CS 2114 “Structured Programming” and to allow you to re-familiarize yourself with our toolchain.

We will begin with some simple exercises to allow you to test your editor and compiler toolchain, then we will develop an object-oriented representation for items in an online shopping cart.

Editing and Compiling

First, let’s make sure you are able to compile and run code on our virtual server. Create the following simple program in your programmer’s text editor of choice; name it hello.cpp:

#include <iostream>

int main(){
    std::cout << "Hello, World!\n";
    return 0;
}

Now move the code to the class virtual server (the IP address is available on the course information page). (Note: you may have saved your program directly on the virtual server from your editor — if you did that, then ignore this step.)

Once the code is on the virtual server, log into the server with ssh. Make sure you are in the correct directory and can see the program file by doing an ls command. Compile the program with:

g++ -Wall -Wextra -pedantic -std=c++17 hello.cpp
Information Icon.png

If your compiler does not support the c++17 flag, try c++14 or c++11. Use the most recent standard that is available to you.

Multi-File Projects

Programming assignments in this class will often consist of multiple files. We recommend that you organize your programming assignments into directories, with one directory per assignment.

  • Create a directory for this lab activity, if you have not already done so.
  • Move into the directory you just created with the cd command if you have not already done so.

Now, create the following new (empty) source files in your project directory:

  • main.cpp, Item.h, Item.cpp

Place the following code in Item.h:

#ifndef ITEM_H
#define ITEM_H

void example();

#endif

Place the following code in Item.cpp:

#include "Item.h"
#include <iostream>

void example(){
    std::cout << "Hello from example()!\n";
}

And place the following code in main.cpp:

#include <iostream>
#include "Item.h"

int main(){
    example();
    return 0;
}

Make sure all three files are saved on the virtual server. To create a running program from these three files, we must do the following:

  1. Compile Item.cpp so that the example() function implementation is available.
  2. Compile main.cpp.
  3. Link the object code produced from Item.cpp with the function call contained in the object code produced by compiling main.cpp.

Fortunately, clang (and g++) can do all of these steps in a single command. You can also do them separately, which might be better in large projects — for now though, we will use the single command:

g++ -Wall -Wextra -pedantic -std=c++17 main.cpp Item.cpp
Information Icon.png

You can often use the wildcard *.cpp in place of a long list of source code files, assuming that all source files in the directory are part of your program.

Before going on, remove the example() function from all three files.

Review: Include Guards

The first two lines in Item.h:

#ifndef ITEM_H
#define ITEM_H

together with the last line:

#endif

are called include guards. Every header file (.h) you create should have include guards around all of the code contained in the file. These are pre-processor directives that prevent the compiler from including the same code more than once in a single program. They work by first checking to see if the identifier ITEM_H exists. (By convention we use the name of the library with any dots transformed into underscores.) If it does not, then the next line defines the identifier, and the rest of the file is included. But if ITEM_H had already been defined (by a previous inclusion of the file), then everything until the #endif will be ignored by the pre-processor. This prevents multiple inclusion from causing problems.

Item: An item in a shopping application

We want to create a class to represent an item in a shopping application. For our first attempt, the item should be able to store the name of a product, as well as its price and the quantity that the customer wants to purchase.

Let’s start with a struct to simply “bundle” the three values together. Place the following code in Item.h:

#include <string>

// ----------

struct Item {
    std::string name;
    double      price;
    int         quantity;
};

There is no need for any code in Item.cpp at this point. Now in main.cpp, let’s make use of the Item:

Item item1;
item1.name     = "Potato Chips";
item1.price    = 1.99;
item1.quantity = 1;

std::cout << "The item in your cart is:\n";
std::cout << '\t' << item1.name << '\n';
std::cout << "\tPrice: $  " << item1.price    << '\n';
std::cout << "\tQuantity: " << item1.quantity << '\n';

Now, compile and run the program.

Exercise: Convert to a class

Now you have an Item defined by a C++ structure, but it would be better if we convert to a class and make use of the following features of Object-Oriented programming:

  • encapsulation
    • Make the attributes of the class private.
    • Ensure that the item can be constructed in convenient and meaningful ways.
    • Create accessors and mutators for the attributes.

Let’s convert to a class and create a constructor that will take the name and price of an item (and optionally a quantity). Update the Item definition that you already created from a struct to the following class:

class Item {
  public:
    Item(std::string name, double price, int quantity=1);
  private:
    std::string name;
    double      price;
    int         quantity;
};

Notice that we will let quantity default to 1. Now create an implementation for the constructor in the file Item.cpp:

/**
 * Constructs an item given name, price, and optionally a quantity.
 * 
 * @param name      name of the item
 * @param price     price for one item
 * @param quantity  number of items desired (default 1)
 */
Item::Item(std::string name, double price, int quantity){
    this->name      = name;
    this->price     = price;
    this->quantity  = quantity;
}

Here, we set each of the item’s attributes to the value in the corresponding parameter. But, since the parameters used exactly identical names to our attributes, we need a way to tell the difference in the code. Every object in C++ can refer to “itself” with the this keyword. The “arrow” operator (->) must be used with this instead of the “dot” operator because this is a pointer to the current object, not an instance of an object. Don’t worry if you haven’t covered pointers yet — you will study those in more detail soon.

Also take note of the comment header for the method — you should re-read the Grading Guidelines for the course to see what documentation you will be required to provide in your homework code. For brevity, the in-lab instructions will not always show the documentation, and you will not be graded on documentation for in-lab activities, but you will be responsible for complete documentation in your homework.

Testing: Modify your main.cpp so that the body looks like the following. (Remove all other code except the return 0;.)

Item item1{ "Potato Chips", 1.78 };
Item item2{ "Doritos", 1.99, 2 };

The program should compile and run, although it doesn’t do anything visible at this point. We need a way to access the information in the item to print it out, but all of this information is currently “locked away” inside the private attributes. We cannot access those from outside the Item class’s code. In the next section we will add some methods to define ways to interact with this information.

Accessors and Mutators

To provide “read-only” access information about an object, we use methods called accessors. When we want to change the state of the object, we use methods called mutators.

Add Accessors

Let’s start by adding an accessor method for the item’s name. Add the following prototype to the Item class definition, just below the prototype for the constructor (in the public section):

std::string get_name() const;

This will be the accessor for the item’s name. The const qualifier following the parameter list is a “promise” that the method will not modify the object in any way when it is executed. Most accessors will be const-qualified in this way.

Now we need to create a corresponding implementation for the get_name() method. Add the following to your Item.cpp file:

std::string Item::get_name() const{
    return name;
}

Be sure not to forget the scope resolution that binds this method to the Item class!

Testing: Add the following code to your main program and compile/run.

// print item1's name
std::cout << item1.get_name() << '\n';

Now follow the same pattern to create accessors for price and quantity. Name them by the same convention (append get_ to the attribute name). Test your program after completing each one.

Add Mutators

For this shopping cart application, it will occasionally be useful to change the price of an item (to apply a discount or promotion) or the quantity (in case the user adds more to the cart or removes an item). We will never allow the name of an item to change, since this doesn’t really make sense in a shopping cart context. So, we need to create mutators for the price and quantity, but not for the name.

Let’s start with a mutator for the price. Add the following prototype to the class definition:

void set_price(double new_price);

Notice that the mutator is not const-qualified. The purpose of the mutator is to change the object in some way, so we certainly can’t promise not to. Mutators will never have const-qualifiers.

Now implement set_price() in the implementation file as follows:

void Item::set_price(double new_price){
    price = new_price;
}

Testing: Add the following lines to your testing program, then compile and run:

// Change item2's price to 2.49
std::cout << "Original " << item2.get_name()  << '\n';
std::cout << "Price:   " << item2.get_price() << '\n';
item2.set_price(2.49);
std::cout << "Updated  " << item2.get_name()  << '\n';
std::cout << "Price:   " << item2.get_price() << '\n';

Follow the same pattern to create a mutator for quantity. Name it by the same convention (append set_ to the attribute name). Test your program after completing the method implementation.

A Shopping Cart

Now we have a simple Item class; let’s build a shopping cart (container) for items using the std::vector from the C++ Standard Template Library.

  • Add code to your program to declare a std::vector capable of storing values of type Item.
  • Using a sentinel-controlled loop, allow the user to enter the name, price, and quantity of items until the user enters nothing at all for the item’s name (i.e. the loop will stop when the user enters the empty string).
    • Be sure that you read the name in such a way that it is allowed to contain whitespace, i.e. “Beef Jerky”. The stream extraction operator is not a good choice for this…
  • Following the input loop, create a loop to print the values of each item in the shopping cart to the screen in a clean, readable format.

Be sure to test your code as you go — compile early and often!


Labsubmitsinglefile.pngWhen you complete the assignment, zip all source code files and submit the archive as OOP_lab01_inlab.zip.