9. ポインタ−変数 −その2−
 「ポインターは難しい!」などというひとがいますが,まったく,そんなことはありません.先入観を棄て理解を試みてください.

 9.1 関数とポインター変数

 関数を使って何らかの処理をするとき,これまでの知識では,return文を使って,ただ1個の数値を返すことしか出来ませんでした.このことで,「関数は不便だ」と感じたひとも多いと思います.
でも,ポインター変数を使えば,その不便さを解消することができます.


プログラム例 9.1.1は,ポインター変数を使って関数呼び出し側へ関数の演算結果を返す方法を示します.
このプログラムで,まず気が付いて欲しいことは,関数 wa() の引数にポインター変数が含まれていることです.
したがって,
 1)関数 wa() に変数のアドレスを渡すことができる.
 2)この場合,2変数a,bの和を代入する変数cのアドレスをwa()に渡せば(教えれば),
 3)cのアドレスを受け取った(教えてもらった)関数wa()は,間接演算子を使って,直接,変数cの
   アドレスに結果を書き込むことができる.
 4)これは,結果的に,変数cに関数の演算結果を返したことと同じである..
ということです.

ここで,紹介したポインター変数の使い方は,最も基本的な必須の知識です.この方法だと,関数から何個でも呼び出し側へ値を返せると言うことになります.


 プログラム例 9.1.1  実行結果
#include <stdio.h>
void wa( int a, int b, int *cptr);
void main(void){
  int a, b, c;
  int *cptr;
  
  a = 25;
  b = 10;
  cptr = &c;  
  wa(a, b, cptr);

  printf(" a=%3d\n b=%3d\n c=%3d\n\n", a, b, c);
}

void wa(int x, int y, int *zptr){
    *zptr = x + y;
}
 a= 25
 b= 10
 c= 35


 もちろん,上のプログラムは,関数呼び出し時の引数に,ポインター変数を使わず直接cのアドレスを書いてもよい.

 プログラム例 9.1.2  実行結果
#include <stdio.h>
void wa( int a, int b, int *cptr);
void main(void){
  int a, b, c;
  
  a = 25;
  b = 10;

  /* 直接cのアドレスを渡してもよい */
  wa(a, b, &c); 
  printf(" a=%3d\n b=%3d\n c=%3d\n\n", a, b, c);
}

void wa(int x, int y, int *zptr){
    *zptr = x + y;
}
 a= 25
 b= 10
 c= 35



 9.2 ポインターを使って複数個の結果を返す
 9.1の方法を使えば,呼び出された関数から呼び出し側へ(正確ではないが)何個でも,数値を返すことができます.
次のプログラム例は,それを示すためのものです.


 プログラム例 9.2.1  実行結果
#include <stdio.h>
void keisan(double f,double g,double *xp,double *yp,double *zp);  
void main(void){
  double a, b;
  double wa, sa, seki;
  double *wa_p, *sa_p, *seki_p;

  wa_p=&wa; sa_p=&sa; seki_p=&seki;
  a = 1.23;
  b = 2.46;
  printf("a=%7.4f  b=%7.4f\n",a,b);

  keisan(a, b, wa_p, sa_p, seki_p); 
  printf("wa=%7.4f\nsa=%7.4f\nseki=%7.4f\n",wa,sa,seki);
}

void keisan(double f,double g,double *xp,double *yp,double *zp)
{
    *xp = f + g;
    *yp = f - g;  
    *zp = f * g;  
}
a= 1.2300  b= 2.4600
wa= 3.6900
sa=-1.2300
seki= 3.0258




 9.3 ポインター変数の宣言時の初期化
 プログラム例 9.3.1は,ポインター変数を宣言時に初期化する方法を紹介するものです.
このプログラムでは,つぎのように初期化しています.
   double wa, sa, seki;
   double *wa_p=&wa, *sa_p=&sa, *seki_p=&seki;
 ただし,ポインター変数にアドレスが代入される変数は,ポインター変数より先に宣言されていなければ初期化できません.
