Наследование классов в С++
В этом туторе я попытаюсь кратко рассказать про наследование классов в С++ и о том, как избежать самой главной ошибки новиков в кодинге - копирования одинакового кода целиком.
Итак приступим. Несмотря на то, что наследование классов - это стандартная функция С++, постараюсь рассказать о ней максимально приближенно в кодингу в Half-Life. Если вы захотите узнать о ней вообще - можете почитать соответствующую литературу по языку :)
Для начала я хочу разобрать самую распространенную ошибку новичков в кодинге - это переписывание необходимых функций целиком. Как же это выглядит на практике ?
А вот как - хочет бедный ньюб сделать себе новую пушку - ну допустим там, автомат какой. и надо ему в этом автомате - лишь модельку сменить, да и ту, что в руках. а все остальное - должно таким же оставаться.
Теперь собсно сам процесс - выделяем mp5.cpp ctrl+C, ctrl+V, поменяем ему дефайн на какой нить WEAPON_AUTOMAT - причем перед этим забыв его прописать в weapons.h - компилятор ехидно говорит новичку, что он дурак.
Начинается усиленный процесс мышления, в ходе которого рождается первый закон программирования на C++ : "функции в одном классе не могут иметь одинаковые имена!" - порой влияние этого закона становиться настолько сильным, что увидев пергрузку функций человек в ужасе удаляет этот код втайне тихо недоумевая почему компилятор не видит столь вопиющего безобразия, но не забывая ругаться, на его собственные, заботливо скопированные файлы\строки - делается вывод о пристрастности компилятора лично к нему, в голову лезут мысли о том, что гадкий мелкософт чего-то там опять намудрил, выпустив такой глючный продукт.
Твердо усвоив вышеприведенный закон новичок вполне логично выводит, что раз функции не могут иметь одинаковые имена - значит им надо присвоить разные, и чтобы самому не запутаться - каждой функции в имени добавлет по одной букве - например P. Вот и получаются у нас загадочные Mp5p, CgrenadeP и прочие извраты. Любой мало-мальский опытный кодер, глян
ув на этот изврат непременно подумает пару нехороших слов, про "автора" этого кода, а менее воспитанный еще и скопирует эти слова из мозга в голосовой буфер, чтобы воспроизвести все это в адрес новичка :)
Но как я уже сказал, влиянее первого закона + страх перед компилятором + извечное программерское "если это работает - лучше я не буду ничего трогать" создают в мозгу новичка непреодолимый психологический барьер, преодолеть который для многих, порой - невыполнимая задача. Вот и кодинг для них в основном сводиться переименовыванию имен функций...
Это у нас была теория с небольшим отклоненением в психологию, теперь перейдем непосредственно к практике, и пусть знания, полученные вами из этого тутора станут первым шагом на пути к написанию кода с нуля :)
Для начала рассмотрим само понятие "наследование классов". Я не изучал подробно какие-либо языки кроме асма и С++, а поэтому не знаю есть ли классы в том же визуал бейсике или делфи (скорее всего нету). Поэтому предполагаю, что вы с этим понятием столкнулись впервые и не знаете, что оно из себя представляет. Итак наследование классов - это иерархия. Легче все представить себе это графически. Откройте файл cbase.h (лучше всего из спирита - там нагляднее, но и обычная халфа тоже подойдет). Вот собсно как все выглядит.
прим. G-Cont. иерархия на данный момент дана для спирита 1.5 CB
Class Hierachy на самом деле - это не класс, просто для удобства.
Давайте подробно рассмортим, какой класс подчиняется какому и почему было сделано именно так, а не иначе.
Как видно из рисунка - главным классом является CBaseEntity - из него собсно "произрастают" все остальные. Именно в классе CBaseEntity нахоядтся всем знакомые pev->skin, pev->body, pev->sequence и прочие параметры. (если быть точным, то они входят в глобальную структуру entvars_t, но я не хочу сильно усложнять материал).
Как вы знаете - все эти pev->skin, pev->body, pev->sequence доступны из любой энтити и компилятор вам не скажет, что он видит эту переменную первый раз в жизни.
Далее на одном и том же уровне идут CPointEntity и CBaseDelay, чисто от самой pointentity можно добиться не так уж и много - зачастую, их юзают просто для указания координат, например как info_player_start.
CBaseDelay - мощная штука - из него практически вырастает все, что есть на сервере. Как видите оттуда идут патроны и итемы игрока, а также его оружие.
Из CBaseToggle у нас получаються основные брашевые энтити - всякие двери и поезда, а из CBaseMonster - даже сам игрок :) Ведь игрок по сути дела - тоже монстр, только управлемый геймером :).
Данная иерархия очень упрощенная и не содержит ВСЕХ классов сервера, она нужна, просто для понятия кодером что кому принадлежит. так допустим, если это CBaseToggle, то в его класс входят всякие функции для перемещения брашевых энтитей. А для оружия - всякие там функции добавления - удаления патронов.
Ессно, что чем глубже класс - тем больше в его распоряжении всяких функций от надклассов, которые даже не нужно вызывать - они уже готовы к использованию. Ну а теперь вернемся к пресловутому примеру с оружием.
Вот два рисунка. Задача следующая: добавить новую пушку, у которой требуется поменть только лишь модельку и кол-во патронов.
Собственно это и есть основная идея наследования классов.
Хотим создать новую пушку на основе старой, заменив модель ? нет проблем :)
class Cnewweapon : public CMP5 и мы имеем в своем распоряжении ВСЕ функции MP5, причем нам не надо их даже декларировать!
Что у нас изменилось ? v_ моделька пушки ? в каких функциях она у нас прописана ? Замечательно - в Precache и в deploy. декларируем две эти функции. В прeкеше можно просто прекешить нашу новую модельку и не писать более ничего - так или иначе, все остальное уже прекешировано в Mp5 :)
Теперь пишем коротенькую функцию deploy, сменив там модельку автомата на свою. Ну и ессно нужно добавить LINK_ENTITY_TO_CLASS - для нашей новой пушки.
Что-то еще ? ах да - информация об оружии.
int CnewWeapon::GetItemInfo(ItemInfo *p)
{
p->pszName = STRING(pev->classname);
p->pszAmmo1 = "9mm";//пишете хоть ракеты - это влияет только на тип добавляемых боеприпасов
p->iMaxAmmo1 = _9MM_MAX_CARRY;//сколько патронов может вместить пушка
p->pszAmmo2 = NULL;
p->iMaxAmmo2 = -1;
p->iMaxClip = GLOCK_MAX_CLIP;//размер клипа
p->iSlot = 1;
p->iPosition = 0;
p->iFlags = 0;
p->iId = m_iId = WEAPON_AUTOMAT;//не забудьте продефайнить в weapons.h ;)
p->iWeight = GLOCK_WEIGHT;// а это можно вообще не трогать :)
return 1;
}
Ессно все добавлемые функции нужно внести в класс новой пушки :)
Ну вот мы и получили пушку, код которой весит в 6 раз меньше оригинала (можете даже вставить этот код в mp5.cpp, если не хотите создавать отдельный файл, которая обладает всеми свойствами Mp5, и все же является совершенно другим, самостоятельным оружием :)
Я специально рассмотрел пример с оружием, но вам не составит никакого труда также добавить нового монстра - например отиса, сделав его из барни :)
Ну вот вроде бы и все на сегодня - надеюсь я понятно объяснил материал.
Надеюсь что эти 8 с лишним килобайт текста пойдут вам на пользу.
Пишите комментарии отзывы и вопросы, если вдруг что непонятно :)
g-cont