2. Классы и объекты в С . Определение класса с помощью class, struct, union. Ограничение доступа к членам класса в С . Члены класса и объекта. Методы. Константные члены. Схемы наследования. - keykranz/oop_ex GitHub Wiki
Класс – тип данных, представляющий совокупность атрибутов объекта (переменные члены класса) и возможных операций над этими объектами (методов класса).
Класс можно определить с помощью следующих ключевых слов:
struct
- для совместимости с языком Си - уровень доступа public по-умолчанию
class
- private
union
- union нужен только для совместимости с языком, т.к. нет контроля со стороны компилятора, его не используем
На основе этого можно организовать класс. Кроме членов данных у них могут быть функции (методы), которые работают с этими данными.
-
private
(частичный) - имеют доступ только члены самого класса. При этом производные классы не имеют доступа к членам private базового класса. -
protected
(защищенный) - имеют доступ методы самого класса и методы производных классов -
public
(общий) - к членам класса можно обратиться из любого места в программе
По правилам инкапсуляции, поля должны быть private или protected.
class <имя класса> [:<список базовых классов>]
{
private: //доступ к данным есть только внутри самого класса
Int a;
protected: //доступ есть внутри класса и во всех его наследниках
Int b;
public: //доступны для внешнего кода
Int f();
}; //класс заканчивается ; как и структура
//Классы описываются в заголовочных файлах .h. Методы же определяются в .cpp
MyClass a, *b = &a;
a.f();
b->f();
По умолчанию, все члены (методы и поля) в struct – public, в class – private, а в union нет уровня protected, так как union не может быть ни базовым классом, ни производным. Разница между struct и class заключается только в уровне доступа к полям.
При разработке класса логично располагать данные по уровням: private, protected, public
При разработке библиотечного класса пользователю стоит дать увидеть public члены, остальные его как правило не интересуют
Размещение полей данных класса (атрибутов) публичными – плохой тон, поскольку это противоречит принципу инкапсуляции (скрывание реализации, предоставление пользователю только интерфейса для работы).
class A
{
private:
int a; // данные объекта
const int cb; // константные данные объекта
static int sc; // общее поле для всех объектов класса, располагает в другой области памяти
static const int scd = 2; // константное общее поле для всех объектов класса, разумно инициализировать его здесь
// с 11 версии с++ в классе можно инициализировать любой другой член
public:
int f(); // метод объекта
// указатель на объект передается неявно
// метод можно объявить внутри класса и вне
int f() const; // константный метод объекта
// если const слева, то метод возвращал бы константу
static int g(); // метод класса, не может быть константным, тк не принимает указателя this
};
Объединения и структуры из си являются частными случаями классов.
union IPv4
{
int IntegerForm;
char ByteForm[4];
};
Однако юнион нельзя использовать в качестве базового класса и использовать для наследования.
struct Font
{
int Symbol:8;
int isItalic:1;
int isBold:1;
int color:3
int :3 //необходимо для «закрытия» куска памяти – чтобы следующий юнион вдруг не попал на разделение байтов
};
Класс может наследоваться от другого класса. Наследование - создание нового класса на основе старого. В C ++ есть 3 схемы наследования:
- публичный (public)- все открытые члены базового класса становятся открытыми членами производного класса, защищенные члены становятся защищенными членами, закрытые элементы базового класса остаются закрытыми в этом классе и недоступны для членов производного класса. (расширение интерфейса)
публичные (public) и защищенные (protected) данные наследуются без изменения уровня доступа к ним; (доступ к унаследованным данным не меняется)
-
защищенный (protected) — все открытые и защищенные члены базового класса становятся защищенными членами производного класса.
-
приватный (private) — открытые и защищенные члены базового класса становятся закрытыми членами производного класса. Они все еще доступны из членов производного класса, но из других частей программы к ним обратиться нельзя. (происходит полная замена интерфейса)
В чем суть. При объявлении полей класса мы можем проинициализировать все поля сразу. Статические поля можем определить вне класса. В разделе инициализации конструктора - не можем определить статические поля. Внутри конструктора - не можем менять поля с модификатором const.
Один общий для всех объектов этого класса. Доступен сразу из класса, без создания объекта. Вызывается для класса. Статические методы не имеют указателя this.
Class A
{
public:
static int a;
static void f();
};
// static поля класса можно проинициализировать здесь
A::a = 0;
A::f();
Константное поле данных не меняет значение на протяжении всей жизни объекта. Инициализируется только в конструкторе. Константный метод не изменяет объект, которым вызывается. Константный объект использует только константные методы.
class A {
public:
const int a;
void f(); //1
void f() const; //2
}
A b; b.f() // 1
Const A c; c.f() // 2
Пример:
class A
{
private:
int a; // Поле объекта
static int sa; // Поле класса
const int ca; // Константа объекта
const static int csa = 0; // Константа класса
// Проинициализировать поля класса можно здесь
// (пример с csa выше)
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;
2 группы методов:
- методы объекта
- методы класса
Метод класса вызывается имя_класса::имя_метода
. Метод объекта вызывается через сам объект. Методы объекта/класса имеют доступ ко всем элементам класса. Методы класса не получают this
. Что делать? Передавать элементы через параметры. Методы класса могут работать только со статическими членами.
Если имена методов одинаковые, то в случае если объект не константный, то будет вызываться обычный метод. Если объект константный, то будет вызываться константный метод. Если константного метода нет, к константному методу нельзя вызвать обычный метод.
Если метод определяем внутри класса, то компилятор делает этот метод по возможности inline
. Можно определить метод вне класса с модификатором inline
(вместо вызова подставляется код). Если метод в одну строчку, то разумно сделать его inline
. Методы не располагаются в самом объекте, это функции, которые при вызове неявно получают указатель на объект. ::
- оператор доступа контекстум
Пример:
class A:
{
private:
int a;
static int sa;
public:
int f(); // метод объекта (не const)
int f() const; // const метод объекта обязуется не менять состояние объекта,
// то есть не менять его любые поля
static int g(); // метод класса
};
int A::f() // :: - оператор доступа к контексту (метод класса вне его описания)
{
return this->a; // this – ключевое слово объекта, его можно не писать
return a; // равносильно
}
int A::g() // Здесь, в статическом методе, можно работать только
{ // со статическими членами.
return sa;
}
Другой пример:
int A::f();
{
this -> a = 2; // this - обращение к объекту класса
return a;
}
int i;
{
int i;
i = 2; // внутренняя i
::i = 3; // внешняя i
}
. //вызов метода через объект
-> //вызов метода через указатель
.* // вызов метода через объект и указатель на метод
->* // вызов метода через указатель на объект и указатель на метод
:: // для статических методов
A obj, *pobj;
obj.f();
pobj->f();
A::g()
Способ объявления статической переменной класс до версии С++11: int A::sc = 2;
В самом классе инициализация может быть только константным выражением - они должны вычисляться на этапе компиляции.
//Объединения и структуры из си являются частными случаями классов.
union IPv4
{
int IntegerForm;
char ByteForm[4];
};
//Однако юнион нельзя использовать в качестве базового класса и использовать для наследования.
struct Font
{
int Symbol:8;
int isItalic:1;
int isBold:1;
int color:3
int :3 //необходимо для «закрытия» куска памяти – чтобы следующий юнион вдруг не попал на разделение байтов
};
//Допускается неполное объявление: без определения класса.
class A;
//Создание объектов проходит как с помощью
MyClass A;
//так и с помощью
MyClass *A = new MyClass;
<class.h>
class B
{
private:
const int a; //поле должно быть инициализировано вместе с экземпляром класса, и не может изменяться.
public:
B(int a); //конструктор
void f() const; //константный метод – может вызываться для константных объектов; не может работать с константными полями
void g();
};
<class.cpp>
#include “class.h”
B::B(int val): a(val) //, c(7) – всё это инициализация
{
}
const B obj; //в константном экземпляре можно вызывать ТОЛЬКО константные методы: obj.f() НО НЕ obj.g()
//Статические члены классов
class A
{
public:
static void func();
private:
static int b;
static const char* text;
};
//Инициализация проходит через
int A::b = 0;
const char* text = "abc";
С ОБЯЗАТЕЛЬНЫМ УКАЗАНИЕМ ТИПА. Без этого компилятор ругнётся на недоступность переменных. Статический метод не привязан к конкретному экземпляру.
По возможности компилятор делает методы класса инлайновыми.