ООП. Пособие по ООП в С++. Создание сложных программных систем Объектно ориентированная технология
Скачать 2.1 Mb.
|
{ тело функции } Спецификатор const нужно указывать как в объявлении, так и в определении постоянной функции - члена. Такая функция может быть вызвана для объектов и константных объектов, вызов не константных функций для константных объектов приведет к ошибке. class CNode { int data; CNode *next; public: CNode ( int d){ data=d; next=0;} void insert_after(CNode *q); int get_data() const {return data ;} CNode *get_next(); }; int main( int argc, _TCHAR* argv[]) { CNode l(0); CNode a(2),b(3),c(4); a.insert_after(&b); b.insert_after(&c); l=a.get_next();cout< { cout< } //константный объект и константный метод const CNode A(2); cout< cout< //ошибка к константному объекту нельзя //применять не константный метод return 0; } 5.8.4.4 Дружественные функции Методы класса используются для реализации свойств объекта. В виде дружественных функции оформляются действия, не представляющие свойства класса, но концептуально входящие в состав класса и нуждающиеся в доступе к его скрытым полям, например, переопределение операции вывода объекта в поток вывода. Правила использования и реализации: • Объявляется внутри функции (только прототип). friend тип функции Имя (список параметров); • В качестве параметра ей должен передаваться объект, ссылка или указатель на объект, так как указатель this ей не доступен. • Дружественная функция может быть внешней функцией или методом другого класса. • Определение дружественной функции осуществляется вне класса, действие спецификатора доступа на нее не распространяется. сlass B{ private: float d; public: B(){d=4;} friend float f(B &O); friend float Add(B &O); //ff – метод касса B }; float f(B &O) { return d*2; } float A::Add(B &O){……} Применение дружественных функций 1) Одну функцию надо сделать доступной двум классам. Например, имеется класс Линейный однонаправленный список и Очередь. Надо реализовать операцию проверки для Линейного списка, входит ли элемент очереди в список, и так же: входит ли элемент очереди в список. class Quele; class List; int find(const List L, const Quele Q) { Tnode *q=L->L; While(q) { While(!Q->Empty()) { if(q->getX()==Q->getx())return 1; Q.pop(); } q=q->next; }return 0; } Можно эту функцию сделать членами обеих классов, но тогда работа приложения будет не эффективной так как, доступ к значению элементов – функции-члены get, используемые в функции, должны выполнять контроль на существование элемента, включенного в проверку. Нельзя сделать одну функцию членами двух различных классов. Но язык С++ предоставляет механизм включения одной внешней функции в различные классы, причем эта функция получает доступ к закрытым и открытым членам класса – эта функция в классе оформляется как друг. class List {protected: struct Tnode { int x; Tnode *next; }; Tnode *L; public: List(){L=0;cout<<"****";} void Insert(int x); int getX(Tnode &q); Tnode * get_List(); friend find(const List &L,const Quele &Q); }; class Quele { …………………. Tnode *Q; friend find(const List &L,const Quele &Q); ………………… } //реализация дружественной функции int find(const List L, const Quele Q) { Tnode *q=L->L; While(q) { While(!Q->Empty()) { if(q->getX()==Q->getx())return 1; Q->pop(); } q=q->next; }return 0; } 2) При реализации перегрузки операций. Если функция-член одного класса должна использоваться в другом классе, т.е. функция член одного класса должна использоваться в другом классе. class List { Tnode *L; public: void output();}; void List::output() { Tnode *q=L; while(q) { cout< } } class Quele { …………………. Tnode *L; friend void List::output(); } Пример реализации классов: include "iostream" #include using namespace std; class student { private: char fam[20]; char group[10]; unsigned short year_zach; char shifr_napravlenia[6]; char shifr_profiliy[6]; char vp_kafedra[40]; public: //конструктор без параметров student() { strcpy(fam, "" );strcpy(group, "" );strcpy(vp_kafedra, "" ); year_zach=0;strcpy(shifr_napravlenia, "" );strcpy(shifr_profiliy, "" ); } //конструктор параметрами student( char *f, char *g, unsigned short y, char * sh_n, char * sh_p, char *vk) { strcpy(fam,f);strcpy(group,g);strcpy(vp_kafedra,vk); year_zach=y;strcpy(shifr_napravlenia,sh_n); strcpy(shifr_profiliy,sh_p); } //деструктор student(){cout<< "delete object" < const char *f){ strcpy(fam,f);} void set_group( const char *g){ strcpy(group,g);} void set_year_zach( unsigned short y){ year_zach=y;} void set_shifr_napravlenia( char *sh_n){ strcpy(shifr_napravlenia,sh_n);} void set_shifr_profiliy( char * sh_p){ strcpy(shifr_profiliy,sh_p);} void set_vp_kafedra( const char *vk){ strcpy(vp_kafedra,vk);} const char * get_fam(){ return fam;} const char *get_group(){ return group;} unsigned short get_year_zach(){ return year_zach;} char * get_shifr_napravlenia(){ return shifr_napravlenia;} char * get_shifr_profiliy(){ return shifr_profiliy;} const char * get_vp_kafedra(){ return vp_kafedra;} //операции с данными объекта //вернуть год поступления, шифр направления, шифр профиля, название выпускающей кафедры по фамилии void fined1( const char *f, char * sh_n, char * sh_p, char *vk); //на каком курсе учится студент int kurs(); char * info() { } }; #include "stdafx.h" #include "student.h" //реализация методов void student::fined1( const char *f, char * sh_n, char * sh_p, char *vk) { if (strcmp(fam,f)==0) { strcpy(sh_n,shifr_napravlenia); strcpy(sh_p,shifr_profiliy); strcpy(vk,vp_kafedra); return; } throw "Это не тот объект" ; } int student::kurs() { time_t t=time(NULL); tm *c=localtime(&t); int y=(c->tm_year+1900)-year_zach; return (y==0)?1:y; } 5.8.5 Методы класса Это статические методы. 5.8.5.1 Статические методы Методы тоже могут быть статическими. Объявление статического метода static загловок метода(){ ……} Правила применения статического метода 1) В определении метода слово static не указывается. 2) Метод, который обращается к статическому полю можно сделать статическим. 3) Если метод статический, то к нему можно обращаться до того, как был создан хотя бы один экземпляр переменной. 4) Статический метод не может обращаться к нестатическим полям класса. 5) Статический метод просто не будет знать, к какому экземпляру обратиться – внутри него нет доступа до указателя this. Оператор this можно использовать только внутри нестатической функции-члена. 6) Для использования статического метода, таким образом, не нужно создавать отдельного экземпляра класса, а обращение ведётся через имя класса и оператор ::. 7) Статические поля и методы, также как и нестатические, могут иметь модификаторы доступа private, protected и public. 8) Для доступа к статической переменной в методе, реализованном вне класса, используется имя: имя класса::имя поля. 9) При использовании статических методов запрещён модификатор const метода, так как метод по определению не может обращаться к нестатическим полям и изменять их. Пример. Применение статического метода к статическому полю class Bank { //переменная для выделения каждому объекту своего номера счета //номер счета данного объекта int nAccountNumber; //текущий баланс double dBalance; protected: static int nNextAccountNumber; public: Bank() { //следующий номер счета nAccountNumber=++Bank1::nNextAccountNumber; Cleare(); //вызывает другой метод } void Cleare() { dBalance=0.0; } static int get_nNextAccountNumber(); int get_nAccountNumber() { return nAccountNumber; } static int get_ nNextAccountNumber(); }; Файл сpp #include "StdAfx.h" #include "Bank1.h" int Bank1::nNextAccountNumber=0; int get_nNextAccountNumber() { return Bank::nNextAccountNumber; } 5.9 Конструкторы класса Это методы, обеспечивающие создание (инициализацию полей) экземпляров класса (объектов). Объект это переменная класса. В классе С++ может быть несколько конструкторов, т.к. для них допускается перегрузка. 5.9.1 Виды конструкторов 1. Конструктор по умолчанию. Если программист не включил в класс ни одного конструктора, то компилятор создаст его автоматически, и он будет вызывается автоматически при определении объекта. Этот метод инициализирует поля данных нулевыми значениями. Примечание. Если класс содержит константные и ссылочные поля, то при использовании конструктора по умолчанию возникнет ошибка, так как этот конструктор не умеет инициализировать такие поля. 2. Явно определенный конструктор Это метод, который явно определен в классе и вызывается автоматически при объявлении экземпляра класса. Такой конструктор реализуется по формату функции, его имя – это имя класса, эта функция не имеет типа, даже void. Имя класса ([список параметров]){ тело конструктора } Виды явно определенных конструкторов: • Без параметров – его называют конструктором по умолчанию. Формат объявления Имя класса() {тело конструктора} Пример объявления в классе явного конструктора, определенного вне класса сlass Droby { private: int a,b; public: Droby() ; //конструктор без параметров }; Droby :: Droby() {a=0;b=1;} //реализация конструктора Пример определения конструктора в классе сlass Droby { private: int a,b; public: Droby() {a=0;b=1;} //реализация конструктора Droby() ; }; • С параметрами – создает объект и инициализирует члены данных значениями параметров. В классе может быть несколько перегруженных конструкторов с различным количеством параметров. Параметры могут быть любого типа, кроме типа этого класса. Формат объявления конструктора с параметрами Имя класса(список параметров); Пример объявления конструктора с параметрами сlass Droby { private: int a,b; public: Droby(int a1, int b1) ; //конструктор с параметрами }; Droby :: Droby(int a1, int b1) {a=a1;b=b1;} //реализация конструктора Формат определения конструктора с параметрами внутри класса Имя класса(список параметров){ операторы инициализирующие поля класса значениями параметров} Пример определения конструктора с параметрами в классе сlass Droby { private: int a,b; public: Droby :: Droby(int a1, int b1) {a=a1;b=b1;} }; 3. Конструктор копирования Стандартный конструктор копирования, который осуществляет поэлементное копирование однотипных объектов. Вызывается при присваивании объектов одного типа. Не стандартный конструктор копирования (создается программистом) включается в класс, в котором есть динамически создаваемые члены данных. Такой конструктор имеет один параметр – ссылку на объект своего класса. Пример применения копирующего конструктора using namespace std; class massiv { private: int n; int *x; public: massiv( int n1){x= new int [n1]; n=n1;} massiv(){x=0; n=0;} massiv( const massiv &O);//копируюий конструктор int getN(); void input(); void output(); void chenge(); //применение копируюего конструктора void operator +(massiv &O1) { for ( int i=0; i *( int a,massiv &O); //friend massiv operator +(massiv &O1,massiv &O2); friend ostream& operator <<(ostream &os,massiv &O); }; 5.9.2 Правила по применению конструктора Параметры конструкторов подчиняются тем же правилам о типах параметров, что и все остальные функции. Так как конструкторы различаются по типам своих параметров, транслятор способен правильно выбрать конструктор. Конструкторы нельзя определять с спецификатором: virtual, const, static. Конструктор глобального объекта вызывается до функции main. Локальные объекты создаются конструктором, как только становится активной область их действия. Конструктор вызывается автоматически при передаче объекта из функции (результат какой-то функции – тип класса). Конструкторы не наследуются. Пример конструкторы в классе Рациональное число #include "iostream" using namespace std; class Droby { private: int a,b; int Nod(); //реализация вне класса public: Droby(int a1, int b1); //объявление конструктора с параметрами Droby(){a=0;b=1;} // с реализацией в классе void Add(Droby &d); void set_a(int a1){a=a1;} void set_b(int b1){b=b1;} int get_a(){return a;} int get_b(){return b;} Droby& Sub(Droby &d); }; //реализация объявленных методов Droby::Droby(int a, int b) { this->a=a; this->b=b;int N=Nod();this->a= this->a/N; this->b= this->b/N; } 5.10 Создание и инициализация объектов (экземпляров класса) Экземпляр (или объект) - это переменная типа класс, созданная конструктором класса. Конструктор класса будет вызван (создаст объект) в следующих случаях: int main() { Droby R1; // вызов конструктора без параметров Droby R2(1,2),R3(2,3); вызов конструктора с параметрами } Экземпляр класса без конструктора может инициализироваться присваиванием ему другого объекта этого же класса. Это не запрещено и в том случае, когда конструкторы в классе определены. int main() { Droby R2(1,2),R3(2,3); Droby R4=R2; //инициализация присваиванием Droby *R5=new Droby(R4); } Алгоритм выполнения присваивания выглядит так, конструктор создает объект R4, а затем в него переписываются значения R2 (просто присваивание). Экземпляр (объект) это переменная данного класса. А класс – тип. Следовательно, переменной будет выделена память в объеме равном количеству байтов под все не статические переменные объекта – члены – данных. Пример для экземпляра D класса Droby будет выделено 8 байт – все переменные не статические int main( int argc, _TCHAR* argv[]) { Droby D(2,3); cout< } Пример для экземпляра класса Droby будет выделено 8 байт, для статической переменной z память в экземпляре не выделяется class A{ static int z; private: int x,y; }; int _tmain(int argc, _TCHAR* argv[]) { A O(); int a; std::cout< 5.11 Деструктор класса Метод, обеспечивающий правильное удаление объектов. Деструктор по умолчанию. Присутствует неявно в каждом классе. Вызывается автоматически, когда объект выходит из области видимости. Видимость объектов определяется по правилам видимости локальных, глобальных, статических переменных. Явно определенный деструктор. Требуется разработать в классе, если среди членов данных имеются динамические переменные, которые удаляются другими средствами. Формат объявления деструктора Имя класса ([список параметров]){тело деструктора} Может быть реализован в классе или вне класса Пример определение и вызов явно определенного деструктора для линейного динамического списка #include "iostream" using namespace std; class CNode { int data; CNode *next; public: CNode ( int d){ data=d; next=0;} CNode(); void insert_after(CNode *q); int get_data() const ; CNode *get_next(); }; CNode::CNode() //деструктор класса CNode { cout<< "next" ; delete next; } int main( int argc, _TCHAR* argv[]) { CNode list(0),*l(0); { CNode a(2),b(3),c(4); //область действия объектов } //завершение действия имен a b c и удаление этих объектов //вызов деструктора три раза return 0; } Результат программы Пример применения деструктора по умолчанию при выходе объекта из области видимости. Потеря объекта. Droby Droby::Sub(Droby &d) { Droby R; R.a= a*d.b-b*d.a; R.b=b*d.b; int N=R.Nod(); R.a/=N; R.b/=N; return R; } Объект R представляет локальную переменную, которая по завершении метода будет удалена из памяти. 5.12 Применение методов к объектам Формат применения метода к объекту Имя объекта . Имя метода ([список параметров]) Метод применяется к объекту, стоящему в вызове слева. Пример выполнения операций с рациональными дробями int main() { Droby R1; R1.set_a(5); //установить значение числителя объекта R1 R1.set_b(6); //установить значение знаменателя объекта R1 Droby R2(1,2),R3(2,3); R1.Add(R2); //к дроби R1 прибавить дробь R2 результат в R1 Droby R=R1; cout< //вычесть из R дробь R3 //результат в R, а затем в R4 } Пример реализации взаимодействия объектов Диаграмма классов Реализация #pragma once #include "Circle.h" class Menu { private: Circle C; public : static void outparametrs( int r); }; #include "Menu.h" #include Menu::outparametrs( int r){ C.set_r(r); std::cout<< "Perimetr=" < < Circle { private : double r; static const double pi; public : Circle():r(0){}; Circle( double r1); double S(); double P(); }; #include "Circle.h" const double Circle::pi=3.14; Circle::Circle( double r1) { |