➡ فهرست
انواع Record یا Record type ها احتمالا منعطفترین ساختارهای داده هستند. به صورت مفهومی یک نوع Record قالبی است برای یک ساختار که اجزای آن ممکن است ویژگیهای کاملا متفاوتی داشته باشند. به عنوان مثال فرض کنید که میخواهیم اطلاعات مرتبط با یک فرد را ثبت کنیم. اطلاعات همیشگی عبارتند از:
علاوه بر این موارد، اگر آن فرد متاهل است یا جدا شده است، تاریخ (آخرین) ازدواج ؛ اگر جدا شده است تاریخ (آخرین) جدایی و این که آیا این اولین جدایی است یا نه، و اگر کلا مجرد بوده است هیچ کدام از این اطلاعات اخیر لازم نیست.
تمام این «اطلاع» ها میتواند در «یک» رکورد بیان شوند و هر بخش از این اطلاعات میتواند به صورت جداگانه مورد دسترسی قرار گیرد.
به صورت رسمیتر، یک رکورد ساختاری است که از تعداد ثابتی مولفه که فیلد 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 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 }
⬅ مثال
⚪ موارد زیر را همیشه در نظر داشته باشید:
نام تمام فیلدها باید منحصر به فرد باشند، حتی اگر در variant های متفاوتی آمده باشند.
اگر یک variant خالی است (یعنی هیچ فیلدی ندارد) به شکل
C:()
نوشته میشود.
هر FieldList میتواند فقط یک variant part داشته باشد که در صورت وجود باید بعد از fiexd part رکورد قرار گیرد. سینتکس دیاگرامهای 7.a و 7.b را مجددا ملاحظه کنید:


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

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 عملا 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] را ست میکند.
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 غیرقابل دسترسی است.