Visual C++ 2008でc++プログラミングの勉強をしています。
ファイルから文字列をfgetsで読み込み、vectorにいれる処理をプログラミングしようとしているのですが、うまくいきません。
input.txt------------------
sumday
monday
tuesday
-------------------------
このような入力ファイルをfgetsで読み込み、各行を文字列としてvectorにpush_backし、 読み込みが終わった後にvectorの全要素をループで出力するというシンプルなものなのですが、以下のようにコーディングしました。
#include <stdio.h>
#include <stdlib.h>
#include <vector>
int main(void){
using namespace std;
FILE *fp;
fp = fopen("input.txt","r");
std::vector<char *> my_vector;
char buff[16];
while(fgets(buff, 256, fp) != NULL) {
char copy_of_buff[16];
std::strcpy(copy_of_buff, buff);
my_vector.push_back(copy_of_buff);
printf("output from fgets..... %s \n", copy_of_buff);
}
vector<char *>::iterator it = my_vector.begin(); //
while( it != my_vector.end() ) //
{
printf("output from vector.... %s \n", *it);
++it; //
}
fclose(fp);
return 0;
}
以下のような出力がなされるものと思っていたのですが、
outputs from fgets .... sunday
outputs from fgets .... monday
outputs from fgets .... tuesday
output from vector .... sunday
output from vector .... monday
output from vector .... tuesday
実際は以下のように、vectorからの出力分がすべて最後にpush_backした"tuesday"となりました。
outputs from fgets .... sunday
outputs from fgets .... monday
outputs from fgets .... tuesday
output from vector .... tuesday
output from vector .... tuesday
output from vector .... tuesday
fgetsしたあとの処理が問題だと思うのですが、原因がよく分かりません。非常に基本的なことだと思うのですがwebで調べてもいまいちわかりません。
原因が分かる方、よろしくお願いします。
No.2ベストアンサー
- 回答日時:
最初のプログラムは「文字列」ではなく「文字列が入っていた場所の先頭アドレス」をプッシュバックしています。
my_vectorが「char *」つまり「文字列が入っている場所の先頭アドレス」を要素に持つと宣言されているのですから、文字列そのものは入れられません。
「中に入っている文字列は毎回違う」のですが、残念ながら「プッシュバックされた文字列の先頭アドレスは毎回同じ」です。
ループでは「©_of_buff[0]」が3回プッシュバックされます。
プッシュバックのループを抜けると「文字列」が破棄されますが「メモリには最後にプッシュバックした時の残骸である、最後の文字列が残って」います。
で、ベクター要素を走査すると、毎回「©_of_buff[0]」が取り出されます。
copy_of_buffは既に破棄されているので、取り出したアドレスが差すメモリには、最後にそこに入れた文字列の残骸があります。
そして、表示ルーチンでは、その「残骸」を表示する事になります。
修正したプログラムでは、stringをプッシュバックしているので、string(のコピー)がプッシュバックされます。
my_vectorが「string」つまり「文字列そのもの」を要素に持つと宣言されているのですから、文字列そのものが入れられます。
最初のプログラムの
char copy_of_buff[16];
を
char *copy_of_buff = malloc(16);
にすれば、とりあえずは動きます。
これが意図した通りに動く理由は「毎回、新しい領域を確保している」「毎回、新しく確保した領域のアドレスをプッシュバックしている」「表示が終るまで確保したメモリを破棄しないで取ってある」からです。
但し、この修正だけでは「確保した領域を使い終わったのに開放してない」ので、不完全です。
++it; //
を
free(*it++); //
に変えて、表示し終わったら解放しましょう。
>最初のプログラムは「文字列」ではなく「文字列が入っていた場所の
>先頭アドレス」をプッシュバックしています。
>「中に入っている文字列は毎回違う」のですが、残念ながら「プッシュ
>バックされた文字列の先頭アドレスは毎回同じ」です。
なるほど。よく分かりました。
やはりC/C++のアドレスなどの概念は(私にとっては)難しいですね。
ループなどの処理と一緒になると、さらにややこしくなりますね。
ありがとうございました。
No.3
- 回答日時:
最初のプログラムが動かなかった理由は、以下のサンプルを見れば一目瞭然だと思います。
int a;
std::vector<int *> my_vector;
a = 10;
my_vector.push_back(&a); // 10はプッシュされない。プッシュされるのはaのアドレス
a = 20;
my_vector.push_back(&a); // 20はプッシュされない。プッシュされるのはaのアドレス
a = 30;
my_vector.push_back(&a); // 30はプッシュされない。プッシュされるのはaのアドレス
a = 40;
vector<int *>::iterator it = my_vector.begin(); //
while( it != my_vector.end() ) //
{
printf("output from vector.... %d \n", **it);
// *itを参照すると「aのアドレス」が取り出される。
// **itを参照すると「aのアドレスが指す中身」が取り出される。
// 「aのアドレスが指す中身」とは「今のaの値」の事。
// 「今のaの値」は40なので「40」しか表示しない。
++it; //
}
このプログラムはchar *がint *になっただけで、やっている事は「質問者さんが最初に書いたプログラムと同じ」です。
これで、最初のプログラムがマトモに動かない理由が理解できたと思います。
No.1
- 回答日時:
>while(fgets(buff, 256, fp) != NULL) {
> char copy_of_buff[16];
> std::strcpy(copy_of_buff, buff);
> my_vector.push_back(copy_of_buff);
> printf("output from fgets..... %s \n", copy_of_buff);
>}
copy_of_buff は while ループをまわる度に確保と破棄を繰り返します。
その先頭アドレスを my_vector に格納しているわけですが、当然ループから抜ければ
先頭アドレスの指す先は「無効」です。
動作しているのは、たまたま、あなたの使っているコンパイラが
毎回「同じ場所に」 copy_of_buff の領域を確保し、ループから抜けた時にその領域を
そのまま放置しているからでしょう。
C++ で書くなら、文字列は string で、ファイルからの入出力は fstream で行いましょう。
ありがとうございます。
string、fstreamをつかって書き直してみました。(少し出力形式が違いますが)
#include <fstream>
#include <string>
#include <vector>
#include <iostream>
using namespace std;
int main( )
{
ifstream ifs("input.txt");
string buf;
std::vector<string> my_vector;
while(ifs && getline(ifs, buf)) {
cout << buf << endl;
my_vector.push_back(buf) ;
}
vector<string>::iterator it = my_vector.begin();
while( it != my_vector.end() )
{
cout << *it << endl;
++it;
}
return 0;
}
結果的に自分の望む出力になりましたが、まだ理由がよく分かりません.......................
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
関連するカテゴリからQ&Aを探す
おすすめ情報
- ・漫画をレンタルでお得に読める!
- ・人生のプチ美学を教えてください!!
- ・10秒目をつむったら…
- ・あなたの習慣について教えてください!!
- ・牛、豚、鶏、どれか一つ食べられなくなるとしたら?
- ・【大喜利】【投稿~9/18】 おとぎ話『桃太郎』の知られざるエピソード
- ・街中で見かけて「グッときた人」の思い出
- ・「一気に最後まで読んだ」本、教えて下さい!
- ・幼稚園時代「何組」でしたか?
- ・激凹みから立ち直る方法
- ・1つだけ過去を変えられるとしたら?
- ・【あるあるbot連動企画】あるあるbotに投稿したけど採用されなかったあるある募集
- ・【あるあるbot連動企画】フォロワー20万人のアカウントであなたのあるあるを披露してみませんか?
- ・映画のエンドロール観る派?観ない派?
- ・海外旅行から帰ってきたら、まず何を食べる?
- ・誕生日にもらった意外なもの
- ・天使と悪魔選手権
- ・ちょっと先の未来クイズ第2問
- ・【大喜利】【投稿~9/7】 ロボットの住む世界で流行ってる罰ゲームとは?
- ・推しミネラルウォーターはありますか?
- ・都道府県穴埋めゲーム
- ・この人頭いいなと思ったエピソード
- ・準・究極の選択
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
関数内で設定したポインタ値に...
-
メモリ関連のエラーを取り除く...
-
C++Builder 2009 テキスト...
-
strcat関数を自作したいです
-
別ファイルの内容を検索したい...
-
char*を初期化したいのですが
-
ASCIIコードへの変換方法
-
char型にint型の数値を代入する。
-
ポインタで詰まりました;
-
C++のnewで確保したメモリーの...
-
VB.NETでファイル名順にファイ...
-
C言語 構造体の中に共用体を定...
-
関数から配列を返すには?
-
C言語のポインタに直接アドレス...
-
malloc呼び出し時のセグメンテ...
-
C言語 配列の長さの上限
-
構造体配列のソート
-
C言語の文字列?処理 strcpyやl...
-
C#で構造体の配列を持った構造...
-
2次元配列を戻り値とする関数?
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
char*を初期化したいのですが
-
C言語のintとcharの違いってな...
-
CStringからchar*への型変換に...
-
C言語にて構造体のメンバがNULL...
-
fstream型オブジェクトを関数の...
-
小数点入りの文字列をfloat型に...
-
char型にint型の数値を代入する。
-
const char* s1とただのchar s1...
-
new charとnew char[N]の違いは?
-
動的メモリの初期化方法について。
-
エクセルのMID関数は、C言語では?
-
C言語 strstrの実装
-
SetWindowTextについて。
-
char 文字列型 の表現範囲が-12...
-
文字列の途中から途中までを抽出
-
2次元配列の文字"列"の初期化方法
-
DWORDとcharの変換
-
C++17で、unsigned char * 配列...
-
C言語の文字リテラル中の16進文...
-
strcat関数を自作したいです
おすすめ情報