Сім чудес і два фокуси на Дельфі

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

Максим Кузьмінський

Чи вірите Ви в дива чи ні, Ви, напевно, погодитеся зі мною, що іноді щось таке трапляється з кодом наших програм, і вони раптом перестають компілюватися або, що ще підступніше, починають видавати абсолютно непередбачуваний результат. І ось тоді, зізнайтеся, вас починають відвідувати дивні думки про участь у всіх цих чудеса якихось потойбічних сил.

У цій статті ми спробуємо зірвати таємничий покрив з декількох, найпростіших "чудес" і переконаємося, що все це - лише обман, ілюзія, а часто - майстерне шахрайство.

Ми розглянемо сім (з багатьох) таких чудес і спробуємо розгадати їхні секрети. Зрозумівши механізм їх походження, ми, в ув'язненні, покажемо два приклади використання цих таємних сил у "мирних цілях". Наша мета - краще дізнатися Delphi і в майбутньому уникнути деяких що важко помилок.

Для того, що б ви зрозуміли, що я маю на увазі, давайте розглянемо один дуже простий приклад.

Чудо Перше (Round Miracle).

Відкрийте Delphi, створіть новий проект, назвіть його AllMiracles, покладіть кнопку на головну форму і напишіть в обробнику події OnClick наступний код:

procedure TfrmAllMiracles.btnRoundMrclClick (Sender: TObject);

begin

ShowMessage (IntToStr (Round (3.5) - Round (2.5)));

end;

Figure 1.

А тепер зупиніться і скажіть, який результат ви очікуєте побачити. Я сподіваюся ви не сказали "1", адже інакше це не було б диво. Ті, у кого добре розвинена інтуїція, можуть сказати "0", і це буде ще далі від правильної відповіді. І лише ті, хто часто грає в Спортлото або, на худий кінець, уважно читає документацію, відповість "2" і це буде правильно. Не вірите? - Тисніть F9.

Читаємо Help по функції Round:

Round returns an Int64 value that is the value of X rounded to the nearest whole number. If X is exactly halfway between two whole numbers, the result is always the even number.

Ось таке воно, "Кругле диво".

Сподіваюся, тепер ви зрозуміли, про що ми будемо говорити сьогодні. У цій статті немає складних, хитромудрих прикладів. Код - гранично спрощений що б виділити саму суть проблеми. А наше з вами справа - розібратися в ній і, якщо можна, виправити ситуацію. Як, наприклад, в наступному випадку.

Чудо Друге (Absolute Miracle).

Покладіть на головну форму створеного раніше проекту нову кнопку і напишіть в його обробнику події OnClick такий код:

procedure TfrmAllMiracles.btnAbsMrclClick (Sender: TObject);

var

i1: int64;

begin

i1: = abs (low (integer));

ShowMessage (IntToStr (i1));

end;

Figure 2.

Перш ніж натиснути F9, проаналізуємо написане. Low від integer - значення відоме всім, записане навіть в Help'е і рівне -2147483648, тобто число негативне.

Help не говорить про функції Abs нічого нового:

Abs returns the absolute value of the argument X. X is an integer-type or real-type exdivssion.

Змінна i1 описана як int64, і це правильно, тому що 2147483648 - вже виходить за межі типу integer. Це значення (2147483648) ми й очікуємо побачити на екрані, чи не так? А ось і ні. Перевірте. На екрані знову - 2147483648. Як абсолютне значення може бути негативним?

Давайте ще раз, уважніше розглянемо вираз abs (low (integer)). Що можна ще сказати про нього? Не дивлячись на наявність в ньому функцій, це - константа

Читаємо Help за темою "Constant exdivssions":

... Constant exdivssions cannot include variables, pointers, or function calls, except calls to the following divdefined functions: Abs ... Low ...

спробуємо описати константу зі значенням рівним цьому висловом:

...

const

ci = abs (low (integer));

...

Figure 3.

Код компілюється. Значить ми - праві, а це означає, що результат виразу визначається ще на стадії компіляції. Далі, low (integer)) має цілий тип. Abs від integer - теж ціле, а нам потрібно int64. Поробуем переписати код наступним чином:

procedure TfrmAllMiracles.btnAbsMrclClick (Sender: TObject);

const

ci = abs (low (integer));

var

i1: int64;

begin

/ / I1: = abs ((low (integer)));

