Перегрузка операций для класса



Допустим, мы сделали классы для точки и треугольника. И далее хотим вводить и выводить их легко через cin и cout. Например: point M; cin >> M; О том как это сделать и будет данная глава. Ну и также как сделать арифметические операции: сложение 2 точек, вычитание и прочее. Чтобы мы могли писать просто:

point M,N; cin >> M >> N; cout << M + N << endl;

Запоминай конструкцию:

 

// перегрузка оператора вывода для точки

ostream& operator<<(ostream& output, point& M) {

           cout << "("<< M.x << ", " << M.y << ", " << M.z << ")";

           return output;

}

 

// перегрузка оператора ввода для точки

istream& operator>>(istream& input, point& M) {

           cin >> M.x >> M.y >> M.z ;

           return input;

}

Вместо input может быть любое другое название переменной, в остальном же ничего менять не стоит. Для другого класса вместо point& M, нужно писать сам другой класс: например для треугольника: tring& M. Ну и само имя M тоже не принципиально.

Т.е. если я буду вводить, например, cin >> M; то у меня будет выполняется то что я прописал в функции istream operator: cin >> M.x >> M.y >> M.z ; И далее возвращается объект типа istream, а именно поток ввода. Для вывода (ostream) всё аналогично.

Например, мы создали: point M(1,2,4); и далее пишем cout << M; у нас выведется (1,2,4)- это то что мы прописали в функции ostream operator. Можешь сам поэкспериментировать.

Вот перегрузка операторов ввода-вывода для треугольника:

ostream& operator<<(ostream& output, tring& M) {

           cout << "A" << M.A << ", B" << M.B << ", C" << M.C << endl;

           return output;

}

istream& operator>>(istream& input, tring& M) {

           cout << "А:\n"; cin >> M.A;

           cout << "В:\n"; cin >> M.B;

           cout << "С:\n"; cin >> M.C;

           return input;

}

Теперь посмотрим другие перегрузки, например для точки. На нижеприведённых примерах ты можешь понять как это делать:

point operator+(point a, point b) {

           point c;

           c.x= a.x + b.x;

           c.y= a.y + b.y;

           c.z= a.z + b.z;

           return c;

}

point operator-(point a, point b) { point c; c.x= a.x - b.x; c.y= a.y - b.y; c.z= a.z - b.z; return c; }

 

// скалярное произведение

double operator%(point a, point b) { return a.x*b.x + a.y*b.y + a.z*b.z; }

 

// векторное произведение

point operator*(point a, point b) {

           point c;

           c.x= a.y*b.z - a.z*b.y;

           c.y= a.z*b.x - a.x*b.z;

           c.z= a.x*b.y + a.y*b.x;

           return c;

}

 

// проверка равенства 2 точек

bool operator==(point a, point b) {

           if ( (a.x==b.x)&&(a.y==b.y)&(a.z==b.z) ) return true;

           else return false;

}

Например, я пишу: point M,N; cin >> M >> N; И далее я могу делать с этими точками операции, которые прописал в перегрузках, например: double s= M%N; тут я вычислил скалярное произведение 2 точек. Или так: point P= M*N; тут будет уже векторное произведение.

Или могу даже использовать условия: if (M==N) { что-то делать } т.к. проверка равенства прописана в перегрузке.

Перегрузку можно прописать и при самом объявлении класса. Например, ту же проверку равенства 2 точек можно записать внутри класса так:

bool operator==(point b) {

           if ( (x==b.x)&&(y==b.y)&(z==b.z) ) return true;

           else return false;

}

 

Примеры трёх полезных задач (программ) с использованием концепции классов

Ниже я приведу 3 программы, которые я писал сам для своих нужд и интересов. В них ты можешь увидеть силу и мощь объектно-ориентированного программирования, как компактно и красиво всё сжимается и как удобно с этим работать. Сначала будет приведён код с программой и комментариями, а затем будем разбирать суть и зачем здесь мы использовали классы, что было бы без них.

Естественно, не стоит воспринимать объектно-ориентированное программирование как панацею и пихать куда не надо, просто в некоторых случаях данная концепция позволяет упростить и/или ускорить процесс разработки и сам код.

 

1. Учёт упражнений с девушками на улице

Суть в том что я сначала вбиваю какие упражнения у меня есть. А далее я буду запускать программу чтобы забить сколько я сделал того или иного упражнения: программа в цикле будет запрашивать у меня эти значения. Если не сделал, пишется 0. И после каждого прохода цикла данные сохраняются в текстовой файл. Можно потом уже использовать имеющиеся данные. Показывается- сколько сделано сегодня и вообще. И в цикле программы спрашивается- в новый день нужно переходить или ещё продолжается текущий.

Это старая, более ранняя версия программы- но она тут будет тоже подходящей.

// Файл Supply.h

#ifndef SUPP

#define SUPP

 

#include <iostream>

#include <cmath>

#include <string>

#include <fstream>

#include <Windows.h>

using namespace std;

 

// Вспомогательные дизайновые функции

void _() { cout << endl; }

void _(int k) { for(int i=0;i<k;i++) cout << endl; }

void __(int k) {

           if (k==0) system("CLS");

           if (k==1) {

                           _(); system("pause");

           }

           if (k==2) {

                           __(1); __(0);

           }

}

// кол-во цифр в числе (для вывода дней 001, 024 и т.п)

int qq (int x) {

           if(x==0) return 1;

           return log10(x)+1;

}

/////////////////////////////////////////////////////

const int BuffNumberOfActions= 500;

int NumberOfActions;

 

// действие, упражнение

class action {

public:

           int Total, Today;

           string Name;

           action (string Name1="", int Total1=0, int Today1=0) {

                           Name= Name1; Total= Total1; Today= Today1;

           }

};

 

// перегрузка операторов потока для упражнения

ostream& operator<<(ostream& stream, action& x) {

           cout << x.Total << " (+" << x.Today << ") \t[" << x.Name << "]";

           return stream;

}

istream& operator>>(istream& stream, action& x) {

           cout << "Сделано \"" << x.Name << "\" ";

           int buff; cin >> buff; x.Today+=buff;

           return stream;

}

#endif

 

// Файл Source.cpp

#include "Supply.h"

 

// информация о программе

void prog_info() {

           cout << "НОВЕЛЛА УНИВЕРСАЛЬНЫЙ ДВИЖОК-ПРОГРАММА" << endl;

           cout << "Программа для учёта вбросов, упражнений, подходов к девушкам и людям" << endl;

           cout << "[25.03.2017] Автор: Васька Мырт (vk.com/id176208527)" << endl;

           _();

           cout << "Виртуальной валюты тут нет" << endl;

           cout << "Сам можешь создать свои учитываемые действия" << endl;

           _();

           cout << "For working russian font use the lucida console font in console options" << endl;

           __(2);

}

 

