Символы кириллицы выводит некорректно. Как сделать чтобы выводился читабельный текст на русском языке? Тип приложения - не Qt, Qt Creator 4.5.0 основан на Qt 5.10.0. Win7.
|
Объектно-ориентированное программирование
10.1 Возникновение объектного подхода в программировании
Первые программы, создававшиеся в те времена, когда значения битов в регистрах переключались тумблерами на системной консоли и тут же отображались загорающимися индикаторами — эти первые программы были чрезвычайно просты. Писали их непосредственно в машинных кодах, или, в лучшем случае, на ассемблере — языке, заменяющем коды машинных команд буквенными мнемониками. В последствии, по мере усложнения компьютеров и увеличения размеров программ, отслеживать возникающие ошибки становилось всё труднее. Поэтому стала возрастать популярность языков программирования высокого уровня, а число программ, написанных целиком на языке машинных команд, наоборот, начало сокращаться. Языки высокого уровня обеспечивали более высокий уровень абстракции, приближая конструкции и операторы языка к понятиям, которыми оперирует человек.
Исторически одним из первых языков высокого уровня был Фортран, завоевавший огромную популярность и до сих пор используемый иногда в научных и инженерных расчётах. Подход к программированию, на котором был основан и он, и многие другие ранние языки, получил название процедурного программирования. В рамках этого подхода в программе отдельно хранятся процедуры — блоки кода, каждый из которых выполняет какое-то самостоятельное действие, и переменные — блоки данных (см.рис. 10.1), к которым обращаются процедуры для получения исходных значений и для сохранения результата. Такая чётко структурированная программа создавала меньше возможностей не заметить ошибку. Поэтому производительность труда программистов, освоивших процедурную парадигму, ощутимо вырастала, а вместе с производительностью труда вырастали размеры программ и их функциональные возможности. Код серьёзных программных продуктов всё чаще писался коллективно, и скоро процедурный подход перестал казаться таким уж защищённым от ошибок. Например, нередко возникали ситуации, когда несколько программистов одинаково называли свои переменные, т. е. фактически использовали одну и ту же глобальную переменную в разных целях, в результате чего её значение хаотично менялось при вызове разных процедур.
Процедурный подход претерпел ряд модернизаций, более современные языки высокого уровня заимствовали некоторые принципы функционального программирования (одним из удачных примеров такого симбиоза является язык C), большие программы делились на модули, а фирмы вводили собственные строгие политики в области оформления программного кода. Но в большой программе по-прежнему было слишком трудно разобраться и слишком просто запутаться, поэтому проблема всё равно оставалась.
Объектный подход родился как следующий важный шаг на пути качественного написания больших программ. В нём предлагается разделять программу на самостоятельные части — объекты, наделённые собственными свойствами, текущим состоянием, и умеющие взаимодействовать друг с другом и с окружающей средой — примерно так, как это происходит у объектов реального мира.
В упрощённом виде такая парадигма получила название объектноориентированного программирования (ООП) — подхода, который позволяет использовать в программе объекты и даже поощряет эту практику, но не требует, чтобы программа состояла из одних только объектов.
Классическое определение объекта звучит следующим образом:
Объект — это осязаемая сущность, которая чётко проявляет своё поведение.
Читателю, для которого объектный подход к программированию внове, такое определение наверняка покажется слишком туманным. Позже конкретные примеры прояснят ситуацию, а пока поговорим о внутреннем устройстве объекта.
Объект состоит из следующих трёх частей:
- имя объекта;
- состояние (переменные состояния);
- методы (операции).
На рис. 10.2 изображены два объекта с именами "Объект 1" и "Объект 2". Первый объект имеет две переменные состояния и три метода, в то время как второй объект обходится одной единственной переменной состояния и двумя методами.
Интерфейс объекта с окружающей средой (пользователем, остальной частью программы, операционной системой и т. д.) полностью осуществляется методами: к состоянию объекта нет другого доступа извне, кроме как через его методы. Например, если объект должен передавать окружающей среде информацию о значении одной из своих переменных состояния — для этого создают специальный метод.
Закрытость внутреннего состояния объекта от окружающей среды известна также как свойство инкапсуляции. Инкапсуляция означает, что объект содержит внутри себя данные и методы, оперирующие этими данными. Фактически, для окружающей среды объект представляет собой аналог "чёрного ящика": принимает входные воздействия и выдаёт в качестве реакции на них выходные, но при этом никак не проявляет свою внутреннюю структуру.
Для взаимодействия друг с другом объекты обмениваются сообщениями, причём объект, получивший сообщение, может либо проигнорировать сообщение, либо выполнить содержащуюся в нём команду (с помощью какого-либо из своих методов).
Однотипные объекты образуют класс. Под однотипными объектами мы понимаем такие объекты, у которых одинаковы наборы методов и переменных состояния. При этом объекты, принадлежащие одному классу, имеют разные имена и, вероятно, разные значения переменных состояния. Например, можно придумать класс "студент", объектами которого будут конкретные студенты вуза. Объект класса "студент" должен иметь переменные состояния, в которых содержится информация о конкретном студенте: Ф.И.О., номер студенческой группы, домашний адрес, список изучаемых дисциплин и т. д. Конкретный список переменных зависит от задачи, для решения которой создаётся программа. Так, если поставлена задача автоматизировать работу университетской библиотеки, то объекты класса "студент" определённо должны содержать информацию о книгах, взятых на абонемент конкретным студентом. Если автоматизируется учёт успеваемости, то в списке книг нет необходимости, но зато в состояние объекта обязательно должны быть включены оценки по изучаемым дисциплинам.
Более того, когда мы рассматриваем сущности реального мира, с которыми должна иметь дело создаваемая программа, мы можем назначить некую сущность на роль объекта или на роль класса объектов, также в зависимости от конкретной задачи. Представим себе, что одна из подзадач программы — систематизировать социальные роли, такие как "домохозяйка", "пенсионер", "студент" (например, для учёта доходов и льгот). Вполне вероятно, что в такой программе "социальная роль" будет объявлена классом, а сущность "студент" будет всего лишь одним из объектов.
Иными словами, нет одинакового для всех ситуаций правила, по которому сущность делают объектом или классом объектов. Всегда необходимо исходить из большей целесообразности.
В реальном мире из родственных по смыслу сущностей часто можно составить иерархию "от общего к частному". Такие отношения в объектно- ориентированном подходе называются наследованием. Из двух классов, находящихся в отношении наследования, более общий класс называется базовым или родительским классом, а класс, представляющий собой более частный случай, называется дочерним или производным классом. Производный класс может заимствовать атрибуты (свойства и методы) базового класса. Это означает, что если в программе используются родственные по смыслу классы, обладающие некоторыми одинаковыми свойствами и методами — лучше определить один базовый класс, находящийся в вершине иерархии, и разместить дублирующиеся свойства и методы в нём. В этом случае производные классы смогут автоматически унаследовать эти атрибуты от базового класса, и поэтому их не придётся описывать снова и снова. Например, если программа оперирует классами "студент", "преподаватель" и "инженер", логично ввести дополнительный базовый класс "человек", переместив в него атрибуты, содержащие имя, адрес, другие личные данные, а также методы, манипулирующие этими данными.
Помимо двух уже рассмотренных качеств — инкапсуляции и наследования — у объектов есть ещё третье основополагающее качество: полиморфизм. Это означает, что объекты могут вести себя по-разному в зависимости от ситуации. Одно из основных проявлений полиморфного поведения — перегрузка функций. Объект может содержать в себе несколько методов с одинаковыми именами, принимающих разные наборы параметров. В результате, передавая объекту данные, можно обращаться к одному и тому же имени метода, не заботясь о типе, в котором представлены данные. Правильно сконструированный объект автоматически выполнит наиболее подходящий метод из группы.
Инкапсуляция, наследование и полиморфизм являются тремя основополагающими принципами объектно-ориентированного программирования и в том или ином виде реализуются во всех объектно-ориентированных языках. В следующих разделах мы увидим, как конкретно эти принципы применены в C++.