Russian Qt Forum
Ноябрь 23, 2024, 15:47 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 ... 3 4 [5] 6 7 ... 13   Вниз
  Печать  
Автор Тема: Как писать ООП программы?  (Прочитано 86696 раз)
8Observer8
Гость
« Ответ #60 : Февраль 22, 2014, 18:31 »

Большое спасибо за ответы! Я их постепенно буду разбирать Улыбающийся

А разве у Вас есть конструктор принимающий QString?

Большое спасибо! Это я поторопился. Добавил конструктор принимающий const QString&.

И зачем метод name возвращает QString по значению? Для этого нет никаких оснований, правильно вернуть константную ссылку

Да, точно! Ведь переменная mName будет всегда и это не тот случай, когда мы не можем возвращать ссылку (то есть, в нашем случае, объект, на который указывает ссылка, не будет уничтожен): http://www.e-reading.co.uk/chapter.php/1002058/56/Mayers_-_Effektivnoe_ispolzovanie_CPP.html

Вот нужно будет мне ещё понять, почему возвращать именно константную ссылку... А если она будет неконстантная?

Вот, что я понял. Получается, что если мы вызываем метод вот так:

Код:
ivan.setName("Ivan");

то конструируется новый временный объект, который передаётся в функцию через ссылку, поэтому не вызывается конструктор копирования и это быстрее по сравнению с передачей по значению. Хотя мы врядли передадим большое имя, но лучше приучаться сразу передавать через константную ссылку. Кстати, я пробовал писать без const, вот так: void setName(QString& name) выдаёт:

Цитировать
main.cpp:31: error: no matching function for call to 'Person::setName(const char [5])'
     ivan.setName("Ivan");
                        ^

А если так: void setName(const QString& name), то нормально:

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
class Person {
public:
   Person() {}
 
   Person(const QString& name) {
       mName = name;
   }
 
   void setName(const QString& name) {
       mName = name;
   }
 
   const QString& name() const {
       return mName;
   }
private:
   QString mName;
};
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   Person ivan;
   ivan.setName("Ivan");
 
   cout << ivan.name() << endl;
   cout.flush();
 
   return a.exec();
}
 
« Последнее редактирование: Февраль 22, 2014, 18:36 от 8Observer8 » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #61 : Февраль 22, 2014, 18:55 »

А если так: void setName(const QString& name), то нормально:
..
Код
C++ (Qt)
   Person(const QString& name) {
       mName = name;
   }
 
Не совсем, это конструктор, правильно (во всяком случае точнее) использовать инициализацию
Код
C++ (Qt)
Person(const QString& name) : mName(name) {}
 

По поводу "изменилась ссылка", "быстрее" и.т.п. Не в этом дело. Пример: пусть есть ф-ция
Код
C++ (Qt)
QRect GetRect( ... some args..)
В чем разница между
Код
C++ (Qt)
QRect R = GetRect(...);  // вариант 1
 
и
Код
C++ (Qt)
QRect R;
R = GetRect(...);  // вариант 2
 
Непонимающий
Записан
Hrundel
Гость
« Ответ #62 : Февраль 23, 2014, 02:39 »

В ООП нужно рассуждать структурно. Одна из лучших структур наиболее ярко демонстрирурующая ООП - это дерево. Любое дерево. Так например, классификация животного мира.

Базовый класс от которого происходят все животные и у них У ВСЕХ есть свойcтва базового класса = животное

У животного есть одно и очень важное свойсто - оно живое.
Раз оно живое значит у него есть как минимум три состояния.
1. рождение или появление на свет
2. жизнь
3. умирание или смерть.

Код
C++ (Qt)
class Animal
{
public:
      Animal():alive(false){}
      ~Animal(){}
 
      bool isAlive(){return alive;}  // жив ли ?
      void birth(){alive = true;}    // рождение
      void death(){alive = false;} // смерть
 
private:
      bool alive;
}
 

От этого класса наследуются царства хордовые и безпозвоночные

Код
C++ (Qt)
class Vertebrates : public Animal
{
public:
       Vertebrates(int input)vertebras(inpupt), spine(ture){}
       ~Vertebrates(){}
 
       int getCountOfVertebras(){return vertebras;} // получить информацию о кол-ве позвонков
       bool hasSpine(){return spine;}
private:
       int vertebras;
       bool spine;
}
 
От царства безпозвоночных наследуются Черви и Насекомые
От царства хордовых Рыбы, Амфибии, Птицы и Млекопитающие
От млекопетающих звери и еще пара *(уже не помню)
От зверей сумчатые, плацентарные и еще один *(опять не помню)

