| |||
УказателиДоброго времени суток. Вы читаете одиннадцатый выпуск рассылки "Создаём компьютерную игру". Для тех, кто только присоединился: В прошлом выпуске мы обсуждали установку DirectX SDK. Если Вы скачивали архив с сайта рассылки, то скорее всего он у Вас не распаковался. У меня временно не было ftp-доступа к сайту, поэтому я не мог перезалить архив как только узнал. Четвёртого апреля (в субботу) архив был исправлен, сейчас всё работает. Сегодняшняя же тема - указатели. Начнём как обычно издалека. Когда операционная система запускает программу которую Вы написали, все данные относящиеся к программе помещаются в оперативную память. Как Вы, наверное, помните (из статьи про типы данных), память в компьютере состоит из байтов. Байты в памяти нумерются, т.е. у каждого байта есть порядковый номер - его адрес. С помощью адреса, процессор может обратиться к любому байту в памяти. Адреса хранятся в шестнадцатиричном формате. Обычно мы используем десятичную систему. В компьютерах удобнее всего использовать двоичную (числа формируются всего из двух цифр: 0 и 1), или степень двойки (16 = 24). При этом в шестнадцатиричной системе 16 цифр: 0, 1, ... 9, A, B, C, D, E, F (где цифры от a до f соответствуют десятичным 10-15). Адрес выглядит примерно так: 0x0012fc2c = 1244204 в десятичной системе счисления. 0x (ноль икс) перед адресом, говорит, что число - шестнадцатиричное. Ну так вот, у каждого байта есть свой адрес. Когда ваша программа попадает в оперативную память, всем данным присваиваются адреса, чтобы процессор мог к ним обращаться. Каким данным присваиваются адреса: переменным, функциям, структурным переменным, объектам классов. При этом данные хранятся в двух разных участках памяти: стеке и куче:
a хранится в куче (heap), b хранится в стеке (stack). Все глобальные переменные попадают в кучу, все локальные переменные попадают в стек. что такое стек, Вы можете узнать на сайте рассылки. Куча же - это область памяти где данные никак не организованы. Теперь, рассмотрим простое объявление переменной: int a; a - идентификатор с помощью которого мы можем обращаться к какой-то области памяти. При этом данная переменная занимает четыре байта. Обращение к переменной происходит по первому байту. При создании переменной a, ей присваивается адрес 0x0012fc28 (адрес взят для примера). Процессор обращается к этой переменной с помощью этого адреса. Сама же переменная занимает в памяти следующие байты: 0x0012fc28, 0x0012fc29, 0x0012fc2a, 0x0012fc2b (обратите внимание на последние цифры в адресах). Где первый байт - адрес переменной. Теперь мы плавно переходим к рассмотрению указателей. Следует заметить, что Указатели есть не во всех языках программирования. Указатель (pointer) - это переменная, значением которой является адрес. Чтобы получить адрес обычной переменной можно воспользоваться следующим синтаксисом: cout << &a; На экран будет выведен адрес переменной a. Указатель можно создать следующим образом:
Здесь & - операция получения адреса. Не путайте использование & для получения адреса и для передачи в функцию значения по ссылке. В данном примере мы создали указатель ptr на тип int и присвоили этому указателю адрес, где хранится переменная a. Когда Вы создаёте указатель, всегда инициализируйте его каким-нибудь значением. По умолчанию в указателе может храниться любой адрес, если Вы попытаетесь изменить значение в этом адресе, то вполне вероятно, что как минимум возникнет ошибка при выполнении программы. Через указатель можно изменять значение хранящееся по адресу. Для этого указатель нужно разыменовать:
Здесь мы воспользовались указателем, чтобы изменить значение переменной a. В данном случае * - операция разыменования. Не путайте с объявлением указателя! При разыменовании указателя мы получаем доступ к значению адреса в памяти, на который указывает указатель. Указатель на тип voidДо сих пор мы использовли ключевое слово void (void - недействительный, пустой) в заголовках функций, когда нам не нужно было возвращать никаких значений. Указатели на разные типы не могут использоваться друг с другом:
Чтобы обойти это ограничение можно воспользоваться указателем на void:
Указатели на void особенно полезны при использовании с классами. В DirectX очень многие объекты создаются как указатели на void. Указатели-константыМы уже не раз встречались с указателями-константами. Это такие указатели, значение которых не может быть изменено. Т.е. не могут быть изменены адреса. Значения же хранящиеся в адресах могут изменяться. Более известное имя указателей-констант - массивы. Хотя надо заметить, что данный вид указателей используется не только с массивами:
Передача аргумента в функцию по указателюМы уже умеем передавать аргументы в функции двумя способами: по значению и по ссылке. Третий способ - передача по указателю (pass-by-pointer).
Операция newВ программе pseudo_game мы использовали заданный массив. Мы не могли сделать что-нибудь подобное:
Компилятор должен знать заранее (до начала выполнения программы) сколько памяти выделить на массив. То же самое и с классами: все объекты классов должны быть созданы до начала выполнения программы. Вы не можете динамически создать объект. Операция new позволяет обойти это ограничение. (В примере используется класс tank из программы pseudo_game):
Здесь t34 и map - указатели. В данном примере мы вынуждены использовать одномерный массив для представления двумерного. Как в данном случае получить доступ к произовльному элементу? Вот как выглядит инициализация клетки (5,6):
Думаю, данный пример нуждается в пояснениях. Запись (5,6) - координата клетки, куда нам нужно что-нибудь поместить. 5 - шестая строка. Напомню, что отсчёт ведётся с нуля. 6 - седьмой столбец. В одномерном массиве данный адрес нужно считать так: в строке - c элементов (пусть пользователь ввёл 10). Сначала нужно посчитать все клетки в предыдущих строках (пять строк (нулевая, первая, вторая, третья, четвёртая) по 10 элементов) - 5*с. Затем к этому количеству нужно прибавить ещё 6+1 (дополнительная клетка нужна, так как клетки отсчитываются от нуля). Получившееся значение (57) мы прибавляем к map. А map - указатель на char. И вот этот вот 57-й адрес от начала массива мы разыменовываем и присваиваем ему 'T'. Как просто!!! :) На самом деле - действительно просто. Нужно только привыкнуть. Многие функции в наших программах придётся переписывать, но указатели позволяют сделать программу более гибкой. Запомните: когда мы прибавляем какое-то значение к указателю, значение указателя увеличивается не на количество байтов, а на количество элементов того типа, на который указывает указатель. Вот почему так важно при объявлении указателя писать тип. Кстати, такой вид двухмерных массивов очень популярен. Когда видеокарта выводит на экран пиксели, все вычисления производятся именно с одномерным массивом. Операция deleteОперация delete используется для освобождения памяти после того как она стала не нужна:
Лучше всего освобождать память всегда когда Вы её выделяете. Особенно когда память выделялось в какой-нибудь из функций. Доступ к объектамПоследнее на что хочу обратить внимание. Доступ к членам класса объекта созданного с помощью указателей отличается от того, что мы видели раньше. t34->fuel = 100; На сегодня всё. Следуюий выпуск опять будет не про C++. Постараюсь всё-таки вернуться на запланированную частоту выхода рассылки (один раз в неделю). Пока не получается. Сначала было очень много работы, сейчас уже надо писать выпуски по математике, она нам скоро пригодится. К тому же уже пора переносить старые выпуски на сайт. Ведущий - Роман. P.S.: Если вы читаете данный выпуск через месяц или более после выхода, пожалуйста, загляните на сайт рассылки, там вы найдёте исправленную и дополненную информацию из выпуска. | |||
| |||
| |||
| |||
Сообщить о нарушении данной рассылкой правил Сервиса | |||
Отписаться : Нажмите и отправьте это письмо |
среда, 15 апреля 2009 г.
Создаём компьютерную игру. Выпуск 11.
Подписаться на:
Комментарии к сообщению (Atom)
Комментариев нет:
Отправить комментарий