Класс и конструктор класса

Обновлено: 28.04.2024

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

Конструктор Students

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

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

Теперь добавим прототип конструктора в файл students.h .

В файле students.cpp определим сам конструктор.

В main() мы принимаем от пользователя имя и фамилию ученика, и сохраняем их во временных локальных переменных. После этого создаем новый объект класса Students , передавая его конструктору эти переменные.

Сохранение оценок в файл

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

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

Пример файла с оценками:

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

Переменная fout — это объект класса ofstream , который находится внутри библиотеки fstream . Класс ofstream используется для записи каких-либо данных во внешний файл. Кстати, у него тоже есть конструктор. Он принимает в качестве параметров имя выходного файла и режим записи.

В данном случае, мы используем режим добавления — std::ios:app (англ. append). После завершения работы с файлом, необходимо вызвать метод close() для того, чтобы закрыть файловый дескриптор.

Чтобы сохранить оценки студента, мы будем вызывать только что созданный метод save() .

Деструктор Students

Логично было бы сохранять все оценки после того, как работа со студентом закончена. Для этого создадим деструктор класса Students , который будет вызывать метод save() перед уничтожением объекта.

Добавим прототипы деструктора и метода save() в students.h .

И определим эти функции в students.cpp .

Содержимое main.cpp останется прежним. Скомпилируйте и запустите программу. Перед тем, как приложение завершит свою работу, в директории с исполняемым файлом будет создан новый текстовый файл с оценками — students.txt .

В этой статье показано, как определить и использовать определяемые пользователем ссылочные типы и типы значений в C++/CLI.

Создание экземпляра объекта

Ссылочные типы (ref) можно создать только в управляемой куче, а не в стеке или в собственной куче. Типы значений можно создать в стеке или управляемой куче.

Неявно абстрактные классы

Неявно абстрактный класс нельзя создать экземпляр. Класс неявно абстрагируется, когда:

  • базовый тип класса является интерфейсом и
  • класс не реализует все функции-члены интерфейса.

Возможно, не удается создать объекты из класса, производного от интерфейса. Причина может быть в том, что класс неявно абстрагируется. Дополнительные сведения об абстрактных классах см. в разделе "Абстрактные".

В следующем примере кода показано, что MyClass невозможно создать экземпляр класса, так как функция MyClass::func2 не реализована. Чтобы включить компиляцию примера, раскомментируйте MyClass::func2 его.

Видимость типов

Вы можете управлять видимостью типов среды CLR. При ссылке на сборку вы можете контролировать, видимы ли типы в сборке или невидимы за пределами сборки.

По умолчанию до Visual Studio 2005 собственные типы имели общедоступную доступность за пределами сборки. Включите предупреждение компилятора (уровень 1) C4692 , чтобы узнать, где неправильно используются частные собственные типы. Используйте make_public pragma, чтобы предоставить доступ к собственному типу в файле исходного кода, который нельзя изменить.

Дополнительные сведения см. в разделе Директива using.

Выходные данные

Теперь давайте перезаписаем предыдущий пример, чтобы он был создан в виде библиотеки DLL.

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

Выходные данные

Видимость элемента

Вы можете сделать доступ к члену открытого класса из одной сборки, отличной от доступа к ней извне сборки с помощью пар описателей public доступа, protected и private

В этой таблице приведены сведения о влиянии различных описателей доступа:

Описатель Действие
public Член доступен внутри и за пределами сборки. Дополнительные сведения см. в разделе public .
private Член недоступен как внутри, так и за пределами сборки. Для получения дополнительной информации см. private .
protected Член доступен внутри и за пределами сборки, но только для производных типов. Дополнительные сведения см. в разделе protected .
internal Член является общедоступным внутри сборки, но закрытым за пределами сборки. internal — контекстно-зависимое ключевое слово. Дополнительные сведения см. в статье Context-Sensitive Keywords (C++/CLI and C++/CX) (Контекстно-зависимые ключевые слова (C++/CLI и C++/CX)).
public protected -или- protected public Член является общедоступным внутри сборки, но защищен за пределами сборки.
private protected -или- protected private Член защищен внутри сборки, но закрытый за пределами сборки.

В следующем примере показан открытый тип с элементами, объявленными с помощью разных описателей доступа. Затем он показывает доступ к этим членам из сборки.

Выходные данные

Теперь давайте создадим предыдущий пример в виде библиотеки DLL.

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

Выходные данные

Открытые и частные собственные классы

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

Затем создайте файл исходного кода, который использует собственный тип:

Теперь скомпилируйте клиент:

Статические конструкторы

Тип СРЕДЫ CLR , например класс или структуру, может иметь статический конструктор, который можно использовать для инициализации статических элементов данных. Статический конструктор вызывается по крайней мере один раз и вызывается до первого обращения к любому статическому элементу типа.

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

Компилятор не может встраивать вызов конструктора, если класс имеет статический конструктор. Компилятор не может встраивать вызов какой-либо функции-члена, если класс является типом значения, имеет статический конструктор и не имеет конструктора экземпляра. Среда CLR может встраивлять вызов, но компилятор не может.

Определите статический конструктор как частную функцию-член, так как она должна вызываться только средой CLR.

Выходные данные

Семантика указателя this

При использовании C++\CLI для определения типов this указатель в ссылочный тип имеет дескриптор типа. Указатель this в типе значения имеет тип внутреннего указателя.

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

Выходные данные

Функции скрытия по сигнатуре

В стандартном C++ функция в базовом классе скрывается функцией, которая имеет то же имя в производном классе, даже если функция производного класса не имеет такого же типа или числа параметров. Это называется семантикой скрытия по имени . В ссылочных типах функция в базовом классе скрывается только функцией в производном классе, если имя и список параметров совпадают. Она называется семантикой скрытия по сигнатуре .

Класс считается классом скрытия по сигнатуре, когда все его функции помечены в метаданных как hidebysig . По умолчанию все классы, созданные в разделе /clr , имеют hidebysig функции. Если класс имеет hidebysig функции, компилятор не скрывает функции по имени в каких-либо прямых базовых классах, но если компилятор обнаруживает класс hide-by-name в цепочке наследования, он продолжает это поведение скрытия по имени.

При семантике скрытия по сигнатуре при вызове функции в объекте компилятор определяет наиболее производный класс, содержащий функцию, которая может удовлетворить вызов функции. Если в классе есть только одна функция, которая удовлетворяет вызову, компилятор вызывает ее. Если в классе существует несколько функций, которые могут удовлетворить вызов, компилятор использует правила разрешения перегрузки для определения вызываемой функции. Дополнительные сведения о правилах перегрузки см. в разделе "Перегрузка функций".

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

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

В следующем примере показано, что функция в базовом классе не скрыта функцией в производном классе.

Выходные данные

В следующем примере показано, что компилятор Microsoft C++ вызывает функцию в самом производном классе, даже если преобразование требуется для сопоставления одного или нескольких параметров, а не вызывает функцию в базовом классе, который лучше подходит для вызова функции.

Выходные данные

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

Выходные данные

Конструкторы копии

Стандарт C++ говорит, что конструктор копирования вызывается при перемещении объекта, таким образом, что объект создается и уничтожается по одному адресу.

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

Дополнительные сведения см. в разделе /clr (компиляция среды CLR).

В следующем примере показано, когда конструктор копирования не создается.

Выходные данные

Деструкторы и методы завершения

Деструкторы в ссылочных типах выполняют детерминированную очистку ресурсов. Методы завершения очищают неуправляемые ресурсы и могут вызываться детерминированным деструктором или недетерминированным сборщиком мусора. Сведения о деструкторах в стандартном C++см. в разделе "Деструкторы".

Сборщик мусора СРЕДЫ CLR удаляет неиспользуемые управляемые объекты и освобождает память, когда они больше не требуются. Однако тип может использовать ресурсы, которые сборщик мусора не знает, как освободить. Эти ресурсы называются неуправляемыми ресурсами (например, дескрипторами собственных файлов). Мы рекомендуем освободить все неуправляемые ресурсы в методе завершения. Сборщик мусора освобождает управляемые ресурсы недетерминированно, поэтому небезопасно ссылаться на управляемые ресурсы в методе завершения. Это потому, что возможно, сборщик мусора уже очистил их.

