Лекция-2.01.ppt
- Количество слайдов: 40
Класс как основной механизм абстракции Лекция 10. 02. 2014 г. 1
Основная задача классов — предоставить программисту инструмент для создания новых типов, которые можно без ограничений использовать в программе наряду со встроенными (предопределенными в языке) типами. Новые типы создаются для воплощения концепций, которые не выражаются непосредственно (или адекватно) встроенными типами. Классы – это новшество, появившееся в языке С++. Для определения класса используется новое ключевое слово class. Однако и унаследованные из языка С конструкции: структура (struct), перечисление (enum) и объединение (union) также являются средствами создания новых типов. Лекция 10. 02. 2014 г. 2
Что может размещаться внутри объявления класса В классе размещаются: Вложенные типы Члены класса Данные (поля) Функции Переменные Методы Константы Конструктор по умолчанию Деструктор Копирующий конструктор Функции-операторы Конструктор преобраз. типа Функции преобразования типов Конструктор с параметрами Лекция 10. 02. 2014 г. 3
Рассмотрим пример, приведенный ранее при изучении языка С: динамически расширяемый массив (Vector). Реализуем его, как и в языке С, с помощью структуры, но с учетом того, что структура в языке С++ – это полноценный тип, членами которого являются не только данные, но и функции. Vector + sz : int = 0 + elem : double* = NULL + space : int = 0 + Init() : void + Resize(int) : void + Qsort(int, int) : void + reserve(int) : void + swap(int, int) : void + Print() : void space sz elem[0] elem[1] elem[…] elem[sz-1] … Лекция 10. 02. 2014 г. … … 4
Реализация типа Vector с помощью структуры. //Vector. h - объявление типа Vector struct Vector { int sz; double* elem; int space; void reserve(int); void swap(int, int); void Init(); void Resize(int); void Push_back(double); void Qsort(int, int); void Print(); }; //Vector. cpp – реализация типа Vector #include <cstdlib> #include <iostream> #include "Vector. h" using namespace std; void Vector: : Init() { sz = space = 0; elem = NULL; } void Vector: : Print() { for(int i = 0; i < sz; ++i) cout << "v[" << i << "]=" << elem[i] << " "; cout << "n--------n"; } Лекция 10. 02. 2014 г. 5
Реализация типа Vector с помощью структуры. void Vector: : reserve(int newalloc) { if (newalloc <= space) return; double* p = new double[newalloc]; for (int i=0; i<sz; ++i) p[i] = elem[i]; if(elem) delete[] elem; elem = p; space = newalloc; } void Vector: : Resize(int newsize) { reserve(newsize); for(int i = sz; i < newsize; ++i) elem[i] = 0; sz = newsize; } void Vector: : Push_back(double d) { if (sz == 0) reserve(8); else if (sz == space) reserve(2*space); elem[sz++] = d; } Лекция 10. 02. 2014 г. 6
Реализация типа Vector с помощью структуры. void Vector: : swap(int i, int j) { double x; x = elem[i]; elem[i] = elem[j]; elem[j] = x; } void Vector: : Qsort(int left, int right) { int last = left; if (left >= right) return; swap(left, (left + right)/2); for (int i = left + 1; i <= right; i++) if (elem[i] < elem[left]) swap(++last, i); swap(left, last); Qsort(left, last-1); Qsort(last+1, right); } Лекция 10. 02. 2014 г. 7
Тестирование типа Vector, реализованного структурой. //Ex 060. cpp - тестирование типа Vector #include <cstdlib> #include "Vector. h" int main() { Vector z; z. Init(); for(int i = 0; i < 20; ++i) z. Push_back((double)((20 -i)*(20 -i))); z. Print(); z. Qsort(0, 19); z. Print(); z. Resize(25); z. Print(); z. Resize(5); z. Print(); z. Push_back(123. 45); z. Print(); system("PAUSE"); return 0; } Лекция 10. 02. 2014 г. 8
Анализ примера реализации типа Vector с помощью структуры Главный недостаток этой реализации – общедоступность всех элементов (членов структуры), позволяющая иметь открытый доступ к «внутреннему устройству» (реализации) вектора. Основные цели разграничения доступа к членам класса: üобеспечение связывания функций-членов класса с переменными-членами класса таким образом, чтобы только эти функции непосредственно зависели от представления класса и имели непосредственный доступ к переменнымчленам класса; üобеспечение инкапсуляции, т. е. отделения внешнего интерфейса класса от его реализации. Члены класса, объявленные с модификатором доступа private (закрытые члены класса), могут использоваться только методами данного класса. К членам класса, объявленным с модификатором public (открытым членам класса), разрешен доступ не только функциям-членам класса, но и обращение из-за пределов объявления класса (из внешних функций или методов других классов). Поэтому такие члены класса образуют открытый интерфейс класса. Лекция 10. 02. 2014 г. 9
Порядок объявления открытых и закрытых членов класса Язык C++ не регламентирует порядок следования членов класса в определении. Допускается также существование в определении класса нескольких секций с одинаковыми правами доступа. Можно сначала объявлять открытые члены класса (т. е. интерфейс класса), а затем — члены класса с ограниченным или закрытым доступом. Это обеспечит большее удобство восприятия кода. Лекция 10. 02. 2014 г. 10
Выполним для рассмотренного выше примера разграничение доступа к членам типа Vector, т. е. разделим «интерфейс» и «реализацию» . Vector - sz : int = 0 - elem : double* = NULL - space : int = 0 +Init() : void +Resize(int) : void +Num() : int +Get. Val(int) : double +Set. Val(int, double) : void +Qsort(int, int) : void - reserve(int) : void - swap(int, int) : void + Print() : void Расширим также функциональность типа Vector, добавив в интерфейс методы, обеспечивающие прямой доступ «извне» к элементам вектора. Лекция 10. 02. 2014 г. 11
Теперь объявление класса Vector будет выглядеть так: //Vector. h - объявление типа Vector struct Vector { private: int sz; double* elem; int space; void reserve(int); void swap(int, int); public: void Init(); void Set. Val(int, double); double Get. Val(int); int Num(); void Resize(int); void Push_back(double); void Qsort(int, int); void Print(); }; Заметим, что в языке С++ ключевые слова struct и class практически равнозначны, однако для объявления классов рекомендуется использовать class. Лекция 10. 02. 2014 г. 12
Приведем реализацию новых функций, а также функции Print: . . . int Vector: : Num() { return sz; } double Vector: : Get. Val(int i) { if( (i < 0) || (i >= sz)) return 0. ; return elem[i]; } void Vector: : Set. Val(int i, double x) { if( (i < 0) || (i >= sz)) return; elem[i] = x; } void Vector: : Print() { for(int i = 0; i < Num(); ++i) cout << "v[" << i << "]=" << Get. Val(i) << ", "; cout << "n--------n"; } Заметим, что функция Print теперь не требует доступа к закрытым членам класса, поэтому ее можно сделать внешней функцией и исключить из интерфейса класса Vector. Лекция 10. 02. 2014 г. 13
Модификация типа Vector: Vector - sz : int = 0 - elem : double* = NULL - space : int = 0 +Init() : void +Resize(int) : void +Num() : int +Get. Val(int) : double +Set. Val(int, double) : void +Qsort(int, int) : void - reserve(int) : void - swap(int, int) : void Print(Vector* v) { for(int i = 0; i < v->Num(); ++i) cout << "v[" << i << "]=" << v->Get. Val(i) << ", "; cout << "n--------n"; } Лекция 10. 02. 2014 г. 14
Теперь заголовочный файл Vector. h будет таким: //Vector. h - объявление типа Vector class Vector { private: int sz; double* elem; int space; void swap(int, int); void reserve(int); public: void Init(); void Set. Val(int, double); double Get. Val(int); int Num(); void Resize(int); void Push_back(double); void Qsort(int, int); }; void Print(Vector*); Лекция 10. 02. 2014 г. 15
//Ex 060. cpp #include <cstdlib> #include <iostream> #include "Vector. h" using namespace std; int main() { Vector z; z. Init(); for(int i = 0; i < 20; ++i) z. Push_back((double)((20 -i)*(20 -i))); Print(&z); z. Qsort(0, 19); Print(&z); z. Resize(25); Print(&z); z. Resize(5); Print(&z); z. Push_back(123. 45); Print(&z); cout << "[" << 2 << "]=" << z. Get. Val(1) << endl; system("PAUSE"); return 0; } Лекция 10. 02. 2014 г. 16
Инициализация объектов. Конструктор класса При определении экземпляра класса (объекта), возникает важный вопрос о его инициализации (т. е. установки объекта в начальное состояние). Для этого мы использовали специальную функцию-член класса Init() : void Vector: : Init() { sz = space = 0; elem = NULL; } Этот подход не вполне надежен: программист может или забыть проинициализировать объект, или проинициализировать его более одного раза. Проблема инициализации объектов в ООП решается таким образом, что в составе любого класса обязательно имеется хотя бы одна функция специального вида, которая называется конструктором. Особая роль конструктора дополнительно подчеркивается тем, что конструктор имеет имя, совпадающее с именем класса. Кроме того, конструктор никогда не имеет возвращаемого значения. Лекция 10. 02. 2014 г. 17
Пример объявления класса с конструктором //Vector. h - объявление класса Vector class Vector { private: int sz; double* elem; int space; void swap(int, int); void reserve(int); public: Vector(); // конструктор void Set. Val(int, double); double Get. Val(int); int Num(); void Resize(int); void Push_back(double); void Qsort(int, int); }; . . . Vector: : Vector() { sz = space = 0; elem = NULL; } Лекция 10. 02. 2014 г. 18
Инициализация объектов В отличие от функции Init(), вызов конструктора гарантирован: конструктор вызывается всегда при попадании объекта в область видимости или при размещении объекта в динамической памяти посредством операции new, например: //Ex 060_Vector. cpp #include <cstdlib> #include <iostream> #include "Vector. h" using namespace std; . . . int main() { Vector z; // z. Init(); - не нужно!!!. . . } Лекция 10. 02. 2014 г. 19
Альтернативные способы инициализации объектов. Перегрузка конструкторов Так же как и любые другие функции языка C++, конструктор можно перегружать. Поэтому класс может иметь несколько конструкторов с различными списками параметров. Это позволяет иметь несколько вариантов инициализации объекта. Лекция 10. 02. 2014 г. 20
Пример класса с двумя конструкторами //Vector. h - объявление класса Vector class Vector { private: int sz; double* elem; int space; void swap(int, int); void reserve(int); public: Vector(int n = n_min); // конструктор 1 Vector(double*, int); // конструктор 2 void Set. Val(int, double); double Get. Val(int); int Num(); void Resize(int); void Push_back(double); void Qsort(int, int); }; Лекция 10. 02. 2014 г. 21
Пример класса с двумя конструкторами Vector: : Vector(int n) { // конструктор 1 sz = 0; if(n > 0) { elem = new double[n]; space = n; } else { elem = NULL; space = 0; } } Vector: : Vector(double* x, int n) { // конструктор 2 elem = new double[n]; space = sz = n; for(int i = 0; i < n; i++) elem[i] = x[i]; }. . . Vector v 1(4); double z[] = {2, 5, 8, 12, 15, 18}; Vector v 2(z, sizeof z/sizeof z[0]); Лекция 10. 02. 2014 г. 22
Конструктор по умолчанию Конструктор, допускающий вызов без аргументов, называется конструктором по умолчанию. Если программист объявил несколько конструкторов, но среди них нет ни одного конструктора по умолчанию, транслятор генерирует сообщение об ошибке при попытке создания объекта без аргументов, например: class A { int x; public: A (int z): x(z) {} }; int main(void) { A a 1(2); A a 2; // Ошибка! return 0; } class A { int x; public: A (int z = 0): x(z) {} }; int main(void) { A a 1(2); A a 2; // Нет ошибки return 0; } Лекция 10. 02. 2014 г. 23
Если программист не объявил ни одного конструктора, то конструктор по умолчанию создается транслятором автоматически, например: class A { int x; }; int main(void) { A a 2; // Ошибки нет return 0; } Лекция 10. 02. 2014 г. 24
Разрушение среды функционирования объектов. Деструкторы Конструкторы отвечают за инициализацию объектов. Иногда создание объекта предполагает помимо прочего захват каких-либо ресурсов (например, резервирование динамической памяти). Это означает, что для таких классов необходимо наличие функции, которая будет гарантированно (как и в случае с конструктором) вызываться по окончании использования объекта. Окончание использования объекта может быть связано с выходом его из области видимости или с освобождением динамической памяти, занятой объектом, например: { Vector v 1(50); } // объект v 1 вышел из области видимости Vector *pv 2 = new Vector (100); . . . delete pv 2; // освобождение памяти, занятой pv 2 Лекция 10. 02. 2014 г. 25
Корректное разрушение среды, в которой функционирует объект, обеспечивается специальной функцией-членом класса, которая называется деструктором. Класс может иметь не более одного деструктора. Как правило, деструкторы вызываются неявно. Деструктор не может иметь ни параметров, ни возвращаемого значения. Имя деструктора образуется присоединением знака "тильда" к имени класса. class Vector {. . . public: ~Vector() { sz = space = 0; delete[] elem; } }; Лекция 10. 02. 2014 г. 26
Проверка работы деструктора. //Ex 060_Vector. cpp #include <cstdlib> #include <iostream> #include "Vector. h" using namespace std; int main() { Vector *z = new Vector(10); for(int i = 0; i < 10; ++i) z->Push_back((double)((10 -i)*(10 -i))); Print(z); z->Qsort(0, z->Num()-1); Print(z); delete z; { double x[] = {2, 5, 8, 12, 15, 18}; Vector v(x, sizeof x/sizeof x[0]); Print(&v); v. Push_back(123. 45); Print(&v); } system("PAUSE"); return 0; } Лекция 10. 02. 2014 г. 27
Константные члены класса Существует две разновидности константных членов класса: константные данные и константные функции. Константные данные используются для представления констант, действующих в области видимости класса и независимых для каждого объекта этого класса. Константные функции-члены класса не могут изменить состояние объекта. Они могут вызывать только другие константные функции-члены класса. Попытки вызвать функцию, относительно которой нельзя гарантировать неизменность объекта после ее работы, блокируются компилятором. Лекция 10. 02. 2014 г. 28
Пример использования константы // Im. String. h class Immutable. String { const int length; // Фактическая длина строки - константа char *value; public: Immutable. String( const char *p. Str = "" ) : length( strlen(p. Str) ) // Инициализация константы { value = new char[ length + 1]; assert( value != 0 ); strcpy( value, p. Str ); } void Print. Ln() const { // Константная функция cout << """ << value << """ << endl; } ~Immutable. String () { delete [] value; } //. . . }; Лекция 10. 02. 2014 г. 29
Константные члены класса Значение нестатической константы-члена класса может зависеть от аргументов, переданных конструктору, поэтому невозможно записать ее инициализацию подобно инициализации обычной константы (в точке объявления с использованием знака присваивания). С другой стороны, внутри конструктора нельзя поместить присваивание константе какого-либо значения (это следует из определения константы). Поэтому для инициализации констант, а также и других требующих инициализации членов класса используется специальный синтаксис инициализаторов. Immutable. String( const char *p. Str = "" ) : length( strlen(p. Str) ) // Инициализация константы {. . . } Лекция 10. 02. 2014 г. 30
Статические члены класса В классе можно объявить статические переменные и статические константы, которые, являясь элементами класса, не являются элементами экземпляра класса (объекта), т. е. они являются общими для всех объектов данного класса. Статические члены класса существуют в области видимости класса и обеспечивают независимость от состояния и поведения отдельного объекта. Классический пример использования статических данных – подсчет числа объектов одного типа, одновременно находящихся в памяти. Лекция 10. 02. 2014 г. 31
Пример класса со статическими членами //Vector. h - объявление класса Vector class Vector { private: int sz; double* elem; int space; static const int DEFAULT_SPACE; static int counter; // Счетчик объектов void swap(int, int); void reserve(int); public: Vector(int = DEFAULT_SPACE); // конструктор 1 Vector(double*, int); // конструктор 2 ~Vector(); // деструктор void Set. Val(int, double); double Get. Val(int); int Num(); void Resize(int); void Push_back(double); void Qsort(int, int); static int Get. Counter(); }; void Print(Vector*); Лекция 10. 02. 2014 г. 32
Пример класса со статическими членами //Vector. cpp. . . const int Vector: : DEFAULT_SPACE = 10; int Vector: : counter = 0; int Vector: : Get. Counter(){ return counter; }. . . void Print(Vector* v) { if(v->Num() == 0) cout << "Vector is empty. "; for(int i = 0; i < v->Num(); ++i) cout << "v[" << i << "]=" << v->Get. Val(i) << ", "; cout << "n--------n"; } Лекция 10. 02. 2014 г. 33
К статическим членам класса нужно обращаться без указания имени объекта, используя в качестве квалификатора имя класса. В приведенном примере функция Get. Counter() также объявлена статической. Статический метод класса — это функция, которой требуется доступ к членам класса, но не требуется, чтобы она вызывалась для конкретного объекта класса. Поэтому статические функции не могут обращаться к нестатическим членам класса. Функция Get. Counter() работает только со статическими данными, поэтому она и не обязана существовать в контексте объекта. Кроме того, может потребоваться обратиться к ней даже в том случае, когда ни одного объекта типа Vector еще не размещено в памяти. Лекция 10. 02. 2014 г. 34
Тестирование класса со статическими членами //Ex 060_Vector. cpp #include <cstdlib> #include <iostream> #include "Vector. h" using namespace std; int main() { cout << "Counter=" << Vector: : Get. Counter() << endl; Vector *z = new Vector(10); cout << "Counter=" << z->Get. Counter() << endl; for(int i = 0; i < 10; ++i) z->Push_back((double)((10 -i)*(10 -i))); Print(z); z->Qsort(0, z->Num()-1); Print(z); delete z; cout << "Counter=" << Vector: : Get. Counter() << endl; { double x[] = {2, 5, 8, 12, 15, 18}; Vector v(x, sizeof x/sizeof x[0]), r(100), w; cout << "Counter=" << r. Get. Counter() << endl; Print(&v); Print(&r); Print(&w); } cout << "Counter=" << Vector: : Get. Counter() << endl; system("PAUSE"); return 0; } Лекция 10. 02. 2014 г. 35
Члены класса, требующие обязательную инициализацию Обязательную инициализацию требуют следующие члены класса: константы; ссылки; объекты, не имеющие конструктора по умолчанию. Для таких членов класса нельзя написать операцию присваивания, поэтому их инициализация производится с использованием синтаксиса инициализаторов: class X { int b; public: Х( int a ) { b = a; } }; class T { const int a; X m. X; Х& mref. X; public: Т( int i, int j, X& ref. X ) : а( i ), m. X( j ) , mref. X( ref. X ) { } }; Лекция 10. 02. 2014 г. 36
Константный указатель this Часто возникает необходимость возвратить из функции-члена класса ссылку на объект-экземпляр такого класса. Для доступа из метода класса к объекту класса, для которого вызван метод, в языке C++ существует служебное слово this, означающее объявленный неявно указатель на объект, для которого вызвана функция-член класса. Фактически каждая нестатическая функция-член класса My. Complex имеет дополнительный «скрытый» параметр My. Complex *const this. Лекция 10. 02. 2014 г. 37
Пример: класс «комплексные числа» : //My. Complex. cpp #include <cstdlib> #include <iostream> using namespace std; class My. Complex { double re; double im; public: My. Complex( double real =0. 0, double image =0. 0) { re = real; im = image; } double Get. Re() const { return re; } double Get. Im() const { return im; } My. Complex& Add( const My. Complex& arg 2 ); My. Complex& Sub( const My. Complex& arg 2 ); My. Complex& Mult( const My. Complex& arg 2 ); }; My. Complex& My. Complex: : Add( const My. Complex& arg 2 ) { re += arg 2. re; im += arg 2. im; return *this; } My. Complex& My. Complex: : Sub( const My. Complex& arg 2 ) { re -= arg 2. re; im -= arg 2. im; return *this; } Лекция 10. 02. 2014 г. 38
Пример: класс «комплексные числа» (продолжение) My. Complex& My. Complex: : Mult( const My. Complex& arg 2 ) { double x = re * arg 2. re - im * arg 2. im; double y = re * arg 2. im + im * arg 2. re; re = x; im = y; return *this; } void Print(const My. Complex& z) { cout << z. Get. Re() << "+" << z. Get. Im() << "*in"; } int main() { My. Complex x(1. , 2. ), y(3. , 4), z(0. , 1. ), t(2. , 3. ); cout << "x="; Print(x); cout << "y="; Print(y); cout << "z="; Print(z); cout << "t="; Print(t); x. Add(y). Sub(z). Mult(t); // цепочка действий cout << "x="; Print(x); system("PAUSE"); return 0; } Лекция 10. 02. 2014 г. 39
Встроенные функции-члены класса Обычная функция C++ может быть объявлена встроенной, или подставляемой, с помощью спецификатора inline, помещаемого в начале заголовка функции. Этот спецификатор указывает компилятору на то, что в месте вызова функции следует попытаться (если это возможно) встроить код функции, а не обращение к ней. Для объявления функции-члена класса в качестве встроенной имеются два способа: üпоместить определение функции в пределах определения класса; üобъявить функцию с явным спецификатором inline с последующим её определением за пределами класса. Лекция 10. 02. 2014 г. 40
Лекция-2.01.ppt