2013-03-21 24 views
7

Vì vậy, tôi đang gặp một chút vấn đề không thể đọc đúng một tệp nhị phân trong cấu trúc của tôi. Cấu trúc là:Đọc tập tin nhị phân thành một cấu trúc (C++)

struct Student 
{ 
    char name[25]; 
    int quiz1; 
    int quiz2; 
    int quiz3; 
}; 

Đó là 37 byte (25 byte từ mảng char và 4 byte cho mỗi số nguyên). Tệp .dat của tôi là 185 byte. Đó là 5 học sinh với 3 lớp số nguyên. Vì vậy, mỗi học sinh chiếm 37 byte (37 * 5 = 185).

Nó trông giống như thế này ở định dạng văn bản đơn giản:

Bart Simpson   75 65 70 
Ralph Wiggum   35 60 44 
Lisa Simpson   100 98 91 
Martin Prince   99 98 99 
Milhouse Van Houten 80 87 79 

Tôi có thể đọc từng hồ sơ cá nhân bằng cách sử dụng mã này:

Student stud; 

fstream file; 
file.open("quizzes.dat", ios::in | ios::out | ios::binary); 

if (file.fail()) 
{ 
    cout << "ERROR: Cannot open the file..." << endl; 
    exit(0); 
} 

file.read(stud.name, sizeof(stud.name)); 
file.read(reinterpret_cast<char *>(&stud.quiz1), sizeof(stud.quiz1)); 
file.read(reinterpret_cast<char *>(&stud.quiz2), sizeof(stud.quiz2)); 
file.read(reinterpret_cast<char *>(&stud.quiz3), sizeof(stud.quiz3)); 

while(!file.eof()) 
{ 
    cout << left 
     << setw(25) << stud.name 
     << setw(5) << stud.quiz1 
     << setw(5) << stud.quiz2 
     << setw(5) << stud.quiz3 
     << endl; 

    // Reading the next record 
    file.read(stud.name, sizeof(stud.name)); 
    file.read(reinterpret_cast<char *>(&stud.quiz1), sizeof(stud.quiz1)); 
    file.read(reinterpret_cast<char *>(&stud.quiz2), sizeof(stud.quiz2)); 
    file.read(reinterpret_cast<char *>(&stud.quiz3), sizeof(stud.quiz3)); 
} 

Và tôi nhận được một cái nhìn tốt đẹp đầu ra, nhưng tôi muốn có thể đọc trong một cấu trúc toàn bộ tại một thời điểm, không chỉ riêng từng thành viên của mỗi cấu trúc tại một thời điểm. Mã này là những gì tôi tin cần thiết để hoàn thành nhiệm vụ, nhưng ... nó không hoạt động (tôi sẽ hiển thị kết quả sau đó):

* không bao gồm các phần tương tự như mở tệp và cấu trúc khai vv

file.read(reinterpret_cast<char *>(&stud), sizeof(stud)); 

while(!file.eof()) 
{ 
    cout << left 
     << setw(25) << stud.name 
     << setw(5) << stud.quiz1 
     << setw(5) << stud.quiz2 
     << setw(5) << stud.quiz3 
     << endl; 

    file.read(reinterpret_cast<char *>(&stud), sizeof(stud)); 
} 

OUTPUT:

Bart Simpson    16640179201818317312 
ph Wiggum    288358417665884161394631027 
impson     129184563217692391371917853806 
ince      175193530917020655191851872800 

phần duy nhất nó không lộn xộn lên là tên đầu tiên, sau đó nó xuống đồi .. tôi đã thử tất cả mọi thứ và tôi không có ý tưởng gì là sai. Tôi thậm chí đã tìm kiếm qua những cuốn sách tôi có và tôi không thể tìm thấy bất cứ điều gì. Những thứ trong đó trông giống như những gì tôi có và chúng hoạt động, nhưng vì một số lý do kỳ quặc của tôi thì không. Tôi đã làm file.get (ch) (ch là char) ở byte 25 và nó trả về K, là ASCII cho 75 .. đó là điểm kiểm tra đầu tiên, vì vậy, mọi thứ đều ở đâu. Nó chỉ là không đọc trong cấu trúc của tôi đúng.

Bất kỳ trợ giúp nào sẽ được đánh giá rất nhiều, tôi chỉ bị mắc kẹt với vấn đề này.

CHỈNH SỬA: Sau khi nhận được một lượng lớn dữ liệu đầu vào bất ngờ và tuyệt vời từ các bạn, tôi đã quyết định thực hiện lời khuyên của bạn và gắn bó với việc đọc từng thành viên một lúc. Tôi làm mọi việc sạch hơn và nhỏ hơn bằng cách sử dụng các chức năng. Cảm ơn bạn một lần nữa vì đã cung cấp đầu vào nhanh chóng và sáng suốt. Nó được nhiều đánh giá cao.

NẾU bạn quan tâm trong cách giải quyết không được đề xuất nhiều nhất, hãy cuộn xuống phía dưới, tới câu trả lời thứ 3 của user1654209. Cách giải quyết đó hoạt động hoàn hảo, nhưng đọc tất cả các nhận xét để xem tại sao nó không được ưa chuộng.

+1

Bạn có thể cho biết cách bạn đã viết tệp không? –

+2

Nếu bạn in 'sizeof (Student)' bạn sẽ thấy rằng nó là _not_ 37 byte. Nó có thể có thể là 40 hoặc 56. –

+0

@RetiredNinja Tôi không bao gồm tất cả các mã để tránh quá dài của một bài đăng, xin lỗi. Đây là toàn bộ mã của tôi @ codepad: http://codepad.org/331LdCYY –

Trả lời

8

Cấu trúc của bạn gần như chắc chắn được đệm để duy trì sự liên kết nội dung của nó. Điều này có nghĩa rằng nó sẽ không được 37 byte, và rằng không phù hợp gây ra đọc để đi ra khỏi đồng bộ. Nhìn vào cách mỗi chuỗi đang mất 3 ký tự, có vẻ như nó đã được đệm đến 40 byte.

Vì phần đệm có thể nằm giữa chuỗi và số nguyên, ngay cả bản ghi đầu tiên cũng không chính xác.

Trong trường hợp này, tôi khuyên bạn không nên cố gắng đọc dữ liệu của mình dưới dạng đốm màu nhị phân và gắn vào đọc từng trường riêng lẻ. Nó mạnh mẽ hơn nhiều, đặc biệt là nếu bạn thậm chí muốn thay đổi cấu trúc của bạn.

+0

Vâng, thật là 40 byte ... thật không may. Bất kỳ cách nào để khắc phục vấn đề đó? Tệp .dat là 185 byte. Tôi muốn cố gắng đọc nó một cách tổng thể, nếu tôi có thể. Trừ khi không có cách nào. –

+0

Nó không phải là một vấn đề, nó là một tính năng. Bạn có khả năng có thể hack xung quanh nó, nhưng kết quả sẽ rất mong manh. Tôi khuyên bạn nên chống lại nó. – JasonD

+0

Bạn có ý nghĩa gì bởi "mong manh"? –

4

Nếu không nhìn thấy mã ghi dữ liệu, tôi đoán bạn ghi dữ liệu theo cách bạn đọc trong ví dụ đầu tiên, từng phần tử một. Sau đó, mỗi bản ghi trong tệp thực sự sẽ là 37 byte.

Tuy nhiên, vì trình biên dịch các cấu trúc để đặt thành viên trên ranh giới đẹp vì lý do tối ưu hóa, cấu trúc của bạn là 40 byte. Vì vậy, khi bạn đọc cấu trúc đầy đủ trong một cuộc gọi duy nhất, sau đó bạn thực sự đọc 40 byte tại một thời điểm, có nghĩa là đọc của bạn sẽ đi ra khỏi giai đoạn với các hồ sơ thực tế trong tập tin.

Bạn phải thực hiện lại văn bản để viết cấu trúc hoàn chỉnh trong một lần, hoặc sử dụng phương pháp đọc đầu tiên mà bạn đang đọc một trường thành viên tại một thời điểm.

+0

Vâng, có vẻ như mọi người trên cùng một trang trên trang này ... tệp gốc có thể đã được viết một thành viên tại một thời điểm, do đó không tạo ra phần đệm (?). –

+0

Phương pháp ghi dữ liệu phải làm gì với nó? Điều duy nhất quan trọng là làm thế nào các dữ liệu này trông giống như trong tập tin nhị phân, nó không quan trọng làm thế nào họ đã đạt được điều đó. Họ thậm chí có thể không được viết ở đó bởi bất kỳ mã nào cả, nhưng được chế tác thủ công hay gì đó. Điều quan trọng là dữ liệu trông như thế nào (bố cục của chúng trong tệp) và mã đọc phải khớp với (ví dụ: sử dụng cùng bố cục cho các cấu trúc trong bộ nhớ hoặc các giá trị tương tự cho tìm kiếm). – SasQ

3

Một cách giải quyết đơn giản là để đóng gói cấu trúc của bạn tới 1 byte

sử dụng gcc

struct __attribute__((packed)) Student 
{ 
    char name[25]; 
    int quiz1; 
    int quiz2; 
    int quiz3; 
}; 

sử dụng msvc

#pragma pack(push, 1) //set padding to 1 byte, saves previous value 
struct Student 
{ 
    char name[25]; 
    int quiz1; 
    int quiz2; 
    int quiz3; 
}; 
#pragma pack(pop) //restore previous pack value 

EDIT: Như ahans người dùng khẳng định: gói pragma được hỗ trợ bởi gcc kể từ phiên bản 2.7.2.3 (phát hành năm 1997) nên có vẻ an toàn khi sử dụng gói pragma làm ký hiệu đóng gói duy nhất nếu bạn đang nhắm mục tiêu msvc và gcc

+0

Cảm ơn bạn rất nhiều! :) Nó thực sự làm việc hoàn hảo tải toàn bộ cấu trúc. Tôi lo lắng về việc sử dụng nó, mặc dù, sau khi đọc tất cả các ý kiến, heh –

+1

Có thực sự không cần cho phiên bản GCC đặc biệt, nó cũng hiểu '#pragma pack' giống nhau cách MSVC làm. – ahans

+0

@ahans Chắc chắn, mặc dù tôi không nhớ phiên bản gcc nào đã giới thiệu hỗ trợ gói pragma.Nếu ai đó có thông tin, vui lòng nhận xét và tôi sẽ chỉnh sửa câu trả lời –

2

Như bạn đã biết, phần đệm là vấn đề ở đây. Ngoài ra, như những người khác đã đề xuất, cách thích hợp để giải quyết điều này là để đọc từng thành viên riêng lẻ như bạn đã làm trong ví dụ của bạn. Tôi không mong đợi điều này chi phí nhiều hơn là đọc toàn bộ điều trong một lần thực hiện khôn ngoan. Tuy nhiên, nếu bạn vẫn muốn đi trước và đọc nó như là một lần, bạn có thể nói với trình biên dịch để làm đệm khác nhau:

#pragma pack(push, 1) 
struct Student 
{ 
    char name[25]; 
    int quiz1; 
    int quiz2; 
    int quiz3; 
}; 
#pragma pack(pop) 

Với #pragma pack(push, 1) bạn nói với trình biên dịch để lưu các giá trị gói hiện hành về một ngăn xếp nội bộ và sử dụng giá trị gói 1 sau đó. Điều này có nghĩa là bạn có được một sự liên kết của 1 byte, có nghĩa là không có padding ở tất cả trong trường hợp này. Với #pragma pack(pop) bạn yêu cầu trình biên dịch lấy giá trị cuối cùng từ ngăn xếp và sử dụng sau này, do đó khôi phục hành vi của trình biên dịch được sử dụng trước định nghĩa của struct.

Trong khi #pragma thường cho biết các tính năng phụ thuộc vào trình biên dịch không phụ thuộc vào trình biên dịch, tính năng này hoạt động ít nhất với GCC và Microsoft VC++.

+0

Ahh, cảm ơn bạn đã giải thích thêm về việc triển khai đó đã làm gì. Tôi đã không hoàn toàn chắc chắn những gì từng có nghĩa là và lời giải thích của bạn dễ hiểu hơn những gì tôi đã đọc trên các trang web khác. –

0

Có nhiều cách để giải quyết vấn đề của chuỗi này. Dưới đây là giải pháp dựa trên việc sử dụng liên kết của cấu trúc và ký tự char:

#include <fstream> 
#include <sstream> 
#include <iomanip> 
#include <string> 

/* 
This is the main idea of the technique: Put the struct 
inside a union. And then put a char array that is the 
number of chars needed for the array. 

union causes sStudent and buf to be at the exact same 
place in memory. They overlap each other! 
*/ 
union uStudent 
{ 
    struct sStudent 
    { 
     char name[25]; 
     int quiz1; 
     int quiz2; 
     int quiz3; 
    } field; 

    char buf[ sizeof(sStudent) ]; // sizeof calcs the number of chars needed 
}; 

void create_data_file(fstream& file, uStudent* oStudent, int idx) 
{ 
    if (idx < 0) 
    { 
     // index passed beginning of oStudent array. Return to start processing. 
     return; 
    } 

    // have not yet reached idx = -1. Tail recurse 
    create_data_file(file, oStudent, idx - 1); 

    // write a record 
    file.write(oStudent[idx].buf, sizeof(uStudent)); 

    // return to write another record or to finish 
    return; 
} 


std::string read_in_data_file(std::fstream& file, std::stringstream& strm_buf) 
{ 
    // allocate a buffer of the correct size 
    uStudent temp_student; 

    // read in to buffer 
    file.read(temp_student.buf, sizeof(uStudent)); 

    // at end of file? 
    if (file.eof()) 
    { 
     // finished 
     return strm_buf.str(); 
    } 

    // not at end of file. Stuff buf for display 
    strm_buf << std::setw(25) << std::left << temp_student.field.name; 
    strm_buf << std::setw(5) << std::right << temp_student.field.quiz1; 
    strm_buf << std::setw(5) << std::right << temp_student.field.quiz2; 
    strm_buf << std::setw(5) << std::right << temp_student.field.quiz3; 
    strm_buf << std::endl; 

    // head recurse and see whether at end of file 
    return read_in_data_file(file, strm_buf); 
} 



std::string quiz(void) 
{ 

    /* 
    declare and initialize array of uStudent to facilitate 
    writing out the data file and then demonstrating 
    reading it back in. 
    */ 
    uStudent oStudent[] = 
    { 
     {"Bart Simpson",   75, 65, 70}, 
     {"Ralph Wiggum",   35, 60, 44}, 
     {"Lisa Simpson",   100, 98, 91}, 
     {"Martin Prince",   99, 98, 99}, 
     {"Milhouse Van Houten", 80, 87, 79} 

    }; 




    fstream file; 

    // ios::trunc causes the file to be created if it does not already exist. 
    // ios::trunc also causes the file to be empty if it does already exist. 
    file.open("quizzes.dat", ios::in | ios::out | ios::binary | ios::trunc); 

    if (! file.is_open()) 
    { 
     ShowMessage("File did not open"); 
     exit(1); 
    } 


    // create the data file 
    int num_elements = sizeof(oStudent)/sizeof(uStudent); 
    create_data_file(file, oStudent, num_elements - 1); 

    // Don't forget 
    file.flush(); 

    /* 
    We wrote actual integers. So, you cannot check the file so 
    easily by just using a common text editor such as Windows Notepad. 

    You would need an editor that shows hex values or something similar. 
    And integrated development invironment (IDE) is likely to have such 
    an editor. Of course, not always so. 
    */ 


    /* 
    Now, read the file back in for display. Reading into a string buffer 
    for display all at once. Can modify code to display the string buffer 
    wherever you want. 
    */ 

    // make sure at beginning of file 
    file.seekg(0, ios::beg); 

    std::stringstream strm_buf; 
    strm_buf.str(read_in_data_file(file, strm_buf)); 

    file.close(); 

    return strm_buf.str(); 
} 

Trả lời cuộc gọi() và nhận một chuỗi được định dạng để hiển thị thành std :: cout, ghi vào tệp hoặc bất kỳ thứ gì.

Ý tưởng chính là tất cả các mục bên trong một liên minh bắt đầu tại cùng một địa chỉ trong bộ nhớ. Vì vậy, bạn có thể có một char hoặc wchar_t buf có cùng kích thước với cấu trúc mà bạn muốn ghi vào hoặc đọc từ một tệp. Và lưu ý rằng không phôi là cần thiết. Không có một diễn viên nào trong mã.

Tôi cũng không phải lo lắng về việc đệm.

Đối với những người không thích đệ quy, xin lỗi. Làm việc nó ra với đệ quy là dễ dàng hơn và ít lỗi dễ bị cho tôi. Có thể không dễ dàng hơn cho người khác?Việc thu thập có thể được chuyển đổi thành vòng lặp. Và họ sẽ cần phải được chuyển đổi thành vòng lặp cho các tệp rất lớn.

Đối với những người thích du ngoạn, đây là một ví dụ khác về việc sử dụng đệ quy.

Tôi không tuyên bố rằng việc sử dụng liên minh là giải pháp tốt nhất hay không. Dường như nó là một giải pháp. Có thể bạn sẽ thích nó?