=Shapes=
==Problem==
* You’re making an app that allows kids to create pictures using rectangles and circles of different sizes and colors.
* A pictures is now stored as a file with one shape per line
* Input format for a rectangle is (unit is pixels)
** color Rectangle height width x y
* Input format for a circle is
** color Circle radius x y
* All shapes should have an ID (in case two shapes are otherwise identical)
===Problem-solving===
* It helps tremendously to draw the inheritance hierarchy for this problem and place data members and functions in the hierarchy before try to implement the solution
===Part I===
* Your app will
** read in a file of shapes
** create an object for each shape
** print a summary of each shape
* For starters, create the base class and create objects of this class
* Your printed summary should include the color and unique ID. For example, for a rectangle:
** ID#0, Yellow
* For a circle:
** ID#1, Blue
===Part II===
* Next create derived classes and create objects of these classes instead of the base class
* To the printed summary, add type and dimensions. For example, for a rectangle:
** Rectangle (25 x 30), ID#0, Yellow
* For a circle:
** Circle (radius 12), ID#1, Blue
* Let the user select a color to change all of the shapes to before they are printed to the console
===Part III===
* Finally, add functions to find area and edge length for a shape
* To the printed summary, add area and edge length. For example, for a rectangle:
** Rectangle (25 x 30), ID#0, Yellow – area:750 – edge:110
* For a circle:
** Circle (radius 12), ID#1, Blue – area:452.39 – edge:75.4
===Unimplemented Tasks===
====Scaling====
* What if we wanted to also be able to scale an object’s size?
* Let the user scale all objects before printing them to the console
====Moving====
* What if we wanted to include x, y coordinates of the shape’s center in the shape summary?
* What if we wanted to be able to move a shape (positive y meaning up and positive x meaning right)?
* Allow the user to move all shapes’ positions by a certain amount before printing them to the console
==Solution==
===Source.cpp===
/*
Test case classes (you should provide specific test cases)
Test file should include both Circle and a Rectangle
Test file should include shapes with dimension values of 0
Test file should include shapes of many different colors and sizes
*/
#include
#include
#include
#include
#include
#include "Circle.h"
#include "Rectangle.h"
#include "Shape.h"
using namespace std;
void create_objects_from_file(string filename, vector & shapes);
int main()
{
vector shapes;
// 1. READ IN A FILE AND CREATE AN OBJECT FOR EACH SHAPE
cout << "Filename:" << endl;
string filename;
getline(cin, filename);
// static helper function
create_objects_from_file(filename, shapes);
// Let the user select a color to change all of the shapes to before they are printed to the console
string user_fav_color;
cout << "Fav color: ";
getline(cin, user_fav_color);
// 2. PRINT A SUMMARY OF THE SHAPES (to_string() is fine)
for (int i = 0; i < shapes.size(); i++)
{
// All derived classes have access to the base class set_color() function
shapes[i]->set_color(user_fav_color);
// Each derived class implements and executes these differently. HERE IS POLYMORPHISM
cout << shapes[i]->to_string() << endl;
cout << "\tAREA:" << shapes[i]->area() << endl;
cout << "\tEDGE:" << shapes[i]->edge_length() << endl;
}
system("pause");
return 0;
}
/*
Function to load contents of a file and create shape objects depending on file contents
@param filename name of file to load shapes from
@param shapes vector into which to load new shape objects
*/
void create_objects_from_file(string filename, vector & shapes)
{
ifstream in_file;
in_file.open(filename);
// Note: we don't test if the filename is valid or not
string line;
// loop continues as long as another line can be read
while (getline(in_file, line))
{
// cout << "LINE: " << line << endl;
// use an istringstream when trying to read different data types from a string
istringstream strm(line);
string color;
string shape_type;
strm >> color;
strm >> shape_type;
// This is a pointer to the shape we are going to create for the given line
Shape * new_shape = NULL;
if (shape_type == "Rectangle")
{
int width, height;
strm >> width >> height;
// The substitution principle allows us to put an object of a derived class
// where an object of the base class is expected
new_shape = new Rectangle(color, width, height);
}
else if (shape_type == "Circle")
{
int radius;
strm >> radius;
// The substitution principle allows us to put an object of a derived class
// where an object of the base class is expected
new_shape = new Circle(color, radius);
}
// cout << "shape created with color: " << new_shape->get_color() << endl;
// this is NULL if the shape_type was neither Rectangle nor Circle
if (new_shape != NULL)
{
shapes.push_back(new_shape);
}
}
}
===Shape.h===
#include
using namespace std;
#pragma once
class Shape
{
public:
// CONSTRUCTOR
/*
Default constructor for a Shape object
*/
Shape();
// OTHER MEMBER FUNCTIONS
/*
@return color of *this* Shape object
*/
string get_color() const;
/*
@param new_color color to be set as *this* Shape object's color
*/
void set_color(string new_color);
/*
@return the unique id for this Shape
*/
int get_id() const;
/*
Function to compute shape's area. Note that this can't be computed for a generic shape
hence why it is a pure virtual function
@return the area of *this* Shape object
*/
// a Pure Virtual Function must, by definition, be overwritten by any derived class
virtual double area() const = 0;
/*
Function to compute shape's perimeter edge length. Note that this can't be computed for a generic shape
hence why it is a pure virtual function
@returns the perimeter of *this* Shape object
*/
// a Pure Virtual Function must, by definition, be overwritten by any derived class
virtual double edge_length() const = 0;
/*
@return a string representation of *this* Shape object
*/
virtual string to_string() const;
private:
// DATA MEMBERS
string color;
int id;
// static variables exist once for all instances of the class
static int next_id;
};
===Shape.cpp===
Note that even though Shape is an abstract class (by virtue of having a pure virtual function), there is still an implementation file.
#include
#include "Shape.h"
// initialize our static variable
int Shape::next_id = 0;
Shape::Shape()
{
color = "";
id = next_id; // static variable at work
next_id++;
}
string Shape::get_color() const
{
return color;
}
void Shape::set_color(string new_color)
{
color = new_color;
}
int Shape::get_id() const
{
return id;
}
string Shape::to_string() const
{
// use an ostringstream when creating a string from different data types
ostringstream strm;
strm << "ID# " << id << ", " << color;
return strm.str();
}
===Circle.h===
#pragma once
// to BE a Shape, we must know what a Shape is
#include "Shape.h"
// Here is where we define that a Circle IS-A Shape and inherits all of its members and functions
class Circle : public Shape
{
public:
// CONSTRUCTOR
/*
Constructor to construct a new Circle object
@param _color color of new Circle object
@param _radius radius of the new Circle object
*/
Circle(string _color, int _radius);
// MEMBER FUNCTION
/*
@return a string representation of *this* Circle object
*/
virtual string to_string() const;
/*
@return area of *this* Circle object
*/
virtual double area() const;
/*
@return perimeter edge length of *this* Circle object
*/
virtual double edge_length() const;
private:
// DATA MEMBERS
int radius;
// Note that this inherits all other data members and member functions that it needs from the base class
};
===Circle.cpp===
#include
#include "Circle.h"
Circle::Circle(string _color, int _radius)
:Shape(), radius(_radius)
// Everything after ':' is the initializer list
// The Shape() constructor is called by default but we include it for emphasis
{
set_color(_color);
}
string Circle::to_string() const
{
// use an ostringstream when creating a string from different data types
ostringstream strm;
strm << "Circle (radius " << radius << "), "
<< Shape::to_string();
return strm.str();
}
double Circle::area() const
{
return 3.14159 * radius * radius;
}
double Circle::edge_length() const
{
return 2 * 3.14159 * radius;
}
===Rectangle.h===
#pragma once
// to BE a Shape, we must know what a Shape is
#include "Shape.h"
// Here is where we define that a Rectangle IS-A Shape and inherits all of its members and functions
class Rectangle : public Shape
{
public:
// CONSTRUCTOR
/*
Constructor to construct a new Rectangle object
@param _color color of new Rectangle object
@param _radius radius of the new Rectangle object
*/
Rectangle(string _color, int _width, int _height);
// MEMBER FUNCTION
/*
@return a string representation of *this* Rectangle object
*/
virtual string to_string() const;
/*
@return area of *this* Rectangle object
*/
virtual double area() const;
/*
@return perimeter edge length of *this* Rectangle object
*/
virtual double edge_length() const;
private:
int width;
int height;
// Note that this inherits all other data members and member functions that it needs from the base class
};
===Rectangle.cpp===
#include
#include "Rectangle.h"
Rectangle::Rectangle(string _color, int _width, int _height)
:Shape(), width(_width), height(_height)
// Everything after ':' is the initializer list
// The Shape() constructor is called by default but we include it for emphasis
{
set_color(_color);
}
string Rectangle::to_string() const
{
// use an ostringstream when creating a string from different data types
ostringstream strm;
strm << "Rectangle (" << width << " x " << height << "), "
<< Shape::to_string();
return strm.str();
}
double Rectangle::area() const
{
return width * height;
}
double Rectangle::edge_length() const
{
return 2 * width + 2 * height;
}
===shapes.txt===
Here is a possible input file
Red Circle 38 12 22
Blue Circle 18 56 79
Green Rectangle 22 42 190 76
Yellow Circle 78 78 190
Black Circle 1 78 78
Supercalifragilisticexpialidocious this is the only error we test
Grey Circle 3 78 78
Black Circle 5 78 78
Green Rectangle 10 10 100 100
Pink Circle 10 50 50