C++ ファイル操作 書いて覚えるための初心者自己中記事②
の続き。
binary.txt ファイルにあらかじめ
Hello My name is robot
と入れてあります。
そのファイルを読んで値を16進数で16桁づつ表示する。
------------------------------------------------
const int WIDTH = 16;
int main(){
fstream file;
file.open("binary.txt",ios::in|ios::binary);
if(! file.is_open()){
return EXIT_FAILURE;
}
do{
unsigned char buf[WIDTH]; //グローバル定数?でいいのか? の値分の配列を作成
file.read ( (char*)buf,sizeof buf);//読み込み
for(int i = 0,size = (int)file.gcount() ; i < size ; ++ i){
printf("%02X ",buf[i]);
}
cout << endl;
}while(!file.eof());//eof = end of file ファイルの最後に到達したらtrue でなければfalse
file.close();
}
--------------------------
(結果)
48 65 6C 6C 6F 20 4D 79 20 6E 61 6D 65 20 69 73
20 72 6F 62 6F 74
------------------------------------------------
fstreamクラスのメンバ関数 gcount は最後の操作で実際に取得したバイト数を返してくれる。gcount -> get countの略
do while の2回目以降で行なっている file.read について。
1回目はファイル内の頭からデータを16バイト分取得するのはわかるけど、
2回目以降ってどうなってるの?
まだそのことについての記載がないけど、多分カーソルみたいなのがあって読み込んだところで止まってるんだろう。きっと。
こういった感じでファイルやメモリの中身をチェックする行為をダンプ( dump )というそうです。
次にファイルの複製。
------------------------------------------------
const char FILE_NAME1 = "hello.txt";
const char FILE_NAME2 = "hello2.txt";
const int BUF_SIZE = 1024;
int main(){
fstream src;
fstream cpy;
char buf[BUF_SIZE];
src.open(FILE_NAME1,ios::in|ios::binary);
if(!src.is_open()){
return EXIT_FAILURE;
}
cpy.open(FILE_NAME2,ios::out|ios::binary);
if(!cpy.is_open()){
src.close();
return EXIT_FAILURE;
}
do{
src.read( (char*)&buf,sizeof buf);
cpy.write( (char*)&buf,src.gcount());
}while(!src.eof());
src.close();
cpy.close();
}
------------------------------------------------
さっきのやり方でできた。
read でも write でも引数がすらすらできないなぁ。
charポインタでキャストする。その為に 変数のアドレス露出で & を付ける。
さっきは1文字、というか1バイトづつ表示させるためにwhile の中で for してたけど
今回はただコピーするだけだから必要なし。
fstreamクラスのオブジェクト cpy をopen できなかった時の処理で src をclose するのは忘れないようにしないと。
あと write する時の引数でサイズは src.gcount( ) にするのに気づかなかった・・。
直したけど。
ファイルの入出力では色んなエラーがあるらしい。
streamクラスのメンバ関数で fail はエラーが出ると true を返すらしい。
なので fail を積極的に組み込む方が良い。
------------------------------------------------
const char FILE_NAME1 = "hello.txt";
const char FILE_NAME2 = "hello2.txt";
const int BUF_SIZE = 1024;
int main(){
fstream src;
fstream cpy;
char buf[BUF_SIZE];
src.open(FILE_NAME1,ios::in|ios::binary);
if(!src.is_open()){
return EXIT_FAILURE;
}
cpy.open(FILE_NAME2,ios::out|ios::binary);
if(!cpy.is_open()){
src.close();
return EXIT_FAILURE;
}
bool error = false; //最後にエラーだった時の処理を纏める為、変数にエラーだった場合trueを入れておく
do{
src.read( (char*)&buf,sizeof buf);
if(src.fail() && !src.eof()){ //fail() == true && eof() == false の時は errorにtrue
error = true;
break;
}
cpy.write( (char*)&buf,src.gcount());
if(cpy.fail()){ //fail() == true の時はerrorにtrue
error = true;
break;
}
}while(!src.eof());
src.close();
cpy.close();
if(error){ //errorがtrueだった場合の処理
cout << "ファイル操作でエラーが発生しました。" << endl;
remove(FILE_NAME2); //コピー先のファイルを削除
return EXIT_FAILURE;
}
}
------------------------------------------------
fstreamクラスのメンバ関数 fail( ) を使ってエラー処理を追加した。
エラーが発生したと判明したら do while を抜けてファイルを close してエラー処理。
ファイル削除の関数 remove( ) は#include <cstdio> です。
なお、read( ) の際のエラーチェックで eof( ) もチェックしていますがこれは、
ファイルの終わりまで来た時にも fail( ) は true を返すそうで、eof( ) でファイルの終わりまで来たかどうかも一緒にチェックしないといけないそうです。迷惑ですね。
あと、 src.fail( ) で行いましたが fstreamクラスはすごいですね演算子オーバーロードされているそうで ! src でも同じことができるそうです。
次、
さっき2回目のread で途中から入力されてるのはなんで?カーソルみたいなのがあるんだろう。と言ってたけど、判明。
ファイルポインタ
ファイルの入出力後にその場にとどまるカーソルみたいなの。
ファイルポインタの位置を操作する関数
fstreamクラスのメンバ関数で
seekg(バイト数);
ファイルの先頭から引数で指定したバイト数の位置に移動する。
seekg の g は get の事、読み出し用のファイルポインタを移動させられる。
fstreamクラスのメンバ関数 open の引数にあるフラグで ios::in の場合はseekg という事なのか。
fsreamクラスのメンバ関数でclear( ) について
ファイルアクセスの警戒モードを解除する関数。
警戒モード(俺がわかるようにいってるだけの言葉) はfstreamクラスでopenしたファイルでなんらかのエラー状態になると以後操作が出来なくなる状態のこと。
このなんらかのエラーにはファイルの終端に到達した場合も含まれる。
あってるかどうかわからないけど、聞いてる限りではfstreamクラスのメンバ関数failがエラー認定してるイメージ。failはeofがtrueでもtrueになるって言ってたから。
とにかくそれを解除するのがfstreamクラスのメンバ関数clear です。
clearがなんなのか知らないでコード見てた時は、消しちゃったよ!って思った。
seekg の他。
seekp について。
seekg が読み出し用のファイルポインタ移動関数
seekp は書き出し用のファイルポインタ移動関数
です。
テキストを読むと、実際はseekg でもseekp でも同じだそう。
読み出し用、書き出し用で別になっていないらしい。・・・?
そして seekgもseekg も引数としてバイト数を入れるが、もう一つ引数を入れることができる。このもう一つの引数にはフラグをいれられる。
ios::beg ファイルの先頭から移動(フラグ無しと同じ --デフォルト引数?)
ios::cur 現在の位置から移動
ioa::end ファイルの末尾から移動
引数のバイト数には負の値を入れてバックすることも可能。
ここまでがファイルポインタを移動させるstreamクラスのメンバ関数。
あとは、ファイルポインタの位置を確認するメンバ関数がある。
tellg
tellp
さっきの話と同様、同じです。
このメンバ関数が返すファイルポインタの位置は streampos という型だそうで正体不明です。今は、かも。
とにかくこの帰ってきた値に普通に足し算とかやっちゃだめ。
じゃあ何に使うかというとseekg やら seekp への引数としてしようが可能。
streampos pos = file.tellg ( ) ;
file.ssekg( 30 );
~~何か処理したのち~~
file.seekg( pos );
で元の場所に戻す。とかの使い方。
ファイルを読み込んで16進数を横に16・縦に16で1ページとして表示させてコマンドでページ移動できるもの。(テキストまま)
------------------------------------------------
const int WIDTH = 16;
const int HIGHT = 16;
const int PAGE_SIZE = WIDTH * HIGHT;
class DumoFIle{
public:
bool Run();
private:
bool Open();
void Close();
void Dump();
bool Control();
private:
fstream c_file;
int c_page;
};
~~~~~~~~~~~~~~~
bool DumoFIle::Run(){
if(! Open()){
return false;
}
do{
Dump();
}while(Control());
Close();
return true;
}
bool DumoFIle::Open(){
string filename;
cout << "ファイル名を入力して下さい" << endl;
getline(cin,filename);
c_file.open(filename.c_str(),ios::in|ios::binary);
c_page = 0;
return c_file.is_open();
}
void DumoFIle::Close(){
c_file.close();
}
void DumoFIle::Dump(){
cout << endl;
c_file.clear();
c_file.seekg(c_page * PAGE_SIZE);
for(int h = 0 ; h < HIGHT; ++h){
unsigned char buf[WIDTH];
c_file.read( (char*)buf,sizeof buf);
for(int w = 0 ,size = (int)c_file.gcount() ; w < size ; ++w){
printf("%02X ",buf[w]);
}
}cout << endl;
}
bool DumoFIle::Control(){
while(true){
int command;
cout << "1.次へ 2.前へ 3.終了" << endl;
cin >> command;
switch(command){
case 1:
if(! c_file.eof()){
++c_page;
return true;
}else{
//コマンド再入力
break;
}
case 2:
if(c_page > 0){
--c_page;
return true;
}else{
//コマンド再入力
break;
}
case 3:
return false;
}
}
}
~~~~~~~~~~~~~~~
int main(){
DumoFIle file;
if(! file.Run()){
return EXIT_FAILURE;
}
}
------------------------------------------------
一回自分で作って見たらうまくいかなかった・・・。
表示はちゃんと出来たんだけどファイルの終わりまで行くと空白になって、戻っても空白のまま。
何がダメだったのかいまいち。。。
でも自分で作るのはコード見て理解するのとは脳の使う場所が違うからとてもいい。
まだファイル操作続くからとりあえずここまで。