質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
char

charは文字データ型を指します。一文字分の文字コードの格納を想定としている型です。

ファイル

ファイルとは、文字列に基づいた名前又はパスからアクセスすることができる、任意の情報のブロック又は情報を格納するためのリソースです。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

5回答

1768閲覧

char*型変数の扱いが分からない

tetatetu

総合スコア26

char

charは文字データ型を指します。一文字分の文字コードの格納を想定としている型です。

ファイル

ファイルとは、文字列に基づいた名前又はパスからアクセスすることができる、任意の情報のブロック又は情報を格納するためのリソースです。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2021/03/27 10:23

編集2021/03/27 10:27

はじめに、質問内容が錯綜気味であることをお許しください。問題の切り分け自体に困難を覚えている状態で、藁をもすがりたいという思いで質問させて頂きます。

Visual Studioを使って他人が書いたC++のプログラムを読んでいるのですが、その中で、**「char*型の引数filenameが名前のファイルを新しく作って、書き込み処理をして保存する」**という目的で、以下のようにofstreamを使った関数があります:

void createFile(const char* filename){ ofstream file; file.open(filename); //以下、ファイルの書き込み処理 }

このスニペットでは、もし引数に指定した新規ファイル名と同じ名前のファイルが該当のディレクトリに存在する場合、そのファイルの中身を上書きしてしまう、というものでした。
これを避けるために、よくあるように**「同名のファイルが作成されようとしている場合、その新規ファイルのファイル名について、そのファイル名の末尾に数字を付け足すことで上書きを回避する」という機能を付けようと思いました**。そのために、このサイトを真似して、まず最初に引数のfilenameと名前が一致するファイルがディレクトリに存在するかチェックして、存在しなければそのまま通して、存在するならば名前が一致しなくなるまでfilenameの中身に数字をつけ足す、というコードを実装しようとしました:

void createFile(char* filename){ // filenameと一致するファイルが存在するかチェック ifstream fileCheck; fileCheck.open(filename); for(int i=1;;i++){ if(!fileCheck.fail()){//同名ファイルが既に存在していた場合 //名前が一致するファイルがなくなるまでfilenameの中身を更新 *filename = *filename + '_' + i; }else{//同名ファイルが存在しない場合 fileCheck.close(); break; } } ofstream file; file.open(filename); //以下、ファイルの書き込み処理 }

引数filenameの中身を書き換えることになるので、データ型をconst char*からconstを取り外してchar*単体に変更しました。
しかし、*filename = *filename + '_' + i;の個所で、データ型に関するエラーが発生します。

(事情があり、いま元のエラーメッセージの原文を表示することが出来ません。申し訳ありません。)

私の憶測ですが、filenamechar*型にもかかわらず、char型である*filenameの中に複数のchar型およびint型の値を連結してできたstring型の値を代入している、ということでしょうか?

char*型というデータ型について理解できていないのが原因だと思い、調べたのですが、よくわかりません。以下に、私が行った試行錯誤を示します:

  • はじめ、char*を単に「char型のポインター」だと思い、「char型なら『文字』であって、『文字列』ではないから、そもそもfilenameに『ファイルの名前という文字列』を代入するのは不可能ではないか?」と思いました。しかし、このソースコードでこのcreateFile関数を使っている場面では、普通にstring型の値を放りこんでおり、混乱しました。
  • あるサイトの解説によると「char*型はstring型が出来る前の昔の記法」ということだったので、string型として使ってよいのかと思いました。
  • しかし、上のエラーのように、*filenameの中にstringを放り込もうとすると、怒られます。

何が間違っているのでしょうか?

大変説明不十分で錯綜気味な質問で申し訳ないですが、ご回答頂けると幸いです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dodox86

2021/03/27 10:37

> これを避けるために、よくあるように「同名のファイルが作成されようとしている場合、その新規ファイルのファイル名について、そのファイル名の末尾に数字を付け足すことで上書きを回避する」という機能を付けようと思いました。 出発点が苦肉の策なのか、それとも発想に無かっただけなのか分かりませんが、「同名のファイルの場合、新規作成ではなく、後ろへ追記」と言う選択肢はないのでしょうか。ファイルopenの操作に関して、様々な開き方があります。あと、根本的にcharやchar *のポインタなどのプリミティブな変数の操作とC++のstd::stringの操作を混同しています。
tetatetu

2021/03/27 10:52

ご回答ありがとうございます。 >同名のファイルの場合、新規作成ではなく、後ろへ追記」と言う選択肢はないのでしょうか それについてですが、今回開発中のプログラムの目的上、同名のファイルがいくつも生成されることが起こりえます。また、一度生成したファイルを後から読み込んで、ファイルの末尾に追記する、という操作は特に考えておりません。 文字通り、新たにファイルを生成する際に、既存のファイルのファイル名とのダブりによる上書きの回避、が目的です。
tetatetu

2021/03/27 11:00

>根本的にcharやchar *のポインタなどのプリミティブな変数の操作とC++のstd::stringの操作を混同 しかし、上記の`createFile`関数の`char*`型の引数に`string`型の値を渡している記述がありました。(より正確には、ダブルクオーテーション""で囲まれたものの後に`.c_str()`が付いたものを引数に渡している) また、他の解説をみると、通常は`filename`に当たる部分はダブルクオーテーション""で囲まれた文字列を渡しているケースが多いのですが、これはstring型とは違うのでしょうか?
maisumakun

2021/03/27 11:05

> また、他の解説をみると、通常は`filename`に当たる部分はダブルクオーテーション""で囲まれた文字列を渡しているケースが多いのですが、これはstring型とは違うのでしょうか? const char[](const char*としても扱える)です。
kansuke_t

2021/03/27 14:37

文字列の連結が上手くいったとしても, ファイルが,hogeとhoge_1があったらファイル名の重複がおこるので, 数字を付け足すときに,付け足し後のファイル名が重複していないか, 確認する必要があるのではないでしょうか? 勘違いだったらすみません。
dodox86

2021/03/28 03:50

>@質問者tetatetuさん [2021/03/27 20:00]の本欄コメントより: > 上記の`createFile`関数の`char*`型の引数に`string`型の値を渡している記述がありました。(より正確には、ダブルクオーテーション""で囲まれたものの後に`.c_str()`が付いたものを引数に渡している) どんなコードか分かりかねるのですが、例えば以下のようなものでしょうか? ("hello!"と出力されます) #include <iostream> void createFile(const char* p) { std::cout << p << std::endl; } int main() { createFile(std::string("hello!").c_str()); return 0; } 既にいくつもていねいな回答をいただいていますので、良く読みましょう。分からなければ、それぞれにフィードバックすべきです。
tetatetu

2021/03/28 10:29

dodox86様 はい、その通りになります。他の回答者の方々のお話をお聞きすると、ポインタと配列の関係についてよくわかっていないことによる考え違いが生じていたのだと思いますので、そのあたりの勉強をすれば自然と理解できると信じ、確認してまいりたいと思います。ご回答ありがとうございました。
guest

回答5

0

あるサイトの解説によると「char*型はstring型が出来る前の昔の記法」

そのココロは、昔とは全然違うということであって、お互いに入れ替えが効くということではないでしょう。例えば、昔は時計はネジを巻いていたけどいまは電池で動くから、新しい時計にネジを付けたらなにか動くかというとそんなことは無いし、昔の時計に電池を突っ込んでもなにも起こらないです。(実際には、stringのデータをchar型に渡したり、char型をstring型に暗黙に変えてしまう仕組みはありますけれど)

char*を単に「char型のポインター」だと思い

いや、それは間違ってないですよ。char*は単にchar型へのポインタです(char型へのという読み方?呼び方?を強く推奨します)。ただ、「単にポインター」があるという言い方は、「単なるポインターではない」なにものかがあると考えているのでしょうか? そんなものはないと思いますが。

char型なら『文字』であって、『文字列』ではない

その通りですが、では文字列ってなに? 文字の配列で、終端に'\0'がついているもの。配列は、要素となる型のデータが密に、順に並んでいるものですから、C/C++では、配列をその先頭の要素へのポインタを以て扱います。先頭の位置がわかれば、2番目、3番目...と順に辿って配列全体にアクセス出来ますからね。なので、char*型で文字列を扱える、ということになります。
ついでにここで確認しておくと、文字型というのは「(少なくとも)文字に割り当てられた範囲の数値を扱うことができる整数型」だったりします。

私の憶測ですが、filename がchar型にもかかわらず、char型であるfilenameの中に複数のchar型およびint型の値を連結してできたstring型の値を代入している、ということでしょうか?

これは間違い。string型の値が最初からあって、それにchar型やchar*型を絡めた演算をするなら結果がstring型になることはあります。しかし、それにはstring型が絡むことが必要であって、string型なんて全然ないところに突如string型が生まれることはありません(その意味では、質問で'string型'に言及しているところは全て誤りです。そのプログラムにstring型は登場していないので)。

char型の値に参照演算子を作用させたら、それはcharが指している単独のchar型の値になります。
*filename /* char型 */ = *filename /*char型*/ + '_' /* char型 */ + i /* int型 */;
右辺は単なる数値計算に過ぎません。値としてはint型の範囲を取りうるので、コンパイル時のオプションによっては左辺のchar型を溢れてしまう可能性があるので警告ぐらいは出るかもしれませんが、通常特に文句を言われること無くコンパイルを通ると思います(Javaだとchar型に対するint型データの代入はエラーになりますけどね)。本当に「エラー」になったのですか?
で、この左辺が
filenameになっているあたりがやりたいことに対して全然合っていないです。
*filenameってのはつまりfilename[0]と等価なわけで、その式はfilenameの最初の文字をゴニョゴニョといじっているだけ。それはどう考えてもやりたいことと違うでしょう。

まず、ポインタの扱いについては特訓が必要に思えます。
それとは別に、string型の扱いもちゃんと知っておいたほうがいい、というところでしょうか。

聞かれていない気もしますが、どうするかと言う話てあれば
Cであれば、

C

1void createFile(const char* filebody){ 2 char filename[MAX_PATH]; //MAX_PATHはOSで許されるファイル名の最大長 3 int i=0 4 snprintf(filename,MAX_PATH,"%s_%d",filebody,i); 5 File* fp=fopen(filename,"r");

とでもするし、
c++であれば

C++

1void createFile(const string filebody){ 2 string filename; 3 int i=0; 4 filename = filebody + '_' + std::to_string(i); 5 std::ofstream file; 6 file.open(filename);

などとすればよいのではないかと思います。

投稿2021/03/27 13:41

編集2021/03/27 13:48
thkana

総合スコア7610

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tetatetu

2021/03/28 10:24

ご回答ありがとうございます。 詳しく解説して頂き大変助かります。ご指摘の通り、ポインタと配列の関係性、文字/文字列などの基礎知識の不足からきた考え違いのようですので、その辺りの訓練を徹底して参りたいと思います。 サンプルコードの方も参考にさせていただきます。
guest

0

*filename = *filename + '_' + i;
これは、「ファイル名の末尾に数字を付け足す」コードではありません。

createFile の呼び出し元で、char filename[] = "abc.txt" だったとすると、

*filename は、filename[0] で 'a'、すなわち 0x61 です。
'' は 0x5f ですから、i が 0 の場合、
*filename + '
' + i は、0x61 + 0x5f + 0x00 で 0xc1 です。
filename は "\xc1" "bc.txt" になりました。
文字コードが Shift-JIS だったら "チbc.txt" です。

次にトライするのが
0xc1 + 0x5f + 0x01 で 0x122 です。
filename[0] は char なので 0x22、すなわち '"' になります。
fillename は、""bc.txt" になりました。

追記
やりたいことはこういうことでしょう。

C++

1#include <iostream> 2#include <fstream> // ifstream, ofstream 3#include <string> // to_string 4using namespace std; 5 6void createFile(const string& filename) 7{ 8 string fname = filename; 9 int i; 10 for (i = 1; i < 1000; i++) { 11 ifstream fileCheck(fname); 12 if (fileCheck.fail()) break; 13 fname = filename + '_' + to_string(i); 14 } 15 if (i == 1000) return; 16 17 ofstream file(fname); 18 file << "abcdefg\n"; 19} 20 21int main() 22{ 23 createFile("abc"); 24}

最初に実行すると、abc ができて、次に実行すると abc_1 ができます。
ファイルは 1000個までしかできないように制限しています。

投稿2021/03/27 12:26

編集2021/03/27 15:13
kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tetatetu

2021/03/28 10:20

ご回答ありがとうございます。勘違いを詳しく説明して頂いてありがとうございます。ポインタ、配列、文字、文字列周りの基礎知識の不足からきた間違いのようですので、その辺りの勉強して参りたいと思います。 サンプルの方もありがとうございます。やはりchar*ではなく、stringを使う方が良いみたいですね。
guest

0

filenameのポインタの領域は、データサイズがどれだけあるのか不明なので使用しないほうが良さそうです。filenameに追記するとエラーになると思います。

例えば、新規領域を用意してファイル名を新たに作るのが良いと思います。

C

1void createFile(char* filename){ 2 // filenameと一致するファイルが存在するかチェック 3 char temp_name[1024]; 4 char * filename_kouho = filename; 5 for(int i=1;;i++){ 6 ifstream fileCheck(filename_kouho); 7 if(fileCheck.is_open()){ 8 //新しいファイル名の領域を用意する。 9 //初期化する。 10 memset(temp_name,0, 1024); 11 //元の名前の長さを取得する。 12 int len = strlen(filename); 13 14 //元のファイル名をコピーする。 15 strncpy(temp_name, filename, len); 16 17 //元のファイル名の後ろに、数字を入れる。 18 sprintf(&temp_name[len], "%d",i); 19 20 //次のチェック候補とする。 21 filename_kouho = temp_name; 22 //*filename = *filename + '_' + i; 23 }else{//同名ファイルが存在しない場合 24 fileCheck.close(); 25 printf("%s\n", filename_kouho); 26 break; 27 } 28 } 29 30 ofstream file; 31 file.open(filename_kouho); 32 file.close(); 33 printf("create file\n"); 34 35 //以下、ファイルの書き込み処理 36} 37

投稿2021/03/28 10:30

編集2021/03/28 13:06
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazuma-s

2021/03/28 11:50

char temp_name[1024]; が if文の中のブロック内で宣言されていますが、 これはブロック内でしか寿命がない自動変数なので、 その領域へのポインタを filename_kouho に持たせて、 ブロックの外からアクセスするのは未定義動作です。 char temp_name[1024]; を for文の前に置いてください。
退会済みユーザー

退会済みユーザー

2021/03/28 13:07

その通りですね!修正しました。指摘ありがとうございます!
tetatetu

2021/03/28 13:10

ご回答ありがとうございます。より安全なメモリの使い方として参考にさせて頂きます。
guest

0

char* で渡された文字列は基本、後で変更はできない、と思っときましょう。
C++なら、stringが使えるので、それに変換してからどーこーするのがいいでしょう

投稿2021/03/27 10:55

y_waiwai

総合スコア87719

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tetatetu

2021/03/27 11:04

ご回答ありがとうございます。この場合` void createFile(char* filename)`の引数をいっそ`char*`ではなく`string`にしてしまうということは出来るのでしょうか?
y_waiwai

2021/03/27 11:05

そりゃできるでしょうけど、呼び出す方を全部修正しなければならないですが、そこらへんは大丈夫でしょうか
cateye

2021/03/27 11:36 編集

ファイル名の長さ(文字数)の最大値は、分かりますか? ・・・もしそうなら、ファイル名以上長さを持つ配列に、ファイル名をコピーして末尾に文字(列)を追加すればなんとかなりそう。
tetatetu

2021/03/28 10:07

cateye様 ご回答ありがとうございます。ファイル名の長さは可変になっており、データのもつ初期条件によって変化します。なので、ファイル名の長さによって条件分岐するのは難しいかもしれません。 しかし、同じファイルのものがあった場合に、ファイル名を配列にコピーして、push_backで文字を追加するのは出来そうです。参考にさせていただきます。
tetatetu

2021/03/28 10:08

y_waiwai様 修正はそこまで数が多くないので可能だと思います。しかし、やはり今回はchar * のまま、関数内部でstringにパースする処理をしようと思います。
dodox86

2021/03/28 10:49

>@質問者 tetatetuさん [2021/03/28 19:07]のコメントより: > なので、ファイル名の長さによって条件分岐するのは難しいかもしれません。 cateyeさんが書かれているファイル名以上の長さの配列、というのはそういうことではなくて、 システムがファイル名として持ち得る最大の長さの配列をあらかじめとっておき、その配列上で操作する、と言う意味です。例えばthkanaさんの回答でのC言語での参考コードがそれにあたります。 システムが持ち得るファイル名(パス名)最大の長さ、と言うのはちょっと深い話があるのですが、おおむねMAX_PATHであるとか、PATH_MAXとかのマクロ(#define)で定義されていて、char path[MAX_PATH]などと宣言してそれを使えばおおむねOK、と言うことになっています。
kazuma-s

2021/03/28 11:53

void createFile(char* filename) を、 void createFile(String filename) にしても、void createFile(const String& fillename) にしても、 呼び出し元の char * を変更する必要はありません。 String のコンストラクタが呼び出されて初期化されるからです。
guest

0

自己解決

皆様ご回答ありがとうございます。
皆様のご回答を拝見したところ、ポインタ、配列、文字、文字列周りの基礎知識の不足からきた間違いのようですので、その辺りの勉強して参りたいと思います。今回の問題の理解のために必要な勉強個所が分かり、大変な収穫でした。

なお、今回は急ぎの事もあり、y_waiwai様のご回答にあるように、char*型で渡されたfilenameを関数の内部でstringに変換して処理する、というやり方を取ることにしました。

ベストアンサーですが、みなさまのご回答があまりにも有難く甲乙つけがたいため、申し訳ありませんが、今回は無しということにさせて頂きます。(これがマナー違反でしたら申し訳ありません。)
(代わりになるか分かりませんが、皆様全てのご回答に高評価を押させて頂きます)

重ねまして、皆様ご回答ありがとうございました。

投稿2021/03/28 10:37

編集2021/03/28 13:13
tetatetu

総合スコア26

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問