среда, 15 апреля 2009 г.

Создаём компьютерную игру. Выпуск 11.


ПРОФЕССИОНАЛЬНАЯ КОНФЕРЕНЦИЯ ПО СИСТЕМНОМУ АДМИНИСТРИРОВАНИЮ
В составе любого современного бизнеса есть IT-составляющая, поэтому конференция, призванная научить обслуживать эту IT-составляющую, по праву может считаться центральным IT-мероприятием! RootConf пройдет в Москве, 13 и 14 апреля в конференц-центре Инфопространство. Из последних новостей: определена Программа конференции из 50 докладов и мастер-классов, спонсоры мероприятия оплатили 100 льготных билетов - спешите, их осталось всего 69!


Указатели

Доброго времени суток. Вы читаете одиннадцатый выпуск рассылки "Создаём компьютерную игру".

Для тех, кто только присоединился:
Узнать чем мы здесь занимаемся, Вы можете, изучив архив. В общих чертах узнать о целях рассылки можно в первом выпуске или в разделе "О сайте" на сайте рассылки.

В прошлом выпуске мы обсуждали установку DirectX SDK. Если Вы скачивали архив с сайта рассылки, то скорее всего он у Вас не распаковался. У меня временно не было ftp-доступа к сайту, поэтому я не мог перезалить архив как только узнал. Четвёртого апреля (в субботу) архив был исправлен, сейчас всё работает.

Сегодняшняя же тема - указатели.

Начнём как обычно издалека. Когда операционная система запускает программу которую Вы написали, все данные относящиеся к программе помещаются в оперативную память. Как Вы, наверное, помните (из статьи про типы данных), память в компьютере состоит из байтов. Байты в памяти нумерются, т.е. у каждого байта есть порядковый номер - его адрес. С помощью адреса, процессор может обратиться к любому байту в памяти. Адреса хранятся в шестнадцатиричном формате. Обычно мы используем десятичную систему. В компьютерах удобнее всего использовать двоичную (числа формируются всего из двух цифр: 0 и 1), или степень двойки (16 = 24). При этом в шестнадцатиричной системе 16 цифр: 0, 1, ... 9, A, B, C, D, E, F (где цифры от a до f соответствуют десятичным 10-15). Адрес выглядит примерно так: 0x0012fc2c = 1244204 в десятичной системе счисления. 0x (ноль икс) перед адресом, говорит, что число - шестнадцатиричное.

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

При этом данные хранятся в двух разных участках памяти: стеке и куче:

 int a = 0;  int main() {   int b = 0;   return 0; }

a хранится в куче (heap), b хранится в стеке (stack). Все глобальные переменные попадают в кучу, все локальные переменные попадают в стек. что такое стек, Вы можете узнать на сайте рассылки. Куча же - это область памяти где данные никак не организованы.

Теперь, рассмотрим простое объявление переменной:

int a;

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

При создании переменной a, ей присваивается адрес 0x0012fc28 (адрес взят для примера). Процессор обращается к этой переменной с помощью этого адреса. Сама же переменная занимает в памяти следующие байты: 0x0012fc28, 0x0012fc29, 0x0012fc2a, 0x0012fc2b (обратите внимание на последние цифры в адресах). Где первый байт - адрес переменной.

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

Следует заметить, что Указатели есть не во всех языках программирования.

Указатель (pointer) - это переменная, значением которой является адрес.

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

cout << &a;

На экран будет выведен адрес переменной a.

Указатель можно создать следующим образом:

 int a = 0; int* ptr;  ptr = &a;  cout <

Здесь & - операция получения адреса. Не путайте использование & для получения адреса и для передачи в функцию значения по ссылке.

В данном примере мы создали указатель ptr на тип int и присвоили этому указателю адрес, где хранится переменная a.

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

Через указатель можно изменять значение хранящееся по адресу. Для этого указатель нужно разыменовать:

 int a = 0; int* ptr = a;  *ptr = 10; // то же самое что и: a = 10;  cout <

Здесь мы воспользовались указателем, чтобы изменить значение переменной a. В данном случае * - операция разыменования. Не путайте с объявлением указателя!

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

Указатель на тип void

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

Указатели на разные типы не могут использоваться друг с другом:

 int a = 0; float b = 1;  int* ptr_a = &a; float* ptr_b = &b; // ptr_a = ptr_b; // Так нельзя!!!

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

 int a = 0; float b = 1; char c = 2;  int* ptr_a = &a;   // Указатель на тип int float* ptr_b = &b; // Указатель на тип float void* ptr_c = &c;  // указатель на void  // ptr_a = ptr_b; // Так нельзя!!!  ptr_c = a; // Оба варианта ptr_c = b; // корректны

