🔔
در این فصل به تبعیت از کتاب pascal user manual and report از نماد
↑
استفاده کردهایم، حال آن که در کدهای نوشته شده حتی برای
کامپایلر Pascal P5
باید از نماد ^ استفاده کنیم.
➡ فهرست
sequnece از بسیاری جهات سادهترین روش ساختاربندی است. در حرفهی data-processing عبارتی که به صورت عمومی برای توصیف یک sequence پذیرفته شده است، sequential file است. پاسکال به سادگی فقط از کلمهی فایل برای مشخص کردن یک ساختار که از توالیای از مولفهها ساخته شدهاست استفاده میکند ــ همهی آن مولفهها یا component ها از یک نوع هستند. textfile یک نوع بخصوص از فایل است که از یک توالی از خطهای با طول متغیر از کاراکترها تشکیل شده است. textfile ها پایههای ارتباط قابل خواندن بین انسانها و سیستمهای کامپیوتری را شکل میدهند. شکل زیر به روشنتر شدن بحث کمک میکند:
component --> sequence --> file --> textfile
💡 در زبان پاسکال file یک structured type است که از یک sequence از component های هم نوع تشکیل شده است.
ترتیب طبیعی component ها از ابتدا به انتهای sequence تعریف شده است و در هر لحظه فقط یک component به صورت مستقیم قابل دسترسی است. بقیهی component ها به ترتیب با پیش رفتن در فایل قابل دسترسی هستند. تعداد component ها، که طول فایل نامیده میشود، در هنگام تعریف فایل به صورت ثابت مشخص نمیشود. این ویژگیای است که آشکارا تمایز فایل و آرایه را نشان میدهد. به فایلی که حاوی هیچ component ای نباشد فایل خالی یا empty file میگویند. بنابراین یک نوع file با انواع array و record و set فرق دارد چون فایل ساختاری با دسترسی ترتیبی یا sequential است که همهی component هایش از یک نوع است.
File = sequential access + same component type
Array = random access + same component type + fixed number of components
سینتکس دیاگرام نوع فایل به صورت زیر است:

اعلان هر متغیر فایل، مثلا F، به صورت خودکار یک buffer variable را وارد
برنامه میکند که با نشانهی F↑ بیان میشود. این buffer variable
از نوع component type است.
به buffer variable به عنوان یک دسترسی به فایل نگاه کنید که از طریق آن
میتوان مقدار component های موجود را بررسی کرد (یا خواند) و یا
component های جدیدی
ایجاد کرد (یا نوشت) و در بعضی file operation ها به صورت خودکار به جلو برده
میشود.
به کل متغیر فایل یا file variable نمیتوان assignment کرد. به جای آن میتوان از buffer variable برای append کردن component ها به صورت تک به تک و ترتیبی (sequential) استفاده کرد. اگر موقعیت فایل بعد از آخرین component اش قرار گیرد buffer variable به صورت undefined در میآید.
سینتکس دیاگرام buffer variable به صورت زیر است:

پردازش ترتیبی، طول متغیر، و وجود یک buffer variable نشان میدهند که
فایلها میتوانند با حافظهی ثانویه و تجهیرات جانبی
مرتبط باشند.
اینکه component های فایل چگونه تخصیص داده شوند به پیادهسازی زبان/کامپایلر
بستگی دارد، اما میتوانیم فرض کنیم که در هر لحظه از زمان فقط تعدادی
از component ها در حافظهی اصلی قرار دارند و فقط component ای که با
F↑
مشخص شده است به صورت مستقیم قابل دسترسی است.
🔔 حافظهی ثانویه مثل هارد دیسک و فلاپی و تجهیزات جانبی یا peripherals مثل صفحه کلید و چاپگر.
وقتی که متغیر بافر
F↑
به بعد از پایان فایل F برده شده
باشد، تابع boolean از پیش تعریف شدهی
Eof(F)
مقدار True و در غیر این صورت False برمیگرداند.
پروسیجرهای اصلی برای کار با فایلها عبارتند از:
Reset(F) :
با قرار دادن موقعیت مکاننمای داخلی
فایل در ابتدای آن، بررسی (خواندن) F را آغاز
میکند. اگر F خالی یا empty نباشد مقدار اولین component آن به
F↑
assign میشود و Eof(F) نیز False میگردد.Rewrite(F) : تولید (نوشتن) فایل F را آغاز میکند.
مقدار فعلی F با empty file جایگزین میشود (یعنی یک فایل بدون هیچ
component ای). Eof(F) مقدار True میگیرد و ممکن است یک فایل جدید نوشته شود.Get(F) : فایل را یک component به جلو میبرد و مقدار این component را به
متغیر بافر F↑ نسبت میدهد. اگر compnent بعدیای وجود نداشته باشد
Eof(F) مقدار True میگیرد و F↑ مقداری تعریف نشده خواهد داشت.
اگر Eof(F) مقدار True داشته باشد یا F در حال ایجاد شدن باشد در این صورت
فراخوانی پروسیجر Get(F) خطا خواهد داد. #ThinkPut(F) : مقدار متغیر بافر F↑ را به فایل F اضافه یا append میکند.
نتیجهی این عمل خطا است مگر اینکه قبل از اجرای این پروسیجر
predicate یا گزارهی
Eof(F) مقدار True داشته باشد. بعد از یک Put(F) موفق Eof(F) همچنان True
باقی میماند و F↑ تعریف نشده میشود. اگر در حال inspection یا خواندن
از F باشیم استفاده از Put(F) خطا است.📝 به طور کلی همه عملیاتِ تولید و بررسی sequential file میتوانند تماما از طریق چهار file operator ابتدایی و گزارهی Eof بیان شوند.
در عمل ترکیب دو عملیات جلو بردن موقعیت مکان نمای داخلی فایل با دسترسی به buffer variable کاملا عادی و طبیعی است. از همین رو دو پروسیجر زیر را نیز برای کار با فایلهای ترتیبی فراهم کردهایم:
Read(F, X) : X یک متغیر است. این پروسیجر معادل کد زیر است:
BEGIN
X := F↑;
Get(F)
END
Write(F, E) : E یک expression است. این پروسیجر نیز معادل کد زیر است:
BEGIN
F↑ := E;
Put(F)
END
❗ در واقع Read و Write پروسیجرهای ویژهای هستند که برای پذیرش تعداد متغیری از پارامترهای واقعی توسعه داده شدهاند. توجه کنید که خود ما به عنوان برنامهنویس نمیتوانیم پروسیجری با variable number of arguments بنویسیم.
⚪ در کد زیر پروسیجر Read معادل compound statement ای است که در ادامه آمده است:
Read(F, V1, ..., Vn)
{ is equivalent to }
BEGIN
Read(F, V1);
...;
Read(F, Vn)
END
⚪ پروسیجر ویژهی Write و معادل آن که با چند پروسیجر Write پی در پی نوشته میشود را در کادر زیر ملاحظه کنید:
Write(F, E1, ..., En)
{ is equivalent to }
BEGIN
Write(F, E1);
...;
Write(F, En)
END
در مثالهای بالا V1...Vn متغیر هستند و E1...En هم expression اند.
⚪
مزیت استفاده از پروسیجرهای Read و Write فقط در این نیست که باعث کوتاهتر
شدن برنامه میشوند، بلکه از نظر مفهومی هم سادهترند چون
میتوانیم وجود متغیر بافر F↑، که گاهی اوقات undifined است، را نادیده
بگیریم. با این وجود buffer variable را میتوان به عنوان ابزاری برای
«نگاه به جلو» به کار برد:
Reset(F);
WHILE NOT Eof(F) DO
BEGIN
{ look at F↑ and decide what to do }
Get(F);
END
در کد بالا قبل از اینکه با پروسیجر Get از component فعلی عبور کنیم بر مبنای
مقدار متغیر بافر F↑ که حاوی
مقدار component فعلی است
تصمیم میگیریم که در ادامه باید چه کاری انجام دهیم. این همان
مفهوم look ahead یا نگاه به جلو است.
⚪ چند مثال از اعلان متغیرهایی از نوع فایل:
VAR
Data: FILE OF Integer;
A: Integer;
PlotFile: FILE OF RECORD
C: Color;
Len: Natural
END;
Club: FILE OF Person;
P: Person;
⚪ بر مبنای declaration های بالا چند مثال از statement هایی که با فایلها کار میکنند را در زیر ارائه کردهایم:
A := Data↑;
Get(Data);
Read(Data, A);
PlotFile↑.C := Red;
PlotFile↑.Len := 17;
Put(PlotFile);
Club↑ := P;
Put(Club);
Write(Club, P)
⚪ فایلها میتوانند نسبت به program و یا نسبت به یک پروسیجر local تعریف شوند و یا اینکه از قبل خارج از program وجود داشته باشند. این مورد آخر را external file مینامیم. فایلهای خارجی یا external file ها از طریق program heading و به عنوان پارامتر به برنامه ارسال میشوند. ( فصل ۳ را ببینید)
⛔ مثال 9.1. این برنامه نه در P5 و نه در Free Pascal و نه در GNU Pascal کامپایل و اجرا نشد.
⛔ مثال 9.2. این برنامه نه در P5 و نه در Free Pascal و نه در GNU Pascal کامپایل و اجرا نشد.
⬅ مثال. این برنامه در P5 اجرا نشد.
textfile ها فایلهایی هستند که از یک توالی از کاراکترها تشکیل شدهاند که به خطهایی با طول متغیر تقسیم بندی میشوند. نوع از پیش تعریف شدهی Text برای اعلان textfile ها استفاده میشود.
میتوانیم فرض کنیم که نوع Text روی نوع دادهی اصلی Char و اضافه کردن
(فرضی) یک line terminator یا
end-of-line marker
ایجاد شده است. بنابر این
نوع Text معادل
PACKED FILE OF Char
نیست. این
end-of-line marker
میتوانند توسط پروسیجرها و توابع مخصوص textfile زیر تولید شده و یا تشخیص
داده شوند:
Writeln(F) : خط جاریِ textfile داده شده را پایان میدهد یا terminate
میکند.Readln(F) : به ابتدای خط بعدی textfile داده شده پرش یا skip میکند.
F↑ برابر اولین کاراکتر خط بعد میشود.Eoln(F) : یک تابع Boolean است که بیان میکند آیا به
end-of-line خط جاری در textfile داده شده رسیدهایم یا نه. اگر بله،
F↑ معادل مکان line seperator خواهد بود ولی خود F↑ دارای مقدار blank است.❓ با نگاه کردن به کد مثالهای Read1 و ReadLn1 سعی کنید رفتار پروسیجرهای Read و ReadLn و Write و WriteLn و توابع Eoln و Eof را تحلیل کنید.
⚪
اگر F یک textfile و Ch یک متغیر از نوع کاراکتر باشد Write(F, Ch) معادل و
نسخهی مختصر تکه کد زیر است:
BEGIN
F^ := Ch;
Put(F)
END
⚪
پیاده سازی Read(F, Ch) به دو صورت ممکن است و انتخاب یکی از این
دو شیوه وابسته به برنامهنویسان
کامپایلر است:
F↑ را در متغیر Ch میریزد.در هر دو حالت بعد از assignment، پروسیجرِ Get(F) اجرا میشود.
⚪ Input و Output نام دو متغیر textfile استاندارد هستند که به عنوان پارامترهای program برای خواندن و نوشتن text به صورت واضح و خوانا استفاده میشوند. در فصل ۱۲ به تفصیل در مورد این متغیرها و فرمهای توسعه یافتهی پروسیجرهای Read و Write و ReadLn و WriteLn صحبت خواهیم کرد.
تکه کدهای زیر از قراردادهای پیشگفته برای نشان دادن بعضی از عملیاتهای رایج بر روی textfile ها استفاده میکنند:
نوشتن در یک textfile.
فرض کنید که P(C) یک کاراکتر (بعدی) را محاسبه میکند و آن را در پارامتر C قرار میدهد. برای terminate شدن خط فعلی، متغیر B1 به True ست میشود و برای نشان دادن اینکه متن باید خاتمه یابد متغیر B2 به True مقداردهی میشود:
Rewrite(Y);
REPEAT
REPEAT
P(C);
Write(Y, C)
UNTIL B1;
WriteLn(Y)
UNTIL B2
خواندن از یک textfile.
فرض کنید که Q(C) بیانگر پردازش یک کاراکتر (بعدی) C باشد و R نیز عملی است که در صورت مواجه شدن با end-of-line باید اجرا شود:
Reset(X);
WHILE NOT Eof(X) DO BEGIN
WHILE NOT Eoln(X) DO BEGIN
Read(X, C);
Q(C)
END;
R;
ReadLn(X)
END
کپی کردن یک textfile در textfile دیگر با حفظ line structure فایل اول:
Reset(X);
Rewrite(Y);
WHILE NOT Eof(X) DO BEGIN
WHILE NOT Eoln(X) DO BEGIN { copy a line }
Read(X, C);
Write(Y, C)
END;
ReadLn(X);
WriteLn(Y)
END
⬅ مثال. با GNU Pascal کامپایل شود.
🔵
نکتهای در باب پیادهسازی: یک روش سرراست برای بیان
نشانگر end-of-line استفاده از کاراکترهای کنترلی است. به عنوان مثال، در
مجموعهی کاراکترهای اسکی، دو کاراکتر cr یا carriage return و lf
یا line feed به صورت قراردادی برای نشان دادن پایان یک خط استفاده میشوند.
به هر حال، بعضی سیستمهای کامپیوتری از مجموعه کاراکترهایی عاری از چنین
control character هایی استفاده میکنند؛ این موضوع باعث میشود که
از روشهای دیگری برای بیان end-of-line استفاده کرد.