Оператор return
Оператор return завершает выполнение функции, в которой он задан, и возвращает управление в вызывающую функцию, в точку, непосредственно следующую за вызовом. Функция main передает управление операционной системе. Формат оператора:
return [выражение] ;
Значение выражения, если оно задано, возвращается в вызывающую функцию в качестве значения вызываемой функции. Если выражение опущено, то возвращаемое значение не определено. Выражение может быть заключено в круглые скобки, хотя их наличие не обязательно.
Если в какой-либо функции отсутствует оператор return, то передача управления в вызывающую функцию происходит после выполнения последнего оператора вызываемой функции. При этом возвращаемое значение не определено. Если функция не должна иметь возвращаемого значения, то ее нужно объявлять с типом void.
Таким образом, использование оператора return необходимо либо для немедленного выхода из функции, либо для передачи возвращаемого значения.
Пример:
int sum (int a, int b)
{ renurn (a+b); }
Функция sum имеет два формальных параметра a и b типа int, и возвращает значение типа int, о чем говорит описатель, стоящий перед именем функции. Возвращаемое оператором return значение равно сумме фактических параметров.
Пример:
void prov (int a, double b)
{ double c;
if (a<3) return;
else if (b>10) return;
else { c=a+b;
if ((2*c-b)==11) return;
}
}
В этом примере оператор return используется для выхода из функции в случае выполнения одного из проверяемых условий.
Указатели также являются переменными. Но они содержат не символы или числа, а адреса памяти. Если вы рассматриваете память как огромный массив чисел, то указатель будет одним элементом массива, в котором хранится индекс другого элемента массива.
Например, пусть у нас есть следующие объявления и инициализация:
1 2 3 4 5 6 7 8 | var I: Integer; J: Integer; C: Char; begin I := 4222; J := 1357; C := 'A'; |
Предположим, что это дало нам такую компоновку памяти:
Теперь, после выполнения этого кода (предполагая что P - это указатель):
1 | P := @I; |
Мы получаем:
В предыдущих диаграммах я всегда показывал все байты. Это обычно не является необходимым, так что мы можем показать эту же ситуацию как:
Эта диаграмма более не показывает настоящих размеров (C выглядит так же, как и I или J), но этого достаточно, чтобы продемонстрировать, что происходит с указателями.
nil
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end. — Henry Spencer
nil - это специальное значение указателя. Оно может быть присвоено любому указателю. nil означает пустой указатель (nil - это сокращение от Лат. nihil, что означает ничего или ноль; кое-кто расшифровывает NIL как аббревиатуру от Not In List - не в списке). Это означает, что указатель был определён (инициализирован, присвоен), но вы не должны пытаться получить значение, на которое он указывает (в языке C, nil называется NULL — см. цитату в начале секции).
Nil никогда не указывает на допустимую память, но т.к. это вполне конкретное значение, то подпрограммы могут сравнивать указатели с этим значением (например, используя функцию Assigned()). Нельзя проверить, является ли любое другое (не-nil) значение указателя допустимым. Мусорный или неинициализированный указатель ничем не отличается от допустимого указателя (см. ниже). Не существует способа их отличать друг от друга. Программная логика всегда должна гарантировать, что указатель либо допустим, либо равен nil (**).
В Delphi, nil имеет значение 0 (прим. пер.: т.е. nil = Pointer(0) или 0 = Integer(nil)) - т.е. он указывает на самый первый байт в памяти. Очевидно, что этот байт никогда не будет доступен для Delphi кода. Но обычно вы не должны рассчитывать на то, что указатель nil будет равен 0, если только вы точно не знаете, что вы делаете. Числовое значение nil может быть изменено в следующих версиях Delphi по какой-то причине (***).
Типизированные указатели
В простом примере выше P имеет тип Pointer. Это означает, что P содержит адрес, но вы не знаете, переменная какого типа лежит по этому адресу. Вот почему обычно используются типизированные указатели, т.е. указатель интерпретируется как указывающий на переменную (область памяти) определённого типа.
Предположим, что у нас есть ещё один указатель, Q:
1 2 | var Q: ^Integer; |
Q типа ^Integer, что читается как "указатель на Integer" (мне сказали, что ^Integer расшифровывается как ↑Integer). Это означает, что Q - это не Integer, но вместо этого указывает на память, которая может быть использована как Integer. Если вы присвоите адрес J в Q, используя оператор взятия адреса @ или эквивалентную функциональность псевдофункции Addr,
1 | Q := @J; // Q := Addr(J); |
то тогда Q будет указывать на место по адресу $00012348 (Q ссылается (references) на место памяти, занимаемое J). Но поскольку Q является типизированным указателем, то компилятор будет трактовать память, на которую указывает Q, как число типа Integer. Integer является базовым типом Q.
Хотя вы навряд ли увидите псевдофункцию Addr в реальном коде, она полностью эквивалентна @. Однако у @есть недостаток: если его применять к сложному выражению, то не всегда ясно, указатель чего берётся.Addr же, используя синтаксис функции, получается намного более читабельным, поскольку целевое выражение заключается в скобки:
1 2 | P := @PMyRec^.Integers^[6]; Q := Addr(PMyRec^.Integers^[6]); |
Прим. пер.: поэтому неплохо использовать скобки вместе с @, хотя это и не обязательно:
1 | P := @(PMyRec^.Integers^[6]); |
Присваивание с использованием указателей происходит немного иначе, чем при прямом присвоении переменной. Обычно для работы у вас будет только указатель. Если вы присваиваете значение обычной переменной, вы пишите что-то вроде:
1 | J := 98765; |
Это записывает число 98765 ($000181CD) в место памяти, занимаемое переменной J. Но чтобы получить доступ к этой памяти через указатель Q, вы должны работать косвенно, используя оператор ^:
1 | Q^ := 98765; |
Это называется разыменованием указателя. Вы должны следовать по воображаемой "стрелочке" до места, на которое указывает Q (другими словами, до Integer по адресу $00012348) и сохранить там значение.
Для записей, синтаксис языка позволяет вам опускать оператор ^, если код не теряет при этом своего смысла. Но лично я всегда явно указываю оператор для улучшения читабельности.
Обычно полезно определять типы для используемых в программе указателей. Например, вы не можете использовать ^Integer при указании типа параметра подпрограммы, так что вам придётся объявить новый тип:
1 2 3 4 | type PInteger = ^Integer;
procedure Abracadabra(I: PInteger); |
Фактически, тип PInteger и некоторые другие часто используемые типы уже определены в библиотеке Delphi (модулиSystem, SysUtils и Types). Начинать имя типов указателей с заглавной буквы P и следующим за ней именем типа, на переменную которого указывает указатель, является традицией, рекомендованной к выполнению. Если базовый тип указателя начинается с заглавной T, то T обычно опускается. Например:
1 2 3 4 5 | type PByte = ^Byte; PDouble = ^Double; PRect = ^TRect; PPoint = ^TPoint; |
Анонимные переменные
(прим. пер.: не путать с захваченными переменными в анонимных методах)
В предыдущих примерах переменные объявлялись только там, где они были необходимы. Иногда вы не знаете, понадобится ли вам переменная или как много переменных. Используя указатели, вы можете создавать так называемые анонимные переменные. Вы можете попросить библиотеку языка выделить вам кусок памяти и вернуть указатель на него, используя псевдофункцию New():
1 2 3 4 | var PI: PInteger; begin New(PI); |
New() - это псевдофункция компилятора. Она резервирует память для базового типа PI и записывает адрес на эту память в указатель PI. У самой переменной здесь нет имени (т.е. она анонимная) - имя есть только у указателя на переменную. Получить доступ к такой переменной можно только используя указатель. Теперь вы можете присваивать ей значения, передавать её в подпрограммы, избавиться от неё, когда она станет вам не нужна, используя вызов Dispose(PI):
1 2 3 4 5 | PI^ := 12345; ListBox1.Add(IntToStr(PI^)); // куча кода Dispose(PI); end; |
Вместо использования New и Dispose вы можете спуститься на уровень пониже и использовать GetMem иFreeMem. Но подпрограммы New и Dispose имеют несколько преимуществ: они осведомлены о типе указателя (прим. пер.: поэтому автоматически определяют размер памяти), а также инициализируют и освобождают содержимое участка памяти, если это необходимо. Так что рекомендуется всегда использовать New и Dispose, вместо GetMem и FreeMem, там, где это возможно.
Всегда гарантируйте, что каждый вызов New() будет иметь пару в виде вызова Dispose() с тем же самым значением и типом указателя, в противном случае память может быть освобождена неверно или не до конца.
Сейчас может быть не очевидно, чем же это лучше, чем объявлять переменную явно, но бывают ситуации, когда это полезно, обычно, если вы не знаете, как много переменных вам понадобиться. Подумайте об узлах в связанном списке (см.ниже) или о TList-е. TList хранит указатели, и если вы хотите иметь список значений Double, то вы просто вызываете New()для каждого значения и храните его в TList:
1 2 3 4 5 6 7 8 9 | var P: PDouble; begin while HasValues(SomeThing) do begin New(P); P^ := ReadValue(SomeThing); MyList.Add(P); // и т.д... |
Конечно же, вам нужно будет потом вызывать Dispose() для каждого значения, когда список не будет больше нужен.
Используя анонимные переменные, легко показать, что типизированные указатели могут определять, как используется память. Два указателя разных типов, указывающие на одно и то же место в памяти, будут показывать разные значения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | program InterpretMem;
{$APPTYPE CONSOLE}
var PI: PInteger; PC: PAnsiChar; begin New(PI); PI^ := $006D654D; // Байты $4D $65 $6D $00 PC := PAnsiChar(PI); // Теперь оба указателя указывают на одно место в памяти Writeln(PI^); // Печатаем число. Writeln(PC^); // Печатаем один символ ($4D). Writeln(PC); // Печатаем строку в стиле C (байты $4D $65 $6D $00 интерпретируются как PChar) Dispose(PI); Readln; end. |
PI заполняет память значением $006D654D (7169357). На диаграмме (напомню, что все адреса были придуманы мной):
PC указывает на ту же самую память (поскольку базовые типы указателей несовместимы, вы не можете просто так присвоить один указатель другому — вам нужно использовать преобразование типов). Но PC является указателем наAnsiChar, так что если вы берёте PC^, то вы получаете AnsiChar - один символ с ASCII значением $4D или 'M'.
Вообще-то, PC - это особый случай, поскольку тип PAnsiChar, хотя он формально указывает на символ AnsiChar, трактуется специальным образом, немного иначе, чем остальные типы указателей. Я объясняю это в другой статье. PC, если он не разыменовывается, обычно трактуется как указатель на текст, заканчивающийся нулевым символом #0, поэтомуWriteln(PC) покажет текст, сформированный байтами $4D $65 $6D $00, т.е. 'Mem'.
Когда мне нужно подумать об указателях, и особенно о сложных ситуациях с ними, я обычно беру бумажку и ручку и рисую диаграммки типа тех, что вы видели выше. Я также даю переменным выдуманные адреса, если это нужно (не обязательно использовать все 32 бита, адреса типа 30000, 40000, 40004, 40008 и 50000 тожевполне подойдут).
57. Основы редактора QT (C++). Понятие оператора. Идентификаторы. Константы. Условные операторы. Логические и арифметические операторы.
58. Основы редактора QT (C++). Работа с текстом.
59. Оформление документации с перекрывающими друг друга объектами. Размещение на полосе текстового материала, его перекомпоновка, перевод связанных блоков в независимые.
Текст размещается в текстовых блоках; ему присущи: выравнивание (по центру, по краю и т.п.), перенос, цвет, шрифт, кегель (высота символа), отступы от границ блока, начертание (норм., курсив, жирн. и проч.), трекинг (межбуквенный интервал), интерлиньяж (межстрочный интервал) и проч. Связанные текстовые блоки – блоки имеющие один общий текст; изменение формы блока влечет за собой «перетекание» текста; выполняется инструментом «Связывание» и указывается последовательность сцепки блоков. Разрыв связи между двумя блоками приведет к тому, что текст останется в первом блоке, а второй окажется пустым. Основные элементы публикации - линии, текстовые и графические блоки. Компоновка перекрывающихся объектов может быть: обтекание (тексом изображения или линии), наложение (передний / задний план)+ в InDesign можно присвоить прозрачность объектам (от 0 до 100%).
- 11. Базы данных, определение, типы бд. Понятие домена, атрибута,
- 32. История создания эвм. Поколения и классы эвм. Структура
- 46. Общая характеристика операционной системы Linux. Основные
- 67. Понятие нормальной формы отношений. Условия нахождения бд
- 1.3D моделирование в компьютерной графике
- 2. Case-средства. Работа с case-средством Erwin. Логическая и
- 4. Алгоритм декомпозиции отношения с целью его нормализации.
- 7. Архивирование данных. Преимущества и недостатки современных
- 12. Баухауз и его вклад в развитие мирового дизайна.
- 13. Бизнес - цели и жизненный цикл изделия
- 15. Василий Кандинский – теория цвета. Психология цвета.
- 16. Векторная и растровая графика, основные понятия, области
- 18. Виды обеспечения систем компьютерной графики.
- 21. Единый формат векторной графики.
- Пример использования tadoConnection
- Пример использования параметров запроса
- Синхронизация данных клиента и сервера.
- Работа с транзакциями
- Пример работы с транзакциями
- Доступ к данным
- Пример работы с отложенными изменениями.
- Cals-идеология
- Cals-технологии
- Cals-системы
- Плюсы и минусы
- 28. Использование векторной и растровой графики в web.
- 30. Использование международных стандартов.
- 34. Конструктивизм в художественном дизайне.
- 35. Кривая Безье, ее построение и редактирование.
- 13.1. Определение класса
- 13.1.1. Данные-члены
- 13.1.2. Функции-члены
- 13.1.5. Объявление и определение класса
- 13.2. Объекты классов
- 13.4. Неявный указатель this
- 13.4.1. Когда использовать указатель this
- 38. Мастера модерна. А. Ванде Вельде, ч. Р. Макинтош.
- 43. Обеспечение информационной безопасности.
- 44. Области применения компьютерной графики.
- 48. Оптические устройства хранения информации. Виды и основные
- 50. Основные принципы cals.
- 52. Основные структурные элементы эвм (материнские платы,
- Основные характеристики шрифтов
- Художественный облик шрифтов
- 55. Основы композиции в промышленном дизайне. Категории
- Оператор break
- Оператор return
- 60. П. Беренс – первый промышленный дизайнер.
- 61. Параллельное и последовательное моделирование
- 62. Параметризация в компьютерной графике.
- 63. Первые теории дизайна Дж. Рескин, г. Земпер, ф. Рело.
- 65. Понятие ключа, первичного ключа, индекса.
- Первая нормальная форма (1nf)
- Вторая нормальная форма (2nf)
- Третья нормальная форма (3nf)
- Методы решения
- Процедура принятия решений
- 4. Отчеты - позволяют обобщать и распечатывать информацию. Создание базы данных
- Создание формы
- Вызовы sql в pl/sql-ном блоке
- Вызовы sql в pl/sql-ном блоке
- См. Вопрос 122 fat32. Чем она лучше fat16?
- Оттенки фиолетового
- Оттенки синего
- Оттенки зеленого
- Оттенки желтого
- Цветовая гармония
- Восприятие цвета
- Различие между цветами Различные источники света
- Различная ориентация
- Различия в восприятии размера