Указатели на void особенно полезны при использовании с классами. В DirectX очень многие объекты создаются как указатели на void.

Указатели-константы

Мы уже не раз встречались с указателями-константами. Это такие указатели, значение которых не может быть изменено. Т.е. не могут быть изменены адреса. Значения же хранящиеся в адресах могут изменяться.

Более известное имя указателей-констант - массивы.

Хотя надо заметить, что данный вид указателей используется не только с массивами:

 int a = 1; int* ptr_a = &a; void* const c_ptr; *c_ptr = 2; // c_ptr = ptr_a; // так не получится. c_ptr - константа *c_ptr = 3;

Передача аргумента в функцию по указателю

Мы уже умеем передавать аргументы в функции двумя способами: по значению и по ссылке. Третий способ - передача по указателю (pass-by-pointer).

 int main() {   int a = 10;   pass_by_pointer(&a); // Передача адреса переменной    return 0; }  void pass_by_pointer (void* ptr) { *ptr = 5; }

Операция new

В программе pseudo_game мы использовали заданный массив. Мы не могли сделать что-нибудь подобное:

 cin >> s; // количество строк cin >> c; // количество столбцов  char map[s][c];

Компилятор должен знать заранее (до начала выполнения программы) сколько памяти выделить на массив. То же самое и с классами: все объекты классов должны быть созданы до начала выполнения программы. Вы не можете динамически создать объект.

Операция new позволяет обойти это ограничение. (В примере используется класс tank из программы pseudo_game):

 /* Здесь представлены сразу два примера: создание указателя на tank и выделение памяти для хранения всех клеток игрового поля */  tank* t34 = new tank; // выделение памяти под объект tank                       // Здесь t34 указатель на тип tank  int s,c; // количество строк и столбцов cin >> s; cin >> c;  int symbols = s*c; // общее количество клеток  char* map; // указатель map = new char [symbols]; // выделение памяти на все клетки 

Здесь t34 и map - указатели.

В данном примере мы вынуждены использовать одномерный массив для представления двумерного. Как в данном случае получить доступ к произовльному элементу? Вот как выглядит инициализация клетки (5,6):

 *(map+5*c+6+1) = 'Т'; 

Думаю, данный пример нуждается в пояснениях. Запись (5,6) - координата клетки, куда нам нужно что-нибудь поместить. 5 - шестая строка. Напомню, что отсчёт ведётся с нуля. 6 - седьмой столбец. В одномерном массиве данный адрес нужно считать так: в строке - c элементов (пусть пользователь ввёл 10). Сначала нужно посчитать все клетки в предыдущих строках (пять строк (нулевая, первая, вторая, третья, четвёртая) по 10 элементов) - 5*с. Затем к этому количеству нужно прибавить ещё 6+1 (дополнительная клетка нужна, так как клетки отсчитываются от нуля).

Получившееся значение (57) мы прибавляем к map. А map - указатель на char. И вот этот вот 57-й адрес от начала массива мы разыменовываем и присваиваем ему 'T'. Как просто!!! :)

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

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

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

Операция delete

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

 delete [] map;  delete t34;

Лучше всего освобождать память всегда когда Вы её выделяете. Особенно когда память выделялось в какой-нибудь из функций.

Доступ к объектам

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

t34->fuel = 100;

На сегодня всё. Следуюий выпуск опять будет не про C++. Постараюсь всё-таки вернуться на запланированную частоту выхода рассылки (один раз в неделю). Пока не получается. Сначала было очень много работы, сейчас уже надо писать выпуски по математике, она нам скоро пригодится. К тому же уже пора переносить старые выпуски на сайт.

Ведущий - Роман.
Если у вас есть вопросы или что-то непонятно по данному выпуску, пишите на e-mail: el_rey2007@bk.ru

P.S.: Если вы читаете данный выпуск через месяц или более после выхода, пожалуйста, загляните на сайт рассылки, там вы найдёте исправленную и дополненную информацию из выпуска.

Internet explorer 8 финал
Обнови свой браузер, скачай восьмую версию.
Терминал.ру - автомат успеха!
Комфортный вход и успех в сетевых проектах. Не нужно приглашать людей!
Отзывы о товарах и услугах!
База отзывов о товарах и услугах, собранная сообществом пользователей.


Глава Microsoft Стив Балмер 20 апреля в Москве на конференции для веб-разработчиков ReMIX
Новейшие технологии для веб-разработчиков, веб-дизайнеров и пользователей Интернет. Бесплатная регистрация и доступ ко всем материалам конференции. Конкурсы с ценными призами.


Сообщить о нарушении данной рассылкой правил Сервиса
Отписаться : Нажмите и отправьте это письмо

Комментариев нет:

Отправить комментарий