virtual function.
protected keyword.
October 15, 2002 (14:00) for Tuesdays' lab
October 17, 2002 (14:00) for Thursdays' lab
.tgz package should contain the
following files:
- Canvas.h, Canvas.cpp,
CharScreen.h, CharScreen.cpp,
Shape.h, Shape.cpp
Rectangle.h, Rectangle.cpp,
RectangleFilled.h, RectangleFilled.cpp,
Cross.h, Cross.cpp,
main.cpp, Makefile, main (executable)
In this program, four different graphics objects
(one Rectangle, two RectangleFilled,
and one Cross) are created and user
can move their position and size by commands.
The graphics objects to be created are
Rectangle, RectangleFilled, and Cross.
Rectangle is the same shape as in Lab #6 which consists of
four boundary lines.
RectangleFilled is a Rectangle whose inside
is filled with a specified "fill character".
(Note that actually this follows "is-a" relationship.
RectangleFilled "is a" kind of Rectangle. )
Cross is a cross-like shape.
You are required to create class Shape,
besides class Rectangle,
RectangleFilled, and Cross
(Rectangle class also needs to be modified).
Regarding the output device, use class Canvas and
class CharScreen defined in Lab #6.
The class hieararchy is shown in Figure 1.
The purpose to create class Shape is to bring
extensibility to the program, similarly to class Canvas.
Class Rectangle, RectangleFilled,
and Cross can be defined by inheriting
class Shape and add additional features.
If you want define more graphics objects with new geometric shapes,
you can take advantage of Shape class in the same way.
Class Shape contains attributes such as
centerX, centerY, sizeX
, and sizeY
(these are declared as protected so that they can be
directly accessed in the derived class also) and member functions such as
setSize(), moveTo(), moveDelta() and
draw().
All of these members are common features which any types of
"Shape" such as Rectangle, RectangleFilled, Cross, etc. should have
(moveDelta() has been added to realize "relative" movement. See
the execution result for its behavior).
First, you create class Rectangle and
class Cross by inheriting class Shape.
The ways to draw a Rectangle and Cross should be
different because they have a different geometric shape.
Therefore, you need to define draw() function for each class
which has a different body.
Next, you create class RectangleFilled by inheriting
class Rectangle. In class RectangleFilled,
one additional attribute fillCharacter is added.
To set this attribute, setFillCharacter()
needs to be defined.
Also, draw() function needs to be defined for this class. This
draw() uses fillCharacter to fill the inside in.
A RectangleFilled can be drawn by filling the inside of
the rectangle first and then overlaying the border.
This means, in RectangleFilled::draw(),
first you fill the inside of the rectangle with fillCharacter,
and then you overlay the border by calling draw() function
in class Rectangle.
(Use a notation Rectangle::draw(); to call
draw() defined in Rectangle. "::" is
called scope resolution operator.)
The key point of this program is to store a pointer of a derived class object into a superclass's pointer variable to introduce polymorphism.
In function createShapes(), four different graphics objects
are created, and the pointers to every objects are stored in
array shapes whose elements are pointers to Shape
(This is possible in case that Rectangle,
RectangleFilled and Cross
are all derived from class Shape.
If they are not derived class, the compiler will make a compilation error).
Looking at showShapes(),
it is just saying "draw()" for each element of array shapes.
However, the "draw()" will behave differently depending on what
type of object shapes[i] points to.
If shape[i] points to a Rectangle object,
shape[i]->draw() will call Rectangle::draw(),
and
if shape[i] points to a Cross object,
shape[i]->draw() will call Cross::draw(),
and so on.
Thus, even if the name of the message is same (like draw),
the object that receives the message behaves differently
depending on its object type.
This concept is called polymorphism.
Also, in this program, selection of appropriate functions is
done in runtime. This is called runtime binding
(or dynamic binding).
(Another example of polymorphism with static binding
appears in operator overloading
which will be covered in the later lecture).
Note that, to make a member function polymorphic,
you need to declare draw()
as "virtual" in base class (Shape).
If virtual is not added,
shape[i]->draw() will just call Shape::draw()
which is left as a blank in this program.
Other functions without
virtual such as setSize() etc. are not polymorphic.
Also note that "virtual void show() const { }" in Canvas.h
has been added
and the argument for showShapes() in main.cpp
has been changed to be "const Canvas*". This makes
the show() function polymorphic.
Meaning, when you define a new display device class by
inheriting Canvas class,
you don't have to modify this callee side code.
Thus, in this program, polymorphism is happening in both class hierarchies for display devices and for graphics objects.
A sample execution result of this program is shown in Figure 2.
|
//
// Canvas.h -- Canvas class
//
#ifndef CANVAS_H
#define CANVAS_H
class Canvas {
private:
char* buf; // buffer body
int sizeX; // size of X direction
int sizeY; // size of Y direction
public:
Canvas(int sx, int sy);
~Canvas(void);
int getSizeX() const;
int getSizeY() const;
void putPixel(int x, int y, char pixel) const;
char getPixel(int x, int y) const;
void clear() const;
virtual void show() const { }; // <-- add this
};
#endif // CANVAS_H
|
// // Canvas.cpp -- Canvas class implementation // // (Same as Lab #6) |
// // CharScreen.h -- CharScreen class // // (inherits Canvas class. // display functionality to physical screen device is added) // // (Same as Lab #6) |
// // CharScreen class implementation // // (Same as Lab #6) |
//
// Shape.h -- Shape class
//
#ifndef SHAPE_H
#define SHAPE_H
class Canvas;
class Shape {
protected: // to make it accessible in derived class
double centerX, centerY;
double sizeX, sizeY;
public:
Shape(void);
void setSize(double sx, double sy);
void moveTo(double x, double y);
void moveDelta(double dx, double dy);
virtual void draw(const Canvas* s) const { };
};
#endif
|
//
// Shape.cpp -- Shape class implementation
//
#include "Shape.h"
Shape::Shape(void)
: centerX(0.0), centerY(0.0), sizeX(0.0), sizeY(0.0)
{
}
//
// move to absolute position
//
void Shape::moveTo(double x, double y)
{
centerX = x;
centerY = y;
}
//
// move to relative position
//
void Shape::moveDelta(double dx, double dy)
{
centerX += dx;
centerY += dy;
}
//
// set size
//
void Shape::setSize(double sx, double sy)
{
sizeX = sx;
sizeY = sy;
}
|
//
// Rectangle.h -- Rectangle class
//
#ifndef RECTANGLE_H
#define RECTANGLE_H
#include "Shape.h"
class Canvas;
class Rectangle : public Shape {
private:
public:
void draw(const Canvas* s) const;
};
#endif
|
//
// Rectangle class implementation
//
#include "Rectangle.h"
#include "Canvas.h"
void Rectangle::draw(const Canvas* s) const
{
// (Same as Lab #6)
}
|
//
// RectangleFilled.h -- RectangleFilled class
//
#ifndef RECTANGLE_FILLED_H
#define RECTANGLE_FILLED_H
#include "Rectangle.h"
class Canvas;
class RectangleFilled : public Rectangle {
private:
char fillSymbol; // additional attribute
public:
RectangleFilled() : fillSymbol('/') { } // default fillSymbol = '/'
void setFillSymbol(const char sym);
void draw(const Canvas* s) const;
};
#endif
|
//
// RectangleFilled.cpp -- RectangleFilled class implementation
//
#include "RectangleFilled.h"
#include "Canvas.h"
void RectangleFilled::draw(const Canvas* s) const
{
// Add your code here
}
void RectangleFilled::setFillSymbol(const char sym)
{
// Add your code here
}
|
//
// Cross.h -- Cross class
//
#ifndef CROSS_H
#define CROSS_H
#include "Shape.h"
class Canvas;
class Cross : public Shape {
private:
public:
void draw(const Canvas* s) const;
};
#endif
|
//
// Cross.cpp -- Cross class implementation
//
#include "Cross.h"
#include "Canvas.h"
void Cross::draw(const Canvas* s) const
{
// Add your code here
}
|
//
// main.cpp -- A simple graphics object
//
#include <iostream.h>
#include <stdlib.h>
#include "CharScreen.h"
#include "Rectangle.h"
#include "RectangleFilled.h"
#include "Cross.h"
const int scrSizeX = 70; // screen size X
const int scrSizeY = 20; // screen size Y
const double defaultSizeX = 5.0; // default size X
const double defaultSizeY = 5.0; // default size Y
const int numShapes = 4; // number of shapes
Shape* shapes[numShapes]; // array of pointers to Shape
void createShapes(void)
{
int i = 0;
Rectangle* r;
RectangleFilled* rf;
Cross* c;
r = new Rectangle();
r->setSize(defaultSizeX, defaultSizeY);
r->moveTo(15.0, 5.0);
shapes[i++] = r;
rf = new RectangleFilled();
rf->setSize(defaultSizeX, defaultSizeY);
rf->moveTo(35.0, 5.0);
shapes[i++] = rf;
rf = new RectangleFilled();
rf->setFillSymbol('=');
rf->setSize(defaultSizeX, defaultSizeY);
rf->moveTo(15.0, 13.0);
shapes[i++] = rf;
c = new Cross();
c->setSize(defaultSizeX, defaultSizeY);
c->moveTo(35.0, 13.0);
shapes[i++] = c;
}
//
// Show all shapes
//
void showShapes(const Canvas* s) // <-- different from Lab #6
{
s->clear();
for (int i = 0; i < numShapes; i++) {
shapes[i]->draw(s);
}
s->show();
}
//
// Move all shapes
//
void moveShapes(double dx, double dy)
{
for (int i = 0; i < numShapes; i++) {
shapes[i]->moveDelta(dx, dy);
}
}
//
// Change the size of all shapes
//
void setShapeSize(double sx, double sy)
{
for (int i = 0; i < numShapes; i++) {
shapes[i]->setSize(sx, sy);
}
}
int main()
{
char c;
double x, y;
CharScreen s(scrSizeX, scrSizeY);
createShapes();
for (;;) { // loop forever
showShapes(&s);
cout << "Command: {(m)oveDelta, (s)etSize} <x> <y> ";
cin >> c >> x >> y;
switch (c) {
case 'm':
moveShapes(x, y);
break;
case 's':
setShapeSize(x, y);
break;
default:
return 0;
break;
}
}
}
|
# # Makefile # main : Canvas.o CharScreen.o Shape.o Rectangle.o RectangleFilled.o Cross.o main.o g++ -o main Canvas.o CharScreen.o Shape.o Rectangle.o RectangleFilled.o Cross.o main.o Canvas.o : Canvas.h Canvas.cpp g++ -c Canvas.cpp CharScreen.o : CharScreen.h CharScreen.cpp Canvas.h Canvas.cpp g++ -c CharScreen.cpp Shape.o : Shape.h Shape.cpp CharScreen.h CharScreen.cpp g++ -c Shape.cpp Rectangle.o : Shape.h Shape.cpp Rectangle.h Rectangle.cpp CharScreen.h CharScreen.cpp g++ -c Rectangle.cpp RectangleFilled.o : Shape.h Shape.cpp RectangleFilled.h RectangleFilled.cpp CharScreen.h CharScreen.cpp g++ -c RectangleFilled.cpp Cross.o : Shape.h Shape.cpp Cross.h Cross.cpp CharScreen.h CharScreen.cpp g++ -c Cross.cpp main.o : Canvas.h Canvas.cpp CharScreen.h CharScreen.cpp Shape.h Shape.cpp Rectangle.h Rectangle.cpp RectangleFilled.h RectangleFilled.cpp Cross.h Cross.cpp main.cpp g++ -c main.cpp clean : rm main *.o *~ |
naur[11] main
***** *****
* * *///*
* * *///*
* * *///*
***** *****
***** *
*===* *
*===* *****
*===* *
***** *
Command: {(m)oveDelta, (s)etSize} <x> <y> m 3 3
***** *****
* * *///*
* * *///*
* * *///*
***** *****
***** *
*===* *
*===* *****
*===* *
***** *
Command: {(m)oveDelta, (s)etSize} <x> <y> m -2 -3
***** *****
* * *///*
* * *///*
* * *///*
***** *****
***** *
*===* *
*===* *****
*===* *
***** *
Command: {(m)oveDelta, (s)etSize} <x> <y> s 7 5
******* *******
* * */////*
* * */////*
* * */////*
******* *******
******* *
*=====* *
*=====* *******
*=====* *
******* *
Command: {(m)oveDelta, (s)etSize} <x> <y> s 5 3
***** *****
* * *///*
***** *****
***** *
*===* *****
***** *
Command: {(m)oveDelta, (s)etSize} <x> <y>
|