Homework 27: Better Box
Concepts
Focus on three main concepts in this assignment: defining class constants, the difference between member variables vs. member functions and the importance of private and public class members.
Member Functions
Consider a program that works with photographs. Photos have a height and a width. We might model such a class like this:
public:
Photo();
int height;
int width;
};
Since photos have a height and a width, they also have an area. What is area? It's a value generated by multiplying a Photo object's height and width together. How might we enable our Photo class such that a Photo object is able to tell us its area? In other words, what do we need to do in order to be able to write code like the following?
cout << "The area of Photo p is" << p.area() << endl;
To support the ability to write p.area()
, we must define area as a member function, or "a function that belongs to objects of a class." We do this in two steps.
- Declare the function prototype in the header file
- Define the function implementation in the implementation file
In other words, in the header file photo.h, we should add:
public:
Photo();
int area();
int height;
int width;
};
And in the implementation file, photo.cpp, we should add:
return height * width;
}
The function prototype in photo.h tells the machine, "Hey, Photo objects have a member function called area that returns an int." The function definition in photo.cpp tells the machine, "Hey machine, the definition of the Photo member function area() I told you about in photo.h works like this."
Look at the body of the area function above. Notice how the function, since it is declared "inside" the class, has direct access to everything else inside the class, such as height and width.
This is so important, we'd like you to read it again. Look at the body of the area function above. Notice how the function, since it is declared "inside" the class, has direct access to everything else inside the class, such as height and width.
Remember a couple assignments ago where you accessed a Person p
's age with p.age
whereas you accessed a Book b
's title attribute with b.getTitle()
? This is because a Person's age
was defined as a member variable, whereas a Book's title was accessed via the member function getTitle()
.
When should you use member variables instead of member functions? Today you'll see one reason why you should tend to use member functions.
Public vs. Private Members
In your last assignment, you defined a simple box class that used the public:
syntax to tell the machine that the height, width and depth attributes were "accessible from outside the object." For example:
photo.h
public:
Photo();
int area();
int height;
int width;
};
photo.cpp
height = 8;
width = 5;
}
That public:
thing you see above in the header file tells the machine, "Hey computer, when a programmer instantiates a Photo object, they should be allowed to access the values of height and width." In other words, this mechanism allows you to do the following:
cout << myFamily.height; // accessing the value of height
myFamily.height = 20; // assigning a value to height
Do you see how you can access the height attribute of the Photo myFamily
and do you also see how you can assign new values to those data members?
More importantly, do you see the problem? Look at this:
Uh oh! Because the height attribute is public, any programmer using Photo objects can assign any integer value to that member variable, including ones that don't make sense, such as negative heights.
"Big whoop," you say, "I read that we can make data members private." And you'd be right (and deserve a gold star for doing the required reading). Let's change our class so that arbitrary values can't be assigned to data members.
photo.h
public:
Photo();
int area();
private:
int height;
int width;
};
Ahh, there, now no one can assign values to Photo objects:
However, now you've introduced another problem. By declaring the data members to be private
, you've disabled all access to member variables! This means that you can no longer do this:
The height attribute is declared to be private
so there is no direct access to the attribute. "I hate C++!" you say? Don't worry, here's how you can control access to data members: define member functions we call "accessor methods."
Accessor Methods, aka Getters and Setters
First, don't be fooled by the word "method" -- it really just means "function." If you hang around the object-oriented clique, you'll hear people talk about "methods" but they really just mean "functions that belong to instances of a class" or "functions that belong to objects."
Let us restate the problem above: we do not want to allow direct access to member variables because invalid values can be assigned to them, causing the world to end (or, less dramatically, causing our program to be incorrect). We can declare a data member to be private, but then the programmer loses the ability to read the value of a data member as well.
The solution? Let us define two member functions that we can use to manage reading and writing to data members.
photo.h
public:
Photo();
int area();
int getHeight();
int getWidth();
void setHeight(int h);
void setWidth(int w);
private:
int height;
int width;
};
photo.cpp
height = 8;
width = 5;
}
int Photo::getHeight() {
return height;
}
int Photo::getWidth() {
return width;
}
void Photo::setHeight(int h) {
if (h > 0) {
height = h;
}
}
void Photo::setWidth(int w) {
if (width > 0){
width = w;
}
}
If you were to implement the code above, your programs could then do the following:
p.setHeight(20); // height is now 20
p.setHeight(-69); // height is unchanged
Spend time looking at the code in photo.cpp above to see what those functions do. Notice how getHeight and getWidth are very similar. Almost every "getter" function you write will follow this pattern.
In order to understand how this works, you should realize that member functions have full access to everything declared inside the class (they aren't subject to the public/private rules).
Things That Are "Constant" Across All Instances of a Class
Let's say you went to the planet Womanz, where all living beings are female. (The planet Womanz is way better than Earth). If you created a Woman class to model beings from planet Womanz, how would you model the gender of Woman objects?
In this case, gender will always be "female" for every Woman instance. You might think of gender as being "constant" across all instances of Woman.
How do we tell the machine about a fact that is "universal" or should not change for every instance of a class? By declaring a class constant using a particular syntax.
public:
static const bool GENDER = true; // let true mean female, false mean male
};
To access the constant, you must remember that it is declared "inside" the class. Hence, from within the class you can access GENDER
directly. For example, consider an isFemale() member function:
return GENDER;
}
A bit of a silly function, but you see how the member function has direct access to the variable GENDER
? Again, this is because member functions have full access to everything declared inside the class.
In contrast, from outside the class, you must use the :: (scope resolution) operator. Here's an example.
By typing Woman::GENDER
you're telling the computer, "Give me the value of the GENDER variable that is static (unchanging) for the class."
The keyword static
means different things in different contexts. For now, just remember that when you want to define a constant in a class, you must use static const [datatype] [name] = value
.
Remember, the variable is an attribute of the class not instances of the class (objects). So the following isn't correct:
bool areWomenFemale = adaLovelace::GENDER;
areWomenFemale = adaLovelace.GENDER;
Classes can have constants that are "universal" for all instances. But such a constant is an attribute of the class, not instances of the class. In the above incorrect code, an attempt is made at accessing the GENDER attribute of a Woman object. Again, the class constant is accessed via the class:
Instructions
Create an improved version of your ever-powerful and world-changing Box class such that it passes the test suite provided. Explore the test functions to see the API that your Box class must support. For example, every Box instance must have a height, width and depth, but these should be private
. You box will need getters and setters for all attributes. You must define a member function called volume
. Lastly, you must define a class variable (using static const
) called DEFAULT_DIMENSION.
An important part of this assignment is reading code, so be sure to take a look at test.cpp
to see how your Box must behave. When correct, your Box should pass all the unit tests, and you should not see "FAILED" printed on the console.
Requirements and Rubric
A friendly message from The Terminator, our grading program
Hello ag *bzzzt* again. I will check for the following:
Your program must not print "FAILED" in order to receive full credit.
You must not modify main.cpp (except adding your name), test.cpp or test.h.
Your class definitions should be in box.h and box.cpp, as provided.
I will *bzzzt* try to break your Box with my tzzzzests. Can you *bzzt* defeat me?
This work is worth 180 points.
Requirement | Points | Notes |
---|---|---|
Place your name in the comment header in main.cpp | 5 | |
Correct submission of src directory as a .zip file. | 5 | |
Declares Box class in box.h | 10 | |
Defines Box constructors and member functions in box.cpp | 10 | |
Passes all tests (10 pts each) | 150 |
Concepts Exercised: objects, classes, unit testing, reading code, member functions, class variables, scope, accessor methods
© 2011 Yong Joseph Bakos.