براي كار با فايلها، از كلاسهاي ifstream و ofstream استفاده ميشود كه در سر فايل <fstream> تعريف شدهاند.
ابتدا به نحوه باز كردن يك فايل و خواندن از آن ميپردازيم.
براي باز كردن يك فايل ورودي، بايد يك شيء از كلاس ifstream تعريف كنيم:
code:
#include <fstream>#include <iostream>
using namespace std;
void main()
{
ifstream file("c:\\1.txt");
if ( !file )
{
cout << "Error in opening file.";
return;
}
...
...
}
و يا
...
ifstream file;
file.open(filename);
if ( !file )
...
...
عملگر (!) براي جريانها به نحوي تعريف شده است كه در صورتي كه خطايي وجود داشته باشد، مقدار true برميگردانند.
وقتي يك شيء از كلاس ifstream ساخته ميشود، به طور پيش فرض فايل ورودي در نظر گرفته ميشود. راه ديگر اين است كه شيءي از كلاس fstream ساخته شده و سريحاً مشخص شود كه يك فايل ورودي است:
code:
fstream file;
file.open ( filename, ios::in );
ميتوان با تركيبي از فلگهاي مشخص شده در ios ، نحوه باز شدن فايل را به صورت كامل مشخص كرد. تعدادي از اين فلگها به شرح زير است:
ios::in – باز كردن فايل براي خواندن
ios::out – باز كردن فايل براي نوشتن در آن ( اشارهگر فايل در ابتداي فايل قرار ميگيرد و در صورت وجود فايل، عمل نوشتن، روي دادههاي قبلي انجام ميشود ).
ios::app – باز كردن فايل براي نوشتن در انتهاي آن ( اشارهگر فايل در انتهاي فايل قرار ميگيرد. توجه كنيد كه فايل بايد از قبل وجود داشته باشد )
ios::trunc – در صورت وجود فايل از قبل، محتويات آنرا پاك كرده و اشارهگر فايل را در ابتداي فايل قرار ميدهد.
ios::binary – فايل را براي ورودي و خروجي binary باز ميكند. در صورت مشخص نكردن اين فلگ فايل به صورت متني باز ميشود و تمام محتويات فايل، هنگام خواند به صورت كاراكترهاي ASCII در محدوده مجاز (از 32 تا 127 به علاوه يك سري كاراكترهاي خاص مثل ‘\n’ و ‘\r’ ) در نظر گرفته ميشود.
اين فلگها را ميتوان به صورت تركيبي نيز به كار برد؛ به مثال زير توجه كنيد كه فايل 1.txt را براي خواندن و نوشتن باز ميكند و در صورت وجود فايل از قبل، محتويات قبلي آن را پاك ميكند:
code:
#include <fstream>#include <iostream>
using namespace std;
void main()
{
fstream file;
file.open ( "c:\\1.txt", ios::in | ios::out | ios::trunc );
if ( !file )
{
cout << "Error in opnenin file." << endl;
return;
}
file << "This is the beginnig of the file.";
// move the file pointer back 9 characters
file.seekg ( -9, ios::end );
char s[20];
file >> s;
cout << s;
file.close();
}
Output:
the
در اين مثال، يك متن در فايل نوشته شدهاست و سپس اشاره گر خواندن فايل، 9 كاراكتر به عقب برگردانده شده است و يك رشته از فايل خوانده شدهاست. كار تابع seekg، جابجا كردن اشارهگر خواندن فايل است؛ پارامترهاي اين تابع به شرح زير ميباشد:
code:
seekg ( int offset, ios::seekdir way );پارامتر اول، مقدار جابجايي را مشخص ميكند و پارامتر دوم مشخص ميكند كه اين مقدار از كجاي فايل در نظر كرفته شود. سه فلگ (flag يا پرچم) كه براي اين پارامتر تعريف شدهاند به شرح زير هستند:
ios::cur
مقدار offset از مو قعيت جاري فايل در نظر گرفته ميشود
ios::beg
مقدار offset از ابتداي فايل در نظر گرفته ميشود
ios::end
مقدار offset از انتهاي فايل در نظر گرفته ميشود
تابعي كه در مقابل اين تابع وجود دارد، تابع teelg() ميباشد كه موقعيت جاري اشارهگر خواندن فايل را به صورت يك عدد صحيح برميگرداند.
توابع seekp و tellp نيز به همين صورت، براي كار كردن با موقعيت اشارهگر نوشتن در فايل به كار ميروند ( حرف g در tellg و seekg از “get pointer” ميآيد و حرف p در tellp و seekp از “put pointer” ميآيد ).
eof() و fail() :
تابع eof()، در صورتي كه تلاش براي خواندن از انتهاي فايل صورت گيرد، مقدار true برميگرداند ( eof مخخف End Of File است ). از اين تابع بايد با دقت استفاده كنيد، چرا كه اگر آخرين كاركتر فايل به طور معمول خوانده شود و اشارهگر فايل در انتهاي فايل قرار گيرد، اين تابع همچنان مقدار false برميگرداند ؛ تنها وقتي اين تابع true برميگرداند كه تلاش براي خواندن از انتهاي فايل صورت گيرد.
به مثال زير توجه كنيد تا موضوع روشن شود:
فرض كنيد در درايو C: فايلي با نام “test.txt” وجود دارد كه محتويات آن به اين صورت است:
23
45
(به يك خط خالي انتهاي فايل توجه كنيد)
فرض كنيد از كد زير براي چاپ اعداد فايل استفاده ميكنيم:
code:
#include <fstream>#include <iostream>
using namespace std;
void main()
{
fstream file;
file.open ( "c:\\test.txt", ios::in );
if ( !file )
{
cout << "Error in opnenin file." << endl;
return;
}
int i;
while ( !file.eof() )
{
file >> i;
cout << i << endl;
}
file.close();
}
Output:
23
45
45
همنانطور كه ميبينيد، عدد 45 دو بار چاپ شده است. علت اين است كه بعد از اينكه عدد 45 خوانده ميشود، اشارهگر فايل روي كاراكتر انتهايي فايل قرار دارد ولي در واقع يك خط خالي براي خواند وجود دارد و تابع eof() همچنان مقدار false را برميگرداند. وقتي در دور بعدي حلقه، عملگر استخراج از جريان سعي در خواندن يك عدد ديگر ميكند، به انتهاي فايل رسيده و پرچم eofbit در فايل set ميشود و مقدار متغير i نيز بدون تغيير باقي ميماند. احضار بعدي تابع eof() مقدار true را در پي دارد.
براي رفع اين مشكل، بايد از تابع fail() استفاده كنيد كه مقدار پرچم failbit را برميگرداند. اين پرچم در صورتي set ميشود كه دادهي معتبري از فايل خوانده نشده باشد. وقتي تابع fail() مقدار true برگرداند، به اين معني است كه عمل خواند قبلي، با موفقيت كامل انجام نشدهاست (البته اين فقط در مورد عملگر >> صدق ميكند؛ در مورد تابع getline قضيه اندكي فرق ميكند).
با اصلاح كد بالا به صورت زير، مشكل رفع ميشود:
code:
while ( !file.eof() ){
file >> i;
if ( !file.fail() )
cout << i << endl;
}
حال اگر فايل “c:\\test.txt” را به صورت زير تغيير دهيم:
23
45
Hello
192
اگر از كد اول (بدون تابع fail ) استفاده شود، عدد 45 بدون وقفه چاپ ميشود (امتحان كنيد) و اگر از كد اصلاح شده (با استفاده از fail) استفاده شود، برنامه بعد از چاپ 23 و 45، بدون چاپ خروجي ديگري، در يك حلقه بي انتها گير كرده و خاتمه نمييابد. علت اين است كه وقتي كه عملگر >> در دور سوم حلقه سعي در خواندن عدد صحيح بعدي ميكند، چون به يك كاراكتر غيد عددي برخورد ميكند، پرچم failbit را set كرده و بدون خواندن چيزي، برميگردد. چون به انتهاي فايل نيز نرسيدهايم، تابع eof همچنان fasle ميباشد. پرچم failbit نيز تا زماني كه صريحاً پاك نشود، "يك" باقي ميماند. براي پاك كردن پرچمهاي خطا بايد از تابع clear() استفاده شود. راه حل مشكل بالا، اگر هدف فقط حذف كاراكترهاي زائد باشد، به صورت زير است:
code:
while ( !file.eof() ){
file >> i;
if ( file.fail() )
{
file.clear();
file.ignore ( 1000, '\n' );
}
else
cout << i << endl;
}
براي پارامتر اول تابع ignore كه تعداد كاراكترهاست، يك مقدار بزرگ قرار دادهايم تا تمام كاراكتر ها را تا ابتداي خط بعدي حذف كند. توجه كنيد كه وقتي پرچم failbit مقداردهي شده باشد، هيچ تابع ديگري كار نخواهد كرد. بنابر اين قبل از هر كاري بايد آنرا پاك كرد (به همين علت تابع clear قبل از تابع ignore قرار داده شدهاست. اگر جاي اين دو تابع عوض شود، برنامه باز هم در يك حلقه بينهايت گير ميكند).
عملگر (!) ، در واقع مقدار تابع fail را برميگرداند. در ضمن عملگر void* نيز براي streamها طوري تعريف شده است كه مقدار معكوس تابع fail را برميگرداند (يعني !fail() ) . اگر متوجه نشديد نگران نباشيد. الآن متوجه ميشويد! هر گاه ما نوعي را در جايي قرار دهيم كه در آنجا معني خاصي نميدهد، كامپايلر سعي ميكند آن را به نوعي تبديل كند كه در آنجا معني بدهد! مثلاً اگر بنويسيم if ( cin ) ...; ، cin يك شرط نيست كه ما آن را در دستور if قرار دهيم؛ بنابراين كامپايلر سعي ميكند آن را به نوعي تبديك كند كه بتوان مقدار آن را به صورت true يا false تفسير كرد. در اين مورد، كامپايلر آن را به نوع void* تبديل ميكند و اين تبديل براي streamها مقدار !fail() را برميگرداند. مثلاً به جاي قطعه كد بالا ميتوانيد بنويسيد:
code:
while ( !file.eof() ){
file >> i;
if ( file )
cout << i << endl;
else
{
file.clear();
file.ignore ( 1000, '\n' );
}
}
_________________
آگه 50000 نفر هم به یک چیز اشتباه اعتقاد داشته باشن , اون چیز هنوز هم اشتباه است !
محمـــــــــــــــــــــدرضا . اگر گه گاهی سراغتون افتاد , خوشحال میشم : http://360.yahoo.com/molla652003
سوابق کاریه من :
معاد
روزی یک حدیث
ACM_sharif
طراحی الگوریتم
در اعماق بی کران
Art Of Programming
عکس های بچه ها
نماز
معرفی سایت های مفید
شعر های دوران مهدکودک
موفقیت
موسیقی