// новая игра или продолжаем

bool NewGame () {

           cout << "(0) Новая игра" << endl;

           cout << "(1) Продолжить" << endl;

           bool x; cin>>x; return !x;

}

 

// данные игрока (выводятся наверху)

void info (string name, int day, action actions[]) {

           __(0);

           cout << name << " <";

           if(qq(day)==1) cout << "00"; if(qq(day)==2) cout << "0";

           cout << day << ">" << endl;

           _();

           for(int i=0;i<NumberOfActions;i++) cout << actions[i] << endl;

           _(2);

}

 

// новый день или продолжаем

bool NewDay () {

           cout << "(+) новый день" << endl;

           cout << "(a.k) продолжается" << endl;

           char x; cin>>x;

           if (x=='+') return true;

           else return false;

}

 

// сохранение учётных данных после каждого цикла

void SaveData (string name, int day, action actions[], string filename, int &NumberOfActions) {

           ofstream fout;

           fout.open(filename);

           fout << NumberOfActions << endl;

           fout << name << " " << day << endl;

           for(int i=0;i<NumberOfActions;i++) fout << actions[i].Name << " " << actions[i].Total << " " << actions[i].Today << endl;

           fout.close();

           cout << "Данные сохранены"; __(1);

}

 

// чтение учётных данных при продолжении игры

void ReadData (string &name, int &day, action actions[], string filename, int &NumberOfActions) {

           ifstream fin;

           fin.open(filename);

           fin>>NumberOfActions>>name>>day;

           for(int i=0;i<NumberOfActions;i++) fin >> actions[i].Name >> actions[i].Total >> actions[i].Today;

           fin.close();

}

 

// названия учитываемых действий (тут мы их вводим)

void ActionsNames (action actions[]) {

           for(int i=0;i<NumberOfActions;i++) {

                           cout << "Действие " << i+1 << ": "; cin >> actions[i].Name;

           }

}

 

// главная функция

int main() {

           SetConsoleCP(1251);

           SetConsoleOutputCP(1251);

           prog_info();

           bool newgame= NewGame();

 

           string name;

           _(); cout << "Введите имя: "; cin >> name; _();

 

           action *actions= new action [BuffNumberOfActions];

           if(newgame) {

                           cout << "Сколько будет учитываемых действий: "; cin >> NumberOfActions;

                           ActionsNames(actions);

           }

           int day= 1;

 

           ifstream fin;

           string filename= "saveksm_"; filename+=name; filename+=".txt";

           bool pass= true;

           if(!newgame) {

                           fin.open(filename);

                           if(!fin) {

                                           cout << "Файл не найден" << endl;

                                           pass= false; __(1);

                           }

                           if(fin) ReadData(name,day,actions,filename,NumberOfActions);

           }

 

           // основной цикл игры

           if(pass) {

                           bool newday= false;

                           bool first= true;

                           while(true) {

                                           if(!first) {

                                                           info(name,day,actions);

                                                           newday= NewDay();

                                           }

                                           if(first) first= false;

                                           if(newday) {

                                                           day++;

                                                           for(int i=0;i<NumberOfActions;i++) {

                                                                           actions[i].Total+=actions[i].Today;

                                                                           actions[i].Today= 0;

                                                           }

                                           }

                                           for(int i=0;i<NumberOfActions;i++) {

                                                           info(name,day,actions);

                                                           cin >> actions[i];

                                           }

                                           info(name,day,actions);

                                           SaveData(name,day,actions,filename,NumberOfActions);

                           }

           }

           // конец основного цикла

}

Заметь как мы выводим тут большую часть кода через функции и классы подальше от главной функции- так всё выглядит гораздо более понятным, простым, удобным- так и следует делать. Если можно какой-то достаточно большой кусок кода отдельно в функцию вытащить- лучше всё же вытащить, пусть этот кусок не мозолит глаза там где не нужно. Интерфейс в тени, исполнение на виду в главной функции- простое, понятное и компактное.

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

Кроме того, мы можем использовать эту программу как шаблон и на её основе делать другие, например где упражнения уже чётко заданы. Можно также дополнять класс упражнения, расширять функционал. Вот например какой класс уже получился в счётчике упражнений программы "социальный гигантус":

class action {

public:

           int Total, Today; // количество- всего и сегодня

           string Name; // название упражнения

           int TotalNL, TodayNL; // неудачные выполнения- всего и сегодня

           bool withNL; // учитываем неудачные или нет вообще в принципе

           bool daypass, needpass; // разрешено это упражнение для текущего дня и необходимо ли оно для текущего дня

           int ball; // балл для оценки сложности упражнения и участия в проценте выполненной нормы за день

           int maxToday; // сколько сегодня нужно сделать этого упражнения

           // конструктор класса со значениями по умолчанию

           action (string Name1="", int Total1=0, int Today1=0, int TotalNL1=0, int TodayNL1=0, bool w1= false, bool d1= false, int b1=0, bool n1= false, int m1= 0) {

                           Name= Name1; Total= Total1; Today= Today1; TotalNL= TotalNL1; TodayNL= TodayNL1; withNL= w1; daypass= d1; ball= b1; needpass= n1; maxToday= m1;

           }

};

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

Также для количества упражнений используется константа NumberOfActions- для шаблонного перебора в цикле. Даже если мы изменим значение константы, все циклы в программе будут так же работать и не надо в каждом цикле менять это число. Я хочу чтобы ты привыкал к такой универсальности и настраивал свой мозг так думать при разработке своих программ. Кстати, ты можешь заметить что при таком подходе в программе становится всё меньше чисел и всё больше слов.

