C言語 プログラムを3か月で身につけるための第3週でやるべきこと

  • このエントリーをはてなブックマークに追加
Pocket

C言語を3か月で身につけてるための第3週でやるべきことは、

・ポインタ

です。ポインタは一番ややこしい部分なので、1週間じっくり時間をかけてやってほしいです。

大まかなスケジュールはこちらのページを参照ください。

———————————————————————–
C言語 プログラムを3か月で身につけるスケジュール
———————————————————————–

では、早速書いていきます。

第3週

ポインタとは

ある変数のアドレスを格納する変数をポインタと呼びます。
アドレスとは、各変数に割り当てられたメモリの番地のことです。

コンピュータには、メモリというものがあります。何ギガとか何メガと言うのを聞いたことがあると思います。それがコンピュータに割り当てられた容量です。

プログラムで、宣言された変数は、このメモリ領域のどこかに割り当てられます。こういうふうに割り当てられていくメモリの番地のことをアドレスと呼んでいます。

そして、このアドレスを格納する変数がポインタと呼ばれています。

ポインタの使い方

ポインタの使い方ですが、まずは宣言の仕方を解説します。

int* ip;

と記述します。変数の前に「*」(アスタリスク)をつけるだけです。
これだけで、ポインタの宣言ができてしまいます。

これで、通常の変数と同様に扱うことができます。ただ、気をつけなければいけないのは、これは変数が持っている数値ではなく、アドレスが格納されているということです。

例えば、このまま、ip + 1 とかしてしまうと、アドレスがint型分(2byteか4byteは処理系による)進んでしまいます。ここが、ポインタのややこしいところで、int型のポインタなので、インクリメントされるアドレスはint型分になるのです。単純に「1」足されるのではないことに注意が必要です。

また、ポインタに格納されているアドレスのデータへのアクセスも可能です。ここがさらにややこしくしている点ですが、これができなければ、ポインタを扱う意味はないと思います。

ポインタに格納されているアドレスのデータへのアクセスは「*ip」と記述すれば、アクセスできます。

例えば、int a;という変数があって、ipがその変数aのアドレスを格納しているとします。そのとき、aという変数に10という値が入っていたら、「*ip」も10という値になります。

逆に「*ip」を5に書き換えたら、変数aの値も5になります。ポインタipに変数aのアドレスを代入するときの記述はこのようになります。

int* ip;
inta;

ip = &a;

変数の前に「&」を記述すれば、その変数のアドレスを取り出すことができます。

私はこのように覚えました。

宣言で「*」がついているものは、ついていないものより、1つ階層が下になっているから、同等に扱えない。 同等に扱うには「*」をつけて、階層を1つ上げてやる。または、「*」のついていないほうに「&」をつけて、階層を1つ下げてやる。

荒っぽく言えば、こういうことなので、これで整合性が取れます。

実際に、ポインタを使ったプログラムを書いてみます。

(例) #include <stdio.h>

     main() {
        int  x;    /* 変数 */
        int* ip;  /* ポインタ */

        /* ポインタにxのアドレスをセット */
        ip = &x;

        /* xに10を代入し、内容を表示 */
        x = 10;
        printf( "xに10を代入したときの変数の表示\n" );
        printf( "x = %d\n", x );
        printf( "*ip = %d\n", *ip );
        printf( "ip = %p\n\n", ip );

        *ip = 5;
        printf( "*ipに5を代入したときの変数の表示\n" );
        printf( "x = %d\n", x );
        printf( "*ip = %d\n", *ip );
        printf( "ip = %p\n", ip );     
     }

     出力結果
     xに10を代入したときの変数の表示
     x = 10
     *ip = 10
     ip = xxxxxxxx

     *ipに5を代入したときの変数の表示
     x = 5
     *ip = 5
     ip = xxxxxxxx

ipに格納されるアドレスは不明なので、ここでは「xxxxxxxx」と記述しています。実際にコピペして実行してもらえば、わかると思いますが、xに10を入れたら、ipも10。ipの値を変更したら、xの値も変更されます。

これは、どちらも同じアドレスのデータを操作しているからなんですね。イメージがつきにくいかもしれませんが、ポインタの使い方としてはこんな感じになることを覚えていってください。

ポインタと配列の関係

C言語のプログラムを3か月で身につけるための第2週でやるべきことで、配列について学んだと思います。この配列とポインタも密接な関係があるので、解説していきます。

おさらいですが、配列はこのように宣言しましたね。

int a[100];

この配列aは[]を省略して記述したら、配列0番目の先頭アドレスを指すことになります。つまり、どういう事かというと、a[0]と書くのと単にaと書くのでは、全く別のものということです。

この配列aは[]を省略して記述したら、配列0番目の先頭アドレスを指すことになります。つまり、どういう事かというと、a[0]と書くのと単にaと書くのでは、全く別のものということです。

a[0]は配列の0番目の値、aは配列0番目のアドレスとなり、aがアドレスと言うことはポインタへの代入が可能だと言うことです。

こういう記述が可能です。

int a[100];
int* pt;

pt = a;

また、pt = &a[0]; と記述しても同じです。ただ、pt = &pt[1]; と記述してしまうと、配列1番目のアドレスを格納することになるので、これはまた別物です。