и так далее до какой-нибудь мыши полевки обыкновенной.

При этом у каждого нового класса в этом дереве есть свои новые функции или особенности.
Но каждый из них наследует все свойства классов от которых происходит.

Каждый из классов может иметь малюсенькую функцию, поэтому не обязательно каждый класс прописывать в отдельном файле. Их можно все писать в одном заголовочном файле.

Для того, чтобы составить такую сложную иерархию - нужен план. Для составления такого плана используется UML. Иначе потеряешь обозримость проекта. Разрабы используют или Entrprice Architekt или Visual Paradigm. Я предпочитаю второе. Самое приятное в этих программах то, что после составления очень сложной и увесистой модели можно сгенерировать код в C++. На Visual Paradigm код получается рабочий.
« Последнее редактирование: Февраль 23, 2014, 14:10 от Hrundel » Записан
8Observer8
Гость
« Ответ #63 : Февраль 23, 2014, 14:44 »

В ООП нужно рассуждать структурно. Одна из лучших структур наиболее ярко демонстрирурующая ООП - это дерево. Любое дерево. Так например, классификация животного мира.

Спасибо за идею с животными! Интересно поразвивать будет. Кстати, у Гради Буч вся книга посвящена таким деревьям (такой подход применим во всех областях науки, человеческой жизни, природы, игр и т.д.). Я введение прочитал. Хорошо, что напомнили. Нужно её проработать до конца.

На всякий случай напоминаю:
Перевод: http://rutracker.org/forum/viewtopic.php?t=3343958
Оригинал: http://kickass.to/object-oriented-analysis-and-design-with-applications-3rd-editio-t2753820.html

Каждый из классов может иметь малюсенькую функцию, поэтому не обязательно каждый класс прописывать в отдельном файле. Их можно все писать в одном заголовочном файле.
Вот это пока неочевидно. Примеров никаких не видел и не придумывал. Я заметил, что когда делаешь что-то, то обычно первый раз делаешь неправильно, а потом, когда видишь, как правильно, и переделываешь своё под правильное, то запоминается гораздо лучше. Несколько классов в одном файле, это пока для меня непостижимо.

Для того, чтобы составить такую сложную иерархию - нужен план. Для составления такого плана используется UML. Иначе потеряешь обозримость проекта. Разрабы используют или Entrprice Architekt или Visual Paradigm. Я предпочитаю второе. Самое приятное в этих программах то, что после составления очень сложной и увесистой модели можно сгенерировать код в C++. На Visual Paradigm код получается рабочий.
Большое спасибо! Вот такие инструменты мне обязательно нужно освоить Улыбающийся

По поводу "изменилась ссылка", "быстрее" и.т.п. Не в этом дело. Пример: пусть есть ф-ция
Спасибо! Чуть позже доберусь до этого. Пока не подсказывайте. Сам подумаю Улыбающийся

Я восстановил полиморфизм и добавил возможность считать периметр и площадь через статические функции.

