Загрузка...
 
Печать
ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  Основы языка Cpp

Основы языка С++




Язык C++(external link) - компилируемый, объектно-ориентированный, статически типизированный язык программирования общего назначения. До недавнего времени подавляющее большинство коммерческих игр для ПК и консолей создавалось с его помощью. Даже приход платформы .NET и языка программирования C# (читается "Си шарп") не смог кардинально изменить ситуацию. По C++ написана не одна сотня книг (многие из которых на русском языке). А так как данный курс не претендует на полноту изложения, настоятельно рекомендуем прочесть хотя бы пару из них. Не факт, что всё усвоишь, но основные моменты всё равно запомнишь.

Объектно-ориентированное программирование. Понятие класса

Независимо от языка программирования объектно-ориентированный подход имеет ряд общих принципов:
  • Возможность создавать абстрактные типы данных, позволяющая наряду с предопределёнными типами данных (integer, bool, double и др.) вводить свои собственные типы данных (классы) и объявлять совего рода "переменные" таких типов данных (объекты). В этом случае программист оперирует не машинными терминами (переменная, функция), а объектами реального мира, поднимаясь тем самым на новый абстрактный уровень. Напрмер яблоки и людей нельзя умножать друг на друга. Низкоуровневый код запросто может совершить такую логическую ошибку, тогда как при использовании абстрактных типов данных такая операция становится невозможной.
  • ИНКАПСУЛЯЦИЯ допускает взаимодействие пользователя с абстрактными типами данных только через их интерфейс. Реализация объекта остаётся закрытой от посторонних глаз с целью предотвратить модифицирование (изменение) этих ообъектов. Использование инкапсуляции позволяет разработать автономный объект и использовать его, прибегая лишь к небольшому числу интерфейсных методов и не заботясь о его внутренней реализации. Каждый объект касса ответственнен только за свои действия. Поэтому игра не особо "заботится" о том, что происходит с каждым из объектов её классов.1 Игра лишь предоставляет основу (фреймворк) для создания объектов классов. Как правило, в основе данного фреймворка лежит игровое поле (экран) с объектами на нём, а также некий цикл обновления (update loop), который сообщает объектам, что прошло столько-то времени. Для неигровых персонажей (NPC) цикл обновления указывает направление движения/атаки. Для персонажа игрока вдобавок происходит обработка пользовательского ввода. Т.е. несмотря на то, что каждый объект игры в принципе автономен, каждый из них знает, что делать в каждый момент времени.
  • НАСЛЕДОВАНИЕ позволяет на основе существующего класса создавать другие классы, которые автоматически получают его возможности. Зачастую многие классы бывают достаточно сложны, поэтому прибегают к их последовательной разработке, выстраивая иерархию классов от общего к частному.
  • ПОЛИМОРФИЗМ - возможность классов содержать методы с одинаковым названием, но с разным набором аргументов. Очень полезно, например, при обработке массивов данных разного типа.
Абстрактные типы данных (в нашем случае это классы) позволяют программисту вводить в программу переменные с желаемыми свойствами, когда возможностей существующих в языке типов данных не хватает. Связи между объектами реального мира зачастую настолько сложны, что для их эффективного моделирования необходим отдельный язык программирования. Разрабатывать специализированный язык программирования для каждой прикладной задачи - очень дорогое удовольствие. Поэтому во многие языки программирования (C++, PHP, Java, Python и др.) вводится объектно-ориентированный подход, который позволяет создать свой мини-язык путём введения классов и объектов. Переменными здесь уже являются объекты, в качестве типа данных для которого выступает класс. Класс описывает состав объекта - переменные и функции, которые тем самым определяют поведение объекта.
Классы являеются своего рода "шаблонами" для создания на их основе различных объектов.
Одним из самых больших преимуществ ООП является возможность повторного использования ранее написанного кода (т.н. рефакторинг, reusing). Т.е. если вдруг решишь сделать ролевую игру на основе готовой (например на просторах космоса), нет необходимости повторно создавать игровые объекты с нуля. Достаточно отредактировать уже имеющиеся.

Использование заголовочных файлов (header files)

Функционал некоторого исходного файла (с расширением .cpp) может быть представлен его заголовочным фалом (с расширением .h). К заголовочным файлам часто обращаются как к интерфейсам. Например, ты можешь создать класс с названием Animal (от англ. Animal - животное), который воспроизводит функционал некоего "базового" животного в отдельно взятой игре. Класс будет объявлен в заголовочном файле (в C++, перед использованием чего угодно, это "что угодно" необходимо сначала объявить) (например animal.h) и затем уже будет реализован (имплементирован) в исходном файле, ассоциированном с ним (Animal.cpp). Таким образом, заголовочный файл предоставляет интерфейс для класса Animal, предназначенный для того, чтобы класс и функции этого класса были доступны для других файлов в пределах данной гипотетической игры.

Следующий пример показывает объявление нашего класса Animal, которое размещается в заголовочном файле animal.h. Это объявление затем становится интерфейсом класса Animal. Данный код (вообще, он считается "псевдокодом") не нужно отправлять на компиляцию в MS Visual C++. Его надо просто прочитать и попытаться в нём разобраться.

animal.h
class Animal
{
public:
Animal();		//Объявление конструктора
virtual ~Animal();	//Объявление деструктора

private:
	bool m_alive;
};