i1: = abs (int64 (low (integer)));

ShowMessage (IntToStr (i1));

end;

Figure 4.

Тепер - запрацювало. Секрет "Абсолютного дива" розкритий! До речі, abs (int64 (low (integer))) - теж константа.

Наступне диво - приклад того, як цілком правильний код відмовляється компілюватися.

Диво третє (One more low integer miracle).

Нова кнопка на формі буде реагувати на натискання наступним чином:

procedure TfrmAllMiracles.btnLowIntMrclClick (Sender: TObject);

var

lowInt: integer;

begin

lowInt: = -2147483648;

ShowMessageFmt ('% d', [lowInt]);

end;

Figure 4.

Абсолютно звичайна процедура. У нас виникло бажання привласнити деякої змінної цілком законне значення. Але цей код не компілюється:

Overflow in conversion or arithmetic operation

Тиснемо F1 на повідомленні про помилку і читаємо:

The compiler has detected an overflow in an arithmetic exdivssion: the result of the exdivssion is too large to be redivsented in 32 bits.

Мабуть компілятор намагається визначити константу цілого типу зі значенням 2147483648, а тільки потім змінити її знак, але це йому не вдається. Перепишемо код:

procedure TfrmAllMiracles.btnLowIntMrclClick (Sender: TObject);

var

lowInt: integer;

begin

lowInt: =-int64 (2147483648);

/ / LowInt: = -2147483648;

ShowMessageFmt ('% d', [lowInt]);

end;

Figure 5.

От тепер - все нормально. Приклад дуже простий, але дає нам уявлення про те, як компілятор Delphi обробляє константи і визначає їх тип.

А от наступне диво - приклад того, до якої плутанини може призвести перевантаження функцій. Такі чудеса ми найчастіше самі влаштовуємо собі через неуважність, а потім годинами шукаємо помилки.

Чудо четверте (String Trick).

Ну, що ж, додамо знову кнопку на нашу форму і задамо наступний код для події OnClick:

procedure TfrmAllMiracles.btnCopyMrclClick (Sender: TObject);

const

cs: array [0 .. 1] of char = "01";

begin

ShowMessage (copy (cs, 0,1) + copy (cs, 1,1));

end;

Figure 6.

Я знаю, що ви вже чекаєте каверзи і все ж таки результат може виявитися несподіваним: "00".

Як завжди звернемося до Help'у, дивимося функцію Copy:

Returns a substring of a string or a segment of a dynamic array.

...

function Copy (S; Index, Count: Integer): string;

function Copy (S; Index, Count: Integer): array;

...

Справа в тому, що у виразі copy (cs, 0,1) + copy (cs, 1,1) обидва рази викликаються різні версії функції copy, перший раз - для динамічних масивів, які нумеруються з 0, а другий раз - для рядків , перший елемент яких має індекс 1. Обидва рази cs перетвориться до необхідного типу, і те, що cs, як масив починається з нульового елемента, в даному випадку не має ніякого значення.

А тепер, нарешті, ми добралися і до об'єктів. Безліч Дельфійських чудес пов'язані з тим, що об'єкти в Delphi - автоматично разименуемие посилання, які можуть вказувати на звільнену або зайняту кимось іншим область пам'яті. Про такі випадки написано чимало. Наше диво - інше.

Чудо п'яте (Is-Miracle).

Опишіть в розділі protected нашої форми полі FControl типу TСontrol і задайте для ще однієї - нової кнопки таку ось реакцію на її натискання:

procedure TfrmAllMiracles.btnIsMrclClick (Sender: TObject);

begin

if (FControl is TControl) then

begin

if not Assigned (FControl) then

FControl: = TControl.Create (Self);

end

else

ShowMessage ('Not a Control');

end;

Figure 7.

Таке "Диво" я бачив кілька разів і в різних проявах. Скільки разів би ви не натискали на кнопку btnIsMrcl, ви щораз будете бачити повідомлення 'Not a Control', а конструктор TControl так ніколи і не буде викликаний.

Ось, що говорить Help:

... The exdivssion object is class returns True if object is an instance of the class denoted by class or one of its descendants, and False otherwise. (If object is nil, the result is False.)

Справа в тому, що оператор is використовує посилання на клас об'єкта, а не те, як описана змінна, яка по суті - простий покажчик. Так що TControl не завжди TControl.

