اینجا هستید : safarionline.ir / books / pumr / ch07

فصل ۷ − Record Types

➡ فهرست

  1. فصل ۷ − Record Types
  2. Fixed Records
  3. variant records
  4. with statement
    1. nested with statement

انواع Record یا Record type ها احتمالا منعطف‌ترین ساختارهای داده هستند. به صورت مفهومی یک نوع Record قالبی است برای یک ساختار که اجزای آن ممکن است ویژگی‌های کاملا متفاوتی داشته باشند. به عنوان مثال فرض کنید که می‌خواهیم اطلاعات مرتبط با یک فرد را ثبت کنیم. اطلاعات همیشگی عبارتند از:

  1. نام
  2. قد
  3. جنسیت
  4. تاریخ تولد
  5. تعداد عائله
  6. وضعیت تاهل

علاوه بر این موارد، اگر آن فرد متاهل است یا جدا شده است، تاریخ (آخرین) ازدواج ؛ اگر جدا شده است تاریخ (آخرین) جدایی و این که آیا این اولین جدایی است یا نه، و اگر کلا مجرد بوده است هیچ کدام از این اطلاعات اخیر لازم نیست.

تمام این «اطلاع» ها می‌تواند در «یک» رکورد بیان شوند و هر بخش از این اطلاعات می‌تواند به صورت جداگانه مورد دسترسی قرار گیرد.

Fixed Records

به صورت رسمی‌تر، یک رکورد ساختاری است که از تعداد ثابتی مولفه که فیلد field نامیده می‌شوند، ساخته شده است. برخلاف آرایه، مولفه‌های یک رکورد می‌تواند type های مختلفی داشته باشند و نمی‌توانند توسط یک expression ایندکس شوند. تعریف یک نوعِ رکورد برای هر مولفه یک type و یک field identifier مشخص می‌کند تا بیانگر آن فیلد باشند. scope این field identifier (نام فیلد) داخلی‌ترین رکوردی است که در آن تعریف شده است. دو عملیات صحیح برای تمام متغیرهایِ نوعِ رکورد عبارتند از انتساب و انتخاب مولفه‌ها.

به منظور اینکه type مولفه‌ی انتخاب شده، از روی متن برنامه و بدون اجرای آن مشخص باشد، record selector از تعداد ثابتی field identifier بجای ایندکس‌های قابل محاسبه تشکیل شده است.

⚪ سینتکس دیاگرام نوع Record به صورت زیر است:

⚪ سنتکس دیاگرام FieldList نیز از قرار زیر است:

⚪ و سینتکس دیاگرام FixedPart هم اینگونه است:

⚪ سینتکس دیاگرام RecordSection:

⚪ با مرور قوانین و سینتکس دیاگرام‌های بالا مثال ساده‌ای ارائه می‌کنیم. فرض کنید می‌خواهیم روی اعداد مختلط یا complex محاسباتی انجام دهیم. این اعداد به شکل a+ib هستند؛ a و b اعدادی از نوع Real و i ریشه‌ی دوم -1 یا $\sqrt{-1}$ است.

هیج نوع از پیش تعریف شده‌ای برای اعداد complex وجود ندارد. با وجود این می‌توانیم به سادگی یک نوعِ رکورد تعریف کنیم تا اعداد complex را نمایندگی کند. این رکورد به دو فیلد از نوع Real نیاز دارد، یکی برای قسمت حقیقی و یکی هم برای قسمت موهومی.

با کاربست قوانین بالا، تعریف و اعلان زیر را خواهیم داشت:

TYPE
    Complex = PECORD
        Re, Im: Real
    END;
VAR
    Z: Complex;

در مثال بالا Complex یک type identifier است، Re و Im هر کدام یک identifier of field هستند و Z متغیری از نوع Complex است. در نتیجه Z رکوردی است که از دو مولفه یا فیلد ایجاد شده است.

⚪ برای دسترسی به یک مولفه از رکورد، ابتدا نام رکورد می‌آید و سپس یک . و بعد field identifier متناظر با آن مولفه قرار می‌گیرد. سینتکس دیاگرام زیر را از فصل ۶ ملاحظه کنید.

به طور مثال:

{ 5 + 3i }
Z.Re := 5;
Z.Im := 3;

⚪ به همین شیوه نوعی که یک تاریخ یا date را نمایندگی کند می‌تواند به شکل زیر تعریف شود:

