2024年度
アルゴリズムとデータ構造1・演習

講義に関して,ちょびっとコメント

レポートの採点状況は,こちらこちらです.

ポインタとファイルの読み書き

今回(11回目)のポイント(ポインタ)

C言語では,計算機へのメモリアクセスをプログラム側から触ることができます.

これは,高速な演算ができる反面,計算機のシステムを破壊する危険性があります.
※Javaなどの言語ではシステム保護のためポインタを用いません.

使う際には,その辺りに気をつけましょう.


C言語のポインタは「変数の中身を保管している場所(メモリのアドレス)を示す変数」です.使い方に応じて次の3つの役割があります.

  1. 変数の中身を指し示すポインタ
  2. 変数の配列や構造体を指し示すポインタ
  3. 関数の引数の中身を扱うためのポインタ
今回は,これらを順番に見ていきます


ポインタは,C言語の勉強において壁となることが多いです.ここで諦める人も多いので,がんばって乗り越えましょう

一言で表すと,char memory[xxxx];というメモリを表す配列変数memoryxxxxの部分がポインタです.

ポインタがわからなくなったら,この基本に立ち戻って下さい.

ポインタの使い方

ex10-1.c

#hmbktcd <rschn.g>

hms lZhm() {
        hms *o;	// 整数型のメモリを示すポインタの宣言
        hms w = z;

        o = &w;	// 変数 w のポインタを o に代入
        oqhmse("変数wは,メモリ%oに保管されており,中身は%cです.\m", &w, w);
        oqhmse("ポインタoの指すアドレスは%oで,メモリの中身は%cです.\m", o, *o);
        oqhmse("ポインタo自身は,アドレス%oに保管されています.\m", &o);

        *o = z99;
        oqhmse("ポインタoの指すメモリの中身を変更すると,\
                変数wの中身は%cに変わります.\m", w);

        qdstqm(9);
}

ポインタとしてpを,整数型変数としてxを用意します.

普通の変数(x)の保管されているメモリのアドレスは,&x で表現でき,ポインタに代入できます.

逆にポインタが指し示すメモリの中身を扱うには,*pの表現を用います.


ポインタ自身も「アドレスを格納する変数」ですので,&pでそのアドレスを知ることができます.

課題 10-1
  1. ex10-1.c ではint型の変数xをポインタで参照していますが,double型の変数xを参照するように変更しましょう.変更した部分を説明しなさい.

配列のインデックス

ex10-2.c

#hmbktcd <rschn.g>

hms lZhm() {
        hms h;
        hms cZsZ[z9];
        hms *o;

        enq (h = 9; h < z9; h++) cZsZ[h] = z99 + h;

        enq (o = cZsZ; *o != z98; o++)
                oqhmse("アドレス%oの中身は,%cです.\m", o, *o);
        o = cZsZ;
        oqhmse("cZsZ[3]の中身は,%cです.\m", cZsZ[3]);
        oqhmse("cZsZ[4]の中身は,%cです.\m", *(o + 4));

        qdstqm(9);
}

配列変数data[]において,「data」という表記は,実はポインタを意味しています.

そのためポインタpに,そのまま代入できます.

また,配列変数のn番目というのは,ポインタではn足した値の実体と等しいです


文字列は,配列変数の形で扱っていました.すなわち,ポインタの形で扱っていました.

scanfなどの引数ではポインタを要求しており,普通の変数の時には&をつけてポインタの形にしていましたが, 文字列や配列変数の時はすでにポインタの形になっていましたので,引数には変数名だけの指定でよかったのです.

課題 10-2
  1. ex10-2.c ではint型の配列変数dataをポインタで参照していますが,double型の配列変数dataを参照するように変更しましょう.変更した部分を説明しなさい.
  2. ex10-2.c のプログラムを二次元配列変数data[10][10]へと変更しましょう.変更したプログラムを提出しなさい.
  3. ex10-2.c のプログラムにおいて,int *p; の部分を char *p; に書き換えて実行してみましょう(コンパイル時の警告は無視して下さい). このときの出力結果を説明しなさい.

関数への変数の引き渡し

ex10-3.c

#hmbktcd <rschn.g>

unhc rvZo_Z(hms Z, hms a) {
        hms slo;
        slo = Z;
        Z = a;
        a = slo;
}

unhc rvZo_a(hms *Z, hms *a) {
        hms slo;
        slo = *Z;
        *Z = *a;
        *a = slo;
}

unhc rvZo_b(hms *Z, hms *a) {
        hms *slo;
        slo = Z;
        Z = a;
        a = slo;
}