Метод завершения Visual C++ не совпадает с методом Finalize . (Документация по CLR использует метод завершения и Finalize метод синонимов). Метод Finalize вызывается сборщиком мусора, который вызывает каждый метод завершения в цепочке наследования классов. В отличие от деструкторов Visual C++, вызов метода завершения производного класса не приводит к вызову компилятора метода завершения во всех базовых классах.

Так как компилятор Microsoft C++ поддерживает детерминированный выпуск ресурсов, не пытайтесь реализовать Dispose или Finalize методы. Однако если вы знакомы с этими методами, вот как метод завершения Visual C++ и деструктор, вызывающий сопоставление метода завершения с шаблоном Dispose :

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

Компилятор Microsoft C++ позволяет определению деструктора деструктору детерминированно очищать объекты. Деструктор используется для освобождения всех ресурсов, которые требуется детерминировать. Если метод завершения присутствует, вызовите его из деструктора, чтобы избежать дублирования кода.

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

Наличие деструктора не подразумевает наличие метода завершения. Однако наличие метода завершения подразумевает, что необходимо определить деструктор и вызвать метод завершения из этого деструктора. Этот вызов обеспечивает детерминированный выпуск неуправляемых ресурсов.

Вызов деструктора подавляет с помощью SuppressFinalizeзавершения объекта. Если деструктор не вызывается, метод завершения типа в конечном итоге будет вызываться сборщиком мусора.

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

Код, написанный на Visual C++ и скомпилированный с помощью /clr деструктора типа, если:

Объект, созданный с помощью семантики стека, выходит из области действия. Дополнительные сведения см. в разделе Семантика стека C++ для ссылочных типов.

Исключение возникает во время создания объекта.

Объект является членом объекта, деструктор которого выполняется.

Вы вызываете оператор delete для дескриптора (handle to Object Operator (^)).

Вы явно вызываете деструктор.

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

При вызове Dispose(void) типа.

Если вы не используете семантику стека для ссылочных типов и создаете объект ссылочного типа в управляемой куче, используйте синтаксис try-finally , чтобы убедиться, что исключение не препятствует запуску деструктора.

Если у типа есть деструктор, компилятор создает Dispose метод, реализующий IDisposable. Если тип, написанный на Visual C++ и имеющий деструктор, используемый с другого языка, вызов IDisposable::Dispose этого типа приводит к вызову деструктора типа. Если тип используется из клиента Visual C++, вы не можете напрямую вызвать Dispose деструктор. Вместо этого вызовите деструктор с помощью delete оператора.

Если тип имеет метод завершения, компилятор создает Finalize(void) метод, который переопределяет Finalize.

Если тип имеет метод завершения или деструктор, компилятор создает Dispose(bool) метод в соответствии с шаблоном конструктора. (Дополнительные сведения см. в разделе "Шаблон удаления"). Не удается явно создать или вызвать Dispose(bool) в Visual C++.

Если тип имеет базовый класс, соответствующий шаблону конструктора, деструкторы для всех базовых классов вызываются при вызове деструктора для производного класса. (Если тип написан в Visual C++, компилятор гарантирует, что типы реализуют этот шаблон.) Другими словами, деструктор ссылочного класса цепочек к его базам и членам, указанным стандартом C++. Сначала выполняется деструктор класса. Затем деструкторы для его членов выполняются в обратном порядке, в котором они были построены. Наконец, деструкторы для его базовых классов выполняются в обратном порядке, в котором они были созданы.

Деструкторы и методы завершения не допускаются внутри типов значений или интерфейсов.

Метод завершения может быть определен или объявлен только в ссылочных типах. Как и конструктор и деструктор, метод завершения не имеет возвращаемого типа.

После выполнения метода завершения объекта методы завершения в любых базовых классах также вызываются, начиная с наименее производного типа. Методы завершения для элементов данных не автоматически связаны с методом завершения класса.

Если метод завершения удаляет собственный указатель в управляемом типе, необходимо убедиться, что ссылки на собственный указатель или через него не собираются преждевременно. Вызовите деструктор управляемого типа вместо использования KeepAlive.

