اما بیایید قدم به قدم پیش برویم:
باز کردن یک فایل
اولین عملیاتی که معمولاً روی یک شی از این کلاس ها انجام می شود، مرتبط کردن آن با یک فایل واقعی است. این روش به عنوان باز کردن یک فایل شناخته می شود. یک فایل باز در داخل یک برنامه توسط یک جریان نمایش داده می شود (یعنی یک شی از یکی از این کلاس ها؛ در مثال قبلی، این myfile بود) و هر عملیات ورودی یا خروجی انجام شده بر روی این شی جریان به فایل فیزیکی مرتبط اعمال می شود.
برای باز کردن یک فایل با یک شی جریان، از تابع عضو آن open استفاده می کنیم:
;open (filename, mode)
جایی که نام فایل رشته ای است که نشان دهنده نام فایلی است که باید باز شود، و حالت یک پارامتر اختیاری با ترکیبی از فلگهای زیر است:
ios::in باز کردن فایل جهت عملیات ورودی
ios::in باز کردن فایل جهت عملیات ورودی
ios::binary باز کردن در حالت باینری
ios::ate موقعیت اولیه را در انتهای فایل تنظیم میکند.
اگر این پرچم تنظیم نشده باشد، موقعیت اولیه ابتدای فایل است.
ios::app تمام عملیات خروجی در انتهای فایل انجام می شود و محتوا به محتوای فعلی فایل اضافه می شود.
ios::trunc اگر فایل برای عملیات خروجی باز شده باشد و از قبل وجود داشته باشد، محتوای قبلی آن حذف شده و با محتوای جدید جایگزین می شود.
همه این فلگها را می توان با استفاده از عملگر بیتی OR (|) ترکیب کرد. برای مثال، اگر بخواهیم فایل example.bin را در حالت باینری برای افزودن داده باز کنیم، میتوانیم این کار را با فراخوانی زیر به تابع عضو open انجام دهیم:
![]()
هر یک از توابع عضو باز کلاس های ofstream، ifstream و fstream دارای یک حالت پیش فرض هستند که اگر فایل بدون آرگومان دوم باز شود، استفاده می شود:
| کلاس | پارامتر مود پیش فرض |
|---|---|
| ofstream | ios::out |
| ifstream | ios::in |
| fstream | ios::in | ios::out |
برای کلاسهای ifstream و offstream، ios::in و ios::out بهطور خودکار و به ترتیب فرض میشوند، حتی اگر حالتی که آنها را شامل نمیشود به عنوان آرگومان دوم به تابع عضو باز ارسال شود (پرچمها ترکیب میشوند).
برای fstream، مقدار پیشفرض تنها زمانی اعمال میشود که تابع بدون تعیین مقداری برای پارامتر mode فراخوانی شود. اگر تابع با هر مقداری در آن پارامتر فراخوانی شود، حالت پیش فرض لغو می شود، نه ترکیب.
جریانهای فایلی که در حالت باینری باز میشوند، عملیات ورودی و خروجی را مستقل از هرگونه ملاحظات قالب انجام میدهند. فایلهای غیر باینری به عنوان فایلهای متنی شناخته میشوند و ممکن است برخی از ترجمهها به دلیل قالببندی برخی از نویسههای خاص (مانند نویسههای بازگشتی خط جدید و حملونقل) رخ دهد.
از آنجایی که اولین وظیفه ای که بر روی جریان فایل انجام می شود عموماً باز کردن یک فایل است، این سه کلاس شامل سازنده ای هستند که به طور خودکار تابع عضو باز را فراخوانی می کند و دقیقاً پارامترهای مشابه این عضو را دارد. بنابراین، میتوانیم شی قبلی myfile را نیز اعلام کنیم و همان عملیات باز کردن را در مثال قبلی خود با نوشتن انجام دهیم:
![]()
ترکیب ساخت و ساز شی و باز شدن جریان در یک عبارت واحد. هر دو فرم برای باز کردن یک فایل معتبر و معادل هستند.
برای بررسی اینکه آیا جریان فایل با موفقیت باز شده است یا خیر، میتوانید این کار را با تماس با عضو is_open انجام دهید. این تابع عضو یک مقدار bool با مقدار true برمیگرداند در صورتی که در واقع شی جریان با یک فایل باز مرتبط باشد یا در غیر این صورت false:
![]()
بستن یک فایل
وقتی عملیات ورودی و خروجی روی یک فایل تمام شد، آن را می بندیم تا سیستم عامل مطلع شود و منابع آن دوباره در دسترس قرار گیرد. برای آن، تابع عضو جریان را بسته میخوانیم. این تابع عضو، بافرهای مرتبط را شستشو می دهد و فایل را می بندد:
;()myfile.close
هنگامی که این تابع عضو فراخوانی می شود، شی جریان می تواند مجدداً برای باز کردن یک فایل دیگر استفاده شود و فایل دوباره برای باز شدن توسط فرآیندهای دیگر در دسترس است.
در صورتی که یک شی از بین برود در حالی که هنوز با یک فایل باز مرتبط است، تخریب کننده به طور خودکار تابع عضو بسته را فراخوانی می کند.
فایل های متنی
جریان های فایل متنی آنهایی هستند که پرچم ios:: باینری در حالت باز کردن آنها گنجانده نشده است. این فایلها برای ذخیره متن طراحی شدهاند و بنابراین تمام مقادیری که از/به آنها ورودی یا خروجی میشوند ممکن است دچار تغییرات قالببندی شوند که لزوماً با مقدار دودویی تحت اللفظی آنها مطابقت ندارد.
عملیات نوشتن روی فایل های متنی به همان روشی که با cout عمل کردیم انجام می شود:

