انتقل إلى المحتوى
Topos Uranos

ملخص

يختم هذا الدرس الوحدة السابعة من الدورة، المخصّصة للنصوص والملفات. فهو يعيد كتابة برنامج المقدمة بما تعلّمناه، وهو البرنامج الذي كان يفقد بيانات دون تنبيه ويقيس خطأً الأسماء المكتوبة بحروف خارج ASCII؛ ويعيد بناء سلسلة المفاهيم الممتدة من السلسلة النصية إلى تنسيق الملف؛ ويفصل ما يصحّ من ذلك في أي لغة عمّا يخصّ C++؛ ويجمع السلوك غير المعرَّف في الوحدة، مع ما يوقفه الكاشف وما لا يراه أحد؛ ويكرّر في تقييم الخروج مهارات تقييم الدخول؛ وينتهي بستة تحدّيات تجمع الوحدة كلها.

أهداف التعلم

  1. معالجة نص حقيقي مع ترميزه: التنبؤ بحجم السلسلة النصية بالبايتات، وعدّ حروفها، وتجنّب العمليات التي تُفسد UTF-8.
  2. قراءة المدخلات والملفات بإحكام، مع التمييز بين المعطى غير الصالح ونهاية البيانات والتحقق من صحة كل معطى بوصفه نصًّا كاملًا.
  3. إنتاج ملفات نتائج بتنسيق محدَّد، مع الإبلاغ عن كل سطر غير صالح برقمه.
  4. التعرّف على السلوك غير المعرَّف في الوحدة (العرض المعلَّق، والفهرس الخارج عن السلسلة النصية) وتمييزه من الأعطال المعرَّفة التي تمرّ هي الأخرى بصمت.
  5. الفصل، فيما تعلّمناه في الوحدة، بين ما يصحّ في أي لغة وما يخصّ C++.

ما حقّقته الوحدة

سعت الوحدة إلى تعليم التعامل مع النص بوصفه معطى، وإلى جعل البرنامج يقرأ الملفات ويكتبها دون أن ينهار أمام مدخلات مشوَّهة. وقد تحقّق المقصد بالاستناد إلى ما أعلنت المقدمة أنه معلوم فحسب: المحرف بوصفه رمزًا من الوحدة 2، والبتات من الوحدة 3، والمتجهات والسجلات من الوحدة 6، والدوال ذات العقد من الوحدة 5. وبرنامج قائمة الدرجات، الذي كان يقرأ بـ >> فيفقد ليلى حداد ويوسف ويعلن أن «سمير» أطول من «Carmen»، يُكتب الآن هكذا: يقرأ أسطر ملف، ويفصل الدرجة عند آخر مسافة، ويتحقق من صحتها بالدالة parseInt من درس قراءة المدخلات بإحكام، ويبلغ عن السطر غير الصالح عبر مجرى الأخطاء، ويقيس الأسماء بالمحارف، ويترك القائمة في ملف آخر. ولكي يكون المثال مكتفيًا بنفسه، يكتب البرنامج أولًا ملف البيانات، الذي يكون موجودًا من قبل في الواقع العملي.

برنامج بلغة C++
#include <charconv>#include <cstddef>#include <fstream>#include <iostream>#include <print>#include <string>#include <string_view>​// تحكم على نص: هل هو عدد صحيح بالضبط.// الشرط المسبق: لا شيء.// الشرط اللاحق: هو شرط الدرس «قراءة المدخلات بإحكام».bool parseInt(std::string_view text, int& value){    int result{};    const auto last{text.data() + text.size()};    const auto [stop, error]{std::from_chars(text.data(), last, result)};    if (error != std::errc{} || stop != last) {        return false;    }    value = result;    return true;}​// عدد محارف نص UTF-8.// الشرط المسبق: text نص UTF-8 صالح.// الشرط اللاحق: تعيد عدد المحارف (لا البايتات) في text.std::size_t letterCount(std::string_view text){    std::size_t count{0};    for (const char c : text) {        if ((c & 0xC0) != 0x80) {            ++count;        }    }    return count;}​int main(){    {        std::ofstream out{"grades.txt"};        std::println(out, "Carmen 65\nسمير 70\nليلى حداد 58\nيوسف ستة\nكريم 61");    }    std::ifstream in{"grades.txt"};    std::ofstream report{"report.txt"};    if (!in || !report) {        std::println(std::cerr, "تعذّر فتح الملفات");        return 1;    }    std::string line{};    std::string longest{};    int lineNumber{0};    while (std::getline(in, line)) {        ++lineNumber;        const std::size_t space{line.rfind(' ')};        int grade{};        if (space == std::string::npos || !parseInt(std::string_view{line}.substr(space + 1), grade)) {            std::println(std::cerr, "سطر غير صالح رقم {}: «{}»", lineNumber, line);            continue;        }        const std::string name{line.substr(0, space)};        std::println(report, "{:<10}{:>3}", name, grade);        if (letterCount(name) > letterCount(longest)) {            longest = name;        }    }    std::println("أطول اسم: {} ({} محارف)", longest, letterCount(longest));}
المخرجات
سطر غير صالح رقم 4: «يوسف ستة»أطول اسم: ليلى حداد (9 محارف)

لا تعرض الشاشة إلا رسالة الخطأ والخلاصة؛ أما القائمة ففي الملف، الذي يعرضه الأمر cat (وفي PowerShell، Get-Content) بعد تشغيل البرنامج:

الطرفية
$ cat report.txtCarmen     65سمير       70ليلى حداد  58كريم       61

كل جزء يجيب عن عيب من عيوب المقدمة. فالقراءة سطرًا سطرًا وrfind تسمحان بأسماء فيها مسافات؛ وnpos وparseInt ترفضان سطر يوسف، وتبلغان عنه برقمه، وتتركان كريمًا يمضي؛ وletterCount تعدّ المحارف، فتفوز «ليلى حداد»، بتسعة محارف وسبعة عشر بايتًا، عن جدارة؛ ويكتمل report.txt عند انتهاء main، حين يُغلق مجراه.

السلاسل النصية

قدّم درس السلاسل النصية النوع std::string بوصفه تسلسلًا من char مع عمليات على النص. وعلّم البحث بـ find، التي تعيد موضعًا أو npos، ومقارنة تلك القيمة دائمًا مقارنة صريحة، والاستخراج بـ substr، التي تشترط موضعًا صالحًا؛ وقدّم std::string_view، الآمن معاملًا والخطِر إن عاش أطول من النص الذي يراقبه، وسلاسل C، على سبيل المقابلة.

النص ليس ASCII

ميّز درس النص ليس ASCII بين المحرف ونقطة ترميزه وبايتاته، وشرح كيف يوزّع UTF-8 البتات على ما بين بايت واحد وأربعة بايتات. واستنتج من ذلك أن size تعدّ البايتات، وأن المحارف تُعدّ بـ (c & 0xC0) != 0x80، وأن العكس والتحويل إلى الأحرف الكبيرة والقطع بايتًا بايتًا تُفسد النص، وأن البحث والضمّ لا يفسدانه، وما الذي لا تفعله المكتبة القياسية.

قراءة المدخلات بإحكام

عامل درس قراءة المدخلات بإحكام المدخلات بوصفها حدًّا غير موثوق. وعلّم قراءة الأسطر بـ std::getline، والتمييز بـ fail() وeof() بين المعطى غير الصالح والعدد الذي لا يتّسع له النوع ونهاية البيانات، والاستدراك بـ clear وignore، والتحقق من صحة العدد بوصفه نصًّا بـ std::from_chars، مع اشتراط استهلاك النص كله؛ وختم بـ parseInt وreadInt، وهما دالتان عند الحدّ لهما عقد.

الملفات النصية

بيّن درس الملفات النصية أن الملف مجرى، له حالة std::cin نفسها. وجعل التحقق من الفتح قاعدة؛ وبرهن لماذا تعالج while (!in.eof()) معطى زائدًا؛ وعلّم الكتابة بـ std::println(out, …)، بالاستبدال أو بالإلحاق، والفصل بين الرسائل والنتائج بـ std::cerr؛ وردّ ذلك كله إلى تنسيق سطر محدَّد ومُتحقَّق من صحته.