Во время компиляции можно определить, имеет ли тип метод завершения или деструктор. Дополнительные сведения см. в статье Compiler Support for Type Traits (C++/CLI and C++/CX) (Поддержка характеристик типов компилятором (C++/CLI and C++/CX)).

В следующем примере показаны два типа: один из них содержит неуправляемые ресурсы, а другой — управляемые ресурсы, которые освобождаются детерминированным образом.

Чтобы настроить, как класс инициализирует его члены или вызывать функции при создании объекта класса, определите конструктор. Конструкторы имеют имена, совпадающие с именами классов, и не имеют возвращаемых значений. Вы можете определить столько перегруженных конструкторов, сколько необходимо для настройки инициализации различными способами. Как правило, конструкторы имеют открытые специальные возможности, чтобы код за пределами определения класса или иерархии наследования может создавать объекты класса. Но вы также можете объявить конструктор как protected или private .

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

При объявлении экземпляра класса компилятор выбирает, какой конструктор будет вызываться на основе правил разрешения перегрузки:

  • Конструкторы могут быть объявлены как inline , , explicitfriend или constexpr .
  • Конструктор может инициализировать объект, объявленный как const , volatile или const volatile . Объект становится const после завершения конструктора.
  • Чтобы определить конструктор в файле реализации, присвойте ему полное имя, как и любая другая функция-член: Box::Box() .

Списки инициализаторов элементов

При необходимости конструктор может иметь список инициализаторов элементов, который инициализирует члены класса перед запуском тела конструктора. (Список инициализаторов элементов не совпадает со списком инициализаторов типа std::initializer_list .)

Предпочитать инициализаторы элементов перечисляют значения вместо назначения значений в тексте конструктора. Список инициализаторов элементов напрямую инициализирует элементы. В следующем примере показан список инициализаторов элементов, состоящий из всех identifier(argument) выражений после двоеточия:

Идентификатор должен ссылаться на член класса; он инициализирован со значением аргумента. Аргумент может быть одним из параметров конструктора, вызова функции или . std::initializer_list

const члены и члены ссылочного типа должны быть инициализированы в списке инициализаторов элементов.

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

Конструкторы по умолчанию

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

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

Если используется неявный конструктор по умолчанию, обязательно инициализировать элементы в определении класса, как показано в предыдущем примере. Без этих инициализаторов члены будут неинициализированы, а вызов Volume() создаст значение мусора. Как правило, рекомендуется инициализировать элементы таким образом, даже если не используется неявный конструктор по умолчанию.

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

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

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

Это утверждение является примером проблемы "Большинство vexing Parse". Можно интерпретировать myclass md(); как объявление функции или как вызов конструктора по умолчанию. Поскольку средства синтаксического анализа C++ предпочитают объявления по сравнению с другими вещами, выражение рассматривается как объявление функции. Дополнительные сведения см. в разделе "Большинство синтаксического анализа".

Если объявлены какие-либо конструкторы, отличные от по умолчанию, компилятор не предоставляет конструктор по умолчанию:

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

Однако для инициализации массива объектов Box можно использовать набор списков инициализаторов:

Дополнительные сведения см. в разделе "Инициализаторы".

Конструкторы копии

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

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

При определении конструктора копирования необходимо также определить оператор присваивания копирования (=). Дополнительные сведения см. в разделе "Назначение " и " Копирование конструкторов" и операторов присваивания копирования.

Вы можете запретить копирование объекта, определив конструктор копирования как удаленный:

При попытке копирования объекта возникает ошибка C2280: попытка ссылаться на удаленную функцию.

Конструкторы перемещения

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

Компилятор выбирает конструктор перемещения, когда объект инициализируется другим объектом того же типа, если другой объект будет уничтожен и больше не нуждается в его ресурсах. В следующем примере показано одно дело, когда конструктор перемещения выбирается с помощью разрешения перегрузки. В конструкторе, который вызывает get_Box() , возвращаемое значение является xvalue (значение eXpiring). Поэтому он не назначается какой-либо переменной и поэтому выходит за пределы области действия. Чтобы обеспечить мотивацию для этого примера, давайте предоставим Box большой вектор строк, представляющих его содержимое. Вместо копирования вектора и его строк конструктор перемещения "крадет" его из значения "box", чтобы вектор теперь принадлежит новому объекту. Вызов std::move необходим, так как оба vector класса string реализуют собственные конструкторы перемещения.

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

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

