Перегрузка операций для класса
Допустим, мы сделали классы для точки и треугольника. И далее хотим вводить и выводить их легко через 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.
________________________________________________
РАЗДЕЛ 3
Дата добавления: 2018-09-22; просмотров: 235; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!