マルチスレッド版数独自動生成ソフトC++コードを題材とする超初心者のためのVisual Studio C++講義
第8章 ポインタの学習

第5話 2次元ポインタ

第4話では2次元を1次元に還元して考えましたが、

2次元のポインタも使えます。

2次元ポインタコード例

#include<iostream>//インクルードファイルiostreamの読み込み

#include<conio.h>//while(!_kbhit());を使うためのお呪い

#include<string> //文字列変数を使えるようにするために組み込む

#include <iomanip> //setprecisionを使えるように組み込む

#include <cmath>//powなどを使うときに必要

#include <ctime>//time()(←現時刻発生する関数)を使うために必要

using namespace std;//coutを使うときに必要なお呪い

const int n = 8;//具体的な数字でなく n とすると一般的に扱える


void 社員1(int(*a)[n]);//ポインタを扱う社員の一人

  int main() {//私は社長だ。

  int a[n][n]; // 8 行分のポインタ配列

  社員1(a);

  while (!_kbhit());//待機させるための命令

  return 0;//int main() を終わるためのお呪い

}

//ポインタを扱う社員の一人
void 社員1(int(*a)[n]) {

  for (int i = 0; i < n; i++) {

    for (int j = 0; j < n; j++) {

      a[i][j] = 8 * i + j + 1;
    }

  }
 
  for (int i = 0; i < n; i++) {

    for (int j = 0; j < n; j++) {

      if (a[i][j] < 10)cout << " ";

      cout << a[i][j] << " ";

   }

   cout << endl;

  }

}
実行結果


1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64


● 「なぜ2次元ポインタを学ぶのか」

皆さんは「第4話で1次元に還元できるのに、なぜ2次元ポインタを学ぶの?」と疑問を持ちますね。


というメリットがあるからです。

次話の課題のためにコードを第4話に戻し、

第7章第13話に関連付けるために社員1()の名称を部長(int* a)に変えます。

さらに、

  int* a = (int*)calloc(64, sizeof(int));

の定義は社長が行い

  部長(a);

という形でポインタを送る形にします。

#include<iostream>//インクルードファイルiostreamの読み込み

#include<conio.h>//while(!_kbhit());を使うためのお呪い

#include<string> //文字列変数を使えるようにするために組み込む

#include <iomanip> //setprecisionを使えるように組み込む

#include <cmath>//powなどを使うときに必要

#include <ctime>//time()(←現時刻発生する関数)を使うために必要

using namespace std;//coutを使うときに必要なお呪い

void 部長(int* a);//ポインタを扱う社員の一人

int main() {//私は社長だ。

  int* a = (int*)calloc(64, sizeof(int));

  部長(a);

  while (!_kbhit());//待機させるための命令

  return 0;//int main() を終わるためのお呪い

}

//ポインタを扱う部長
void 部長(int* a) {

  for (int i = 0; i < n; i++) {

    for (int j = 0; j < n; j++) {

      *(a + 8 * i + j) = 8 * i + j + 1;

    }

  }

  for (int i = 0; i < n; i++) {

    for (int j = 0; j < n; j++) {

      if (*(a + 8 * i + j) < 10)cout << " ";

      cout << *(a + 8 * i + j) << " ";

    }

    cout << endl;

  }

}
第7章第13話を参照して

void 課長1(int *a);//自然配列担当社員と表示担当社員の統率を担当

void 課長2(int *a);//対角線部分交換社員fと表示担当職員の統率を担当

void 課長3(int *a);//緑部分交換社員gと表示担当職員の統率を担当

void 課長4(int *a);//明るい紫部分交換社員fと表示担当職員の統率を担当

void 自然配列担当社員(int *a);

void 表示担当社員(int *a)
;
void 交換係社員(int *x,int *y);

//fの任務は各対角線部分を点対称移動させることである
void f(int *a);

//gの任務は緑の部分を上下線対称移動させることである
void g(int *a);

//hの任務は明るい紫部分を左右線対称移動させることである
void h(int *a);

4人の課長、自然配列担当社員、表示担当社員、交換係社員、f、g、hを採用して

コードを書き換えて