Так, я сподіваюся ви розумієте, що TControl тут обраний випадково, з таким же успіхом це міг бути і будь-який інший клас.

Випадок коли FControl посилається на вже звільнений об'єкт або є локальною і непроініціалізірованной змінної, дає непредказуемие результати і може призвести до зовсім не чудесному краху аплікації.

А от для наступного дива я знайшов тільки непряме пояснення в Help'е і тому ми будемо змушені провести невеликий експеримент.

Чудо шосте (Is-Miracle II)

Давайте подивимося ще на одне, схоже диво пов'язане з оператором is. Додамо до нашої групи проектів (ProjectGroup1) новий проект - DLL з ім'ям AllMirrLib, в єдиному модулі якого буде наступний код:

library AllMirrLib;

uses

Controls;

function IsControlLib (const anObj: TObject): boolean;

begin

Result: = anObj is TControl;

end;

exports

IsControlLib;

Figure 9.

Як ви бачите ця бібліотека експортує лише одну дуже просту функцію, яка повертає знеченіе True в тому випадку, якщо її єдиний параметр походить від TControl і False - в інших випадках.

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

unit AllMir;

interface

...

implementation

{$ R *. DFM}

function IsControlLib (const anObj: TObject): boolean; external 'AllMirrLib.DLL';

Figure 10.

Тепер, як зазвичай, додамо на форму нову кнопку:

procedure TfrmAllMiracles.btnIsMrcl2Click (Sender: TObject);

begin

FControl: = TControl.Create (nil);

try

if not IsControlLib (FControl) then

ShowMessage ('Not a Control');

finally

FreeAndNil (FControl);

end;

end;

Figure 11.