ptを配列aの先頭アドレスとすると、ポインタを1つ進めた場合、ptはpt[1]のアドレスになり、さらに1つ進めるとpt[2]のアドレスになります。これが、ポインタの1つの特徴です。先ほど、ポインタに1足してもアドレスが1増えるわけではないということをお伝えしましたが、こういうときに使い勝手がいいわけです。

このような記述があったとします。

int a[100];
int* pt;
int i;

pt = a;

for(i = 0; i < 100; i++) {
    printf("a[%d] = %d\n", i, a[i]);
    printf("*(pt + %d) = %d\n", i, *(pt + i));
}

実際には配列に値を入れないとダメですが、この場合、どちらも同じ値を表示します。つまり、配列でa[i]と指定することと、ポインタで*(pt + i)とすることは等価です。

ただし、「()」のつける位置を間違えてはいけません。*(pt) + iとすると、0番目の配列の値にiを足したものになってしまい、i番目の配列の値を取るということと異なります。

配列をポインタで表すと何のメリットがあるかというと、配列の先頭アドレスを関数で渡すことができるため、関数内で配列の操作ができることです。

関数内で配列の操作ができるので、呼び出し元の配列は関数呼出し後、変更後の配列のデータを扱うことができます。

ポインタと関数の関係

C言語のプログラムを3か月で身につけるための第2週でやるべきことで、関数の引数や戻り値に配列は指定できないと説明しました。

配列を関数の引数や戻り値に指定はできませんが、配列の先頭アドレスを引数に持たせることができます。つまりポインタで配列を操作することになります。

C言語のプログラムを3か月で身につけるための第2週でやるべきことの関数の解説のところで、示したプログラムを少し変更します。

(例) #include <stdio.h>

     #define MAX 65535

     /* 四則演算するための関数 */
     int  Calculation( int x, int y, int* result ) {
        int ret = 1;

        result[0] = x + y;
        result[1] = x - y;
        result[2] = x * y;
        if ( y == 0 ) {
           result[3] = MAX;
           ret = 0;
        } else {
           result[3] = x / y;
        }

        return ret;
     } 

     main() {
        int  x, y;    /* 算術演算させるための変数 */
        int  ans[4];  /* 算術演算の結果 */
        char kigo[4]; /* +, -, *, / を格納 */
        char ret;     /* 関数の戻り値 */

        /* 初期化 */
        kigo[0] = '+';
        kigo[1] = '-';
        kigo[2] = '*';
        kigo[3] = '/';

        for ( int i = 0; i < 4; i++ ) {
           ans[i] = 0;
        }

        /* 算術演算させる数値の格納(ユーザがキーボードで入力) */
        /* ここでは文字入力されたときの処理を入れてないので   */
        /* 数字以外のものを入力するとプログラムは暴走する。   */
        /* また、整数のみしか扱っていない。                   */
        printf( "x = " );
        scanf( "%d", &x );
        printf( "y = " );
        scanf( "%d", &y );

        /* 四則演算実行 */
        ret = Calculation( x, y, ans );

        /* 結果を出力 */ 
        for ( i = 0; i < 4; i++ ) {
           printf( "x %c y = %d\n", kigo[i], ans[i] );
        }
        if ( ret == 0 ) {
           printf( "割り算はできませんでした。\n" );
        }
     }

     入力        
     x = 7 Enter 
     y = 3 Enter

     出力結果
     x + y = 10
     x - y = 4
     x * y = 21
     x / y = 2

与えられた入力に対して、四則演算を全て実行し、結果を返すプログラムです。始めは演算結果を「0」で初期化しています。その後、入力された数値とともに、結果を入力するans配列の先頭アドレスを、四則演算関数に渡しています。

四則演算関数の内部で、計算結果をans配列に入れていることになります。
ここで、注意して欲しいのは、配列が4つあることが前提の処理にしています。

万が一、渡されたポインタのアドレスが不正なものだったり、引き渡された配列が4未満だったら、正常に動作しません。したがって、関数がポインタを引数としてもらって、内部で扱う場合は、十分にエラーチェックしてから処理を行わせるようにしましょう。

この例の場合は、配列の要素数も引数で渡すべきでしょうし、関数内部で配列でのアクセスにしているところはポインタにしておくほうがいいでしょう。

ここは、あなた自身が、どう記述するのか最善かを考えてやってみてください。

まとめ

C言語のプログラムを3か月で身につけるための第3週でやるべきことを解説しました。

・ポインタ

ポインタは覚えると便利ですが、非常に複雑です。最低限、このページの内容のことはわかるようになってください。そうすれば、さらに複雑な状況になっても理解できると思います。

私もこのポインタが一番訳が分かりませんでした。研修の時もついていけませんでした。それでも、今は組み込みマイコンのプログラマーとしてやっていけています。

だから、あなたもあきらめずに、チャレンジしてください。何事もそうですが、あきらめたら、そこで終わりです。

ぜひ、ポインタの扱い方を1週間でマスターし、C言語のプログラムを3か月で身につけるための第4週でやるべきことへ進んでください。

  • このエントリーをはてなブックマークに追加

コメントを残す

*