int ResultProc (action actions[], int day) {

           if(RestDay(day)) return 100;

           else {

                           int TodayBall= 0;

                           NeedPass(actions,day);

                           for(int i=0;i<NumberOfActions;i++) {

                                           if(actions[i].needpass) {

                                                           if(actions[i].Today <= actions[i].maxToday) TodayBall+= actions[i].Today * actions[i].ball;

                                                           if(actions[i].Today > actions[i].maxToday) TodayBall+= actions[i].maxToday * actions[i].ball;

                                           }

                           }

                           return TodayBall*100/MaxBall(actions,day);

           }

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

2. Учёт бюджета

Суть программы- мы вводим категории доходов и сами доходы, категории расходов и сами расходы- программа высчитывает различные показатели: сколько процентов дохода даёт та или иная категория, сколько мы зарабатываем в час, сколько мы максимум можем заработать такой ставкой и т.д. и т.п.

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

// Файл Supply.h

#ifndef SUPP

#define SUPP

 

#include <iostream>

#include <string>

#include <Windows.h>

using namespace std;

 

// Вспомогательные дизайновые функции

void _() { cout << endl; }

void _(int k) { for(int i=0;i<k;i++) cout << endl; }

void __(int k) {

           if (k==0) system("CLS");

           if (k==1) {

                           _(); system("pause");

           }

           if (k==2) {

                           __(1); __(0);

           }

}

 

// процент х от y

double proc (double x, double y) {

           return x*100/y;

}

// тут не только вычисляется процент, но и выводится как надо

void _proc (char c, double x, double y, string s) {

           cout << c << " " << proc(x,y) << "% от общего " << s << "а" << endl;

}

void ___() {

           _(); cout << "________________________________________________________________________________"; _();

}

 

// сумма всех элементов массива

template <typename Type>

Type ArraySum (Type m[], int size) {

           Type res= m[0];

           for (int i=1; i<size; i++) res= res + m[i];

           return res;

}

// максимальный элемент массива

template <typename Type>

Type ArrayMax (Type m[], int size) {

           Type max= m[0];

           for (int i=1; i<size; i++) {

                           if ( m[i] > max ) max= m[i];

           }

           return max;

}

template <typename Type>

Type ArrayMin (Type m[], int size) {

           Type min= m[0];

           for (int i=1; i<size; i++) {

                           if ( m[i] < min ) min= m[i];

           }

           return min;

}

///////////////////////////////////////////////

int D= 30; // бюджетный период по умолчанию

double sleep_koef= 0.3; // процент сна по умолчанию

 

// категория доходов или расходов

class unit {

public:

           string name; // название категории

           double value, worktime; // значения в деньгах и времени

           bool revenue; // доход или расход

           unit (string name1="", double value1=0, double worktime1=0, bool revenue1=true) {

                           name= name1; value= value1; worktime= worktime1; revenue= revenue1;

           }

           char sign() {

                           if(revenue) return '+';

                           else return '-';

           }

           string type() {

                           if(revenue) return "доход";

                           else return "расход";

           }

           // временно-денежный коэффициент или доход/расход в час

           double vdk() {

                           return value/worktime;

           }

           // фоновый доход/расход в день независимо от работы

           double day_background() {

                           return value/D;

           }

           // фоновый в час

           double hour_background() {

                           return value/(24*D);

           }

           // фоновый в секунду

           double second_background() {

                           return value/(86400*D);

           }

};

 

// перегрузка оператора ввода для категории дохода или расхода

istream& operator>>(istream &stream, unit &x) {

           cout << "Имя категории: "; cin >> x.name;

           cout << "Денежное значение: "; cin >> x.value;

           if(x.revenue) {

                           cout << "Общие часы работы: "; cin >> x.worktime;

           }

           return stream;

}

// перегрузка оператора вывода

ostream& operator<<(ostream &stream, unit &x) {

           cout << "--> " << x.name << " \t(" << x.sign() << x.value << ")" << endl;

           if(x.revenue) {

                           cout << "Время работы в часах= " << x.worktime << endl;

                           cout << "Временно-денежный коэф= " << x.vdk() << endl;

           }

           cout << "Фоном за день: " << x.sign() << x.day_background() << endl;

           cout << "Фоном за час: " << x.sign() << x.hour_background() << endl;

           cout << "Фоном за секунду: " << x.sign() << x.second_background() << endl;

           return stream;

}

 

// сложение 2 категорий дохода или расхода

unit operator+(unit t1, unit t2) {

           unit total;

           total.revenue= t1.revenue + t2.revenue;

           total.value= t1.value + t2.value;

           total.worktime= t1.worktime + t2.worktime;

           total.name= "Общий " + total.type();

           return total;

}

 

// вычитание 2 категорий (для расчёта чистого дохода, т.е. доход минус расход)

unit operator-(unit &tr, unit &tc) {

           unit pure;

           pure.value= tr.value-tc.value;

           pure.worktime= tr.worktime;

           pure.name= "Чистый доход";

           return pure;

}

 

// максимальный и минимальный доход в час из всех занятий (категорий доходов)

double maxvdk (unit tr[], int size) {

           double *vdks= new double [size];

           for(int i=0; i<size; i++) vdks[i]= tr[i].vdk();

           return ArrayMax(vdks,size);

}

double minvdk (unit tr[], int size) {

           double *vdks= new double [size];

           for(int i=0; i<size; i++) vdks[i]= tr[i].vdk();

           return ArrayMin(vdks,size);

}

#endif

 

// Файл main.cpp или Source.cpp

#include "Supply.h"

 

// Информация о программе

void prog_info() {

           cout << "Бюджетная 5.0" << endl;

           cout << "Программа для учёта доходов и расходов" << endl;

           cout << "[26.03.17] Васька Мырт" << endl;

           cout << "Код написан с нуля и более гибок и универсален" << endl;

           cout << "Вносятся свои категории доходов и расходов" << endl;

           __(2);

}

 

// Главная функция

int main() {

           // возможность ввода русских категорий

           SetConsoleCP(1251);

           SetConsoleOutputCP(1251);

           prog_info();

 

           cout << "Введите бюджетный период: "; cin >> D;

           cout << "Условный процент сна: "; cin >> sleep_koef; sleep_koef/=100;

           double active_day= 24*(1-sleep_koef);

           _();

               

           // выяснение количества категорий для создания массивов- это не очень совершенно, но так было проще, понятнее и быстрее

           int NumberOfRevenue, NumberOfCost;

           cout << "Сколько будет категорий доходов: "; cin >> NumberOfRevenue;

           cout << "Сколько будет категорий расходов: "; cin >> NumberOfCost;

           bool onlytr= NumberOfCost==0; // если введём 0 для количества расходов, то мы не будем их вводить и выводить- тут мы создаём такую возможность

           unit *TotalRevenue= new unit [NumberOfRevenue];

           if(onlytr) NumberOfCost=1; // для исключения некоторых ошибок

           unit *TotalCost= new unit [NumberOfCost];

           _();

 

           // ввод доходов

           cout << "@ Доходы" << endl;

           for (int i=0; i<NumberOfRevenue; i++) {

                           cout << "// Категория " << i+1 << endl;

                           cin >> TotalRevenue[i];

           }

           _();

           // ввод расходов если они есть

           if(!onlytr) {

                           cout << "@ Расходы" << endl;

                           for (int i=0; i<NumberOfCost; i++) {

                                           TotalCost[i].revenue= false;

                                           cout << "// Категория " << i+1 << endl;

                                           cin >> TotalCost[i];

                           }

                           _();

           }

           if(onlytr) {

                           TotalCost[0].name= "CostBuffer";

                           TotalCost[0].revenue= false;

                           TotalCost[0].value= 0;

           }

           cout << "Данные получены" << endl;

           __(2);

               

           // сумма всех категорий доходов и расходов- тут классы и шаблонная функция одновременно! вот это уже достаточно круто так делать

           unit TR= ArraySum(TotalRevenue,NumberOfRevenue);

           unit TC= ArraySum(TotalCost,NumberOfCost);

           unit PureTR= TR - TC;

               

           // вывод результатов

           cout << TR << endl;

           if(!onlytr) cout << TC << endl;

           _();

           cout << PureTR;

           _proc('*',PureTR.value,TR.value,TR.type());

           ___();

           cout << "/// По категориям ///" << endl;

           _();

           for (int i=0; i<NumberOfRevenue; i++) {

                           cout << TotalRevenue[i];

                           _proc('*',TotalRevenue[i].value,TR.value,TR.type()); _();

           }

           _();

           if(!onlytr) {

                           for (int i=0; i<NumberOfCost; i++) {

                                           cout << TotalCost[i];

                                           _proc('-',TotalCost[i].value,TC.value,TC.type());

                                           _proc('+',TotalCost[i].value,TR.value,TR.type());

                                           _();

                           }

           }

           ___();

           cout << "# САМЫЕ ВАЖНЫЕ ПОКАЗАТЕЛИ" << endl;

           _();

           cout << "1. Общий доход: " << TR.value << endl;

           cout << "--> Чистый: " << PureTR.value << endl;

           _();

           if(!onlytr) {

                           cout << "2. Расходы (минимально необходимый доход) " << TC.value << endl;

                           cout << "--> " << proc(TC.value,TR.value) << "%" << endl;

                           _();

                           cout << "Необходимое время работы по категориям:" << endl;

                           for(int i=0;i<NumberOfRevenue;i++) cout << "* " << TotalRevenue[i].name << " (" << TC.value/TotalRevenue[i].vdk() << " часов)" << endl;

                           cout << "В среднем: " << TC.value/TR.vdk() << endl;

                           cout << "Дневной минимум выживания: " << TC.day_background() << endl;

                           _();

           }

           cout << "3. Общее время работы: " << TR.worktime << " часов (" << TR.worktime/active_day << " дней)" << endl;

           cout << "Процент работы от всего времени: " << proc(TR.worktime,24*D) << "%" << endl;

           cout << "* от всего времени бодствования: " << proc(TR.worktime,active_day*D) << "%" << endl;

           _();

           cout << "4. Средний ВДК: " << TR.vdk() << endl;

           cout << "min(" << minvdk(TotalRevenue,NumberOfRevenue) << ") and max(" << maxvdk(TotalRevenue,NumberOfRevenue) << ")" << endl;

           cout << "Чистый ВДК (pVDK): " << PureTR.vdk() << endl;

           cout << "Фоновый средний ВДК: " << TR.hour_background() << endl;

           _();

           cout << "5. Рекомендованный предел дневных расходов: " << TR.day_background() << endl;

           _();

           cout << "6. Максимально возможный доход по ВДК: " << active_day*D*maxvdk(TotalRevenue,NumberOfRevenue) << endl;

           cout << "По категориям:" << endl;

           for(int i=0;i<NumberOfRevenue;i++) cout << "--> {" << active_day*D*TotalRevenue[i].vdk() << "} " << TotalRevenue[i].name << endl;

           _();

           cout << "7. Средний рабочий день: " << TR.worktime/D << " часов" << endl;

 

           __(1);

}

В данной программе мы использовали класс категории дохода или расхода для более удобного хранения и реализации. Весь интерфейс, так сказать, а именно- ввод категорий, вывод, суммирование, различные функции- всё это мы поместили в отдельный заголовочный файл. А в главном исполнительном cpp файле мы уже просто выводим что нам нужно и используем уже готовые объявленные функции. Даже можно забыть как там это всё работает в самих классах и функциях- один раз создали, а далее просто спокойно без лишних умственных напряжений пользуемся благами нашего интеллектуального труда для новых и новых задач. Останется только помнить имена всех созданных нами в программе функций и методов класса- ну или в крайнем случае можно подглянуть.

Если бы мы не использовали отдельные классы и функции, лишние сотни строк кода в главной функции мозолили бы нам глаза и запутывали. Кроме того, мы можем дополнять класс категории новыми функциями (методами), атрибутами и всё- в главной функции менять уже ничего не надо, останется просто использовать новое что мы добавили- исправления и доработки становятся быстрее, проще и удобнее.

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

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

Советую ещё раз пройтись по всему коду от начала до конца и разобрать что и почему в нём происходит. Ну и скомпилируй это и сам испытай программу.

3. Счётчик процента времени

Суть программы: я заношу в файл данные по поводу того что я делал в течение дня в таком формате:

7:10 - 7:20 умывался *

7:21 - 7:42 зарядка +

7:43 - 8:25 какая-то работа !

8:26 - 8:31 зарядка +

Программа подсчитывает, сколько времени занимает каждое действие и выводит это по убыванию времени. Также у каждого действия есть знак: ! важное, + полезное, - вредное, * нейтральное. Выводится также сколько действий определённого знака делалось в количестве дел и по времени (в сумме). Также используются проценты для наглядности- от всего времени учёта, от суток и т.п.

Вся информация заносится в файл src.txt, который создаётся в той же папке, где и сама программа.

 

// Файл Supply.h

#ifndef SUP

#define SUP

 

#include <iostream>

#include <string>

#include <cmath>

#include <Windows.h>

#include <fstream>

using namespace std;

 

// Дизайновые функции для удобства

void _() { cout << endl; }

void _(int _k_) { for(int I=0; I<_k_; I++) cout<<endl; }

void __(int k) {

           if(k==0) system("CLS");

           if(k==1) {

                           _(); system("pause");

           }

           if(k==2) {

                           __(1); __(0);

           }

}

void ___() {

           _(); cout << "________________________________________________________________________________"; _();

}

// перевод строки в число

int CharToInt (char c) { return (int)c - (int)'0'; }

int StringSize (string s) {

           char c= s[0]; int I=0; int stringsize=0;

           while ((int)c != 0) {

                           I++; c=s[I]; stringsize++;

           }

           return stringsize;

}

int StringToInt (string s) {

           int sum=0;

           for (int I=0; I<StringSize(s); I++) sum+=CharToInt(s[I])*pow(10,StringSize(s)-I-1);

           return sum;

}

// процент х от y

double proc (double X, double Y) {

           return X*100/Y;

}

 

// Функции и классы программы

class vakit {

public:

           int hours; int minutes;

};

vakit ToVakit (string x) {

           string Hours= "", Minutes= "";

           int i= 0;

           while (x[i]!=':') Hours+=x[i++];

           i++;

           while (x[i]!='\0') Minutes+=x[i++];

           vakit t;

           t.hours= StringToInt(Hours);

           t.minutes= StringToInt(Minutes);

           return t;

}

 

class geler {

public:

            vakit start, finish; string process; int sign;

            int TimeDist () {

                            return finish.minutes - start.minutes + (finish.hours - start.hours)*60 + 1 ;

            }

};

geler ToGeler (string s) {

           geler g;

           string Time1= "", Time2= "", Process= "";

           int i= 0;

           while (s[i]!=' ') Time1+=s[i++];

           i+=3;

           while (s[i]!=' ') Time2+=s[i++];

           i++;

           while ((int)s[i+1]!=0) Process+=s[i++];

           if(s[i]=='!') g.sign= 3;

           if(s[i]=='+') g.sign= 2;

           if(s[i]=='*') g.sign= 1;

           if(s[i]=='-') g.sign= 0;

           g.process= Process;

           g.start= ToVakit(Time1);

           g.finish= ToVakit(Time2);

           return g;

}

char FromSign (int x) {

           if(x==3) return '!';

           if(x==2) return '+';

           if(x==1) return '*';

           if(x==0) return '-';

}

#endif

 

// Файл main.cpp

#include "Supply.h"

// информация о программе

void program_info() {

           cout << "Счётчик процента времени 2.0 [03.06.2017]" << endl;

           cout << "Автор: Васька Мырт (vk.com/id176208527)" << endl;

           _();

           cout << "Ввод в формате: --> 7:22 - 7:35 зарядка на улице +" << endl;

           cout << "Доступные знаки дел: + - * !" << endl;

           _();

           cout << "[2.0] Вывод дел по убыванию времени" << endl;

           cout << "[2.0] Изменено описание" << endl;

           cout << "[2.0] Чтение из файла" << endl;

           __(2);

}

 

// главная функция

int main() {

           // возможность ввода русских букв

           SetConsoleCP(1251);

           SetConsoleOutputCP(1251);

 

           program_info();

 

           // начальные ячейки памяти разных типов для программы

           geler Buff[1000]; // 1000 берётся тупо для резерва

           double TimeDists[1000]; double TimeAll= 0;

           int q[4]= {0,0,0,0};

           double qTime[4]= {0,0,0,0};

           string s;

           int i=0;

           ifstream f;

               

           cout << "Занесите информацию в файл src.txt в папку с программой" << endl;

           __(1);

 

           // обработка файла, где должна быть информация

           f.open("src.txt");

           if(!f) cout << "Ошибка! Файл не найден" << endl;

           if(f) {

                           while(!f.eof()) {

                                           // обработка данных в самом файле

                                           getline(f,s);

                                           Buff[i]= ToGeler(s);

                                           TimeDists[i]= Buff[i].TimeDist();

                                           TimeAll+= TimeDists[i];

                                           q[Buff[i].sign]++;

                                           qTime[Buff[i].sign]+= TimeDists[i];

                                           i++;

                           }

           }

 

           // слияние одних и тех же действий по времени

           bool *povtor= new bool [i];

           for(int I=0;I<i;I++) povtor[I]= false;

           int povtors= 0;

           string S;

           //

           for(int j=0; j<i-1; j++) {

                           if(!povtor[j]) {

                                           S= Buff[j].process;

                                           for(int k= j+1; k<i; k++) {

                                                           if(Buff[k].process==S) {

                                                                           povtor[k]= true;

                                                                           povtors++; q[Buff[j].sign]--;

                                                           }

                                           }

                           }

           }

           string *NewProc= new string [i-povtors];

           double *NewTimeDists= new double [i-povtors];

           int J=0;

           for(int j=0;j<i;j++) {

                           if(!povtor[j]) {

                                           NewProc[J]= Buff[j].process;

                                           NewTimeDists[J]= TimeDists[j];

                                           J++;

                           }

           }

           J=0;

           for(int j=0; j<i-1; j++) {

                           if(!povtor[j]) {

                                           S= Buff[j].process;

                                           for(int k= j+1; k<i; k++) {

                                                           if(Buff[k].process==S) {

                                                                           NewTimeDists[J]+=TimeDists[k];

                                                           }

                                           }

                                           J++;

                           }

           }

 

           // вывод общей статистики

           ___();

           cout << "Общее время всех дел: " << TimeAll << " мин (" << TimeAll/60 << " ч) {" << proc(TimeAll,(double)1440) << "% от суток}" << endl;

           cout << "Общее количество внесённых дел по списку: " << i << endl;

           for(int j=3; j>=0; j--) {

                           if(q[j]==0) continue;

                           if(j==3) cout << "!!! [";

                           if(j==2) cout << "+++ [";

                           if(j==1) cout << "*** [";

                           if(j==0) cout << "--- [";

                           cout << q[j] << "]";

                           if(qTime[j]<=59) cout << " \t/ " << qTime[j] << " мин";

                           if(qTime[j]>=60) cout << " \t/ " << qTime[j]/60 << " ч";

                           cout << " \t(" << proc(qTime[j],TimeAll) << "%, " << proc(qTime[j],(double)1440) << "%c)" << endl;

           }

           ___();

               

           // сортировка по убыванию времени

           string *SorteProc= new string [i-povtors];

           double *SorteTimeDists= new double [i-povtors];

           double MaxTime; int MaxPozition;

           double *MaxTimes= new double [i-povtors];

           int *MaxPozitions= new int [i-povtors];

           //

           for(int j=0; j<i-povtors; j++) SorteTimeDists[j]= NewTimeDists[j];

           for(int j=0; j<i-povtors; j++) {

                           MaxTime=SorteTimeDists[0]; MaxPozition= 0;

                           for(int _j=1;_j<i-povtors;_j++) {

                                           if(SorteTimeDists[_j]>=MaxTime) {

                                                           MaxTime=SorteTimeDists[_j]; MaxPozition=_j;

                                           }

                           }

                           MaxTimes[j]=MaxTime; MaxPozitions[j]=MaxPozition;

                           SorteTimeDists[MaxPozition]=0;

           }

           for(int j=0; j<i-povtors; j++) SorteProc[j]= NewProc[MaxPozitions[j]];

               

           // вывод отсортированных данных

           cout << "[#] Общие введённые данные:\n";

           for(int j=0; j<i-povtors; j++) {

                           cout << ">> " << SorteProc[j] << endl << "-- -- -- ";

                           if(MaxTimes[j]<=59) cout << MaxTimes[j] << " мин";

                           if(MaxTimes[j]>=60) cout << MaxTimes[j]/60 << " ч";

                           cout << " \t(" << proc(MaxTimes[j],TimeAll) << "%, " << proc(MaxTimes[j],(double)1440) << "%c)" << endl;

           }

           _();

           __(1);

}

 

Теперь разбираемся- что тут происходит. Программа выглядит реально сложно и громоздко, и даже мне самому сейчас тяжело разобраться во всём этом, несмотря на то что я сам это писал. Так не только у меня бывает, а практически у всех кто практикует программирование :)

