2. Классы и объекты в С++.

Определение класса с помощью class, struct, union. Ограничение доступа к членам класса в С++. Члены класса и объекта. Методы. Константные члены. Схемы наследования.

Классы

В ООП есть понятие класс. В С работали с struct вы объединяли поля разных типов. Идея простая почему бы не добавить в эту структуру методы.

Примечание: Разработчик Страуструп сделал так чтобы люди, которые пишут на C легко могли перейти на С++, или программа написанная на С работали компилятором С++. Структура нам нужны для преемственности, чтобы перейти на С++.

Класс можно организовать на основе следующих ключевых слов:

  • struct

  • class

  • union - использовать это мы не будем. Это не контролируется. Это плохо. Нет доступа protected.

Кроме члены данных, у них могут быть методы или функции обработки данных, работающие с этими данными.

Вспомним: про идею инкапсуляции, что мы формируем какое-то понятие и работаем с набором методом и функций. Важно мы не работаем с полями данных. Есть языки, которые дают возможность работать с полями, нет уровней доступа к членам класса - ЭТО ПЛОХО, нужен чтобы был контроль со стороны программиста.

Выделяют следующие уровни доступа к членам class:

  • private (частичный, собственный) - имеют доступ только члены самого класса. При этом производные классы не имеют доступа к членам private базового класса. Доступ к членам класса имеем только из методов этого класса.

  • protected (защищенный) - имеют доступ методы самого класса и методы производных классов

  • public (общий) - к членам класса можно обратиться из любого места в программе

Примечание: при доступе public мы возвращаемся к проблеме в структурном программировании, при изменении данных, искать что или кто вносит изменения, разбираться в коде. Желательно не всегда выбирать protected.

По правилам инкапсуляции, поля должны быть private или protected.

По умолчанию, все члены (методы и поля) в struct – public, в class – private, а в union нет уровня protected, так как union не может быть ни базовым классом, ни производным. Разница между struct и class заключается только в уровне доступа к полям.

Создание класса

class <имя класса> [:<список баз>] // баз - родительские классы
{
private:		
    <список полей>
protected:
    <список полей>
public:
    <список полей>
};

Примечание. Квадратные скобки [какое-то содержимое] означают, что содержимое - не обязательно. В данном случае, не обязательно указывать список баз. В данном примере поля указаны в том порядке, в каком их удобнее читать. Сначала описываются private поля, потом protected, а в конце public.

Дынные объекта разделяются на два типа: члены объекта и члены класса. При поиске размере sizeof будут учитываться только члены объекта и что-то еще, но без членов класса.

Методы делятся на 2 группы: методы объекта и методы класса. В классах мы будем методы будем вызывать для объектов. Для структуры мы использовали ., если это сами данные или '->' то это указатель на данные.

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

В С этот оператор к глобальному контексту.

int i;
{
  int i;
  i = 2; // то второй i будет использован
  ::i = 3; // то первый i будет использован
}

Модификаторы const и static

Поля

В чем суть. При объявлении полей класса мы можем проинициализировать все поля сразу. Статические поля можем определить вне класса. В разделе инициализации конструктора - не можем определить статические поля. Внутри конструктора - не можем менять поля с модификатором const.

Рассмотрим пример для полей класса:

class A
{
private:
    int a = 3; // так можно только с C++11
    int a; // член объекта
    const int cа; // константный член объекта
    static int sa; // член класса
    static const int scd = 2; // константный член класса, так он общий и константный, то инициализация возможно в самом классе
        
public:
    A(int ia) :
    // Объект еще не создан
        a(ia),  // Так можно - объекта еще нет	 
        ca(ia), // Так можно - объекта еще нет
        sa(ia), // Ошибка - sa не член объекта
        csa(ia) // Ошибка - cas не член объекта
    {	
    // Объект создан 
        a = ia;   // Так можно
        ca = ia;  // Константу изменить нельзя - объект уже создан
        sa = ia;  // Так можно	
        csa = ia; // Константу изменить нельзя - объект уже создан
    }
};

// static поля класса можно проинициализировать здесь
int A::sa = 0;
const int A::csa = 0;

Дополнительная информация из книги Герберта Шилдта "Самоучитель С++".

Если мы предваряем объявление переменной-члена словом static, мы сообщаем компилятору, что может существовать только одна копия этой переменной, и что все объекты данного класса будут использовать эту единственную копию. В отличие от обычных данных членов, для каждого объекта класса не создается индивидуальной копии статической переменной-члена. Сколько бы объектов класса мы не создавали, будет существовать только один экземпляр статического данного члена. В результате все объекты класса используют одну и ту же переменную. Когда создается первый объект, все статические переменные инициализируются нулем.

