File:
point.h
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
#ifndef _point_h_
#define _point_h_
/*
* Class to represent a single point.
*/
class Point {
public:
// Construct a point with the indicated coordinates. Note
// use fo a constructor list instead of assignments in the body.
Point(double x = 0.0, double y = 0.0): m_x(x), m_y(y) { }
// Return either ordinate.
double abscissa() const { return m_x; }
double ordinate() const { return m_y; }
// Print a string on the indicated stream. cout is type of
// std::ostream (descendent class, actually)
void print(std::ostream &strm) const
{
strm << "(" << m_x << "," << m_y << ")";
}
// Add or subtract two points
Point plus(const Point &b) const
{
return Point(m_x + b.m_x, m_y + b.m_y);
}
Point minus(const Point &b) const
{
return Point(m_x - b.m_x, m_y - b.m_y);
}
// Negate the point
Point negate() const
{
return Point(-m_x, -m_y);
}
// Move the point by a given amount
void move(double dx, double dy)
{
m_x += dx;
m_y += dy;
}
// Return the distance between points
double distance(const Point &b) const
{
double xd = m_x - b.m_x;
double yd = m_y - b.m_y;
return sqrt(xd*xd + yd*yd);
}
// Return the area of the rectangle whose corners are the
// current point and the argument.
double area(const Point &b) const
{
return abs(m_x - b.m_x) * abs(m_y - b.m_y);
}
private:
// The coordinates of the point.
double m_x, m_y;
};
/*
* Print the point. When you have Point p, and you say
* cout << p
* the compiler translates it to
* p.operator<<(cout)
*/
static ostream & operator<<(std::ostream &strm, const Point &p) {
p.print(strm);
return strm;
}
#endif
This is a class created in a file by itself. The file type is
.h by convention, though this is not a rule of the language.
It is usual to name the file after the class, but there is no
requirement about that, either.
There is an odd notation near the top:
#ifndef _point_h_
#define _point_h_
and at the bottom
#endif
This is a compile-time if statement, obeyed by the compiler during
translation (rather thah having the compiler translate it to be
obeyed at run time). The first tests of the symbol
_point_h_ is undefined. If it is undefined, then compiler
defines it, then reads the rest of the file. If it is defined, the
compiler skips to the matching
#endif, which effectively
skips the whole file. The purpose of all this is to prevent the
class defintion from being read more than once.
This can become a problem in complex, multi-file programs.
For now, just follow the pattern when you create the class.
Choose a symbol which looks like the file name.
Another other new thing in this example is the constructor syntax.
The constructor sets the initial values of the variables
m_x and m_y using an initializer list. The syntax is as
shown, giving the name of an object variable with its value after in
parentheses. The list is intoduced by a colon after the constructor
argument list, and
multiple initializations are separated by commas.
Some fun facts about initializer lists:
- Initializer lists are allowed only on constructors.
- The initializations are performed in the order the variables are
declared in the class, not
the order given in the list.
- In most cases of setting object data, either initializer list or
assignments work fine. (There are a few odd exceptions; see below.)
- Technically, initializer list setting
may save work because otherwise,
the declared object variable will be initialized with
its default, then reset by the assignment in the constructor. The
initializer list replaces the default initialization.
On the off chance that anyone is still reading this note, the case
where it matters is when the object variable can be initialized,
but assignment fails. Notably:
- Variables declared const, since they are initialized to
their constant value, but then may not be assigned.
- Reference variables, for which initialization establishes
what they are aliasing and assignment is applied to the aliased variable.
So assignment is very different from initialization.
Finally, the point can be printed with the << operator.
In Java, this is done by implementing toString() to convert the object to
a string, which System.out.print and friends use it to convert the object
to a string, then print it. In C++, you create an operator<<.
- Create a function named operator<< with the signature shown.
(It's at the bottom of the file.)
- When an object p declared as a Point is used as
cout << p,
the compiler translates it to p.operator<<(cout), so it
runs the function.
- The function shown is declared static. That is needed when the
function is placed in the header file. I will explain why if you ask, but
mostly just remember to do it.
- The operator function works by calling a print method in the class.
This is usual, but not required unless printing needs data which is not available
through extractors. In this example, the operator function could be written
without there being a print method.
- The std::cout object is an std::ostream by inheritance. You should
not about inheritance from Java, but we have not talked about its weirdness in C++.
For our purposes, always pass and return them as references, as shown.
- The operator<< function returns the stream so that chaining works.
cout << "The corners are " << p << " and " << q << endl;
becomes
operator<<(operator<<(operator<<(operator<<(operator<<(cout,"The corners are "),p)," and "),q),endl);
So each level passes the cout on to the next. (And now you can't unsee it.)