Мы будем получать строку, например: 7:21 - 7:42 зарядка +

Строку получаем из файла с помощью функции getline. Заносим это всё в строку s. Далее наша задача расщепить строку на составляющие: начальное время, конечное время, само действие и знак этого действия. Для хранения этих 4 разносортных элемента мы вводим класс и называем его geler.

Также нам нужно из строки "7:21" получить само время, а именно- часы и минуты. Чтобы высчитывать сколько минут продолжалось действие. Для хранения часов и минут мы создаём класс vakit.

Для расщепления строки на 4 вышеуказанных куска, используем функцию ToGeler. Она принимает строку, а возвращает объект класса geler. Аналогично функция ToVakit, которая расщепляет строку на часы и минуты, при этом это используется также в более общей функции ToGeler. Продолжительность действия в минутах реализована как метод класса geler и называется TimeDist.

Знак можно было хранить и в char, но тут решено хранить в int, чтобы потом можно было выводить эти знаки в цикле, что более компактно, а также это заметно укомплектовывает подсчёт количеств действий и времён действий данного знака, это можно видеть тут: q[Buff[i].sign]++; qTime[Buff[i].sign]+= TimeDists[i]; Т.е. сам знак, будучи в типе int, является порядковым номером массива-накопителя.

В плане объектно-ориентированной организации, впрочем, это всё. Классы тут используются для более удобного хранения и использования данных, расщепляемых из строки.

 

