Форум программистов, компьютерный форум CyberForum.ru

Файл произвольного доступа - C++

Восстановить пароль Регистрация
 
 
Рейтинг: Рейтинг темы: голосов - 23, средняя оценка - 4.61
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,511
03.07.2011, 14:36     Файл произвольного доступа #1
Очень прошу помощи так как не могу с ними разобраться который день, видать делаю одну и ту же ошибку но не нахожу. Создаю свой класс и потом когда пытаюсь его записать и считать возникают ошибки.
Ну да наверное проще начать обо всём по порядку чтобы не запутывать.

Проблема первая: файл каждый раз после записи 10 объектов моего класса в бинарном режиме при открытии текстовым редактором выглядит по разному, хотя размер его одинаковый, это меня настораживает.

Пример: запускаю скомпилированное приложение чтобы оно записало 10 объектов моего класса ClientData созданных конструктором по умолчанию и открываю через текстовый редактор и вижу символы :u.ы-I, закрываю и открываю опять своё приложение и вижу уже символы :ucИќ, за третим разом :uО(фҐ , тоесть первые 2 символа всегда те же, а остальные меняются, так ведь быть не должно? Значит ошибка у меня в классе? Тогда просьба подсказать где именно вроде строки уже не использую и все типы данных не должны меняться ...

Сам класс ClientData мой представляет собой класс с 3мя данными (номер, имя, баланс) и set и get методами для них (проверок кое-где нету, но не в них суть).


ClientData.h (Заголовочный файл к классу)
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifndef CLIENTDATA_H
#define CLIENTDATA_H
 
#include <string>
using std::string;
 
 
class ClientData
{
public:
    //конструктор по умолчанию который устанавливает
    //номер =0, имя = пустой строке и баланс =0
    ClientData(int=0,string="",double=0.0);
 
    void setID(int);
    int getID() const;
 
    void setName(string);
    string getName() const;
 
    void setBalance(double);
    double getBalance() const;
 
 
private:
    int ID; //номер
    char name[15]; //имя
    double balance; // баланс
};
 
#endif
ClientData.cpp (реализация самого класса)
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <string>
using std::string;
 
#include <cstring>
#include "ClientData.h"
 
ClientData::ClientData(int i, string n, double b)
{
    setID(i);
    setName(n);
    setBalance(b);
}
 
void ClientData:: setID(int i)
{
    ID=i;
}
 
int ClientData:: getID() const
{
    return ID;
}
 
void ClientData:: setName(string n)
{
    strncpy(name,n.data(),14);
}
 
string ClientData:: getName() const
{
    return name;
}
 
void ClientData:: setBalance(double b)
{
    balance=b;
}
 
double ClientData:: getBalance() const
{
    return balance;
}
main(совершаю только запись в файл 10 объектов типа ClientData
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <iomanip>
#include <fstream>
#include "ClientData.h"
using namespace std;
 
void newRecord(fstream&);
int menu();
void print(fstream&);
void print2(fstream&);
 
int main()
{
    //создаю объект типа ClientData
    ClientData client; 
    
    //открываю файл для чтения и записи в бинарном режиме
    fstream file("trans.dat",ios::binary|ios::in|ios::out);
    if (!file)
     cerr<<"Error";
 
    //записываю 10 моих объектов в файл
    for (int i=0;i<10;i++)
     file.write(reinterpret_cast<const char*>(&client),sizeof(ClientData));
}
Очень прошу помочь разобраться.
Лучшие ответы (1)
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
pito211
 Аватар для pito211
186 / 173 / 8
Регистрация: 22.03.2010
Сообщений: 612
03.07.2011, 14:51     Файл произвольного доступа #2
конструктор неправильно написан
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,511
03.07.2011, 14:53  [ТС]     Файл произвольного доступа #3
pito211,напиши что именно, ато там 3 строчки, вроде всё ок ... хотя может слона я и не замечаю, но вы мне покажите его хоть
pito211
 Аватар для pito211
186 / 173 / 8
Регистрация: 22.03.2010
Сообщений: 612
03.07.2011, 14:54     Файл произвольного доступа #4
хотя может и правильно, но как то криво. Эта строка
C++
1
(int=0,string="",double=0.0)
easybudda
Модератор
Эксперт С++
 Аватар для easybudda
9372 / 5422 / 914
Регистрация: 25.07.2009
Сообщений: 10,423
03.07.2011, 14:54     Файл произвольного доступа #5
Цитата Сообщение от Gepar Посмотреть сообщение
C++
1
2
3
4
void ClientData:: setName(string n) 
{
  strncpy(name,n.data(),14); 
}
Не факт, что проблема в этом, но тем не менее, здесть завершающего нуля не хватает. strncpy(), если ей передать больше символов, чем указано, скопирует, сколько сможет, и '\0' не добавит. Добавьте
C++
1
2
3
4
5
void ClientData:: setName(string n) 
{
  strncpy(name,n.data(),14);
  name[14] = '\0'; 
}
Цитата Сообщение от Gepar Посмотреть сообщение
C++
1
2
3
4
string ClientData:: getName() const 
{ 
  return name; 
}
А это просто не корректно. Функция должна string возвращать, возвращает то ли char* то ли const char*... Оно может само как-нибудь и приводится к нужному виду, но я бы как-то так сделал:
C++
1
2
3
4
5
string ClientData:: getName() const 
{ 
  string ret(name);
  return ret; 
}
И самое главное, что не понятно - а где собственно объекты ClientData инициализируются? По сути Вы в файл случайные данные отправляете...
pito211
 Аватар для pito211
186 / 173 / 8
Регистрация: 22.03.2010
Сообщений: 612
03.07.2011, 15:10     Файл произвольного доступа #6
кстати фаил не закрыл
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,511
03.07.2011, 15:27  [ТС]     Файл произвольного доступа #7
Цитата Сообщение от easybudda Посмотреть сообщение
А это просто не корректно. Функция должна string возвращать, возвращает то ли char* то ли const char*... Оно может само как-нибудь и приводится к нужному виду, но я бы как-то так сделал:
Переводится Хотя я попробую сделать как Вы написали, может эти преобразования как-то затрагивают мою запись в файл ...
Цитата Сообщение от Gepar Посмотреть сообщение
И самое главное, что не понятно - а где собственно объекты ClientData инициализируются?
Так вот же в .h файле всё инициализируется:
Цитата Сообщение от Gepar Посмотреть сообщение
//конструктор по умолчанию который устанавливает
//номер =0, имя = пустой строке и баланс =0
ClientData(int=0,string="",double=0.0);
Добавлено через 1 минуту
Цитата Сообщение от pito211 Посмотреть сообщение
хотя может и правильно, но как то криво. Эта строка
Ага, строка, из которой я потом копирую данные в массив символов.


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

Добавлено через 1 минуту
Цитата Сообщение от easybudda Посмотреть сообщение
скопирует, сколько сможет, и '\0' не добавит. Добавьте
Кстати с вашим кодом будет два хвоста у файла (если меньше 14 символов то будет один символ конца например 10ым, а потом 14ым, можно тоже добавить проверку, ну да пока пусть так

Добавлено через 2 минуты
Учёл все правки, по прежнему каждый раз при открытии файла текстовым редактором символы разные после каждого запуска программы Код в первом посте сейчас поправлю с учётом правок.

Добавлено через 3 минуты
А нет, не поправлю, в любом случае код ClientData.cpp (реализация самого класса) теперь такой:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <string>
using std::string;
 
#include <cstring>
#include "ClientData.h"
 
ClientData::ClientData(int i, string n, double b)
{
    setID(i);
    setName(n);
    setBalance(b);
}
 
void ClientData:: setID(int i)
{
    ID=i;
}
 
int ClientData:: getID() const
{
    return ID;
}
 
void ClientData:: setName(string n)
{
    strncpy(name,n.data(),14);
    name[14] = '\0';
}
 
string ClientData:: getName() const
{
    string ret(name);
    return ret;
}
 
void ClientData:: setBalance(double b)
{
    balance=b;
}
 
double ClientData:: getBalance() const
{
    return balance;
}
Всё остальное не изменилось, что же ещё не так не пойму никак, соответственно если работать с таким классом то потом данные у меня с него читаются не корректно так что надо разобраться почему они записываются каждый раз по разному ... посмотрите ещё пожалуйста, может Вы чего увидите ещё подозрительного ...
easybudda
Модератор
Эксперт С++
 Аватар для easybudda
9372 / 5422 / 914
Регистрация: 25.07.2009
Сообщений: 10,423
03.07.2011, 15:27     Файл произвольного доступа #8
Цитата Сообщение от Gepar Посмотреть сообщение
Кстати с вашим кодом будет два хвоста у файла (если меньше 14 символов то будет один символ конца например 10ым, а потом 14ым, можно тоже добавить проверку, ну да пока пусть так
Нуль-терминированная строка - это та, в которой за последним символом '\0' находится. Если строка помещается в буфере большего размера, то без разницы, сколько там ещё нулей после того, что в конце строки. А вот если нуля совсем не окажется - беда...
И тем не менее - данные для записи в файл откуда берутся?
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,511
03.07.2011, 15:31  [ТС]     Файл произвольного доступа #9
Цитата Сообщение от easybudda Посмотреть сообщение
И тем не менее - данные для записи в файл откуда берутся?
Тоесть? Вот же из main:
ClientData client; - создаю объект класса ClientData, создаю по умолчанию значит номер должен быть равен 0, имя= пустой строке и баланс =0.0. Потом пишу 10 раз его в файл
for (int i=0;i<10;i++)
file.write(reinterpret_cast<const char*>(&client),sizeof(ClientData));
Можно в main и инициализировать мой объект, но это ничего не меняет (всё равно каждый раз когда запускаю программу после выполнения получаю что та же часть символов изменяется, а ведь они не должны меняться, правильно я понимаю ... или всё же могут ... или нет?).
Bers
Заблокирован
03.07.2011, 16:48     Файл произвольного доступа #10
В код особливо не втыкал...
но могу предположить, что
file.write(reinterpret_cast<const char*>(&client),sizeof(ClientData)); //записывает в файл содержимое некоторого куска памяти.

И раз уж данные в файле каждый раз разные, стало быть... в самой памяти каждый раз разное....

А что там вообще в памяти?

Цитата Сообщение от Gepar Посмотреть сообщение
int ID; //номер
char name[15]; //имя
double balance; // баланс
А там есть массив чаров, который нигде явно нулями не инициализируется, а потому имеет полное право содержать всякий разный мусор.

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

Не знаю, как точно работает ваш сеттер. Но если ваш сеттер устанавливает дефолтное "",
то в лучшем случае только первый элемент обнуляется (если вообще обнуляется) и последний - нулём-терминатором.

А весь остальной мусор как был, так и остаётся.


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


/ps проинициализируйте массив чаров явно нулями, тогда мусора не будит. А в файле всегда будит одно и тоже
grizlik78
Эксперт C++
 Аватар для grizlik78
1882 / 1414 / 101
Регистрация: 29.05.2011
Сообщений: 2,958
03.07.2011, 18:32     Файл произвольного доступа #11
Цитата Сообщение от Gepar Посмотреть сообщение
C++
1
strncpy(name,n.data(),14);
Вообще-то я уже говорил вчера, почему вместо .data() здесь надо использовать .c_str()
ForEveR
Модератор
Эксперт C++
 Аватар для ForEveR
7927 / 4709 / 318
Регистрация: 24.06.2010
Сообщений: 10,524
Завершенные тесты: 3
03.07.2011, 19:20     Файл произвольного доступа #12
А чем вариант с boost::serialization не понравился? Я ведь целиковый вариант в прошлой теме дал.
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,511
03.07.2011, 19:46  [ТС]     Файл произвольного доступа #13
ForEveR, а я такой нехороший так и не прочитал, извините, забылось за выходные )
Хотя что-то не сильно гугл выводит меня на информацию о нём и на сайте cplusplus.com нет статьи о нём ...

Цитата Сообщение от grizlik78 Посмотреть сообщение
Вообще-то я уже говорил вчера, почему вместо .data() здесь надо использовать .c_str()
С ней что-то тоже файл меняется в итоге.

Изменил вот так свою реализацию класса (менялась только реализация функции setName)
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <string>
using std::string;
 
#include <cstring>
#include "ClientData.h"
 
ClientData::ClientData(int i, string n, double b)
{
    setID(i);
    setName(n);
    setBalance(b);
}
 
void ClientData:: setID(int i)
{
    ID=i;
}
 
int ClientData:: getID() const
{
    return ID;
}
 
void ClientData:: setName(string n)
{
    const char* lname=n.data();
    int length=n.size();
    length=(length<15 ? length : 14);
    strncpy(name,lname,length);
    name[length]='\0';
}
 
string ClientData:: getName() const
{
    string ret(name);
    return ret;
}
 
void ClientData:: setBalance(double b)
{
    balance=b;
}
 
double ClientData:: getBalance() const
{
    return balance;
}
и ... всё равно меняется результат записи класса каждый раз, есть ещё идеи? Вот про char была хорошая идея, я на него до этого не думал.

Добавлено через 4 минуты
У меня есть ещё main по записи и выводу данных в файл этого класса(я его не постил чтобы не запутывать) так вот после этих исправлений в классе он по прежнему после считывания выводит испорченные данные (портит имя и баланс, номер не портит) так что массив символов имя видать по прежнему всё перекручивает, хотя я вроде уже переписал его так что портить же не должен был он ничего
grizlik78
Эксперт C++
 Аватар для grizlik78
1882 / 1414 / 101
Регистрация: 29.05.2011
Сообщений: 2,958
03.07.2011, 20:28     Файл произвольного доступа #14
Цитата Сообщение от Gepar Посмотреть сообщение
С ней что-то тоже файл меняется в итоге.
Я не говорил, что файл не будет меняться. Вопрос в другом: считывается опять неправильно?

Цитата Сообщение от Gepar Посмотреть сообщение
и ... всё равно меняется результат записи класса каждый раз, есть ещё идеи? Вот про char была хорошая идея, я на него до этого не думал.
Теперь получилось точно как с .c_str(), только в 100500 раз более громоздко
Считывание не работает? Файл можно не смотреть даже.
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,511
03.07.2011, 20:47  [ТС]     Файл произвольного доступа #15
Цитата Сообщение от grizlik78 Посмотреть сообщение
Считывание не работает? Файл можно не смотреть даже.
Нет, оно работает некорректно, притом очень странно, эх ладно, запощу почти полный свой main(надеюсь не испугаю тех кто заходит в тему )
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <iostream>
#include <iomanip>
#include <fstream>
#include "ClientData.h"
using namespace std;
 
void newRecord(fstream&);
void print(fstream&);
int main()
{
    ClientData client;
    fstream file("trans.dat",ios::binary|ios::in|ios::out);
    if (!file)
     cerr<<"Error";
 
    for (int i=0;i<100;i++)
     file.write(reinterpret_cast<const char*>(&client),sizeof(ClientData));
     newRecord(file);
     newRecord(file);
     newRecord(file);
     print(file);
}
 
void newRecord(fstream& file)
{
    if (!file)
     cerr<<"\nSomethin was wrong ...\n";
    cout<<"\nEnter number of new accaunt:\n? ";
    int account;
    cin>>account; //считать номер аккаунта
 
    file.seekp((account-1)*sizeof(ClientData)); //установить указатель на нужный номер
    ClientData client;
    file.read(reinterpret_cast<char*>(&client),sizeof(ClientData)); //считать текущее значение запрошенного аккаунта
 
    //если аккаунт пуст то считать и записать данные
    if(client.getID()==0)
    {
        cout<<"\nEnter name and balance for this client:";
        double balance;
        string name;
        cin>>name>>balance;
        client.setID(account);client.setBalance(balance);client.setName(name);
        file.seekp((account-1)*sizeof(ClientData));
        file.write(reinterpret_cast<const char*>(&account),sizeof(ClientData));
 
        //вывод добавлен чтобы убедиться что сразу после создания записи она нормально считывается
        file.seekp((account-1)*sizeof(ClientData));
        file.read(reinterpret_cast<char*>(&account),sizeof(ClientData));
        cout<<setw(3)<<left<<client.getID()<<setw(10)<<left<<client.getName()<<setw(6)<<left<<client.getBalance()<<endl;
    }
 
    //иначе выдать предупреждение
    else
     cerr<<"\nError, accaunt #"<<account<<" already exist!\n";
}
 
void print(fstream& print)
{
    print.seekp(0);
    ClientData client;
 
    //ищем хотя бы одно заполненное поле в БД
    do
    {
        print.read(reinterpret_cast<char*>(&client),sizeof(ClientData));
    }while (client.getID()==0);
 
    //если такое поле есть то логично вывести подписи к столбцам
    if (client.getID()!=0)
    {
        cout<<setw(3)<<left<<"ID"<<setw(10)<<left<<"NAME"<<setw(6)<<left<<"BALANCE"<<endl;
    }
    //иначе выйти из функции так как все поля БД пусты
    else
     return;
 
    print.clear();//сбросить флаг конца файла
    print.seekp(0);
 
    print.read(reinterpret_cast<char*>(&client),sizeof(ClientData)); //считать первую запись
    while(!print.eof())
    {
        //если запись существует то вывести данные
        if (client.getID()!=0)
        {
            cout<<setw(3)<<left<<client.getID()<<setw(10)<<left<<client.getName()<<setw(6)<<left<<client.getBalance()<<endl;
        }
        print.read(reinterpret_cast<char*>(&client),sizeof(ClientData));
    }
    print.clear(); //сбросить флаг конца файла для выполения последующих операций
 
}
Посмотрите пожалуйста, тут всё очень и очень интересно:
1) Я вызываю 3 раза создание нового клиента (есть менюшка но это ещё + код, не буду запутывать), после его создания я добавил считывание и вывод данных. По работе этого я вижу что всё хорошо и данные после записи считываются нормально. Если пытаешься перезаписать данные то функция отлично видит что они уже есть так что пишет вроде нормально, вроде.
2)Я вызываю функцию для печати данных и что я вижу: поле номер считалось нормально, поле имя пустое(чаще всего), а в поле баланс поселилось что-то непонятное вида 2.53354e-307 которое появляется всегда в нём.

Помогите, справиться с этим злосчастным классом, а, очень хочется разобраться с ним
На всякий случай креплю все файлы в zip архиве, может кому так будет удобнее.
Вложения
Тип файла: zip c++.zip (22.5 Кб, 13 просмотров)
Bers
Заблокирован
03.07.2011, 21:31     Файл произвольного доступа #16
Вы сохраняете объект с мусором. Избавьтесь от мусора в массиве чаров, и будит вам щастье. В чом проблема то?
ForEveR
Модератор
Эксперт C++
 Аватар для ForEveR
7927 / 4709 / 318
Регистрация: 24.06.2010
Сообщений: 10,524
Завершенные тесты: 3
03.07.2011, 21:57     Файл произвольного доступа #17
Gepar, boost::serialization

Не по теме:

А вообще это круто - искать инфу про boost на cplusplus.com.
А в прошлой теме дан вариант записи/считывания через serialization... Посмотри - почитай. Сто процентов получится лучше, чем самому писать в бинарники.

grizlik78
Эксперт C++
 Аватар для grizlik78
1882 / 1414 / 101
Регистрация: 29.05.2011
Сообщений: 2,958
03.07.2011, 23:00     Файл произвольного доступа #18
Сообщение было отмечено автором темы, экспертом или модератором как ответ
В функции newRecord:
C++
1
2
        file.write(reinterpret_cast<const char*>(&account),sizeof(ClientData));
        file.read(reinterpret_cast<char*>(&account),sizeof(ClientData));
Какой-такой account? Чего вы нам голову морочите? account это int, а не ClientData!
Bers
Заблокирован
03.07.2011, 23:57     Файл произвольного доступа #19
кстати, да. 50 и 46 строчка.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
04.07.2011, 14:25     Файл произвольного доступа
Еще ссылки по теме:

C++ Не могу прочитать файл произвольного доступа с конца
C++ В чем ошибка(файлы произвольного доступа)
C++ Корень произвольного степени из произвольного числа, введенного с клавиатуры

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,511
04.07.2011, 14:25  [ТС]     Файл произвольного доступа #20
Тьфу-ты ну ты, вот где собака была зарыта Теперь поправил и оно и сохранило и считало нормально данные (мусор отбрасывается при выводе так как о уже после '\0'). Ну что же, теперь осталось дописать остальные функции по работе с файлом.

Кстати ещё такой вопрос относительно этого класса: как переименовывать созданный файл? Ну был 1.dat, а я после того как скопировал с него данные хочу чтобы он стал 123.dat например. Есть ли в с++ для этого функции какие или надо использовать возможности виндовс через system("") ?

Добавлено через 1 минуту
ForEveR, ну я хочу разобраться с этим методом записи до конца, а потом уже с другими потому и не воспользовался им.

Bers, так а где мусор то, а если без мусора то тогда как по вашему должна выглядеть функция по записи имени в символьный массив (это чтобы ещё и при открытии текстовым редактором файла он выглядел всегда одинаково, хотя это уже и не столь важно).
Yandex
Объявления
04.07.2011, 14:25     Файл произвольного доступа
Ответ Создать тему
Опции темы

Текущее время: 21:26. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru