情報処理Ⅱ

情報処理Ⅱ
第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 関数名( ); とみなして呼び
出しを試みる.
対策


呼び出す順序に注意して関数を並べる.
関数プロトタイプを使用する.
関数プロトタイプ
(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 <stdlib.h>)
変数(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: 処理系が知らないうちに値を変える可能性があ
る.参照は可能.
*p は左辺値
constの使用例:
にできない




int function(char *p){ }
int function(const char *p){ }
int function(char * const p){ }
int function(const char * const 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週間後)の授業開始時に回
収する.