การอ่านจาก ifstream จะไม่อ่านช่องว่าง

ฉันกำลังใช้ lexer แบบกำหนดเองใน C++ และเมื่อพยายามอ่านในช่องว่าง ifstream จะไม่อ่านออกมา ฉันกำลังอ่านอักขระทีละอักขระโดยใช้ >> และช่องว่างทั้งหมดก็หายไป มีวิธีใดบ้างที่จะทำให้ ifstream เก็บช่องว่างทั้งหมดและอ่านให้ฉันฟัง ฉันรู้ว่าเมื่ออ่านสตริงทั้งหมด การอ่านจะหยุดที่ช่องว่าง แต่ฉันหวังว่าการอ่านอักขระทีละอักขระ ฉันจะหลีกเลี่ยงพฤติกรรมนี้ได้

พยายามแล้ว: .get() แนะนำโดยหลายคำตอบ แต่มีผลเช่นเดียวกับ std::noskipws นั่นคือตอนนี้ฉันได้รับช่องว่างทั้งหมดแล้ว แต่ ไม่ใช่ อักขระขึ้นบรรทัดใหม่ที่ฉันต้องใช้เพื่อสร้างโครงสร้างบางส่วน

นี่คือรหัสที่ละเมิด (ความคิดเห็นเพิ่มเติมถูกตัดทอน)

while(input >> current) {
    always_next_struct val = always_next_struct(next);
    if (current == L' ' || current == L'\n' || current == L'\t' || current == L'\r') {
        continue;
    }
    if (current == L'/') {
        input >> current;
        if (current == L'/') {
            // explicitly empty while loop
            while(input.get(current) && current != L'\n');
            continue;
        }

ฉันกำลังทะลุบรรทัด while และดูทุกค่าของ current ที่เข้ามา และ \r หรือ \n ไม่ใช่ค่าเหล่านั้นอย่างแน่นอน - อินพุตจะข้ามไปยังบรรทัดถัดไปในไฟล์อินพุต


person Puppy    schedule 21.07.2011    source แหล่งที่มา
comment
หากคุณตัดบนบรรทัด while คุณคงคาดหวังว่าจะไม่เห็น \n ใน current เหมือนกับว่า get พบกับ \n คุณจะอยู่ในบรรทัดต่อไป ไม่ใช่บรรทัด while หรือผมเข้าใจผิด?   -  person CB Bailey    schedule 21.07.2011
comment
L'\n' คือ 16 บิต wchar_t ไม่ใช่ 8 บิต char แต่นั่นไม่น่าจะแตกต่างกัน   -  person René Richter    schedule 21.07.2011
comment
@Charles: จากนั้นมันจะหยุดแตกและไม่เริ่มแสดงเนื้อหาของบรรทัดถัดไปในไฟล์ @เรเน่: มันคือ wifstream   -  person Puppy    schedule 21.07.2011
comment
ใช้ input.get() สำหรับทั้งสามอินพุตใช่ไหม   -  person René Richter    schedule 21.07.2011
comment
@Rene: สองรายการแรกทำงานได้อย่างสมบูรณ์ตามที่คาดไว้ และไม่มีช่องว่างให้ข้ามไป   -  person Puppy    schedule 21.07.2011
comment
แล้วมันอาจจะเป็นตรรกะเหรอ? ดูคำตอบที่แก้ไขของฉัน   -  person René Richter    schedule 21.07.2011


คำตอบ (10)


มีเครื่องมือจัดการเพื่อปิดการใช้งานพฤติกรรมการข้ามช่องว่าง:

stream >> std::noskipws;
person R. Martinho Fernandes    schedule 21.07.2011
comment
ฉันได้ช่องว่างทั้งหมดแล้ว แต่ยังไม่มีการขึ้นบรรทัดใหม่ - person Puppy; 21.07.2011
comment
และคุณยังสามารถใช้ stream.unsetf(ios_base::skipws); เพื่อลบแฟล็กรูปแบบนั้นด้วยตนเอง - person sth; 21.07.2011
comment
@sth: นั่นคือสิ่งที่ noskipws ทำจริงๆ - person R. Martinho Fernandes; 21.07.2011

ตัวดำเนินการ >> กินช่องว่าง (เว้นวรรค แท็บ ขึ้นบรรทัดใหม่) ใช้ yourstream.get() เพื่ออ่านอักขระแต่ละตัว

แก้ไข:

ระวัง: แพลตฟอร์ม (Windows, Un*x, Mac) ต่างกันในการเขียนโค้ดขึ้นบรรทัดใหม่ อาจเป็น '\n', '\r' หรือทั้งสองอย่าง นอกจากนี้ยังขึ้นอยู่กับว่าคุณเปิดสตรีมไฟล์อย่างไร (ข้อความหรือไบนารี)

แก้ไข (วิเคราะห์โค้ด):

หลังจาก

  while(input.get(current) && current != L'\n');
  continue;

จะมี \n ใน current หากยังไม่ถึงจุดสิ้นสุดของไฟล์ หลังจากนั้นคุณจึงวนลูป while ที่อยู่นอกสุดต่อไป อักขระตัวแรกในบรรทัดถัดไปจะอ่านเป็น current นั่นไม่ใช่สิ่งที่คุณต้องการเหรอ?

ฉันพยายามทำให้เกิดปัญหาของคุณอีกครั้ง (โดยใช้ char และ cin แทน wchar_t และ wifstream):

//: get.cpp : compile, then run: get < get.cpp

#include <iostream>

int main()
{
  char c;

  while (std::cin.get(c))
  {
    if (c == '/') 
    { 
      char last = c; 
      if (std::cin.get(c) && c == '/')
      {
        // std::cout << "Read to EOL\n";
        while(std::cin.get(c) && c != '\n'); // this comment will be skipped
        // std::cout << "go to next line\n";
        std::cin.putback(c);
        continue;
      }
     else { std::cin.putback(c); c = last; }
    }
    std::cout << c;
  }
  return 0;
}

โปรแกรมนี้ใช้กับตัวมันเอง โดยกำจัดความคิดเห็นในบรรทัด C++ ทั้งหมดในเอาต์พุต การวนซ้ำ while ภายในไม่กินข้อความทั้งหมดจนถึงจุดสิ้นสุดของไฟล์ โปรดสังเกตคำสั่ง putback(c) หากปราศจากสิ่งนั้นบรรทัดใหม่ก็จะไม่ปรากฏขึ้น

ถ้ามันไม่ทำงานเหมือนกันสำหรับ wifstream มันจะมากแปลกยกเว้นด้วยเหตุผลหนึ่ง: เมื่อไฟล์ข้อความที่เปิดอยู่ ไม่ได้บันทึกเป็นถ่าน 16 บิต และ \n ถ่าน ลงเอยด้วยไบต์ผิด...

person René Richter    schedule 21.07.2011
comment
อย่ารับ \r เช่นกัน และฉันกำลังเปิดในโหมดข้อความบน Windows ซึ่งก็คือ CRLF - person Puppy; 21.07.2011
comment
@DeadMG: เมื่อฉันใช้ int c = std::cin.get(); ฉันได้รับ ASCII 10 บนเครื่อง Windows เมื่อฉันกด ENTER ดังนั้นจึงควรทำงานกับ ifstream ในลักษณะเดียวกัน - person René Richter; 21.07.2011
comment
นั่นไม่ใช่สิ่งที่เกิดขึ้น สิ่งที่เกิดขึ้นคือไฟล์ทั้งหมดถูกอ่านใน while loop และไม่สามารถออกได้ สิ่งที่คุณตั้งใจไว้ค่อนข้างจะเป็นสิ่งที่คุณพูด เมื่อพบจุดสิ้นสุดของบรรทัด การวนซ้ำจะสิ้นสุดลงและดำเนินต่อไปในลูปด้านนอก - person Puppy; 21.07.2011
comment
@DeadMG: แปลกมาก... ฉันก็เลยลองอีกครั้ง - person René Richter; 21.07.2011

คุณสามารถเปิดสตรีมในโหมดไบนารี:

std::wifstream stream(filename, std::ios::binary);

คุณจะสูญเสียการดำเนินการจัดรูปแบบใดๆ ที่ได้รับจากสตรีมของฉัน หากคุณทำเช่นนี้

อีกทางเลือกหนึ่งคืออ่านสตรีมทั้งหมดเป็นสตริงแล้วประมวลผลสตริง:

std::wostringstream ss;
ss << filestream.rdbuf();

แน่นอนว่าการรับสตริงจาก ostringstream จำเป็นต้องมีสำเนาของสตริงเพิ่มเติม ดังนั้นคุณจึงสามารถพิจารณาเปลี่ยนแปลงสิ่งนี้ได้ในบางจุดเพื่อใช้สตรีมแบบกำหนดเองหากคุณรู้สึกอยากผจญภัย แก้ไข: มีคนอื่นพูดถึง istreambuf_iterator ซึ่งอาจเป็นวิธีที่ดีกว่าการอ่านสตรีมทั้งหมดเป็นสตริง

person Pete    schedule 21.07.2011

ล้อมสตรีม (หรือบัฟเฟอร์โดยเฉพาะ) ด้วย std::streambuf_iterator? นั่นควรละเว้นการจัดรูปแบบทั้งหมด และยังให้อินเทอร์เฟซตัววนซ้ำที่ดีอีกด้วย

อีกทางหนึ่ง วิธีที่มีประสิทธิภาพมากกว่าและป้องกันการเข้าใจผิดอาจใช้ Win32 API (หรือ Boost) เพื่อแมปหน่วยความจำไฟล์ จากนั้นคุณสามารถสำรวจโดยใช้พอยน์เตอร์ธรรมดา และรับประกันว่าจะไม่มีการข้ามหรือแปลงอะไรเลยในรันไทม์

person jalf    schedule 21.07.2011
comment
ความคิดที่น่าสนใจ ฉันยังไม่เคยใช้คลาสตัววนซ้ำนั้นมาก่อน ฉันจะลองดู - person Puppy; 21.07.2011
comment
ฉันพบว่าตัววนซ้ำนี้เป็นวิธีเดียวที่สมเหตุสมผลในการทำงานกับ IOStreams หากคุณต้องการควบคุมสิ่งที่คุณกำลังทำและสิ่งที่เกิดขึ้น แน่นอนว่ายังคงช้าอยู่ ตามที่คุณคาดหวังไว้ว่าอะไรก็ตามที่รวม IOStreams (ช้า) กับ I/O ต่ออักขระ (ช้าเช่นกัน) จะเป็นเช่นนี้ แต่มันได้ผล! - person jalf; 21.07.2011

คุณสามารถรวมสตรีมไว้ใน std::streambuf_iterator เพื่อรับข้อมูลที่มีช่องว่างและบรรทัดใหม่เช่นนี้

           /*Open the stream in default mode.*/
            std::ifstream myfile("myfile.txt");

            if(myfile.good()) {
                /*Read data using streambuffer iterators.*/
    vector<char> buf((std::istreambuf_iterator<char>(myfile)), (std::istreambuf_iterator<char>()));

                /*str_buf holds all the data including whitespaces and newline .*/
                string str_buf(buf.begin(),buf.end());

                myfile.close();
            } 
person HaseeB Mir    schedule 22.07.2018
comment
+1 สำหรับการใช้ myfile.good() - ฉันคิดว่านั่นพิมพ์ผิด แต่เห็นว่า good() เป็นจริงเมื่อ good()=1 eof()=0 failed()=0 bad()=0 - ดูเหมือนว่า เหนือกว่าการตรวจสอบ eof() มาก - person Neil McGill; 03.06.2020
comment
BTW คุณยังสามารถทำ std::vector‹char› buf(std::istreambuf_iterator‹char›(myfile), {}); - ไม่แน่ใจว่าชัดเจนกว่านี้หรือไม่ afaik มันเรียกใช้ตัวสร้างเริ่มต้นดังนั้น {} อาจให้เบาะแสเพิ่มเติมเกี่ยวกับเรื่องนั้น - person Neil McGill; 03.06.2020

ตัวแยกกระแสข้อมูลทำงานเหมือนกันและข้ามช่องว่าง

หากคุณต้องการอ่านทุกไบต์ คุณสามารถใช้ฟังก์ชันอินพุตที่ไม่ได้ฟอร์แมตได้ เช่น stream.get(c)

person Bo Persson    schedule 21.07.2011
comment
ตามคำตอบของ @CharlesBailey: ฉันยังไม่ได้รับอักขระขึ้นบรรทัดใหม่ - person Puppy; 21.07.2011

ทำไมไม่ใช้เพียงแค่ getline ?

คุณจะได้รับช่องว่างทั้งหมด และถึงแม้คุณจะไม่ได้อักขระที่ท้ายบรรทัด แต่คุณก็ยังรู้ว่าพวกมันอยู่ที่ไหน :)

person Matthieu M.    schedule 21.07.2011

ตามค่าเริ่มต้น การตั้งค่าสถานะ skipws นี้ได้รับการตั้งค่าไว้แล้ว บนออบเจ็กต์ ifstream ดังนั้นเราต้อง ปิดใช้งาน วัตถุ ifstream มีการตั้งค่าสถานะเริ่มต้นเหล่านี้เนื่องจาก std::basic_ios::init เรียกใช้บนวัตถุ ios_base ใหม่ทุกรายการ (รายละเอียดเพิ่มเติม) รายการใดรายการหนึ่งต่อไปนี้จะได้ผล:

in_stream.unsetf(std::ios_base::skipws);
in_stream >> std::noskipws; // Using the extraction operator, same as below
std::noskipws(in_stream); // Explicitly calling noskipws instead of using operator>>

ธงอื่นๆ แสดงอยู่ใน การอ้างอิง cpp

person Ben Butterworth    schedule 16.10.2019

เพียงใช้ Getline

while (getline(input,current))
{
      cout<<current<<"\n";

}
person shawon    schedule 06.02.2019
comment
ไม่ใช่คำตอบที่ดี ... คุณอาจมีบรรทัดที่ไม่มี '\n' ต่อท้าย ... ในกรณีนั้นคุณจะเพิ่ม '\n' ถึงแม้ว่ามันจะไม่ได้อยู่ที่นั่นก็ตาม - person StyleZ; 03.04.2021

ฉันลงเอยด้วยการแคร็กเปิด Windows API และใช้มันเพื่ออ่านไฟล์ทั้งหมดลงในบัฟเฟอร์ก่อน จากนั้นจึงอ่านอักขระบัฟเฟอร์นั้นทีละอักขระ ขอบคุณเพื่อน.

person Puppy    schedule 21.07.2011