C言語入門講座

トップ>コンテンツ>C言語>10.ポインタ

 当講座はメールマガジン「Cプロダクション」の2000年5月10日から2002年7月31日までの記事を再編集したものです。

10.ポインタ

10-1. ポインタについて
10-2. ポインタの宣言
10-3. ポインタの使用例
10-4. ポインタと関数
10-5. 関数への配列の渡し方とポインタとの関連
10-6. ポインタで配列
10-7. ポインタ文字列と標準文字列の違い
10-8. ポインタの加減算
10-9. 二次元配列とポインタ
10-10. ポインタのポインタ
10-11. ポインタ配列
10-12. 二次元配列をポインタからアクセスする
10-13. ポインタ関数
10-14. 関数のポインタ
10-15. ポインタ関数ポインタ
10-16. コマンドライン

10-1. ポインタについて

 いよいよC言語の難関の一つといわれるポインタに来てしまいました。
このポインタがあるために志半ばにして息絶えたプログラマーは数知れません。

・・・ちょっといいすぎたかな?

ポインタ地獄から瀕死の状態でなんとか生還した私がポインタの回避について
手助けを致します。←話がちがうって(^^;;

 基本的にポインタを使うとプログラムの解析が大変になるので必要最低限に
留めておくべきだと考えています。これで人に見せる時分かりやすいプログラム
であれば事はスムーズに進むでしょう。

 しかし、他人の書いたプログラム、しかもポインタを駆使した素晴らしい
プログラムだった場合・・・地獄です。

これからポインタについて説明するわけですが、ポインタをおぼえる
過程で重要な事を述べておきます。

・ポインタは何のためにある?
・ポインタは何を格納する?
・ポインタを使うとどんな利点がある?

の3つです。初めて習うのに上の3つを肝に命じるのも酷ですが、
それを忘れた時ポインタでつまづくのでは?っと私の経験談です(爆)。

因みに構造体の時も、
「そんなの使わなくたって立派なプログラムできるよ〜だ」と言ってたら、
やはりつまづいてしまいました(爆)。


◇ポインタって何?

 それでは早速ポインタとはC言語ではどんな働きをするものか簡単に
説明します。
一般的にポインターと呼ばれるものの共通点は、目的地を指し示すものになり
ます。

ポインター犬は獲物を発見すると立ち止まってその方向を指し示すため、それが
名前の由来になったそうです。(参考:広辞苑)
勿論レーザーポインタも目的とするものをレーザーで指し示すためにあります。
・・・そういえばマウスの矢印もポインターっていいますね(笑)。
おまけにショートカットやリンクもそうです。

そしてC言語のポインタもある場所を指し示すものなのです。

−そのある場所とは?

プログラムの場合、場所として扱えるのはメモリになります。
今まで、メモリーに関しての説明もしていないので従来のメモリの番地が・・・
という説明からは入らないつもりです。
 それよりもグラフィカルで直感的に理解できるような方法を考えています。

例としてデスクトップとアイコンとマウスポインタで説明します。

デスクトップはメモリー全体を示します。
そしてアイコンが所々に散乱しています(笑)。
そこでマウスポインタに指示を与えるとします。

まず左上のマイコンピュータのアイコンをクリックしてください。
・・・と指示します。

そのためには目でマウスポインタを追いながら目的のアイコンまで移動します。
そしてクリックします。これで目的のアイコンが選択されました。
・C言語ではポインタが目的の変数の場所を示した状態になります。

それからダブルクリックするなり操作します。
・C言語のポインタでも目的の変数を操作することが可能になります。

最初の「ポインタは何を格納する?」は変数の場所でした。

マウスポインタの例ではあまりに抽象的だったので実際のプログラミ
ングに置きかえるのは難しかったのではと後で読み返すとそんな感じでした。

 それではもっと本質的な部分をショートカットの例を用いて説明します。

◇ショートカットの例

 まず、デスクトップ上で右クリックし新規作成でショーカットを選びます。
これで一応ショーカットは作られますがこれだけではどこにもリンクしていない
ためまったく機能しません。
・C言語ではポインタの宣言にあたります。

 次に、リンクする場所を決めます。URLでもプログラムの場所でもかまいま
せん。これでやっとショートカットとして機能します。
・ポインタに変数のアドレスを設定するのと同等です。

 そしてそのショートカットをダブルクリックすることにより遠隔操作を可能に
します。
・ポインタを使って本来操作できない領域にある変数を使うことができます。

注意!
ショーカットで気をつけないといけないのはリンク切れです。
ポインタにおいても同じ事で設定したアドレスに目的の変数が存在しなくなった
場合プログラムが暴走する可能性があります。


10-2. ポインタの宣言

◇ポインタの宣言

それでは早速ポインタを宣言しましょう。
int型変数を操作するポインタ変数pの宣言。

int *p;

これでポインタpが宣言されました。
実はC言語の宣言の書式はあまり好きではないのです。
何故かって?それは後々混同しやすい事が起きるからです。

ちなみにC++では

int* p;

と宣言できます。これなら意味の解釈も楽になります。
まず誤解してもらいたくない事柄
1.変数名に*をつけたものがポインタではない。
2.ポインタに代入するのは変数の値ではない。アドレスである。
3.宣言時の型は操作する変数の型でありポインタの型ではない
  因みにポインタの型はアドレスを代入することからintと同じになります。

ということで宣言式を解釈すると...

int        *           p
操作する変数の型 ポインタとしての宣言 ポインタ名

になります。

次にポインタに操作する変数のアドレスを設定します。

p = (操作する変数のアドレス);

これでOKです。上の1で誤解している人には p である意味自体わからない
と思います。この p がポインタ名なのです。

次に問題なのは変数のアドレスをどうやって表すのかです。
変数の頭に & をつけるとその変数のアドレスを表します。
因みにこの & はビット演算子の & とは別物になりアドレス演算子と呼びます。
たとえば操作する変数を var とすると。

p = &var;

のようにします。
最後に変数の遠隔操作。これはどうやるのかというとポインタに間接参照演算子
をつけて操作します。その演算子が * です。紛らわしいでしょう。
そう宣言のときの * とも掛け算の * とも違うのです。

p = &var;
*p = 3;

上の式はポインタ p に変数 var を操作するように設定したので、*p は間接的
に var を操作することになります。つまり上の式ではポインタを使って var 
に3を代入しているのです。

ここまでのおさらい。

int *p;  /* int型変数を操作するポインタ p の宣言 */
int var; /* int型変数 var の宣言 */

p = &var; /* var のアドレスをポインタ p に設定 */
*p = 3;   /* p による遠隔操作ここでは var に3を代入 */


当たり前なのですがポインタが操作する変数は変えることができます。

int *p;        /* int型変数を操作するポインタ p の宣言 */
int var1,var2; /* int型変数 var1 var2 の宣言 */

p = &var1; /* var1 のアドレスをポインタ p に設定 */
*p = 3;   /* p による遠隔操作 var1 に3を代入 */

p = &var2; /* こんどは操作する変数を var2 に変更 */
*p = 7;   /* p による遠隔操作 var2 に7を代入 */

10-3. ポインタの使用例

ポインタの使用例と利便性のお話です。
というわけで今回はプログラミングに触れません(笑)。

◇ポインタの使用例

 よくポインタを利用される場面として関数の呼出しの仮引数があります。
関数は実行結果を値として返す事ができますが2つ以上の結果が欲しい時には
仮引数にポインタを使い呼び出す時に関数によって結果を格納する変数のアド
レスを引数として渡します。
 このように関数に間接参照させる事によって変数はスコープの壁を超えてア
クセスする事ができます。(これについては次回解説します)

 次に多いのが配列の代わりに使ってリスト構造を作る場合です。
これが結構難しくて他人の書いたプログラムだと解釈が大変です。(^^;;
リストにポインタを使う利点としては並び替え(ソート)やリストの挿入・削除
ができることです。別にポインタを使わなくてもできないことは無いのですが
ポインタが並び順を記録するのと違ってリストを書きなおす作業が入ったりす
るのでデータが膨大になるとコンピュータに大きな負荷をかけてしまいます。

 最後に構造体内での利用です。
ここまで使いこなす事ができればC言語で組めないプログラムはないでしょう。
上のリストと重複しますが多次元のリストをチェーン状に繋いだりする事がで
きます。

 ポインタが難しいと言われるのは理論ではなくてその利用法だと思います。
ハッキリ申しますと関数・配列・構造体について基本的な事がわかっていない
とポインタの利用方法がわからないし、ポインタを使い始めるとさらに関数な
どについて原理的な部分から再復習する必要性がでてきます。
 しかしポインタを利用して得られるものは絶大です。オマケにバグの発生率
も最大です(笑)。ポインタを使えば万能なプログラムが作れますがその万能さ
が諸刃の剣で使い方を誤ると「出力結果が違う」とか「ループが終わらない」
といった程度では済まないことが起きます。運がよくてプログラムの強制終了
最悪だとシステムのフリーズ・ダウン・クラッシュなどを引き起こします。
UNIX系のOSだとメモリ管理がしっかりしているのでそこまではならない
と思いますが念のためサーバーに使われているマシンでの試行は避けたほうが
いいと思います。(サーバーでアプリ開発する人はいないと思いますが...)


10-4. ポインタと関数

◇ポインタと関数

 いままで関数を使っていて変数の受け渡しで不便だと感じた事はありますか?
例えば、関数は引数として変数の値を代入するとき複数の値を代入する事が
できますが値を返す時は1種類だけになります。

 それは関数の戻り値が呼出し側では関数の値になるからです。

main()
{
    var = func(10,5);
}

int func(int x, int y)
{
    return (x + y);
}

つまり上の例の場合func(10)は15となりvarに代入されるのです。
returnに記述できる値は式でもかまいませんが1種類になります。

もし return x,y; とした場合どうなるか。
この時のカンマは順次演算子となり値は一番右側だけ評価されます。
つまり
return y; と同じになってしまうわけです。

それでは2つの変数を代入してそれぞれ10倍にして返したい。
このように2つ以上の結果を得たい場合、どのようにすれば良いでしょう。

一つはグローバル変数を使うことです。

int x,y;

main()
{
    func(10,15);
}

void func(int a, int b)
{
    x = a * 10;
    y = b * 10;
}

こうすれば戻り値としての扱いではなくfunc関数内で代入処理をすることで
解決します。
しかし、この方法ではローカル変数ではスコープ外となって代入操作が
できないので不便です。
もちろん全てグローバル変数にすれば解決しますが、識別子名が重なったり
間違って使用したりする可能性が大きくなりバグの原因となります。
またそれぞれの変数がどこで使われるのかが把握できなくなり困ります。

そういうことで限定的にスコープの枠を超えてアクセスが可能ならば
非常に便利になるわけです。

それではさっきのプログラムをポインターを利用して書き換えてみます。
もちろん、変数はmainのローカル変数にします。

#include<stdio.h>
void func(int*, int*);

main()
{
    int x = 10, y = 15;
    func(&x, &y);
    printf("x=%d\ny=%d",x,y);
}

void func(int *a, int *b)
{
    *a = *a * 10;
    *b = *b * 10;
}

ここではprintfも加えてみました。
ここはとても重要な所なので一行ずつ解説していきます。

1.void func(int*, int*);

プロトタイプ宣言です。funcではポインタを使って間接参照をするので
仮引数はポインタとして宣言する必要があります。

2.func(&x, &y);

こちらは呼出しの時の記述です。ポインタにはアドレスをセットするので
引数にはポインタによって参照される変数のアドレスを入れます。

3.void func(int *a, int *b)

ここがfunc関数の本体です。この時点で

a = &x; b = &y; 

の操作が行われています。ちなみに a と b はポインタです。
ここでもう一度忠告しますが、

int *p;

と宣言した時、

int * までが型で p が識別子名です。これを間違うとどうにもならないので
しっかりおぼえてください。

4.*a = *a * 10;
    *b = *b * 10;

さいごに処理部分です。すでにアドレスのセットは終わっているので
それぞれ

    x = y * 10;
    x = y * 10;

を意味します。

10-5. 関数への配列の渡し方とポインタとの関連

◇関数への配列の渡し方

ここは一旦ポインタから外れて配列と関数になります。
でもポインタのような部分があるので説明します。

▼例1
main()
{
    int i=1,j=2,ans;
    ans=func(i,j);
}

int func(int x,int y)
{
    return (x*y);
}

これは渡した2つ整数の積を求める関数です。(ヘッダー部分は省略しています)
通常変数ならば上のようにして値を渡しますが、配列の場合はどうでしょう。

▼例2
 1:main()
 2:{
 3:    int i[]={1,2};
 4:    int ans;
 5:    ans=func(i[]);
 6:}
 7:
 8:int func(int x[])
 9:{
10:    return (x[0] * x[1]);
11:}

このプログラムは変数を配列に置き換えただけのものです。
ちなみにコンパイルはできません。
まず4行目のi[]で構文エラーになってしまいます。
それと配列は変数を並べたものを指すので、値は複数あります。
とりあえず次の方法でこの場合解決はしますが、配列の長さがわからない
場合や配列が長い場合はどうしようもありません。

▼例3
 1:main()
 2:{
 3:    int i[]={1,2};
 4:    int ans;
 5:    ans=func(i[0].i[1]);
 6:}
 7:
 8:int func(int x,int y)
 9:{
10:    return (x,y);
11:}

それでは一般的な配列の渡し方を以下に記述します。

▼例4
 1:main()
 2:{
 3:    int i[]={1,2};
 4:    int ans;
 5:    ans=func(i);
 6:}
 7:
 8:int func(int x[])
 9:{
10:    return (x[0] * x[1]);
11:}

例2にそっくりですがi[]をiとするだけで解決しました。
これは変数の場合の「値渡し」とはちがう事を意味します。
それは次に説明したします。


◇ポインタとの関連

配列の受け渡しにはポインタの働きと密接な関係があります。
ポインタを利用して変数を渡す時は

1.変数のアドレスを渡す
2.そのアドレスをポインタにセットする

という方法になります。これが配列の引渡しと同じということになります。
因みにi[]という配列のアドレスはiになります。
だから引数のi[]をiに変える事でfuncに配列のアドレスが渡されます。

・・・ということは?

▼例5
 1:main()
 2:{
 3:    int i[]={1,2};
 4:    int ans;
 5:    ans=func(i);
 6:}
 7:
 8:int func(int *x)
 9:{
10:    return (*x * *(x+1));
11:}

こんなことができてしまいます。
つまりアドレスで渡されるのならポインタで受けとってみよう
ということです。

ここで *x     は配列の先頭の値 i[0]
       *(x+1) は配列i[0]の次の要素の値

になるのです。

こうなってくるとポインタで配列が作れるのでは?
となってきます。

10-6. ポインタで配列

 前回、配列を関数へ引き渡す方法とそれを配列・ポインタで受け取る方法を
説明しました。それではポインタで配列ができるのではないかということで
今回はポインタで配列を作ってみます。

◇ポインタで配列

これは前回のプログラム。(プロトタイプ宣言等は省略しています)

 1:main()
 2:{
 3:    int i[]={1,2};
 4:    int ans;
 5:    ans=func(i);
 6:}
 7:
 8:int func(int *x)
 9:{
10:    return (*x * *(x+1));
11:}

これで、配列をfunc()に渡しポインタで受け取りました。

引数の i は配列 i の先頭アドレスを意味します。
それをポインタに渡したことでポインタxは配列をアクセスすることが
できました。その時の対応は次のとおりです。

 x     --> i(アドレス)
*x     --> i[0]
*(x+1) --> i[1]

ということで最初からポインタで配列が作れないかやってみることにしました。

#include<stdio.h>

main()
{
    char *c ="Cプロダクション";
    printf("%s",c);
}

なぜ、文字列かというとint型ではコンパイルできませんでした(:_;)
文字列のみ宣言できるようです。

ここで注意してもらいたいのは初期化している代入式は

 c = "Cプロダクション";

であることです。宣言時の * はポインタ型であることを表すもので
間接演算子の * ではないので注意してください。
ということでポインタの初期化は値でなくあくまでアドレスを渡している
ことになります。

前回のプログラムの8行目 int func(int *x) も同様で
 x に配列の先頭アドレスを代入しているのです。

◇──────────────────────────────────◇
│C言語のポインタではここらへんが勘違いしやすいところなのですが   │
│C++ではポインタの宣言が次のようになっているのでわかりやすいと  │
│思います。                             │
│int* i;                               │
│char* c = "Cプロダクション";                    │
◇──────────────────────────────────◇

■最重要事項■
次にポインタを初期化する場合の注意。

#include<stdio.h>

main()
{
    char *c;
    c ="Cプロダクション";
    printf("%s",c);
}

DOS系では上のように記述しても動作しますが、理論的にも実運用てきにも
危険極まりありません。

これで「ページ違反です」とか「スタックエラーです」というダイアログが
出てプログラムの強制終了が出るくらいならまだマシでOSがフリーズしても
おかしくないほど危険です。(いまのところエラーは出ませんが・・・)

普通こういう場合はmalloc関数を使います。(これは後で・・・)
特に文字列が標準入力からだったりすると必須です。

因みに上のプログラムLinuxサーバでコンパイル・実行したところ
一瞬で危険なプロセスと判断されたのか抹殺されてコアを吐いて死にました。

別にサーバーにはまったく影響ないのですが、
自分でサーバーでの使用を禁止しておきながら・・・反省です。

みなさんは絶対サーバー(特にプロバイダの)では実行しないでくださいね。
もし暴走したりしたらまわりに迷惑がかかるだけでなく自分の接続権利が
抹殺されるかもしれないので(笑)。

2011-08-09追記:
上記の理由についてご質問がありましたので。回答を追記いたします。

理論的に危険な理由:
C言語の変数は初期化が必要でその際に必要なメモリーを確保します。
char配列で文字列を初期化する場合は文字列の長さに合わせてメモリーの確保が
行われます。

一方ポインタは参照先のアドレスと変数の型のみを定義し、メモリの確保はポイ
ンタ分だけで参照先の分はわからないので確保しません。
その後ポインタに対して初期化を行う場合、ポインタに割り当てるアドレスは変
数として確保されているメモリの先頭アドレスになりますが、
ここに未定義のアドレスやプログラム実行用(ワークメモリ)以外のアドレスを
割り当てた場合、理論的には参照先が存在しないことになります。
Java的にはNullPointerExceptionってところでしょうか。C言語系ではコンパイル
時にチェックされない可能性がありますので見落としやすいところです。

実運用的に危険な理由:
サンプルのプログラムの場合"Cプロダクション"は直接ソースに書かれている
文字列で、実行時はメモリに展開したプログラム本体の中にあり、プログラムが
使用するワークメモリ領域とは別の場所になります。
このようなアクセスを許すと、プログラムが自分自身を書き換えたり、他の実行中
プログラムにアクセスして改変できてしまうため32bit以上の処理系の場合、
メモリアクセスを監視して、アクセス違反があった場合にプログラムを
強制停止しています。現在は16bit処理系が使われることが殆どなくなっている
と思いますのでプログラムが強制終了される程度で済む場合が多いかと思います。

16bit処理系でサンプルプログラムを実行した場合"Cプロダクション"には
最後にnull文字が入っているためprintf("%s",c)で出力してもnull文字でストップ
しますが、これが文字列ではなく文字で初期化(シングルクォートで挟んだ1文字)
した場合、null文字が付与されない為、printfはnull文字が見つかるまでひたすら
メモリーの内容を出力します。書き込みに成功した場合は実行中のプログラム
自身が破壊される事になります。

以上でC言語で扱う変数などのオブジェクトは明示的にメモリ確保されたもの
なのでポインタでアクセスする際も正しくメモリ確保されたものに割り当てる
必要があります。それでも配列や文字列の場合はループ上限の数え間違い等で
確保された領域を飛び出し強制終了されることがありますので油断はできません。

2011-08-10追記:
昨日の追記について私の知識不足により不適切な表現がございました。
上記の「ワークメモリ領域」→.bssまたは.dataに該当
「直接ソースに書かれている文字列」→.rodataに該当
と解釈頂ければと思います。
メモリのアクセス違反の危険性についても認識違いや誇張した表現で不適切と判断いたしました。


10-7. ポインタ文字列と標準文字列の違い

ポインタの初期化によって作られた文字列と配列で作られた文字列の
違いを解説します。

◇ポインタ文字列と標準文字列の違い

まずはそれぞれのプログラムから見てみましょう。

main()
{
    char *str ="Cプロダクション";
    printf("%s\n",str);
}

main()
{
    char str[] ="Cプロダクション";
    printf("%s\n",str);
}

これでは、記述以外に何が違うのかがまったくわかりませんね(^^;;

実際にどのような動作をしているのかを説明します。

ポインタの場合:
まずポインタstrを宣言します。
メモリにstrの領域(長さはint)が作られます。
次に"Cプロダクション"をメモリに格納します。
そして、その文字列の先頭アドレスをstrに代入します。

配列による文字列の場合:
配列strを宣言します。
次に"Cプロダクション"をメモリに格納します。
その先頭アドレスを識別子strとして定義します。

・・・といった感じです。

◇注意────────────────────────────────◇

 実際はコンパイラの設計によって違うかもしれません。
また、文字列がプログラム領域に存在する状態で使われるのか
データ領域にコピーして使うのかもまちまちなので保証はできかねます。
 因みに私はコンパイラの設計をしたことがないのでアセンブリの経験を
元に予想して書いています。
 別にわからなくてもC言語プログラミング上影響は無いと思うので追求は
しないつもりです。

◇──────────────────────────────────◇

そして、決定的な違いは、

 ポインタ型はポインタの分(int1つ)だけ余分にメモリを消費するが
ポイントする文字列を換えることができる。(文字列中の先頭位置も換えれる)

ということです。これは文字列処理で力を発揮します。

#include<stdio.h>
main()
{
    char *str ="Cプロダクション";
    for(;*str!='\0';str+=2)
    printf("%s\n",str);
}

/* 結果-------------
Cプロダクション
プロダクション
ロダクション
ダクション
クション
ション
ョン
ン

------------------*/

これはprintfの%sの性質を利用したものです。
このようにポインタによる文字列の場合先頭の位置を換えることが
可能で上のような処理が簡単に行えます。
配列では結果的に同じ物は記述できますが、内部動作まで同じ物は
書けません。

 ポインタと配列は似たような操作ができますがポインタが配列を定義
できなかったり配列がポインタ特有のアドレス換えができないことから
両者は似て非なるものである。

そして配列をポインタとして定義した『ポインタ配列』というものが
存在します。ややこしいー(^^;;;

ポインタは配列では在りませんが、今度は配列をポインタとして定義します。
(言葉で説明するとドンドン深みにハマリそう・・・)

char *str[]={"おはよう","こんにちは","こんばんは"};

このようなものをポインタ配列といいます。
これと同じようなものを配列で作ると

char str[][11]={"おはよう","こんにちは","こんばんは"};

となります。
この2つの違いはさっきの例と同じでポインタ配列の方はポインタが
配列になっていてそれぞれが各文字列の先頭アドレスを記憶しています。
そしてその分余計にメモリを消費しています。
ただし、メモリへの文字列の格納には次の点が違います。
配列は、一番長い文字列に合わせてメモリを確保するので合計33バイト
消費しています。それに比べポインタ配列の方は必要な分だけ確保すれば
いいので文字列の分の確保量は31バイトで済みます。

これは配列の場合、全体の先頭アドレスを識別子に当てているだけなので
列の長さは一定にしないといけないためです。

それに比べポインタ配列は各行のアドレスを保持しているため列の長さは
揃える必要が無いのです。

ポインタ配列:
str[0]--------->おはよう\0
str[1]--------->こんにちは\0
str[2]--------->こんばんは\0

2次元文字列:
str[0]--------->おはよう\0\0\0
str[1]--------->こんにちは\0
str[2]--------->こんばんは\0

矢印ははそれぞれ文字列の先頭アドレスを指します。
\0はnull文字です。配列では初期化で指定されない部分はnull文字になります。

2次元文字列のstr[1]やstr[2]は個別にアドレスを記憶しているわけではない
ので列の長さを一定にしてアクセスできるようにしています。

逆にいえばポインタ配列の方は各行を指すアドレスを換えることが
可能なのです。

#include<stdio.h>
main()
{
    int i,buff;
    char *str[] ={"おはよう","こんにちは","こんばんは"};
    for(i=0;i<3;i++)printf("%s\n",str[i]);
    printf("\n");buff=str[0];str[0]=str[1];str[1]=buff;
    for(i=0;i<3;i++)printf("%s\n",str[i]);
}

/* 結果 ---------
おはよう
こんにちは
こんばんは

こんにちは
おはよう
こんばんは
---------------*/

どうでしょう。「おはよう」と「こんにちは」が入れ替わってしまいました。
 直接文字列の書き換えなしで位置の交換ができるのはポインタによる利点
なのです。これはソート(並べ替え)処理に力を発揮します。
また応用すれば文字の検索・置換や挿入・削除が効率的に行えるプログラムが
できます。
 配列のみで同じ事を実現しようとするとポインタを使用するのに比べ
余分な書き換えが生じて効率的ではありません。

10-8. ポインタの加減算

◇ポインタの加減算
上記で利用したプログラム

#include<stdio.h>
main()
{
    char *str ="Cプロダクション";
    for(;*str!='\0';str+=2)
    printf("%s\n",str);
}

/* 結果-------------
Cプロダクション
プロダクション
ロダクション
ダクション
クション
ション
ョン
ン

についてポインタの加算部分を説明します。

ここではポインタstrはfor分で1回に2つ加算されています。
ポインタの値(str)はアドレスなのでアドレスが2つ増えた(正確には進んだ)
事が分かります。つまり2バイトづつ進んでいるわけです。

(注意:アドレスの進み方は型の影響を受けます)

よって文字列の先頭が2バイト(全角1文字)分づつずれて[結果]のような表示が
できたわけです。

それでは今度はポインタの減算を含めてみます。

#include<stdio.h>
main()
{
    int cnt=0;
    char *str ="Cプロダクション";
    for(;*str!='\0';str+=2,cnt++)
    printf("%s\n",str);
    for(;cnt>=0;str-=2,cnt--)
    printf("%s\n",str);
}

/* 結果
Cプロダクション
プロダクション
ロダクション
ダクション
クション
ション
ョン
ン

ン
ョン
ション
クション
ダクション
ロダクション
プロダクション
Cプロダクション
*/

printfの%sは文字列の先頭(ここではポインタの示すアドレス)から
\0(null)までを表示する変換指定子なので上のような結果が得られます。


ポインターの加減算は参照するアドレス値の加減算を行うのですが、
その単位が変数型によって影響を受けます。

つまり p+1 としてもアドレス値が1増えるとは限らないのです。
それは次の例で確かめて見ましょう。

◇文字型の場合

まずは文字型の場合です。

#include<stdio.h>
main()
{
    char str[]="ABCDEFGHIJKLMN";
    char *p;

    for(p=str;*p!='\0';p++)printf("%p = %c\n",p,*p);
}

/* 結果
0064FDF4 = A
0064FDF5 = B
0064FDF6 = C
0064FDF7 = D
0064FDF8 = E
0064FDF9 = F
0064FDFA = G
0064FDFB = H
0064FDFC = I
0064FDFD = J
0064FDFE = K
0064FDFF = L
0064FE00 = M
0064FE01 = N
*/

これはちゃんと1バイトずつアドレス値がふえていますね。
pを1増やすたびアドレス値がchar一つ分ふえています。

◇整数型の場合

#include<stdio.h>
main()
{
	int arr[]={1,2,3,4,5,6,7,8,9,0};
	int *p;

	for(p=arr;*p!=0;p++)printf("%p = %d\n",p,*p);
}

/* 結果
0064FDDC = 1
0064FDE0 = 2
0064FDE4 = 3
0064FDE8 = 4
0064FDEC = 5
0064FDF0 = 6
0064FDF4 = 7
0064FDF8 = 8
0064FDFC = 9
*/

今度はアドレス値が4バイトずつ増えています。
p++なのでプログラム上ではアドレス値が1ずつ増えるような
感じはしますが実際はintひとつ分アドレスが進んでいます。

以上をまとめるとポインタの演算は

p+N (Nは整数)の場合

現在のアドレス値 + N×sizeof(ポインタの宣言型)

になります。

◇配列との関係

さらに配列との関係をアドレス値を基準にして説明します。

#include<stdio.h>
main()
{
  int i,arr[] = {0,1,2,3,4,5,6,7,8,9};
  int *point;

  point = arr;
  for(i=0;i<10;i++){
    printf("&arr[%d] = %p arr[%d] = %d ",i,&arr[i],i,arr[i]);
    printf("point+%d = %p *(point+%d) = %d\n",i,point+i,i,*(point+i));
  }
}

/* 結果
&arr[0] = 0064FDDC arr[0] = 0 point+0 = 0064FDDC *(point+0) = 0
&arr[1] = 0064FDE0 arr[1] = 1 point+1 = 0064FDE0 *(point+1) = 1
&arr[2] = 0064FDE4 arr[2] = 2 point+2 = 0064FDE4 *(point+2) = 2
&arr[3] = 0064FDE8 arr[3] = 3 point+3 = 0064FDE8 *(point+3) = 3
&arr[4] = 0064FDEC arr[4] = 4 point+4 = 0064FDEC *(point+4) = 4
&arr[5] = 0064FDF0 arr[5] = 5 point+5 = 0064FDF0 *(point+5) = 5
&arr[6] = 0064FDF4 arr[6] = 6 point+6 = 0064FDF4 *(point+6) = 6
&arr[7] = 0064FDF8 arr[7] = 7 point+7 = 0064FDF8 *(point+7) = 7
&arr[8] = 0064FDFC arr[8] = 8 point+8 = 0064FDFC *(point+8) = 8
&arr[9] = 0064FE00 arr[9] = 9 point+9 = 0064FE00 *(point+9) = 9
*/

配列をポインタから参照した時の対応表みたいのを出してみました。
配列にアクセスする時のポインタの記述の参考になると思います。

前回の説明の通り、point+i の値は

  pointの値(つまり配列arrの先頭アドレス)+型サイズ(int)×i

になっています。(ちゃんと16進数で4バイトづつ増えています。)
対応としては、

アドレス値 :&arr[i] = point+i
要素の値  :arr[i]  = *(point+i)

の関係になっています。


10-9. 二次元配列とポインタ

◇2次元配列とポインタ

まずは2次元配列をポインタを使ったようにアクセスしてみます。
注:ポインタは使っていません。

#include<stdio.h>
main()
{
    int matrix[2][3]={{1,2,3},{4,5,6}};

    printf("matrix[0][0] = %d\n",**matrix        );
    printf("matrix[0][1] = %d\n",*(*matrix+1)    );
    printf("matrix[0][2] = %d\n",*(*matrix+2)    );
    printf("matrix[1][0] = %d\n",**(matrix+1)    );
    printf("matrix[1][1] = %d\n",*(*(matrix+1)+1));
    printf("matrix[1][2] = %d\n",*(*(matrix+1)+2));

    printf(" &matrix[0][0] = %d\n",&matrix[0][0]);
    printf(" &matrix       = %d\n",&matrix);
    printf("  matrix       = %d\n",matrix);
    printf(" *matrix       = %d\n",*matrix);
    printf("**matrix       = %d\n",**matrix);

    printf("&matrix[0][0]+1 = %d\n",&matrix[0][0]+1);
    printf("&matrix+1       = %d\n",&matrix+1);
    printf(" matrix+1       = %d\n",matrix+1);
    printf("*matrix+1       = %d\n",*matrix+1);

}

/* 結果(注:今回に限ってアドレス値を10進数で表示しました)
matrix[0][0] = 1
matrix[0][1] = 2
matrix[0][2] = 3
matrix[1][0] = 4
matrix[1][1] = 5
matrix[1][2] = 6
 &matrix[0][0] = 6618604
 &matrix       = 6618604
  matrix       = 6618604
 *matrix       = 6618604
**matrix       = 1
&matrix[0][0]+1 = 6618608
&matrix+1       = 6618628
 matrix+1       = 6618616
*matrix+1       = 6618608
*/

配列の場合サンプルのようなポインタもどきが出来るのです。
 &matrix:行列の先頭アドレス
  matrix:最初の行の先頭アドレス
 *matrix:最初の列の先頭アドレス
**matrix:要素の値

これだけでは結果として
&matrix=matrix=*matrix、で同じ値になります。
ただし+nとした時に増分するアドレスが変わります。

 &matrix+1:表の終わりの次にあるアドレス(+24)
  matrix+1:次の行のアドレス(+12)
 *matrix+1:次の要素のアドレス(+4)

一度は図に書いてアドレスの対応を取ってみると理解しやすくなります。

それでは2次元配列をポインタでアクセスしてみましょう。

main()
{
    int matrix[2][3]={{1,2,3},{4,5,6}};
	int **point;
	point=matrix;
    printf("matrix[0][0] = %d\n",**point        );
    printf("matrix[0][1] = %d\n",*(*point+1)    );
    printf("matrix[0][2] = %d\n",*(*point+2)    );
    printf("matrix[1][0] = %d\n",**(point+1)    );
    printf("matrix[1][1] = %d\n",*(*(point+1)+1));
    printf("matrix[1][2] = %d\n",*(*(point+1)+2));
}

コンパイル時にポインタへの代入に警告が出てきて
実行するとページ違反で強制終了しました。
なぜ、このようなことになったのでしょう。


          

10-10. ポインタのポインタ

◇ポインタのポインタ

前回のプログラムでは、

main()
{
    int matrix[2][3]={{1,2,3},{4,5,6}};
        int **point;
        point=matrix;
    printf("matrix[0][0] = %d\n",**point        );
    printf("matrix[0][1] = %d\n",*(*point+1)    );
    printf("matrix[0][2] = %d\n",*(*point+2)    );
    printf("matrix[1][0] = %d\n",**(point+1)    );
    printf("matrix[1][1] = %d\n",*(*(point+1)+1));
    printf("matrix[1][2] = %d\n",*(*(point+1)+2));
}

コンパイル時にポインタへの代入に警告が出てきて
実行するとページ違反で強制終了しました。

なぜ、このようなことになったのでしょう。
というところから始まります。
因みにプログラム中に出ている **point は何でしょう?

これがポインタのポインタです。

そして次の行の代入は間違いです。
2重ポインタ(ここでは**pointのことをそう呼ぶことにします)は
ポインタを指し示すポインタを意味します。

それでは、代入するものは?

次の例を見てみましょう。

#include<stdio.h>

main()
{
        int i=10;
        int *p;
        int **point;

        p=&i;
        point=&p; /* ポインタpのアドレスを代入 */

        printf("      i = %d\n",i);
        printf("     *p = %d\n",*p);
        printf("**point = %d\n",**point);
        printf("     &i = %p\n",&i);
        printf("      p = %p\n",p);
        printf(" *point = %p\n",*point);
        printf("     &p = %p\n",&p);
        printf("  point = %p\n",point);
        printf(" &point = %p\n",&point);
}

/* 結果
      i = 10
     *p = 10
**point = 10
     &i = 0064FE00
      p = 0064FE00
 *point = 0064FE00
     &p = 0064FDFC
  point = 0064FDFC
 &point = 0064FDF8

*/

2重ポインタはポインタを指し示すためにあるので代入する値は
ポインタpのアドレスになります。

また、上の結果を表にしてみます。

メモリ番地  変数名 値
64FE00番地  i      10(10進)
64FDFC番地  p      64FE00(16進)
64FDF8番地  point  64FDFC(16進)

プログラム上で代入した通り
pointの値=pのアドレス
  pの値=iのアドレス

になっています。ここでそれぞれ間接アクセスすると

              |------------|
0064FE00 i    |10(10進)    | i,*p,**point
              |------------|
0064FDFC p    |64FE00(16進)| &i,p,*point
              |------------|
0064FDF8 point|64FDFC(16進)| &p,point
              |------------|

**point:

   +<point>-+   +<p>-----+   +<i>-----+
   | 64FDFC |-->| 64FE00 |-->|   10   |
   +--------+   +--------+   +--------+

*point
   +<point>-+   +<p>-----+
   | 64FDFC |-->| 64FE00 |
   +--------+   +--------+

*p
   +<p>-----+   +<i>-----+
   | 64FE00 |-->|   10   |
   +--------+   +--------+

こんな感じになります。



10-11. ポインタ配列

◇ポインタ配列

 前も、少し触れましたが今回、徹底的にポインタ配列を検証してみます。

#include<stdio.h>

int main()
{
  int i;
  char *p_str[4]={"あいうえお","ABC",
                    "いろはにほへと","XYZ"};
  for(i=0;i<4;i++){
    printf("&p_str[%d] = %p ",i,&p_str[i]);
    printf("p_str[%d] = %p String = %s\n",i,p_str[i],p_str[i]);
  }
  return 0;
}

/* 結果1
&p_str[0] = 0064FDF4 p_str[0] = 0040A138 String = あいうえお
&p_str[1] = 0064FDF8 p_str[1] = 0040A143 String = ABC
&p_str[2] = 0064FDFC p_str[2] = 0040A147 String = いろはにほへと
&p_str[3] = 0064FE00 p_str[3] = 0040A156 String = XYZ
*/
            p_str[4]
           +--------+                 +--------------+
64FDF4番地 | 40A138 | ---> 40A138番地 | "あいうえお" |
           +--------+                 +-------+------+
64FDF8番地 | 40A143 | ---> 40A143番地 | "ABC" |
           +--------+                 +-------+----------+
64FDFC番地 | 40A147 | ---> 40A147番地 | "いろはにほへと" |
           +--------+                 +-------+----------+
64FE00番地 | 40A156 | ---> 40A156番地 | "XYZ" |
           +--------+                 +-------+

図に示すとこのようになります。
ポインタ配列全てで50バイト消費しています。
(文字列:34バイト ポインタ16バイト)

これが配列だった場合

#include<stdio.h>

int main()
{
  int i;
  char str[4][15]={"あいうえお","ABC",
                    "いろはにほへと","XYZ"};
  for(i=0;i<4;i++){
    printf("str[%d] = %p String = %s\n",i,str[i],str[i]);
  }
    printf("%d",sizeof(str));
  return 0;
}

/*
str[0] = 0064FDC8 String = あいうえお
str[1] = 0064FDD7 String = ABC
str[2] = 0064FDE6 String = いろはにほへと
str[3] = 0064FDF5 String = XYZ
60
*/
            str[4]
           +------------------+
64FDC8番地 | "あいうえお"     |
           +------------------+
64FDD7番地 | "ABC"            |
           +------------------+
64FDE6番地 | "いろはにほへと" |
           +------------------+
64FEF5番地 | "XYZ"            |
           +------------------+

このように、ポインタの分が必要無い代わりに各行を同じ長さにする
必要があり、結果無駄な領域が発生して60バイト消費しています。

 以上、構造上の違いにより単純に2次元配列を『ポインタのポインタ』に
代入できないことがわかります。
(ポインタのポインタとは **point などを言います)
 つまり、2次元配列を間接アクセス演算子を使って**strのようにアクセスは
できても、**point などポインタのポインタとして宣言されているものには
代入できないのです。

では、どうしてもこの前の2次元配列をポインタでアクセスしたい。
また、この配列を他の関数からアクセスさせたい。
そんなときはどうしましょう。


10-12. 二次元配列をポインタからアクセスする

どうしてもこの前の2次元配列をポインタでアクセスしたい。
また、この配列を他の関数からアクセスさせたい。

というところから続きます。
まず、問題になっているプログラムを再度、掲載してみます。

main()
{
    int matrix[2][3]={{1,2,3},{4,5,6}};
    int **point;
    point=matrix;
    printf("matrix[0][0] = %d\n",**point        );
    printf("matrix[0][1] = %d\n",*(*point+1)    );
    printf("matrix[0][2] = %d\n",*(*point+2)    );
    printf("matrix[1][0] = %d\n",**(point+1)    );
    printf("matrix[1][1] = %d\n",*(*(point+1)+1));
    printf("matrix[1][2] = %d\n",*(*(point+1)+2));
}

◇2次元配列をポインタからアクセスする

一つの解決法として、ポインタへ配列全体の先頭アドレスを渡す方法があります

前回のようにポインタのポインタでは失敗したので、ポインタにしようと言う
ことになります。

#include<stdio.h>

main()
{
    int matrix[2][3]={{1,2,3},{4,5,6}};
    int *point;
    point=matrix;
    printf("matrix[0][0] = %d\n",*(point)    );
    printf("matrix[0][1] = %d\n",*(point+1)  );
    printf("matrix[0][2] = %d\n",*(point+2)  );
    printf("matrix[1][0] = %d\n",*(point+3+0));
    printf("matrix[1][1] = %d\n",*(point+3+1));
    printf("matrix[1][2] = %d\n",*(point+3+2));
}

printf文では少し変わったことをしていますね。
そもそも、この方法ではコンパイル時に警告が出ます。
さらに2次元配列にアクセスできるようになっていても
ポインタからアクセスする時は1次元配列のような扱いに
なっているのであまりよい例ではありません。


◇ポインタ配列を使う

今度はポインタ配列をつかった方法でやってみましょう。

#include<stdio.h>

main()
{
    int matrix[2][3]={{1,2,3},{4,5,6}};
    int *point[2];
    point[0]=matrix[0];
    point[1]=matrix[1];
    printf("matrix[0][0] = %d\n",*(point[0])  );
    printf("matrix[0][1] = %d\n",*(point[0]+1));
    printf("matrix[0][2] = %d\n",*(point[0]+2));
    printf("matrix[1][0] = %d\n",*(point[1]+0));
    printf("matrix[1][1] = %d\n",*(point[1]+1));
    printf("matrix[1][2] = %d\n",*(point[1]+2));
}

この方法が最も適していると思います。文字列の場合は特に便利です。
ポインタ配列の場合、行ごとに代入しないといけないのですが、
文字列の場合は配列を使わなくてもポインタで初期化できるので
前回のようにポインタ配列で初期化すると簡単に文字列の配列ができます。

 上記より良い方法を読者の方から紹介していただきました。
心から厚く御礼申し上げます。

▼引用:一部編集▼━━━━━━━━━━━━━━━━━━━━━━━━━━◆

 int 型の2次元配列をポインタでアクセスする場合、
もちろんいろいろな手があるのですが

    int matrix[2][3]={{1,2,3},{4,5,6}};
    int *point[2];

が最も適しているとのことですが、私は異なる意見を有しています。

この int 型の2次元配列の場合

    int (*x)[3];

という「int 型要素3個の配列へのポインタ」を定義してから配列のデータ
にアクセスするというのが最も適していると思われます。

また、特に文字列の場合

    char str[16][128];
    char (*sPtr)[128] = str;

としてやると sPtr[0] や sPtr[1] などはもちろんのこと、sPtr ++ などの
演算も矛盾なく利用することができます。

▲ここまで▲━━━━━━━━━━━━━━━━━━━━━━━━━━━━━◆

これは、前回のポインタ配列を使った方法とは行と列が逆になった方法です。

また、サンプルプログラムまで添付していただきました。

#include <stdio.h>

int main()
{
    int data[][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*dPtr)[3] = data;

    printf("data[0][0] = %d\n", dPtr[0][0]);
    printf("data[0][1] = %d\n", dPtr[0][1]);
    printf("data[0][2] = %d\n", dPtr[0][2]);
    printf("data[1][0] = %d\n", dPtr[1][0]);
    printf("data[1][1] = %d\n", dPtr[1][1]);
    printf("data[1][2] = %d\n", dPtr[1][2]);

    return 0;
}

私が紹介したものに比べ随分すっきりして読みやすいプログラムです。

ここで、ふと思ったのですが、配列をポインタのようにアクセスすること、
つまり、間接アクセス演算子(*)を使ってアクセスはできましたが、
この逆、ポインタを配列のような形式でアクセスできるのでは?

さっきのサンプルを書き換えてみます。

#include<stdio.h>

int main()
{
    int data[3] = {1, 2, 3};
    int *dPtr   = data;

    printf("data[0] = %d\n", dPtr[0]);
    printf("data[1] = %d\n", dPtr[1]);
    printf("data[2] = %d\n", dPtr[2]);

    return 0;
}

/* 結果
data[0] = 1
data[1] = 2
data[2] = 3
*/

期待どおりですね。

1.読者様より提供していただいたサンプル

#include <stdio.h>

int main()
{
    int data[][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*dPtr)[3] = data;

    printf("data[0][0] = %d\n", dPtr[0][0]);
    printf("data[0][1] = %d\n", dPtr[0][1]);
    printf("data[0][2] = %d\n", dPtr[0][2]);
    printf("data[1][0] = %d\n", dPtr[1][0]);
    printf("data[1][1] = %d\n", dPtr[1][1]);
    printf("data[1][2] = %d\n", dPtr[1][2]);

    return 0;
}

これについて、ポインタ配列を使った方法と比較したいと思います。
まず提供していただいたものは『配列のポインタ』と解釈するのが
自然だと思います。

メモリーのイメージ

dPtr          data[2][3]
+-----+      +-----------+
|&data|----->| 1 | 2 | 3 |
+-----+      |-----------|
             | 4 | 5 | 6 |
             +-----------+

dPtrは要素が3つの配列のポインタである。
dPtr + 1 とすると data[1][0] のアドレスを示す。


2.ポインタ配列を使った方法のサンプルはこちら。

#include<stdio.h>

int main(){
    int matrix[2][3]={ {1, 2, 3}, {4, 5, 6} };
    int *point[2];
    point[0]=matrix[0];
    point[1]=matrix[1];

    printf("matrix[0][0] = %d\n",point[0][0]);
    printf("matrix[0][1] = %d\n",point[0][1]);
    printf("matrix[0][2] = %d\n",point[0][2]);
    printf("matrix[1][0] = %d\n",point[1][0]);
    printf("matrix[1][1] = %d\n",point[1][1]);
    printf("matrix[1][2] = %d\n",point[1][2]);

    return 0;
}

前回のサンプルに習って少し書き換えてあります。

メモリーのイメージ

point[2]        matrix[2][3]
+-------+      +-----------+
|data[0]|----->| 1 | 2 | 3 |
+-------+      |-----------|
|data[1]|----->| 4 | 5 | 6 |
+-------+      +-----------+

メモリーイメージでどちらが適当なのか一目瞭然ですね。

アクセスに関しては1の方法が適当だと思います。
2の場合はソートなどの処理に利用するのに適していると思います。


10-13. ポインタ関数

◇ポインタ関数

 前回までポインタのポインタやポインタ配列、配列のポインタとやってきて
さらにポインタ関数・・・。やっかいですね。

正式にはポインタ型関数と呼ぶようです。似たようなものに関数のポインタが
あり、前回のポインタ配列、配列のポインタのような関係のように見えますが
これがまた、説明しにくいものなので、まずはポインタ型関数からやっていき
ます。

ポインタ型関数を簡単に説明しますと『戻り値がポインタ』である関数です。
戻り値がポインタ???となるとわけわからなくなるのではっきり申しますと
『戻り値がポイント先のアドレス』のことを言います。

#include <stdio.h>

char *string_func(int n);

main()
{
    int     i;
    
    for ( i=1;i<=3;i++){
        printf("String No.%d %s\n",i,string_func(i));
    }
    return 0;
}

char *string_func(int n)
{
    static char *string[] = {
        "",
        "C-Production",
        "Official HomePage",
        "Spring Version"
    };
    return string[n];
}

/* 結果 */
String No.1 C-Production
String No.2 Official HomePage
String No.3 Spring Version

ここで少しポインタ関数の動作について説明します。
main()のprintf文(ステートメント)で string_func(i) として
呼び出しています。printfの%sでは文字列の先頭アドレスと
対応させれば良いので*(参照演算子)はつけていません。

呼出し後、変数 i の値が string_func() の 変数 n に代入されます。
ここでは例として i は 1 としてみます。

すると、string_func() は戻り値として string[1] つまり
"C-Production" という文字列の先頭アドレスを返します。

これが呼び出し側のstring_func()の値となり、
文字列の書き出しが行われます。

一応安全性を考えて string[] は static char * 型にしました。
実際は auto でも動作は同じです(ただし暴走する危険性あり)。

識別名でなくアドレス値によってアクセスを行うので
関数が終了して auto 変数が開放されても問題ないようです。
ただし、開放された以上、他の変数がその領域を使い始める
可能性があるので static にしておいてください。

それではもう一つのサンプル

#include <stdio.h>

int *int_func(int);

main()
{
    printf(" int_func(30) value = %d\n",int_func(30));
    printf("*int_func(30) value = %d\n",*int_func(30));
    return 0;
}

int *int_func(int n)
{
    int *integer;
    integer = &n;
    return integer;
}

/* 結果 */
 int_func(30) value = 6618624
*int_func(30) value = 30

*(参照演算子)をつけて呼び出した時とそうでない時で、
関数の値はちゃんと対応していますね。
1回目はアドレスの取得
2回目は値の取得


10-14. 関数のポインタ

◇関数のポインタ

 関数も変数と同様、アドレスを持ちます。
 そこで今回の関数のポインタはその関数のアドレスをポイントしよう
というものです。

◇宣言の例

型 (*識別名)(引数の型);

ここで型と引数の型はポイントされる関数と一致しなければなりません。

例えば、ポイントされる関数が int func(char) の場合
関数のポインタ宣言は

int (*識別名)(char);

となります。

◇実践サンプル
では早速サンプル。

void func(int);

int main()
{
    void (*P_func)(int); /* (1) */
    P_func = func;       /* (2) */
    (*P_func)(1);        /* (3) */
    return 0;
}

void func(int i)
{
    printf("%dが渡されました\n",i);
    return;
}

解説
(1)関数ポインタP_funcの宣言文です。
(2)関数funcのアドレスを関数ポインタP_funcに代入します。
(3)関数ポインタP_funcを介して関数funcを呼び出しています。

◇関数ポインタ−応用

次のプログラムはANDとORの演算をして表示するものです。

#include<stdio.h>

int and(int x,int y);
int or(int x,int y);

void main()
{
    int (*p_func[2])(int x,int y);
    int i,j,k;

    p_func[0] = and;
    p_func[1] = or;

    printf("x  y  AND  OR\n");
    for(i=0;i<2;i++){
        for(j=0;j<2;j++){
            printf("%d  %d  ",i,j);
            for(k=0;k<2;k++){
                printf(" %d  ",(*p_func[k])(i,j));
            }
            printf("\n");
        }
    }
}

int and(int x, int y)
{
    return x&&y;
}

int or(int x, int y)
{
    return x||y;
}

/* 結果
x  y  AND  OR
0  0   0   0
0  1   0   1
1  0   0   1
1  1   1   1
*/

ポイントの仕方を図示すると次のようになります。

 p_op[4]          int and(int x, int y)
+---------+      +---------------------+
| p_op[0] |----->| { return x && y }   |
+---------+      +---------------------+
| p_op[1] |--+
+---------+  |    int or(int x, int y)
             |   +---------------------+
             +-->| { return x || y }   |
                 +---------------------+

このようにポインタ配列と組み合わせることによって
別の関数をfor文で連続的に呼び出すことができます。


10-15. ポインタ関数ポインタ

◇ポインタ関数ポインタ

またわけのわからないタイトルを作ってしまいました。
ポインタ関数のポインタです。
つまり今度はポインタ関数をポイントしようということです。

宣言文は次のようになります。

型 (*識別名)(引数の型);

さっきと同じですね。
実際に int *p_func(void) を *P_func でポイントする時は

int *(*P_func)(void);
~~~~~
と宣言文を書けばOKです。ちなみに波線の部分が型に相当します。

最後に前回のプログラムに関数ポインタを付け加えてみましょう。

#include <stdio.h>

char *string_func(int n);

int main()
{
    int     i;
    char *(*P_func)(int); /* ここを追加 */
    P_func = string_func; /* 代入も同じですね */

    for ( i=1;i<=3;i++){
        printf("String No.%d %s\n",i,(*P_func)(i));
    }
    return 0;
}

char *string_func(int n)
{
    static char *string[] = {
        "",
        "C-Production",
        "Official HomePage",
        "Spring Version"
    };
    return string[n];
}



10-16. コマンドライン

◇コマンドライン

コマンドラインとはプログラム起動時にmain関数へ引き渡す引数のことです。
ちなみにコマンドとはコンソール画面上で入力する実行ファイル名などを
示します。そのライン上がコマンドラインであると思ってください。

◇配列で受け取る

・サンプルプログラム
(注:行番号を入れてあります)

1: #include<stdio.h>
2: main(int argc, char *argv[])
3: {
4:     int i;
5:     printf("argc = %d\n",argc);
6:     for(i=1;i<argc;i++)
7:         printf("param%d    %s\n",i,argv[i]);
8: }


・実行方法(実行ファイル名が test.exe の場合)

>test.exe C-Production No.114 Standard Edition [Enter]
 ~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~ ~~~~~~~~ ~~~~~~~
この下線の部分がmain関数の引数として渡されます。
コマンドそのものも入ります。

・実行結果

argc = 5
param1    C-Production
param2    No.114
param3    Standard
param4    Edition

・解説
2行目の仮引数 argc はコマンドライン引数の個数を受け取ります。
そして *argv[] はコマンドラインの文字列を取得します。
ちなみに上のプログラムでは

argv[0] -->"test.exe"
argv[1] -->"C-Production"
argv[2] -->"No.114"
argv[3] -->"Standard"
argv[4] -->"Edition"

となっています。
メモリーイメージは

       +------+       +----------+
argv[0]|      |------>| test.exe |
       |------|       |----------+---+
argv[1]|      |------>| C-Production |
       |------|       |--------+-----+
argv[2]|      |------>| No.114 |
       |------|       |--------+-+
argv[3]|      |------>| Standard |
       |------|       |---------++
argv[4]|      |------>| Edition |
       +------+       +---------+

このようになります。


◇ポインタで受け取る

コマンドラインは配列だけでなくポインタでも受け取ることができます。

・サンプルプログラム
(注:行番号を入れてあります)

1: #include<stdio.h>
2: main(int argc, char **argv)
3: {
4:     int i;
5:     printf("argc = %d\n",argc);
6:     for(i=1;i<argc;i++)
7:         printf("param%d    %s\n",i,*(++argv));
8: }

argc = 5

param1    C-Production
param2    No.114
param3    Standard
param4    Edition

さっきと似ているように思えますがメモリーイメージは違って
次のようになります。

 argv
+------+      +------+       +----------+
|      |--+-->|      |------>| test.exe |
+------+  |   |------|       |----------+---+
          +-->|      |------>| C-Production |
          |   |------|       |--------+-----+
          +-->|      |------>| No.114 |
          |   |------|       |--------+-+
          +-->|      |------>| Standard |
          |   |------|       |---------++
          +-->|      |------>| Edition |
              +------+       +---------+

argvをインクリメントすることにより次の文字列を指す

補足説明:
コマンドラインについて読者様より質問をいただきました。

> コマンドライン引数なんですが、このまま打鍵すると、
> セパレートと言うのかデリミタと言うのか、それで
> 半角スペースを含む文字列が取得できませんね・・・。
>
> ダブルクォーテーションで挟むとOKでしょうか?

基本的にダブルクォートでいけます。
Cプロダクションでのチェック環境(Win98+BCC5.5)でも大丈夫でした。

そういえばWindowsなどはスペースを含むディレクトリ(My Document)があって
対応がややこしいですね。

ショートカットの内容やレジストリをいじる時などもこのスペース付
ディレクトリによく悩まされます(笑)

Copyright© 2000-2009 C-Production All Rights Reserved.