ページ 1 / 1
二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 01:46
by ホットココア
クラスのメンバ変数のint型二次元配列を戻り値にして外部からもたまに見れるようにしたいです。
二次元配列を引数にするなら検索などで見つかるんですが、戻り値だと聞いたことが無いです。出来ないんでしょうか?
ご存知の方教えていただけませんか。
環境VC++2008EE、Windows7
やりたいことは大体こんな感じですがエラー出てしまいます。
コード:
#include <iostream>
using namespace std;
class CSample
{
public:
CSample(){ // array配列を初期化 }
const int (*array)[5] GetArray(){ return array; }
private:
int array[3][5];
};
void PrintArray(const int (*array)[5])
{
for(int i = 0; i < 3; ++i){
for(int j = 0; j < 5; ++j){
cout << array[i][j] << endl;
}
}
}
int main()
{
CSample obj;
PrintArray( obj.GetArray() );
return 0;
}
エラー
error: ';' が、識別子'GetArray'の前に必要です。
error: 型指定子がありません - int と仮定しました。メモ:C++ は int を既定値としてサポートしていません。
warning:'GetArray': 戻り値の型がありません。'int' を返すメンバ関数とみなします。
error: 'CSample::array' : 再定義されています。異なる派生型です。'CSample::array' の宣言を確認してください。
error: 'PrintArray' : 1番目の引数を'int'から 'const int (*)[5]' に変換できません。(新しい機能 ; ヘルプを参照)
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 01:53
by ホットココア
「こーど」「/こーど」で囲むの忘れてたのでもう一度投稿
コード:
#include <iostream>
using namespace std;
class CSample
{
public:
CSample(){ // array配列を初期化 }
const int (*array)[5] GetArray(){ return array; }
private:
int array[3][5];
};
void PrintArray(const int (*array)[5])
{
for(int i = 0; i < 3; ++i){
for(int j = 0; j < 5; ++j){
cout << array[i][j] << endl;
}
}
}
int main()
{
CSample obj;
PrintArray( obj.GetArray() );
return 0;
}
エラー
error: ';' が、識別子'GetArray'の前に必要です。
error: 型指定子がありません - int と仮定しました。メモ:C++ は int を既定値としてサポートしていません。
warning:'GetArray': 戻り値の型がありません。'int' を返すメンバ関数とみなします。
error: 'CSample::array' : 再定義されています。異なる派生型です。'CSample::array' の宣言を確認してください。
error: 'PrintArray' : 1番目の引数を'int'から 'const int (*)[5]' に変換できません。(新しい機能 ; ヘルプを参照)
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 01:55
by ホットココア
何度もすみません コメントアウトがおかしかったのでもう一度投稿させてください
コード:
#include <iostream>
using namespace std;
class CSample
{
public:
CSample(){ /* array配列を初期化 */ }
const int (*array)[5] GetArray(){ return array; }
private:
int array[3][5];
};
void PrintArray(const int (*array)[5])
{
for(int i = 0; i < 3; ++i){
for(int j = 0; j < 5; ++j){
cout << array[i][j] << endl;
}
}
}
int main()
{
CSample obj;
PrintArray( obj.GetArray() );
return 0;
}
エラー
error: ';' が、識別子'GetArray'の前に必要です。
error: 型指定子がありません - int と仮定しました。メモ:C++ は int を既定値としてサポートしていません。
warning:'GetArray': 戻り値の型がありません。'int' を返すメンバ関数とみなします。
error: 'CSample::array' : 再定義されています。異なる派生型です。'CSample::array' の宣言を確認してください。
error: 'PrintArray' : 1番目の引数を'int'から 'const int (*)[5]' に変換できません。(新しい機能 ; ヘルプを参照)
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 04:18
by フリオ
Cですけど参考になれば
コード:
#include <stdio.h>
int (*func1(int (*a)[5]))[5]
{
int i;
for(i = 0; i < 3; ++ i){
int j;
for(j = 0; j < 5; ++ j) a[i][j] = 0;
}
return a;
}
typedef int (*int_array)[5];
int_array func2(int (*a)[5])
{
int i;
for(i = 0; i < 3; ++ i){
int j;
for(j = 0; j < 5; ++ j) a[i][j] = 5 * i + j;
}
return a;
}
void print(int (*a)[5])
{
int i;
for(i = 0; i < 3; ++ i){
int j;
for(j = 0; j < 5; ++ j) printf("%2d ", a[i][j]);
putchar('\n');
}
}
int main(void)
{
int a[3][5];
print(func1(a));
print(func2(a));
return 0;
}
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 04:51
by ホットココア
ありがとうございます。こんな構文見たこと無いですがちゃんと実行できてびっくりです。
よければ解説してもらえないですか?
例えばint (*func1(int (*a)[5]))[5]ってどこまでが戻り値でどこからが引数でしょう?
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 05:48
by フリオ
考え方は、変数の宣言と同じです。
int (*a)[5]; は、int (*)[5]型の変数 a の宣言です。
同じように
int (*func1(int (*a)[5]))[5]; は、int (*)[5]型の関数 func1(int (*a)[5])の宣言です。
a が func1(int (*a)[5]) に変わるだけです。
この場合の引数は int (*a)[5] です。
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 08:30
by a5ua
配列の要素数が固定なら、以下のようにしてもいいと思います。
コード:
#include <iostream>
using namespace std;
class CSample
{
public:
CSample(){}
// const int [3][5]への参照を返す
const int (&GetArray())[3][5]{ return array; }
private:
int array[3][5];
};
// const int [3][5]への参照を受け取る
void PrintArray(const int (&array)[3][5])
{
for(int i = 0; i < 3; ++i){
for(int j = 0; j < 5; ++j){
cout << array[i][j] << endl;
}
}
}
int main()
{
CSample obj;
PrintArray( obj.GetArray() );
return 0;
}
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 11:40
by maru
ほぼ解決しているようですが、
ホットココア さんが書きました:クラスのメンバ変数のint型二次元配列を戻り値にして外部からもたまに見れるようにしたいです。
なら、「外部から」「たまに見」る機能(関数)を friend にするのが一般的ではないでしょうか?
そうでないとせっかくカプセル化した内部情報を外部にさらすことになります。私なら、このような関数はつくろうとは思いません。
逆にメンバー変数を外部にさらす必要があるということは、そのクラスでやりたいことが明確に定義できていないということではないでしょうか?
もう一度設計に立ち戻ることをお勧めいたします。
たとえば今回の例では、PrintArray をメンバー関数にしてしまえばクラスの内部データ構造を外部に見せる必要がなくなります。
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 11:52
by softya(ソフト屋)
私もarrayをprivateにしている意味が全くないのでやめたほうが良いと思います。
それなら、arrayをpublicにしたのと同じです。
なんの為にクラスにしているかを再考されたほうがよろしいかと。
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 13:56
by ホットココア
使い方はなんとなく分かったんですがint (*func1(int (*a)[5]))[5]ですが、
戻り値と関数名は関係ないのにどうして関数名が戻り値に巻き込まれるような構文なんでしょう?
int (*)[5] func1(int (*a)[5])のようになるのが自然なような気がするんですがこれだとエラーになってしまいます。
それと
typedef int (*int_array)[5];
int_array func2(int (*a)[5])
{
}
こっちの方はさっぱりわかりません。これだとint_array func2(int (*a)[5])がint (*)[5] func2(int (*a)[5])のよ
うに展開(typedefは展開ではないと思いますが)さ
れるような気がするんですが何故上手くいくのが分かりません。
参照もあるんですね。
これもなんで関数名が戻り値に巻き込まれるような構文なんでしょう?
サンプルコードは例えなんですが実際は他のクラスの関数で
CSampleクラスの変数を見なきゃ処理できないみたいなのになってしまってて、
自分もprivateメンバを外に出したくは無いんですが設計がだめなのか・・・
フレンドにするのが一般的なんですね。
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 14:16
by maru
実際に二次元配列を戻り値にせず、要素を標準出力に出力する方法を書いてみました。
コード:
#include <iostream>
using namespace std;
class CSample
{
static const size_t array_size_1 = 3;
static const size_t array_size_2 = 5;
private:
int array_[array_size_1][array_size_2];
public:
CSample()
{ for (size_t i = 0; i < array_size_1; ++i)
for (size_t j = 0; j < array_size_2); ++j)
array_[i][j] = 0;
}
// クラスオブジェクトを出力ストリームに出力する(無くても可)、あまり標準的ではないオーバーライド
const CSample& operator>>(std::ostream& o) const
{ for (size_t i = 0; i < array_size_1; ++i)
for (size_t j = 0; j < array_size_2); ++j)
o << array_[i][j] << endl;
return *this;
}
// クラスオブジェクトを出力ストリームに出力するグローバル関数のフレンド宣言
friend std::ostream& operator<<(std::ostream&, const CSample&);
};
// クラスオブジェクトを出力ストリームに出力するグローバル関数
std::ostream& operator<<(std::ostream& o, const CSample& r)
{
r >> o; // 代わりに for 文で o << r.array_[i][j] << endl;を実行しても可
return o;
}
int main(int , char* [])
{
CSample obj;
cout << obj; //オブジェクトをcoutに出力
return 0;
}
どうしても2次元配列を戻り値にしたいのであれば、STLのvectorを使えば、もっと分かりやすく使用することが出来ます。
コード:
class CSample
{
public:
std::vector<std::vector<int> > getArray(void)
{
std::vector<std::vector<int> > result(array_size_1, array_size_2);
for (size_t i = 0; i < array_size_1; ++i)
for (size_t j = 0; j < array_size_2; ++j)
result[i][j] = array_[i][j];
return result;
}
// 他の定義は省略
}
int main(int, char*[])
{
CSample obj;
std::vector<std::vector<int> > array = obj.getArray();
// array[i][j]を使って要素をアクセスする。
return 0;
}
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月23日(水) 15:13
by softya(ソフト屋)
サンプルコードは例えなんですが実際は他のクラスの関数で
CSampleクラスの変数を見なきゃ処理できないみたいなのになってしまってて、
自分もprivateメンバを外に出したくは無いんですが設計がだめなのか・・・
フレンドにするのが一般的なんですね。
私から見ると、その変数とその変数に関する処理をクラスにしてしまったらダメなのかって気がしますが。
そうすれば、クラスを受渡して処理させることが出来ます。
出来るかどうかだけでも検討してみてください。
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月24日(木) 00:29
by ホットココア
みなさんお忙しいところどうもありがとうございました。
いろいろな書き方が出きるんだなあと思えて勉強になりました。
フレンドはあまり使ったこと無かったんですが使ってみると結構便利なんですね。
二次元配列の戻り値もわかりましたし、解決できました。
ありがとうございました。
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月24日(木) 17:56
by ISLe
ホットココア さんが書きました:戻り値と関数名は関係ないのにどうして関数名が戻り値に巻き込まれるような構文なんでしょう?
蛇足かもしれませんが、
int foo(void);
というのは、
fooが「戻り値がint型の関数型」であるという宣言です。
つまりfooに関数呼び出し演算子を付けると戻り値の型になるわけです。
コンパイラは関数名を元に型を判断しますから関係大アリなのです。
Re: 二次元配列を戻り値にしたい
Posted: 2011年2月25日(金) 01:29
by ホットココア
ホットココア さんが書きました:int foo(void);
というのは、
fooが「戻り値がint型の関数型」であるという宣言です。
つまりfooに関数呼び出し演算子を付けると戻り値の型になるわけです。
コンパイラは関数名を元に型を判断しますから関係大アリなのです。
関数にも型があるんですね、だから巻き込まれていいんですね。
戻り値の型と関数や関数名にはそんな関係があるなんて知りませんでした。
フリオさんが仰ってた
ホットココア さんが書きました:int (*func1(int (*a)[5]))[5]; は、int (*)[5]型の関数 func1(int (*a)[5])の宣言です。
っていうのはそういうことだったんですね。
初心者なのでコンパイラのしくみまで知りませんでした。
入門的なことだけこなして知った気になってたって思います。
すごく腑に落ちました。
どうもありがとうございました。