実行結果


1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64

64 2 3 4 5 6 7 8
9 55 11 12 13 14 15 16
17 18 46 20 21 22 23 24
25 26 27 37 29 30 31 32
33 34 35 36 28 38 39 40
41 42 43 44 45 19 47 48
49 50 51 52 53 54 10 56
57 58 59 60 61 62 63 1

64 2 59 4 5 6 7 8
9 55 11 52 13 14 15 16
41 18 46 20 21 22 23 24
25 34 27 37 29 30 31 32
33 26 35 36 28 38 39 40
17 42 43 44 45 19 47 48
49 50 51 12 53 54 10 56
57 58 3 60 61 62 63 1

64 2 59 5 4 6 7 8
16 55 11 52 13 14 15 9
41 23 46 20 21 22 18 24
25 34 30 37 29 27 31 32
33 26 35 36 28 38 39 40
17 42 43 44 45 19 47 48
49 50 51 12 53 54 10 56
57 58 3 60 61 62 63 1

を実現してください。

ノーヒントでは難しすぎる課題になりますので、

交換の原則は

    
a[i][j] *(a + n * i + j)

にすることです。

*(a + n * i + j)の a + n * i + j はアドレスを計算しています。

a[0][0]に相当する値が入っているアドレスは先頭アドレスで、

*a となります。

a[0][1]なら、そこから一つアドレスが進むので、

*(a + 1) です。

同様にして

