[МУЗЫКА] Для переменных, описанных в секции Var, память выделяется до начала работы программы. Все переменные, с которыми мы с вами работали до сих пор, являлись статическими. И когда программа выделяет память под статические переменные, то размер выделенной области памяти не может изменяться в процессе выполнения программы. И кроме того, если память выделена для хранения каких-то данных, то нельзя ту же самую память до конца работы программы использовать для хранения других данных. Итак, какие переменные и связанные с ними типы данных называются статическими? И до сих пор мы с вами работали только со статическими переменными. Но в Паскале можно использовать также динамические переменные. В чем основное отличие между статическими переменными и динамическими? Память для динамических переменных выделяется в процессе выполнения программы и может освобождаться до окончания ее работы. То есть для динамической переменной мы с вами можем как выделить память в процессе работы программы, так и забрать ее обратно, вернув в число свободной памяти. Для обращения к статической переменной мы всегда используем имя этой переменной, ну или если это массив, то еще номер. И к каждому из таких имен, после того, как программа откомпилирована, ставится в соответствии некоторый адрес. Что такое адрес, каждый из вас знает хорошо. У каждого из вас есть, например, домашний адрес. Это улица, это дом, это номер квартиры. И для переменной так же имеет место понятие адреса, только адресом переменной является число. Это шестнадцатеричное число, то есть записанное в шестнадцатеричной системе счисления, и это число показывает, начиная с какого именно байта будут храниться соответствующие данные. Для динамической переменной, для того чтобы обратиться к участку динамической памяти, мы используем переменную указатель, и переменная указатель содержит адрес этой области, то есть номер того места в памяти, начиная с которого будут храниться соответствующие данные. И указатель получит значение адреса только после того, как будет выделена соответствующая область памяти для той переменной, с которой он будет связан. Переменная, которая размещается динамически, не объявляется в разделе Var, но определяется своим указателем. Итак, указатель в языке Паскаль хранит адрес памяти. Впрочем, и в остальных языках, например, в языке Си, это точно так же — значением переменной типа указатель всегда является адрес. В Pascal ABC имеются типизированные указатели, то есть там содержится адрес ячейки памяти определенного типа, а также бестиповой указатель, там содержится адрес памяти, который не связан с данными определенного типа. Для бестипового указателя мы можем произвести настройку на данные любого типа, а для типизированного — только на данные того типа, с которым связан этот указатель. Если мы хотим задать указатель на тип T, то мы используем форму, я буду называть этот символ крышка. На самом деле в некоторых языках это показатель степени, но в языке Pascal ABC это не так. Итак, указатель на T. Например, если мы хотим задать указатель на тип integer, то мы говорим: type pinteger. И дальше вот эта вот крышечка, указатель на integer. Если мы хотим задать бестиповой указатель, то для него есть специальное ключевое слово pointer, pointer по-английски — это указатель. И для доступа к ячейке памяти, адрес которой хранится в указателе, мы используем операцию разыменование, которая как раз обозначается вот этим символом крышка. Например, пусть у нас есть переменная i целого типа. При этом эта переменная размещается в некоторой области памяти, у нее есть имя и некоторое количество памяти, которое отведено под хранение этих данных. И кроме того, у этой переменной есть адрес. Мы с вами видим на рисунке, что над изображением значения переменной теперь еще появился адрес — 0x02. В наших примерах я буду использовать некие абстрактные адреса, но если вы захотите увидеть значение указателя, то вы можете под отладчиком в языке Паскаль посмотреть соответствующее значение, оно там будет видно. Итак, у нас есть переменная типа integer, и у нас есть указатель pi, который является указателем на тип integer. Теперь, когда мы с вами объявили такую переменную, обращаю ваше внимание, что она тоже статическая, появилась область памяти pi, у которой так же есть свой номер, то есть адрес, он изображен рядом с именем переменной. И теперь мы с вами можем записать операцию pi, присвоить адрес переменной i. А коммерческая как раз признак того, что мы берем адрес некоторой переменной. И теперь мы с вами имеем в памяти компьютера следующую картину. У нас значением переменной pi является адрес 0x02, который является номером первой ячейки той области памяти, в которой у нас содержится переменная i. Говорят, что этот указатель связан с этой переменной. Теперь мы можем записать следующее выражение: pi операция разыменования присвоить 5. Что при этом происходит? Мы рассматриваем значение в той ячейке, на которую указывает указатель pi. И туда, в эту ячейку, мы записываем значение, равное 5. Таким образом, теперь мы можем обратиться к значению переменной i, либо, как обычно, i присвоить и некоторое значение соответствующего типа, либо мы можем использовать операцию разыменования. pi операция разыменования присвоить некоторое значение, в данном примере 5. Результат будет тот же самый. И операция разыменования, к сожалению, не может применяться к бестиповому указателю. Рассмотрим пример работы с бестиповым указателем. Типизированный указатель может быть неявно преобразован к бестиповому. И обратное преобразование может быть выполнено только явно. Например, пусть у нас есть два типа: указатель на вещественный тип preal и указатель на целый тип pint. И переменные p: pointer — это бестиповой указатель, переменная pr типа real и переменная r типа real, pr есть указатель, а r — просто обычная вещественная переменная. И также pi — указатель на целое, и i — просто обычная целая переменная. Произведем следующие действия: pr присвоим адрес r. Таким образом наш указатель настроен на ячейку, где находится переменная r. Дальше мы можем записать: pr разыменование присвоить 3,14. В результате в этой ячейке, там, где содержится переменная r, будет получено значение 3,14. Мы можем его вывести на экран. Написать, что pr операция разыменования равно, и дальше получить на экране значение, равное 3,14. Дальше мы можем p присвоить pr. p у нас бестиповой указатель, и теперь он будет указывать на тип real, поскольку мы присвоили ему значение указателя, которое указывает на этот тип. Дальше мы с вами можем взять указатель pi и настроить на адрес переменной i. Теперь мы с вами выполняем обратное преобразование: p мы присвоим pint (pi). До этого у нас p показывал на тип real, а теперь он будет указывать на тип integer. Теперь мы можем pi операция разыменования присвоить 13 и вывести на экран полученное значение. Это значение будет равно 13. Какие операции возможны над указателями? Указатели, во-первых, можно сравнивать на равенства и неравенства. Как обычные переменные. Кроме того, если у нас типизированный указатель, то два типизированных указателя можно сравнивать между собой, используя операции меньше, больше, меньше равно, больше равно. При этом будут сравниваться соответствующие значения, то есть адреса тех переменных, на которые указывает наш указатель. Для типизированных указателей еще доступны операции указатель присвоить, указатель плюс i или минус i, а также разность указателей, при этом i должно быть целым числом. То есть мы с вами можем передвинуть указатель на определенное количество ячеек вперед либо назад, а также вычислить разность двух указателей одинакового типа. И для того чтобы показать, что указатель никуда не указывает, есть стандартная константа nil, так называемый нулевой указатель. Если мы присвоим указателю значению этой константы, то она становится определенной, и ее значение равно пустому указателю. Это признак того, что она никуда не указывает. Например, p присвоить nil, и указатель p получит значение пустого указателя. Теперь рассмотрим, какие стандартные процедуры для работы с памятью есть в языке Паскаль. Все процедуры сведены в таблицу, в этой таблице p является типизированным указателем, в первом столбце показаны имя и параметры процедуры, затем какое действие она выполняет, а в последнем столбце будет пример. Итак, New (p). Обращаю ваше внимание, что в наших предыдущих примерах мы с вами не выделяли память для указателя, а просто настраивали указатель на уже имеющуюся область памяти. Так вот для того, чтобы выделить новую память, связанную с указателем, используется стандартная процедура new. New по-английски — это новый, и при этом выделяется динамическая память. Размер памяти будет равен размеру типа, с которым связан этот указатель, и этот указатель настраивается на эту переменную. Ну, например, New (pr). Здесь pr должен быть типизированным указателем. Если мы можем выделить память, то у нас должна быть возможность и очистить ее. Вернуть ее в область свободной памяти. Для этого существует стандартная процедура dispose — dispose по-английски уничтожить. Здесь освобождается динамическая память, которая ранее была выделена при помощи указателя p. То есть сначала мы должны выполнить процедуру New, а затем мы можем при помощи процедуры dispose память освободить. Например, dispose (pr). Итак, у нас с вами довольно небогатый набор процедур для работы с памятью. Но при помощи этих процедур мы с вами научимся решать некоторые уже известные нам задачи. И при помощи диномических переменных некоторые задачи решаются гораздо проще, чем без них. [МУЗЫКА] [МУЗЫКА] [МУЗЫКА]