Foreversoft.ru

IT Справочник
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Язык си классы

Урок №113. Классы, Объекты и Методы

Обновл. 2 Фев 2020 |

Хотя C++ предоставляет ряд фундаментальных типов данных (например: char, int, long, float, double и т.д.), которых бывает достаточно для решения относительно простых проблем, для решения сложных проблем функционала этих простых типов может не хватать.

Классы

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

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

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

Результат выполнения программы выше:

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

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

Единственным существенным отличием здесь является public ключевое слово в классе. О нём мы поговорим детальнее в следующем уроке.

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

В C++ переменная класса называется экземпляром или объектом класса. Точно так же, как определение переменной фундаментального типа данных (например, int x ) приводит к выделению памяти для этой переменной, так же и создание объекта класса (например, DateClass today ) приводит к выделению памяти для этого объекта.

Методы классов

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

Класс Date с методом вывода даты:

Точно так же, как к членам структуры, так и к членам (переменным и функциям) класса доступ осуществляется через оператор выбора членов ( . ):

Результат выполнения программы выше:

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

Однако есть несколько отличий. В версии DateStruct для print() нам нужно было передать переменную структуры непосредственно в функцию print() в качестве параметра. Если бы мы этого не сделали, то print() не знал бы, какую переменную DateStruct выводить. Нам тогда бы пришлось явно ссылаться на члены структуры внутри функции.

Методы класса работают несколько иначе: все вызовы функций-членов должны быть связаны с объектом класса. Когда мы вызываем today.print() , мы сообщаем компилятору вызвать метод print() объекта today .

Рассмотрим определение метода print() ещё раз:

На что фактически ссылаются m_day , m_month и m_year ? Они ссылаются на связанный объект today (который определён caller-ом).

Поэтому, при вызове today.print() , компилятор интерпретирует:

m_day как today.m_day ;

m_month как today.m_month ;

m_year как today.m_year .

Если бы мы вызвали tomorrow.print() , то m_day ссылался бы на tomorrow.m_day .

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

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

Использование префикса m_ (англ. «m» = «members») для переменных-членов помогает различать переменные-члены от параметров функции или локальных переменных внутри методов класса. Это полезно по нескольким причинам:

Во-первых, когда мы видим переменную с префиксом m_ , то мы понимаем, что работаем с переменной-членом класса.

Во-вторых, в отличие от параметров функции или локальных переменных, объявленных внутри функции, переменные-члены объявляются в определении класса. Следовательно, если мы хотим знать, как объявлена ​​переменная с префиксом m_ , то мы понимаем, что искать нужно в определении класса, а не внутри функции.

Обычно программисты пишут имена классов с заглавной буквы.

Правило: Пишите имена классов с заглавной буквы.

Читать еще:  Синус в си шарп

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

Результат выполнения программы выше:

Name: John
Id: 5
Wage: $30

Name: Max
Id: 6
Wage: $32.75

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

Примечание о структурах в C++

В языке C структуры могут только хранить данные и не могут иметь связанных методов. В C++, после проектирования классов (используя ключевое слово class), Бьёрн Страуструп размышлял о том, нужно ли, чтобы структуры (которые были унаследованы из языка С) имели связанные функции-члены. После некоторых размышлений он решил, что нужно. Поэтому в программах выше мы также можем использовать ключевое слово struct, вместо class, и всё будет работать!

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

Правило: Используйте ключевое слово struct для структур, используемых только для хранения данных. Используйте ключевое слово class для объектов, объединяющих как данные, так и функции.

Заключение

Оказывается, стандартная библиотека C++ полна классов, созданных для нашего удобства. std::string, std::vector и std::array — это всё типы классов! Поэтому, когда вы создаёте объект любого из этих типов, вы создаёте объект класса. А когда вы вызываете функцию с использованием этих объектов, вы вызываете метод:

Классы в C++: руководство для начинающих!

Всем привет! Объекты очень важная вещь в программировании, которая может облегчить решения многих задач. Например нужно вести дневник пользователя: год рождения, имя, фамилия, местожительство, все это можно продолжать еще очень долго. Поэтому, как и другие языки программирования C++ обзавелся — классами.

Как создать класс

Чтобы объявить класс нужно использовать данную конструкцию:

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

Что такое класс

Это абстрактный тип данных. Он сочетает в себе два функционала:

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

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

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

Так свойства класса Worker (рабочий) может иметь — имя, производительность (полезность работы) за 6 месяцев, среднюю производительность.

Методы — это обычные функции, в функционале которых можно использовать свойства.

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

Что такое ООП

Раньше программистам приходилось весь функционал программы записывать в одном файле. Что в будущем неизбежно приводило к путанице из-за нескольких сотен и даже тысяч строк. А с приходом классов появилась возможность отделять любую часть программы в отдельный файл.

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

В ООП входит такие свойства:

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

Псевдо ООП в C

Язык Си не является объектно-ориентированным языком. И значит все что будет описано ниже это костыли и велосипеды.
ООП включает в себя три столпа: инкапсуляция, наследование, полиморфизм. Ниже я покажу как этих вещей можно добиться в С.

Инкапсуляция
подразумевает скрытие данных от разработчика. В ООП языках мы обычно скрываем поля класса, а для доступа к ним пишем сеттеры и геттеры. Для скрытия данных в Си, существует ключевое слово static, которое, помимо других своих назначений, ограничивает видимость переменной (функции, структуры) одним файлом.

Компилятор выдает ошибку

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

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

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

Теперь, работа с этой структурой, может осуществляться с помощью сеттеров и геттеров.

Как было показано выше, в «конструкторе» создаются две структуры, и работа с private полями ведется через функции. Конечно этот вариант не идеальный хотя бы потому, что никто не застрахован от присвоения приватной структуре null-указателя. Тем не менее, оставить один указатель лучше, чем хранить все данные в паблик структуре.

Наследование
как механизм языка не предусмотрено, поэтому тут без костылей никак не обойтись. Решение, которое приходит в голову — это просто объявить структуру внутри структуры. Но для того чтобы иметь возможность обращаться к ее полям напрямую, в C11 есть возможность объявлять анонимные структуры. Их поддерживает как gcc, так и компилятор от microsoft. Выглядит это вот так.

Компилировать надо с флагом -fms-extensions. Таким образом, становится возможным доступ к полям структуры в обход ее имени.
Но надо понимать, что анонимными могут быть только структуры и перечисления, но мы не можем объявлять анонимными примитивные типы данных.

Полиморфизм
В языках программирования и теории типов полиморфизмом называется способность функции обрабатывать данные разных типов. И такую возможность предоставляет ключевое слово _Generic, которое было введено в С11. Но стоит оговориться, что не все версии gcc его поддерживают. В _Generic передаются пары тип-значение, а при компиляции они транслируются в нужное значение. В общем, лучше один раз увидеть.

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

Здесь видно, что в зависимости от типа данных будет возвращаться разное значение. А раз _Generic возвращает какое-то значение, так почему бы ему не вернуть указатель на функцию, тогда можно заставить одну и ту же «функцию» работать с разными типами данных.

Теперь одну и туже функцию можно использовать с разными структурами.

Классы в C++ — урок 10

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

Практически любой материальный предмет можно представить в виде совокупности объектов, из которых он состоит. Допустим, что нам нужно написать программу для учета успеваемости студентов. Можно представить группу студентов, как класс языка 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.

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

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

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

Мы можем исправить двойки, если ученик будет хорошо себя вести, и вовремя сдавать домашние задания. А на «нет» и суда нет 🙂

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

Ссылка на основную публикацию
Adblock
detector