Наш простой класс имеет конструктор(external link), деструктор(external link) и одну переменную типа bool. Тип Boolean подразумевает всего два значения - true (истина) или false (ложь). Перед деструктором стоит служебное слово virtual, о котором мы расскажем позднее. Пока лишь отметим, что здесь оно также необходимо.

Но мы двигаемся дальше, и сейчас посмотрим на реализацию этого класса, размещённую в исходном файле Animal.cpp.
Animal.cpp
#include "animal.h"

Animal::Animal()	//Реализация конструктора
{
	m_alive = true;
}

Animal::~Animal()	//Реализация деструктора
{
	m_alive = false;
}

Для того, чтобы компилятор(external link) связал объявление класса с исходным фалом Animal.cpp, где содержится реализация класса, в начале Animal.cpp необходимо указать ссылку на заголовочный файл (animal.h). Для этого в начале Animal.cpp размещают специальное выражение #include (от англ. "вложить"). Оно просто "говорит" компилятору взять всё содержимое указанного файла и разместить его на этом месте. В нашем случае в начале исходного файла мы ставим команду #include "animal.h".

Но зачем вообще нужен заголовочный файл? Нельзя ли обойтись без него, разместив весь исходный код (объявление класса + его реализация) в одном исходном файле (.cpp)? Ответ - да, можно. Более того, ты можешь разместить функции (в нашем случае это конструктор и деструктор) прямо внутри объявления класса:
Animal.cpp
class Animal
{
	public:
		Animal()
		{
			m_alive = true;
		};

	virtual ~Animal()
		{
			m_alive = false;
		};

	private:
		bool m_alive;
}

Но такой подход имеет свои недостатки:
  1. Если ты размещаешь объявление класса в начале исходного файла. то теряешь возможность иметь отдельно вынесенный интерфейс для своего класса. На деле это усложнит код, что, в свою очередь, снизит его пригодность к обслуживанию и внесению измемнений (Maintainability) и, как следствие, усложнит его использование (usability). Подробнее об этом читаем здесь. Это не проблема, когда приложение содержит всего один класс. Но любая игра насчитывает десятки, а то и сотни классов, размещать которые в одном исходном файле будет крайне необдуманно.
  2. Если ты размещаешь реализацию класса внутри его объявления, у тебя нет защищённого интерфейса. Вся суть интерфейсов состоит в том, чтобы позволить разработать класс, который может быть позднее использован в других проектах БЕЗ отображения его реализации. Таким образом, для того чтобы выяснить, что делает данный класс, любой желающий может просмотреть его интерфейс (обычно это заголовочный файл с расширением .h). Но никто не может увидеть исходный код с реализацией этого класса, скрытый, как правило, в недрах динамически подключаемой библиотеки (.dll). Экспонирование функций динамически подключаемой библиотеки (.dll) в заголовочных файлах (.h) - обычная практика в программировании. И хотя, вместо DLL-библиотеки, у нас вполне открытый и доступный для прочтения исходный файл (.cpp), принцип остаётся тот же.

В нашей гипотетической игре, если мы захотим создать ещё один инстанс(external link) (от англ. instance - экземпляр) нашего класса Animal (уже в другом исходном файле), всё, что нам нужно будет сделать, это вставить выражение #include с указанием на интерфейс этого класса где-то в начале исходного файла, который будет создавать этот экземпляр. Само собой, выражение #include необходимо размещать ДО того места в коде, где ты пытаешься использовать данный класс. В следующем примере мы вставляем выражение #include "animal.h" в "какой-то другой исходный файл" и создаём после этого экземпляр класса Animal (изначально определённый в animal.h), назвав его dog:
SomeOtherSourceFile.cpp
#include "animal.h"

int WINAPI WinMain(HINSTANCE instance, HINSTANCE prev, LPSTR cmdLine, int cmdShow)
{
	Animal dog;

	return true;
}

Этот принцип мы возьмём на вооружение и будем повсеместно применять при создании движка и игры. Едва ли не каждый класс будет создаваться со своим внешним интерфейсом (в виде заголовочного файла .h). При программировании движка эти интерфейсы будут отлинкованы (скомпонованы через выражения #include) в один "центральный" заголовочный файл Engine.h, что полностью удовлетворяет нашему требованию о "единой точке контакта". Весь функционал нашего будущего движка будет представлен в одном этом файле. Более того, для использования движка (например при создании игры) достаточно включить в проект его главный заголовочный файл, указав в начале исходного файла (.cpp) всего одно выражение #include "Engine.h". Чуть позднее мы ещё раз обсудим концепцию "единой точки контакта".

Служебное слово virtual и "виртуальный" деструктор

В нашем предыдущем примере с классом Animal, ты заметил ключевое слово virtual, стоящее перед деструктором класса. Оно служит для информирования компилятора о том, что данная функция может быть переопределена (overriden) дочерним классом.

Источники:


1. Morrison M. Sams Teach Yourself Game Programming in 24 hours. - Sams Publishing. 2002


ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  Основы языка Cpp

Contributors to this page: slymentat .
Последнее изменение страницы Пятница 12 / Июль, 2019 13:00:45 MSK автор slymentat.

Помочь проекту

Яндекс-деньги: 410011791055108