Теоретические сведения по C++ для студентов курса “Программирование на основе классов и шабллонов” кафедры ИУ5 МГТУ им. Н.Э. Баумана.
Все объектно-ориентированные языки программирования, включая C++, основаны на трёх фундаментальных концепциях: инкапсуляции, полиморфизме и наследовании.
Инкапсуляция — это механизм объединения данных и кода, который с ними работает, а также их защита от внешнего вмешательства. В ООП данные и методы объединяются в объект, который часто называют «чёрным ящиком». Внутренние элементы объекта могут быть закрытыми (private) — доступными только внутри объекта, или открытыми (public) — доступными извне. Обычно открытая часть объекта служит контролируемым интерфейсом для работы с его закрытыми данными. Объект в ООП можно рассматривать как переменную пользовательского типа, содержащую несколько связанных данных и методов.
Полиморфизм — это свойство, позволяющее использовать одно и то же имя для выполнения разных, но логически схожих операций. В C++ это реализуется, например, через перегрузку функций, когда одно имя функции используется для работы с разными типами данных, а компилятор сам выбирает нужную реализацию. Общая идея полиморфизма выражается формулой «один интерфейс — множество методов». Это упрощает программы, так как программисту не нужно помнить множество имён для похожих действий. Полиморфизм также применяется к операторам (перегрузка операторов), позволяя использовать стандартные операторы для пользовательских типов данных.
Наследование — это механизм, при котором один класс может получать свойства другого и дополнять их своими особенностями. Оно позволяет строить иерархии классов, что упрощает описание сложных систем. Например, класс «дом» может наследовать свойства класса «строение», который, в свою очередь, относится к более общему классу. Наследование избавляет от необходимости повторно описывать общие характеристики для каждого объекта и играет ключевую роль в объектно-ориентированном программировании.
Одним из важнейших понятий языка C++ является класс. Класс служит механизмом для создания объектов и лежит в основе объектно-ориентированного программирования. Он определяет новый пользовательский тип данных, на основе которого создаются объекты.
Класс объявляется с помощью ключевого слова class. По синтаксису объявление класса похоже на объявление структуры и имеет следующий общий вид:
class имя_класса {
закрытые данные и функции
public:
открытые данные и функции
};
Список объектов при объявлении класса не является обязательным — объекты можно объявлять позже. Однако имя класса необходимо, так как оно становится именем нового типа данных.
Все переменные и функции, объявленные внутри класса, называются членами класса members. По умолчанию члены класса являются закрытыми private, то есть доступны только другим членам этого же класса. Для объявления открытых членов используется ключевое слово public. Открытые члены доступны как внутри класса, так и из любой части программы, использующей данный класс.
Рассмотрим простой пример объявления класса:
class myclass {
int a; // закрытая переменная
public:
void set_a(int num);
int get_a();
};
В этом классе переменная a является закрытой, а функции set_a() и get_a() — открытыми. Функции, объявленные внутри класса, называются функциями-членами (методами). Они имеют доступ к закрытым данным класса. В то же время закрытая переменная a недоступна для функций, не являющихся членами myclass.
Хотя функции объявлены внутри класса, их определения могут находиться вне его. Для этого используется оператор расширения области видимости ::, который связывает функцию с конкретным классом:
void myclass::set_a(int num)
{
a = num;
}
int myclass::get_a()
{
return a;
}
Основная форма определения функции-члена выглядит следующим образом:
тип_возвращаемого_значения имя_класса::имя_функции(параметры)
{
тело функции
}
Определение класса само по себе не создаёт объектов, а лишь задаёт новый тип данных. Для создания объектов необходимо объявить их, используя имя класса как тип:
myclass ob1, ob2;
Каждый объект имеет собственный набор данных, принадлежащих классу. Обращение к открытым членам объекта осуществляется с помощью оператора точки.
Ниже приведён пример программы, использующей класс myclass:
#include <iostream>
class myclass {
int a;
public:
void set_a(int num);
int get_a();
};
void myclass::set_a(int num)
{
a = num;
}
int myclass::get_a()
{
return a;
}
double average(myclass* mas, int size)
{
double averageValue{};
for (int i = 0; i < size; ++i ){
averageValue += mas[i].get_a();
}
return averageValue/size;
}
int main()
{
const int size = 4;
myclass obj;
myclass arr[size];
obj.set_a(10);
std::cout << obj.get_a() << "\n";
for (int i = 0; i < size; ++i ){
arr[i].set_a(i + 1);
}
for (int i = 0; i < size; ++i ){
std::cout << arr[i].get_a() << "\n";
}
std::cout << "среднее значение массива объектов myclass: "<< average(arr, size) << '\n';
return 0;
}
Конструктор — это специальная функция-член класса, которая автоматически вызывается при создании объекта. Конструктор имеет то же имя, что и класс, и не имеет возвращаемого типа.
Основное назначение конструктора — корректная инициализация полей объекта и подготовка его к использованию.
В языке C++ допускается наличие нескольких конструкторов с разными наборами параметров (перегрузка конструкторов).
Конструктор по умолчанию — это конструктор, который не принимает параметров. Он вызывается:
Пример:
class myclass {
int a;
public:
myclass() {
a = 0;
}
};
Если конструктор по умолчанию не объявлен явно, компилятор может сгенерировать его автоматически, однако при использовании динамической памяти рекомендуется определять его самостоятельно.
Конструктор с параметрами позволяет инициализировать объект значениями, переданными при его создании. Это повышает удобство и безопасность работы с объектами.
Пример:
class myclass {
int a;
public:
myclass(int value) {
a = value;
}
};
Такой конструктор вызывается при создании объекта с аргументами.
Конструктор копирования используется для создания нового объекта на основе уже существующего объекта того же класса. Он принимает ссылку на объект того же типа.
Конструктор копирования вызывается в следующих случаях:
Общий вид конструктора копирования:
myclass(const myclass& other)
{
// копирование данных
}
При наличии динамической памяти конструктор копирования должен выполнять глубокое копирование, то есть выделять собственную память и копировать данные, а не просто копировать указатели.
Оператор присваивания (operator=) используется для копирования данных в уже существующий объект. В отличие от конструктора копирования, он не создаёт новый объект.
Пример формы объявления:
myclass& operator=(const myclass& other)
{
if (this != &other) {
// освобождение старых ресурсов
// копирование данных
}
return *this;
}
const myclass& other— чтобы не копировать и не изменять источник;myclass&в возвращаемом типе — чтобы поддерживать цепочки присваивания (a = b = c;) и работать эффективно.thisэто указатель на текущий объект, для которого был вызван метод класса.
При работе с динамической памятью оператор присваивания также должен обеспечивать глубокое копирование и защиту от самоприсваивания.
Деструктор — это специальная функция-член класса, которая автоматически вызывается при уничтожении объекта. Имя деструктора совпадает с именем класса и начинается с символа ~.
Деструктор:
Пример:
class myclass {
public:
~myclass() {
// освобождение ресурсов
}
};
Основная задача деструктора — освобождение ресурсов, таких как динамическая память, файлы и другие системные ресурсы. Корректная реализация деструктора предотвращает утечки памяти.
Конструкторы и деструктор работают совместно и обеспечивают корректный жизненный цикл объекта:
При использовании динамической памяти обязательным является наличие:
Это правило известно как правило трёх.
В языке C++ стандартные операторы потокового ввода и вывода << и >> могут быть перегружены для работы с объектами пользовательских типов. Это позволяет выводить и вводить объекты классов так же удобно, как и встроенные типы данных.
Назначение перегрузки операторов << и >>
Общая форма перегрузки оператора <<
Оператор вывода обычно перегружается в виде внешней функции:
std::ostream& operator<<(std::ostream& out, const myclass& obj);
Основные особенности:
std::ostream& — поток вывода;const ссылке, так как он не изменяется;Пример перегрузки оператора <<
std::ostream& operator<<(std::ostream& out, const myclass& obj)
{
out << obj.get_a();
return out;
}
Возврат ссылки на поток позволяет выполнять: std::cout << obj1 << obj2;
Общая форма перегрузки оператора >>
Оператор ввода также перегружается как внешняя функция:
std::istream& operator>>(std::istream& in, myclass& obj);
Особенности:
std::istream& — поток ввода;функция возвращает ссылку на поток ввода.
Пример перегрузки оператора >>
std::istream& operator>>(std::istream& in, myclass& obj)
{
int temp;
in >> temp;
obj.set_a(temp);
return in;
}
Операторы << и >> перегружаются вне класса, так как:
Для доступа к закрытым полям класса обычно используются:
get() и set();Использование friend
При необходимости прямого доступа к закрытым данным класса оператор может быть объявлен как дружественный:
class myclass {
int a;
public:
friend std::ostream& operator<<(std::ostream&, const myclass&);
};
friend позволяет функции обращаться к закрытым членам класса, не нарушая его целостность.
В языке C++ арифметические операторы (+, -, *, / и др.) могут быть перегружены для работы с объектами пользовательских типов. Это позволяет выполнять арифметические операции над объектами так же, как и над встроенными типами данных.
Перегрузка арифметических операторов позволяет:
Бинарный арифметический оператор можно перегрузить:
Общий вид:
myclass operator+(const myclass& other) const;
или
myclass operator+(const myclass& a, const myclass& b);
Пример перегрузки оператора + (метод класса)
class myclass {
int a;
public:
myclass(int value = 0) : a(value) {}
myclass operator+(const myclass& other) const {
return myclass(a + other.a);
}
};
Здесь:
const);Унарные операторы работают с одним объектом (например -obj);
Бинарные операторы работают с двумя объектами (obj1 + obj2).
Правила перегрузки одинаковы, различается количество параметров.
Операторы сравнения (==, <, >, …) также могут быть перегружены и обычно возвращают bool.
bool operator==(const myclass& other) const {
return a == other.a;
}
Конструктор преобразования — это конструктор, который принимает один параметр другого типа и используется для преобразования значения этого типа в объект класса.
Пример:
class myclass {
int a;
public:
myclass(int value) {
a = value;
}
};
Теперь возможно:
myclass obj = 10; // неявное преобразование
Явные и неявные преобразования
Чтобы запретить неявные преобразования, используется ключевое слово explicit:
explicit myclass(int value) {
a = value;
}
В этом случае допустимо только явное преобразование:
myclass obj(10); // корректно
Конструкторы преобразования применяются:
Оператор преобразования типа позволяет преобразовать объект класса в значение другого типа.
Общий вид:
operator тип() const;
Пример оператора преобразования типа
class myclass {
int a;
public:
operator int() const {
return a;
}
};
Теперь возможно:
myclass obj(5);
int x = obj; // преобразование объекта в int
Отличие конструктора преобразования и оператора преобразования
| Механизм | Направление преобразования |
|---|---|
| Конструктор преобразования | из другого типа → в объект класса |
| Оператор преобразования | из объекта класса → в другой тип |