Дополнительные сведения о написании конструктора нетривиального перемещения см. в разделе "Конструкторы перемещения" и "Операторы присваивания перемещения" (C++).

Явно заданные по умолчанию и удаленные конструкторы

Конструкторы копирования по умолчанию , конструкторы по умолчанию, конструкторы перемещения, операторы присваивания копирования, операторы присваивания перемещения и деструкторы. Вы можете явно удалить все специальные функции-члены.

Конструкторы constexpr

Конструктор может быть объявлен как constexpr , если

  • он либо объявлен как стандартный, либо удовлетворяет всем условиям для функций constexpr в целом;
  • класс не имеет виртуальных базовых классов;
  • каждый из параметров является литеральным типом;
  • тело не является блоком try-block функции;
  • инициализированы все нестатические члены данных и подобъекты базового класса;
  • Значение , если класс является (a) объединением, имеющим члены варианта, или (б) имеет анонимные объединения, инициализируется только один из членов профсоюза;
  • каждый нестатический член данных типа класса, а все подобъекты базового класса имеют конструктор constexpr.

Конструкторы списков инициализаторов

Затем создайте объекты Box следующим образом:

Явные конструкторы

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

Можно инициализировать Box следующим образом:

Или передать целое значение функции, принимающей объект Box:

В некоторых случаях подобные преобразования могут быть полезны, однако чаще всего они могут привести к незаметным, но серьезным ошибкам в вашем коде. Как правило, необходимо использовать ключевое explicit слово в конструкторе (и определяемых пользователем операторах), чтобы предотвратить такое неявное преобразование типов:

Когда конструктор является явным, эта строка вызывает ошибку компилятора: ShippingOrder so(42, 10.8); . Дополнительные сведения см. в разделе о преобразованиях определяемых пользователем типов.

Порядок строительства

Конструктор выполняет свою работу в следующем порядке.

Вызывает конструкторы базовых классов и членов в порядке объявления.

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

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

Выполняет весь код в теле функции.

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

Выходные данные будут выглядеть следующим образом.

Конструктор производного класса всегда вызывает конструктор базового класса, чтобы перед выполнением любых дополнительных операций иметь в своем распоряжении полностью созданные базовые классы. Конструкторы базового класса вызываются в порядке наследования, например, если ClassA является производным от , производным от ClassC ClassB которого является конструктор, ClassC сначала вызывается конструктор, а затем ClassB конструктор, а затем ClassA конструктор.

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

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

Отменяется код в теле функции конструктора.

Объекты базовых классов и объекты-члены удаляются в порядке, обратном объявлению.

Если конструктор не делегируется, все полностью созданные объекты базового класса и члены уничтожаются. Однако поскольку сам объект не полностью построен, деструктор не выполняется.

Производные конструкторы и расширенная инициализация агрегатов

Если конструктор базового класса не является открытым, но доступен для производного класса, нельзя использовать пустые фигурные скобки для инициализации объекта производного типа в /std:c++17 режиме, а затем в Visual Studio 2017 и более поздних версий.

В следующем примере показана соответствующая реакция на событие в C++14:

В C++17 Derived теперь считается агрегатным типом. Это означает, что инициализация Base через закрытый конструктор по умолчанию происходит непосредственно как часть расширенного правила агрегатной инициализации. Ранее частный Base конструктор был вызван через Derived конструктор, и он был успешно выполнен из-за friend объявления.

В следующем примере показано поведение C++17 в Visual Studio 2017 и более поздних версий в /std:c++17 режиме:

Конструкторы для классов с множественным наследованием

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

Должны выводиться следующие выходные данные:

Делегирующие конструкторы