Прототипы

Для вынесения функций и методов класса куда-то в отдельные места мы можем использовать прототипы.

Например, возьмём класс двумерной точки:

class point {

public:

           double x,y; point (double x1=0, double y1=0) { x=x1; y=y1; }

           double module() { return sqrt(x*x+y*y); }

};

Можно эту функцию модуля вынести за класс таким образом- в самом классе оставить только прототип, а саму функцию прописать снаружи с определённой пометкой:

class point {

public:

           double x,y; point (double x1=0, double y1=0) { x=x1; y=y1; }

           double module();

};

double point::module() { return sqrt(x*x+y*y); }

Вот и всё. Можно также вынести и функцию конструктора класса. Тогда всё будет выглядеть так:

class point {

public:

           double x,y;

           point (double,double);

           double module();

};

double point::module() { return sqrt(x*x+y*y); }

point::point(double x1=0, double y1=0) { x=x1; y=y1; }

Это нужно для того чтобы мы могли выносить все эти функции в отдельный файл. В заголовочном файле, который назовём, скажем, point.h, мы можем оставлять только прототипы- они будут показывать что класс умеет- какие у него методы. Чисто демонстрация функционала без ничего лишнего. А сама реализация этих методов, этого функционала будет в отдельном файле.

Тогда в point.h у нас будет лежать класс с прототипами:

class point {

public:

           double x,y;

           point (double,double);

           double module();

};

И для реализаций мы должны создать файл cpp, но назвать его как заголовочный, т.е. point.cpp. В этом 3-м файле будет уже исполнение:

double point::module() { return sqrt(x*x+y*y); }

point::point(double x1=0, double y1=0) { x=x1; y=y1; }

Ну и естественно, в файле cpp мы должны подключить сам файл h, т.е. #include "point.h" в файл point.cpp тоже нужно написать.

Тогда вся программа будет выглядеть так:

// Файл main.cpp

#include "point.h"

int main() { point a(2,4); cout << a.module() << endl; system("pause"); }

 

// Файл point.h

#include <iostream>

#include <cmath>

using namespace std;

class point { public: double x,y; point (double,double); double module(); };

 

// Файл point.cpp

#include "point.h"

double point::module() { return sqrt(x*x+y*y); }

point::point(double x1=0, double y1=0) { x=x1; y=y1; }

 

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

Например, добавим в файл main.cpp какую-нибудь функцию, пусть будет вычисление квадрата числа. Можно сделать и таким образом:

#include "point.h"

double q(double x);

int main() { point a(2,4); cout << a.module() << endl; system("pause"); }

double q(double x) { return x*x;}

 

Шаблоны классов

Так же как и в случае с функциями, для классов нам тоже могут понадобиться шаблоны. Например, возьмём тот же класс точки, для удобства поместим всё в 1 файл main.cpp. Мы прописали что координаты у нас могут иметь тип double. А если я хочу, например, чтобы координаты были только целые, т.е. типа int, тогда нужно прописывать отдельный класс. А есть же ещё типы float, long long int и прочие- и для каждого придётся создавать новый отдельный класс. Чтобы этого избежать, мы вводим шаблон для класса:

#include <iostream>

#include <cmath>

using namespace std;

// класс двумерной точки с шаблоном

template <typename Type>

class point {

public:

           Type x,y;

           point (Type x1=0, Type y1=0) { x=x1; y=y1; }

           double module() { return sqrt(x*x+y*y); }

};

// главная функция

int main() {

           setlocale(0,"");

           point <int>a(2,4);

           cout << a.module() << endl;

           system("pause");

}

Важный момент: при создании точки нужно указывать уже тип координат, как мы это сделали тут: point <int>a(2,4); Иначе не будет компилироваться и вообще работать.

...

Шаблонов может быть несколько. Например, мы хотим чтобы координаты у нас могли быть разного типа. Тогда сделаем 2 шаблона для каждой координаты:

#include <iostream>

#include <cmath>

using namespace std;