TYPE
    Date = PACKED RECORD
        Year: 1900..2100;
        Mo  : (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
        Day : 1..31
    END;

❗ توجه داشته باشید که با این نوع Date، می‌توان ۳۱ آوریل را هم تعریف کرد!

⚪ یک اسباب‌بازی می‌تواند به شیوه‌ی زیر توصیف شود:

TYPE
    TOY = RECORD
        Kind: (Ball, Top, Boat, Doll, Blocks, Game, Model, Book);
        Cost: Real;
        Received: Date;
        Enjoyed: (Alot, Some, Alittle, None);
        Broken, Lost: Boolean
    END;

⚪ یک تکلیف درسی می‌تواند به شیوه‌ی زیر تعریف شود:

TYPE
    Assignment = PACKED RECORD
        Subject: (History, Language, Lit, Math, Psych, Science);
        Assigned: Date;
        Grade: 0..4;
        Weight: 1..10
    END; 

مثال 7.1

⚪ اگر خود رکورد در داخل ساختار دیگری به کار رفته باشد شیوه‌ی نام‌گذاریِ متغیرِ رکورد این ساختار را نشان می‌دهد. برای مثال فرض کنید می‌خواهیم آخرین زمان واکسیناسیون آبله را برای تمام اعضای یک خانواده ثبت کنیم. یک امکان این است که اعضای خانواده را از نوع enumerated تعریف کنیم و سپس date ها را در یک آرایه از رکوردها نگهداری کنیم:

TYPE
    FamilyMember = (Father, Mother, Child1, Child2, Child3);
VAR
    VaccinationDate: Array [FamilyMember] OF Date;

یک بروزرسانی فرضی به صورت زیر انجام خواهد شد:

VaccinationDate[Child3].Mo := Apr;
VaccinationDate[Child3].Day := 23;
VaccinationDate[Child3].Year := 1973

variant records

گاهی اوقات ممکن است بخواهیم اطلاعاتی را در یک رکورد ذخیره کنیم که وابسته به وجود اطلاعات دیگری در همان رکورد باشند. در این حالت می‌توانیم یک variant record type تعریف کنیم که شامل فیلدهای اضافی‌ای است که وابسته به مقدار فیلد دیگرند.

سینتکس یک نوع record پیش‌بینی‌هایی برای variant part انجام داده است، که به صورت ضمنی دلالت بر این دارد که یک نوعِ رکورد ممکن است به شیوه‌ای تعریف شود که دارای چند «ریخت» باشد. این بدان معنی است که متغیرهای گوناگون و اگر چه از یک نوع، می‌توانند ساختارهایی داشته باشند که در تعداد و نوع مولفه‌ها متفاوت باشند.

هر variant توسط یک لیست از اعلان مولفه‌های وابسته به آن که در داخل پرانتز محصور شده‌اند توصیف می‌شود. قبل از هر لیست یک یا چند constant می‌آید، و قبل از این مجموعه‌ی لیست‌ها هم یک عبارت case می‌آید که نوع این constant ها را مشخص می‌کند (یعنی نوعی که ملاک تشخیص گونه‌های مختلف رکورد است.)

⚪ به عنوان یک مثال نوع داده‌ی MaritalStatus را در نظر بگیرید:

TYPE
    MaritalStatus = (Married, Widowed, Divorced, Single);

حالا می‌توانیم افراد را با داده‌ای از نوع زیر توصیف کنیم:

TYPE
    Person = RECORD
        { fields common to all persons go here }
        CASE MaritalStatus OF
            Married: ( { fields of married persons only } );
            Single: ( { fields of single persons only } );
            ...
    END

💡 توجه کنید که هر مقدار از نوعی که به وسیله‌ی آن variant ها یا گونه‌ها تشخیص داده می‌شوند (به آن tag type هم می‌گوییم)، باید صراحتا با یکی از variant ها لیست شده باشند. در مثال بالا ثابت‌های Widowed و Divorced هم باید همراه با Married و Single بیایند تا مثال صحیح باشد.

معمولا یک فیلد از خود رکورد، variant صحیحِ فعلی‌اش را مشخص می‌کند. به عنوان مثال، رکوردِ تعریف شده در بالا یعنی Person محتمل است که یک فیلد «مشترک» به صورت زیر داشته باشد:

MS: MaritalStatus;

این وضعیت که فراوان اتفاق می‌افتد می‌تواند با اعلان مولفه‌ی تمایز دهنده (همان فیلدی که بر اساس آن یک variant فعال می‌شود)، که به آن tag field هم می‌گویند، در خود عبارت case گنجانده و مختصر شود. یعنی:

CASE MS: MaritalStatus OF

❗ اگر tag field وجود نداشته باشد پاسکال اصلا نمی‌تواند کمک کند که در حال حاضر کدام variant معتبر است. در واقع برنامه‌نویس می‌گوید من می‌دانم که چکار می‌کنم و خودم variant صحیح را تشخیص می‌دهم.

💥 حتی در صورت وجود tag field باز هم پاسکال در مورد دستکاری اشتباه فیلدها از variant های متفاوت خطا نمی‌دهد و سخت‌گیری نمی‌کند. تفسیر دقیق و درست فضای variant که در اختیار رکورد است و اینکه با چه فیلدهایی باید مورد دسترسی قرار بگیرند تا داده‌ها معنادار باشند کاملا بر عهده‌ی برنامه‌نویس است.

⚪ بهتر است قبل از این که Person را به عنوان یک variant record structure تعریف کنیم، طرح کلی اطلاعات مربوط به یک شخص را برای خودمان بکشیم:

I. Person
    A. name (last, first)
    B. height (natural number)
    C. sex (male, female)
    D. date of birth (year, month, day)
    E. number of dependents (natural number)
    F. marital status
        if married, widowed
            a. date of marriage (year, month, day)
        if divorced
            a. date of divorce (year, month, day)
            b. first divorce (false, true)
        if single

تصویر زیر دو رکورد نمونه، که با اطلاعات دو شخص متفاوت پر شده‌اند، را نشان می‌دهد:

⚪ حال با دانسته‌هایمان می‌توانیم رکورد Person را به صورت زیر بنویسیم:

TYPE
    String15 = PACKED Array [1..15] OF Char;

    Status = (Married, Widowed, Divorced, Single);

    Date = PACKED RECORD
        Year: 1900..2100;
        Mo  : (Jan, Feb, Mar, Apr, May, Jun,
               Jul, Aug, Sep, Oct, Nov, Dec);
        Day : 1..31;
    END;

    Natural = 0..MaxInt;

    Person  = RECORD
        Name: RECORD
            First, Last: String15
        END;
        Height: Natural; { centimeters }
        Sex: (Male, Female);
        Birth: Date;
        Depdts: Natural;
        case MS: Status OF
            Married, Widowed: (MDate: Date);
            Divorced: (DDate: Date; FirstD: Boolean);
            Single: ()
    END { Person }

مثال

⚪ موارد زیر را همیشه در نظر داشته باشید:

  1. نام تمام فیلدها باید منحصر به فرد باشند، حتی اگر در variant های متفاوتی آمده باشند.

  2. اگر یک variant خالی است (یعنی هیچ فیلدی ندارد) به شکل C:() نوشته می‌شود.

  3. هر FieldList می‌تواند فقط یک variant part داشته باشد که در صورت وجود باید بعد از fiexd part رکورد قرار گیرد. سینتکس دیاگرام‌های 7.a و 7.b را مجددا ملاحظه کنید:

  4. یک variant خودش هم می‌تواند یک variant part داشته باشد؛ بنابراین variant part ها می‌توانند تو در تو یا nested باشند. علت آن هم با مشاهده‌ی سینتکس دیاگرام variant کاملا مشخص است زیرا variant خودش نیز حاوی موجودیت FieldList است.

  5. scope یا حوزه‌ی constant identifier های نوع enumerated که در داخل یک نوع رکورد تعریف شده‌اند به سراسر بلاکی که آن رکورد را محصور کرده است گسترش می‌یابد. ⬅ مثال

⚪ دسترسی به یک مولفه‌ی رکورد به سادگی و با تبدیل ساختار درختی طرح رکورد به ساختار خطی، و با حرکت از بیرون به داخل، انجام می‌شود. به عنوان مثال فرض کنید متغیر P از نوع Person تعریف شده است. حال می‌توانیم دومین فردی که در مثال 7.g دیدیم را ایجاد کنیم:

P.Name.Last := 'Whitcomb       ';   { | for this assignment, string must be    }
P.Name.First := 'William        ';  { | exactly 15 characters long             }
P.Height := 186;
P.sex := Male;
P.Birth.Year := 1951;
P.Birth.Mo := Sep;
P.Birth.Day := 12;
P.Depdts := 1;
P.MS := Single;

with statement

شیوه‌ی نشانه‌گذاری بالا ممکن است قدری خسته کننده باشد و بخواهید آن را مختصر کنید و با with statement بنویسید. with statement عملا scope ای که حاوی field identifier های متغیر رکورد مورد نظر است را باز می‌کند، بنابراین field identifier ها می‌توانند مثل variable identifier ها مورد استفاده قرار گیرند (و فرصتی برای کامپایلر فراهم می‌شود که qualified statement را بهینه سازی کند.)

💡 منظور از qualified statement بدنه‌ی دستور with است.

سینتکس دیاگرام with statement به شکل زیر است:

داخل qualified statement یا همان بدنه‌ی with statement، فیلدهایِ متغیرِ رکوردِ موردِ نظر فقط با field identifier مورد دسترسی قرار می‌گیرند و نیازی به آوردن نام record variable در ابتدای field identifier نیست.

⚪ with statement زیر معادل assignment های کادر قبلی است:

WITH P DO BEGIN
    WITH Name DO BEGIN
        Last := 'Whitcomb       ';  { | for this assignment, string must be    }
        First:= 'William        '   { | exactly 15 characters long             }
    END;

    Height := 186;
    Sex := Male;

    WITH Birth DO BEGIN
        Year := 1951;
        Mo := Sep;
        Day := 12
    END;

    Depdts := 1;
    MS := Single
END

⚪ همچنین

VAR
    CurrentDate: Date;
...
WITH CurrentDate DO
    IF Mo = Dec THEN BEGIN
        Mo := Jan;
        Year := year + 1
    END ELSE
        Mo := Succ(Mo)

معادل کد زیر است:

VAR
    CurrentDate: Date;
...
IF CurrentDate.Mo = Dec THEN BEGIN
    CurrentDate.Mo := Jan;
    CurrentDate.Year := CurrentDate.Year + 1
END ELSE
    CurrentDate.Mo := Succ(CurrentDate.Mo)

⚪ کد زیر، بروزرسانی واکسیناسیون Child3 را، که قبلا مثال آن را دیده بودیم، نشان می‌دهد:

WITH VaccinationDate[Child3] DO BEGIN
    Year := 1973;
    Mo := Apr;
    Day := 23
END

⚪ وقتی که یک with statement می‌خواهد اجرا شود قبل از اجرای qualified statement یا همان بدنه‌ی with، یک اتصال به record variable برقرار می‌شود. بنابراین انتساب‌هایی که به وسیله‌ی qualified statement بر روی هر مولفه‌ی رکورد انجام شود هویت record variable را تغییر نخواهد داد. به عنوان مثال:

VAR
    Who: FamilyMember;
...
Who := Father;
WITH VaccinationDate[Who] DO BEGIN
    Who := Mother;
    Mo := Jul;
    Day := 7;
    Year := 1947
END

with statement بالا فیلدهای VaccinationDate[Father] را ست می‌کند.

nested with statement

with statement های تو در تو یا nested، می‌توانند به صورت مختصر هم نوشته شوند. شکل نوشتاری:

WITH R1, R2, ..., Rn DO
    S

معادل statement های زیر است:

WITH R1 DO
    WITH R2 DO
        ...
            WITH Rn DO
                S

بنابراین مثالی که یک شخص P را معرفی می‌کرد می‌تواند به صورت زیر بازنویسی شود:

WITH P, Name, Birth DO BEGIN
    Last := 'Whitcomb       ';
    First := 'William        ';
    Height := 186;
    Sex := Male;
    Year := 1951;
    Mo := Sep;
    Day := 12;
    Depdts := 1;
    MS := Single;
END { WITH }

🔔 طریقه‌ی resolve شدن متغیرها در داخل with statement از داخلی‌ترین رکورد به بیرونی ترین رکورد و سپس به variable identifier های خارج از ساختار رکوردها انجام می‌گیرد. کد زیر را ببینید. پاسکال مسیرهای کامنت شده را بررسی می‌کند و از ساختار دارای qualification بیشتر به سمت ساده‌تر حرکت می‌کند:

WITH R1, R2, R3 DO
    ...
{-----------------------------------------
    1. R1.R2.R3.Field
    2. R1.R3.Field
    3. R2.R3.Field
    4. R3.Field
    5. R2.Field
    6. R1.Field
    7. Field ( variable)
------------------------------------------}

مثال

⚪ مثال زیر نادرست است چون تعریف متغیر A ابهام برانگیز است:

VAR
    A: Array [2..8] OF Integer;
    A: 2..8;

اما این یکی درست است چون متغیر Integer به نام A به سادگی از متغیر Real به نام B.A قابل تمایز است. به همین صورت متغیر B که از نوع رکورد تعریف شده است از متغیر Boolean تعریف شده به نام B در داخل خود رکورد قابل تشخیص است:

VAR
    A: Integer;
    B: RECORD
        A: Real;
        B: Boolean
    END;

در داخل qualified statement زیر که با S نشان داده شده است:

WITH B DO
    S

identifier های A و B حالا دیگر بیان‌کننده‌ی مولفه‌های B.A و B.B هستند و متغیر Integer تعریف شده به نام A غیرقابل دسترسی است.

© کلیه‌ی حقوق برای safarionline.ir محفوظ است.