فصل نهم
فایلها
اطلاعاتی که در متغیرهایی که تاکنون شناختیم نگهداری میشود، در حافظه اصلی کامپیوتر قرار میگیرد و بهمحض خاموش شدن سیستم یا اتمام برنامه کلاً نابود میشود. در بسیاری از کاربردها نیاز داریم تا اطلاعات را برای دوره طولانی نگهداری نماییم. فایلها این امکان را فراهم میآورند. اطلاعاتی که در فایل ذخیره میشود، بهجای حافظه اصلی در حافظه ثانویه یا دیسک قرار میگیرد و مادامیکه این اطلاعات و فایلها را پاک نکنیم، ماندگار خواهد بود. فایلهای موسیقی، فیلم، فایلهای متنی، فایلهای برنامه ++C همگی نمونههایی از کاربرد فایل میباشند. در این فصل میخواهیم با نحوه ایجاد و نوشتن اطلاعات در فایل و همچنین خواندن اطلاعات از فایل در زبان ++C آشنا شویم. در این فصل، روی فایلهای متنی تمرکز خواهیم داشت. فایلهای متنی فایلهایی هستند که حاوی اطلاعاتی هستند که برای کاربران قابلخواندن هستند. بهعنوانمثال ترکیبی از اعداد، حروف الفبا، حروف فاصله و .. میتواند در فایلهای متنی به کار گرفته شود. بهعنوانمثال فایل کد برنامههای C++ که با پسوند cpp ذخیره میشوند، فایل متنی تلقی میشوند. پسوند عمومی فایلهای متنی txt است.
مراحل کار با فایل به شرح زیر است:
1- تعریف متغیر فایل و معرفی مسیر فایل و بازکردن فایل برای خواندن یا نوشتن اطلاعات
2- خواندن یا نوشتن اطلاعات
3- بستن فایل
برای کارکردن با فایلها لازم است با استفاده از معرفی زیر، کتابخانه مربوطه را به کامپایلر معرفی کنیم:
با این فایل سرفصل، کتابخانه استاندارد ++C دو نوع داده به نامهای ifstream و ofstream را تعریف میکند. ifstream نشانگر جریانی از کاراکترهاست که از یک فایل ورودی میآید و به همین ترتیب ofstream نشانگر جریانی از کاراکترهاست که به سمت یک فایل خروجی میرود.
عملاً این کار امکان استفاده از توابع مربوط به کار کردن با فایلها را فراهم میآورد. در ادامه به معرفی سناریوهای خواندن و نوشتن یک فایل میپردازیم.
نوشتن اطلاعات در فایل
برای نوشتن اطلاعات در فایل ابتدا لازم است متغیری را از نوع فایل خروجی یا همان ofstream تعریف کنیم. سپس با استفاده از تابع open میتوانیم آن فایل را باز کنیم:
پارامتر اول تابع open مسیر و نام فایل را مشخص میکند. در اینجا میخواهیم اطلاعات را در فایل test.txt بنویسیم. هنگامیکه از این دستور استفاده میکنیم، چنانچه فایلی به این نام از قبل وجود داشته باشد، اطلاعات درون آن پاک شده و آماده نوشتن میشود. در صورتی هم که چنین فایلی موجود نباشد، یک فایل جدید به همین اسم ساخته میشود و آماده نوشتن اطلاعات میشود.
در گام دوم، باید اطلاعات موردنظر را به فایل منتقل کنیم. شیوه نوشتن اطلاعات در یک فایل دقیقاً مشابه نمایش اطلاعات در خروجی برای کاربر است. درواقع خروجی که با دستور cout به کاربر نشان داده میشود هم حالت خاصی از فایل به شمار میآید. به دستور زیر توجه کنید:
از شکلدهندههای setw ، endl ، setprecision نیز میتوان برای نوع داده ofstream استفاده کرد.
درنهایت باید فایل بسته شود. بستن فایل باعث میشود اولاً تمامی تغییرات و نوشتنهای انجامشده به فایل منتقل شود و در دیسک نوشته شود. ثانیاً فایل موردنظر آزاد شده و برنامههای دیگر بتوانند با آن کار کنند و مثلاً اطلاعات نوشتهشده را بخوانند. نحوه بستن فایل بهصورت زیر است:
همانگونه که اشاره کردیم، دستور open برای فایلهای خروجی باعث میشود که چنانچه فایل از قبل وجود داشته باشد، کل محتوای آن پاک شود. در بسیاری از موارد ما میخواهیم اطلاعات قبلی حفظ شود و اطلاعات جدید در ادامه اطلاعات موجود نوشته شود. در این صورت باید از دستور زیر استفاده شود:
اضافه کردن پارامتر ios::app که مخفف append است، باعث میشود اطلاعات جدید در امتداد اطلاعات قبلی ذخیره شود.
کل برنامه نوشتن اطلاعات در فایل بهصورت زیر است:
خواندن اطلاعات از فایل
برای خواندن اطلاعات نیز باید ابتدا متغیر فایل تعریف شود. این بار نوع این متغیر باید ifstream باشد. i مخفف input بوده و نشان میدهد که فایل برای خواندن اطلاعات و بهعنوان ورودی در نظر گرفته شده است.
توجه کنید که نوع داده ifstream فقط بهعنوان فایل ورودی میتواند به کار گرفته شود و همینطور نوع داده ofstream فقط بهعنوان فایل خروجی میتواند به کار گرفته شود. اگر بخواهیم عمل خواندن و نوشتن را روی یک فایل داشته باشیم از نوع دادهای به نام fsteram استفاده میکنیم.
در اینجا، فایل بهصورت خودکار باز میشود و نیازی نیست از دستور open بهصورت مجزا استفاده شود. در عوض، ازآنجاکه ممکن است به دلایل مختلف مانند پیدا نکردن فایل (وجود نداشتن فایل) یا خراب بودن رسانهای که فایل روی آن ذخیره شده است نظیر لوح فشرده، امکان استفاده از فایل وجود نداشته باشد، باید حتماً قبل از استفاده این موضوع کنترل شود. به عبارتی باید کنترل کنیم که واقعاً فایل موجود بوده و آماده استفاده است. برای این کار از دستور (تابع) is_open() استفاده میکنیم. درصورتیکه مقدار بازگشتی این تابع true باشد، یعنی فایل موجود بوده و قابل استفاده است. این موضوع به کمک یک دستور شرطی if کنترل میشود.
بهصورت پیشفرض، سیستم، فایل را در مسیری که برنامه در آن قراردارد و اجرا میشود جستجو میکند. درصورتیکه فایل در مسیر دیگری باشد باید علاوه بر نام فایل، مسیر آن نیز ذکر شود:
خواندن اطلاعات از فایل به کمک دستور getline انجام میشود که یک خط از محتویات فایل مربوطه را خوانده و در متغیر دیگری (در این مثال line) که بهعنوان پارامتر دوم به آن داده میشود، قرار میدهد. این متغیر از نوع رشته است. به مثال زیر توجه کنید:
با توجه به اینکه معمولاً در یک فایل چندین خط اطلاعات موجود است، این کار باید بهدفعات تکرار شود. برای این منظور از یک حلقه while استفاده میکنیم. حال این سؤال مطرح میشود که این حلقه تا کجا باید ادامه پیدا کند و چطور متوجه شویم که به انتهای فایل رسیدهایم و همه اطلاعات موجود در آن را خواندهایم. تابع getline مقدار بولین برمیگرداند که تا زمانی که به انتهای فایل نرسیده باشد، مقدار true دارد و وقتی به پایان فایل برسد، false میشود. بنابراین حلقه while بهصورت زیر نوشته میشود. فرض کنید هر بار خطی را که از فایل میخوانیم، همانجا به کاربر نشان دهیم:
در این نمونه کد، طی یک حلقه while خط به خط از فایل خوانده میشود و به کاربر نشان داده میشود.
در پایان پس از اتمام فایل باید آن را به کمک دستور close بست:
همه عملیاتی که تاکنون آموختهایم، عملگر دریافتکننده (>>)توابع get و ignore نیز برای نوع داده ifstream مجاز میباشند. برنامه کامل خواندن اطلاعات از فایل بهصورت زیر است:
مثالهایی از کارکردن با فایلها
مثال: تابعی بنویسید که نام دو فایل را بهعنوان پارامتر ورودی دریافت کند و اطلاعات فایل اول را در فایل دوم کپی کند. به عبارتی یک کپی از فایل اول تهیه کند.
برای حل این مثال، بهصورت همزمان به عملیات خواندن و نوشتن فایل نیاز خواهیم داشت. بهاینترتیب که ابتدا باید هر دو فایل را بازکنیم و یکی را برای خواندن و دیگری را برای نوشتن آماده کنیم. سپس اطلاعات را از فایل اول خوانده و در فایل دوم بنویسیم. در این مثال فرض میکنیم که هر بار یک خط را در متغیر line خوانده و در فایل دوم مینویسیم. متغیر originalFile نشاندهنده فایل مبدأ و copyFile نشاندهنده فایل مقصد است.
مثال: تابعی بنویسید که اطلاعات دو فایل را خوانده، ادغام نماید و در فایل سوم بنویسد. مسئله شکستن یک فایل بزرگ به چند فایل کوچک و بعد به هم چسباندن فایلهای کوچک و تشکیل فایل اصلی از مسائل پرکاربرد است. فرض کنید میخواهید یک فایل خیلی بزرگ را روی CD کپی نمایید یا از طریق پست الکترونیکی ارسال نمایید. درصورتیکه حجم فایل از حجم CD یا حجم مجاز ارسال فایل از طریق پست الکترونیکی بیشتر باشد، مجبور خواهیم بود فایل را به تکههای کوچکتر بشکنیم و پس از ارسال مجدداً این قطعات را ادغام کنیم و فایل اصلی را شکل دهیم. در این مثال میخواهیم با نحوه ادغام فایلها آشنا شویم. برای این منظور دو فایل ورودی و یک فایل حاصل خواهیم داشت. مشابه تابع کپی، اطلاعات از فایل اول خوانده میشود و در فایل سوم نوشته میشود. پس از اتمام اطلاعات فایل اول، نوبت به فایل دوم میرسد. مجدداً اطلاعات از فایل دوم خوانده میشود و در فایل سوم نوشته میشود. کد زیر این تابع را نشان میدهد:
حل مسئله ، مطالعه یک مورد
مقایسه دو لیست
مسئله : برنامهای داریم که درست بودن دادههای ورودی برای آن حیاتی و اساسی است. دادهها غیر منفی فرض میشوند. برای اطمینان از درست بودن دادهها آنها را در فایل ورودی توسط دو اپراتور دو بار وارد میکنیم و با یک عدد منفی دو لیست را از هم جدا میکنیم. این دو لیست باید با هم یکسان باشند. اگر این دو لیست یکسان نباشند، در دادههای ورودی خطایی وجود دارد. برای مثال اگر دنباله اعداد 8, 14 , 17 ,-5 ,8 , 14, 17 باشد، دو لیست یکسان بوده و دادهها بهدرستی وارد شدهاند. اگر دنباله بهصورت 8 , 12 , 17 , -5 , 8 , 14, 17 باشند در ورود اطلاعات خطایی رخ داده است. برنامهای مینویسیم که لیستهای موجود در فایل ورودی را مقایسه کند و در صورت بروز تفاوت بین آنها آن جفت غیر یکسان را چاپ کند. تعداد عناصر آرایهها معلوم نیست. اما مطمئن هستیم که از 500 مورد بیشتر نیستند.
ورودی: فایلی به نام (dataFile) شامل دو لیست اعداد صحیح و مثبت که توسط یک عدد منفی از هم جدا شدهاند و طول یکسان دارند.
خروجی: پیامی مبنی بر یکسان بودن دو لیست یا چاپ جفتهای غیر یکسان در دو لیست.
توضیح: ازآنجاکه دو لیست در یک فایل هستند، ابتدا لیست را خوانده و ذخیره میکنیم تا به عدد منفی برسیم. آنگاه لیست دوم را خوانده و آن را با لیست اولی مقایسه میکنیم. برای ذخیره لیست اولی از آرایهای به نام firstList استفاده میکنیم. تعریف این آرایه چنین است:
ممکن است از همه حافظه درخواست شده از کامپیوتر استفاده نکنیم. شکل 9-1 را ببینید.
بعدازآنکه عناصر آرایه firstList را با دادههای موجود در فایل از ابتدا تا رسیدن به عدد منفی پر کردیم. اولین عنصر firstList را با اولین عدد لیست دوم که بعد از عدد منفی در فایل قرارداد مقایسه میکنیم. سپس دومین عنصر firstList را با دومین عدد لیست دوم مقایسه کرده و همینطور ادامه میدهیم تا به پایان لیست برسیم. دو حالت اتفاق میافتد. یا دو لیست کاملاً برابر هستند یا حداقل به یک مورد تفاوت میرسیم.
در مورد اول پیام یکسان بودن دو لیست را چاپ میکنیم.
در مورد دوم موارد تفاوت را از firstList و از لیست دوم با هم چاپ میکنیم.
فرضیه: دو لیست دارای اندازه یکسان هستند.
ساختمان داده: از یک آرایه یکبعدی با عناصر int به نام firstList برای نگهداری اولین لیست استفاده میکنیم.
الگوریتم به شرح زیر است:
- شروع به خواندن از فایل کن تا به عدد منفی برسی (تابع ReadFirstList)
- هر یک از اعدادی را که خواندی به آرایه اضافه کن
- درنهایت تعداد اعداد خواندهشده را بهعنوان اندازه لیست نگهدار.
- مجدداً خواندن دادهها را از فایل به تعداد اندازه لیست ادامه بده (تابع CompareLists)
- هر عددی را که از فایل خواندی با عدد متناظر در آرایه مطابقت بده
- در اولین موردی که مغایرتی مشاهده شد، آن را چاپ کن و false را بهعنوان نتیجه مطابقت برگردان
- درصورتیکه هیچ مغایرتی مشاهده نشد، true را به نشانه مطابقت کامل برگردان
لیست برنامه چنین است (به توضیحات و پیششرطهای برنامه توجه کنید):
برنامه با دو مجموعه داده اجرا شده است: یکی با دو لیست یکسان و دیگری با دو لیست غیر یکسان داده و نتایج حاصل از هرکدام ذیـلاً نشان داده شده است: