Начнём, как обычно, издалека.
Когда операционная система запускает программу которую вы написали, все данные относящиеся к программе помещаются в оперативную память. Как вы, наверное, помните (из урока про типы данных), память в компьютере состоит из байтов. Байты в памяти нумерются, т.е. у каждого байта есть порядковый номер - его адрес. С помощью адреса процессор может обратиться к любому байту в памяти. Адреса хранятся в шестнадцатиричном формате. Обычно мы используем десятичную систему. В компьютерах удобнее всего использовать двоичную (числа формируются всего из двух цифр: 0 и 1), или степень двойки (16 = 24). При этом в шестнадцатиричной системе 16 цифр: 0, 1, ... 9, A, B, C, D, E, F (где цифры от a до f соответствуют десятичным 10-15). Адрес выглядит примерно так: 0x0012fc2c, что равно числу 1244204 в десятичной системе счисления. 0x (ноль икс) перед адресом говорит, что число шестнадцатиричное.
Ну так вот, у каждого байта есть свой адрес. Когда ваша программа попадает в оперативную память, всем данным присваиваются адреса, чтобы процессор мог к ним обращаться. Каким данным присваиваются адреса: переменным, функциям, структурным переменным, объектам классов.
При этом данные хранятся в двух разных участках памяти: стеке и куче:
PHP код:
int a = 0;
int main()
{
int b = 0;
return 0;
}
a хранится в куче (heap), b хранится в стеке (stack). Все глобальные переменные попадают в кучу, все локальные переменные попадают в стек. что такое стек, мы рассмотрели в прошлом уроке. Куча же - это область памяти где данные никак не организованы.
Теперь, рассмотрим простое объявление переменной:
a - идентификатор с помощью которого мы можем обращаться к какой-то области памяти. При этом данная переменная занимает четыре байта. Обращение к переменной происходит по первому байту.
При создании переменной a, ей присваивается адрес 0x0012fc28 (адрес взят для примера). Процессор обращается к этой переменной с помощью этого адреса. Сама же переменная занимает в памяти следующие байты: 0x0012fc28, 0x0012fc29, 0x0012fc2a, 0x0012fc2b (обратите внимание на последние цифры в адресах). Где первый байт - адрес переменной.
Теперь мы плавно переходим к рассмотрению указателей.
Следует заметить, что указатели есть не во всех языках программирования.
Указатель (pointer) - это переменная, значением которой является адрес.
Чтобы получить адрес обычной переменной можно воспользоваться следующим синтаксисом:
На экран будет выведен адрес переменной a.
Указатель можно создать следующим образом:
PHP код:
int a = 0;
int* ptr;
ptr = &a;
cout << ptr;
Здесь & - операция получения адреса. Не путайте использование & для получения адреса и для передачи в функцию значения по ссылке.
В данном примере мы создали указатель ptr на тип int и присвоили этому указателю адрес, где хранится переменная a.
Когда вы создаёте указатель, всегда инициализируйте его каким-нибудь значением. По умолчанию в указателе может храниться любой адрес, и если вы попытаетесь изменить значение в этом адресе, то вполне вероятно возникновение ошибки при выполнении программы.
Через указатель можно изменять значение хранящееся по адресу. Для этого указатель нужно разыменовать:
PHP код:
int a = 0;
int* ptr = &a;
*ptr = 10; // то же самое что и: a = 10;
cout << a; вывод на экран: 10
Здесь мы воспользовались указателем, чтобы изменить значение переменной a. В данном случае * - операция разыменования. Не путайте с объявлением указателя!
При разыменовании указателя мы получаем доступ к значению адреса в памяти, на который указывает указатель.
Указатель на тип void
До сих пор мы использовли ключевое слово void (void - недействительный, пустой) в заголовках функций, когда нам не нужно было возвращать никаких значений.
Указатели на разные типы не могут использоваться друг с другом:
PHP код:
int a = 0;
float b = 1;
int* ptr_a = &a;
float* ptr_b = &b;
// ptr_a = ptr_b; // Так нельзя!!!
Чтобы обойти это ограничение можно воспользоваться указателем на void:
PHP код:
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 = ptr_a; // Оба варианта
ptr_c = ptr_b; // корректны
Указатели на void особенно полезны при использовании с классами. В DirectX очень многие объекты создаются как указатели на void.
Указатели-константы
Мы уже не раз встречались с указателями-константами. Это такие указатели, значение которых не может быть изменено. Т.е. не могут быть изменены адреса. Значения же хранящиеся в адресах могут изменяться.
Более известное имя указателей-констант - массивы.
Хотя надо заметить, что данный вид указателей используется не только с массивами:
PHP код:
int a = 1;
int b = 5;
int* ptr_a = &a;
int* const ptr_b = &b;
*ptr_b = 2;
// ptr_b = ptr_a; // так не получится. ptr_b - константа
*ptr_b = 3;
Передача аргумента в функцию по указателю
Мы уже умеем передавать аргументы в функции двумя способами: по значению и по ссылке. Третий способ - передача по указателю (pass-by-pointer).
PHP код:
int main()
{
int a = 10;
pass_by_pointer(&a); // Передача адреса переменной
cout << a; // 5
return 0;
}
void pass_by_pointer (void* ptr)
{
*ptr = 5;
}
При передаче по указателю мы передаём адрес. Внутри функции параметр ptr может непосредственно влиять на содержимое внешней переменной a.
Операция new
В программе pseudo_game мы использовали заданный массив. Мы не могли сделать что-нибудь подобное:
PHP код:
cin >> s; // количество строк
cin >> c; // количество столбцов
char map[s][c];
Компилятор должен знать заранее (до начала выполнения программы) сколько памяти выделить на массив. То же самое и с классами: все объекты классов должны быть созданы до начала выполнения программы. Вы не можете динамически создать объект.
Операция new позволяет обойти это ограничение. (В примере используется класс tank из программы pseudo_game):
PHP код:
/*
Здесь представлены сразу два примера:
создание указателя на tank
и
выделение памяти для хранения всех клеток игрового поля
*/
tank* t34 = new tank; // выделение памяти под объект tank
// Здесь t34 - указатель на тип tank
int s,c; // количество строк и столбцов
cin >> s;
cin >> c;
char** map; // указатель
map = new char*[s];// указателю присваем адресс массива указателей char*
for(int i=0;i<s;i++) map[i]=new char[c];
Здесь t34 и map - указатели.
В данном примере мы вынуждены использовать одномерный массив для представления двумерного. Как в данном случае получить доступ к произовльному элементу? Вот как выглядит инициализация клетки (5,6):
Думаю, данный пример нуждается в пояснениях. Мы создаем указатель на массив указателей типа char.
Потом мы должны каждому указателю присвоить массив char. Это очень тяжело на первый взгляд. Попробуй-те запустить этот пример и затем отследить его отладчиком
Операция delete
Операция delete используется для освобождения памяти после того как она стала не нужна, дабы не допустить утечки памяти:
PHP код:
for(int i=0;i<s;i++) delete [] map[i];
delete [] map;
delete t34;
Доступ к объектам
Последнее на что хочу обратить внимание. Доступ к членам класса объекта созданного с помощью указателей отличается от того, что мы видели раньше.
t34->fuel = 100;
То есть вместо точки используются символы ->.
Прошу заметить, что динамический двумерный массив я написал сам, а не автор урока.
Причина - сильно устарелый метод. Для того чтобы вам лучше понять указатели, я тоже написал на указателях.
Есть более современный и простой метод для написания динамических массивов, но он требует библиотеку std и будет работать лишь в С++.
PHP код:
#include <vector>
using namespace std;
int main()
{
vector<int> x; //vector - объект. <int> - тип данных хранимых в этом объекте. x - название объекта
x.push_back(5); // занести в массив x пятерку. Теперь x[0] = 5.
vector<int> array(4,100); // массив с четырьмя элементами равных 100.
array.resize(10); //расширить массив до 10 ячеек. Теперь массив выглядит так 100,100,100,100,0
}
На сегодня всё.