情報処理Ⅱ 第7回:2003年12月2日(火)
問題(授業がつまらない人のため に) ぷよ連結問題 前提 : フィールドを2次元 配列で定義し,各マスの 「ぷよ」を int 型の値で表現 する. ある地点を入力に取り,そ れと連結する「ぷよ」の個 数を求めよ. 消去できる「ぷよ」 (N 個以上 連結するぷよと,それに隣接する特 殊なぷよ ) のマスを求めよ. : ぷよ : 特殊なぷよ
本日のテーマ : 関数と変数 目的 関数を自分で定義し,変数の利用方法・範囲を明示的に制 限することで,適切な機能分割(モジュール化,再利用) を図る. してはいけないこと main 関数のみで 100 行以上のプログラム グローバル変数を駆使するプログラム
関数 (Function) 関数の分類 自作関数 : 自分で定義する.⇒ 本日のテーマ 標準ライブラリ関数 : ANSI で定義されている. printf など. POSIX 準拠関数 : オペレーティングシステムのインタ フェースや環境を規定した関数ライブラリ. open など. その他のライブラリ関数 : OpenGL の glutCreateWindow など. ANSI: American National Standards Institute POSIX: Portable Operating System Interface for UNIX
関数定義の方法 型名 関数名 ( 引数並び ) { 文...} 構文 : 型名 関数名 ( 引数並び ) { 文...} 型名は,関数の戻り値の型.値を返さないときは,何も書 かないか, void と書く. 引数並びは, 0 個以上の「型名 変数名」をカンマで挟んだ もの.引数がないときは,何も書かないか, void と書く. 例 : double my_atof(const char *str0) {…} 例 : void procedure(char *p, int x, int y) {…} 変数が [ ] を伴っていれば,それはポインタと同じ. 例 : int main(int argc, char *argv[ ]) {…} は int main(int argc, char **argv) {…} と書いても同じ.
値の前後にカッコは不要. 引数の授受 (1) double f(double x) {return x+1;}... b=f(a); としたとき x を関数 f の仮引数 (formal argument) , a を実引数 (actual argument) と言う.これらを区別する必要のないときは, ともに引数 (argument) という. x = a; の代入を行ってから,関数本文の処理に入る.関数 の処理が終われば,変数 x ( のオブジェクト ) は消滅する. 関数処理中に return 値 ; があれば,そこで関数の処理を 終え,値を戻り値 (return value) とする. void 型の関数処理中に return; があれば,そこで関数の 処理を終える.戻り値なし. 暗黙の型変換は,仮引数への代入時と,戻り値オブジェ クトの生成時に起こり得る.
引数の授受 (2) C の関数呼び出しでは必ず値渡し (call by value) になる. 値渡し : 実引数のコピーが仮引数に格納される.その後, 仮引数の値を変更しても,実引数の値には影響しない. 参照渡し (call by reference) をしたければ,ポインタを引 数とすればよい. 参照渡し : 仮引数は,実引数の別名となる.その後,仮引 数の値を変更すれば,実引数の値もそれに変わる. C ではこの意味での参照渡しをすることができない.
関数定義の順番 呼び出す関数は,呼び出す前に ( プログラムファイルの 上のほうで ) 宣言されていなければならない. int 関数名 ( ); 宣言や定義がない場合は, int 関数名 ( ); とみなして呼び 出しを試みる. 対策 呼び出す順序に注意して関数を並べる. 関数プロトタイプを使用する.
関数プロトタイプ (Function prototype) 「関数原型」ともいう. 型名 関数名 ( 引数の型の並び ); 構文 : 型名 関数名 ( 引数の型の並び ); 「引数の型の並び」は,「引数(型と変数)の並び」でも よい.このとき仮引数名は無視される. セミコロンは必須. 例 : double my_atof(const char *); 関数プロトタイプのリストを作り,その次に関数定義を 並べれば,関数定義の順番を気にすることなくプログラ ムを記述できる.
main 関数の型 main 関数の ( 戻り値の ) 型は, int とする. void main(...) と する本も多いが,規格上適切ではない. 正常終了は return 0; と書き, 異常終了は return 1; と書くのが一般的. main 関数の中以外でプログラムの実行を終えたいときは exit(0); exit(EXIT_SUCCESS); exit(EXIT_FAILURE); のいずれかを書く. ( 要 #include )
変数 (Variable) 学ぶこと 型以外の属性 ( 記憶域クラス,型修飾子 ) 有効範囲
用語説明 識別子 (Identifier): 変数名,関数名,型定義名などの「名 前」 すでに宣言・定義された識別子で新たに宣言・定義しよう とすると,コンパイルエラー. オブジェクトと識別子の違い 識別子は,プログラム ( 静的 ) で記述する「ラベル」 オブジェクトは,プログラム実行中 ( 動的 ) に生成される 「実体」 同一の識別子に対して複数のオブジェクトが生成されるこ ともある.
型の属性 記憶域クラス extern, static, auto, register 型修飾子 const, volatile 例 :extern void function1(const char *); int x(void) {static int c=0;...}
記憶域クラス(1) extern: 他の場所で宣言された識別子を参照する. static: そのオブジェクトの生存期間は静的記憶域期間で ある. auto: そのオブジェクトの生存期間は自動記憶域期間で ある. extern と static は,関数に対しても指定できる.ただし static の意味は異なる.
記憶域クラス(2) 静的記憶域期間 ( static 変数) static を指定した場合や,なくてもグローバル区間(ブロックの ないところ)で定義した変数が該当する. プログラムの実行に先立ち,一度だけ初期値が設定される. 初期値を指定しないオブジェクトには 0 が代入される. 初期値は,コンパイル時に計算可能な定数式でなければならな い. 自動記憶域期間 ( auto 変数) auto を指定した場合や,なくてもブロック内で定義した変数, 関数の仮引数が該当する. オブジェクトの定義の時点で,毎回初期化される. 初期値を指定しないオブジェクトの初期値は不定. 初期値は任意の計算式でよい.
型修飾子 const: その識別子の値はプログラムによって変更できな い.参照は可能. volatile: 処理系が知らないうちに値を変える可能性があ る.参照は可能. const の使用例 : int function(char *p){ } int function(const char *p){ } int function(char * const p){ } int function(const char * const p){ } p は左辺値 にできない *p は左辺値 にできない
変数の有効範囲(1) 各変数は,定義された位置や記憶域クラスの指定により, 有効範囲 (scope) を持つ. グローバル区間で定義された変数 … ファイル末尾まで . ブロックの中で定義された変数 … ブロック終了まで . ブロックを終えると, auto 変数のオブジェクトは破棄され, static 変数のオブジェクトは保存される.
変数の有効範囲(2) 変数の有効範囲とオブジェクトの生存期間 各変数に結び付けられたオブジェクトは,指定された記憶 域クラスに応じて生成・破棄される. 破棄されるまでは,スコープの外からでも ( ポインタなど で間接的に ) オブジェクトの参照ができる. 関数内の auto 変数は,関数処理のたびに生成される. ⇒ 再帰関数が構成できる.
まとめ 関数を自分で定義することができる.関数を呼び出すと き,引数は値渡しで授受される. それまでに定義した関数しか呼び出すことができない (と考えるのがよい)ので,関数定義の順番に注意する か,関数プロトタイプを利用する. 変数には,型とは別の属性として,記憶域クラスと型修 飾子を指定できる. static 変数と auto 変数とでは,大き く挙動が異なる. 変数の定義とオブジェクトの生成は別. オブジェクトの生成・破棄のタイミングに注意する.
発展的な話題(1) 型名 関数名 ( 引数名並び ) 「引数の型と引数名 ; 」の並び { 文...} 古い関数定義の構文 (K&R): 型名 関数名 ( 引数名並び ) 「引数の型と引数名 ; 」の並び { 文...} 例 : int main(argc, argv) int argc; char *argv[ ]; {…} 比較:現在の構文 (ANSI) int main(int argc, char *argv[ ]) {…}
発展的な話題(2) void 型 関数の(戻り値の)型や引数として記述可能. void 型の関数の式を値として利用できない(コンパイルエ ラー). void f(void) {…} としたとき, f(); は OK . printf("%d", f()) は NG . void 型の変数は定義できない(コンパイルエラー). void * 型の変数は定義可能.この型は,ポインタ間の変換 などで用いられる.
レポート課題 課題:「バージョン」について バージョン管理されたソフトウェアを一つ取り上げながら, バージョンの必要性・有用性をA4用紙2枚で論述する. 適切に引用し,友人などの協力があれば謝辞をつける. 提出方法:12月16日(2週間後)の授業開始時に回 収する.