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