0

いくつかの属性(文字列)を保持する次のUser.hがあります。User.cppにはすべての定義があります。

//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

別のクラス「File」を使用して、ランダムにアクセスされた.datファイルから新しいユーザーを挿入/ユーザーを表示しています

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

class File{
    public:

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

};
#endif

ファイルクラスの定義

//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";  
}

実行中に上記の問題が発生します。ただし、コンパイルの問題はありません。

内部に「文字列属性」を持つオブジェクトを処理する際に問題はありますか?

助けてください

4

5 に答える 5

1

そのような非PODタイプを読み取ることはできません。文字列はPODタイプではありません。 C ++のPODタイプとは何ですか?

文字列の保存方法に応じて、文字列を正しく読み取る方法はいくつかあります。

テキストファイルの場合:

文字列が空白で両側が区切られた1つの単語である場合は、単純な古い>>演算子を使用できます。複数の単語がある場合は、それを独自の行に格納して、getlineを使用できます。

バイナリファイルの場合:

文字列をnullで終了する形式で格納します。ヌル文字をチェックしながら、一度に1文字ずつ読み取ります。または、文字列のサイズを格納する整数を文字列の前に追加します。それを読むときは、最初に整数を読み、次にその数の文字を読みます。

于 2010-10-30T16:48:31.583 に答える
1

問題は、C++コードとCの考え方を混ぜ合わせていることだと思います。ここで実際に行う必要があるのはopeartor>>()、C++IOストリームとともに抽出演算子を使用することです。read()これは、関数とともにC標準IOを使用するのとは対照的です。オブジェクトの書き込みoperator<<()には、Cwrite()関数の代わりに挿入演算子を使用してください。

文字列を操作するには、を使用しますstd::string。このクラスはとを提供operator<<()operator>>()ます。つまり、 C++IOストリームオブジェクトはどこにあるのかとstd::string s言うことができます。これにより、The Right Thing(tm)が実行されます。ここでの哲学は、クラスがユーザーであるあなたよりもオブジェクトをシリアル化する方法をよく知っているということです。したがって、<<および>>演算子を使用して実行します。io << sio >> siostd::stringstd::string

アイデアを続けると、作成者であるあなたはUser、オブジェクトをシリアル化する方法を誰よりもよく知っていUserます。したがって、クラスのユーザーにサービスとして<<および>>演算子を提供します。User「クラスのユーザー」は、オブジェクトを適切にシリアル化する方法を完全に忘れた1週間後のあなたかもしれません。(または、覚えていると思いますが、実際には詳細を忘れて、コードにバグが発生しています)。例:

// 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ステートメントがどこにあるかを知っています:それはあなたの実装ファイルにあり、それはその実装ファイルでのみ衝突を引き起こします。それは一種の銃です(プラスチック製の水鉄砲ですが、それでも銃です)。自分の足だけを撃つ(濡らす)ことができ、他の人の足を撃つことはできません。私の意見では、これはまったく問題ありません。

于 2010-10-30T18:09:17.883 に答える
0

はい、std :: stringクラスは単純な古いデータではありません。つまり、ポインタが含まれています。この方法で文字列クラスを保存/ロードすると、ポイントされたデータロード/保存されず、ポインタの値のみがロード/保存されます。

さらに、sizeof(tempUser)には、文字列が指すテキストのサイズは含まれません。

解決策は、データの読み取り/書き込み方法を変更することです。1つの方法は、std::stringのようなデータ型を処理するboost::serializationを使用することです。もう1つの方法は、各文字列をテキストドキュメントの個別の行に(バイナリモードではなく)自分で書き込み、readlineを使用してそれらを読み戻すことです。

于 2010-10-30T16:26:00.677 に答える
0

文字列はオブジェクトです。つまり、その内容を記述しているわけではありません。

ユーザーを作成し、ファイルを調べて、私が何を意味するかを確認してください。次に読んでいるのは、無効なメモリ位置を指すいくつかのポインタです。

于 2010-10-30T16:26:25.117 に答える
0

独自のカスタムシリアル化コードを作成するのではなく、GoogleProtocolBuffersのようなものがより少ない労力であなたが望むことをするかもしれません。単純な構造化データを場所から場所へ渡すのに理想的です。

于 2010-10-30T16:38:01.107 に答える