Problem overview
A class bundles some values with the questions you can ask about them. Here, Rectangle remembers a width and a height and answers three questions: area() is width times height, perimeter() is twice the width plus twice the height, and isSquare() tells whether the two sizes are equal.
For a 3 by 4 rectangle the area is 12, the perimeter is 3 + 4 + 3 + 4 = 14, and it is not a square. For 5 by 5 the area is 25, the perimeter is 20, and it is a square. Each object keeps its own sizes, so two rectangles never interfere with each other: a 2 by 10 and a 10 by 2 both have area 20 and perimeter 24.
Approach
The class needs only the two sizes, so it stores two int fields and every method works from them. Nothing has to be looped over or looked up, so each method runs in constant time.
Step by step
- Declare the fields. Under
public:, writeint width;andint height;. EveryRectangleobject gets its own copy of both. - Store the constructor's parameters. The parameters have the same names as the fields, so inside the constructor plain
widthmeans the parameter andthis->widthmeans the field of the object being built. Writethis->width = width;and the same for the height. - area returns
width * height. Inside a method,widthandheightare the fields of the object the method was called on. - perimeter returns
2 * (width + height). The parentheses make the addition happen first. - isSquare returns
width == height. A comparison is already abool, so noifis needed.
The solution in C++
class Rectangle {
public:
int width;
int height;
Rectangle(int width, int height) {
this->width = width;
this->height = height;
}
int area() {
return width * height;
}
int perimeter() {
return 2 * (width + height);
}
bool isSquare() {
return width == height;
}
};
Using the class looks like this:
#include <iostream>
using namespace std;
// the class above goes here
int main() {
cout << boolalpha;
Rectangle a(3, 4);
Rectangle b(5, 5);
cout << a.area() << " " << a.perimeter() << " " << a.isSquare() << endl;
cout << b.area() << " " << b.perimeter() << " " << b.isSquare() << endl;
Rectangle c(2, 10);
Rectangle d(10, 2);
cout << c.area() << " " << c.perimeter() << " " << d.area() << " " << d.perimeter() << endl;
}
It prints:
12 14 false
25 20 true
20 24 20 24
The lesson's tests print the booleans as True and False; C++'s cout prints 1 and 0 unless you add boolalpha, as the example above does.
Details worth knowing
- The class ends with a semicolon.
};after the closing brace is required in C++, and forgetting it gives a confusing compiler error. public:matters. Members of aclassare private by default, so withoutpublic:the code that creates the objects couldn't call the constructor or the methods.this->is a pointer access.thispoints to the current object, so its fields are reached with->. Writingwidth = width;would assign the parameter to itself and leave the field uninitialised.- Methods that only read can be
const. Writingint area() constpromises the method won't change the object, and the compiler enforces it. The lesson's code leavesconstout to keep the first class simple, and both forms work with the lesson's tests. Marking read-only methodsconstbecomes a habit worth forming once the basics are comfortable.
What each object remembers
The last two rectangles in the example are worth a second look. c is built with width 2 and height 10, and d with width 10 and height 2. They are two separate objects: changing one would never touch the other, because each carries its own width and height. Their areas and perimeters match because multiplication and addition don't care about order, not because the objects share anything. Turning a rectangle on its side changes neither number.
The same holds for a square. Rectangle(5, 5) is not a different kind of object: it is a rectangle whose two fields happen to be equal, which is exactly what isSquare() checks.
Another way: the initializer list
C++ has a second way to fill in fields, written between the constructor's parameter list and its body. Each field is followed by the value for it in parentheses, and because the parameter and the field may share a name, width(width) reads naturally: the field goes outside the parentheses, the parameter inside.
#include <iostream>
using namespace std;
class Rect {
public:
int width;
int height;
Rect(int width, int height) : width(width), height(height) {}
int area() { return width * height; }
};
int main() {
Rect r(3, 4);
cout << r.width << " " << r.height << " " << r.area() << endl;
}
This prints:
3 4 12
Both forms give the same objects. The lesson's steps use the assignments inside the body, so the solution above does too. The initializer list becomes more useful later, for example when a parent class's constructor has to be called, as in the Employee and Manager lesson.
Testing
Try a few more inputs in a program of your own and compare with what you expect by hand. Rectangle(1, 1) should give area 1, perimeter 4 and a square. Rectangle(1, 10000) should give area 10000 and perimeter 20002. With the largest sizes the lesson allows, 10000 by 10000, the area is 100000000, which still fits comfortably in an int.
Common mistakes
- Computing the perimeter as
2 * width + height, which doubles only one side. - Returning from
isSquarewith a single=, which assigns instead of comparing. - Leaving out the
returnin a method that returns a value: the method then has undefined behaviour in C++.