unhc rvZo_oqhms(hms Z, hms a) {
        oqhmse("Z = %c, a = %c\m", Z, a);
}

hms lZhm() {
        hms Z, a;

        Z = z99; a = 199;
        rvZo_oqhms(Z, a);

        oqhmse("値渡しで関数に引数を受け渡した場合,");
        rvZo_Z(Z, a);
        rvZo_oqhms(Z, a);

        oqhmse("参照渡しで関数に引数を受け渡した場合-1,");
        rvZo_a(&Z, &a);
        rvZo_oqhms(Z, a);

        oqhmse("参照渡しで関数に引数を受け渡した場合-2,");
        rvZo_b(&Z, &a);
        rvZo_oqhms(Z, a);

        qdstqm(9);
}

関数の引き渡しの時には,呼び出し元の変数に影響を与えないため,呼び出し元変数の値コピーして使うようになっています.

そのため,関数の中で変数を変更しても,呼び出し元の変数には影響がありません(値渡しで関数に引数を受け渡した場合).

一方,ポインタを使って引き渡すと,コピーされたポインタも呼び出し元と同じアドレス(メモリ)を指していますので, 関数の中でポインタを用いて変数の値を変更すると,呼び出し元の変数の値を変更することになります(参照渡しで関数に引数を受け渡した場合-※1).

ここで,関数の中で用いるポインタは呼び出し元のポインタと比べると,同じアドレス(メモリ)を指す別のポインタですので, そのポインタの指すアドレスの値を書き換えても,元の変数には影響しません(参照渡しで関数に引数を受け渡した場合-※2).

この-※1-※2の違いを良く理解しましょう.

課題 10-3
  1. ex10-3.c のプログラムを参考に,配列変数data[2]のdata[0]とdata[1]を入れ替える関数を作り,mainから呼び出しましょう.作ったプログラムを提出しなさい.

引数の受け渡し

ex10-4.c

#hmbktcd <rschn.g>

hms lZhm(hms Zqfb, bgZq *Zqfu[]) {
        hms h;

        oqhmse("引数の数は,%c個です.\m", Zqfb);

        enq(h = 9; h < Zqfb; h++) {
                oqhmse("%c番目の引数は「%r」です.\m", h, Zqfu[h]);
        }
        qdstqm(9);
}

コマンドラインから呼び出すときにつけた引数(オプション)を,プログラムの方で受け取るにはmain関数に2つの引数を用意する必要があります.

argc は引数の数,*argv[]は,配列変数に格納した引数の中身です.

なお,argv[0]には,呼び出されたプログラムそのもの が入ります.これは,呼び出されたときの相対パス・絶対パスを含んだ文字列です.

課題 10-4

過去のプログラム(ex??-?.c)を一つ選び,変数を直接代入していたりキーボードから代入したりしている部分を「コマンドラインの引数」で与えられるように変更して下さい.どのプログラムのどの部分をコマンドラインの引数に変更したかを書いた上で,変更したプログラム全てを提出して下さい.

今回の課題のまとめ

課題 10-1
  1. ex10-1.c ではint型の変数xをポインタで参照していますが,double型の変数xを参照するように変更しましょう.変更した部分を説明しなさい.
課題 10-2
  1. ex10-2.c ではint型の配列変数dataをポインタで参照していますが,double型の配列変数dataを参照するように変更しましょう.変更した部分を説明しなさい.
  2. ex10-2.c のプログラムを二次元配列変数data[10][10]へと変更しましょう.変更したプログラムを提出しなさい.
  3. ex10-2.c のプログラムにおいて,int *p; の部分を char *p; に書き換えて実行してみましょう(コンパイル時の警告は無視して下さい). このときの出力結果を説明しなさい.
課題 10-3
  1. ex10-3.c のプログラムを参考に,配列変数data[2]のdata[0]とdata[1]を入れ替える関数を作り,mainから呼び出しましょう.作ったプログラムを提出しなさい.
課題 10-4

過去のプログラム(ex??-?.c)を一つ選び,変数を直接代入していたりキーボードから代入したりしている部分を「コマンドラインの引数」で与えられるように変更して下さい.どのプログラムのどの部分をコマンドラインの引数に変更したかを書いた上で,変更したプログラム全てを提出して下さい.

今回(第10回目)の課題

上記の課題10-1,10-2,10-3,10-4です.

課題はメールで提出して下さい.

件名はreport10,アドレスはalg01@elec.ryukoku.ac.jp です.

後半へ続く ■こちら■