a[0][3]→*(a + 3)  a[0][4]→*(a + 4)  a[0][5]→*(a + 5)   a[0][6]→*(a + 6) a[0][7]→*(a + 7)

    for (int j = 0; j < n; j++) {

の計算からa[0][7]が1列目の最後ですからa[1][0]は*(a + 8)ですが、

現在 n は 8 ですからa + 1 * nです。したがって、a[1][0]→*(a + 1 * n)です。

これは i = 1 の場合です。全て並べると

a[1][0]→*(a + 1 * n)  a[1][1]→*(a + 1 * n + 1)  a[1][2]→*(a + 1 * n + 2)  a[1][3]→*(a + 1 * n + 3)  

a[1][4]→*(a + 1 * n + 4)  a[1][5]→*(a + 1 * n + 5)  a[1][6]→*(a + 1 * n + 6)  a[1][3]→*(a + 1 * n + 7)  

i = 2 の場合で全て並べると、

a[2][0]→*(a + 2 * n)  a[2][1]→*(a + 2 * n + 1)  a[2][2]→*(a + 2 * n + 2)  a[2][3]→*(a + 2 * n + 3)  

a[2][4]→*(a + 2 * n + 4)  a[2][5]→*(a + 2 * n + 5)  a[2][6]→*(a + 2 * n + 6)  a[2][3]→*(a + 2 * n + 7)  

以上3例やって

    a[i][j] → *(a + n * i + j)

の仕組みが理解できますね。

普遍(抽象)と具体を理解するときに、 n 、 i 、 j に具体的な数字を代入してみることはとても大切なことです。

具体的な数字を逆に n 、 i 、 j に置き換えること抽象化も大切です。

数学も国語も共通点があり、具体例から抽象を導いたり、

抽象から具体例を考えることが大切なのです。

具体と抽象をいったり来たりしながら書くのが文章です。

一般的な話だけでは分からないので、具体的なエピソードを入れる!

具体例から一般的な結論を書く!

これを普通に行っています。

私はタイピングミスが多いので、

意外に感じる人が多いでしょうが、

応募総数が多かった時代に高校2年生女子から読書感想文の指導を頼まれ、

課題図書の栃木県の最高賞である優秀賞に導いた体験を持っています。

全国の方を13年度分それぞれ5つの分野

(小学校1・2学年の部、小学校3・4学年の部、小学校5・6年の部、中学校の部、高等学校の部)

のべ65部門について調べたことがありますが、

最優秀賞である内閣総理大臣章はすべて課題図書の部から選ばれています。

つまり、内閣総理大臣賞は課題図書の部から選ぶという内規があるのでしょう。

つまり、課題図書の最高賞である優秀賞は実質最優秀賞です。

そのときの応募総数が2万4千人ですから、

女子生徒を2万4千人の頂点に導いた体験を持つのです。

自己紹介が遅れましたが、私は栃木県の元県立高校数学教諭です。

元が付くのは私は現在71才であるかです。

読書感想文の指導を数学の先生に頼んでくる女子生徒もユニークですが、

引き受ける私も個性的ですね(笑)。

何しろ、彼女は内閣総理大臣賞 = 64万人の頂点に立ちたいを指導をお願いしてきて、

引き受けたのですから。

引き受けた理由は、彼女に文才があることを知っていたからです。

話が脱線しましたが、具体化と抽象化は国語にも数学にも必要な能力であり、

もちろんプログラミングにも必要な能力です。

さて、具体的な説明に戻りましょう。

//fの任務は対角線部分を点対称移動させることである
void f(int a[n][n]) {

  //対角線点対称移動
  for (int i = 0; i < n / 2; i++) {

    int x =
a[i][i];

ならば


    
a[i][j] → *(a + n * i + j) ですから
    
    j に i を代入して

    int x =
*(a + n * i + i);

とします。


    int y = a[n - i - 1][n - i - 1];

ならば


      a[i][j] → *(a + n * i + j) ですから    
    
    i に n - i - 1 を代入して

    j に n - i - 1 を代入して

    int y =
*(a + n * (n - i - 1) + n - i - 1);
     


となります。

頭が混乱しますが、

以上を参考にして

  //逆対角線点対称移動
  for (int i = 0; i < n / 2; i++) {

    int x = a[i][n - i - 1];

    int y = a[n - i - 1][i];

    交換係社員(&x, &y);

    a[i][n - i - 1] = x;

    a[n - i - 1][i] = y;

  }
  //逆対角線点対称移動終了

}

//gの任務は緑部分を線対称移動させることである
void g(int a[n][n]) {

   //緑の部分の第1列から第4列までの上下交換開始
  for (int i = 0; i < n / 2; i++) {

     int x = a[i][(2 + i) % (n / 2)];

    int y = a[n - i - 1][(2 + i) % (n / 2)];

    交換係社員(&x, &y);

    a[i][(2 + i) % (n / 2)] = x;

    a[n - i - 1][(2 + i) % (n / 2)] = y;

  }
  //緑の部分の第1列から第4列までの上下交換

  //緑の部分の第5列から第8列までの上下交換開始
  for (int i = 0; i < n / 2; i++) {

    int x = a[i][n - 1 - (2 + i) % (n / 2)];

    int y = a[n - i - 1][(2 + i) % (n / 2)];

    交換係社員(&x, &y);

    a[i][n - 1 - (2 + i) % (n / 2)] = x;

    a[n - i - 1][(2 + i) % (n / 2)] = y;

  }
  //緑の部分の第5列から第8列までの上下交換

}

//hの任務は明るい紫部分を線対称移動させることである
void h(int a[n][n]) {

  //明るい紫の部分の第1行から第4行までの左右交換開始
  for (int i = 0; i < n / 2; i++) {

    int x = a[i][(3 + i) % (n / 2)];

    int y = a[i][n - 1 - ((3 + i) % (n / 2))];

    交換係社員(&x, &y);

    a[i] [(3 + i) % (n / 2)] = x;

    a[i][n - 1 - ((3 + i) % (n / 2))] = y;

   } 
  //明るい紫の部分の第1行から第4行までの左右交換終了

  //明るい紫の部分の第5行から第8行までの交換開始
  
  for (int i = 0; i < n / 2; i++) {

    int x = a[n - i - 1][(3 + i) % (n / 2)];

    int y = a[n - i - 1][n - 1 - ((3 + i) % (n / 2))];

    交換係社員(&x, &y);

    a[n - i - 1] [(3 + i) % (n / 2)] = x;

    a[n - i - 1][n - 1 - ((3 + i) % (n / 2))] = y;

   } 
 //明るい紫の部分の第5行から第8行までの交換終了

}


についても考えてください。

第8章第4話へ 第8章第6話へ

本講義トップへ