Главная страница
Навигация по странице:

  • 2.5.2

  • 2.7.1

  • 2.7.2

  • 2.7.3

  • 2.7.4

  • 2.7.5

  • Объектноориентированное программирование


    Скачать 1.73 Mb.
    НазваниеОбъектноориентированное программирование
    Дата21.11.2018
    Размер1.73 Mb.
    Формат файлаpdf
    Имя файлаOOP-PrePrint.pdf
    ТипКонспект
    #57177
    страница2 из 15
    1   2   3   4   5   6   7   8   9   ...   15
    2.5.1
    Аргументы по-умолчанию
    Аргумент по-умолчанию – это то, о чем мечтали программисты C: чтобы иногда не надо было при вызове задавать некоторые параметры, которые в этом случае должны иметь некоторое «обычное» значение: void f(int x, int y=5, int z=10); void g(int x=5, int y); /* Неправильно! По умолчанию задаются только последние аргументы
    */ f(1); // будет вызвано f(1, 5, 10) f(1, 2); // будет вызвано f(1, 2, 10) f(1, 2, 3); // будет вызвано f(1, 2, 3)
    Желание программистов C контролировать типы параметров в define
    -ах породило в C++ inline
    -функции. Такая функция – это обычный define с параметрами, но без использования символов «
    \
    » и с проверкой типов.
    Желание узаконить в параметрах define имя типа породило template
    Главный плюс template
    – то, что
    #define с одинаковыми параметрами по- родит два одинаковых куска кода. А template в компиляторе скорее всего будет с оптимизирован: одинаковые куски кода будут соединены в один.
    Имеется больший контроль типов по сравнению с
    #define
    2.5.2
    Ссылки и указатели
    В C++ ссылка – это простой ссылочный тип, менее мощный, но более безопасный, чем указатель, унаследованый от языка Си. Если мы объявили переменную, то для хранения значений выделяется память. Чтобы изменить или прочитать значение переменной (то есть значение находящейся в этой области памяти), мы обращаемся по имени этой переменной. В языке Cи имя сущности (переменной, типа, функции и т.д.) – это идентификатор. С точки зрения программиста, объявляя ссылку (или же указывая, что она бу- дет возвращаемым значением или аргументом функции), мы задаём альтер- нативный идентификатор для уже созданного объекта. В языке Cи ссылок нет. С точки зрения реализации, ссылка – это, по сути, указатель, который жестко привязан к области памяти, на которую он указывает, и который ав- томатически разыменовывается, когда мы обращаемся по имени ссылки.
    Например: int a; // переменная типа int, размещеннная по адресу 0xbfd86d6c int &ra = a; // альтернативное имя для переменной по адресу 0xbfd86d6c

    15 cout << &a << “\n” << &ra << “\n”;
    Приведенному выше коду будет соответствовать следующий вывод:
    0xbfd86d6c
    0xbfd86d6c
    То есть оба имени a
    и ra привязаны к одному и тому же адресу.
    Ссылки нельзя объявлять без привязки к переменной (то есть не иници- ализировав при объявлении). После объявления ссылки её невозможно при- вязать к другой переменной.
    Важно отличать ссылки от оператора взятия адреса
    &
    (address of). Опера- тор взятия адреса используется для уже созданного объекта с целью полу- чить его адрес (то есть адрес области памяти, где хранятся значения), а ссылка это только задание альтернативного имени объекта (с точки зрения программиста, а не реализации). Например: int a = 3; // переменная типа int размещена по адресу 0xbfd86d6c int *p = &a; /* указатель типа int* с именем "p" по адресу 0xbf971c4c, значение этого указателя - адрес объекта с именем "a" - 0xbfd86d6c (это значение можно будет менять): */ p = &b;
    Отличие указателя от ссылки в том, что получить само значение пере- менной, на которую указывает указатель, можно только выполнив опера- цию разыменовывания
    *
    (символ «
    *
    » в объявлении является объявлением указателя, а при применении к уже созданной переменной является опера- тором разыменовывания). Например: int a = 3; int *p = &a; // объявили, создали, и инициализировали объект cout << *p << '\n'; /* здесь к уже созданному объекту с именем "p" применяется оператор "*", который означает “считать значение из "p", которое является адресом и далее считать данные по этому адресу” */
    Итого есть два оператора:
    *
    и
    &
    . Первый по данному адресу, который хранится в переменной типа int*
    , возвращает собственно данные, располо- женные по этому адресу. Второй по данной переменной узнаёт её адрес в памяти.
    2.6
    Ключевое слово static
    В С/С++ имеется пять применений ключевого слова static static int i = 0; static void foo() {}

    16
    Глобальная переменная, объявленная с ключевым словом static
    , будет иметь internal linkage
    , т.е. она будет объявлена глобальной только в рам- ках одной единицы трансляции (чаще всего, такой единицей трансляции яв- ляется файл). Таким образом использование static помогает избавиться от ошибок линковки из-за того что в разных объектах трансляции были объяв- лены глобальные переменные с одинаковыми именами. void foo() { static int i = 0;
    }
    Заметим, что наличие глобальных переменных скорее всего свидетель- ствует об ошибках проектирования. В крайнем случае следует использовать синглтоны (см. главу 12 «Паттерны проектирования»).
    Глобальная функция, объявленная с ключевым словом static
    , будет также иметь internal linkage
    . Наличие глобальных функций об ошибках проектирования не свидетельствует, как правило. void foo() { static int i = 0;
    }
    Локальная переменная, объявленная с ключевым словом static
    , будет иметь локальную область видимости и время жизни – от инициализации до завершения программы. Таким образом, состояние статической переменной сохраняется между вызовами функции. Инициализация локальной статиче- ской переменной будет происходить в тот момент когда выполнение про- граммы пройдёт через строчку с объявлением переменной. Если конструк- тор локальной статической переменной выбросит исключение (которое бу- дет обработано), то при следующем прохождении этой строчки будет также выполнена попытка инициализации переменной. Если инициализация ста- тической локальной переменной прошла успешно, инициализации более происходить не будет. По-умолчанию, статические переменные POD-типов инициализируются нулями. class MyClass { static void foo(); static int i;
    }; int MyClass::i = 0; void MyClass::foo() { }

    17
    Атрибут класса, объявленный с ключевым словом static
    , будет иметь глобальную область видимости (через класс, разумеется) и время жизни – от инициализации до завершения программы. Инициализация статических атрибутов происходит так же как и глобальных переменных: в глобальной области видимости объявляется тип переменной, затем её имя (с указанием класса в котором она содержится), и, опционально, инициализатором, например: int MyClass::variable_ = 5;
    По-умолчанию, статические пе- ременные-члены также будут инициализированы нулями.
    Метод класса, объявленный с ключевым словом static
    , будет иметь глобальную область видимости. В отличие от других функций-членов, ста- тический метод не будет получать указатель
    T * this на текущий объект
    (см. п. 3.6) и соответственно не может быть объявлен со спецификаторами const или virtual
    , по этой же причине статические методы не имеют пря- мого доступа к нестатическим полям класса.
    Каждый нестатический метод, помимо явно объявленных параметров, получает еще один скрытый параметр: константный указатель на объект, для которого он вызван. В С++ это указатель обозначается зарезервирован- ным словом this
    . Когда имя параметра метода совпадает с именем поля класса, доступ к полю выполняется через этот указатель (например, this->num
    ).
    2.7
    Ключевое слово const
    Есть две точки зрения на использование const
    : const
    – это плохо. От него больше хлопот, чем пользы, ошибки какие- то странные вылезать начинают, лучше им не пользоваться. const
    – это хорошо. const не дает менять объекты, которые не должны меняться, таким образом оберегает от ошибок, его надо использовать везде где только можно.
    В английской литературе можно часто встретить термины const correct- ness и const correct code, для кода, который корректно использует const const имеет немного разный смысл в зависимости от того где находится.
    2.7.1
    Объявление переменных
    Самый простой случай, обычная переменная. Переменная объявляется, тут же инициализируется, менять ее значение больше нельзя: const int p=4; p=5; //ошибка

    18
    Про использование const с указателями есть известный C++ паззл, ко- торый любят давать на собеседованиях при приеме на работу. Чем отлича- ются: int *const p1 int const* p2 const int* p3
    Правило тут такое: провести мысленно вертикальную черту по звез- дочке. То, что находится справа относится к переменной. То, что слева – к типу, на который она указывает. Вот например: int *const p1
    Cправа находится p1
    , и это p1
    константа. Тип, на который p1
    указывает, это int
    . Значит получился константный указатель на int
    . Его можно ини- циализировать лишь однажды и больше менять нельзя. Нужно так: int q=1; int *const p1 = &q; //инициализация в момент объявления
    *p1 = 5; //само число можно менять
    Вот так компилятор не пропустит, потому что идет попытка присвоения константе: int q=1; int *const p1; p1 = &q; //ошибка
    Следующие объявления – это по разному записанное одно и то же объ- явление. Указатель на целое, которое нельзя менять. int const* p2 const int* p3
    Обычно в реальных программах используется вариант объявления const int
    , а int const используется, чтобы запутать на собеседовании. int q=1; const int *p; p = &q; //на что указывает p можно менять
    *p = 5; //ошибка, число менять уже нельзя const можно использовать со ссылками, чтобы через ссылку нельзя было поменять значение переменной. int p = 4; const int& x = p; //нельзя через x поменять значение p x = 5; //ошибка

    19
    Константная ссылка (например, int& const x
    ) – это нонсенс. Она по определению константная. Компилятор скорее всего выдаст предупрежде- ние, что он проигнорировал const
    2.7.2
    Передача параметров в функцию
    const удобен, если нужно передать параметры в функцию, но при этом надо обязательно знать, что переданный параметр не будет изменен: void f1(const std::string& s); void f2(const std::string* sptr); void f3(std::string s);
    В первой и второй функции попытки изменить строку будут пойманы на этапе компиляции. В третьем случае в функции будет происходить работа с локальной копией строки, исходная строка не пострадает.
    Приведение
    Foo**
    к const Foo**
    приводит к ошибке потому что такое приведение может позволить менять объекты, которые константны. class Foo { public: void modify(); //вносит какие-либо изменения
    }; int main() { const Foo x;
    Foo* p; const Foo** q = &p; // q теперь указывает на p; и это ошибка
    *q = &x; // p теперь указывает на x p->modify(); // попытка изменить const Foo!!
    }
    Самый простой способ это исправить – это поменять const Foo**
    на const Foo* const*
    2.7.3
    const данные классов
    Значения const данных класса задаются один раз и навсегда в конструк- торе. class CFoo
    { const int num; public:
    CFoo(int anum);
    };
    CFoo::CFoo(int anum) : num(anum)
    {
    }

    20
    Интересный момент со static const данными класса. Вообще для дан- ных целого типа (
    enum
    , int
    , char
    ) их значения можно задавать прямо в объ- явлении класса. Следующий код правильный с точки зрения стандарта: class CFoo
    { public: static const int num = 50;
    };
    Но в Visual C++ 6.0 такое задание значения не работает, это один из багов
    Visual C++ 6.0. Тут задавать значение static const переменной следует отдельно. Вместо того, чтобы запоминать, когда можно при объявлении пи- сать инициализацию, когда нельзя, лучше сразу написать так: class CFoo
    { public: static const int num;
    }; const int CFoo::num = 20;
    2.7.4
    const функции классов
    Функция класса, объявленная const
    , трактует this как указатель на кон- станту. Вообще тип this в методе класса
    X
    будет
    X*
    . Но если метод класса объявлен как const
    , то тип this будет const X*
    . В таких методах не может быть ничего присвоено переменным класса, которые не объявлены как static или как mutable
    . Также const
    -функции не могут возвращать не const ссылки и указатели на данные класса и не могут вызывать не const функции класса. const
    -функции иногда называют инспекторами (inspector), а остальные мутаторами (mutator). class CFoo
    { public: int inspect() const; // Эта функция обещает не менять *this int mutate(); // Эта функция может менять *this
    };
    В классе могут присутствовать две функции отличающиеся только const
    : class CFoo
    { public: int func () const; int func ();
    };

    21
    Не всякая функция может быть объявлена константной. Конструкторы и деструкторы не могут быть объявлены как const
    . Также не бывает static const функций. class CFoo
    { int i; public: static int func () const; //ошибка
    };
    Официально такого понятия как константный класс (
    const class
    ) не существует. Но часто под этим понимается объявление вида const CFoo р;
    Экземпляр класса
    CFoo
    , объявленный таким образом, обещает сохранить физическое состояние класса, не менять его. Как следствие, он не может вы- звать не const функции класса
    CFoo
    . Все данные, не объявленные как const
    , начинают трактоваться как const
    . Например: int становится int const int * становится int * const const int * становится int const *const
    Единственный способ инициализировать константные поля, поля- ссылки и вызвать конструкторы родительских классов с определёнными па- раметрами – список инициализации. Список инициализации отделяется от прототипа конструктора двоеточием и состоит из инициализаторов разде- лённых запятыми. Например он может выглядеть так: struct A {
    A(int){ }
    }; struct B: A { const int c_; unsigned d_; unsigned& r_;
    B(): A(5), c_(4), r_(d_)
    { d_ = 5;
    }
    };
    Отметим, что всегда первыми будут вызваны конструкторы родитель- ских классов, а затем уже произойдёт инициализация членов класса, в по- рядке их объявления в классе. Т.е. порядок полей в списке инициализации на порядок инициализации влияния иметь не будет.

    22
    2.7.5
    Несколько фактов о const
    Ключевое слово const перед объявлением массива или указателя отно- сится к элементам массива, а не самой переменной-массиву. Т.е. const int* p;
    указывает на элемент типа const int и его значение нельзя изменять.
    При этом ничто не запрещает изменять значение самого указателя, если хо- тите это запретить – напишите const после «звёздочки».
    Любой тип
    T
    приводим к типу const T
    , массивы из элементов таких ти- пов также приводимы, так что не стоит волноваться из-за того что у вас ука- затель на строку типа char*
    , а функция принимает в себя аргумент типа const char*
    . Вообще слова const и static в объявлениях функций следует расставлять строго до тех пор пока программа не прекратит компилиро- ваться. Я ещё ни разу не видел, чтобы компилирующаяся программа пере- ставала правильно работать от расстановки const и static
    (на нормальных компиляторах). Винт закручивается следующим образом: до срыва, затем
    пол-оборота назад.
    Ключевое слово const перед структурой или классом, по сути, добавляет ключевое слово const ко всем его полям. Исключение составляют поля- ссылки, поля объявленные с ключевым словом mutable и статические поля.
    Т.е., для примера выше, следующий код будет успешно скомпилирован (и, по идее, изменит значение поля d_
    ): const B b; b.r_ = 7;
    Константные методы отличаются от неконстантных лишь тем что указа- тель this имеет тип не
    T* const
    , а const T* const со всеми вытекающими отсюда последствиями. Неконстантные методы не могут быть вызваны у объектов являющимися константными.

    23
    3.
    КЛАССЫ В ООП
    3.1
    Что такое класс?
    В окончательном виде любая программа представляет собой набор ин- струкций процессора. Все, что написано на любом языке программирования
    – более удобная, упрощенная запись этого набора инструкций, облегчающая написание, отладку и последующую модификацию программы. Чем выше уровень языка, тем в более простой форме записываются одни и те же дей- ствия.
    С ростом объема программы становится невозможным удерживать в па- мяти все детали, и становится необходимым структурировать информацию, выделять главное и отбрасывать несущественное. Этот процесс называется повышением степени абстракции программы.
    Для языка высокого уровня первым шагом к повышению абстракции яв- ляется использование функций, позволяющее после написания и отладки функции отвлечься от деталей ее реализации, поскольку для вызова функ- ции требуется знать только ее интерфейс. Если глобальные переменные не используются, интерфейс полностью определяется заголовком функции.
    Следующий шаг – описание собственных типов данных, позволяющих структурировать и группировать информацию, представляя ее в более есте- ственном виде. Например, все разнородные сведения, относящиеся к од- ному виду товара на складе, можно представить с помощью одной струк- туры.
    Для работы с собственными типами данных требуются специальные функции. Естественно сгруппировать их с описанием этих типов данных в одном месте программы, а также по возможности отделить от ее остальных частей. При этом для использования этих типов и функций не требуется пол- ного знания того, как именно они написаны – необходимы только описания интерфейсов. Объединение в модули описаний типов данных и функций, предназначенных для работы с ними, со скрытием от пользователя модуля несущественных деталей является дальнейшим развитием структуризации программы.
    Все три описанных выше метода повышения абстракции преследуют цель упростить структуру программы, то есть представить ее в виде мень- шего количества более крупных блоков и минимизировать связи между ними. Это позволяет управлять большим объемом информации и, следова- тельно, успешно отлаживать более сложные программы.

    24
    Введение понятия класса является естественным развитием идей мо- дульности. В классе структуры данных и функции их обработки объединя- ются. Класс используется только через его интерфейс – детали реализации для пользователя класса не существенны.
    Идея классов отражает строение объектов реального мира – ведь каждый предмет или процесс обладает набором характеристик или отличительных черт, иными словами, свойствами и поведением. Программы в основном предназначены для моделирования предметов, процессов и явлений реаль- ного мира, поэтому удобно иметь в языке программирования адекватный инструмент для представления моделей.
    Класс является типом данных, определяемым пользователем. В классе задаются свойства и поведение какого-либо предмета или процесса в виде полей данных (аналогично структуре) и функций для работы с ними. Созда- ваемый тип данных обладает практически теми же свойствами, что и стан- дартные типы (напомню, что тип задает внутреннее представление данных в памяти компьютера, множество значений, которое могут принимать вели- чины этого типа, а также операции и функции, применяемые к этим величи- нам).
    Существенным свойством класса является то, что детали его реализации скрыты от пользователей класса за интерфейсом. Интерфейсом класса явля- ются заголовки его открытых методов. Таким образом, класс как модель объекта реального мира является черным ящиком, замкнутым по отноше- нию к внешнему миру.
    Идея классов является основой объектно-ориентированного программи- рования (ООП). Основные принципы ООП были разработаны еще в языках
    Simula-67 и Smalltalk, но в то время не получили широкого применения из- за трудностей освоения и низкой эффективности реализации. В С++ эти кон- цепции реализованы эффективно и непротиворечиво, что и явилось основой успешного распространения этого языка и внедрения подобных средств в другие языки программирования.
    Идеи ООП не очень просты для практического использования (их негра- мотное применение приносит гораздо больше вреда, чем пользы), а освое- ние существующих стандартных библиотек требует времени и высокого уровня первоначальной подготовки.
    Конкретные переменные типа данных «класс» называются экземпля- рами класса, или объектами. Объекты взаимодействуют между собой, посы- лая и получая сообщения. Сообщение – это запрос на выполнение действия, содержащий набор необходимых параметров. Механизм сообщений реали-

    25 зуется с помощью вызова соответствующих функций. Таким образом, с по- мощью ООП легко реализуется так называемая «событийно-управляемая модель», когда данные активны и управляют вызовом того или иного фраг- мента программного кода.
    Примером реализации событийно-управляемой модели может служить любая программа, управляемая с помощью меню. После запуска такая про- грамма пассивно ожидает действий пользователя и должна уметь правильно отреагировать на любое из них. Событийная модель является противопо- ложностью традиционной (директивной), когда код управляет данными: программа после старта предлагает пользователю выполнить некоторые действия (ввести данные, выбрать режим) в соответствии с жестко задан- ным алгоритмом.
    Класс – это описание определяемого типа. Любой тип данных представ- ляет собой множество значений и набор действий, которые разрешается вы- полнять с этими значениями. Например, сами по себе числа не представ- ляют интереса – нужно иметь возможность ими оперировать: складывать, вычитать, вычислять квадратный корень и т. д. В С++ множество значений нового типа определяется задаваемой в классе структурой данных, а дей- ствия с объектами нового типа реализуются в виде функций и перегружен- ных операций С++.
    Данные класса называются полями (по аналогии с полями структуры), а функции класса – методами. Поля и методы называются элементами класса.
    Описание класса в первом приближении выглядит так: class <имя> {
    [ private: ]
    <описание скрытых элементов> public:
    <описание доступных элементов>
    }; // Описание заканчивается точкой с запятой
    1   2   3   4   5   6   7   8   9   ...   15


    написать администратору сайта