خواندن از روی یک فایل نیز می تواند به همان روشی که با cin انجام دادیم انجام شود:

این مثال آخر یک فایل متنی را می خواند و محتوای آن را روی صفحه چاپ می کند. ما یک حلقه while ایجاد کرده ایم که با استفاده از getline فایل را خط به خط می خواند. مقداری که توسط getline برگردانده میشود، ارجاع به خود شی جریان است، که وقتی به عنوان یک عبارت بولی ارزیابی میشود (مانند این حلقه while) در صورتی که جریان برای عملیاتهای بیشتر آماده باشد، درست است و اگر انتهای فایل هر کدام از آنها را داشته باشد، نادرست است. به دست آمده یا اگر خطای دیگری رخ داده باشد.
چک کردن پرچم های ایالت
توابع عضو زیر برای بررسی وضعیتهای خاص یک جریان وجود دارند (همه آنها یک مقدار bool را برمیگردانند):
()bad : اگر عملیات خواندن یا نوشتن ناموفق باشد، مقدار true را برمیگرداند. به عنوان مثال، در موردی که سعی می کنیم در فایلی بنویسیم که برای نوشتن باز نیست یا دستگاهی که در آن می خواهیم بنویسیم فضای خالی باقی نمانده است.
()fail: درست را در موارد مشابه bad() برمیگرداند، اما همچنین در مواردی که یک خطای فرمت رخ میدهد، مانند زمانی که یک کاراکتر الفبایی استخراج میشود زمانی که میخواهیم یک عدد صحیح را بخوانیم.
eof()
اگر فایلی که برای خواندن باز است به پایان رسیده باشد، true برمی گرداند.
good()
این پرچم عمومی ترین حالت است: در موارد مشابهی که فراخوانی هر یک از توابع قبلی true را برمی گرداند false را برمی گرداند. توجه داشته باشید که خوب و بد دقیقاً متضاد نیستند (خوب پرچم های ایالت های بیشتری را به طور همزمان بررسی می کند).
تابع عضو clear() می تواند برای تنظیم مجدد پرچم های حالت استفاده شود.
موقعیت یابی جریان را دریافت و قرار دهید
تمام اشیاء جریان های i/o به صورت داخلی -حداقل- یک موقعیت داخلی را حفظ می کنند:
ifstream، مانند istream، موقعیت دریافت داخلی را با محل عنصری که در عملیات ورودی بعدی خوانده می شود، نگه می دارد.
ofstream، مانند ostream، موقعیت قرارگیری داخلی را با مکانی که عنصر بعدی باید نوشته شود، نگه می دارد.
در نهایت، fstream، هر دو موقعیت، get و put را مانند iostream حفظ می کند.
این موقعیتهای جریان داخلی به مکانهایی در جریان اشاره میکنند که در آن عملیات خواندن یا نوشتن بعدی انجام میشود. این موقعیت ها را می توان با استفاده از توابع عضو زیر مشاهده و اصلاح کرد:
tellg() و tellp()
این دو تابع عضو بدون پارامتر مقداری از نوع عضو streampos را برمیگردانند، که نوعی نشاندهنده موقعیت دریافت فعلی (در مورد tellg) یا موقعیت قرار دادن (در مورد tellp) است.
seekg() و seekp()
این توابع اجازه می دهد تا مکان گرفتن و قرار دادن موقعیت را تغییر دهید. هر دو عملکرد با دو نمونه اولیه مختلف بارگذاری می شوند. شکل اول این است:
;seekg ( position )
;seekp ( position )
با استفاده از این نمونه اولیه، اشاره گر جریان به موقعیت مطلق تغییر می کند (شمارش از ابتدای فایل). نوع این پارامتر streampos است که همان نوع بازگشتی توسط توابع tellg و tellp است.
شکل دیگر این توابع این است:
;seekg ( offset, direction )
;seekp ( offset, direction )
با استفاده از این نمونه اولیه، موقعیت get یا put روی یک مقدار افست نسبت به نقطه خاصی که توسط جهت پارامتر تعیین می شود، تنظیم می شود. افست از نوع streamoff است. و جهت از نوع seekdir است که یک نوع برشماری است که نقطه شمارش افست را تعیین می کند و می تواند هر یک از مقادیر زیر را بگیرد:
| ios::beg | افست از ابتدای جریان شمارش می شود |
| ios::cur | افست از موقعیت فعلی شمارش می شود |
| ios::end | افست از انتهای جریان شمارش می شود |
مثال زیر از توابع عضوی که به تازگی دیده ایم برای به دست آوردن اندازه یک فایل استفاده می کند:

به نوع شروع و پایان متغیرها توجه کنید:
;streampos size |
streampos نوع خاصی است که برای بافر و موقعیتیابی فایل استفاده میشود و نوعی است که توسط file.tellg(). مقادیری از این نوع را می توان با خیال راحت از مقادیر دیگر از همان نوع کم کرد و همچنین می تواند به یک نوع عدد صحیح به اندازه کافی بزرگ تبدیل شود تا اندازه فایل را در خود جای دهد.
این توابع موقعیتیابی جریان از دو نوع خاص استفاده میکنند: streampos و streamoff. این انواع نیز به عنوان انواع عضو کلاس جریان تعریف می شوند:
| Type | Member type | Description |
|---|---|---|
| streampos | ios::pos_type | به عنوان fpos تعریف شده است. می توان آن را به/از streamoff تبدیل کرد و می توان مقادیر این نوع را اضافه یا کم کرد.. |
| streamoff | ios::off_type | نام مستعار یکی از انواع داده اساسی (مانند int یا long long) است. |
هر یک از انواع عضو بالا نام مستعار معادل غیرعضو خود است (آنها دقیقاً از همان نوع هستند). فرقی نمی کند که کدام یک استفاده شود. انواع اعضا عمومیتر هستند، زیرا در همه اشیاء جریانی یکسان هستند (حتی در جریانهایی که از انواع شخصیتهای عجیب و غریب استفاده میکنند)، اما انواع غیرعضو به دلایل تاریخی به طور گسترده در کدهای موجود استفاده میشوند.
فایل های باینری
برای فایلهای باینری، خواندن و نوشتن دادهها با عملگرهای استخراج و درج (<< و >>) و توابعی مانند getline کارآمد نیست، زیرا ما نیازی به قالببندی هیچ دادهای نداریم و دادهها احتمالاً در خطوط قالببندی نمیشوند.
جریان های فایل شامل دو تابع عضو هستند که به طور خاص برای خواندن و نوشتن داده های باینری به صورت متوالی طراحی شده اند: نوشتن و خواندن. اولین مورد (نوشتن) یک تابع عضو ostream است (به ارث رسیده توسط ofstream). و read تابع عضو istream است (به ارث رسیده توسط ifstream). اشیاء کلاس fstream هر دو را دارند. نمونه های اولیه آنها عبارتند از:
نوشتن (memory_block، اندازه)؛
خواندن (حافظه_بلاک، اندازه)؛
جایی که memory_block از نوع char* (اشارهگر به char) است و نشاندهنده آدرس آرایهای از بایتها است که در آن عناصر داده خوانده شده ذخیره میشوند یا عناصر دادهای که باید نوشته شوند از آنجا گرفته میشوند. پارامتر اندازه یک مقدار صحیح است که تعداد کاراکترهایی را که باید از/به بلوک حافظه خوانده یا نوشته شوند را مشخص می کند.

در این مثال، کل فایل خوانده شده و در یک بلوک حافظه ذخیره می شود. بیایید بررسی کنیم که چگونه این کار انجام می شود:
ابتدا فایل با پرچم ios::ate باز می شود، به این معنی که نشانگر دریافت در انتهای فایل قرار می گیرد. به این ترتیب، هنگامی که با عضو tellg() تماس می گیریم، مستقیماً اندازه فایل را بدست می آوریم.
هنگامی که اندازه فایل را به دست آوردیم، درخواست تخصیص یک بلوک حافظه به اندازه کافی برای نگهداری کل فایل را داریم:
;memblock = new char[size]
بلافاصله پس از آن، ما شروع به تنظیم موقعیت get در ابتدای فایل می کنیم (به یاد داشته باشید که در انتها فایل را با این اشاره گر باز کردیم)، سپس کل فایل را می خوانیم و در نهایت آن را می بندیم:
;file.seekg (0, ios::beg) ;file.read (memblock, size) ;()file.close
در این مرحله میتوانیم با دادههای بهدستآمده از فایل کار کنیم. اما برنامه ما به سادگی اعلام می کند که محتوای فایل در حافظه است و سپس تمام می شود.
بافرها و همگام سازی
هنگامی که ما با جریان های فایل کار می کنیم، این ها به یک شی بافر داخلی از نوع streambuf مرتبط می شوند. این شی بافر ممکن است یک بلوک حافظه را نشان دهد که به عنوان یک واسطه بین جریان و فایل فیزیکی عمل می کند. به عنوان مثال، با یک جریان، هر بار که تابع عضو put (که یک کاراکتر منفرد می نویسد) فراخوانی می شود، ممکن است کاراکتر به جای اینکه مستقیماً در فایل فیزیکی که جریان با آن مرتبط است نوشته شود، در این بافر میانی درج شود.
سیستم عامل همچنین ممکن است لایه های دیگری از بافر را برای خواندن و نوشتن روی فایل ها تعریف کند.
هنگامی که بافر فلاش می شود، تمام داده های موجود در آن در رسانه فیزیکی (اگر جریان خروجی باشد) نوشته می شود. این فرآیند همزمان سازی نامیده می شود و تحت هر یک از شرایط زیر انجام می شود:
هنگامی که فایل بسته می شود: قبل از بستن یک فایل، تمام بافرهایی که هنوز پاک نشده اند همگام سازی می شوند و تمام داده های در حال انتظار در رسانه فیزیکی نوشته یا خوانده می شوند.
وقتی بافر پر است: بافرها اندازه مشخصی دارند. هنگامی که بافر پر است به طور خودکار همگام می شود.
به صراحت، با دستکاریکنندهها: هنگامی که از دستکاریکنندههای خاصی در جریانها استفاده میشود، یک هماهنگی صریح صورت میگیرد. این دستکاریها عبارتند از: flush و endl.
به صراحت، با عضو تابع sync(): فراخوانی تابع عضو جریان، sync() باعث همگام سازی فوری می شود. اگر جریان بافر مرتبطی نداشته باشد یا در صورت خرابی، این تابع مقدار int برابر با -1 را برمیگرداند. در غیر این صورت (اگر بافر جریان با موفقیت همگام سازی شده باشد) 0 را برمی گرداند.
همیشه در حال یادگیری و بدرود
سعید دامغانیان - رادیو صدای ققنوس
