Menu
flag Ar iconالعربيةdown icon

getline في C++: قراءة سطر كامل وإصلاح تخطي الإدخال وقراءة الملفات

كيف تقرأ std::getline سطرًا كاملًا إلى std::string: استخدامها مع cin، ولماذا تُرجع نصًا فارغًا بعد cin >>، وحلّا cin.ignore وstd::ws، والمحددات المخصصة، وقراءة ملف سطرًا سطرًا حتى نهايته.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

قراءة سطر كامل

يقرأ cin >> text كلمة واحدة: يتوقف عند أول مسافة. لقراءة كل ما كتبه المستخدم حتى مفتاح Enter، بما في ذلك المسافات، استخدم std::getline من الترويسة <string>:

#include <iostream>
#include <string>

int main() {
    std::string name;
    std::cout << "Full name: ";
    std::getline(std::cin, name);
    std::cout << "Hello, " << name << "!\n";
}

كتابة Ada Lovelace تطبع Hello, Ada Lovelace!. أما مع std::cin >> name فسيُطبع Hello, Ada! وتبقى Lovelace منتظرة في الإدخال.

التوقيع هو:

std::istream& std::getline(std::istream& input, std::string& text);
std::istream& std::getline(std::istream& input, std::string& text, char delim);
  • input أي تدفق إدخال: std::cin، أو ملف مفتوح باستخدام std::ifstream، أو std::istringstream مبني من نص.
  • يُستبدل محتوى text (ولا يُضاف إليه) بالمحارف المقروءة.
  • delim هو المحرف الذي ينهي القراءة، وقيمته الافتراضية '\n'. يُزال المحدد من التدفق ولا يُخزَّن في text.
  • القيمة المُرجعة هي التدفق نفسه، وهذا ما يتيح لك اختباره داخل شرط.

لأن getline تعمل بالطريقة نفسها على كل تدفق إدخال، تقرأ الأمثلة القابلة للتشغيل في هذه الصفحة من std::istringstream يحمل نصًا ثابتًا. هذا يجعل المخرجات متوقعة دون أن تكتب شيئًا. وللقراءة من لوحة المفاتيح بدلًا من ذلك، استبدل متغير التدفق بـstd::cin.

المخرجات:

[Ada Lovelace]
[Alan Turing]

تُظهر الأقواس المربعة أن السطر الجديد ليس جزءًا من أي من النصين.

getline بعد cin >> تُرجع نصًا فارغًا

هذه أشهر مشكلات getline. تقرأ رقمًا باستخدام >>، ثم سطرًا باستخدام getline، فتبدو getline كأنها تُتخطى:

المخرجات:

age: 30
city: []

يقرأ input >> age المحرفين 30 ويتوقف عند أول محرف لا يمكن أن يكون جزءًا من رقم، وهو السطر الجديد. يبقى ذلك السطر الجديد في التدفق. ثم تقرأ getline حتى السطر الجديد التالي، فتجده فورًا وتُرجع نصًا فارغًا. ومع std::cin تكون النتيجة أن البرنامج لا يتوقف أبدًا لانتظار الإجابة الثانية.

هناك حلّان معياريان، وكلاهما يوضع بين >> وgetline.

الحل 1: التخلص من باقي السطر باستخدام ignore.

المخرجات:

age: 30
city: [Paris]

يتخلص ignore(n, '\n') من المحارف حتى يزيل سطرًا جديدًا أو n محرفًا، أيهما يأتي أولًا. وتمرير أكبر قيمة ممكنة من std::streamsize يعني "حتى السطر الجديد، مهما كان بعيدًا". ويزيل أيضًا أي شيء كتبه المستخدم بعد الرقم على السطر نفسه، مثل المسافات الزائدة في النهاية. ومع لوحة المفاتيح يكون السطر std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');، ويحتاج إلى #include <limits>.

أما std::cin.ignore() المجردة فتتخلص من محرف واحد بالضبط. تنجح عندما يلي السطر الجديد الرقم مباشرة، وتفشل بمجرد وجود مسافة قبله.

الحل 2: تخطي الفراغات في البداية باستخدام std::ws.

المخرجات:

