第11講 関数と配列
第4話 配列を関数に渡す

解答例
using namespace System;

int main(){
  Console::WriteLine("Visual C++コンソールアプリケーションの世界にようこそ!");

  int a,b;
  a=10;
  b=20;
  Console::WriteLine("{0} {1}",a,b);

  int x[10][10];
  int y[10][10];
  int i,j;

  Console::WriteLine("自然配列を作成");
  for(i=0;i<10;i++){
    for(j=0;j<10;j++){
      x[i][j]=10*i+j+1;
    }
  }


  
Console::WriteLine("自然配列を表示");
  for(i=0;i<10;i++){
    for(j=0;j<10;j++){
      Console::Write("{0,4:D}",x[i][j]);
    }
    Console::WriteLine();
  }


  Console::WriteLine("自然配列を置換");
  for(i=0;i<10;i++){
    for(j=0;j<10;j++){
      y[i][j]=x[j][i];
    }
  }


  
Console::WriteLine("自然配列置換を表示");
    for(i=0;i<10;i++){
      for(j=0;j<10;j++){
        Console::Write("{0,4:D}",y[i][j]);
      }
      Console::WriteLine();
    }


}

(コピーペースト用
using namespace System;

int main(){
Console::WriteLine("Visual C++コンソールアプリケーションの世界にようこそ!");

int a,b;
a=10;
b=20;
Console::WriteLine("{0} {1}",a,b);

int x[10][10];
int y[10][10];
int i,j;

Console::WriteLine("自然配列を作成");
for(i=0;i<10;i++){
for(j=0;j<10;j++){
x[i][j]=10*i+j+1;
}
}

Console::WriteLine("自然配列を表示");
for(i=0;i<10;i++){
for(j=0;j<10;j++){
Console::Write("{0,4:D}",x[i][j]);
}
Console::WriteLine();
}

Console::WriteLine("自然配列を置換");
for(i=0;i<10;i++){
for(j=0;j<10;j++){
y[i][j]=x[j][i];
}
}

Console::WriteLine("自然配列置換を表示");
for(i=0;i<10;i++){
for(j=0;j<10;j++){
Console::Write("{0,4:D}",y[i][j]);
}
Console::WriteLine();
}

}

以上が解答例ですが、横軸対称移動、縦軸対称移動、中央点対称移動などを加える予定です。
これらを加えなかったとしても、関数int main()は大きすぎます。
どうしたらよいでしょうか。
そうです。色の付いているところを関数として独立させればよいのです。つまり、
using namespace System;

int main(){
  Console::WriteLine("Visual C++コンソールアプリケーションの世界にようこそ!");

  int a,b;
  a=10;
  b=20;
  Console::WriteLine("{0} {1}",a,b);

  int x[10][10];
  int y[10][10];
  int i,j;

  関数1;

  
関数2

  
関数3;

  
関数4;

}
のようにしたいわけです。
今回の配列は、グローバル配列(プログラム冒頭で宣言した配列でプログラム全体で有効)ではなく、
mainの中で宣言したローカル配列(関数内でのみ有効で、関数の呼び出しが終わると配列は消滅する)です。
もちろん、今までのように冒頭で宣言してグローバル変数にする手もありますが、
ローカル変数は、関数が呼び出されている間だけ、メモリが割り当てられるのに対して、
グローバル変数では、プログラムが起動している間、メモリが割り当てられています。
この程度のプログラムであれば、メモリの無駄遣いなどまったく心配する必要はありませんが、
学習が進んでいけば、将来メモリをたくさん使うプログラムも組まなければなりません。
是非ともメモリは節約する習慣をつけたいものです。
ですから、今回はグローバル配列ではなく、メモリを節約できるローカル配列のままで行くことにします。
ローカル配列は、関数内でのみ有効ですから、色の部分を関数として独立させた場合、
何らかの方法で、その配列をそれらの関数に渡す必要があります。
といっても配列そのものを渡すことはできません。
そこで、ポインタ変数を利用して、
配列のアドレスであるx[0][0]のアドレスを渡します。

プログラム例を示してみましょう。

using namespace System;
void ds(int* t,int m,int n);
void tn(int* t,int m,int n);
void hj(int* t,int m,int n);
int main(){
  Console::WriteLine("Visual C++コンソールアプリケーションの世界にようこそ!");

  int a,b;
  a=10;
  b=20;
  Console::WriteLine("{0} {1}",a,b);

  int x[10][10];

  ds(&x[0][0],10,10);

  Console::WriteLine("自然配列を表示");
  hj(&x[0][0],10,10);

  tn(&x[0][0],10,10);

  Console::WriteLine("自然配列置換を表示");
  hj(&x[0][0],10,10);

}

void ds(int* t,int m,int n){
  char i,j;
  Console::WriteLine("自然配列を作成");
  for(i=0;i<m;i++){
    for(j=0;j<n;j++){
      *(t+10*i+j)=10*i+j+1;
    }
  }
}

void hj(int* t,int m,int n){
  char i,j;
  for(i=0;i<m;i++){
    for(j=0;j<n;j++){
      Console::Write("{0,4:D}",*(t+10*i+j));
    }
  Console::WriteLine();
  }
}

void tn(int* t,int m,int n){
  char i,j;
  int y[10][10];
  for(i=0;i<m;i++){
    for(j=0;j<n;j++){
      y[i][j]=*(t+10*i+j);
    }
  }
  Console::WriteLine("自然配列を置換");
  for(i=0;i<m;i++){
    for(j=0;j<n;j++){
      *(t+10*i+j)=y[j][i];
    }
  }
}

(コピーペースト用
using namespace System;
void ds(int* t,int m,int n);
void tn(int* t,int m,int n);
void hj(int* t,int m,int n);
int main(){
Console::WriteLine("Visual C++コンソールアプリケーションの世界にようこそ!");

int a,b;
a=10;
b=20;
Console::WriteLine("{0} {1}",a,b);

int x[10][10];

ds(&x[0][0],10,10);

Console::WriteLine("自然配列を表示");
hj(&x[0][0],10,10);

tn(&x[0][0],10,10);

Console::WriteLine("自然配列置換を表示");
hj(&x[0][0],10,10);

}

void ds(int* t,int m,int n){
char i,j;
Console::WriteLine("自然配列を作成");
for(i=0;i<m;i++){
for(j=0;j<n;j++){
*(t+10*i+j)=10*i+j+1;
}
}
}

void hj(int* t,int m,int n){
char i,j;


for(i=0;i<m;i++){
for(j=0;j<n;j++){
Console::Write("{0,4:D}",*(t+10*i+j));
}
Console::WriteLine();
}
}

void tn(int* t,int m,int n){
char i,j;
int y[10][10];
for(i=0;i<m;i++){
for(j=0;j<n;j++){
y[i][j]=*(t+10*i+j);
}
}
Console::WriteLine("自然配列を置換");
for(i=0;i<m;i++){
for(j=0;j<n;j++){
*(t+10*i+j)=y[j][i];
}
}
}



ds(int* t,int m,int n);  ←→  ds(&x[0][0],10,10);
の2つを対比させてください。
x[0][0]のアドレス&x[0][0]をポインタ変数tに渡しています。
ポインタ変数は今までint *tで宣言してきましたが、int* tと宣言してもよいのです。
つまり、int* tとint *tはまったく同じものです。
今回int* tとしたのは、tの部分がポインタ変数であることを強調するためです。
次の10,10では、行数と列数を渡しています。
渡した先では、配列はポインタ変数になっているので、ポインタ変数の中身をいじるには、
*(t+10*i+j)のようにしなければなりません。

尚、void ds(int* t,int m,int n)はvoid ds(int* x,int p,int q)としても何の影響も受けません。
使う変数名は何でもいいのです。
理由は、変数は関数の中でのみ通用するローカル変数なので、名前が重なってもそうでなくても関係ないのです。

例えば、次のように書いても
using namespace System;
void ds(int* v,int a,int b);
       ・
       ・
       ・
void ds(int* p,int c,int d){
  char i,j;
  Console::WriteLine("自然配列を作成");
  for(i=0;i<c;i++){
    for(j=0;j<d;j++){
      *(p+10*i+j)=10*i+j+1;
    }
  }
}
       ・
       ・
       ・
実行結果はまったく同じです。

渡した先の関数ではポインタ変数をつかって、変数の中身を操作していますが、
元の配列が変わっていることをソースに下に示したようにピンクを加えて確認してください。
using namespace System;
void ds(int* t,int m,int n);
void tn(int* t,int m,int n);
void hj(int* t,int m,int n);
int main(){
Console::WriteLine("Visual C++コンソールアプリケーションの世界にようこそ!");

  int a,b;
  a=10;
  b=20;
  Console::WriteLine("{0} {1}",a,b);

  int x[10][10];

  ds(&x[0][0],10,10);

  Console::WriteLine("自然配列を表示");
  hj(&x[0][0],10,10);

  tn(&x[0][0],10,10);

  Console::WriteLine("自然配列置換を表示");
  hj(&x[0][0],10,10);

  Console::WriteLine("配列xを表示");
  char i,j;
  Console::WriteLine("自然配列を作成");
  for(i=0;i<10;i++){
    for(j=0;j<10;j++){
      Console::Write("{0,4:D}",x[i][j]);
    }
    Console::WriteLine();
  }


}
       ・
       ・

       ・
実行結果


確かに最初のx[2][3]とx[3][2]等が入れ替わっています。
理由は、2次元配列といっても
x[0][0],x[0][1],x[0][2],・・・,x[0][10],x[1][0],x[1][1],x[1][2],・・・,x[1][10],x[2][0],x[2][1],x[2][2],・・・,x[9][10],x[10][0],x[10][1],x[10][2],・・・,x[10][10]
のようにアドレスは1次元に並んでいいるからです。
つまり、アドレスとその内容を表にすると

&x[0][0] &x[0][0]+1 ・・・ &x[0][0]+10 &x[0][0]+11 ・・・
x[0][0] x[0][1] ・・・ x[1][10] x[2][0] ・・・

となります。
では皆さん、置換の他に、置換+左右対称移動、置換+左右対称移動+上下対称移動、置換+左右対称移動+上下対称移動+中心点対称移動をさせて、
それぞれを表示させるプログラムを、作りましょう。

置換+左右対称移動+上下対称移動+中心点対称移動は最初の状態に戻っていることがわかります。


第10講第13話へ  第11講第3話へ
 第11講第5話へ 第12講第1話へ



vc++講義第1部へ
vb講義へ
VB講義基礎へ
初心者のための世界で一番わかりやすいVisual Basic入門基礎講座へ
初心者のための世界で一番わかりやすいVisual C++入門基礎講座へ