Делегирующий конструктор вызывает другой конструктор в том же классе для выполнения некоторых действий по инициализации. Эта функция полезна, если у вас есть несколько конструкторов, которые все должны выполнять аналогичную работу. Основную логику можно написать в одном конструкторе и вызвать из других. В следующем тривиальном примере Box(int) делегирует свою работу Box(int,int,int):

Объект, созданный конструкторами, полностью инициализируется сразу после выполнения любого конструктора. Дополнительные сведения см. в разделе "Делегирование конструкторов".

Наследование конструкторов (C++11)

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

Visual Studio 2017 и более поздних версий: оператор using в /std:c++17 режиме и более поздних версиях преобразует все конструкторы из базового класса, за исключением тех, которые имеют идентичную сигнатуру конструкторам в производном классе. Как правило, рекомендуется использовать наследуемые конструкторы, когда производный класс не объявляет новые члены данных или конструкторы.

Шаблон класса может наследовать все конструкторы от аргумента типа, если этот тип определяет базовый класс:

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

Конструкторы и составные классы

Классы, содержащие члены типа класса, называются составными классами. При создании члена типа класса составного класса конструктор вызывается перед собственным конструктором класса. Если у содержащегося класса нет конструктора по умолчанию, необходимо использовать список инициализации в конструкторе составного класса. В предыдущем примере StorageBox при присвоении типу переменной-члена m_label нового класса Label необходимо вызвать конструктор базового класса и инициализировать переменную m_label в конструкторе StorageBox :

Весь реальный мир состоит из объектов. Города состоят из районов, в каждом районе есть свои названия улиц, на каждой улице находятся жилые дома, которые также состоят из объектов.

Практически любой материальный предмет можно представить в виде совокупности объектов, из которых он состоит. Допустим, что нам нужно написать программу для учета успеваемости студентов. Можно представить группу студентов, как класс языка C++. Назовем его Students .

Основные понятия

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

У каждого студента есть имя — name и фамилия last_name . Также, у него есть промежуточные оценки за весь семестр. Эти оценки мы будем записывать в целочисленный массив из пяти элементов. После того, как все пять оценок будут проставлены, определим средний балл успеваемости студента за весь семестр — свойство average_ball .

Методы — это функции, которые могут выполнять какие-либо действия над данными (свойствами) класса. Добавим в наш класс функцию calculate_average_ball() , которая будет определять средний балл успеваемости ученика.

  • Методы класса — это его функции.
  • Свойства класса — его переменные.

Функция calculate_average_ball() просто делит сумму всех промежуточных оценок на их количество.

Модификаторы доступа public и private

Все свойства и методы классов имеют права доступа. По умолчанию, все содержимое класса является доступным для чтения и записи только для него самого. Для того, чтобы разрешить доступ к данным класса извне, используют модификатор доступа public . Все функции и переменные, которые находятся после модификатора public , становятся доступными из всех частей программы.

Закрытые данные класса размещаются после модификатора доступа private . Если отсутствует модификатор public , то все функции и переменные, по умолчанию являются закрытыми (как в первом примере).

Обычно, приватными делают все свойства класса, а публичными — его методы. Все действия с закрытыми свойствами класса реализуются через его методы. Рассмотрим следующий код.

Мы не можем напрямую обращаться к закрытым данными класса. Работать с этими данными можно только посредством методов этого класса. В примере выше, мы используем функцию get_average_ball() для получения средней оценки студента, и set_average_ball() для выставления этой оценки.

Функция set_average_ball() принимает средний балл в качестве параметра и присваивает его значение закрытой переменной average_ball . Функция get_average_ball() просто возвращает значение этой переменной.

Программа учета успеваемости студентов

Создадим программу, которая будет заниматься учетом успеваемости студентов в группе. Создайте заголовочный файл students.h, в котором будет находиться класс Students .

Мы добавили в наш класс новые методы, а также сделали приватными все его свойства. Функция set_name() сохраняет имя студента в переменной name , а get_name() возвращает значение этой переменной. Принцип работы функций set_last_name() и get_last_name() аналогичен.

Функция set_scores() принимает массив с промежуточными оценками и сохраняет их в приватную переменную int scores[5] .

Теперь создайте файл main.cpp со следующим содержимым.