age: 30
city: [Paris]

std::ws مُعدِّل (manipulator) يستهلك كل الفراغات (المسافات وعلامات الجدولة والأسطر الجديدة) في الموضع الحالي. يُرجع input >> std::ws التدفق، لذا يمكن تمريره مباشرة إلى getline. ويتخطى أيضًا الأسطر الفارغة والمسافات في البداية، وهذا مريح مع الأسماء والمدن، لكنه خطأ عندما تكون المسافات في البداية أو السطر الفارغ إدخالًا له معنى. في هذه الحالة استخدم ignore.

القراءة حتى نهاية الإدخال

تُرجع getline التدفق، والتدفق يتحول إلى false بمجرد أن تفشل قراءة. وعندما لا تبقى أسطر، تفشل getline، لذا فإن حلقة while حولها تقرأ كل سطر وتتوقف عند النهاية:

المخرجات:

1: [apples 3]
2: [bread 1]
3: []
4: [milk 2]
read 4 lines

تفصيلان من هذه المخرجات:

  • السطر الفارغ ما زال قراءة ناجحة. يعطي نصًا فارغًا وتستمر الحلقة. وحده الوصول إلى نهاية الإدخال يوقفها.
  • السطر الأخير لا يحتاج إلى سطر جديد في نهايته. لو انتهى النص بـmilk 2 دون \n، لأرجعته getline مع ذلك، ولفشل الاستدعاء التالي.

ومع std::cin تقرأ الحلقة نفسها حتى ينتهي الإدخال. ويحدث ذلك عندما يُمرَّر الإدخال من ملف (./program < data.txt)، أو عندما يضغط المستخدم Ctrl+D على macOS وLinux، أو Ctrl+Z ثم Enter على Windows.

تجنّب النمط while (!input.eof()) { std::getline(input, line); ... }. لا تصبح eof() صحيحة إلا بعد أن تصل قراءة ما إلى النهاية فعلًا، فيُنفَّذ جسم الحلقة مرة إضافية بقراءة فاشلة وبقيمة line قديمة. ضع استدعاء getline في الشرط بدلًا من ذلك.

قراءة ملف سطرًا سطرًا

تستخدم الملفات الحلقة نفسها. افتح الملف باستخدام std::ifstream من <fstream>، وتحقق من أنه فُتح، ثم اقرأ:

المخرجات:

1. Ada 92
2. Grace 88
3. Alan 95

يلتقط if (!file) الملف المفقود أو المسار الخطأ قبل الحلقة. وبدونه تقرأ الحلقة ببساطة صفر أسطر، فيبدو الأمر كملف فارغ لا كخطأ. ويُغلق الملف تلقائيًا عندما يخرج file من النطاق في نهاية main.

الملفات المحفوظة على Windows تنهي كل سطر بـ\r\n. وعندما يُقرأ مثل هذا الملف على Linux أو macOS، تزيل getline المحرف \n لكنها تُبقي \r، فينتهي كل سطر بمحرف إرجاع غير مرئي يُفسد مقارنات مثل line == "done". أزِله بعد القراءة:

if (!line.empty() && line.back() == '\r') {
    line.pop_back();
}

استخدام محدد مخصص

يغيّر الوسيط الثالث المحرف الذي ينهي كل قراءة. من الاستخدامات الشائعة تقسيم سطر واحد عند الفواصل. اقرأ السطر أولًا، ثم قسّمه عبر std::istringstream:

المخرجات:

fields: 3
[Ada Lovelace]
[36]
[London]

تُزال الفاصلة من التدفق ولا تُخزَّن، تمامًا كما يحدث مع السطر الجديد في الصيغة الافتراضية. لاحظ أن السطر الجديد مع المحدد المخصص يصبح محرفًا عاديًا. فإذا استدعيت std::getline(file, field, ',') مباشرة على ملف متعدد الأسطر، يعود الحقل الأخير من سطر والحقل الأول من السطر التالي ملتصقين معًا، كما في "London\nGrace Hopper". قراءة سطر واحد في كل مرة وتقسيم كل سطر على حدة تتجنب ذلك. تشرح صفحة تدفقات النصوص std::istringstream بتفصيل أكبر، بما في ذلك تحويل حقول مثل "36" إلى أرقام.