Примечание. Упростил себе работу с несколькими ветками развития проекта. Из консоли (из под Far'а) переключаю ветки в git и Qt Creator моментально перегружает проект. Пошагово описал здесь: http://www.prog.org.ru/topic_26393_0.html

Теперь мы можешь установить радиус (с помощью функции setRadius(double radius)), потом вызвать полиморфные функции perimeter() и area().

Я понял свою ошибку, когда писал calcPerimeter() и calcArea(). Я боялся, что производить расчёт при каждом вызове - это плохой стиль. Я думал, что нужно один раз посчитать, а пересчитывать только при смене параметров. Периметр и площадь считаются быстро. Но вот если у нас будет сложная фигура (я унаследую её от Shape). Вот создам я новый класс Полигон (многоугольник) и появятся проблемы с пересчётом. Сейчас прорабатываю эту идею.

Теперь приходит понимание, что хорошо спроектированная система будет лучше реагировать на изменения требований. Меня напрягает, что при смене требований мне нужно переписывать всю систему целиком. Хорошо, что пока система не очень большая (мягко говоря). Надеюсь, с опытом будет приходить понимание и скорость.

Old, я видел в Вашем коде (из ответа #58) ключевое слово explicit перед конструктором. Я думаю, что это связано с приведением типов. Сейчас изучаю это провило: "Правило 27: Не злоупотребляйте приведением типов" http://www.e-reading.co.uk/chapter.php/1002058/69/Mayers_-_Effektivnoe_ispolzovanie_CPP.html

Остановился на таком варианте. Пожалуйста, укажите на грубые недочёты, если они есть.

Output
Цитировать
static perimeter: 18.8496
p = 18.8496
s = 28.2743
p = 30
s = 50

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
#include "shape.h"
#include "circle.h"
#include "rectangle.h"
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
void printShapeInfo(Shape *ps) {
   cout << "p = " << ps->perimeter() << endl;
   cout.flush();
   cout << "s = " << ps->area() << endl;
   cout.flush();
}
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   Shape *pc = new Circle(3.0);
   cout << "static perimeter: " << Circle::perimeter(3.0) << endl;
   printShapeInfo(pc);
   delete pc;
 
   Shape *pr = new Rectangle(5, 10);
   printShapeInfo(pr);
   delete pr;
 
   return a.exec();
}
 

shape.h
Код
C++ (Qt)
#ifndef SHAPE_H
#define SHAPE_H
 
class Shape
{
public:
 
   Shape() {}
   virtual ~Shape() {
 
   }
 
   virtual double perimeter() const = 0;
   virtual double area() const = 0;
};
 
#endif // SHAPE_H
 

circle.h
Код
C++ (Qt)
#ifndef CIRCLE_H
#define CIRCLE_H
 
#include "shape.h"
#include <cmath>
 
class Circle : public Shape
{
public:
   Circle(double radius = 0.0) : m_radius(radius) {
   }
 
   static double perimeter(double radius) {
       return (2.0 * M_PI * radius);
   }
 
   static double area(double radius) {
       return (M_PI * radius * radius);
   }
 
   virtual double perimeter() const {
       return perimeter(m_radius);
   }
 
   virtual double area() const {
       return area(m_radius);
   }
 
   inline double radius() const {
       return m_radius;
   }
 
   inline void setRadius(double radius) {
       m_radius = radius;
   }
 
private:
   double m_radius;
};
 
#endif // CIRCLE_H
 

rectangle.h
Код
C++ (Qt)
#ifndef RECTANGLE_H
#define RECTANGLE_H
 
#include "shape.h"
 
class Rectangle : public Shape
{
public:
 
   Rectangle(double height = 0.0, double width = 0.0) :
       m_height(height),
       m_width(width)
   {
   }
 
   static double perimeter(double height, double width) {
       return 2.0 * (height + width);
   }
 
   static double area(double height, double width) {
       return height * width;
   }
 
   virtual double perimeter() const {
       return perimeter(m_height, m_width);
   }
 
   virtual double area() const {
       return area(m_height, m_width);
   }
 
   inline double height() const {
       return m_height;
   }
 
   inline double width() const {
       return m_width;
   }
 
   inline void setHeight(double height) {
       m_height = height;
   }
 
   inline void setWidth(double width) {
       m_width = width;
   }
 
private:
   double m_height;
   double m_width;
};
 
#endif // RECTANGLE_H
 
« Последнее редактирование: Февраль 23, 2014, 14:47 от 8Observer8 » Записан
8Observer8
Гость
« Ответ #64 : Февраль 23, 2014, 14:49 »

Скажите, пожалуйста, а реально ли реализовать функцию draw() для моих классов через OpenGL? Это очень сложно?
« Последнее редактирование: Февраль 23, 2014, 14:51 от 8Observer8 » Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #65 : Февраль 23, 2014, 15:28 »

Скажите, пожалуйста, а реально ли реализовать функцию draw() для моих классов через OpenGL? Это очень сложно?
Реально и не сложно, только эта тема уже будет не ООП. Улыбающийся
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #66 : Февраль 23, 2014, 15:53 »

Периметр и площадь считаются быстро. Но вот если у нас будет сложная фигура (я унаследую её от Shape). Вот создам я новый класс Полигон (многоугольник) и появятся проблемы с пересчётом. Сейчас прорабатываю эту идею.
Очень многие решения зависят от того "а что за плечами", т.е. какой код уже написан? Сейчас у Вас "аж ничего", простейшие Circle/Rectangle переделываются за неск минут как угодно. В результате получается довольно легкое "порхание бабочки", в котором нет ничего плохого - но и толку немного. Поэтому-то люди и советуют типа "возьми реальную задачу". Но то легко сказать, а реально - стоит ли на много недель (минимум) углубляться в "нечто" (специфику/матчасть) только для целей обучения? С этой точки зрения полигон - хороший вариант. Он и достаточно сложен (так резво как с  простыми шейпами не выйдет) - и достаточно прост, ну максимум день на освоение простой геометрии. В общем, как говорил один мой заказчик:
Цитировать
Ну подумай-подумай. А я пока чайку нагною
Улыбающийся

По поводу животных. Красиво, впечатляет. Но в библии есть ответ и на это - ну на то она и библия.
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #67 : Февраль 23, 2014, 19:32 »

А разве у Вас есть конструктор принимающий QString? И зачем метод name возвращает QString по значению? Для этого нет никаких оснований, правильно вернуть константную ссылку

Неправильно. С точки зрения производительности - да, с точки зрения всего остального - нет. Во-первых, теряется гибкость (если было 2 связанных геттера - Document::filePath() и Document::name() (aka QFileInfo(filePath()).fileName()) то что, при кешировании имени менять сигнатуру? нарушается бинарная совместимость, да и код странный - один метод возвращает const QString &, 2й QString). Во-вторых, это небезопасно - может найтись умник, к-ый снимет const со ссылки и возьмет да и поменяет. Не, ну а чо - "квикфикс, сроки горят, потом исправлю").

ЗЫ: Ну то есть если вы юзаете std::string, где корову забанили, то это имеет смысл. Для QString смысла нет.
Записан
8Observer8
Гость
« Ответ #68 : Февраль 24, 2014, 10:10 »

Скажите, пожалуйста, а реально ли реализовать функцию draw() для моих классов через OpenGL? Это очень сложно?
Реально и не сложно, только эта тема уже будет не ООП. Улыбающийся

Спасибо! Вы правы. Специфику OpenGL мы здесь обсуждать не будем. Предлагаю взглянуть на рисование фигур с точки зрения ООП (а OpenGL - это просто будет второстепенный инструмент). Объекту какого класса предложить рисование фигур? Мне предлагали сделать функцию draw() полиморфной в Shape, а потом реализацию писать в дочерних классах, потому что "функция знает как себя рисовать" (что-то в этом роде). А я считаю, что нужно завести для рисования специальный класс с методом draw(). В этот метод мы будем передавать константную ссылку на Shape (и ещё смещение по x и y от начала координат, а так же толщину линии) Вот так:

viewer.h
Код
C++ (Qt)
class Viewer : public QGLWidget
{
public:
   // ...
   void draw(const Shape &ps, int xOffset, int yOffset, int pointSize = 2);
   // ...
}
 

Но теперь я в тупике. Как объект класса Viewer отличит круг от прямоугольника? А если будет другая фигура? Может передавать в draw массив координат точек вершин (который будет внутри Shape)? Как вы полагаете?
« Последнее редактирование: Февраль 24, 2014, 10:17 от 8Observer8 » Записан
Johnik
Крякер
****
Offline Offline

Сообщений: 339


Просмотр профиля
« Ответ #69 : Февраль 24, 2014, 10:34 »

Но теперь я в тупике. Как объект класса Viewer отличит круг от прямоугольника? А если будет другая фигура? Может передавать в draw массив координат точек вершин (который будет внутри Shape)? Как вы полагаете?

Здесь точно та же ситуация как и с std::ostream или QTextStream
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #70 : Февраль 24, 2014, 11:08 »

Неправильно. С точки зрения производительности - да, с точки зрения всего остального - нет. Во-первых, теряется гибкость (если было 2 связанных геттера - Document::filePath() и Document::name() (aka QFileInfo(filePath()).fileName()) то что, при кешировании имени менять сигнатуру? нарушается бинарная совместимость, да и код странный - один метод возвращает const QString &, 2й QString). Во-вторых, это небезопасно - может найтись умник, к-ый снимет const со ссылки и возьмет да и поменяет. Не, ну а чо - "квикфикс, сроки горят, потом исправлю").
Не в курсе о каком кешировании Вы говорите.
Код
C++ (Qt)
const QString & filePath1(..) const;
QString filePath2(..) const;
QString & filePath3(..);
 
Это все вещи разные. В первом случае ясно показано что строка - член класса, вызов очень быстр и ничем не чреват. Часто вызывающий все равно копирует строку в переменную - но то уже его дело. А во втором ясно только что класс умеет выдать строку, а как - дело темное. Может просто вернет член, а может и будет как-то вычислять. Возможно этот вызов затратен или вообще будет выдано не то что надо - напр у класса еще не установлены нужные данные. Поэтому возвращать по значению "на всякий случай" не кажется мне хорошим стилем.

Третий вариант в большинстве случаев просто плох - оказывается что геттеры/сеттеры просто игрушки, так, "чтобы по книжке было". А дошло до дела - вот и (слегка) замаскированный "public"  Улыбающийся
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #71 : Февраль 24, 2014, 11:19 »

Мне предлагали сделать функцию draw() полиморфной в Shape, а потом реализацию писать в дочерних классах, потому что "функция знает как себя рисовать" (что-то в этом роде). А я считаю, что нужно завести для рисования специальный класс с методом draw().
Обоснуйте свое решение

В этот метод мы будем передавать константную ссылку на Shape (и ещё смещение по x и y от начала координат, а так же толщину линии) Вот так:
А откуда Вы возьмете смещения? Они явно принадлежат шейпу, его член(ы). Иначе как Вы будете их хранить? Контейнер шейпов + еще (параллельно) контейнер смещений?  Улыбающийся

Толщина линии. Из той же оперы: цвет, заливать внутренности или нет и.т.п.. Здесь возможны варианты. Вполне нормально если сама шейпа знает свою толщину линии - напр сейчас она "выбрана". Т.е. тоже член. Но также возможно что рисующий хочет какую-то одну толщину  для всех. Как это увязать?
Записан
Hrundel
Гость
« Ответ #72 : Февраль 24, 2014, 11:35 »

Но теперь я в тупике. Как объект класса Viewer отличит круг от прямоугольника? А если будет другая фигура? Может передавать в draw массив координат точек вершин (который будет внутри Shape)? Как вы полагаете?

Ну с этим-то как раз проще всего - на то и полиморфизм. Мне кажеться ты опять думаешь не в ту сторону. После генерализации используются ТОЛЬКО листья дерева. Это значит, что к руту (корню) Shape тебе уже обращаться не нужно.

Объекты потомки Shape, по логики вещей, не могут существовать без Viewer, иначе их существование становится безсмысленным. Поэтому создаваться они должны для Viewer. То есть внутри этого класса.

Вот, это как раз и есть вопросы которыми в основном занят разработчик архитектуры. Как сделать все логичным, оправданным, понятным, простым и удобным!

Поэтому если ты хочешь рисовать круг, то пишешь

Код
C++ (Qt)
Circle *crcl = new Circle();
crcl->draw(x, y, rad);
 

Мне предлагали сделать функцию draw() полиморфной в Shape, а потом реализацию писать в дочерних классах, потому что "функция знает как себя рисовать" (что-то в этом роде).

А я считаю, что нужно завести для рисования специальный класс с методом draw(). В этот метод мы будем передавать константную ссылку на Shape (и ещё смещение по x и y от начала координат, а так же толщину линии) Вот так:

Думаю, что твой путь не самый простой для реализации, да и по понятности кода, в нем будет меньше логики.

Просто напиши в Shape виртуальную функцию и реализуй для каждого отдельно, так проще, понятнее, логичнее.

И вообще Shape должен содержать все возможные виртуальные функции, которые могут быть в дальнейшем использованы для потомков.
Например:

move(int x, int y), hide(), show(), scale(float u, float v), setFillColor(QColor*), setShapeColor(QColor*)  и все тому подобное. Если оно, кончно, планируется к использованию.
« Последнее редактирование: Февраль 24, 2014, 12:01 от Hrundel » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #73 : Февраль 24, 2014, 12:44 »

Объекты потомки Shape, по логики вещей, не могут существовать без Viewer, иначе их существование становится безсмысленным. Поэтому создаваться они должны для Viewer. То есть внутри этого класса.
Не вижу никаких оснований для такого вывода. Напр объекты могут быть загружены из файла и сохранены в др формате - никакого вьюера и близко нет. Или наоборот, могут быть использована неск вьюерами сразу.

Код
C++ (Qt)
Circle *crcl = new Circle();
crcl->draw(x, y, rad);
 
Ну да, а откуда Circle возьмет контекст рисования?
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #74 : Февраль 24, 2014, 13:21 »

Это все вещи разные.
Так Авварон вам про это и говорит.
Это настолько разные вещи, что у этих методов разные сигнатуры и при изменении прототипа с первого на второй (или наоборот) полетит ABI.
А менять их может заставить жизнь при развитии проекта. Например, вы написали такой метод:
Код
C++ (Qt)
const QString & filePath1(..) const;
который просто возвращает ссылку на поле класса, а через год, его нужно поменять так, что бы он начал возвращать временную строку, потому что этот путь уже не просто ссылка на поле, а формируется внутри filePath1 по хитрому алгоритму:
Код
C++ (Qt)
const QString & filePath1(..) const
{
   QString result = prefix() + m_filePath + postfix();
   return result;
}
 
Так просто это сделать не получиться, нужно будет отказываться от возврата константной ссылки, вместе с бинарной совместимостью.
Приплыли.
Поэтому, и нужно возвращать по значению. Улыбающийся
Записан
Страниц: 1 ... 3 4 [5] 6 7 ... 13   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 1.004 секунд. Запросов: 23.