第四回C言語講習

最終更新2014/6/28 執筆者:mznh

準備中・・・

今回はC言語において最も難しい概念と言われているポインタを学びます。

C言語学習者の半分はここで挫折すると言われるポインタですが、頑張ってください。

ポインタ

ポインタを学ぶにはまずアドレスというものを知らなくてはなりません。

アドレス

変数の説明の時にデータを記録しておく箱のようなものと説明しました。

そうするとその箱が置かれている場所。それがアドレスです。

この喩え話を踏まえて具体的な説明に移ります。

今まで私達が変数を宣言して記録してきた機械のことをメモリと呼びます。

またメモリはそれぞれに番号が振られていて管理できるようになっています。

つまり変数が置かれたところの番号がその変数のアドレスになります。

※イメージ

|0x000|0x001|0x002|0x003|...
|0x100|0x101|0x102|0x103|...
|0x200|0x201|0x202|0x203|...
|  :  |  :  |  :  |  :  |

では実際に変数のアドレスを見てみましょう。

変数のアドレスを参照するには&(アドレス演算子)を用います。

具体的には &変数名 でその変数のアドレスを表します。

以下は変数をaを宣言してそのアドレスを出力するプログラムです。

//address.c
#include<stdio.h>

int main(){
  int a;
  printf("a's address is %x\n",&a); //&aとしてaのアドレスを参照している    
  return 0;
}

実行結果は以下のとおり

[hoge@sol ~]$ gcc address.c
[hoge@sol ~]$ ./a.out
a's address is e7e1df3c

なおこの結果は環境ごとに異なります。

また前回ちらっとだけ言いましたが配列の名前はその配列の先頭の要素のアドレスを表します。

つまり int array[10]と宣言されているとき、

array と &array[0] は同値です。

アドレスまとめ

アドレスとは変数の保存されている場所のこと。

変数のアドレスは変数名の先頭に&をつけて表す。

ポインタ

さてやっとポインタが出てきました。ポインタとは一言で言うと「アドレスを格納しておく変数」です。

ポインタの宣言

ポインタの宣言は通常の変数とほぼ同じです。ただ、型の後ろか変数名の前のどちらかに * をつけます。

例えばint型の変数のアドレスを格納するポインタpを宣言する場合は、

int* p;int *p; 

と宣言します。

またこれをint型のポインタとかint*型の変数と言ったりします。

これと先ほどのアドレス演算子を組み合わせて、

int* p;
int a = 100;

p = &a;

とするとpにはaのアドレスが格納されます。

イメージとしてはpからaへ矢印が出ているとかんがえると良いでしょう。

|     |          |     |
|  ------------->| 100 |
|_____|          |_____|
   p                a

つまりpにはaへの矢印が格納されているわけです。

ポインタの指す先の値(間接参照)

では上の例でpが指している値を見るにはどうしたらよいでしょう。

そういう時に * (間接参照演算子)を用います。

ややこしいですがポインタの宣言の時に用いる*と全く一緒の記号を用いて表します。

つまり先ほどの例だと

int* p;
int a = 100;

p = &a;

printf("%d",*p);

とすると100が出力されます。

|     |          |     |
|  ------------->| 100 |
|_____|          |_____|
   p                a

この図だと

矢印自体がpの値で

矢印の指しているものの値が*pの値です。

ややこしいと思うので実世界の話に置き換えると

建物が変数の実態で、住所がアドレスです。

そしてその住所が書かれた手紙やメモ、それ自体がポインタです。(書かれている内容はアドレスです)

さてここまでアドレスとポインタを説明してきましたが、こんなわかりにくいものが何に使える のでしょうか、

実は様々な利点があるのですがその中でも代表的な例を紹介します。

値渡しと参照渡し

今まで関数に何気なく引数を渡してきたと思いますがそれは引数の実態を渡しているわけではありません。

実際には引数のコピーを渡しています。以下のコードを見てみましょう。

#include<stdio.h>

void change_100(int a){
  printf("a is %d \n",a);
  a = 100;
  printf("a is changed to %d \n",a);
}

int main(){
  int x=0;
  printf("x is %d\n",x);
  change_100(x);
  printf("x is %d\n",x);
  return 0;
}

実行結果は以下のようになります。

[hoge@sol ~]$ ./a.out
x is 0
a is 0
a is changed to 100
x is 0

引数として渡したxの値が関数の外では変わっていないのがわかると思います。

これはchange_100関数に渡したのは変数xの実態ではなく変数xのコピー(この場合だと0という値)を渡しているからです。

コピーを渡しているので中でどれだけ変更されても元のxの値は変化しないわけです。

これを変数の値を渡しているので値渡しと呼びます。

でも引数の実態を変更したい場合どのようにしたらいいのでしょうか、

そこで使われるのが参照渡しです。

参照渡しとは変数の値ではなく変数のアドレスを関数に渡します。

そして関数の中では前項の間接参照演算子*を用いて値を変更していきます。

先ほどのchange_100関数を参照渡しを用いて実装すると以下のようになります。

#include<stdio.h>

int change_100(int* a){
  printf("a is %d \n",*a);
  *a = 100;
  printf("a is changed to %d \n",*a);
}

int main(){
  int x=0;
  printf("x is %d\n",x);
  change_100(&x);
  printf("x is %d\n",x);
  return 0;
}

こちらの実行結果は以下のようになります。

[hoge@sol ~]$ ./a.out
x is 0
a is 0
a is changed to 100
x is 100

参照渡しを用いると関数の外でも値が変更されているのが確認できます。

配列とポインタ、参照渡し

ここに関しては多種多様な書き方ができる部分があるためポインタを用いるもののみ説明します。

配列を関数に渡す場合も参照渡しを用います。

例えばint型配列aとint型整数nを引数にとって配列aの要素を最初からn個取り出して出力するprint_array関数を定義してみます。

void print_array(int* a,int n){
  int i;
  for(i=0;i<n;i++){
    printf("%d ",a[i]);
  }
}   

配列なのにポインタを渡しています。どういうことでしょう?

実は配列とは単なるポインタにすぎないのです。

配列は番号の付けられた箱と前に説明しましたがアドレスに関しても同様の性質があります。

配列はメモリ上でも連続で配置されています。よって配列の各要素のポインタに1加算することによって次の要素のアドレスを表すことができます。

わかりにくいですね。

ここで配列の名前は先頭要素のアドレスを表すという性質を思い出してください。

先頭要素のアドレスなのでそれに1足せば配列名がaだった場合a[1]のアドレスということになります。

つまり *(a+1) とa[1]は同じものを表します。 

演習課題

演習1: 幾つか変数を宣言してそれぞれのアドレスを出力してみよ。

演習2: 配列を宣言して各要素のアドレスを出力してみよ。

演習3: 変数aとそれに対応するポインタpを宣言してポインタpにaのアドレスを格 納せよ。

演習4: 3の続き、ポインタpを用いてaの値を間接参照せよ。

演習5: 4の続き、ポインタpを用いてaの値を変更せよ。

演習6: int型のポインタを引数にとりそのポインタの指す先の値を0に変更する関数を定義せよ。

演習7: int型のポインタを2つ引数にとり、それらの指す先の値を交換する関数を定義せよ。

演習8: int型配列へのポインタとその配列の要素数、これら2つの引数を取り配列の要素の合計を返す関数を定義せよ。

参考文献

各回へのリンク


クリエイティブ・コモンズ・ライセンス
この 作品 は クリエイティブ・コモンズ 表示 - 非営利 - 改変禁止 2.1 日本 ライセンスの下に提供されています。免責事項