ifstream으로 이진파일을 콘솔에 출력할때 파일이 한 줄만 읽히는 경우 어떻게 해야할까요?


문제점:

  1. 예를 들어 첫 줄에 1 john vocabook 3 1000 그리고 그 다음줄에 2 mike readingbook 3 2000 ... 쭉 입력했을때 가장 첫줄인 1 john vocabook 3 1000만 출력됩니다.

  2. 동일 콘솔창에서 조회기능을 실행시켰을때 한 번만 실행되고 그 다음번 실행시 파일이 열리지 않습니다. 아마도 showRecords함수에서 첫줄만 읽고 파일을 꺼버리는게 아닌가 싶네요.

초보자라 어찌 코드를 바꿔야할지 모르겠습니다. 아래는 출력, 데이터 삽입, 메인 함수입니다.

void outputLine(ostream &output, const BookData &record) { // 출력시 단위 line
    double total = record.getQuantity() * record.getPrice();
    output << setfill(' ') << left
        << setw(10) << record.getProductNumber()
        << setw(15) << record.getProductName()
        << setw(15) << record.getQuantity()
        << setw(10) << record.getPrice()
        << setw(10) << total << endl;
}

void showRecords(ifstream &dataForIn) { // 출력 함수

    if (!dataForIn) {
        cerr << "File could not be opened." << endl;    // 파일이 열리지 않는 경우 출력
        exit(EXIT_FAILURE);
    }
    cout << left << setfill(' ')
        << setw(10) << "BkNum"
        << setw(15) << "ProdNum"
        << setw(15) << "Quantity"
        << setw(10) << "Price"
        << setw(10) << "Total"  << endl;

    cout << "------------------------------------------------------" << endl;
    //여기까지 헤더 행의 양식을 출력
    cout << resetiosflags(ios::adjustfield);


    BookData record;
    dataForIn.read(reinterpret_cast <char *> (&record), sizeof(BookData));

        // 여러 줄을 읽으려면?
    while (dataForIn && !dataForIn.eof()) {
        if (record.getProductNumber() != 0)
            outputLine(cout, record);
        dataForIn.read(reinterpret_cast <char *> (&record), sizeof(BookData));
    }
    cout << resetiosflags(ios::adjustfield);
}
void newRecord(fstream &dataForOut) {   //  데이터 생성함수
    string productName;
    int quantity;
    double price;
    BookData record;

    // exit program if fstream cannot open file
    if (!dataForOut)
    {
        cerr << "File could not be opened." << endl;
        exit(EXIT_FAILURE);
    } // end if

    while (productNum > 0 && productNum <= recordsize) {
        cout << "Enter product Name, quantity, price\n";
        cin >> productName;
        cin >> quantity;
        cin >> price;

        record.setProductNumber(productNum);
        record.setProductName(productName);
        record.setQuantity(quantity);
        record.setPrice(price);

        dataForOut.seekp((record.getProductNumber() - 1) * sizeof(BookData));
        dataForOut.write(reinterpret_cast <const char *> (&record), sizeof(BookData));

        cout << "Enter product number to insert (1 to " << recordsize << ", 0 to end input)"
            << endl << "?";
        cin >> productNum;
    }
}

int main() {
    fstream inOutData("book.dat", ios::in | ios::out | ios::trunc | ios::binary);
    ifstream inData("book.dat", ios::in | ios::binary);
    printMenu();
    char input = enterChoice();
    while (isValidLetter(input)) 
    {
        switch (input) {
        case 'S':       // 조회 기능
        case 's':
            showRecords(inData);
            printMenu();
            input = enterChoice();
            break;
        case 'U':       // 갱신 기능
        case 'u':
            cout << "------------------EDIT RECORD------------------" << endl << endl;
            cout << "Enter prod number to edit (1 to " << recordsize << ", 0 to end input)"
                << endl << "?";
            cin >> productNum;
            if (productNum == 0) {
                printMenu();
                input = enterChoice();
            }
            updateRecord(inOutData);
            printMenu();
            input = enterChoice();
            break;
        case 'A':       // 생성 기능
        case 'a':
            cout << "Enter product number to insert (1 to " << recordsize << ", 0 to end input)"
                 << endl << "?";
            cin >> productNum;
            if (productNum == 0) {
                printMenu();
                input = enterChoice();
            }
            newRecord(inOutData);
            if (productNum == 0) {
                printMenu();
                input = enterChoice();
            }
            break;
        case 'D':       // 삭제 기능
        case 'd':
            cout << "Enter product number to delete (1 to " << recordsize << ", 0 to end input)"
                << endl << "?";
            cin >> productNum;
            if (productNum == 0) {
                printMenu();
                input = enterChoice();
            }
            deleteRecord(inOutData);
            if (productNum == 0) {
                printMenu();
                input = enterChoice();
            }
            break;
        case 'B':   // 빌드 기능
        case 'b':
            buildFile();            
            printMenu();
            input = enterChoice();
            break;
        case 'Q':   // 종료 기능
        case 'q':
            input = false;
            break;
        default:
            cout << "Wrong Input" << endl;
            input = false;
            break;
        }
    }
}
  • 2016년 11월 04일에 작성됨

조회수 75


3 답변


좋아요
1
싫어요
채택취소하기

제 생각에는 showRecords의 마지막 부분과 newRecord의 while문 내부에서 마지막 부분에 파일의 처음을 가리키는 코드를 추가하면 해결 될 것 같습니다.

  • 2016년 11월 04일에 작성됨
    C++을 중점으로 배우고 있는 학생입니다. 잘 부탁 드립니다!

  • 답변 감사합니다. 처음을 가르킨다는 것이 파일포인터가 가르키는 곳을 말씀하시는 것인가요? showRecords 함수에서 while문 바로 밑에 dataForIn.clear(); dataForIn.seekg(0, ios::beg); 를 추가했더니 파일이 열리는 것으로보아 꺼트려버리진 않는 것 같은데 첫 행만 나오는 문제점 마저도 처음 조회기능을 사용할때만 나오고 조회기능을 그 다음에 실행시켰을 때는 출력해야할 헤더행만 나오고 내용은 전혀 나오질 않네요 ㅠㅠ    이성우   2016.11.4 23:37     
  • 방금 댓글의 코드 대신에 정확한 의미는 모르겠지만 dataForIn.clear(); dataForIn.seekg(record.getProductNumber() * sizeof(BookData)); 를 입력해보니 조회 기능은 계속 실행시킬 수는 있게되네요. 하지만 이 역시도 모두 첫 줄만 나옵니다.    이성우   2016.11.4 23:49     
  • 제가 프로그래밍 도구를 사용할 수 없는 상황이라 정확하게 어디서 문제인지 모르겠습니다. 따라서 의심가는 모든 부분에서(예를 들자면 while문이 루프를 도는가 아닌가 같은) cout << '1' << endl; 같은 코드를 추가하시거나 cout << dataForIn.tellp() << endl; 같은 코드를 추가시켜 확인해 주시기 바랍니다.    유현호   2016.11.5 10:17     

void outputLine(ostream &output, const BookData &record) { // 출력시 단위 line
    double total = record.getQuantity() * record.getPrice();
    output << setfill(' ') << left
        << setw(10) << record.getProductNumber()
        << setw(15) << record.getProductName()
        << setw(15) << record.getQuantity()
        << setw(10) << record.getPrice()
        << setw(10) << total << endl;
}

void showRecords(ifstream &dataForIn) { // 출력 함수

    if (!dataForIn) {
        cerr << "File could not be opened." << endl;    // 파일이 열리지 않는 경우 출력
        exit(EXIT_FAILURE);
    }
    cout << left << setfill(' ')
        << setw(10) << "BkNum"
        << setw(15) << "ProdNum"
        << setw(15) << "Quantity"
        << setw(10) << "Price"
        << setw(10) << "Total"  << endl;

    cout << "------------------------------------------------------" << endl;
    //여기까지 헤더 행의 양식을 출력
    cout << resetiosflags(ios::adjustfield);


    BookData record;
    dataForIn.read(reinterpret_cast <char *> (&record), sizeof(BookData));

        // 여러 줄을 읽으려면?
    while (dataForIn && !dataForIn.eof()) {
        if (record.getProductNumber() != 0)
            outputLine(cout, record);
        dataForIn.read(reinterpret_cast <char *> (&record), sizeof(BookData));
    }
    cout << resetiosflags(ios::adjustfield);

    dataForIn.seekg(0, ios::beg); // 스트림의 처음으로 포인터를 이동시켜줍니다.

}

newRocord 함수에서는 저장된 총 개수를 가지고 그 갯수 이내이면 기존의 코드를 사용하고 그 이외의 productNum 일 경우 뒤로 이어서 쓰던가 혹은 중간에 더미를 넣어주면 될 것 같습니다.

seekp의 반환값을 확인하며 파일 쓰기를 해야 합니다. 1~10 까지 썼는데 11~15 를 뛰어넘고 16부터 바로 쓸 순 없으니까요.

void newRecord(fstream &dataForOut) {   //  데이터 생성함수
    string productName;
    int quantity;
    double price;
    BookData record;

    // exit program if fstream cannot open file
    if (!dataForOut)
    {
        cerr << "File could not be opened." << endl;
        exit(EXIT_FAILURE);
    } // end if

    do
    {
        cout << "Enter product number to insert (1 to " << recordsize << ", 0 to end input)"
            << endl << "?";
        cin >> productNum;

        if (!productNum) // productNum == 0
        {
            break;
        }

        cout << "Enter product Name, quantity, price\n";
        cin >> productName;
        cin >> quantity;
        cin >> price;

        record.setProductNumber(productNum);
        record.setProductName(productName);
        record.setQuantity(quantity);
        record.setPrice(price);

        if (BookData::getCount() > productNum)
        {
        /*
            임의로 함수를 작성해보았습니다. 내부적으로는
            static int BookData::getCount() { return (BookData::static int count); } 로 작성 후
            생성자에서 count++; 를 추가하시면 편하실 겁니다.
            소멸자에서 count--; 를 추가하시면 빼는 함수를 작성하지 않아도 됩니다.
        */
            dataForOut.seekp((record.getProductNumber() - 1) * sizeof(BookData));
            dataForOut.write(reinterpret_cast <const char *> (&record), sizeof(BookData));
        }
        else
        {
            dataForOut.seekp((BookData::getCount() - 1) * sizeof(BookData));
            dataForOut.write(reinterpret_cast <const char *> (&record), sizeof(BookData));
        }

    }while(productNum > 0 && productNum <= recordsize);

}

제가 코딩을 할 수 있는 상황이 아니기 때문에 이 또한 실행되지 않을 수 있습니다. 다만 깊게 생각해서 최대한 바꾸지 않는 선에서 고치려고 노력해 보았습니다.

  • 2016년 11월 05일에 작성됨
    C++을 중점으로 배우고 있는 학생입니다. 잘 부탁 드립니다!

  • seekg로 포인터를 이동시키니까 파일을 강제로 종료시키지는 않아서 덕분에 조회기능을 계속 사용할 수는 있게되었습니다. (기존의 경우 두 번째로 조회기능을 사용할때부터는 파일이 열리지 않음) 하지만 파일의 첫 번째 데이터 행만 읽어내는 문제는 아직 여전합니다.     이성우   2016.11.5 17:30     
  • 자세한 내용은 아래 답변란에 적어두었습니다.    이성우   2016.11.5 17:30     

아래 이미지에서 q라고 적은게 1번칸이고 e라고 적혀있는게 3번칸인데 이것으로 보았을 때, newRecord에서 productNum 기준 1번과 3번에 예시로 입력할 경우 아래와 같이 건너띄고도 입력이 되는 듯합니다.

이미지

그러나 show기능이 매번 항상 첫 데이터만 읽습니다.ㅠㅠ

!파일스트림객체명.eof() 이라는 코드 조건문가 while문의 조건에 들어가면 분명히 파일의 끝까지 읽는 것 일텐데도 while문을 돌고나면 파일포인터는 끝에 가있으면서도 왜 항상 첫 데이터만 읽어내는 것인지 모르겠어요.. 아래 이미지처럼요

이미지

아래는 좀 더 상황을 알 수 있도록 전체 코드를 올렸습니다. 코드는 약간 긴 듯 하지만 사실상 처음에 올렸던 핵심인 showRecord 함수만 고치면 해결할 수 있을 것이라 생각합니ㅏㄷ.

//header.h
#include <iostream>

class BookData
{
public:
    // 기본 생성자
    BookData(int = 0, const std::string & = "", int = 0, double = 0.0);
    // set function = mutater 함수
    // get function = accessor 함수
    void setProductNumber(int);
    int getProductNumber() const;
    void setProductName(const std::string &);
    std::string getProductName() const;
    void setQuantity(int);
    int getQuantity() const;
    void setPrice(double);
    double getPrice() const;
private:
    int ProductNumber;
    char ProductName[15];
    int Quantity;
    double Price;
}; // end class ClientData





//header.source
#include <iostream>
#include "header.h"

BookData::BookData(int ProductNumValue,
                   const std::string &ProductNameString,
                   int QuantityValue,
                   double PriceValue)
            : ProductNumber(ProductNumValue), Quantity(QuantityValue), Price(PriceValue)
{
    setProductName(ProductNameString);  // Name은 따로 글자수에 따른 설정이 필요하므로 set함수 사용
}

void BookData::setProductNumber(int ProductNumValue ) {
    this->ProductNumber = ProductNumValue;  
}

int BookData::getProductNumber() const {
    return ProductNumber;
}

void BookData::setProductName(const std::string &ProductNameString) {
    int length = ProductNameString.size();
    length = (length < 15 ? length : 14);   // 15보다 글자수가 작으면 length, 크면 14를 length값으로 사용
    ProductNameString.copy(ProductName, length);    //  ProductName변수로 length길이만큼 스트링 복사
    ProductName[length] = '\0';
}

std::string BookData::getProductName() const {
    return ProductName;
}

void BookData::setQuantity(int QuantityValue) {
    this->Quantity = QuantityValue;
}

int BookData::getQuantity() const {
    return Quantity;
}

void BookData::setPrice(double PriceValue) {
    this->Price = PriceValue;
}

double BookData::getPrice() const {
    return Price;
}



//source.cpp
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
#include "header.h"
using namespace std;

int recordsize = 0; // 글로벌 변수 선언. 원래 전역변수는 가능한 피하는게 좋음
int productNum = 0;

void printMenu() {
    // 메뉴 문구를 수정하여도 문구가 자동으로 정렬되도록 하기위해 문구를 변수로 저장
    string title = "Main Menu";
    string menu_1 = "(S)how records on Screen";
    string menu_2 = "(U)pdate a Book";
    string menu_3 = "(A)dd a new Book";
    string menu_4 = "(D)elete a Book";
    string menu_5 = "(B)uild a File";
    string menu_6 = "(Q)uit";
    string menu_list[] = { menu_1, menu_2, menu_3, menu_4, menu_5, menu_6 };

    cout << endl;
    cout << '\t' << setfill('-')
        << setw(18 + title.length() / 2)
        << title    // 제목 출력
        << setw(18 - title.length() / 2)
        << ' ' << endl;
    cout << '\t' << 'I' << setfill(' ') << setw(34) << 'I' << endl;
    for (int i = 0; i < 6; i++) {
        cout << '\t' << 'I'
            << setfill(' ') << setw(16 + menu_list[i].length() / 2)
            << menu_list[i]     // 루프로 순서대로 메뉴1~6 까지 출력
            << setw(18 - menu_list[i].length() / 2)
            << 'I' << endl;
    }
    cout << '\t' << 'I' << setfill(' ') << setw(34) << 'I' << endl;
    cout << '\t' << 'I' << setfill('-') << setw(34) << 'I' << endl;
}

bool isValidLetter(char letter) {   // 메인함수에서의 조건을 위한 함수
                                    // 정해진 쿼리가 아니면 false를 리턴
    if (letter == 'S' || letter == 's')
        return true;
    else if (letter == 'U' || letter == 'u')
        return true;
    else if (letter == 'A' || letter == 'a')
        return true;
    else if (letter == 'D' || letter == 'd')
        return true;
    else if (letter == 'B' || letter == 'b')
        return true;
    else if (letter == 'Q' || letter == 'q')
        return true;
    return false;
}

char enterChoice(void) {    // 사용자로부터 메뉴의 입력을 받는 함수
    char choice;
    cout << '\t'; cin >> choice;
    return choice;
}

void outputLine(ostream &output, const BookData &record) { // 출력시 단위 line
    double total = record.getQuantity() * record.getPrice();
    output << setfill(' ') << left
        << setw(10) << record.getProductNumber()
        << setw(15) << record.getProductName()
        << setw(15) << record.getQuantity()
        << setw(10) << record.getPrice()
        << setw(10) << total << endl;
}

void showRecords(ifstream &dataForIn) { // 출력 함수

    if (!dataForIn) {
        cerr << "File could not be opened." << endl;    // 파일이 열리지 않는 경우 출력
        exit(EXIT_FAILURE);
    }
    cout << left << setfill(' ')
    << setw(10) << "BkNum" << setw(15) << "ProdNum" << setw(15) << "Quantity" << setw(10) << "Price" 
    << setw(10) << "Total"  << endl;
    cout << "------------------------------------------------------" << endl;
    //여기까지 헤더 행의 양식을 출력
    cout << resetiosflags(ios::adjustfield);

    BookData record;
    //std::cout << "*****************file position: " << dataForIn.tellg() << std::endl;
    dataForIn.read(reinterpret_cast <char *> (&record), sizeof(BookData));
    //std::cout << "*****************file position: " << dataForIn.tellg() << std::endl;
        // 여러 줄을 읽으려면?
    while (dataForIn && !dataForIn.eof()) {
        if (record.getProductNumber() != 0)
            outputLine(cout, record);
        dataForIn.read(reinterpret_cast <char *> (&record), sizeof(BookData));
    }
    dataForIn.clear();
    dataForIn.seekg(0);
    //std::cout << "*****************file position: " << dataForIn.tellg() << std::endl;
    cout << resetiosflags(ios::adjustfield);

    //double totalAmount;
    //cout << "Total Amount   : " << totalAmount;

}

void updateRecord(fstream &inOutData) { // 갱신 함수
    string productName;
    int quantity;
    double price;
    BookData record;

    // exit program if fstream cannot open file
    if (!inOutData)
    {
        cerr << "File could not be opened." << endl;
        exit(EXIT_FAILURE);
    } // end if

    cout << "Enter New product Name, New quantity, New price\n";
    cin >> productName;
    cin >> quantity;
    cin >> price;

    record.setProductNumber(productNum);
    record.setProductName(productName);
    record.setQuantity(quantity);
    record.setPrice(price);

    inOutData.seekp((record.getProductNumber() - 1) * sizeof(BookData));
    inOutData.write(reinterpret_cast <const char *> (&record), sizeof(BookData));

    cout << "product #" << productNum << " Updated.";
}

void newRecord(fstream &dataForOut) {   //  데이터 생성함수
    string productName;
    int quantity;
    double price;
    BookData record;

    // exit program if fstream cannot open file
    if (!dataForOut)
    {
        cerr << "File could not be opened." << endl;
        exit(EXIT_FAILURE);
    } // end if

    while (productNum > 0 && productNum <= recordsize) {
        cout << "Enter product Name, quantity, price\n";
        cin >> productName;
        cin >> quantity;
        cin >> price;

        record.setProductNumber(productNum);
        record.setProductName(productName);
        record.setQuantity(quantity);
        record.setPrice(price);

        dataForOut.seekp((record.getProductNumber() - 1) * sizeof(BookData));
        dataForOut.write(reinterpret_cast <const char *> (&record), sizeof(BookData));

        cout << "Enter product number to insert (1 to " << recordsize << ", 0 to end input)"
            << endl << "?";
        cin >> productNum;
    }
}

void deleteRecord(fstream &inOutData) { // 삭제 함수
    string productName;
    int quantity;
    double price;
    BookData record;

    // exit program if fstream cannot open file
    if (!inOutData)
    {
        cerr << "File could not be opened." << endl;
        exit(EXIT_FAILURE);
    } // end if

    while (productNum > 0 && productNum <= recordsize) {

        record.setProductNumber(productNum);
        record.setProductName("");
        record.setQuantity(0);
        record.setPrice(0);

        inOutData.seekp((record.getProductNumber() - 1) * sizeof(BookData));
        inOutData.write(reinterpret_cast <const char *> (&record), sizeof(BookData));

        cout << "Enter product number to delete (1 to " << recordsize << ", 0 to end input)"
            << endl << "?";
        cin >> productNum;

    }
}

void buildFile()        // 빌드 함수
{
    system("cls");
    cout << "\n----------------BUILD THE FILE---------------------" << endl;
    cout << "\nEnter number of records :";

    cin >> recordsize;

    ofstream outCredit1("book.dat", ios::out| ios::trunc | ios::binary);

    // exit program if ofstream could not open file
    if (!outCredit1)
    {
        cerr << "File could not be opened." << endl;
        exit(EXIT_FAILURE);
    } // end if

    BookData blankbook; // constructor zeros out each data member
                        /*cout<<sizeof(blankbook)<<endl;*/
                        // output blank records to file
    blankbook.setProductNumber(0);
    blankbook.setProductName("");
    blankbook.setQuantity(0);
    blankbook.setPrice(0);
    for (int i = 0; i < recordsize; ++i)
        outCredit1.write(reinterpret_cast<const char *>(&blankbook),
            sizeof(BookData));

    outCredit1.close();
} // end main


int main() {
    fstream inOutData("book.dat", ios::in | ios::out | ios::trunc | ios::binary);
    ifstream inData("book.dat", ios::in | ios::binary);
    printMenu();
    char input = enterChoice();
    while (isValidLetter(input)) 
    {
        switch (input) {
        case 'S':       // 조회 기능
        case 's':
            showRecords(inData);
            printMenu();
            input = enterChoice();
            break;
        case 'U':       // 갱신 기능
        case 'u':
            cout << "------------------EDIT RECORD------------------" << endl << endl;
            cout << "Enter prod number to edit (1 to " << recordsize << ", 0 to end input)"
                << endl << "?";
            cin >> productNum;
            if (productNum == 0) {
                printMenu();
                input = enterChoice();
            }
            updateRecord(inOutData);
            printMenu();
            input = enterChoice();
            break;
        case 'A':       // 생성 기능
        case 'a':
            cout << "Enter product number to insert (1 to " << recordsize << ", 0 to end input)"
                 << endl << "?";
            cin >> productNum;
            if (productNum == 0) {
                printMenu();
                input = enterChoice();
            }
            newRecord(inOutData);
            if (productNum == 0) {
                printMenu();
                input = enterChoice();
            }
            break;
        case 'D':       // 삭제 기능
        case 'd':
            cout << "Enter product number to delete (1 to " << recordsize << ", 0 to end input)"
                << endl << "?";
            cin >> productNum;
            if (productNum == 0) {
                printMenu();
                input = enterChoice();
            }
            deleteRecord(inOutData);
            if (productNum == 0) {
                printMenu();
                input = enterChoice();
            }
            break;
        case 'B':   // 빌드 기능
        case 'b':
            buildFile();        
            printMenu();
            input = enterChoice();
            break;
        case 'Q':   // 종료 기능
        case 'q':
            input = false;
            break;
        default:
            cout << "Wrong Input" << endl;
            input = false;
            break;
        }
    }
}


  • 2016년 11월 05일에 작성됨

  • 의외로 showRecords 함수에는 문제가 없습니다. dev c++ 설치해서 확인한 결과 같은 장소에 2번 쓰면( add 로 추가한후 다시 같은 장소를 add로 추가하거나 같은 장소를 delete 하셔도 됩니다.) 정상 작동 하는것으로 확인되었습니다. 또한 2번 작성해야 파일에도 변화가 있는것을 확인하였습니다. ifstream의 구조상의 문제(버퍼에 임시저장이라던가..?) 인 듯 합니다만 각 각 다른 stream으로 열어서 그런 것 일 수도 있습니다.    유현호   2016.11.6 11:53     
  • 아... 다른 stream으로 열었다는 것은 showRecord 함수의 파라미터는 ifstream인 반면 newRecord 함수는 fstream인 것을 말씀하시는 건가요? showRecord함수를 newRecord와 같은 파라미터인 fstream inOutData객체를 사용하면 show기능을 사용하였을때 출력이 이상한 값(한자나 이상한 문자)으로 나와서 다른 stream으로 열었습니다.    이성우   2016.11.6 14:56     

로그인이 필요한 기능입니다.

Hashcode는 개발자들을 위한 무료 QnA사이트 입니다. 작성한 답변에 다른 개발자들이 댓글을 작성하거나 좋아요/싫어요를 할 수 있기 때문에 계정을 필요로 합니다.
► 로그인
► 계정만들기
Close