トップ回答者
C++/CLIでの静的な配列について

質問
-
VC2++2008のwindowsフォームアプリケーションで以下のような機能のプログラムを作成しようとしています。
Timerコントロールを使用し,Timer_Tickイベントが起こるたびに配列の中に数値を入れていく。
そのために,以下の様なコードを書きました。
private: System::Void timer2_Tick(System::Object^ sender, System::EventArgs^ e) {
array<int>^a = gcnew array<int>(150);
a[i] = 入れたいデータ;
i++;
}
上記のプログラムを実行すると,データは入っていますが, i の要素以外は全て 0 になってしまいます。
VBAなどのように,配列を静的な変数として使用するにはどのようにすればよろしいでしょうか?
初歩的な質問ですが,宜しくご教授お願い致します。
回答
-
質問の説明の仕方から、スコープなどに理解不足を感じたので参照先を示しましたが、こんどはサンプルを提示します。ウィザードで作成し、ボタンをstartとstop追加して、タイマーを設定・動作させています。配列の数値表示はしないので、ブレークポイントを設定して変数の中身を見てください。VS2005、Windows7 64bitでビルドし動作を確認しました(とりあえず動いたというだけ)。
このコードで、何がどのように動作しているのか理解してから自分のプログラムをしてください。理解せずに適当にやっていくと、どんどん泥沼にはまります。他の方々が既に指摘していますが、図書館に行くとかして自分が分かる内容の書籍(<=意外と重要です)を探すことをお勧めします。インターネットで探してもいいのですが、経験的に、最初は本の方がよいです(なぜかというと、索引があるのと、検索したい単語が書籍の文章中に出てくるから)。
それに、私のサンプルもマズイ部分があるとも限りませんから。
#pragma once namespace TimerTestForm { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); myTimer = gcnew System::Windows::Forms::Timer; a = gcnew array<int>(150); i = 0; myTimer->Tick += gcnew EventHandler( TimerEventProcessor ); myTimer->Interval = 1000; myTimer->Start(); } protected: ~Form1() { if (components) { delete components; } if(a) delete a; if(myTimer) { myTimer->Stop(); delete myTimer; } } private: static System::Windows::Forms::Timer^ myTimer; static array<int>^a; static int i; private: System::Windows::Forms::Button^ button1; private: System::Windows::Forms::Button^ button2; System::ComponentModel::Container ^components; static void TimerEventProcessor( Object^ /*myObject*/, EventArgs^ /*myEventArgs*/ ) { if(i >= 10) i = 0; a[i] += 10; i++; } #pragma region Windows Form Designer generated code #pragma endregion private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { i = 0; myTimer->Start(); } private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) { myTimer->Stop(); } }; }
- 回答としてマーク 山本春海 2012年4月23日 8:33
すべての返信
-
ご返信ありがとうございます。
>>毎回aが確保されるので、i以外は初期値としての0が入っていて、iはたった今代入したから値が入っているという気がします。
ご指摘のことに気づいて,配列の宣言を同じヘッダーファイル内のイベント外に記入したところ,『定義されていない識別子です。』とのエラーが出てしまいました。
お教えいただいたリンク先のサンプルを拝見させていただきましたが,C++とC++/CLIで記述方法の違いがあるためかうまく動作しませんでした。
まだ,勉強不足で,クラスの設計方法もよくわかりませんので,クラスを使用しない方法で解決することはできないでしょうか?
-
TV のクイズ番組を見ることはありますか?出題者が問題を読んでいる途中で回答者が回答権を得た場合、間違えることが多いですよね。特に、手を挙げたところで「~ですが」と聞こえた場合。問題は、最後まで聞かなければわかりません。こういう場所で情報を得るときも同じです。発生している事象、すなわち問題を、もれなく伝えなければ正しい回答は得られません。
『定義されていない識別子です。』とエラーになるとき、定義されていない識別子は何か、ということも、表示されていませんか?そしてそれは、array<> だったりしませんか?
C++ から離れて長いし、特にC++/CLI は触っていないので外しているかもしれませんが、今宣言しているのはグローバルな場所で、そこで宣言するようなことはやめましょう、というのが最近の流れです。必要最小限に公開するように考えましょう。
そして、まずは、本屋に行って5~6冊立ち読みし、理解できそうなものを1~2冊購入してくることを勧めます。Amazon 等ではウェブ立ち読みというものもできますので、そういうものも利用してください。そして、1~2週間時間をもらって、まずは勉強してください。そこまで時間がとれなくても、2~3日はもらって、まずは座学で勉強してください。結果的に、そうする方が短時間でよりよいものを作れます。これは、経験則です。
Jitta@わんくま同盟
-
C++/CLI は C++ をある程度理解し、.NET Framework の知見があることが前提といえる言語環境です。
C++ での経験が浅い状態ではかなり茨の道といえますが、なぜ、C++/CLI を選ばれたのでしょうか。
(インスタンス変数の概念がわからないあたりで、C++ の経験がないか、足りないと判断しています)どうしても、C++/CLI で書き続けないといけないのであれば、その手の入門書籍を探して、基礎から学んでください。
そして、クラスとは何か、インスタンス変数とは何か、C++/CLI でどう書かなければならないかを調べてください。// デフォルトでもインスタンス変数ありますよね。components とか。
// まねして変数を定義しつつ、初期化はコンストラクターでやると実現できるでしょう。
// ただ、適当にやって回避するのではなく、なぜこれでうまくいくのか、どういった書き方なのかは
// きちんと学んでください。質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
-
質問の説明の仕方から、スコープなどに理解不足を感じたので参照先を示しましたが、こんどはサンプルを提示します。ウィザードで作成し、ボタンをstartとstop追加して、タイマーを設定・動作させています。配列の数値表示はしないので、ブレークポイントを設定して変数の中身を見てください。VS2005、Windows7 64bitでビルドし動作を確認しました(とりあえず動いたというだけ)。
このコードで、何がどのように動作しているのか理解してから自分のプログラムをしてください。理解せずに適当にやっていくと、どんどん泥沼にはまります。他の方々が既に指摘していますが、図書館に行くとかして自分が分かる内容の書籍(<=意外と重要です)を探すことをお勧めします。インターネットで探してもいいのですが、経験的に、最初は本の方がよいです(なぜかというと、索引があるのと、検索したい単語が書籍の文章中に出てくるから)。
それに、私のサンプルもマズイ部分があるとも限りませんから。
#pragma once namespace TimerTestForm { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); myTimer = gcnew System::Windows::Forms::Timer; a = gcnew array<int>(150); i = 0; myTimer->Tick += gcnew EventHandler( TimerEventProcessor ); myTimer->Interval = 1000; myTimer->Start(); } protected: ~Form1() { if (components) { delete components; } if(a) delete a; if(myTimer) { myTimer->Stop(); delete myTimer; } } private: static System::Windows::Forms::Timer^ myTimer; static array<int>^a; static int i; private: System::Windows::Forms::Button^ button1; private: System::Windows::Forms::Button^ button2; System::ComponentModel::Container ^components; static void TimerEventProcessor( Object^ /*myObject*/, EventArgs^ /*myEventArgs*/ ) { if(i >= 10) i = 0; a[i] += 10; i++; } #pragma region Windows Form Designer generated code #pragma endregion private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { i = 0; myTimer->Start(); } private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) { myTimer->Stop(); } }; }
- 回答としてマーク 山本春海 2012年4月23日 8:33
-
重箱の隅だと思いますが、これはちょっと…という点。
1.変数 a を150個の配列としていますが、タイマー コールバックでは10個までしか使っていません。どちらが正しいのでしょう?今回はたまたま使用する側の数が少ないですが、多かったなら・・・
2.150個で十分だと思っていましたが、実は200個必要になりました。この時、150がマジック ナンバーとして定義されているので、何カ所直さなければならないか、わかりません。
3.フォームを複数作って別々にカウントしたいのですが、どのフォームも同時にカウントされます。
VBA はわかりませんが、C では、ローカル変数に static と付けることで、関数から抜けても値が保存される変数、静的変数を宣言できました。C++/CLI では、ローカル変数に static と付けることはでき・・・C# はできないのだけれど、C++/CLI はどうなんだろう?できないとすると、インスタンス変数(フィールド変数)で代用します。このインスタンス変数(フィールド変数)を static と宣言するとクラス変数になり、全てのインスタンスで値が共有されます。C のローカル変数を静的に宣言するのと同じ感覚であれば、スコープが広くなりますが、インスタンス変数(フィールド変数)を利用します。
Jitta@わんくま同盟
-
コード内容に関した3つの指摘なので私に対する問いかけだと思いますが、どういうふうに回答すればいいのか迷いました。そこで、指摘された内容に対して、私がどんな意図でサンプルを書いたかを説明します。
1.10個だけ使ったのは、動作を確認するためという単純な理由です。i以外の値が0であるという説明だったので短いループにして動作確認しました(Start/Stopも動作確認のため)。10という値にも深い理由は無く、適当に設定しただけです。150という値は、質問の中にあった値をそのまま使用しました。つまり、配列長さ妥当性については配慮していません。
2.配列長に対して何か手を打つことも考えましたが、その手法を知らない場合は、その説明もしなければならなくなります。質問者の意図している範囲から外れるので、質問者のコードになぞらえたままの方がいいと考え、そのまま記述しました。
3.インスタンス(クラス)に対して理解をされていない方が、複数作ることは無いとも考えましたが、複数作成している可能性が高いと判断しました。理由はtimer2_Tick()と、関数名に"2"と数字が付いていたからです。だからこそ「静的」という具体的な表現を使ったのだと想定しました。つまり「フォームを複数作って別々にカウントしたい」のではなく「フォームを複数作って共通にカウントしたい」のだと解釈しました。
追記:3.は、1つのインスタンスで複数のタイマーを使って共通にカウントしたい、というケースもあります。こちらの方が考え方としては素直ですね。
ご指摘の3点は、確かに疑問符が出るところです(他にも、自分自身どうしようかと考えたところがありますが)。しかしこれらに対する情報が提示されていないので判断のしようがありません。仮に分からなったとしたら、それは新たな質問事項になると考えました。もっとも、問いかけて確認すればよいではないか、と指摘されると反論しにくいですが・・・。結局、唯一意識したのはdeleteの実行です。
まぁ簡単に言うと、私のは「動くだけのサンプル」だということですね。
これで回答になっているでしょうか。
ちなみに、
void TimerEventProcessor( Object^ /*myObject*/, EventArgs^ /*myEventArgs*/ ) { static array<int>^a = gcnew array<int>(150); if(i >= 10) i = 0; a[i] += 10; i++; }
にするとコンパイルエラー(C3145)が発生します。
- 編集済み BlueSkyColors 2012年3月30日 21:42 3.に関して思い出したのでことを追記。