CMPT 225 Lab 2: C++ Classes


Circles

Write a complete C++ class to represent a circle in two-dimensional space.  Your class should consist of a header (circle.h) and an implementation (circle.cpp) file as described below. The circle class should have the folllowing (private) attributes and (public) methods:

Attributes

Methods


Writing a C++ Class

.h and .cpp Files

C++ classes are made up of a header file and an implementation file.  Both files should have the same name except that the header file has a .h extension while the implementation has a .cpp file.  The header file contains the class interface, and the .cpp file contains the implementation.

The header file consists of the class name, and the name (and type) of the member variables and the header for each of the methods.  The .cpp file consists of the definition for each of the class methods.

Public or Private?

Class member variables and methods should be specified as being either private or public.  Private variables or methods can only be accessed from within the class, whereas public variables and methods can be accessed from outside the class.  There are a couple of good general design principles to follow when deciding whether or not to make a method or variable public:

Constructors and Destructors

Every class requires a constructor that creates new objects of that class.  A class will often have multiple constructors that build new objects in slightly different ways.  A constructor is a method that has exactly the same name as the class and has no return type, it is responsible for setting the initial values of the member variables of an object.

C++ classes require destructors.  A destructor is responsible for de-allocating any dynamic memory that an object uses.  If you don't write a destructor for a class a default one is created for you.  It is OK to rely on the default destructor as your class does not use any dynamic memory, if it does, then you must write a destructor.

We'll discuss constructors and destructors at greater length in class.

Syntax

It's easier to show the syntax with a simple example.  Here is a class that represents a rectangle, it will have the following member variables and methods:

Here is the header file, C++ keywords are in bold.

// Rectangle.h file

// Definition of a rectangle class
class Rectangle
{
public: //everything that follows is public
	//Default constructor
	Rectangle(); //the default constructor has no parameters
	// Constructor to create a new rectangle with the given values
	Rectangle(int w, int h);
	
	// Setters that change the values of the attributes
	void setWidth(int w);
	void setHeight(int h);
	
	// Getters that return information about the rectangle, note the const at the end of the method
	// this guarantees that the method cannot alter the member variables
	int getWidth() const;
	int getHeight() const;
	int getArea() const;	
	
	// Display method that prints the rectangle's height and width
	void displayRectangle() const;

private: //everything that follows is private and cannot be "seen" from outside the class
	// Some classes have private methods, this one doesn't!
	int width;
	int height;
}; //Rectangle - note the ";" - don't forget it!
// End of the header file
And here is the implementation file:
// Rectangle.cpp file
#include <iostream> // As we need to print data
#include "Rectangle.h" // The header file for the class - we need this!

using namespace std;

/* 
 * Now follows each of the method implementations.  The <class>:: that precedes each method identifies
 * that the function belongs to the class.  If it is omitted the compiler will attempt to create a separate
 * function, which may or may not be successful.
 */

//Default constructor
Rectangle::Rectangle(){
	width = 1;
	height = 1;
}//Rectangle()

// Constructor to create a new rectangle with the given values
Rectangle::Rectangle(int w, int h)
{
	if (w > 0)
		width = w; // don't need {}s if there is only one line in the body
	else
		width = 1;

	if (h > 0){
		height = h; // but you can use them if you want
	} else{
		height = 1;
	}
}//Rectangle(int, int)

// Setters that change the values of the attributes
void Rectangle::setWidth(int w)
{
	if (w > 0)
		width = w;
	else
		width = 1;
}//setWidth

void Rectangle::setHeight(int h)
{
	if (h > 0)
		height = h;
	else
		height = 1;
}//setHeight

// Getters that return information about the rectangle
int Rectangle::getWidth() const
{
	return width;
}//getWidth

int Rectangle::getHeight() const{
	return height;
}//getHeight

int Rectangle::getArea() const	
{
	return width * height;
}//getArea

// Display method that prints the rectangle's height and width
void Rectangle::displayRectangle() const
{
	cout << endl << "width = " << getWidth();
	cout << ", height = " << getHeight() << endl;
}//displayRectangle

// End of the implementation file

Note

There is (a lot) more to C++ classes than described above but this is enough to get you started.



Compiling and Running

If you actually want to use and test your circle class, you will need a main function which you will compile to use with it. This program is often known as a driver program.

As an example, for testing your circle class I have provided 2 driver programs. Download this zip file and save and unzip it in your lab2 directory. This zipfile contains testcirc1.cpp and testcirc2.cpp, the two driver programs. Each one creates some circles and tests methods. testcirc1.cpp tests the getters and setters. testcirc2.cpp tests intersect.

Compiling the Driver program

Compile these using the following g++ commands:

g++ -o testcirc1 circle.cpp testcirc1.cpp
g++ -o testcirc2 circle.cpp testcirc2.cpp

There is also a test script (again test.py), which you can run:

uname@hostname: ~$ ./test.py
If you have correctly built executables testcirc1 and testcirc2 that solve this lab you will see:
Running test 1... passed
Running test 2... passed
Passed 2 of 2 tests.

If you have passed at least 1 of 2 tests, please ask a TA to take a look at this output, and receive your 1 mark for this lab.

You should also complete the rest of this lab below, but this can be done as homework if you do not have time to complete it in your lab time slot.


Object files

If you wanted to compile your circle class on its own, you could do so. But wait, there's no main function?! You can compile your circle class using the -c option in g++:

uname@hostname: ~$ g++ -c circle.cpp
uname@hostname: ~$ ls
circle.cpp    circle.h      circle.o
But wait, what does it mean to compile a class with no main function? Why would you want to do such a thing? What is that circle.o file that was just created?

circle.o is called an object file. One way to think about object files is that they contain machine instructions for the functions in a class -- they specify how to run the functions. However, nobody is using these functions yet, since there is no main function. So it's as if you have compiled code which is just a set of functions, without any specification of in which order these functions should be called, and on what data.

Why would you want to do this? (1) To make sure some part of your code compiles. (2) To reuse the compilation -- don't need to recompile circle.cpp if you change testcirc.cpp. This may seem trivial in a small example like this one, but makes a big difference in more complex software with many classes.

For example, you can compile testcirc.cpp using the circle.o object file:

uname@hostname: ~$ g++ -o test testcirc1.cpp circle.o
Technically, this process involves linking, in which the compiled machine instructions in circle.o are linked into the new executable test, rather than being recompiled.

Perhaps you're thinking this sounds like a pain. First I ran a g++ command on circle.cpp, then I had to type in another long g++ command to compile testcirc.cpp. It would be great if there were some way to avoid this. Of course, there is.

Makefiles

This is where makefiles come in. Makefiles are like recipes that specify all the components to some software. I provided a Makefile in lab2-test.zip which you downloaded. By convention, it is in a file named Makefile. Type the following
uname@hostname: ~$ make clean
This command will remove any .o files as well as the executables testcirc1 and testcirc2 you may have already created. Now try:
uname@hostname: ~$ make testcirc1
If you have code that properly compiles, you should see:
g++ -c testcirc1.cpp
g++ -c circle.cpp
g++ testcirc1.o circle.o -o testcirc1
Magic! The recipe in Makefile specifies how to build testcirc1. The executable testcirc1 should now exist.

Now try making a small change to testcirc1.cpp (anything at all -- just try adding an empty line in the file). If you again run make testcirc1, you should see:

uname@hostname: ~$ make testcirc1
g++ -c testcirc1.cpp
g++ testcirc1.o circle.o -o testcirc1
Note that circle.cpp was not recompiled.

Makefiles also have a default, called "all." If you run "make all" or just "make", it will build everything specified for all. In this case, this is testcirc1 and testcirc2.

uname@hostname: ~$ make clean
rm -f testcirc1 testcirc2 *.o
uname@hostname: ~$ make
g++ -c testcirc1.cpp
g++ -c circle.cpp
g++ testcirc1.o circle.o -o testcirc1
g++ -c testcirc2.cpp
g++ testcirc2.o circle.o -o testcirc2
You will now have both of your driver programs, and can run test.py on them.

Makefiles allow you to easily recompile your code after making changes, and while debugging. So from now on, you don't need to keep typing in g++ commands, you can instead just run "make".

Of course, there is a lot more to makefiles than what I've written here. In this course, I will never ask you to create/modify makefiles. I will always provide them to you if they are needed. In fact, integrated development environments (IDEs) such as MS Visual Studio or Eclipse are essentially doing this in their "projects." But as a computer scientist, it's important for you to know what's happening "under the hood."




Back to the CMPT 225 homepage.