Як ви вже напевно здогадалися FControl знову виявиться не TControl. Знайдіть в модулі System процедуру _IsClass. Хоч вона й написана на асемблері, неважко зрозуміти, що в ній відбувається - в циклі проглядаються посилання на класи (спочатку власна - об'єкта, а потім - усіх предків) і серед них шукається рівна правий операнд. Давайте змінимо трохи процедуру:

procedure TfrmAllMiracles.btnIsMrcl2Click (Sender: TObject);

var

p1, p2: pointer;

begin

FControl: = TControl.Create (nil);

try

p1: = pointer (FControl.ClassType);

p2: = pointer (TControl);

if not IsControlLib (FControl) then

ShowMessage ('Not a Control');

finally

FreeAndNil (FControl);

end;

end;

Figure 12.

Подивіться під відладчиком значення p1 і p2 - вони рівні. Тепер змінимо і функцію IsControlLib:

function IsControlLib (const anObj: TObject): boolean;

var

p3, p4: pointer;

begin

p3: = pointer (anObj.ClassType);

p4: = pointer (TControl);

Result: = anObj is TControl;

end;

Figure 13.

Тут теж поставимо крапку зупинки і порівняємо значення. Змінні p1, p2 і p3 мають одне й теж значення, а ось p4 - вказує кудись ні туди. Проблема в тому, що в аплікації і в DLL співіснують два різні класи TControl, ось тому равества бути і не може.

Непряме вказівка ​​на цю проблему в Help'е можна знайти в описі методу ClassNameIs.

Читаємо Help:

Use ClassNameIs when writing conditional code based on an object's type or to query objects across modules, or DLLs.

Так, до речі, не забудьте, що у вас два проекти в групі і компілюється завжди тільки активний проект. Так що не забувайте перпеключаться на потрібний проект в міру необхідності або компілюйте відразу все: Alt-P, U.

Наступне диво я зустрів в програмі одного початківця програміста і воно було звичайно злегка закамуфльоване, так що я, на свій сором, навіть не відразу зрозумів у чому справа. Я бачив значення змінних, знав, що це - змінні типу variant, але ніяк не міг зрозуміти чому результат обчислення якогось нескладного вираження весь час помилковий. Перевірте себе і ви.

Диво сьоме (Miracle with Variants).

Як ви вже здогадалися, почнемо з нової кнопки, яка виконує такі дії при натисканні:

procedure TfrmAllMiracles.btnVarMrclClick (Sender: TObject);

var

X, Y, Z: variant;

begin

X: = '1 ';

Y: = '2 ';

Z: = 3;

ShowMessage (X + Y + Z);

end;

Figure 14.

Чи можете ви передбачити результат виразу '1 '+ '2' +3? Якщо ви сказали '6 ', то ви теж попалися. Подивимося уважніше, '1 '+ '2' буде ... звичайно '12 ', 12 +3 = 15. Це і є правильна відповідь.

Отже, ми побачили сім чудес Delphi, сім - з багатьох. Це не означає, що вони - найяскравіші або самі чудесні. Але на них можна багато чому навчитися. Візьмемо останнє, тільки що розглянуте нами, диво. Задумайтеся, як Delphi вдається зводити в одному вираженні значення різних типів? А якщо один з членів вираження - variant?

Фокус перший (Variant trick)

Читаємо Help в розділі "Variants in exdivssions":

... In a binary operation, if only one operand is a variant, the other is converted to a variant ..

Чи не здається вам це дивним - variant можна складати з чим завгодно. Наприклад, integer плюс variant - буде variant, а variant можна знову складати з чим завгодно ...

Нова кнопка на формі буде виконувати наступні дії:

procedure TfrmAllMiracles.btnVarTrickClick (Sender: TObject);

var

v: variant;

b: boolean;

i: integer;

s: string;

d: TDatetime;

x: Double;

begin

v: = 0;

b: = true;

i: = 2;

s: = '3 ';

d: = StrToDateTime ("01 / 01/01 ');

x: = 5;

v: = v + b + i + s + d + x;

ShowMessage (VarToStr (v));

end;

Figure 15.

Чи не здається вам, що диво вже те, що цей код компілюється, але ж він ще й видає якийсь результат. Адже все дуже просто - "variant можна складати з чим завгодно" і знову отримаємо - variant.

Одного разу до мене звернувся один мій знайомий з питанням чи немає в Delphi чогось подібного прихованого параметру Self, але для оператора with. Ні - відповів я йому спершу, а потім замислився ...

Фокус другий (With-trick)

Припустимо у нас є наступна функція:

procedure ShowText (sl: TStringList);

begin

ShowMessage (sl.text);

end;

Figure 16.

І кнопка на формі:

procedure TfrmAllMiracles.btnWithSelfTrickClick (Sender: TObject);

var

sl: TStringList;

begin

sl: = TStringList.Create;

try

sl.CommaText: = '1, 2,3,4,5,6,7,8,9,0 ';

ShowText (sl);

finally

sl.Free;

end;

end;

Figure 17.

І ми, з якихось причин, хочемо позбутися від локальної змінної sl. Але для того, що б звернутися до функції ShowText, ми повинні передати їй параметр типу TStringList. Звідки ж його взяти?

Давайте поміркуємо. Кожен метод отримує прихований параметр Self, може бути як-то можна витягнути його звідти? Писати для цього спеціальний метод якогось класу не хотілося б пак - адже це працювало б тільки для його нащадків.

Давайте почитаємо Help, розділ "TMethod type":

... This type can be used in a type cast of a method pointer to access the code and data parts of the method pointer ...

Чи не це те, що ми шукаємо?

Визначимо тип і функцію:

type

TSimpleMethod = procedure of object;

function GetWithSelf (const pr: TSimpleMethod): TObject;

begin

Result: = TMethod (pr). Data;

end;

Figure 18.

Як бачите, функція приймає покажчик на метод, а повертає об'єкт, що є власником цього методу. Але яким же методом ми скористаємося? Наприклад, метод Free, адже його історія сягає ще до самого TObject'у. Тепер перевіримо себе:

procedure TfrmAllMiracles.btnWithSelfTrickClick (Sender: TObject);

begin

with TStringList.Create do

try

CommaText: = '1, 2,3,4,5,6,7,8,9,0 ';

ShowText (TStringList (GetWithSelf (Free)));

finally

Free;

end;

end;

Figure 19.

Перевірте - працює.


Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Реферат
25.2кб. | скачати


Схожі роботи:
Сім чудес світу
Сім чудес Вінницької області
Сім нових чудес світу
Сім чудес світу як пам`ятники античної культури
Хрестові походи та лицарство Великі князі Сім чудес світу
Сім чудес світу - стародавній світ середні століття і наш час історія цивілізації реферат
Шиллер ф. - Два брата два характери дві долі
Метод Дельфі
Два брати два характери дві долі
© Усі права захищені
написати до нас