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

  • 2. Обработка исключительных ситуаций

  • /C++ ->Code Generation->Enable C++ Exceptions

  • 3. Классы (определение, описание, спецификаторы, элементы) Синтаксис : class имя

  • имя

  • 4. Данные и свойства класса

  • 5. Конструкторы, деструкторы, параметры this Конструктор

  • include struct

  • 8. Индексаторы Индексатор

  • Рис. 17.1.

  • Функция- член класса

  • 11. Перезагрузка операции преобразования типа

  • 12. Наследование Наследование в C++ происходит между классами и имеет тип отношений

  • родительским

  • билеты. OOП билеты 12 шт. 1. Принципы опп наследование, инкапсуляция, полиморфизм Наследование


    Скачать 225.77 Kb.
    Название1. Принципы опп наследование, инкапсуляция, полиморфизм Наследование
    Анкорбилеты
    Дата07.01.2022
    Размер225.77 Kb.
    Формат файлаdocx
    Имя файлаOOП билеты 12 шт.docx
    ТипДокументы
    #325563


    1. Принципы ОПП: наследование, инкапсуляция, полиморфизм

    Наследование — один из четырёх важнейших механизмов объектно-ориентированного программирования (наряду с инкапсуляцией, полиморфизмом и абстракцией), позволяющий описать новый класс на основе уже существующего (родительского), при этом свойства и функциональность родительского класса заимствуются новым классом.

    Простое наследование:

    Класс, от которого произошло наследование, называется базовым или родительским (англ. base class). Классы, которые произошли от базового, называются потомками, наследниками или производными классами (англ. derived class).

    В некоторых языках используются абстрактные классы. Абстрактный класс — это класс, содержащий хотя бы один абстрактный метод, он описан в программе, имеет поля, методы и не может использоваться для непосредственного создания объекта. То есть от абстрактного класса можно только наследовать. Объекты создаются только на основе производных классов, наследованных от абстрактного. Например, абстрактным классом может быть базовый класс «сотрудник вуза», от которого наследуются классы «аспирант», «профессор» и т. д. Так как производные классы имеют общие поля и функции (например, поле «год рождения»), то эти члены класса могут быть описаны в базовом классе. В программе создаются объекты на основе классов «аспирант», «профессор», но нет смысла создавать объект на основе класса «сотрудник вуза».

    Множественное наследование

    При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML.

    Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя.

    Инкапсуляция — свойство программирования, позволяющее пользователю не задумываться о сложности реализации используемого программного компонента (что у него внутри?), а взаимодействовать с ним посредством предоставляемого интерфейса (публичных методов и членов), а также объединить и защитить жизненно важные для компонента данные. При этом пользователю предоставляется только спецификация (интерфейс) объекта.

    Пользователь может взаимодействовать с объектом только через этот интерфейс. Реализуется с помощью ключевого слова: public.

    Пользователь не может использовать закрытые данные и методы. Реализуется с помощью ключевых слов: private, protected, internal.))

    Инкапсуляция — один из четырёх важнейших механизмов объектно-ориентированного программирования (наряду с абстракцией, полиморфизмом и наследованием).

    Сокрытие реализации целесообразно применять в следующих случаях:

    предельная локализация изменений при необходимости таких изменений,

    прогнозируемость изменений (какие изменения в коде надо сделать для заданного изменения функциональности) и прогнозируемость последствий изменений.

    Полиморфизм — возможность объектов с одинаковой спецификацией иметь различную реализацию.

    Язык программирования поддерживает полиморфизм, если классы с одинаковой спецификацией могут иметь различную реализацию — например, реализация класса может быть изменена в процессе наследования.

    Кратко смысл полиморфизма можно выразить фразой: «Один интерфейс, множество реализаций».

    Полиморфизм — один из четырёх важнейших механизмов объектно-ориентированного программирования (наряду с абстракцией, инкапсуляцией и наследованием).
    2. Обработка исключительных ситуаций

    Рассмотрим пример программы, генерирующей исключительную ситуацию «деление на 0».

    #include 
    using namespace std;
    int main() {
      int a = 0, b =10;

      cout << b/a << endl;cin.get();

      return 0;
    }

    При попытке запустить программу на выполнение видим следующее:



    Для обработки исключительной ситуации необходимо операцию деления поместить в блок защищенного кода:

    #include 
    using namespace std;
    int main() {

      int a = 0, b = 10;

      try {

        cout << b / a << endl;

      }

      catch (…)

      {

        cout << «error»;

      }

      cin.get();

      return 0;
    }

    Для корректного запуска программы необходимо также произвести настройки среды разработки и разрешить обработку структурных исключений. Для этого переходим к меню Имя проекта->Свойства

    И для пункта меню C/C++ ->Code Generation->Enable C++ Exceptions устанавливаем значение Yes With SEH Exceptions (/EHa).

    При запуске программы на выполнение имеем следующий результат:


    При компиляции с параметром /EHa, в число обрабатываемых исключений могут входить структурированные исключения C и создаваемые системой или приложением асинхронные исключения, например нарушения, связанные с защитой памяти, делением на ноль и числами с плавающей запятой.
    3. Классы (определение, описание, спецификаторы, элементы)

    Синтаксис:
    class имя класса {
      элементы класса
    };
    или
    struct имя класса {
      элементы класса
    };

    С помощью спецификаторов доступа можно управлять видимостью элементов класса. Спецификатор private (закрытый) означает видимость элементов только для методов и друзей класса. Спецификатор protected (защищенный) означает видимость элементов только для методов и друзей класса и его наследников (наследники могут видеть эти элементы только в объектах собственного типа). Спецификатор public (открытый) означает видимость элементов из любого кода. Действие любого спецификатора распространяется до следующего спецификатора или до конца объявления класса. По умолчанию для элементов класса, объявленного с помощью struct, установлен доступ public, а для класса, объявленного с помощью class, – private.

    Рекомендуется все поля в class делать закрытыми или защищенными элементами, а в struct – открытыми.

    4. Данные и свойства класса

    Данные-члены — это те члены, которые содержат данные класса — поля, константы, события. Данные-члены могут быть статическими (static). Член класса является членом экземпляра, если только он не объявлен явно как static. Давайте рассмотрим виды этих данных:

    Свойства (property)

    Это наборы функций, которые могут быть доступны клиенту таким же способом, как общедоступные поля класса. В C# предусмотрен специальный синтаксис для реализации чтения и записи свойств для классов, поэтому писать собственные методы с именами, начинающимися на Set и Get, не понадобится. Поскольку не существует какого-то отдельного синтаксиса для свойств, который отличал бы их от нормальных функций, создается иллюзия объектов как реальных сущностей, предоставляемых клиентскому коду.

    5. Конструкторы, деструкторы, параметры this

    Конструктор — функция, предназначенная для инициализации объектов класса.Рассмотрим класс date:

     
     
     
     
     
     

    class date
    {
      int day, month, year;
    public:
      set(int, int, int);
    };


    Нигде не утверждается, что объект должен быть инициализирован, и программист может забыть инициализировать его или сделать это дважды.
    ООП дает возможность программисту описать функцию, явно предназначенную для инициализации объектов. Поскольку такая функция конструирует значения данного типа, она называется конструктором. Конструктор всегда имеет то же имя, что и сам класс и никогда не имеет возвращаемого значения. Когда класс имеет конструктор, все объекты этого класса будут проинициализированы.

     
     
     
     
     

    class date {
      int day, month, year;
    public:
      date(int, int, int); // конструктор
    };

    Если конструктор требует аргументы, их следует указать:

     
     
     

    date today = date(6,4,2014); // полная форма
    date xmas(25,12,0); // сокращенная форма
    // date my_burthday; // недопустимо, опущена инициализация

    Если необходимо обеспечить несколько способов инициализации объектов класса, задается несколько конструкторов:

     
     
     
     
     
     
     

    class date {
      int month, day, year;
    public:
      date(int, int, int); // день месяц год
      date(char*); // дата в строковом представлении
      date(); // дата по умолчанию: сегодня
    };

    Деструкторы


    Определяемый пользователем класс имеет конструктор, который обеспечивает надлежащую инициализацию. Для многих типов также требуется обратное действие. Деструктор обеспечивает соответствующую очистку объектов указанного типа. Имя деструктора представляет собой имя класса с предшествующим ему знаком «тильда» 

    . Так, для класса X деструктор будет иметь имя X(). Многие классы используют динамическую память, которая выделяется конструктором, а освобождается деструктором.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    class date
    {
      int day, year;
      char *month;
    public:
      date(int d, char* m, int y)
      {
        day = d;
        month = new char[strlen(m)+1];
        strcpy_s(month, strlen(m)+1,m);
        year = y;
      }
      date() { delete[] month; } // деструктор
    };

    6. Методы класса.

    Методы - это функции, объявление которых размещено внутри определения класса или структуры. В список переменных, доступных для метода, неявно попадают все поля структуры или класса, в котором он объявлен. Другими словами, в список областей видимости метода попадает область видимости структуры.

    пример:

    #include
    struct Vec2f

    {

    float x = 0;

    float y = 0;
    // Объявление метода с именем getLength

    // 1) метод - это функция, привязанная к объекту

    // 2) полное имя метода: "Vec2f::getLength"

    // Метод имеет квалификатор "const", потому что он не меняет

    // значения полей и не вызывает другие не-const методы.

    float getLength() const

    {

    const float lengthSquare = x * x + y * y;

    return std::sqrt(lengthSquare);

    }

    };

    Методу Vec2f::getLength доступны все символы (т.е. переменные, функции, типы данных), которые были объявлены в одной из трёх областей видимости. При наличии символов с одинаковыми идентификаторами один символ перекрывает другой, т.к. поиск происходит от внутренней области видимости к внешней.

    7. Перегрузка методов

    Перегрузка методов – это объявление в классе методов с одинаковыми именами при этом с различными параметрами.

    Имея некий метод, чтобы его перегрузить, другой метод с таким же именем должен отличаться от него количеством параметров и/или типами параметров. Отличия только типами возвращаемых значений методами недостаточно для перегрузки, но если методы отличаются параметрами, тогда перегружаемые методы могут иметь и различные типы возвращаемых значений.

    Пример того, как может быть перегружен метод:

    public void SomeMethod()
    {
       // тело метода
    }
    public void SomeMethod(int a) // от первого отличается наличием параметра
    {
       // тело метода
    }
    public void SomeMethod(string s) // от второго отличается типом параметра
    {
       // тело метода
    }
    public int SomeMethod(int a, int b) // от предыдущих отличается количеством параметров (плюс изменен тип возврата)
    {
       // тело метода
       return 0;
    }

    8. Индексаторы

    Индексатор позволяет работать с классом или структурой таким образом, как если бы это были массивы. Индексация класса выполняется по индексу, указываемому как параметр. Иногда классы, используемые как индексаторы, называют классами-индексаторами.

    Объявление индексатора может иметь следующее формальное описание:

    [атрибуты] [модификаторы] тип this [[атрибуты]

    тип_параметра идентификатор_параметра .,...]

    {объявление аксессоров}

    Индексатор должен иметь как минимум один параметр. Тип и идентификатор параметра указываются в квадратных скобках после ключевого слова this.

    Среда проектирования Visual Studio.NET позволяет использовать мастер создания индексатора: для этого в окне Class View следует выделить имя класса и выполнить команду контекстного меню Add|Add Indexer.

    Диалог C# Indexer Wizard (рис. 17.1) позволяет определить параметры создаваемого индексатора.




    Рис. 17.1. Диалог C# Indexer Wizard
    9. Перезагрузка унарных операций класса

    Если унарная операция перегружается как функция-член, то она не должна иметь аргументов, так как в этом случае ей передается неявный аргумент-указатель this на текущий объект.
    Если унарная операция перегружается дружественной функцией, то она должна иметь один аргумент – объект, для которого она выполняется.

    Таким образом, для любой унарной операции @ aa@ или @aa может интерпретироваться или как aa.operator@(), или как operator @(aa).
    Если определена и та, и другая, то и aa@ и @aa являются ошибками.

    Функция- член класса

    Дружественная функция

     
     
     
     
     
     
     

    class А
    {
      ...
    public:
      A operator !();
      ...
    };

     
     
     
     
     
     
     

    class A
    {
      ...
    public:
      friend A operator !(A);
      ...
    };

     

    10. Перезагрузка бинарных операций класса

    Если бинарная операция перегружается с использованием метода класса, то в качестве своего первого аргумента она получает неявно переданную переменную класса (указатель this на объект), а в качестве второго — аргумент из списка параметров. То есть, фактически бинарная операция, перегружаемая методом класса, имеет один аргумент (правый операнд), а левый передается неявно через указатель this.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    class complex
    {
      double real;
      double imag;
    public:
      complex operator +(const complex &);
      ...
    };
    complex complex :: operator +(complex &c) {
      complex temp;
      temp.real = this->real + c.real;
      temp.imag = this->imag + c.imag;
      return(temp);
    }

     
    Если бинарная операция перегружается дружественной функцией, то в списке параметров она должна иметь оба аргумента:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33

    #include 
    using namespace std;
    class complex
    {
      double real;
      double imag;
    public:
      complex(double r = 0, double i = 0)
      {
        real = r; imag = i;
      }
      void out(void)
      {
        cout << real << " + " << imag << "i" << endl;
      }
      friend complex operator + (const complex &c1, const complex &c2);
    };
    complex operator + (const complex &c1, const complex &c2) 
    {
      complex temp;
      temp.real = c1.real + c2.real;
      temp.imag = c1.imag + c2.imag;
      return(temp);
    }
    int main() 
    {
      complex a(3.1, 4.5), b(2.3, 6.7); // инициализация
      complex c;
      c = a + b;
      c.out();
      cin.get();
      return 0;
    }

    Результат выполнения


     

    Для каждой комбинации типов операндов в переопределяемой операции необходимо ввести отдельную функцию, т.е. компилятор не может производить перестановку операндов местами, даже если базовая операция допускает это. Например, если необходима операция сложения комплексного и вещественного чисел:

     
     
     
     

    complex a, c, d;
    double b;
    c = a + b;
    d = b + a;

    то необходимо переопределить операцию сложения дважды:

    1
    2

    friend complex operator + (complex, double);
    friend complex operator + (double, complex);

    11. Перезагрузка операции преобразования типа

    Например, преобразуем значение типа int в значение типа double:

    1

    2

    int a = 7;

    double b = a; // значение типа int неявно конвертируется в значение типа double

    Язык C++ по умолчанию знает, как выполнять преобразования встроенных типов данных. Однако он не знает, как выполнять конвертацию с пользовательскими типами данных (например, с классами). Именно здесь вступает в игру перегрузка операций преобразования типов данных. Рассмотрим следующий класс:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    class Dollars

    {

    private:

        int m_dollars;

    public:

        Dollars(int dollars=0)

        {

            m_dollars = dollars;

        }

        int getDollars() { return m_dollars; }

        void setDollars(int dollars) { m_dollars = dollars; }

    };

    Класс Dollars содержит некое количество долларов в виде целого числа (переменная-член m_dollars) и предоставляет функции доступа для получения и установления значения m_dollars. В нем также есть конструктор для конвертации значений типа int в тип Dollars (при создании объекта пользователь передает в качестве аргумента значение типа int, которое затем преобразуется в значение типа Dollars).

    Если мы можем конвертировать int в Dollars, то логично было бы, если бы мы могли конвертировать и Dollars обратно в int, не так ли? Иногда это может быть полезным.

    В следующем примере мы используем метод getDollars() для конвертации значения типа Dollars в тип int для его последующего вывода через функцию printInt():

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    void printInt(int value)

    {

        std::cout << value;

    }

    int main()

    {

        Dollars dollars(9);

        printInt(dollars.getDollars()); // выведется 9

        return 0;

    }

    Согласитесь, вызывать каждый раз метод getDollars() не очень удобно. Было бы проще перегрузить операцию преобразования значений типа Dollars в тип int. Делается это следующим образом:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    class Dollars

    {

    private:

        int m_dollars;

    public:

        Dollars(int dollars=0)

        {

            m_dollars = dollars;

        }

        // Перегрузка операции преобразования значений типа Dollars в значения типа int

        operator int() { return m_dollars; }

        int getDollars() { return m_dollars; }

        void setDollars(int dollars) { m_dollars = dollars; }

    };

    12. Наследование

    Наследование в C++ происходит между классами и имеет тип отношений «является». Класс, от которого наследуют, называется родительским (или «базовым»«суперклассом»), а класс, который наследует, называется дочерним (или «производным»«подклассом»).



    В диаграмме, представленной выше, Фрукт является родительским классом, а Яблоко и Банан — дочерними классами.



    В этой диаграмме Треугольник является дочерним классом (родитель — Фигура) и родительским (для Правильного треугольника) одновременно.

    Дочерний класс наследует как поведение (методы), так и свойства (переменные-члены) от родителя (с учетом некоторых ограничений доступа, которые мы рассмотрим чуть позже). Эти методы и переменные становятся членами дочернего класса.

    Поскольку дочерние классы являются полноценными классами, то они могут (конечно) иметь и свои собственные члены.


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