В самом начале программы создается объект класса Students . Дело в том, что сам класс является только описанием его объекта. Класс Students является описанием любого из студентов, у которого есть имя, фамилия и возможность получения оценок.

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

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

Введенные данные мы передаем set-функциям, которые присваивают их закрытым переменным класса. После того, как были введены промежуточные оценки, мы высчитываем средний балл на основе этих оценок, а затем сохраняем это значение в закрытом свойстве average_ball , с помощью функции set_average_ball() .

Скомпилируйте и запустите программу.

Отделение данных от логики

Вынесем реализацию всех методов класса в отдельный файл students.cpp.

А в заголовочном файле students.h оставим только прототипы этих методов.

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

Над крупными проектами обычно работает несколько программистов. Каждый из них занимается написанием определенной части продукта. В таких масштабах кода, одному человеку практически нереально запомнить, как работает каждая из внутренних функций проекта. В нашей программе, мы используем оператор потокового вывода cout , не задумываясь о том, как он реализован на низком уровне. Кроме того, отделение данных от логики является хорошим тоном программирования.

В начале обучения мы говорили о пространствах имен (namespaces). Каждый класс в C++ использует свое пространство имен. Это сделано для того, чтобы избежать конфликтов при именовании переменных и функций. В файле students.cpp мы используем оператор принадлежности :: перед именем каждой функции. Это делается для того, чтобы указать компилятору, что эти функции принадлежат классу Students .

Создание объекта через указатель

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

При создании статического объекта, для доступа к его методам и свойствам, используют операция прямого обращения — « . » (символ точки). Если же память для объекта выделяется посредством указателя, то для доступа к его методам и свойствам используется оператор косвенного обращения — « -> ».

Конструктор и деструктор класса

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

Деструктор класса вызывается при уничтожении объекта. Имя деструктора аналогично имени конструктора, только в начале ставится знак тильды ~ . Деструктор не имеет входных параметров.

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

Создайте файл human.h :

Наследование от базового класса

Теперь создайте новый класс student , который будет наследником класса human . Поместите его в файл student.h .

Функция get_average_score вычисляет среднее арифметическое всех оценок студента. Все публичные свойства и методы класса human будут доступны в классе student .

Конструктор базового класса

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

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

Список оценок студента хранится в векторе.

Создание объекта класса student

Реализуем пользовательский интерфейс для работы с классом student .

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

После инициализации объекта, происходит вывод полного имени студента с помощью функции get_full_name . Эта функция была унаследована от базового класса human .

Мы реализовали часть функционала для нашей базы данных института (я конечно утрирую, когда оперирую столь серьезными высказываниями про настоящую базу данных :)

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

Нужно создать еще один класс, в котором будут храниться данные преподавателей. Дадим ему название — teacher . Как вы уже поняли, мы не будем описывать все методы этого класса с нуля, а просто унаследуем его от класса human . Тогда, не нужно будет реализовывать хранение имени, фамилии и отчества препода. Это уже есть в базовом классе human .

Создайте файл teacher.h :

У класса teacher появилось новое свойство — количество учебных часов, отведенное преподавателю на единицу времени (семестр). Весь остальной функционал наследуется от базового класса human . Если бы мы писали все с нуля, то одинакового кода бы получилось в разы больше, и его поддержка усложнилась бы на порядок.

Создание объекта класса teacher

Изменим содержимое файла main.cpp , чтобы проверить работу класса teacher .

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

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

Также, мы можем создать класс, который будет описывыть студента заочной формы обучения. Его мы унаследовали бы от класса student , добавив какие-либо дополнительные данные.

В класс human можно добавить еще больше свойств, которые будут описывать данные, имеющиеся у любого человека. Например, номер паспорта, дату рождения, прописку и место проживания.

Подобный подход позволяет в разы уменьшить дублирование кода в реальных проектах, и упросить его поддержку.

Когда нужно использовать конструктор

Если у класса много свойств — их совсем не обязательно задавать в конструкторе. Для сохранения отдельных свойств класса используют set-функции. Например, для сохранения номера паспорта, можно создать публичный метод set_passport_number(std::string number) , который будет принимать значение свойства и сохранять его в объекте, через переменную this .

Читайте также: