2010-10-30 3 views
0

У меня есть следующий User.h, который содержит несколько атрибутов (строк). User.cpp имеет все определения.Сбой программы C++ при чтении объекта из файла произвольного доступа

//User.h 
#ifndef USER_H 
#define USER_H 
#include<iostream> 
#include <cstring> 

using namespace std; 

class User{ 

    string username; 

    public: 
     User(); 
     string getUsername() const;      
     void setUsername(string); 

}; 
#endif 

Я использую другой класс, "Файл" для вставки новых пользователей/пользователей Вид из случайным образом доступ .dat файл

//File.h 
#ifndef FILE_H 
#define FILE_H 
#include "User.h" 

class File{ 
    public: 

      void loadUser(); 
      bool addUser(User&); 

}; 
#endif 

определений класса File

//File.cpp 
#include<cstring> 
#include<iostream> 
#include<iomanip> 
#include<fstream> 

#include "User.h" 
#include "File.h" 

using namespace std; 

User tempUser; 
fstream usersFile; 

void File::loadUser(){ 
    usersFile.open("users.dat", ios::in | ios::binary); 

    usersFile.seekg(0); 

    // commenting the following lines prevented the issue 
    usersFile.read(reinterpret_cast<char *>(&tempUser), sizeof(tempUser)); 

    cout<<tempUser.getUsername().c_str(); 

    usersFile.close(); 
} 

bool File::addUser(User& user){ 

    usersFile.open("users.dat", ios::out | ios::ate | ios::binary); 

    // no issue when writing to file 
    usersFile.write(reinterpret_cast<const char *>(&user), sizeof(user)); 

    usersFile.close(); 

    cout<<"User added"; 
} 

I «Получая вышеупомянутую проблему при запуске. Однако проблема с компиляцией.

Есть ли проблема при работе с объектами, которые имеют «строковые атрибуты» внутри, при обработке?

Пожалуйста, помогите

ответ

1

Я думаю, проблема в том, что вы смешиваете код C++ с мышлением C. Здесь вы действительно должны использовать оператор извлечения, opeartor>>(), а также потоки ввода-вывода C++. Это, в отличие от использования стандартного IO C вместе с функцией read(). Для записи объекта используйте оператор ввода operator<<() вместо функции C write().

Для работы со строками используется std::string. Этот класс обеспечивает operator<<() и operator>>(). Итак, вы можете сказать std::string s, а затем io << s и io >> s, где io - это некоторый объект потока C++ IO. Это сделает «Правильная вещь» (tm). Философия здесь заключается в том, что класс std::string знает лучше вас, пользователя, как сериализовать объект std::string. Так давайте это сделаем, с операторами < < и >>.

Продолжая эту идею, вы, как автор User, лучше всех знаете, как сериализовать объект User. Поэтому предоставляйте < < и >> операторам пользователей вашего класса в качестве услуги. «Пользователи вашего класса» вполне могут быть вам через неделю, когда вы полностью забыли, как правильно сериализовать объект User. (Или вы думаете, что помните, но на практике вы забыли деталь, вызвав ошибку в вашем коде). Пример:

// in User.h 
#include <string> 
#include <iosfwd> // forward declarations of standard IO streams 

namespace mine { 
class User { 
    User(const std::string& name) : username(name) { } 
    friend std::ostream& operator<<(std::ostream&, const User&); 
    friend std::istream& operator>>(std::istream&, User&); 

private: 
    std::string username; 
}; 

std::ostream& operator<<(std::ostream& out, const User& u) 
{ 
    return out << u.username; 
} 

std::istream& operator>>(std::istream& in, User& u) 
{ 
    return in >> u.username; 
} 
} // namespace mine 

С этого момента, чтобы сохранить пользователя в файл вы говорите

std::ofstream f("filename"); 
User u("John"); 
f << u; 

Вот и все. Чтобы прочитать пользователя:

std::ifstream f2("filename"); 
f2 >> u; 

Практически рекомендуется обернуть свой код в пространстве имен. IDE дают хорошую визуализацию этой проблемы, показывая, сколько символов видно с помощью функции автозаполнения. Вы видите, какая часть беспорядка существует в глобальном масштабе. Упаковывая свой код в пространство имен, вы группируете его под именем области, сохраняя еще больше беспорядка в области глобального имени. Это просто быть аккуратным. Если вы поместите свой код в свое собственное пространство имен, вы можете выбрать любое имя для функции, класса или переменной, если вы еще не выбрали ее раньше. Если вы не помещаете его в пространство имен, вам необходимо обмениваться именами с другими. Это как скунс, объявляющий свою территорию, только без запаха постели.

В этой заметке я предлагаю вам снять этот using namespace std с вашего заголовка. Это приводит все символы в пространстве имен std в объем всех файлов, которые #include заголовок. Это плохая практика. Скажите только using namespace std в файлах реализации, если хотите, но не в файлах заголовков.

Конечно, некоторые скажут, что это плохая идея. Я лично думаю, что все в порядке, если вы знаете о том, что у вас могут быть конфликты имен в этом конкретном файле реализации. Но, по крайней мере, вы знаете, где это заявление using: оно находится в вашем файле реализации и вызывает конфликты только в этом файле реализации. Это своего рода пистолет (пластиковая водяная пушка, но, тем не менее, пистолет), только вы можете стрелять только своими собственными ногами и никого. Что, на мой взгляд, прекрасно.

0

Да, класс станд :: строка не ясно, старые данные, другими словами, он содержит указатели. Если вы сохраните/загрузите класс строки таким образом, данные , указывающие на, не будут загружены/сохранены, только значение указателя.

Дополнительно sizeof (tempUser) не будет содержать размер текста, на который указывают строки.

Ваше решение - изменить способ чтения/записи данных. Один из способов - использовать boost :: serialization, который обрабатывает типы данных, такие как std :: string. Другим способом было бы записать каждую строку в отдельную строку в текстовом документе самостоятельно (не в двоичном режиме), а затем использовать readline для их повторного чтения.

0

строка - это объект, что означает, что вы не пишете это содержимое.

Попробуйте написать пользователя и изучить файл, чтобы узнать, что я имею в виду. То, что вы читаете, - это некоторые указатели, которые указывают на недопустимые местоположения памяти.

0

Вместо того, чтобы создавать собственный код сериализации, что-то вроде Google Protocol Buffers может сделать то, что вы хотите, с меньшими усилиями. Он идеально подходит для передачи простых структурированных данных с места на место.

1

Вы не можете читать такие типы не-POD. Строка не является типом POD. What are POD types in C++?

Существует несколько способов правильного считывания строк в зависимости от того, как они были сохранены.

Для текстовых файлов:

Если строка только одно слово, отделенная с обеих сторон пробелами, вы можете использовать обычный старый >> оператор. Если это несколько слов, вы можете сохранить его на своей собственной линии и использовать getline.

Для бинарных файлов:

магазина строка в нуле формы. Прочитайте его по одному символу за раз, проверяя нулевой символ. Или добавьте строку с целым числом, сохраняющим ее размер. Когда вы его прочитаете, сначала прочитайте целое число, а затем прочитайте это много символов.

Смежные вопросы