Лекция 10 Объекты
ООП Инкапсуляция Возможность совместного хранения данных и кода для их обработки Наследование Возможность расширять существующие объекты новыми данными и/или методами Полиморфизм Возможность использовать дочерние объекты там, где требуются родительские
Инкапсуляция Набор данных и функций для их обработки описывается классом Класс является пользовательским типом данных. Объекты такого типа называются экземплярами класса Функции-члены класса называются его методами Данные класса называются его полями
Объявление и определение классов Объявление Как и любое объявление, может встречаться сколько угодно раз в единице трансляции Определение Может повторяться в разных единицах трансляции struct A ; class B ; struct A ; class B ; struct A { int data ; } a ; class B { int foo ( int n ) { return n + a.data ; } A a ; }; struct A { int data ; } a ; class B { int foo ( int n ) { return n + a.data ; } A a ; };
Управление доступом public: открытый доступ К членам класса (данным и методам) возможен доступ из любой точки программы protected: защищенный доступ Доступ к членам класса возможен только из методов класса, а также из методов его потомков private: закрытый доступ Доступ к членам класса возможен только из его собственных методов class B { public: B( int n ) { n_ = n ; } int Foo ( int a ) { return n_ + a ; } protected: void set ( int k ) { n_ = k ; } private: int n_ ; int m_n ; }; class B { public: B( int n ) { n_ = n ; } int Foo ( int a ) { return n_ + a ; } protected: void set ( int k ) { n_ = k ; } private: int n_ ; int m_n ; };
Различие class и struct class По умолчанию все члены данных имеют доступ private. struct По умолчанию все члены данных имеют доступ public. struct A { int data ; float data2 ; }; struct A { int data ; float data2 ; }; class A { public : int data ; float data2 ; }; class A { public : int data ; float data2 ; };
Ключевое слово friend Открывает доступ «для избранных» Ключевое слово friend позволяет указать «дружественную» функцию или класс, которые будут иметь доступ ко всем членам данного класса вне зависимости от их спецификаторов доступа. struct A { friend struct B ; friend void foo ( A &, int ); private : int data ; float data2 ; }; void foo ( A & a, int b ) { a.data = b ; } struct A { friend struct B ; friend void foo ( A &, int ); private : int data ; float data2 ; }; void foo ( A & a, int b ) { a.data = b ; } struct B { A a ; void some () { a.data2 = 3.7f ; } }; struct B { A a ; void some () { a.data2 = 3.7f ; } };
Статические поля и методы Статические члены класса Переменные и функции, которые входят в состав класса, но не являются частью экземпляра этого класса, называются статическими. Определение Статические поля требуют обязательного определения вне класса. такое определение может быть снабжено инициализатором struct Complex { Complex () { re = def ; im = def ; } float re, im ; static float def ; static void set_def ( float d ) { def = d ; } }; // Где-то внутри.cpp float Complex::def = 0.0f ; struct Complex { Complex () { re = def ; im = def ; } float re, im ; static float def ; static void set_def ( float d ) { def = d ; } }; // Где-то внутри.cpp float Complex::def = 0.0f ;
Статические константы Инициализация вне конструктора Статическое константное поле интегрального типа может быть инициализировано константным выражением при объявлении. Если требуется иметь такое поле в памяти, его требуется обязательно определить вне определения класса. class A { static const int c1 =2 ; // ок! static int c2 = 2 ; // не константное поле const int c3 = 2 ; // не статическое поле static const int c4 = f ( 7 ); // инициализатор не константа static const float f = 0.7 ; // Не интегральный тип }; class A { static const int c1 =2 ; // ок! static int c2 = 2 ; // не константное поле const int c3 = 2 ; // не статическое поле static const int c4 = f ( 7 ); // инициализатор не константа static const float f = 0.7 ; // Не интегральный тип };
Паттерн «Singleton» Используется когда необходимо гарантированно иметь не более одного экземпляра некоторого класса. struct Singleton { static Singleton & get() { static Singleton s ; return s ; } int data ; private : Singleton (){} }; Singleton::get().data = 4 ; struct Singleton { static Singleton & get() { static Singleton s ; return s ; } int data ; private : Singleton (){} }; Singleton::get().data = 4 ;
Ключевое слово this Действительно только в пределах нестатических методов класса Действительно только для сконструированных объектов Является указателем на текущий экземпляр класса struct A { void f ( int b ) { a = b ; this->b = b ; } int a, b ; }; struct A { void f ( int b ) { a = b ; this->b = b ; } int a, b ; };
Методы Метод Функция, определенная внутри класса. Имеет доступ ко всем членам класса, без указания каких-либо спецификаторов принадлежности. Объявление Объявление метода класса всегда внутреннее. Невозможно объявить метод класса вне его определения. Внутреннее определение Функция, определенная внутри определения класса, является встраиваемой (inline) и подчиняется всем правилам встраиваемых функций (в том числе правил множественного определения) Внешнее определение Внешнее определение метода подчиняется всем правилам обычных функций, в том числе правилу одного определения. Для внешнего определения функции требуется указать имя соответствующего класса
Внешнее и внутреннее определение struct File { File ( const char * fn ) { f_ = fopen ( fn, rt ); } void Read ( void *, int ); private : FILE * f_ ; }; struct File { File ( const char * fn ) { f_ = fopen ( fn, rt ); } void Read ( void *, int ); private : FILE * f_ ; }; #include File.h void File::Read ( void * data, int size ) { fread ( data, size, 1, f_ ); } #include File.h void File::Read ( void * data, int size ) { fread ( data, size, 1, f_ ); } File.h File.cpp
Константные методы Методы, не изменяющие данные класса struct Date { int day () const { return d ; } int month () const ; int reset () { /*...*/} private: int d, m, y ; }; int Date::month () const { return m++ ; // ошибка! } struct Date { int day () const { return d ; } int month () const ; int reset () { /*...*/} private: int d, m, y ; }; int Date::month () const { return m++ ; // ошибка! } Date d ; std::cout
Ключевое слово mutable Применяется только к данным классов Указывает, что это поле может изменяться, даже если объект константный (или из константной функции) Чаще всего используется для кеширования вспомогательных данных
Конструкторы Конструктор Метод для инициализации данных класса. Имя конструктора всегда совпадает с именем класса. Вызывается непосредственно после того, как сконструированы все базовые классы, но еще не инициализированы данные текущего класса Всегда существует Если для класса не указан конструктор, создается конструктор по умолчанию. Такой конструктор автоматически инициализирует данные класса там, где это возможно. Не единственный В классе может быть более одного конструктора, в этом случае они подчиняются правилам перегрузки. Нельзя вызвать один конструктор из другого. Не возвращает значений
Пример struct Date { Date () {} Date ( const char * ) {} Date ( int d, int m, int y ) {} Date ( long timestamp ) {} private: int day ; int mon ; int year ; }; struct Date { Date () {} Date ( const char * ) {} Date ( int d, int m, int y ) {} Date ( long timestamp ) {} private: int day ; int mon ; int year ; }; struct Undead { const int & c ; }; Undead zombie ; // Ошибка struct Undead { const int & c ; }; Undead zombie ; // Ошибка
Конструктор копирования Является конструктором Имя конструктора копирования совпадает с именем класса. Он имеет единственный аргумент – константную ссылку на экземпляр того же класса. Всегда существует Если конструктор копирования не объявлен, он создается автоматически. В этом случае осуществляется побитовое копирование данных исходного объекта в конструируемый struct A { int data ; }; A a ; A b ( a ); struct A { int data ; }; A a ; A b ( a ); struct A { A ( A const & a ); }; A a ; A b ( a ); // Ошибка struct A { A ( A const & a ); }; A a ; A b ( a ); // Ошибка
Инициализация данных Данные инициализируются в конструкторе В определении конструктора можно указать список инициализации – список начальных значений членов класса. Порядок инициализации Данные инициализируются в том порядке, в котором они объявлены внутри класса. Гарантируется, что в том же порядке данные будут расположены в памяти struct A { A () : a ( 1 ), c ( 12 ), b ( 32 ), e ( d ), d ( 10 ), date ( 2009, 12, 01 ) {} int a, b, c, d, e; Date date ; }; struct A { A () : a ( 1 ), c ( 12 ), b ( 32 ), e ( d ), d ( 10 ), date ( 2009, 12, 01 ) {} int a, b, c, d, e; Date date ; };
Деструкторы Деструктор Метод для деинициализации данных класса. Вызывается непосредственно перед удалением памяти, выделенной под объект. Имя деструктора совпадает с именем класса, с добавлением префикса ~. Обычно служит для удаления дополнительной памяти и освобождения ресурсов. Всегда один У класса всегда существует деструктор. Если таковой не указан, создается автоматически. Независим Деструктор не принимает параметров и не возвращает значений
Конструирование и уничтожение Именованный автоматический объект Создается: каждый раз, когда встречается его объявление Удаляется: при выходе из соответствующего блока Объект в свободной памяти Создается: с помощью оператора new Удаляется: c помощью оператора delete Нестатический объект в составе другого Создается: при конструировании родительского объекта Удаляется: при уничтожении родительского объекта Элемент массива Создается: при создании содержащего его массива Удаляется: при удалении содержащего его массива
Конструирование и уничтожение Временный объект Создается: при вычислении значения выражения Удаляется : по окончании вычисления выражения Локальный статический объект Создается: при первом появлении его определения Удаляется: при завершении программы Глобальный объект Создается: при старте программы Удаляется: при завершении программы Объект, созданный путем размещения Создается: в момент размещения в памяти. Удаляется: вручную (явным вызовом деструктора) Объект в составе объединения Не может иметь конструктора и деструктора
RAII RAII (Resource Acquisition Is Initialization) Паттерн проектирования, при котором получение некоторого ресурса осуществляется в конструкторе, а освобождение – в деструкторе. struct File { File ( const char * fn ) { f_ = fopen ( fn, rt ); } ~File () { fclose ( f_ ); } void Read (... ) {} private : FILE * f_ ; }; struct File { File ( const char * fn ) { f_ = fopen ( fn, rt ); } ~File () { fclose ( f_ ); } void Read (... ) {} private : FILE * f_ ; }; void foo () { //... File f ( in.txt ); f.Read (... ); //... } void foo () { //... File f ( in.txt ); f.Read (... ); //... }
Ключевое слово explicit Запрет неявного вызова конструктора Если в некотором выражении в качестве lvalue выступает объект, у которого объявлен конструктор от одного аргумента, а rvalue – значение того же типа, что и аргумент, осуществляется неявное конструирование объекта. Ключевое слово explicit запрещает подобного рода действия. struct A { A ( int c ) : n_ ( c ) {} private : int n_ ; }; struct B { explicit B ( int c ) : n_ ( c ) {} private : int n_ ; }; struct A { A ( int c ) : n_ ( c ) {} private : int n_ ; }; struct B { explicit B ( int c ) : n_ ( c ) {} private : int n_ ; }; A foo () { A a1 ( 7 ); B b1 ( 9 ); B b2 = 3 ; return 7 ; } B bar () { return 42 ; } A foo () { A a1 ( 7 ); B b1 ( 9 ); B b2 = 3 ; return 7 ; } B bar () { return 42 ; }