情報処理 Ⅱ 第 9 回 2014 年 12 月 22 日 ( 月 )
関数とは なぜ関数 関数の分類 自作関数 : 自分で定義する. ユーザ関数 ユーザ定義関数 などともいう. 本日のテーマ ライブラリ関数 : 出来合いのもの.printf など. なぜ関数を定義するのか? 処理を共通化 ( 一般化 ) する プログラムの見通しをよくする 機能分割 ( モジュール化, 再利用 ) 責任 ( あるいは不具合の発生源 ) の最小化 main 関数がないとプログラムは動かない 2
仮引数と実引数 関数定義の例 int min(int x, int y)... 関数呼び出しの例 printf("min: %d n", min(x, y)); 仮引数と実引数の区別 関数定義のxとyは仮引数. 変数であり, それぞれ型名を書く int min(int x, y) は間違い 関数呼び出しの x と y は実引数. 任意の式でよい 仮引数に, 実引数の式の評価値を代入してから, 関数の処理が始まる仮引数と実引数が同じ変数名であっても, 有効範囲が異なるため, 別オブジェクトとなる 3
引数の授受 C の関数呼び出しでは必ず値渡し (call by value) になる. 値渡し : 実引数のコピーが仮引数に格納される. その後, 仮引数の値を変更しても, 実引数の値には影響しない. 参照渡し (call by reference) をしたければ, ポインタ値を引数とすればよい. 参照渡し : 仮引数の値を変更すれば, 実引数の値もそれに変わる.C ではこの意味での参照渡しをすることができないが, ポインタ値を渡すことで, その参照先の値を変えることができる. アドレス渡しともいう. 4
2 つの値を交換する関数 値渡し 失敗 引数は関数内の仮引数にコピーされる. 関数内の仮引数の中で, 値を交換しても, 関数の外のオブジェクトは変更されない. 参照渡し 成功 関数の仮引数は, 指し示す先を持つ. * ポインタ変数 = 値 とすることで, ポインタ変数が指し示す ( 関数の外の ) オブジェクトに値を代入する. swapint.c 5
値渡しで失敗する理由 (1) コード ( 抜粋 ) void swapint_bad(int x, int y) int tmp; swapint_bad tmp = x; x = y; y = tmp; x = 1, y = 1; swapint_bad(x, y); x = 1 main 6
値渡しで失敗する理由 (2) コード ( 抜粋 ) void swapint_bad(int x, int y) int tmp; swapint_bad tmp = x; x = y; y = tmp; x = 1, y = 1; swapint_bad(x, y); x = 1 x = 1 main 7
値渡しで失敗する理由 (3) コード ( 抜粋 ) void swapint_bad(int x, int y) int tmp; swapint_bad tmp = x; x = y; y = tmp; x = 1 tmp x = 1, y = 1; swapint_bad(x, y); x = 1 main 8
値渡しで失敗する理由 (4) コード ( 抜粋 ) void swapint_bad(int x, int y) int tmp; swapint_bad tmp = x; x = y; y = tmp; x = 1, y = 1; swapint_bad(x, y); x = 1 x = 1 tmp=1 main 9
値渡しで失敗する理由 (5) コード ( 抜粋 ) void swapint_bad(int x, int y) int tmp; swapint_bad tmp = x; x = y; y = tmp; x = 1, y = 1; swapint_bad(x, y); x = -11 x = 1 tmp=1 main 10
値渡しで失敗する理由 (6) コード ( 抜粋 ) void swapint_bad(int x, int y) int tmp; swapint_bad tmp = x; x = y; y = tmp; x = 1, y = 1; swapint_bad(x, y); x = -11 1 x = 1 tmp=1 main 11
値渡しで失敗する理由 (7) コード ( 抜粋 ) void swapint_bad(int x, int y) int tmp; swapint_bad tmp = x; x = y; y = tmp; x = 1, y = 1; swapint_bad(x, y); x = -11 1 x = 1 tmp=1 main 12
参照渡しで成功する理由 (1) コード ( 抜粋 ) void swapint_good(int *px, int *py) int tmp; swapint_good tmp = *px; *px = *py; *py = tmp; x = 1, y = 1; swapint_good(&x, &y); x = 1 main 13
参照渡しで成功する理由 (2) コード ( 抜粋 ) void swapint_good(int *px, int *py) int tmp; swapint_good tmp = *px; *px = *py; *py = tmp; x = 1, y = 1; swapint_good(&x, &y); px x = 1 py main 14
参照渡しで成功する理由 (3) コード ( 抜粋 ) void swapint_good(int *px, int *py) int tmp; swapint_good tmp = *px; *px = *py; *py = tmp; x = 1, y = 1; swapint_good(&x, &y); px x = 1 tmp py main 15
参照渡しで成功する理由 (4) コード ( 抜粋 ) void swapint_good(int *px, int *py) int tmp; swapint_good tmp = *px; *px = *py; *py = tmp; x = 1, y = 1; swapint_good(&x, &y); px x = 1 tmp=1 py main 16
参照渡しで成功する理由 (5) コード ( 抜粋 ) void swapint_good(int *px, int *py) int tmp; swapint_good tmp = *px; *px = *py; *py = tmp; x = 1, y = 1; swapint_good(&x, &y); px x = -11 tmp=1 py main 17
参照渡しで成功する理由 (6) コード ( 抜粋 ) void swapint_good(int *px, int *py) int tmp; swapint_good tmp = *px; *px = *py; *py = tmp; x = 1, y = 1; swapint_good(&x, &y); px tmp=1 py x = -11 1 main 18
参照渡しで成功する理由 (7) コード ( 抜粋 ) void swapint_good(int *px, int *py) int tmp; swapint_good tmp = *px; *px = *py; *py = tmp; x = 1, y = 1; swapint_good(&x, &y); px tmp=1 py x = -11 1 main 19
関数の中の配列変数 (1) 関数の中で配列変数を定義すれば, 関数処理の中で確保され, 関数処理が終わると破棄されるような配列が作られる. void print_message(void) char message[] = "Wakayama"; printf("%s n", message); print_message message 'W''a''k''a''y''a''m''a' ' 0' 20
関数の中の配列変数 (2) 関数の仮引数に配列変数を書けば, その配列と型が適合するポインタ変数になる. void print_message(char message[]) printf("%s n", message); 不完全型実体はポインタ変数 要素数 ( 多次元配列の場合は左端のみ ) は無視される. 要素数なしでもよい. print_message message 'W''a''k''a''y''a''m''a' ' 0' 21
多項式の計算 仕様 多項式関数 f(x) = c n x n + c n 1 x n 1 + + c 1 x + c 0 と実数 a が与えられたときに,f(a) を計算する. 考え方 係数 c 0, c 1, c 2, c n を, 配列変数で保持する. c i は,xのi 次の係数 f(a) = ( (c n * a) + c n 1 )* a + ) + c 0 により求め ( ホーナー法 ), 乗算の回数を減らす. v 0 = c n v 1 = v 0 * a + c n 1 v 2 = v 1 * a + c n 2 = c n *a*a + c n 1 *a + c n 2 v i = v i 1 * a + c n i polynomial_mainonly.c 22
コード ( 抜粋 ) 多項式の計算を行う関数 double calc(double f[], int size, double x) f の配列の要素数 double val; (xの次数 + 1) int i; val = f[size 1]; for(i = size 2; i >= 0; i ) val = val * x + f[i]; return val; 係数の配列 ( の先頭を指し示すポインタ変数 ) f(x) を求めるための x の値 v c n v v*x + c n i v = f(x) = c n x n + c n 1 x n 1 + + c 1 x + c 0 関数定義の効果 : 異なる次数の多項式でも, 一つの (C の ) 関数 calc により, 値を求められる. polynomial.c 23
関数と変数 で学ぶこと 目的 関数を自分で定義し, 変数の利用方法 範囲を明示的に制限することで, 適切な機能分割 ( モジュール化, 再利用 ) を図る. してはいけないこと main 関数のみで 100 行以上のプログラム グローバル変数を駆使するプログラム プログラムを読みやすくする 保守性向上 24
変数 (Variable) 識別子 オブジェクト 識別子 (Identifier): 変数名, 関数名, 型定義名などの 名前 識別子とオブジェクト (Object) の違い 識別子は, プログラムファイル ( 静的 ) で記述される ラベル オブジェクトは, プログラム実行中 ( 動的 ) に生成される 実体 同一の識別子に対して複数のオブジェクトが生成されることもある. 25
識別子の宣言に関するルール グローバルに同一の識別子を複数宣言できない. 一つのブロック内に, 同一の識別子を複数宣言できない. 関数はブロック内で定義できない. 必ずグローバル区間での定義となる. GCC( 演習室のコンパイラ ) では, 関数の中に関数を定義できるが, 使用しないこと! ブロック内に変数を定義するときは, それより外にある同一の識別子と重複してもよい. ただし, 外にある同一の識別子は参照できない. モジュール化に関して有用なルール. 26
2 つの重要な記憶域クラス 静的記憶域期間 ( 静的変数,static 変数 ) static を指定した変数と, グローバル変数が該当する. プログラムの実行に先立ち, オブジェクトが生成され, 初期値が設定される. プログラム終了まで破棄されない. 初期値を指定しないオブジェクトには 0 が代入される. 初期値は, コンパイル時に計算可能な定数式でなければならない. 自動記憶域期間 ( 自動変数,auto 変数 ) autoを指定した変数,staticやexternの指定なくブロック内で定義した変数と, 関数の仮引数が該当する. 宣言文を実行するたびに, オブジェクトが生成され, 初期値があれば毎回初期化される. ブロックを終えると, 破棄される. 初期値を指定しないオブジェクトの初期値は不定. 初期値は任意の計算式でよい. 27
有効範囲と記憶域クラス ここで問題 ( 小テストではありません ) グローバルな静的変数は, 宣言 できる できない. グローバルな自動変数は, 宣言 できる できない. ローカルな静的変数は, 宣言 できる できない. ローカルな自動変数は, 宣言 できる できない. ローカルな静的変数の用途 ブロック内で情報を保存しておき, あとで利用する. 動的に確保することなく, 配列領域を戻り値とする. 自動変数では扱いきれない大容量オブジェクト (100 万個の int 配列など ) を取り扱う. 28
有効範囲と生存期間 破棄されるまでは, ブロックの外からでも ( ポインタなどで間接的に ) オブジェクトの参照や値の書き換えができる. ポインタによる参照渡しが可能となる. 関数内の自動変数に対応するオブジェクトは, 関数処理が行われるたびに生成される. 再帰呼び出しをする関数 が構成できる. 何らかの関数の中 message message[0] = 'w'; としてよい wakayama 'W''a''k''a''y''a''m''a' ' 0' 29
年末年始の授業予定 12 月 25 日 ( 木 ): 第 10 回, レポート課題提示 1 月 8 日 ( 木 ): 第 11 回 1 月 19 日 ( 月 ): 第 12 回 1 月 26 日からの週には 3 回授業があり, そのうち 1 回は A601 で実施します. 30