C++ معالجة الأخطاء
مفهوم معالجة الأخطاء في C++
معالجة الأخطاء ( Exceptions Handling ) يقصد منها كتابة الكود الذي قد يسبب أي مشكلة في البرنامج بطريقة تضمن أنه إذا حدث الخطأ المتوقع أو أي خطأ آخر فإن البرنامج لن يعلّق أو يتم إغلاقه بشكل فجائي.
ظهور خطأ في البرنامج بشكل مفاجئ هو أمر سيئ جداً لأنه يؤدي إلى نفور عدد كبير من المستخدمين و عدم رغبتهم في العودة إلى استخدام هذا البرنامج مجدداً.
أنواع الأخطاء
أخطاء لغوية ( Syntax Errors ) و يقصد بها أن تخالف مبادئ اللغة مثل أن تعرّف شيء بطريقة خاطئة أو تنسى وضع فاصلة منقوطة.
أخطاء تحدث أثناء تشغيل البرنامج يقال لها إستثناءات ( Exceptions ) مما يؤدي إلى تعليقه و إيقافه بشكل غير طبيعي.
أخطاء منطقية ( Logical Errors ) و يقصد منها أن الكود يعمل بدون أي مشاكل و لكن نتيجة تشغيل هذا الكود غير صحيحة.
إذاً, أي خطأ برمجي يحدث معك أثناء تشغيل البرنامج يقال له إستثناء ( Exception ) حتى إن كان إسم الخطأ يحتوي على كلمة Error.
بمعنى آخر, أي Error يظهر لك أثناء تشغيل البرنامج يعتبر Exception.
في هذا الدرس ستتعلم كيف تتجنب حدوث أخطاء في البرامج التي تكتبها, و فعلياً ستتعلم كيف تجهز البرنامج للتعامل مع الأخطاء التي قد تحدث أثناء تشغيله لجعل البرنامج شغال دائماً في نظر المستخدم و لا يظهر له أي أخطاء.
بعض الأسباب التي تسبب حدوث إستثناء
في حال كان البرنامج يتصل بالشبكة و فجأةً إنقطع الإتصال.
في حال كان البرنامج يحاول قراءة معلومات من ملف نصي, و كان هذا الملف غير موجود.
في حال كان البرنامج يحاول إنشاء أو حذف ملف و لكنه لا يملك صلاحية لفعل ذلك.
معلومة تقنية
في حال كان الكود الذي كتبته يحتوي على أخطاء لغوية ( Syntax Errors ) لا بد من أن تصلحها كلها حتى يستطيع المترجم تحويل الكود الذي كتبته لكود يفهمه الحاسوب و من ثم ينفذه لك. أي لا يمكنك حماية البرنامج من موجودة في الكود نفسه بل يمكنك حمايته من المشاكل التي قد تحدث وقت عمل هذه الكود.
في هذا الدرس ستتعلم كيف أن المكتبات الجاهزة التي قد تستخدمها مستقبلاً في مشاريعك يتم جعلها تظهر أخطاء إذا لم تستخدمها كما يجب.
معرفة هذا الأمر مهمة جداً لك أيضاً, لأنك ستتمكن من مشاركة الكود الذي تقوم بإعداده مع مبرمجين آخرين و إظهار أخطاء فيه إن لم يقوموا باستخدامه بشكل صحيح.
أمثلة على أنواع الأخطاء في C++
في المثال التالي لم نضع فاصلة منقوطة في آخر الأمر cout مما سيؤدي لحدوث مشكلة عندما يحاول المترجم تشغيل البرنامج.
إذاً الكود التالي يحتوي على خطأ لغوي ( Syntax Error ).
المثال الأول
#include <iostream>
using namespace std;
int main()
{
int x = 10;
cout << x
return 0;
}
•سيظهر الخطأ التالي عند التشغيل و الذي يعني أن المترجم يتوقع أن تضع له فاصلة منقوطة في آخر السطر التاسع.
في المثال التالي قمنا بإنشاء برنامج يطبع للطالب ما إذا كان ناجحاً أو راسباً بناءاً على معدله النهائي.
من المفترض أنه يتم إعتبار الطالب راسب في حال كان معدله بين 0 و 9.9, و يتم إعتباره ناجح في حال كان معدله 10 و 20.
هنا تعمدنا وضع خطأ منطقي ( Logical Error ) حيث أننا عند طباعة نتيجة الطالب لم نتأكد ما إذا كان المعدل بين 0 كحد أدنى و 20 كحد أقصى.
المثال الثاني
#include <iostream>
using namespace std;
int main()
{
float average = 25;
if (average < 10)
{
cout << "The student failed the exam";
}
else if (average >= 10)
{
cout << "The student passed the exam";
}
return 0;
}
•سنحصل على النتيجة التالية عند التشغيل و نلاحظ أنه لا يوجد مشكلة برمجية سببت إيقاف الكود لكننا نعلم أن هناك مشكلة منطقية في الكود لأن المعدل الذي تم على أساسه طباعة جملة النجاح هو معدل مستحيل أن يكون حقيقي.
الكلمة throw في C++
تستخدم هذه الكلمة لرمي إستثناء, أي لإظهار أنه يوجد مشكلة ما في البرنامج.
في المثال التالي قمنا بإنشاء دالة إسمها divide() عند استدعاءها نمرر لها رقمين فتقوم بإرجاع ناتج قسمة العدد الأول على العدد الثاني.
هنا في حال كان الرقم الثاني الذي تم تمريره للدالة (المقسوم عليه) يساوي صفر سيتم رمي إستثناء عبارة عن نص عادي مفاده بأنه في الرياضيات لا يمكن القسمة على صفر.
مثال
#include <iostream>
using namespace std;
// عند استدعاءها نمرر لها عددين فتقوم بإرجاع ناتج قسمة العدد الأول على العدد الثاني divide() هنا قمنا بتعريف دالة إسمها
double divide(double a, double b)
{
// يساوي 0, سيتم رمي إستثناء b في حال كان العدد الثاني الذي سيتم تمريره للباراميتر
if (b == 0)
{
throw "Math Error, you can't divide by 0";
}
// إذا لم يتم رمي إستثناء سيتم إرجاع ناتج القسمة
return a / b;
}
// main() هنا قمنا بتعريف الدالة
int main()
{
// و تمرير عددين لها و من ثم طباعة ناتج القسمة الذي سترجعه divide() هنا قمنا باستدعاء الدالة
cout << divide(5, 2) << endl;
// و تمرير 0 مكان الباراميتر الثاني مما سيؤدي لرمي إستثناء و توقف البرنامج بشكل كلي divide() هنا قمنا باستدعاء الدالة
cout << divide(5, 0) << endl;
return 0;
}
•سنحصل على النتيجة التالية عند التشغيل.
•لاحظ أنه عرض لك ناتج قسمة أول عددين و الذي هو 2.5 بنجاح.
•و عندما حاول أن يعرض ناتج ثاني عددين أخبرك أن البرنامج توقف عن العمل بسبب أنه تم رمي إستثناء.
2.5 terminate called after throwing an instance of 'char const*'
•بالإضافة لذلك فقد ظهرت نافذة منبثقة تخبرك بأن البرنامج توقف بشكل مفاجئ بسبب حدوث مشكلة فيه.
سبب توقف البرنامج عندما قامت الدالة برمي إستثناء هو أننا لم نقم باستدعاء الدالة devide() بشكل محمي رغم أننا نعرف أنها قد تسبب تعليق البرنامج, و نقصد بذلك أننا أي لم نقم باستخدام الجملتين try و catch كما يفترض أن نفعل.
إذا كنت تتساءل عن سبب عدم ظهور الجملة "Math Error, you can't divide by 0" عندما قامت الدالة برمي الإستثناء, فسبب ذلك أننا لم نقم بمعالجة الإستثناء الذي تم رميه كما يفترض و بالطبع ستتعلم كيف تفعل ذلك بعد قليل.
الجملتين try و catch في C++
إلتقاط الإستثناء ( Exception Catching ) عبارة عن طريقة تسمح لك بحماية البرنامج من أي كود تشك بأنه قد يسبب أي خطأ و لتحقيق هذا الأمر نستخدم الجملتين try و catch.
بشكل عام, أي كود مشكوك فيه يجب وضعه بداخل حدود الجملة try.
أي مشكلة تحدث في الجملة catch يتم معالجتها في حدود الجملة try الخاصة بها كالتالي.
try
{
// Protected Code
// هنا نكتب الأوامر التي قد تسبب إستثناء
}
catch(ExceptionType e)
{
// Error Handling Code
// برمي إستثناء try هنا نكتب أوامر تحدد للبرنامج ماذا يفعل إذا قامت الـ
}
الكود الذي نضعه بداخل الجملة try يسمى Protected Code و هذا يعني أن البرنامج محمي من أي خطأ قد يحدث بسبب هذا الكود.
الكود الذي نضعه بداخل الجملة catch يسمى Error Handling Code و يقصد منها الكود الذي سيعالج الإستثناء الذي قد يتم إلتقاطه.
ملاحظة
عندما تستخدم الجملة try حتى لو لم تضع بداخلها أي كود, فأنت مجبر على وضع الجملة catch بعدها.
كما أنه بإمكانك وضع أكثر من جملة catch في حال كان الكود قد يسبب أكثر من خطأ.
في المثال التالي قمنا بإنشاء دالة إسمها devide() عند استدعاءها نمرر لها رقمين فتقوم بإرجاع ناتج قسمة العدد الأول على العدد الثاني.
هنا في حال كان الرقم الثاني الذي تم تمريره للدالة (المقسوم عليه) يساوي صفر سيتم رمي إستثناء عبارة عن نص عادي مفاده بأنه في الرياضيات لا يمكن القسمة على صفر.
بما أن الدالة devide() قد تسبب حدوث خطأ عندما يتم استدعاءها قمنا بوضعها بداخل try/catch
المثال الأول
#include <iostream>
using namespace std;
// عند استدعاءها نمرر لها عددين فتقوم بإرجاع ناتج قسمة العدد الأول على العدد الثاني divide() هنا قمنا بتعريف دالة إسمها
double divide(double a, double b)
{
// يساوي 0, سيتم رمي إستثناء b في حال كان العدد الثاني الذي سيتم تمريره للباراميتر
if (b == 0)
{
throw "Math Error, you can't divide by 0";
}
// إذا لم يتم رمي إستثناء سيتم إرجاع ناتج القسمة
return a / b;
}
// main() هنا قمنا بتعريف الدالة
int main()
{
// و تمرير 0 مكان الباراميتر الثاني مما سيؤدي لرمي إستثناء divide() هنا قمنا باستدعاء الدالة
try
{
cout << divide(5, 0) << endl;
}
// e الإستثناء الذي سيتم رميه سيكون عبارة عن نص (سلسلة من الأحرف) و هذه الأحرف سيتم تمريرها كقيمة للمتغير
catch (char const* e)
{
// e هنا قمنا بطباعة نص الإستثناء الذي تم رميه و تخزينه في المتغير
cout << e << endl;
}
// هنا سيتم تنفيذ الأمر التالي بشكل عادي جداً لأن الإستثناء الذي حدث في السابق تم معالجته
cout << "The program is still working properly :)";
return 0;
}
•سنحصل على النتيجة التالية عند التشغيل.
Math Error, you can't divide by 0 The program is still working properly :)
معلومة مهمة
سبب جعل نوع الباراميتر e يكون char const* بالتحديد هو أننا لاحظنا في المثال السابق أن النص الذي يتم رميه كإستثناء, يكون نوعه كذلك.
مفاهيم مهمة حول معالجة الأخطاء في C++
المفهوم الأول
هنا وضعنا مثال حول كيفية تعريف دالة تفعل throw لأكثر من رقم نوعهم int بالإضافة إلى كيفية إستدعاءها.
في المثال التالي قمنا بتعريف دالة إسمها compareAges() عند استدعاءها نمرر لها عددين, العدد الأول عبارة عن عمر الإبن و الثاني عبارة عن عمر والدته.
الدالة ستقوم بمقارنة عمر الإبن مع عمر والدته و ترجع الفارق بينهما بشرط أن تكون الأعداد التي نمررها لها تعتبر أعداد مقبولة منطقياً, غير ذلك سترمي إستثناء.
مثال
#include <iostream>
using namespace std;
// عند استدعاءها نمرر لها عددين فتقوم بإرجاع عدد يمثل الفارق بينهما compareAges() هنا قمنا بتعريف دالة إسمها
int compareAges(int sonAge, int momAge)
{
// في حال كان عمر الإبن أكبر أو يساوي عمر الأم, سيتم رمي إستثناء رقمه 1
if (sonAge >= momAge)
throw 1;
// في حال كان عمر الإبن أصغر أو يساوي صفر سيتم رمي إستثناء رقمه 2
else if (sonAge <= 0)
throw 2;
// في حال كان عمر الأم أصغر أو يساوي صفر سيتم رمي إستثناء رقمه 3
else if (momAge <= 0)
throw 3;
// في حال لم يكن الفرق بين عمر الأم و الإبن 12 سنة على الأقل سيتم رمي إستثناء رقمه 4
else if (momAge - sonAge < 12)
throw 4;
// إذا لم يتم رمي إستثناء سيتم إرجاع فرق العمر
return momAge - sonAge;
}
// main() هنا قمنا بتعريف الدالة
int main()
{
// و تمرير عددين لها يمثلان عمر أمر و عمر إبنها لمعرفة ما إن كانت الأعمار المدخلة تعتبر مقبولة أم لا compareAges() هنا قمنا باستدعاء الدالة
try
{
compareAges(26, 24);
}
// e الإستثناء الذي سيتم رميه سيكون عبارة عن نص (سلسلة من الأحرف) و هذه الأحرف سيتم تمريرها كقيمة للمتغير
catch (int e)
{
switch(e)
{
case 1:
cout << "Error: Son's age can't be less than his mom! \n";
break;
case 2:
cout << "Error: Son's age can't be less than or equal zero \n";
break;
case 3:
cout << "Error: Mom's age can't be less than or equal zero \n";
break;
case 4:
cout << "Error: Mom's age should be 12 years bigger than son age \n";
break;
}
}
// هنا سيتم تنفيذ الأمر التالي بشكل عادي جداً لأن الإستثناء الذي حدث في السابق تم معالجته
cout << "The program is still working properly :)";
return 0;
}
•سنحصل على النتيجة التالية عند التشغيل.
Error: Son's age can't be less than his mom! The program is still working properly :)
المفهوم الثاني
هنا وضعنا مثال حول كيفية استخدام الرمز ... لحماية الكود من أي إستثناء قد يحدث حتى لو لم نكن نعرف نوع الإستثناء الذي قد يتم رميه.
إذا كنت لا تعرف نوع الإستثناء الذي قد يسببه الكود أو كنت تعرف فقط بعض أنواع الإستثناءات التي قد تحدث و تريد حماية الكود من كل الإستثناءات التي قد تحدث يمكنك وضع الرمز ... فقط في الدالة catch() كالتالي.
try
{
// هنا نكتب الأوامر التي قد تسبب إستثناء
}
catch(...)
{
// سيقوم المترجم بالإنتقال لهنا try أي إستثناء يحدث في الجملة
}
في المثال التالي قمنا بتعريف دالة إسمها checkAge() عند استدعاءها نمرر لها عدد يمثل العمر, فتقوم بالتشييك عليه و إظهار أخطاء في حال كان العمر أصغر أو يساوي 0 أو كان أكبر من 130 أو أو كان أقل من 18.
مثال
#include <iostream>
using namespace std;
// عند استدعاءها نمرر لها عدد يمثل العمر checkAge() هنا قمنا بتعريف دالة إسمها
void checkAge(int age)
{
// في حال كان العمر الذي تم تمريره لها أصغر أو يساوي 0 سترمي إستثناء
if (age <= 0)
throw "Error: Entered age can't be less or equal zero!";
// في حال كان العمر الذي تم تمريره لها أكبر من 130 سترمي إستثناء
if (age > 130)
throw "Error: Entered age is impossible!";
// في حال كان العمر الذي تم تمريره لها أصغر من 18 سترمي إستثناء
if (age < 18)
throw "Error: You are not allowed!";
// إذا لم يتم رمي أي إستثناء سيتم تنفيذ أمر الطباعة التالي و الذي يعني أن العمر مقبول
cout << "Age confirmed!";
}
// main() هنا قمنا بتعريف الدالة
int main()
{
// و تمرير القيمة 15 لها checkAge() هنا قمنا باستدعاء الدالة
try
{
checkAge(15);
}
// بالتقاطه و طباعة الجملة الموضوعة فيه catch() ستقوم الدالة try أي إستثناء يتم رميه في الجملة
catch (...)
{
cout << "Oops.. Something is not right!";
}
return 0;
}
•سنحصل على النتيجة التالية عند التشغيل.
Oops.. Something is not right!
المفهوم الثالث
هنا وضعنا مثال حول كيفية وضع أكثر من catch في حال كان الكود قد يسبب إستثناءات من أكثر من نوع.
في المثال التالي قمنا بتعريف دالة إسمها checkWord() عند استدعاءها نمرر لها نص لتتحقق منه و التأكد ما إن كان يحتوي على كلمة واحدة أم لا.
الدالة سترمي إستثناء قيمته العدد 0 في حال تم تمرير نص فارغ لها, و سترمي إستثناء قيمته الكلمة 'Space' في حال تم تمرير أكثر من كلمة لها.
مثال
#include <iostream>
using namespace std;
// عند استدعاءها نمرر لها نص checkWord() هنا قمنا بتعريف دالة إسمها
checkWord(string s)
{
// في حال كان النص الذي تم تمريره لا يحتوي على أي حرف سيتم رمي إستثناء عبارة عن عدد قيمته 0
if (s.empty())
throw 0;
// "Space" في حال كان النص الذي تم تمريره يحتوي على مسافة فارغة سيتم رمي إستثناء عبارة عن نص قيمته
if (s.find(" ") != string::npos)
throw "Space";
}
// main() هنا قمنا بتعريف الدالة
int main()
{
// هنا قمنا بتعريف النص الذي سنقوم بالتشييك على قيمته
string s = "Hello Word!";
// s للتشييك على قيمة المتغير checkWord() هنا قمنا باستدعاء الدالة
try
{
checkWord(s);
}
// في حال تم رمي إستثناء قيمته العدد 0, سيتم معالجته هنا
catch(int e)
{
cout << "Error: string length is empty! \n";
}
// سيتم معالجته هنا ,Space في حال تم رمي إستثناء قيمته الكلمة
catch(char const* e)
{
cout << "Error: string contain a whitespace! \n";
}
// في حال تم رمي إستثناء من أي نوع آخر, سيتم معالجته هنا
catch(...)
{
cout << "Error: something is not right! \n";
}
cout << "The program is still working properly :)";
return 0;
}
•سنحصل على النتيجة التالية عند التشغيل.
Error: string contain a whitespace! The program is still working properly :)
المفهوم الرابع
هنا وضعنا مثال حول كيفية تعريف نوع إستثناء جديد و استخدامه.
الإستثناءات الإفتراضية في C++
قبل شرح كيفية تعريف إستثناء جديد, يجب أن تعرف كيف تم بناء الإستثناءات الجاهزة في اللغة.
بشكل عام std::exception هو الكلاس الأساسي لأي إستثناء يتم تعريفه لذلك يجب على أي كلاس يمثل إستثناء أن يرث منه.
بعد أن يرث منه, يجب أن يفعل Override لدالة إسمها what() ليحدد فيها قيمة الإستثناء الذي سيتم رميه.
الصورة التالية تظهر لك الإستثناءات الجاهزة في C++ و كيف أنها ترث من الكلاس std::exception.
مثال حول كيفية تعريف إستثناء جديد في C++
في المثال التالي قمنا بتعريف كلاس إسمه MyException و جعلناه يرث من الكلاس exception لكي يمثل إستثناء.
في هذا الكلاس قمنا بإعادة تعريف الدالة what() لجعلها تقوم برمي إستثناء عبارة عن نص (سلسلة أحرف) عندما يتم استدعاءها.
مثال
#include <iostream>
#include <exception>
using namespace std;
// لأننا نريده أن يمثل إستثناء exception يرث من الكلاس MyException هنا قمنا بتعريف كلاس إسمه
class MyException : public exception
{
public:
// لجعلها ترمي الإستثناء الذي نريده عندما نقوم باستدعائها what() هنا قمنا بتعريف الدالة
const char* what() const throw ()
{
return "My Exception is thrown! \n";
}
};
// main() هنا قمنا بتعريف الدالة
int main()
{
// الموجودة فيه what() حتى نتمكن من استدعاء الدالة myExcep إسمه MyException هنا قمنا بإنشاء كائن من الكلاس
MyException myExcep;
// الموجودة فيه بشكل تلقائي what() و الذي بدوره سيقوم باستدعاء الدالة myExcep هنا قمنا برمي إستثناء نوعه
try
{
throw myExcep;
}
// الموجودة فيه what() سيتم إستدعاء الدالة MyException هنا قلنا أنه في حال كان الإستثناء الذي تم رميه نوعه
catch(MyException& e)
{
cout << e.what();
}
// و هنا نقصد من أي نوع كان exception هنا قلنا أنه في حال كان الإستثناء الذي تم رميه نوعه
catch(exception& e)
{
// هنا يمكنك كتابة ماذا نريد أن نفعل في حال حدوث أي إستثناء آخر
}
cout << "The program is still working properly :)";
}
•سنحصل على النتيجة التالية عند التشغيل.
My Exception is thrown! The program is still working properly :)
C++ التعامل مع الملفات
معالجة الملفات في C++
التعامل مع الملفات أو معالجة الملفات ( Files Handling ) يقصد منها إجراء عملية ما على الملفات الموجودة في حاسوب المستخدم كقراءة محتوى ملف و عرضه في البرنامج, إنشاء نسخة منه, تعديل محتواه أو حذفه, سواء كان نوع الملف txt, jpg, mp4 أو أي نوع آخر.
الآن, للتعامل مع الملفات يجب تضمين الحزمة <fstream> لأنها تحتوي على الكلاسات المخصصة لذلك, بالإضافة للحزمة <iostream> لأننا سنحتاج منها العامل << عند الكتابة في الملف.
إذاً يجب كتابة هذين السطرين عند التعامل مع الملفات.
#include <iostream> #include <fstream>
مصطلحات تقنية
إسم الحزمة
<fstream>مشتق من جملة File Stream و التي تعني أنها مخصصة للتعامل مع الملفات.إسم الحزمة
<iostream>مشتق من جملة Input Output Stream و التي تعني أنها تحتوي على أوامر الإدخال و الإجراج سواء على الشاشة أو في الملفات.
كلاسات الحزمة fstream في C++
الحزمة <fstream> تحتوي على الكلاسات الأساسية التالية التي يمكن استخدامها للتعامل مع الملفات.
| الكلاس مع تعريفه | |
|---|---|
ofstream
يستخدم لإنشاء كائن يتيح لنا إمكانية إنشاء ملف جديد و الكتابة فيه. |
|
ifstream
يستخدم لإنشاء كائن يتيح لنا إمكانية قراءة محتوى الملف. |
|
fstream
يستخدم لإنشاء كائن يتيح لنا إمكانية إنشاء ملف جديد, الكتابة فيه و القراءة منه أيضاً.إذاً هذا الكلاس يعتبر دمج للكلاس ofstream و الكلاس ifstream. |
|
طريقة فتح و إغلاق ملف في C++
إذا أردت قراءة محتوى ملف أو الكتابة فيه فلا بد من أن يكون مفتوحاً من قبل برنامجك نفسه حتى تتمكن من ذلك.
الكلاسات الثلاثة ifstream و ofstream و fstream جميعها تحتوي على دالة إسمها open() نستخدمها لنفتح الملف الذي نريد التعامل معه.
بناء الدالة open()
void open(const char *filename, ios::openmode mode)
مكان الباراميتر
filenameنمرر إسم و مسار الملف الذي نريد فتحه كنص عادي.modeهو باراميتر إختياري يمكننا أن نمرر مكانه ثابت أو أكثر من الثوابت الجاهزة في الكلاسiosحتى نحدد للمترجم لماذا نريد فتح الملف.
في الجدول التالي وضعنا أسماء ثوابت الكلاس ios التي يمكنك تمريرها مكان البارميتر mode.
| الثابت مع تعريفه | |
|---|---|
ios::app
يستخدم لإعلام المترجم بأن المحتوى الجديد الذي سيتم إضافته سيوضع في آخر الملف. |
|
ios::ate
يستخدم لإعلام المترجم بأن سيتم فتح الملف بهدف الكتابة و القراءة منه مع الإشارة إلى أنه سيبدأ من آخره. |
|
ios::in
يستخدم لإعلام المترجم بأنه سيتم فتح الملف بهدف القراءة منه. |
|
ios::out
يستخدم لإعلام المترجم بأنه سيتم فتح الملف بهدف الكتابة فيه. |
|
ios::trunc
يستخدم لإعلام المترجم بأنه في حال كان الملف موجود مسبقاً, سيتم مسح محتواه عند فتحه. |
|
عند استدعاء الدالة open() يمكنك استخدام العامل | في حال أردت أن تمرر لها أكثر من قيمة مكان البارميتر mode كالتالي.
ofstream myfile;
myfile.open ("example.txt", ios::out | ios::app);
معلومة تقنية
الكلاس
ofstreamيستخدم الثابتios::outبشكل إفتراضي.الكلاس
ifstreamيستخدم الثابتios::inبشكل إفتراضي.الكلاس
fstreamيستخدم الثابتينios::in | ios::outبشكل إفتراضي.
أهمية إغلاق الملف عند الإنتهاء منه
عند الإنتهاء من التعامل مع أي ملف, قم بإغلاقه على الفور لأن ذلك من شأنه تحسين أداء البرنامج حيث سيخفف من حجم المساحة المحجوزة للملف في الذاكرة بالإضافة إلى أنك تصبح قادر على التعامل مع هذا الملف بشكل مباشر من خارج برنامجك.
الكلاسات الثلاثة ifstream و ofstream و fstream جميعها تحتوي على دالة إسمها close() نستخدمها لإغلاق الملف.
إذاً لإغلاق الإتصال مع أي ملف مفتوح, يجب أن تستدعي الدالة close() من الكائن الذي بالأساس فتحت الملف من خلاله.
التشييك على حالة الكائن الذي نتعامل من خلاله مع الملف في C++
عند استخدام الدالة open() لفتح الملف سواء بهدف القراءة أو الكتابة فيه فإن ذلك قد لا ينجح دائماً.
فمثلاً إذا كنت تريد إنشاء ملف جديد, قد تكون لا تملك صلاحية لإنشاء ملف في الحاسوب أو لا يوجد مساحة كافية لإنشاء الملف فيها, أو أن الملف موجود أصلاً و لكنه مفتوح من قبل برنامج آخر. و في حال كنت تريد قراءة محتوى ملف موجود في الحاسوب قد تواجه أيضاً بعض المشاكل, كأن يكون مسار الملف الموضوع غير صحيح, أو أن الملف مفتوح من قبل برنامج آخر أو أنك لا تملك صلاحية للقراءة منه إلخ..
بعد إنشاء الكائن الذي ستتعامل من خلاله مع الملف, يمكنك استخدام الجمل الشرطية if و else بكل سهولة كالتالي لمعرفة ما إن كان يمكنك التعامل مع الملف أم لا.
ofstream myfile;
myfile.open ("example.txt");
if (myfile)
{
// إذا كان الإتصال بالملف لا يوجد فيه مشاكل, سيتم تنفيذ الأوامر التي نضعها هنا
}
else
{
// إذا أردت إعلام المستخدم بأنه حدث مشكلة أثناء الإتصال بالملف, فيمكنك كتابة ذلك هنا
}
هناك 4 دوال جاهزة يمكنك استخدامها للتأكد من أن الإتصال بالملف سليم و أنه لم تحدث أي مشكلة عند التعامل معه سواء عند القراءة أو الكتابة فيه.
| إسم الدالة مع تعريفها | |
|---|---|
bool bad()
تستخدم لمعرفة ما إن حصلت أي مشكلة عند القراءة أو الكتابة في الملف.ترجع true إذا حدثت مشكلة و ترجع false إذا لم تحدث أي مشكلة.من المشاكل التي نقصدها كأن تحاول الكتابة في الملف و لكنه لا يوجد مساحة كافية للتخزين, أو في حال فتحت ملف بواسطة كائن من ofstream و لكنك كنت تنوي استخدامه للقراءة و ليس للكتابة. في هذه الحالات يمكنك الإستفادة من هذه الدالة لمعرفة ما إن حدث خطأ أم لا. |
|
bool fail()
مثل الدالة bad() تماماً بالإضافة إلى أنها تشيك على المشاكل التي قد تحدث عند التعامل مع محتوى الملف.على سبيل المثال, إذا قمت بقراءة عدد مخزن في الملف و قمت بقراءته في برنامجك و من ثم التعامل معه كأنه عدد عادي بدون أن تحوله لعدد ستجد أنها تنبهك عن هذا الخطأ أيضاً. ترجع true إذا حدثت مشكلة و ترجع false إذا لم تحدث أي مشكلة. |
|
bool eof()
إسم الدالة هو اختصار لجملة End Of File و هي تستخدم لمعرفة ما إن وصلت في القراءة أو الكتابة إلى آخر الملف أم لا.ترجع true إذا كان المترجم وصل لنهاية الملف و ترجع false إذا يصل بعد لنهايته. |
|
bool good()
تستخدم لمعرفة ما إن حصلت أي مشكلة كانت عند التعامل مع الملف و هي تشمل كل أنواع المشاكل التي قد تحدث.ترجع true إذا لم تحدث أي مشكلة و ترجع false إذا حدثت مشكلة ما. |
|
الكلاسات الثلاثة ifstream و ofstream و fstream جميعها تحتوي على هذه الدوال و ستجد كيفية استخدامها لاحقاً في الأمثلة.
أمثلة شاملة على التعامل مع الملفات في C++
المثال الأول
هنا وضعنا مثال حول كيفية إنشاء ملف جديد في الحاسوب و الكتابة فيه بواسطة كائن من الكلاس ofstream.
في المثال التالي قمنا بإنشاء ملف نصي جديد إسمه demo.txt في نفس المشروع الذي نعمل فيه.
بعدها قمنا بالتأكد من أنه قد تم إنشاء الملف بنجاح و من ثم كتابة سطرين بداخله.
مثال
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
// لأننا سنستخدمه لإنشاء ملف جديد و الكتابة فيه outfile إسمه ofstream هنا قمنا بإنشاء كائن من الكلاس
ofstream outfile;
// و فتحه "demo.txt" لإنشاء ملف جديد إسمه open() هنا قمنا باستدعاء الدالة
outfile.open("demo.txt");
// لا يوجد فيه أي مشاكل قبل التعامل معه outfile هنا قمنا بالتأكد من أن الإتصال بالملف الذي يشير له الكائن
if(outfile)
{
// outfile هنا قمنا بإضافة نص في الملف الذي يشير إليه الكائن
outfile << "This is a line.\n";
outfile << "This is another line.\n";
}
// لإغلاق الإتصال مع الملف المفتوح في الذاكرة outfile من الكائن close() هنا قمنا باستدعاء الدالة
outfile.close();
return 0;
}
•عند تشغيل البرنامج سيتم إنشاء ملف إسمه demo.txt في نفس المشروع الذي نعمل فيه و بداخله النص التالي.
This is a line. This is another line.
•بعد أن قمنا بتشغيل البرنامج, قمنا بفتح مجلد المشروع للتأكد من أن الملف demo.txt قم تم إنشاؤه فعلاً فيه.
المثال الثاني
هنا وضعنا مثال حول كيفية قراءة محتوى ملف موجود في الحاسوب و عرضه في البرنامج بواسطة كائن من الكلاس ifstream.
ملاحظة: سنقوم بقراءة محتوى الملف الذي أنشأناه في المثال الأول.
في المثال التالي قمنا بقراءة محتوى الملف demo.txt الذي أنشأناه في المثال الأول في نفس المشروع الذي نعمل فيه.
بعدها قمنا بعرض محتوى الملف في البرنامج, أي في الكونسول.
مثال
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
// لأننا سنستخدمه لقراءة محتوى ملف موجود في الحاسوب infile إسمه ifstream هنا قمنا بإنشاء كائن من الكلاس
ifstream infile;
// "demo.txt" لفتح الملف open() هنا قمنا باستدعاء الدالة
infile.open("demo.txt");
// لا يوجد فيه أي مشاكل قبل التعامل معه infile هنا قمنا بالتأكد من أن الإتصال بالملف الذي يشير له الكائن
if(infile)
{
// سنستخدم هذا المتغير لتخزين كل سطر جديد نقوم بجلبه من الملف فيه بشكل مؤقت قبل عرضه
string line;
// line تجد سطر جديد, ستقوم بوضعه بشكل مؤقت في المتغير getline() طالما أن الدالة while هنا في كل دورة من دورات الحلقة
while (getline (infile, line))
{
// بعدها سنقوم بعرضه و النزول على سطر جديد حتى لا يظهر كل محتوى الملف على سطر واحد
cout << line << endl;
}
}
// لإغلاق الإتصال مع الملف المفتوح في الذاكرة infile من الكائن close() هنا قمنا باستدعاء الدالة
infile.close();
return 0;
}
•عند تشغيل البرنامج سيتم عرض محتوى الملف demo.txt كالتالي.
This is a line. This is another line.
المثال الثالث
هنا وضعنا مثال حول كيفية إنشاء ملف و الكتابة فيه و من ثم قراءة محتواه بواسطة كائن من الكلاس fstream.
في المثال التالي قمنا بإنشاء ملف نصي جديد إسمه demo.txt في نفس المشروع الذي نعمل فيه.
بعدها قمنا بالتأكد من أنه قد تم إنشاء الملف بنجاح و من ثم كتابة سطرين بداخله.
بعدها قمنا بالرجوع لبداية الملف باستخدام الدالة seekg() مع تمرير القيمة 0 لها للإشارة إلى أول حرف موجود في الملف.
بعدها قمنا بقراءة النص الذي أضفناه في الملف منه و عرضه في البرنامج, أي في الكونسول.
مثال
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
// لأننا سنستخدمه لإنشاء ملف جديد و الكتابة فيه file إسمه fstream هنا قمنا بإنشاء كائن من الكلاس
fstream file;
// و فتحه "demo.txt" لإنشاء ملف جديد إسمه open() هنا قمنا باستدعاء الدالة
file.open("demo.txt");
// لا يوجد فيه أي مشاكل قبل التعامل معه file هنا قمنا بالتأكد من أن الإتصال بالملف الذي يشير له الكائن
if(file)
{
// file هنا قمنا بإضافة نص في الملف الذي يشير إليه الكائن
file << "This is a line.\n";
file << "This is another line.\n";
// هنا قمنا بالرجوع لأول الملف لأننا سنقوم بقراءة محتواه من البداية
file.seekg(0);
// سنستخدم هذا المتغير لتخزين كل سطر جديد نقوم بجلبه من الملف فيه بشكل مؤقت قبل عرضه
string line;
// line تجد سطر جديد, ستقوم بوضعه بشكل مؤقت في المتغير getline() طالما أن الدالة while هنا في كل دورة من دورات الحلقة
while (getline (file, line))
{
// بعدها سنقوم بعرضه و النزول على سطر جديد حتى لا يظهر كل محتوى الملف على سطر واحد
cout << line << endl;
}
}
// لإغلاق الإتصال مع الملف المفتوح في الذاكرة file من الكائن close() هنا قمنا باستدعاء الدالة
file.close();
return 0;
}
•عند تشغيل البرنامج سيتم عرض محتوى الملف demo.txt كالتالي.
This is a line. This is another line.
المثال الرابع
هنا وضعنا مثال حول كيفية إضافة نص في آخر النص الموجود في الملف بواسطة كائن من الكلاس ofstream و الثابت ios::app.
ملاحظة: عند محاولة الإتصال بالملف لن يتم إنشاؤه من جديد في حال كان في الأساس موجوداً.
في المثال التالي قمنا بتمرير الثابت ios::app للدالة open() لإعلام المترجم بأننا نريد فتح ملف إسمه append.txt موجود في نفس المشروع الذي نعمل فيه بهدف إضافة نص في آخره و في حال لم يكن موجوداً فإننا نريد إنشاؤه و فتحه أيضاً لذات الهدف.
بعدها قمنا بالتأكد من أن الملف موجود و لا يوجد أي مشكلة في الإتصال به, و من ثم إضافة سطر على المحتوى الموجود فيه.
مثال
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
// لأننا سنستخدمه لإنشاء ملف جديد و الكتابة فيه outfile إسمه ofstream هنا قمنا بإنشاء كائن من الكلاس
ofstream outfile;
// للدالة ios::app و في حال لم يكن موجوداً سيتم إنشاؤه و فتحه لأننا مررنا الثابت "append.txt" لفتح ملف إسمه open() هنا قمنا باستدعاء الدالة
outfile.open("append.txt", ios::app);
// لا يوجد فيه أي مشاكل قبل التعامل معه outfile هنا قمنا بالتأكد من أن الإتصال بالملف الذي يشير له الكائن
if(outfile)
{
// outfile هنا قمنا بإضافة نص في الملف الذي يشير إليه الكائن
outfile << "This is a new line added at the end.\n";
}
// لإغلاق الإتصال مع الملف المفتوح في الذاكرة outfile من الكائن close() هنا قمنا باستدعاء الدالة
outfile.close();
return 0;
}
•عند تشغيل البرنامج سيتم إنشاء ملف إسمه append.txt في نفس المشروع الذي نعمل فيه و بداخله النص التالي.
This is a new line added at the end.
•قم بإغلاق الملف append.txt إذا كنت قد فتحته, ثم قم بتشغيل البرنامج مرة ثانية و لاحظ كيف سيتم إضافة النص "This is a new line added at the end." من جديد في آخره كالتالي.
This is a new line added at the end. This is a new line added at the end.
ننصحك بإغلاق الملف append.txt و تغيير النص الذي وضعناه في السطر 18 لأي نص تريد و من ثم تشغيل البرنامج لملاحظة كيف سيتم إضافة النص الذي كتبته أنت في آخر الملف.
المثال الخامس
هنا وضعنا مثال حول كيفية إضافة نص في أول النص الموجود في الملف بواسطة كائن من الكلاس ofstream و الثابت ios::app.
ملاحظة: عند محاولة الإتصال بالملف لن يتم إنشاؤه من جديد في حال كان في الأساس موجوداً.
لإضافة نص في أول الملف بدون حذف باقي المحتوى الموجود فيه, يجب أن نقرأ محتوى الملف و نخزّنه بشكل مؤقت في متغير نصي.
بعدها نقوم بحذف الملف الأصلي.
بعدها نقوم بإنشاء ملف جديد فارغ بنفس إسم الملف الأصلي.
في الأخير نقوم بإضافة النص الجديد في الملف الجديد و يليه النص الذي قمنا بتخزينه في المتغير.
يمكنك فعل هذا الأمر بطريقة أخرى إن أردت حيث أنك تستطيع إنشاء ملف جديد تضع فيه النص الذي تريد إضافته في الملف.
بعدها تقرأ محتوى الملف الأصلي و تضيفه فيه.
في المثال التالي قمنا بتطبيق الطريقة الأولى التي يمكن استخدامها لإضافة نص في أول الملف.
لأجل هذا الأمر قمنا بإنشاء كائن من الكلاس ifstream لقراءة محتوى الملف الأصلي الذي نريد إضافة نص في أوله.
و كائن من الكلاس ofstream لإنشاء ملف جديد بنفس الإسم و إضافة النص الجديد فيه و يليه النص الذي كان موجوداً في الملف الأصلي.
مثال
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
// لأننا سنتخدمه لقراءة محتوى ملف موجود في الحاسوب infile إسمه ifstream هنا قمنا بإنشاء كائن من الكلاس
// لأننا سنتخدمه للكتابة في نفس الملف السابق outfile إسمه ofstream و قمنا بإنشاء كائن من الكلاس
// سنضع فيه النص الذي نقرؤه من الملف بشكل مؤقت data المتغير
// وضعنا فيه النص الذي ننوي إضافته في بداية الملف على سطر خاص textToAdd المتغير
ifstream infile;
ofstream outfile;
string data;
string textToAdd = "This is a new line added at the begining.\n";
// "prepend.txt" لفتح ملف إسمه open() هنا قمنا باستدعاء الدالة
infile.open("prepend.txt");
// لا يوجد فيه أي مشاكل قبل التعامل معه infile هنا قمنا بالتأكد من أن الإتصال بالملف الذي يشير له الكائن
if(infile)
{
// سنستخدم هذا المتغير لتخزين كل سطر جديد نقوم بجلبه من الملف فيه بشكل مؤقت قبل عرضه
string line;
// line تجد سطر جديد, ستقوم بوضعه بشكل مؤقت في المتغير getline() طالما أن الدالة while هنا في كل دورة من دورات الحلقة
while (getline (infile, line))
{
// data في المتغير line بعدها سيتم إضافة السطر الذي تم وضعه بشكل مؤقت في المتغير
data += line + "\n";
}
// data عند انتهاء الحلقة سيكون كل محتوى الملف قد تم وضعه في المتغير
}
// لإغلاق الإتصال مع الملف المفتوح في الذاكرة infile من الكائن close() هنا قمنا باستدعاء الدالة
infile.close();
// بدل الملف القديم و فتحه أيضاً"prepend.txt" لإنشاء ملف جديد إسمه open() هنا قمنا باستدعاء الدالة
outfile.open("prepend.txt");
// لا يوجد فيه أي مشاكل قبل التعامل معه outfile هنا قمنا بالتأكد من أن الإتصال بالملف الذي يشير له الكائن
if(outfile)
{
// outfile في الملف الجديد الذي يشير إليه الكائن textToAdd هنا قمنا بإضافة النص الموجود في المتغير
outfile << textToAdd;
// أيضاً outfile الذي قمنا بنسخه من الملف الأصلي في الملف الذي يشير إليه الكائن data بعدها قمنا بإضافة النص الموجود في المتغير
outfile << data;
}
// لإغلاق الإتصال مع الملف المفتوح في الذاكرة outfile من الكائن close() هنا قمنا باستدعاء الدالة
outfile.close();
return 0;
}
•عند تشغيل البرنامج سيتم إنشاء ملف إسمه prepend.txt في نفس المشروع الذي نعمل فيه و بداخله النص التالي.
This is a new line added at the begining.
•قم بإغلاق الملف prepend.txt إذا كنت قد فتحته, ثم قم بتشغيل البرنامج مرة ثانية و لاحظ كيف سيتم إضافة النص "This is a new line added at the begining." من جديد في أوله كالتالي.
This is a new line added at the begining. This is a new line added at the begining.
ننصحك بإغلاق الملف prepend.txt و تغيير النص الذي وضعناه في المتغير textToAdd لأي نص تريد و من ثم تشغيل البرنامج لملاحظة كيف سيتم إضافة النص الذي كتبته أنت في أول الملف.
المثال السادس
هنا وضعنا مثال حول كيفية حذف ملف من الحاسوب باستخدام الدالة remove() بالإضافة إلى كيفية طباعة الأخطاء التي قد تحدث عند محاولة حذف الملف.
الدالة remove()
لحذف ملف من الحاسوب نستخدم دالة جاهزة إسمها remove() موجودة في الأساس في الحزمة <iostream> و هي معرفة كالتالي.
int remove(const char* filename)
إذاً عند استدعاءها يجب أن نمرر لها إسم أو مسار الملف الذي نريد حذفه كمصفوفة أحرف أو كمؤشر لها فترجع عدد أكبر من 0 إذا قامت بحذف الملف بنجاح و إن فشلت في حذف الملف لأي سبب كان فإنها ترجع 0.
إذاً في حال قامت الدالة remove() بإرجاع القيمة 0 فهذا دليل على أنها لم تستطع حذف الملف.
في المثال التالي حاولنا حذف ملف إسمه demo.txt إفترضنا أنه موجود في نفس المشروع الذي نعمل فيه.
بعدها قمنا بطباعة ما إن كان الملف قد تم حذفه أم لا.
مثال
#include <iostream>
using namespace std;
int main()
{
// fileName هنا قمنا بتخزين إسم الملف الذي نريد حذفه في مصفوفة الأحرف
char fileName[] = "demo.txt";
// fileName لمحاولة حذف الملف الموجود إسمه في المصفوفة remove() هنا قمنا باستدعاء الدالة
// لمعرفة ما إن كانت قد حذفت الملف أم لا remove() ثم قمنا بالتشييك على القيمة التي سترجعها الدالة
if (remove(fileName) != 0)
{
// في حال لم يتم حذف الملف سيتم طباعة الجملة التالية, ثم نطقتين فوق بعضهما, ثم سبب الخطأ الذي حدث
perror("File deletion failed");
}
else
{
// في حال لم تم حذف الملف بنجاح سيتم تنفيذ أمر الطباعة التالي الذي يعني أنه تم حذف الملف بنجاح
cout << "File deleted successfully";
}
return 0;
}
•عند تشغيل البرنامج, إذا كان يوجد في مشروعك ملف إسمه demo.txt سيتم حذفه و طباعة الجملة التالية.
File deleted successfully
•عند تشغيل البرنامج, إذا كان لا يوجد في مشروعك ملف إسمه demo.txt سيتم طباعة الجملة التالية.
File deletion failed: No such file or directory
المثال السابع
هنا وضعنا مثال حول كيفية استخدام الدوال bad() و fail() و eof() و good() للتشييك على حالة الكائن المستخدم للتعامل مع الملف.
في المثال التالي قمنا بإنشاء كائن من الكلاس ifstream و من ثم حاولنا استخدامه لقراءة محتوى ملف إسمه demo.txt.
إذاً هنا قد تحدث مشكلة في حال لم يكن هناك ملف في المشروع إسمه demo.txt و ستحدث مشكلة حتماً عند محاولة قراءة محتوى الملف لأن الكلاس ifstream مصمم للكتابة في الملف و ليس القراءة منه.
بعدها قمنا بالتشييك على حالة الكائن لمعرفة ما إن حدثت مشكلة عند التعامل مع الملف أم لا.
مثال
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
// لأننا سنستخدمه لقراءة محتوى ملف موجود في الحاسوب infile إسمه ifstream هنا قمنا بإنشاء كائن من الكلاس
ifstream infile;
// مع العلم أنه لا يوجد ملف بهذا الإسم في المشروع "harmash.txt" لفتح ملف إسمه open() هنا قمنا باستدعاء الدالة
infile.open("harmash.txt");
// هنا سيتم طباعة ما إن كنا قد فتحنا الملف و قرأنا الأحرف الموجودة فيه وصولاً لآخر حرف أم لا
if (infile.eof())
{
cout << "You reach the end of file.\n";
}
else
{
cout << "You didn't reach the end of file.\n";
}
// هنا سيتم طباعة أي مشكلة حدثت عند محاولة التعامل مع الملف
if (infile.good())
{
cout << "No problem happed till now.";
}
else
{
// ثم نطقتين فوق بعضهما, ثم سبب الخطأ الذي حدث "Error" سيتم طباعة كلمة
perror("Error");
}
// لإغلاق الإتصال مع الملف في حال كان قد تم فتحه أصلاً infile من الكائن close() هنا قمنا باستدعاء الدالة
infile.close();
return 0;
}
•سنحصل على النتيجة التالية عند التشغيل.
You didn't reach the end of file. Error: No such file or directory
المثال الثامن
هنا وضعنا مثال حول كيفية معرفة حجم الملف مهما كان نوعه.
لمعرفة حجم الملف مهما كان نوعه تحتاج التالي:
إجمالاً Index آخر حرف في الملف يساوي حجم الملف.
لمعرفة Index الحرف الذي يقف عنده المترجم حالياً نستخدم دالة جاهزة إسمها
tellg().للإنتقال من أول حرف في الملف لآخر حرف فيه نستخدم الدالة
seekg(0, ios::end).
في المثال التالي قمنا بإنشاء كائن من الكلاس ifstream و من ثم حاولنا استخدامه لقراءة محتوى ملف إسمه demo.txt.
بعد التأكد من أن الملف مفتوح, قمنا بتخزين Index أول حرف في الملف في متغير إسمه begin و آخر حرف في الملف في متغير إسمه end لأننا من خلال طرحهما من بعضهما سنعرف حجم الملف.
مثال
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
// آخر حرف في الملف index لتخزين end سنستخدم المتغير
streampos end;
// لأننا سنستخدمه لقراءة محتوى ملف موجود في الحاسوب myFile إسمه ifstream هنا قمنا بإنشاء كائن من الكلاس
ifstream myFile;
// إفترضنا أنه موجود في المشروع "demo.txt" لفتح ملف إسمه open() هنا قمنا باستدعاء الدالة
myFile.open("demo.txt");
// لا يوجد فيه أي مشاكل قبل التعامل معه myFile هنا قمنا بالتأكد من أن الإتصال بالملف الذي يشير له الكائن
if(myFile)
{
// لها لجعل المترجم يتوجه لآخر حرف موجود في الملف ios::end و تمرير الثابت seekg() هنا قمنا باستدعاء الدالة
myFile.seekg(0, ios::end);
// end الحرف الحالي الذي يقف عنده المترجم في الذاكرة و من ثم تخزينه في المتغير index لتخزين tellg() هنا قمنا باستدعاء الدالة
end = myFile.tellg();
// آخر حرف في الملف و الناتج سيكون حجم الملف index هنا قمنا بطباعة
cout << "Size is: " << end << " bytes.";
}
else
{
// ثم نطقتين فوق بعضهما, ثم سبب الخطأ الذي حدث "Error" في حال وجود خطأ سيتم طباعة كلمة
perror("Error");
}
// لإغلاق الإتصال مع الملف في حال كان قد تم فتحه أصلاً myFile من الكائن close() هنا قمنا باستدعاء الدالة
myFile.close();
return 0;
}
•عند تشغيل البرنامج, إذا كان يوجد في مشروعك ملف إسمه demo.txt سيتم طباعة حجمه كالتالي مع الإشارة إلى أن حجم الملف سيكون بقدر حجم ملفك الحقيقي.
Size is: 95 bytes.
•عند تشغيل البرنامج, إذا كان لا يوجد في مشروعك ملف إسمه demo.txt سيتم طباعة الجملة التالية.
Error: No such file or directory
المثال التاسع
هنا وضعنا مثال حول كيفية إنشاء نسخة من ملف غير نصي (Binary File) مثل الملفات الصوتية و الفيديوهات إلخ..
ملاحظة: الملفات الغير نصية نتعامل معها بطريقة خاصة.
يوجد طرق خاصة للتعامل مع الملفات الغير نصية مما يجعل عملية التعامل معها أسرع و أكثر كفاءة.
إذاً لن نستخدم الرموز >> و << أو الدالة getline() عند التعامل مع ملفات غير نصية.
الآن, لقراءة محتوى ملف غير نصي نستخدم دالة جاهزة إسمها read() و للكتابة في ملف غير نصي نستخدم دالة جاهزة إسمها write().
الدالة
read()ترجع لك محتوى الملف كمصفوفة أحرف.الدالة
write()تمرر لها المحتوى الذي تريد كتابته في الملف كمصفوفة أحرف أيضاً.
بناء الدالتين read() و write():
char* write ( char* memory_block, int size ) char* read ( char* memory_block, int size )
في المثال التالي قمنا بإنشاء كائن من الكلاس ifstream لنقرأ محتوى ملف إسمه D:/files/logo.PNG و من ثم إنشاء نسخة منه بواسطة كائن من الكلاس ofstream إسمها logo-copy.PNG في نفس المجلد الذي يوجد فيه الملف الأصلي.
ملاحظة: نحن إفترضنا أنه على الحاسوب يوجد ملف إسمه logo.PNG في مجلد إسمه files في القرص D.
إذا كنت ستجرب الكود التالي لا تنسى وضع مسار الملف الذي تريد إنشاء نسخة منه بشكل صحيح نسبةً لك أنت.
مثال
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
// بشكل أساسي لأننا سنستخدمه لحجز مساحة في الذاكرة من أجل وضع المحتوى فيها size هنا قمنا بتجهيز المتغير
streampos size;
// عبارة عن مصفوفة و سنستخدمها لتخزين الأحرف التي نقرأها من الملف الذي نريد إنشاء نسخة منه memblock
char* memblock;
// مع تحديد أننا سنستخدمه لقراءة محتوى ملف غير نصي و جعل المترجم يقف عند آخر حرف موجود فيه عند التعامل معه بالإضافة إلى فتحه ifstream هنا قمنا بإنشاء كائن من الكلاس
ifstream infile ("D:/files/logo.PNG", ios::in|ios::binary);
// مع تحديد أننا سنستخدمه لإنشاء ملف غير نصي و الكتابة فيه بالإضافة إلى فتحه ofstream هنا قمنا بإنشاء كائن من الكلاس
ofstream outfile ("D:/files/logo.PNG", ios::binary|ios::out);
// هنا قمنا بالتأكد من أن كلا الملفين مفتوحين و يمكن التعامل معهما
if (infile.is_open() && outfile.is_open())
{
// لها لجعل المترجم يتوجه لآخر حرف موجود في الملف ios::end و تمرير الثابت seekg() هنا قمنا باستدعاء الدالة
infile.seekg(0, ios::end)
// size آخر حرف في الملف لأنه سيمثل حجمه في المتغير index هنا سيتم تخزين
size = infile.tellg();
// size في الذاكرة مع تحديد أن عدد الأحرف التي سنضعها فيها يساوي عدد الأحرف الموجودة في الملف و الذي قمنا بتخزينه في المتغير memblock هنا قمنا بتحديد بإنشاء المصفوفة
memblock = new char [size];
// هنا قمنا بالرجوع لأول حرف موجود في الملف الأصلي حتى نتمكن من قراءة كل محتواه
infile.seekg (0, ios::beg);
// memblock هنا قمنا بقراءة كل محتوى الملف و تخزينه في المصفوفة
infile.read (memblock, size);
// outfile في الملف الجديد الذي يمثله الكائن memblock هنا قمنا بكتابة كل محتوى المصفوفة
outfile.write(memblock, size);
// لإغلاق الإتصال مع الملفات المفتوحة في الذاكرة outfile و infile من الكائنين close() هنا قمنا باستدعاء الدالة
infile.close();
outfile.close();
}
else
{
// إذا حدث أي مشكلة أثناء فتح الملف الأول أو إنشاء و فتح الملف الثاني سيتم طباعة هذه الجملة
cout << "Operation failed!";
}
return 0;
}
•عند تشغيل البرنامج, سيتم إنشاء نسخة من الملف إن تحدث أي مشكلة و لن يتم طباعة أي شيء في البرنامج.
•عند تشغيل البرنامج, إذا كان يوجد مشكلة في مسارات الملفات الموضوعة أو أنه لا يمكن إنشاؤه لأي سبب كان سيتم طباعة الجملة التالية.
Operation failed!
المثال العاشر
هنا وضعنا مثال حول كيفية حفظ ما يدخله المستخدم بداخل ملف.
في المثال التالي قمنا بإنشاء ملف نصي جديد إسمه data.txt في نفس المشروع الذي نعمل فيه.
بعدها قمنا بالتأكد من أنه قد تم إنشاء الملف بنجاح.
بعدها طلبنا من المستخدم إدخال إسمه و من ثم قمنا بتخزين الإسم الذي يدخله بداخل الملف data.txt.
مثال
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
// لأننا سنستخدمه لإنشاء ملف جديد و الكتابة فيه outfile إسمه ofstream هنا قمنا بإنشاء كائن من الكلاس
ofstream outfile;
// لتخزين الإسم الذي سيدخله المستخدم عند التشغيل name قمنا بتجهيز المتغير
string name;
// و فتحه "data.txt" لإنشاء ملف جديد إسمه open() هنا قمنا باستدعاء الدالة
outfile.open("data.txt");
// لا يوجد فيه أي مشاكل قبل التعامل معه outfile هنا قمنا بالتأكد من أن الإتصال بالملف الذي يشير له الكائن
if(outfile)
{
// name هنا سيطلب من المستخدم إدخال إسمه الكامل و من ثم سيتم تخزين الإسم الذي يدخله في المتغير
cout << "Enter your name: ";
getline(cin, name);
// outfile هنا قمنا بإضافة نص في الملف الذي يشير إليه الكائن
outfile << name;
}
// لإغلاق الإتصال مع الملف المفتوح في الذاكرة outfile من الكائن close() هنا قمنا باستدعاء الدالة
outfile.close();
return 0;
}
•قمنا بتعليم البيانات التي إنتظرنا البرنامج لإدخالها من لوحة المفاتيح باللون الأصفر.
•قم بفتح الملف data.txt الذي تم إنشاؤه في نفس المشروع الذي تعمل فيه و ستجد أنه قد تم حفظ ما أدخلته فيه.
.....................................................................