std::getline مقابل cin.getline

هناك أيضًا دالة عضو باسم مشابه:

char buffer[20];
std::cin.getline(buffer, 20);        // دالة عضو، مصفوفة char ثابتة الحجم

std::string text;
std::getline(std::cin, text);        // دالة حرة، std::string

تكتب cin.getline داخل مصفوفة char، وتخزّن size - 1 محرفًا على الأكثر إضافة إلى المحرف الختامي '\0'. والسطر الأطول يضع التدفق في حالة فشل، وتُتخطى كل قراءة لاحقة حتى تستدعي std::cin.clear(). أما std::getline مع std::string فليس فيها حد للطول تديره. استخدمها إلا إذا كنت تعمل مع واجهة برمجية تتطلب مصفوفة char.

أخطاء شائعة

  • مزج >> وgetline دون التخلص من السطر الجديد. أضف ignore(...) أو استخدم std::ws بينهما. تعرض صفحة الإدخال والإخراج المشكلة نفسها مع std::cin، وكيف تتعافى عندما تفشل قراءة رقمية.
  • استدعاء getline(std::cin, number) مع متغير int. لا تقرأ getline إلا إلى std::string. اقرأ السطر، ثم حوّله باستخدام std::stoi أو std::istringstream.
  • بناء الحلقة على eof(). اختبر استدعاء getline نفسه: while (std::getline(input, line)).
  • نسيان #include <string>. يُصرَّح عن std::getline الخاصة بالنصوص في <string>. بعض المترجمات تمررها عبر <iostream>، ثم يفشل الكود على مترجم آخر.
  • عدم التحقق من أن الملف فُتح. المسار الخطأ يجعل std::ifstream يفشل بصمت، وتُنفَّذ حلقة القراءة صفر مرات.

الأسئلة الشائعة

ماذا تفعل getline في C++؟

تقرأ std::getline(stream, text) المحارف من stream إلى النص text من نوع std::string حتى تصل إلى سطر جديد، ثم تزيل ذلك السطر الجديد من التدفق دون تخزينه. وعلى عكس cin >> text، تحتفظ بالمسافات، لذا تقرأ سطرًا كاملًا مثل اسم كامل أو جملة.

لماذا تُتخطى getline بعد cin >> في C++؟

يتوقف cin >> number عن القراءة عند السطر الجديد الذي كتبته بعد الرقم، ويترك ذلك السطر الجديد في مخزن الإدخال المؤقت. تجده getline التالية فورًا وتُرجع نصًا فارغًا. تخلّص منه أولًا باستخدام std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');، أو استدعِ std::getline(std::cin >> std::ws, text) لتخطي الفراغات في البداية.

كيف أستخدم getline مع محدد غير السطر الجديد؟

مرّر المحدد وسيطًا ثالثًا: std::getline(stream, field, ','). تقرأ حتى الفاصلة التالية، وتخزّن النص الذي قبلها، وتتخلص من الفاصلة. وتُعامل الأسطر الجديدة حينها كمحارف عادية، لذا يُدمج هذا عادةً مع std::stringstream مبني من سطر واحد.

كيف أقرأ ملفًا سطرًا سطرًا في C++؟

افتحه باستخدام std::ifstream file("data.txt"); واكتب حلقة while (std::getline(file, line)) { ... }. تُرجع getline التدفق نفسه، والتدفق يتحول إلى false عندما لا تبقى أسطر، فتتوقف الحلقة عند نهاية الملف.

ما الفرق بين cin.getline وgetline؟

std::getline(std::cin, text) دالة حرة تقرأ إلى std::string، وهو نص يتسع لأي سطر. أما std::cin.getline(buffer, size) فدالة عضو تقرأ إلى مصفوفة char ثابتة الحجم، وتفشل إذا كان السطر أطول من size - 1 محرفًا. استخدم std::getline مع std::string إلا إذا كان لديك سبب لاستخدام المصفوفات الخام.

رسم توضيحي للغات البرمجة في Coddy

تعلّم البرمجة مع Coddy

ابدأ الآن