Виртуальные функции "Форма" Назначение
Я не могу заставить свой класс цилиндров правильно выполнять функции печати и объема. Вот инструкции для назначения: Разработайте класс с именем Shape, который является абстрактным базовым классом. Shape имеет две чисто виртуальные функции, printShapeName и print.
Shape содержит две другие виртуальные функции, area и volume, каждая из которых имеет реализацию по умолчанию, которая возвращает нулевое значение.
Класс Point наследует эти реализации (площадь и объем точки равны нулю) от Shape. Точка имеет координаты x и y для закрытых членов.
Класс Circle является производным от Point с публичным наследованием. Объем Circle равен 0.0, поэтому объем функции-члена базового класса не переопределяется. Круг имеет ненулевую площадь, поэтому функция площади в этом классе переопределяется. Напишите get и установите функции для возврата и назначения нового радиуса для круга.
Класс Cylinder является производным от Circle с публичным наследованием. Цилиндр имеет площадь и объем, отличные от таковых в Круге, поэтому функции площади и объема в этом классе переопределяются. Напишите get и установите функции для возврата высоты и назначения новой высоты.
Создайте одну точку, один круг и один цилиндр, затем напечатайте результаты.
//
// Shape.hpp
// HW6_VirtualFunctions
//
// Created by Aviv Fedida on 11/25/17.
// Copyright © 2017 Aviv Fedida. All rights reserved.
//
#ifndef Shape_hpp
#define Shape_hpp
#include <stdio.h>
#include <iostream>
#include <cmath>
using namespace std;
class Shape {
protected: // protected members
double width, height, radius, pi, x, y;
public:
void setWidth(double a); // prototype for width setter
void setHeight(double b); // prototype for height setter
void setX(double c);
void setY(double d);
void setRad(double r);
void setPi(double p);
double getWidth() { // get width to return only
return width;
}
double getHeight() { // get height to return only
return height;
}
double getX() {
return x;
}
double getY() {
return y;
}
double getRad() {
return radius;
}
double getPi() {
return pi;
}
// Create public virtual functions
virtual void printShapeName() = 0; // pure virtual for printing shape's name
virtual void print(double a, double b) = 0; // pure virtual print function
virtual double area() { // virtual area function returns default null
return 0;
}
virtual double volume() { // virtual default volume function returns null
return 0;
}
}; // END BASE CLASS -------------------------------------------------------------------------------------
class Point: public Shape {
// x & y coordinates needed for a point
public:
// Prototypes for print & set functions
void printShapeName();
void print(double a, double b);
}; // end first derived class ------------------------------------------------------------------------
class Circle: public Point {
public:
// Protoypes for print functions
void printShapeName();
void print(double r, double p);
// Prototypes for area & volume functions
double area(double r, double p);
}; // end second derived class ----------------------------------------------------------------------------
class Cylinder: public Circle {
public:
double area(double r, double p);
void printShapeName();
double volume(double r, double p, double h);
void print(double r, double p, double h);
}; // end third and final derived class --------------------------------------------------------------------------
// Some definitions outside classes
//-------------------------------------------------- BEGIN -----------------------------------------------------------
// Shape base class setter functions defined
void Shape::setWidth(double a) {
width = a;
}
void Shape::setHeight(double b) {
height = b;
}
void Shape::setX(double c) {
x = c;
}
void Shape::setY(double d) {
y = d;
}
void Shape::setRad(double r) {
radius = r;
}
void Shape::setPi(double p) {
p = 3.1416;
pi = p;
}
void Point::printShapeName() { // Print name of class
cout << "Point " << endl;
}
void Point::print(double a, double b) { // Print values within class
cout << "(" << a << "," << b << ")" << endl;
}
void Circle::printShapeName() {
cout << "Circle " << endl;
}
// Circle area function defined
double Circle::area(double r, double p) {
double area;
area = p*(pow(r,2));
return area;
}
void Circle::print(double r, double p) {
cout << "Area of circle is: " << area(r, p) << endl;
cout << "Volume of circle is: " << volume() << endl;
}
void Cylinder::printShapeName() {
cout << "Cylinder " << endl;
}
double Cylinder::area(double r, double p) {
double area;
area = 2*p*r;
return area;
}
double Cylinder::volume(double r, double p, double h) {
double volume;
volume = p*(pow(r,2))*h;
return volume;
}
void Cylinder::print(double r, double p, double h) {
cout << "Area of cylinder is: " << area(r, p) << endl;
cout << "Volume of cylinder is: " << volume(r, p, h) << endl;
}
#endif /* Shape_hpp */
//
// main.cpp
#include <iostream>
#include "Shape.hpp"
#include "Shape.cpp"
using namespace std;
int main() {
double pi = 3.1416; // Variable for pi
// Instantiate class objects
Point guard;
Circle k;
Cylinder cid;
// Instantiate pointers to class objects
Shape *pptr;
Shape *kptr;
Shape *cptr;
// Assign memory of objects to pointer variables
pptr = &guard;
kptr = &k;
cptr = &cid;
// Call objects via pointers and print members
pptr->printShapeName();
pptr->print(5,6);
cout << '\n';
kptr->printShapeName();
kptr->print(9,pi);
cout << '\n';
cptr->printShapeName();
cptr->getHeight();
cptr->setHeight(8);
cptr->print(5,pi);
return 0;
}
Если я пытаюсь добавить третий аргумент высоты в свою функцию печати для класса Cylinder, я получаю ошибку. Это заканчивается использованием моего определения класса Circle.
1 ответ
На случай, если вы еще не нашли ответ, cptr->print(5,pi);
звонки print
с 2 параметрами. Circle
имеет 2 параметра print
Cylinder
имеет 3 параметра print
, поскольку Cylinder
наследует 2 параметра print
от Circle
, Cylinder
печатается так, как будто это Circle
,
Комментарий JakeFreeman может оказаться бесполезным, но он абсолютно прав. Давайте попробуем и сделаем это полезным, не повторяя пару глав вашего учебника.
Вы столкнулись с двумя основными концепциями ОО: инкапсуляция и полиморфизм.
Инкапсуляция является основной проблемой, и решение этой проблемы решает проблему полиморфизма.
Cylinder
объект должен представлять один и только один цилиндр. Он должен иметь свой радиус и высоту, хранящиеся в нем. Вызов функции и передача радиуса и высоты идеологически неверны. Когда вы выполняете метод на Cylinder
, он должен уже знать его радиус и высоту. volume
Метод не должен принимать никаких параметров, потому что все, что нужно для вычисления объема, уже должно быть известно. Это позволяет любой форме иметь одинаковый volume
интерфейс, даже если метод поддержки интерфейса, вероятно, будет отличаться для каждой фигуры.
Точно такая же логика применима к области.
Далее, не все фигуры имеют радиус, поэтому Shape
ничего не должен знать о радиусе. Или высота. Или глубина. Может быть, форма знает точку привязки, но это все, что должна знать форма.
также print
не требует никаких параметров, за исключением, возможно, ссылки на поток ввода-вывода, который вы хотите распечатать, поскольку потоки ввода-вывода - это не то, что обычно ожидалось бы, чтобы цилиндр знал или заботился о том, чтобы его было достаточно.
Один ворчание: зачем вообще обходить пи? Если у вас есть система, в которой число pi не является константой, у вас есть чертовски странная система.
пересмотренная форма:
class Shape {
protected: // protected members
static constexpr double pi = 3.1459 // add more precision as needed
double x, y;
public:
Shape (double posx, double posy): x(posx), y(posy)
{ // set up as much as you can in a constructor because otherwise you always
// have to look over your shoulder and test, "Did the object get properly
// initialized?"
}
void setX(double c)
{
if (c is within logical bounds)
{ // because a setter that does not protect the object is no better for
encapsulation than a public member variable
x = c;
}
}
void setY(double d);
{
if (d is within logical bounds)
{
x = d;
}
}
double getX() {
return x;
}
double getY() {
return y;
}
// Create public virtual functions
virtual void printShapeName() = 0; // pure virtual for printing shape's name
virtual void print() = 0; // pure virtual print function
virtual double area() { // virtual area function returns default null
return 0;
}
virtual double volume() { // virtual default volume function returns null
return 0;
}
};
Примечание. Никаких настроек ничего не требуется. Принуждение setRadius
Функция на квадрате просто тупая.
Обратите внимание также, что это устраняет необходимость Point
, но это хорошо. Является ли круг тип точки? Нет. Круг находится в точке. За очень редкими исключениями, не расширяйте, если нет того, что называется отношениями is-a. Квадрат - это специализированный прямоугольник. Прямоугольник - это специализированная форма. Но это не главное.
Я рекомендую группировку setX
а также setY
в setPosition
это делает и то и другое одновременно, потому что вам будет легче превратить его в атомарную транзакцию позже.
пересмотренный Circle
:
class Circle: public Shape {
protected:
double radius;
public:
Circle(double posx, double posy, double rad): Shape(posx, posy), radius(rad)
{
}
void setRadius(double rad)
{
if (rad makes sense)
{
radius = rad;
}
}
double getRadius()
{
return radius;
}
// Protoypes for print functions
void printShapeName();
virtual void print();
double area();
};
double Circle::area() {
return pi*radius*radius; // note on pow. It can be very slow. For simple
// exponents just multiply
}
void Circle::print() {
cout << "Area of circle is: " << area() << '\n';
// also shy away from `endl` in favour of a raw end of line.
// endl flushes the stream to the underlying media, also very expensive,
// so something best done when you have to or you have nothing better to do.
cout << "Volume of circle is: 0\n";
}
Circle
добавляет то, что нужно добавить к Shape
, Ничего более. Не меньше. Cylinder
будет делать то же самое. Это круг плюс height
, аксессоры для height
, вычисление объема с использованием Circle
"s area
метод и height
, свой area
метод для вычисления его площади поверхности, и другой метод печати, который печатает его статистику
Приложение: если ваш компилятор поддерживает его, и к этому времени должно это сделать, воспользуйтесь override
ключевое слово. Если компилятор находит метод, помеченный как override, и ничего не переопределяет, вы получите хорошее чистое сообщение об ошибке, а не кучу отладок.