template <typename M, typename N>

class point {

public:

           M x; N y; point (M x1=0, N y1=0) { x=x1; y=y1; }

           double module() { return sqrt(x*x+y*y); }

};

int main() {

           setlocale(0,"");

           point <int,double>a(2,4);

           cout << a.module() << endl;

           system("pause");

}

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

...

Вместо типов typename можно указывать обычные в шаблоне, например:

template <typename M, int n>

И далее в классе это может как-то использоваться. Например, от числа n у нас будет зависеть модуль:

template <typename M, int n>

class point {

public:

           M x; M y;

           point (M x1=0, M y1=0) { x=x1; y=y1; }

           double module() {

                           if(n==1) return sqrt(x*x+y*y);

                           if(n==2) return 0;

           }

};

И далее мы можем вызывать это так:

point <float,2> a(2,4);

cout << a.module() << endl;

Можно задать значение по умолчанию:

template <typename M, int n=1>

И если мы напишем просто point <float> a(2,4); то у нас будет n=1 по умолчанию как мы указали. Если хотим сделать n=2, то надо написать всё же эту метку: point <float,2> a(2,4);

...

По умолчанию могут быть и сами типы, например: template <typename M= int, int n=1>

Только важный момент: точку объявлять нужно так: point<> a(2,4); Оставляем пустые угловые скобки.

 

Наследование классов

Бывает, что мы создаём несколько классов, у которых много общего. Например, в игре это могут быть однотипные персонажи: они могут делать одно и то же, т.е. у них будут 90% общие методы, но при этом они отличаются какой-то мелочью. И тогда для каждого класса нужно писать одно и то же. Объединить классы, в которых много одинаковых атрибутов и методов, позволяет наследование.

Посмотрим опять на геометрических абстракциях, просто тут легко приводить примеры. Сделаем новый тип данных- шар. Т.е. класс шара. Шар у нас зависит от точки центра и радиуса. Также у шара будет площадь поверхности и объём. Естественно, у нас уже будет класс для трёхмерной точки. Этот класс можно сделать даже с шаблоном как мы уже научились. Но для простоты сделаем пока просто типа double.

const double pi= 3.141592653;

class point {

public: double x,y,z; point (double x1=0, double y1=0, double z1=0) { x=x1; y=y1; z=z1; }

};

// сам класс для шара теперь

class sphere {

public:

           point center; double radius;

           sphere (point c1=(0,0,0), double r1=0) { center=c1; radius=r1; }

           double square() { return 4*pi*radius*radius; }

           double volume() { return 4*pi*radius*radius*radius/3; }

};

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

class Msphere {

public:

           point center; double radius; double density;

           Msphere (point c1=(0,0,0), double r1=0, double d1=1) { center=c1; radius=r1; density=d1; }

           double square()          { return 4*pi*radius*radius; }

           double volume() { return 4*pi*radius*radius*radius/3; }

           double mass() { return density*volume(); }

};

А если мы теперь хотим чтобы у нас был и заряженный шар- т.е. у него добавится ещё поверхностная плотность заряда в качестве атрибута и заряд в качестве метода. Нужен новый класс и всё копировать с предыдущего. Возникает много лишней писанины, например методы площади и объёма будут во всех классах. Нельзя ли один раз это где-то написать, а дальше использовать? Да, можно- благодаря наследованию.

Возьмём общий шар без массы и зарядов как родительский класс. И от него пронаследуем шар с массой. Сам родительский класс останется без изменений, а дочерний (шар с массой) будет выглядеть так:

class Msphere : public sphere {

public:

           double density;

           Msphere (point c1=(0,0,0), double r1=0, double d1=1) { center=c1; radius=r1; density=d1; }

           double mass() { return density*volume(); }

};

Т.е. мы уже убрали отсюда атрибуты центра и радиуса и методы площади и объёма, т.к. дочерний класс умеет также всё то что и родительский. Наследование делается, как можешь увидеть, через : public sphere. Конструктор тут новый, поэтому его мы не отнаследовали.

Заряженный шар можно отнаследовать уже от массивного:

class Qsphere : public Msphere {

public:

           double charge_density;

           Qsphere (point c1=(0,0,0), double r1=0, double d1=1, double q1=1) { center=c1; radius=r1; density=d1; charge_density=q1;}

           double charge() { return charge_density*volume(); }

};

И далее можно проверить- создать заряженный шар и посчитать всё:

point a; Qsphere f(a,2); // тут плотности будут по умолчанию равны 1, как указано в конструкторе класса, центр по умолчанию в точке (0,0,0)

cout << f.square() << endl;

cout << f.volume() << endl;

cout << f.mass() << endl;

cout << f.charge() << endl;

Родительские классы должны находиться выше в коде, чем дочерние- это логично. Также мы можем наследовать от нескольких классов. Например, пусть наш заряженный шар будет также ещё и бомбой, у которой есть время взрыва и радиус взрыва. Сделаем класс бомбы:

class bomb {

public:

           int boom_time; double boom_radius;

           bomb (int a=100, double b=1) { boom_time=a; boom_radius=b; }

};

И отнаследуем заряженный шар также от класса бомбы:

class Qsphere : public Msphere, public bomb {

public:

           double charge_density;

           Qsphere (point c1=(0,0,0), double r1=0, double d1=1, double q1=1) { center=c1; radius=r1; density=d1; charge_density=q1;}

           double charge() { return charge_density*volume(); }

};

Т.е. теперь наш заряженный шар может играть также роль бомбы. Единственное, мы в конструкторе не дописали переменные бомбы- это значит что заряженный шар принимает эти значения по умолчанию, которые указаны в конструкторе класса бомбы. Мы сами можем занести эти значения и без конструктора, кстати. Например, так:

point a; Qsphere f(a,2); cin >> f.boom_time;

cout << f.boom_time << endl;

Мы можем создавать также несколько конструкторов класса- например, добавить в заряженный шар конструктор, где не надо указывать центр:

Qsphere (double r1=0, double d1=1, double q1=1) { radius=r1; density=d1; charge_density=q1;}

И тогда можем создавать так: Qsphere f(2); Это значит, что у нас тут радиус равен 2, а плотности (2 следующих аргумента конструктора) остаётся по умолчанию равные 1. Это уже знакомая нам перегрузка функций. Компилятор сам определяет, какой конструктор, по перечисленным аргументам: point, double, double, double или же просто double, double, double. Только просто так создавать Qsphere s; и что-то с этим делать нельзя- компилятор не поймёт, какой из конструкторов использовать и работать не будет. Нужно либо иметь в классе 1 конструктор, либо указывать в скобках при создании нового заряженного шара хоть какие-то аргументы конструктора, по типам которых можно будет определить какой именно конструктор использовать.

