二次元配列を戻り値にしたい

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ホットココア

二次元配列を戻り値にしたい

#1

投稿記事 by ホットココア » 8年前

クラスのメンバ変数の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: 二次元配列を戻り値にしたい

#2

投稿記事 by ホットココア » 8年前

「こーど」「/こーど」で囲むの忘れてたのでもう一度投稿

コード:

#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: 二次元配列を戻り値にしたい

#3

投稿記事 by ホットココア » 8年前

何度もすみません コメントアウトがおかしかったのでもう一度投稿させてください

コード:

#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: 二次元配列を戻り値にしたい

#4

投稿記事 by フリオ » 8年前

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: 二次元配列を戻り値にしたい

#5

投稿記事 by ホットココア » 8年前

ありがとうございます。こんな構文見たこと無いですがちゃんと実行できてびっくりです。
よければ解説してもらえないですか?
例えばint (*func1(int (*a)[5]))[5]ってどこまでが戻り値でどこからが引数でしょう?

フリオ

Re: 二次元配列を戻り値にしたい

#6

投稿記事 by フリオ » 8年前

 考え方は、変数の宣言と同じです。
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] です。

アバター
a5ua
記事: 199
登録日時: 9年前

Re: 二次元配列を戻り値にしたい

#7

投稿記事 by a5ua » 8年前

配列の要素数が固定なら、以下のようにしてもいいと思います。

コード:

#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;
}

maru
記事: 150
登録日時: 9年前

Re: 二次元配列を戻り値にしたい

#8

投稿記事 by maru » 8年前

ほぼ解決しているようですが、
ホットココア さんが書きました:クラスのメンバ変数のint型二次元配列を戻り値にして外部からもたまに見れるようにしたいです。
なら、「外部から」「たまに見」る機能(関数)を friend にするのが一般的ではないでしょうか?
そうでないとせっかくカプセル化した内部情報を外部にさらすことになります。私なら、このような関数はつくろうとは思いません。

逆にメンバー変数を外部にさらす必要があるということは、そのクラスでやりたいことが明確に定義できていないということではないでしょうか?
もう一度設計に立ち戻ることをお勧めいたします。
たとえば今回の例では、PrintArray をメンバー関数にしてしまえばクラスの内部データ構造を外部に見せる必要がなくなります。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: 二次元配列を戻り値にしたい

#9

投稿記事 by softya(ソフト屋) » 8年前

私もarrayをprivateにしている意味が全くないのでやめたほうが良いと思います。
それなら、arrayをpublicにしたのと同じです。
なんの為にクラスにしているかを再考されたほうがよろしいかと。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ホットココア

Re: 二次元配列を戻り値にしたい

#10

投稿記事 by ホットココア » 8年前

使い方はなんとなく分かったんですが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メンバを外に出したくは無いんですが設計がだめなのか・・・
フレンドにするのが一般的なんですね。

maru
記事: 150
登録日時: 9年前

Re: 二次元配列を戻り値にしたい

#11

投稿記事 by maru » 8年前

実際に二次元配列を戻り値にせず、要素を標準出力に出力する方法を書いてみました。

コード:

#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;
}

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: 二次元配列を戻り値にしたい

#12

投稿記事 by softya(ソフト屋) » 8年前

サンプルコードは例えなんですが実際は他のクラスの関数で
CSampleクラスの変数を見なきゃ処理できないみたいなのになってしまってて、
自分もprivateメンバを外に出したくは無いんですが設計がだめなのか・・・
フレンドにするのが一般的なんですね。
私から見ると、その変数とその変数に関する処理をクラスにしてしまったらダメなのかって気がしますが。
そうすれば、クラスを受渡して処理させることが出来ます。
出来るかどうかだけでも検討してみてください。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ホットココア

Re: 二次元配列を戻り値にしたい

#13

投稿記事 by ホットココア » 8年前

みなさんお忙しいところどうもありがとうございました。
いろいろな書き方が出きるんだなあと思えて勉強になりました。
フレンドはあまり使ったこと無かったんですが使ってみると結構便利なんですね。
二次元配列の戻り値もわかりましたし、解決できました。
ありがとうございました。

ISLe
記事: 2645
登録日時: 9年前
連絡を取る:

Re: 二次元配列を戻り値にしたい

#14

投稿記事 by ISLe » 8年前

ホットココア さんが書きました:戻り値と関数名は関係ないのにどうして関数名が戻り値に巻き込まれるような構文なんでしょう?
蛇足かもしれませんが、
int foo(void);
というのは、
fooが「戻り値がint型の関数型」であるという宣言です。
つまりfooに関数呼び出し演算子を付けると戻り値の型になるわけです。
コンパイラは関数名を元に型を判断しますから関係大アリなのです。

ホットココア

Re: 二次元配列を戻り値にしたい

#15

投稿記事 by ホットココア » 8年前

ホットココア さんが書きました:int foo(void);
というのは、
fooが「戻り値がint型の関数型」であるという宣言です。
つまりfooに関数呼び出し演算子を付けると戻り値の型になるわけです。
コンパイラは関数名を元に型を判断しますから関係大アリなのです。
関数にも型があるんですね、だから巻き込まれていいんですね。
戻り値の型と関数や関数名にはそんな関係があるなんて知りませんでした。

フリオさんが仰ってた
ホットココア さんが書きました:int (*func1(int (*a)[5]))[5]; は、int (*)[5]型の関数 func1(int (*a)[5])の宣言です。
っていうのはそういうことだったんですね。
初心者なのでコンパイラのしくみまで知りませんでした。
入門的なことだけこなして知った気になってたって思います。
すごく腑に落ちました。
どうもありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る