計算機実習 I
第十一回 (2015 年 6 月 18 日)
ポインタの応用: 参照と間接
http://www.sw.it.aoyama.ac.jp/2015/CP1/lecture11.html
Martin J. Dürst
© 2005-15 Martin
J. Dürst 青山学院大学
目次
- ミニテスト
- 前回のまとめ、演習問題
- 参照渡し
- 間接
- 引数としての関数
ミニテスト
- 授業開始までにログイン済み
- 授業開始まで教科書、資料、筆箱、財布などを鞄に入れ、鞄を椅子の下に
- テスト終了後その場で待つ
前回のまとめ
- 変数は種類によってスタック、ヒープ等に配置
- 配列の指数計算はポインタ演算 (足し算など)
に変換可能
- 動的メモリは
malloc
で確保、realloc
で大きさを調整、free
で返却
- 動的メモリでは「定石」の使用、返す責任者の決定が大切
前回の演習結果
|
10A1 |
10B2 |
10C1 |
10C2 |
100点 |
81 |
41 |
20 |
1 |
60点 |
12 |
51 |
65 |
19 |
エラー |
1 |
- |
2 |
42 |
未提出 |
- |
2 |
9 |
32 |
10C2 の注意点
[昨年度資料につき削除]
ポインタの用途
- 低レベルのアドレス操作 (デバイス割り当てなど)
- 配列の処理とその効率化
- 動的メモリの管理
- 参照 (関数への参照渡しなど)
- 間接 (indirection)
自然言語での参照
- 彼、彼女、彼ら
- これ、それ、あれ
- ここ、そこ、あそこ
引数の値渡しと参照渡しのイメージ
値渡し (call by value):
- 付箋に値を書いて、捨ててもいいように渡す
- 修理するものを直接渡す (例: 靴の修理)
参照渡し (call by reference):
- 箱の中に値を用意し、箱の場所を渡す
- 修理する場所 (住所) を指定 (例:
道路や水道管の修理)
引数の参照渡し
- 目的:
- 関数に値を渡すのではなく、
- 変数への参照を渡すことで
- 変数そのものが関数から変更可能
- 技法:
- 引数をポインタとして定義
- 関数呼び出し時にアドレス演算子 (
&
)
を使用
- 関数の中には常に間接演算子 (
*
)
を使用
- 問題点:
- 自動ではないので注意
(特に scanf
などコンパイラが型をチェックできない場合)
参照渡しの定石 (配列以外)
参照渡しの定石 (配列)
- 配列はポインタとほぼ同等で、参照渡しが自動
- 関数の中では配列とポインタが同等
(引数の定義では int *a;
と int a[];
は完全に同等)
- 配列の長さが分からないので注意が必要:
- 配列の終わりの印を決める (例: 文字列の
'\0'
, 11C1)
- 配列の長さを別の引数で渡す
配列の値渡しの方法
- 参照渡し、関数内で変更しない
- (1) で
const
で変更しないことを宣言、確認
- 関数に渡す前にコピー
- 配列を構造体のメンバで定義、構造体を渡す
参照渡しの応用例: 複数の戻り値
課題: ある関数で n2 と n3
を同時に返す
解決策: 初期されてない参照渡し:
void square_cube(int n, int *pn2, int *pn3)
{
*pn2 = n * n;
*pn3 = *pn2 * n;
}
呼び出し側:
int a=5, a2, a3;
square_cube(a, &a2, &a3);
printf("a=%d, a*a=%d, a*a*a=%d\n", a, a2, a3);
間接 (indirection)
名言:
「全ての情報テクノロジーの問題は間接のレベルを一つ増やせば解決可能」
間接の例:
- IP アドレスの代わりにドメイン名
- 学生本人の代わりにあだ名、学生番号、メールアドレス
- データベースのインデックス
- Web ページ本体とそのアドレス (http://... など)
- Google, Yahoo みたいなサーチエンジン
C の場合には間接をポインタで実現
間接の応用例
順番を入れ替える時に、実際のデータを入れ替えると:
- データの移動が大変
- データの場所が変更、他のところで困る
- 同時に一つの順番しか使えない
間接を使えば全ての問題が解決 (例: 11C2)
引数としての関数
関数 (のポインタ) も引数として関数に渡せる
典型例:
あるパターンで仕事して欲しいが、具体的な仕事が未定
応用例:
相模原キャンパスの全ての講堂にある仕事をして欲しい
解決作:
- 関数「講堂たどり」を作る
- 「講堂たどり」に実際の仕事をやってくれる関数を渡す
- 「講堂たどり」が各講堂で実際の仕事の関数を呼ぶ
様々な整列の実装
- 整列の基準が色々ある
(学生番号、名前、成績、昇順・降順)
- 整列のやり方も色々、複雑
(バブル整列、挿入整列など、二年生後期のデータ構造とアルゴリズムで勉強)
- 一つの関数にまとめようとすると沢山の複雑な関数が必要
整列関数の種類
|
バブル整列 |
挿入整列 |
選択整列 |
クイックソート |
... |
学生番号 |
|
|
|
|
|
名前 |
|
|
|
|
|
数学の成績 |
|
|
|
|
|
英語の成績 |
|
|
|
|
|
... |
|
|
|
|
|
- お互いに独立 (直交)
比較関数の導入
- 整列基準と整列方法が互いに独立
- 項目の順番は「比較関数」で表現可能
- 比較関数は二つの項目 a と b を比較し、戻り値は
- a が先の場合、負の数
- b が先の場合、正の数
- a と b が同じ場合、0
- 比較する比較関数を整列関数に渡すと:
- 整列関数は一つで良い (ライブラリで用意、例:
qsort
)
- 比較関数が比較的簡単
関数に関数を渡す
C 以外のプログラミング言語
- C 言語で関数の作成はある程度の手間
- 比較関数は一般的なデータを引数にとるので、ポインタを使用
- 関数の作成がもっと簡単な言語: 関数型言語 (Lisp,
Haskell など)
- 引数としてブロック (関数と類似)
を渡すプログラミング言語: Ruby
言語による差の具体例
Ruby: students.sort { |a, b| a.number <=> b.number }
C 言語 (11C2 から):
int compareNumbers (const void* a, const void* b)
{
Student *studentPa = *((Student **) a);
Student *studentPb = *((Student **) b);
return strcmp (studentPa->studentNumber,
studentPb->studentNumber);
}
...
qsort(byNumber, studentCount, sizeof(Student*), compareNumbers);
今日の演習
- 11A1: 参照渡しの基本
- 11B1: ポインタ (間接) を使う整列
- 11C1: 参照渡しを使って値を帰す
- 11C2: 学生データの整列
- 全点必須 (発展問題ではない)
- 順番を追加するごとに提出 (部分点)
- 帰る前に必ず10点以上取得
- 締切は月曜日
次回の準備
- 残りの演習問題を宿題として完成
- 今日の復習
- 参考書の第 11 章 (ポインタ、pp. 281-311)
をもう一回よく読む
- 参考書の第 12 章 (ファイル操作、pp. 313-345) を読む