したがって,次のような書き方はエラーとなります.
   double *wa_p=&wa, *sa_p=&sa, *seki_p=&seki;
   double wa, sa, seki;


 プログラム例 9.3.1  実行結果
#include <stdio.h>
void keisan(double f,double g,double *xp,double *yp,double *zp);  
void main(void){
  double a, b;
  double wa, sa, seki;
  double *wa_p=&wa, *sa_p=&sa, *seki_p=&seki;

  a = 1.23;
  b = 2.46;
  printf("a=%7.4f  b=%7.4f\n",a,b);

  keisan(a, b, wa_p, sa_p, seki_p); 
  printf("wa=%7.4f\nsa=%7.4f\nseki=%7.4f\n",wa,sa,seki);
}

void keisan(double f,double g,double *xp,double *yp,double *zp)
{
    *xp = f + g;
    *yp = f - g;  
    *zp = f * g;  
}
a= 1.2300  b= 2.4600
wa= 3.6900
sa=-1.2300
seki= 3.0258



 9.4 1次元配列とポインター変数

 前節では,ポインター変数をつかって関数から複数の値を返す方法を説明しました.
ポインター変数のもうひとつの使い方は,ポインター変数を使って配列変数にアクセスすることです.
まずは,プログラム例 9.4.1で,以下の2点を確認してください.
 1)通常の変数同様,各配列要素のアドレスも次のようにアドレス演算子を使って求めることが出来る.
      &a[i]
 2)配列変数の各要素は,添字の順にメモリー中に隙間なく並べられる.
   プログラムの実行結果を見れば,各要素のアドレスが8バイト単位で連続的に変わっているのがわかると
   思います.これは,説明するまでもないと思いますが,double型(8バイト)の各要素が添字の順に隙間
   なく並べられているということですよね.


 プログラム例 9.4.1  実行結果
#include <stdio.h>
void main(void){
  int size = 6;
  double a[8]={ 1.23, 2.34, 3.45, 4.56, 5.67, 6.78};
  double *aptr;
  int i;

  for(i = 0; i < size; i++){
    aptr = &a[i]; /* 1)配列要素のアドレスを求める*/
    printf("aptr : %u   *aptr : %5.2f\n",aptr,*aptr);
  }
}
aptr : 1244988   *aptr :  1.23
aptr : 1244996   *aptr :  2.34
aptr : 1245004   *aptr :  3.45
aptr : 1245012   *aptr :  4.56
aptr : 1245020   *aptr :  5.67
aptr : 1245028   *aptr :  6.78










 9.5 ポインター変数のインクリメント

 前節では,個々の配列要素のアドレスをアドレス演算子(&)を使って求めました.しかし,ポインター変数を使えば,配列中の1個の要素のアドレスさえわかれば,他の全ての要素のアドレスを知ることができます.いいかえればれ,1個の要素のアドレスさえわかれば,すべての要素の内容を読み書き(アクセス)できるということです.
これも,ポインター変数を使いこなすための必須の知識です.ここでは,その方法を理解してもらいましょう.

まずは,次のプログラム例を見てください.プログラム中で,ポインター変数がインクリメントされていますね.
   aptr++ (++:インクリメント演算子)
インクリメント演算子は,これまでループなどで整数型変数の内容を1増加させるのにつかって来ました.
しかし,ポインター変数の場合,「1回インクリメントする」ということは,「その記憶するアドレスを1だけ進める」という意味ではなく,「配列要素を1個進める」という意味です.
もちろん,ポインター変数の記憶するアドレスそのものはインクリメントするたびに,その型に応じて次のように変化します.
   int *ap     アドレスは4増える    要素は1進む
   float *ap    アドレスは4増える    要素は1進む
   double *ap   アドレスは8増える    要素は1進む
このことを頭に入れて,次のプログラムと実行結果を比較してみてください.


 プログラム例 9.5.1  実行結果
#include <stdio.h>
void main(void){
    double a[6]={ 1.23, 2.34, 3.45, 4.56, 5.67, 6.78};
    double *aptr;

    aptr=&a[0]; /* 1)aptrにa[0]のアドレスを代入*/
    printf("aptr : %u   *aptr : %5.2f\n",aptr,*aptr);

    aptr++;     /* 2)aptrをインクリメント */
    printf("aptr : %u   *aptr : %5.2f\n",aptr,*aptr);

    aptr++;     /* 3)aptrをインクリメント */
    printf("aptr : %u   *aptr : %5.2f\n",aptr,*aptr);

    aptr++;     /* 4)aptrをインクリメント */
    printf("aptr : %u   *aptr : %5.2f\n",aptr,*aptr);

    aptr++;
    printf("aptr : %u   *aptr : %5.2f\n",aptr,*aptr);

    aptr++;
    printf("aptr : %u   *aptr : %5.2f\n",aptr,*aptr);
}
aptr : 1244988   *aptr :  1.23
aptr : 1244996   *aptr :  2.34
aptr : 1245004   *aptr :  3.45
aptr : 1245012   *aptr :  4.56
aptr : 1245020   *aptr :  5.67
aptr : 1245028   *aptr :  6.78



もちろん,説明するまでもないですが,上記のプログラムは,次のようにループを使って簡潔に書くことができます.
先頭の配列要素のアドレスの他に配列要素の個数(配列サイズ)もあらかじめわかっている必要があることに注意しましょう.

 プログラム例 9.5.2  実行結果
#include <stdio.h>
void main(void){
  int size = 6;
  double a[6]={ 1.23, 2.34, 3.45, 4.56, 5.67, 6.78};
  double *aptr;
  int i;

  aptr=&a[0];
  for(i = 0; i < size; i++){
    printf("aptr : %u   *aptr : %5.2f\n",aptr,*aptr);
    aptr++;
  }
}
aptr : 1244988   *aptr :  1.23
aptr : 1244996   *aptr :  2.34
aptr : 1245004   *aptr :  3.45
aptr : 1245012   *aptr :  4.56
aptr : 1245020   *aptr :  5.67
aptr : 1245028   *aptr :  6.78



 9.6 ポインターを使って配列を関数へ渡す

 では,早速,前節で紹介したポインター変数のインクリメント時の性質を使って,全ての配列要素の和を求める関数を作ってみましょう.
関数に教えるべきパラメータ(渡す引数)は,配列サイズと配列の先頭の要素のアドレスだけです.
プログラム例 9.6.1は,求めた総和を関数の戻り値として返す関数で,例9.6.2は,求まった総和をポインタ変数で返す例です.


 プログラム例 9.6.1  実行結果
#include <stdio.h>
double calc_sum(int n, double *ap);

void main(void){
  int size = 6;
  double a[6]={1.23, 2.34, 3.45, 4.56, 5.67, 6.78};
  double souwa;

  souwa = calc_sum( size, &a[0]);
  printf("合計 %6.2f\n", souwa);
}

double calc_sum(int n, double *ap)
{
   int i;
   double sum;

   sum = 0.0;
   for(i = 0; i < n; i++){
      sum += *ap;
      ap++;
   }
   return sum;
}
合計  24.03





 プログラム例 9.6.2  実行結果
#include <stdio.h>
void calc_sum(int n, double *ap, double *sump);

void main(void){
  int size = 6;
  double a[6]={1.23, 2.34, 3.45, 4.56, 5.67, 6.78};
  double souwa;

  calc_sum( size, &a[0], &souwa );
  printf("合計 %6.2f\n", souwa);
}

void calc_sum(int n, double *ap, double *sump)
{
   int i;
   double *sump;

   *sump = 0.0;
   for(i = 0; i < n; i++){
      *sump += *ap;
      ap++;
   }
}
合計  24.03










 9.7 実は,配列変数名は...

 配列の最初の要素のアドレスと要素の数さえわかれば,すべての配列要素にアクセスできることを述べました. その場合,配列の最初の要素のアドレスは,&演算子を使い,&a[0]で求めることができました.
実は,配列変数名そのものも配列の最初の要素のアドレスを返します.したがって,&a[0]のかわりに配列変数名aを使うことも可能です.
 次のプログラム例は,&a[0]と配列変数名aが,いづれも配列変数の先頭要素のアドレスを返すことを示すものです. プログラムを注意深く眺めてみてください.これはよくつかわれる表現方法なので覚えておきましょう!

※配列変数名はポインター変数ではありません.したがって,a++などと書くことはできません.単に,配列の先頭アドレスを返すだけです.



 プログラム例 9.7.1  実行結果
#include 
double calc_sum(int n, double *aptr);

void main(void){
  int size = 6;
  double a[6]={ 1.23, 2.34, 3.45, 4.56, 5.67, 6.78};
  double souwa;

  printf("1)先頭アドレスとして &a[0]を渡す\n");
  printf("  &a[0] : %u\n",&a[0]);
  souwa = calc_sum( size, &a[0] );
  printf("  総和:%7.4f\n", souwa);

  printf("\n2)先頭アドレスとして変数名 a を渡す\n");
  printf("  a : %u\n",a);
  souwa = calc_sum( size, a );
  printf("  総和 : %7.4f\n", souwa);
}

double calc_sum(int n, double *ap)
{
   int i;
   double sum;

   sum = 0.0;
   for(i = 0; i < n; i++){
      sum += *ap;
      ap++;
   }
   return sum;
}
1)先頭アドレスとして &a[0]を渡す
  &a[0] : 1245004
  総和:24.0300

2)先頭アドレスとして変数名 a を渡す
  a : 1245004
  総和 : 24.0300








 9.8 配列へアクセスするもうひとつの方法

 ポインター変数を使って配列要素にアクセスするもうひとつの方法を紹介しましょう.特別説明しなくともすでに気が付いているひともいるかもしれませんね.
下記のプログラムを見てください.関数中に次のような記述があります.
   ap + i;
これは,呼び出し側から受け取った配列 aの先頭アドレスをポインター変数apで受け取り,apに記憶されたアドレスをベースとして,他の要素はそこからの変位iで指定する方法です.もちろん,ap++の表現ではポインター変数の内容は変化しますが,このばあい,apの内容は変化しません.




 プログラム例 9.8.1  実行結果
#include 
void calc_sum(int n, double *aptr);

void main(void){
  int size = 6;
  double a[6]={ 1.23, 2.34, 3.45, 4.56, 5.67, 6.78};
  double souwa;

  calc_sum( size, a );
}

void calc_sum(int n, double *ap)
{
   int i;
   double sum;

   sum = 0.0;
   for(i = 0; i < n; i++){
      /*次の2行でapの値は変わらない*/
      printf("a[%2d] : %7.4f\n", i, *(ap+i));
      sum += *(ap+i);
   }
   printf("-----------------\n");
   printf("総和 :  %7.4f\n", sum);
}
a[ 0] :  1.2300
a[ 1] :  2.3400
a[ 2] :  3.4500
a[ 3] :  4.5600
a[ 4] :  5.6700
a[ 5] :  6.7800
-----------------
総和 :  24.0300



 9.9 説明の必要は無いと思いますが...

説明の必要はないと思いますが,ポインター変数は,つぎのようなつかいかたもできます.いろいろくふうしてみてください.

 1)1つおきに要素を指定
  for(i = 0; i < n; i+=2){
    ap += i; /* apの内容が変化 */
    printf("a[%d]:%4d", *ap);
  }
 2)1つおきに要素を指定
  for(i = 0; i < n; i+=2){
    printf("a[%d]:%4d", *(ap + i));
  }

その他,くふうしだいです.


















 9.10 ここまでくれば...

ここまでに紹介した関数とポインター変数の知識があれば,C言語らしく関数を使ってプログラムを書くことができます.
..............
おっと!
もうひとつ大切なことを忘れていました.プログラミングにもっとも必要なもの,それは「考える習慣」です.