...

Ну и тут стоит ещё упомянуть- мы наследовали обычно через public. class Msphere : public sphere. Но можно наследовать и через private, т.е. написать class Msphere : private sphere. Тогда в дочернем классе все наследуемые вещи станут в категории private.

Также помимо public и private есть тип protected, который используется реже. Мы можем наследовать через него. Это значит что в дочернем классе будут доступны атрибуты и методы родительского класса, но вне этих классов не будут. Также в самом классе мы можем помечать какие-то атрибуты и методы в протектед (protected: и дальше эти атрибуты и методы пишем), тогда они будут доступны только в дочерних классах и самом родительском классе, а в остальной программе (снаружи всей этой цепочки классов) не будут. Наследуемые вещи переходят в дочернем классе в категорию protected.

 

Полиморфизм

Рассмотрим теперь ещё одну новую штуку, которая может быть полезной. Например, у нас есть несколько классов и в каждом есть свой метод, который называется одинаково, но делает разные вещи. Чтобы далеко не ходить, оставим эти шары и сделаем для них метод информацию о них чтобы было проще понять. Например, у обычного шара:

void info() { cout << "Обычный шар" << endl; }

И аналогично сделаем для 2 других шаров. Уберём использование центральной точки и бомбы чтобы было проще. Получим:

class sphere {

public:

           double radius;

           sphere ( double r1=0) { radius=r1; }

           double square()          { return 4*pi*radius*radius; }

           double volume() { return 4*pi*radius*radius*radius/3; }

           void info() { cout << "Обычный шар" << endl; }

};

class Msphere : public sphere {

public:

           double density;

           Msphere (double r1=0, double d1=1) { radius=r1; density=d1; }

           double mass() { return density*volume(); }

           void info() { cout << "Массивный шар" << endl; }

};

class Qsphere : public Msphere{

public:

           double charge_density;

           Qsphere (double r1=0, double d1=1, double q1=1) { radius=r1; density=d1; charge_density=q1;}

           double charge() { return charge_density*volume(); }

           void info() { cout << "Заряженный шар" << endl; }

};

Для каждого шара есть свой метод инфо, который выводит разное. Мы можем это протестировать:

sphere s1; Msphere s2; Qsphere s3;

s1.info(); s2.info(); s3.info();

Заметим, что метод у нас называется одинаково, при этом объекты однотипные. Возникает вопрос- можно ли как-то это объединить и использовать info в цикле в массиве? Т.е. в 1 массив объединить обычный шар, массивный и заряженный, а далее их использовать как нужно. Да, это можно сделать с помощью полиморфизма. Чтобы проще было понять, в данном случае полиморфизм- возможность объединить в одном массиве элементы разного типа.

Мы можем использовать динамическую память и указатели, т.е. чтобы объекты ссылались друг на друга. Мы уже такое делали с массивами. Но можно и с обычными объектами. Чуть позже ты увидишь, зачем это нам нужно.

sphere *s1= new sphere; s1->info();

Когда мы работаем с указателями, вместо точки при вызове метода используется стрелочка. А именно, это 2 символа подряд, которые ты можешь видеть.

Суть в том, что мы в родительском классе можем ссылаться на дочерний, т.е. написать так: sphere *s1= new Qsphere; s1->info(); И выведется инфо для обычного шара. Всё осталось так же, поменялось только вместо new sphere стало new Qsphere.

Если мы хотим выводить метод инфо для заряженного шара, используя обычный и ссылки, то при объявлении обычного класса нужно сделать функцию инфо виртуальной, а именно написать вначале ключевое слово virtual. Это будет выглядеть так:

class sphere {

public:

           double radius;

           sphere ( double r1=0) { radius=r1; }

           double square()          { return 4*pi*radius*radius; }

           double volume() { return 4*pi*radius*radius*radius/3; }

           virtual void info() { cout << "Обычный шар" << endl; }

};

Тогда при таком коде: sphere *s1= new Qsphere; s1->info();  выведется уже инфо для Qsphere. Притом что если мы и массивный шар так объявим: Msphere *s2= new Qsphere; s2->info(); то всё равно выведется заряженный шар несмотря на то что в самом Msphere функция info не виртуальная. Т.е. виртуальность, если так можно выразиться, в данном случае тоже наследуется.

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

Далее это мы уже применим всё для полиморфизма:

sphere *m[3];

m[0]= new sphere;

m[1]= new Msphere;

m[2]= new Qsphere;

for(int i=0; i<3; i++) m[i]->info();

При этом нужно сделать виртуальной функцию инфо в нашем родительском классе sphere. И тогда выведется:

Обычный шар

Массивный шар

Заряженный шар

Т.е. как видишь благодаря полиморфизму мы можем объединить в массив объекты разного типа и по одному имени метода делать разные вещи. Вот и вся суть.

Когда одна и та же переменная может быть разных типов, она называется полиморфной. В данном случае *m является полиморфной переменной. Также аналогично есть полиморфные функции- это мы уже видели, в частности, в шаблонах.

Если в языке программирования за переменной чётко фиксирован её тип, то говорят что в языке статитическая типизация данных. Например, мы пишем int a= 6; и всё- переменная a имеет целый тип и никакой другой. В с++ у нас статическая типизация, как и например в языках си, паскаль, java. Задачу полиморфизма в языке с++ мы решаем через наследование классов, виртуальных функций и указателей- т.е. всё достаточно сложно. Но тем не менее такая "полиморфная" возможность есть.

А бывают языки с динамической типизацией, например python или ruby. При динамической типизации не нужно указывать тип переменной- он автоматически определяется. Можно просто написать: a= 6; и всё, компилятор сам поймёт что это целое число. Потом можно изменить значение переменной a на дробное, логическое, строку и т.п. Все переменные при динамической типизации полиморфные и здесь уже всё само по себе может объединяться без дополнительных ухищрений.

Также можно сделать в родительском классе функцию чисто виртуальной, т.е. написать так: virtual void info() = 0; И тогда все дочерние классы обязаны теперь иметь эту функцию info, иначе работать не будет. Т.е. мы можем создавать абстрактный родительский класс из этих виртуальных нулевых функций как источник, каркас- а далее от него наследовать похожие классы, которые обязаны иметь функции, которые в родительском объявлены как виртуальные и нулевые.

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

________________________________________________


Дата добавления: 2018-09-22; просмотров: 150; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!