Если мы объявляем данное-член вида static внутри класса, мы не определяем его. Мы обязаны выполнить глобальное определение этой переменной где-то в другом месте, вне класса, используя оператор разрешения видимости, чтобы указать, к какому классу она относится.

Методы

Есть методы класса, а есть методы объекта. Метод класса вызывается <имя_класса>::<имя_метода>. Метод объекта вызывается через сам объект. Методы объекта/класса имеют доступ ко всем элементам класса. Методы класса не получают this. Что делать? Передавать элементы через параметры. Методы класса могут работать только со статическими членами.

Пример:

class A
{
private:
    int a = 3; // так можно только с C++11
    int a; // член объекта
    const int cа; // константный член объекта
    static int sa; // член класса
    static const int scd = 2; // константный член класса, так он общий и константный, то инициализация возможно в самом классе
public:
    int f(); // метод объекта
    int f() const; // константный метод объекта
    cont int f(); // метод объекта возвращает константу
    static int g(); // метод класса, позволяет не создавать объект и работать с объектом
    // методы класса не могут быть константными
};

// Модификатор сonst в примере int f() const влияет какой будет метод вызова,
// если объект константный, а нет константного метода, то простой метод вызвать не можем.
// Также для конст или неконст объекта будет вызываться константный метод.

inline int A::f() // :: - оператор доступа к контексту (метод класса вне его описания)
{
    return this->a; // this – ключевое слово объекта, его можно не писать
    return a;       // равносильно
}

inline int A::g()   // Здесь, в статическом методе, можно работать только
{                  // со статическими членами.
    return sa;
}

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

Определение метода класса:

  • Если определить класс внутри класса, то по возможности компилятор устанавливает модификатор inline к данному методу.

  • Если вне класса, то не устанавливается модификатор.

Операторы вызова методов:

  • . вызов метода через объект

  • -> вызов через указатель на объект

  • ::

  • .*

  • ->*

A obj, *pobj;
obj.f();
pobj->f();
// если это static int g()
A::g();

Наследование в С++

В С++ есть множественное наследование – класс создается из нескольких других.

Дополнительная информация из книги Герберта Шилдта "Самоучитель С++".

Если базовый класс наследуется как public, все открытые члены базового класса становятся открытыми членами производного класса, защищенные члены становятся защищенными членами, закрытые элементы базового класса остаются закрытыми в этом классе и недоступны для членов производного класса.

Если базовый класс наследуется как protected, все открытые и защищенные члены базового класса становятся защищенными членами производного класса.

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

Во всех случаях наследования, закрытые члены базового класса остаются закрытыми и в этом классе.

Пример:

class A
{
private:
    int a;
protected:
    int b;
public:
    int f();
};

class B: {private(по умолчанию)/public/protected} A
{
private:
    int c;
protected:
    int d;
public:
    int g();
};

Схема для данного наследования

  • с private наследованием(по умолчанию):

Следуя из выше указанного кусочка кода, пусть мы создаем класс B, который наследуется от класс A, при этом в классе A член a с доступом private, не наследуется в класс B, а член c и b могут наследоваться. И метод f() тоже. При этом если объектом класса B будет вызван метод g(), то он может менять члены 'c, b, d' и вызывать метод f(). Сам же метод f() может менять только члены b, a

Мы полностью подменили интерфейс и тем самым можем полностью поменять понятия, т.е. произошла полная смена схемы интерфейса f()->g()

Все современные языки оставили только схему с public наследованием, но в С++ есть и private, и protected.

  • Вариант с protected наследованием:

При этой схеме наследования члены базового класса с доступом public получают protected. Интерфейс тоже меняется.

Главное отличие в том, что интерфейс базового класса будет доступен для производных классов от класса B.

В данном примере: b и f() перешли в уровень protected, все остальное без изменений. Кроме того, в наследуемых от B классах можно использовать интерфейс базового класса А.

  • Вариант с public наследованием:

Все члены со своим доступом, расширения интерфейса. Используется часто по причине того, что можем менять один объект на другой, а при смене интерфейса это невозможно.

А в случае когда надо часть оставить, а часть перекрыть интерфейса - тогда можем для любого члена базового класса в производном постановить его уровень доступа:

using <имя класса>::<элемент>;

using A::f;

Last updated