情報処理Ⅱ
2
本日学ぶこと
関数と変数
目的
関数を自分で定義し,変数の利用方法・範囲を明示的に制限 することで,適切な機能分割(モジュール化,再利用)を図る. してはいけないこと
main関数のみで100行以上のプログラム グローバル変数を駆使するプログラム プログラムを 読みやすくする ⇒ 保守性保守性向上 入p.1093
関数
(Function)
関数の分類
自作関数: 自分で定義する.⇒ 本日のテーマ ライブラリ関数: 出来合いのもの.printfなど. なぜ関数を定義するのか?
処理を共通化(一般化)する プログラムの見通しをよくする main関数がないとプログラムは動かない 入pp.274-275 リp.3464
関数定義の方法
構文
:
型名
型名
関数名
関数名
(
(
引数並び
引数並び
) {
) {
文
文
...}
...}
型名は,関数の戻り値の型.値を返さないときは,voidと書く. 引数並びは,0個以上の「型名 変数名」をカンマで挟んだもの. 引数がないときは,何も書かないか,voidvoidと書く. 例: double myatof(const char *str0) {double myatof(const char *str0) {……}} 例: void procedure(int x, int y) {void procedure(int x, int y) {……}}
一括の変数宣言と異なり, このintは省略できない.
5
void型
void
void
は「何もない」や「無効な値」を表す型名
×× void x;void x; ○○ void *x;void *x; 用途
関数が引数や戻り値を持たないことを明示するとき •• void exit(int status);void exit(int status); •
• void procedure(void);void procedure(void);
任意のポインタ型を表現するとき
•
• void *p;void *p; •
• void *malloc(size_t size);void *malloc(size_t size);
プログラムを終了するライブ ラリ関数.通常,exit(0);exit(0); もしくは exit(1);exit(1); のいず れかで呼び出す. 入p.274 リp.349, p.300, p.498, p.501
6
関数の呼び出し
関数定義の例:
double f(double x)
double f(double x)
{return x+1;}
{return x+1;}
関数呼び出しの例:
b=f(a);
b=f(a);
x を関数 f の仮引数仮引数(parameter), a を関数 f の実引数実引数(argument) という.これらを区別する必要のないときは,ともに引数引数という. • 仮引数には型名も書く.実引数には書かない. x = a; の代入を行ってから,関数本文の処理に入る.関数 の処理が終われば,変数 x (のオブジェクト)は消滅する. x = f(x); と書いてもよい.このとき,仮引数の x と,実 引数の x (あらかじめ定義しておく)は,別のオブジェクトであ る. 変数aの値を引数として, 関数fを呼び出し,その戻 り値を,変数bに代入する. 入pp.277-278 リpp.347-3487
return
関数処理中に
return
return
値
値
;
;
があれば,そこで関数の処理
を終え,値を
戻り値
戻り値
(return value)とする.
値の前後にカッコは不要. 戻り値の型がvoidなら, return;return; と書ける.
戻り値の型がvoid以外なら,必ずreturn 値;で処理を終え
るように書く.
8
引数の授受
Cの関数呼び出しでは必ず
値渡し
値渡し
(call by value)にな
る.
値渡し: 実引数のコピーが仮引数に格納される.その後,仮コピー 引数の値を変更しても,実引数の値には影響しない. 参照渡し
参照渡し
(call by reference)をしたければ,ポインタ
値を引数とすればよい.
参照渡し: 仮引数の値を変更すれば,実引数の値もそれに変 わる.Cではこの意味での参照渡しをすることができないが,ポ インタ値を渡すことで,その参照先の値を変えることができる.参照先の値を変える • ポインタ値とは…配列変数の名前,または変数に「&」をつけ たもの 入p.277, p.285 リpp.347-3489
二つの値を交換する関数
値渡し ⇒ 失敗
引数は関数内の仮引数にコピーされる. 関数内の仮引数の中で,値を交換しても,関数の外のオブジェ クトは変更されない. 参照渡し ⇒ 成功
関数の仮引数は,指し示す先を持つ. 「*ポインタ変数 = 値」とすることで,ポインタ変数が指し示す (関数の外の)オブジェクトに値を代入する. swapint_bad.c, swapint.c10
値渡しで失敗する理由
コード(抜粋)
void swapint_bad(int x, int y) void swapint_bad(int x, int y)
{ { int tmp; int tmp; tmp = x; tmp = x; x = y; x = y; y = tmp; y = tmp; } } int x = 1, y = int x = 1, y = --1;1; swapint_bad(x, y); swapint_bad(x, y); 関数swapint_badの中 y = -1 x = 1 関数mainの中 y = -1 x = 1 tmp tmp=1 x = -1 y = 1
11
参照渡しで成功する理由
コード(抜粋)
void swapint_good(int *void swapint_good(int *pxpx, int *, int *pypy) )
{ { int tmp; int tmp; tmp = * tmp = *pxpx;; * *pxpx = *= *pypy;; * *pypy = tmp;= tmp; } } int x = 1, y = int x = 1, y = --1;1; swapint_good(&x, &y); swapint_good(&x, &y); 関数swapint_goodの中 関数mainの中 y = -1 x = 1 tmp tmp=1 x = -1 y = 1 py px リp.356
12
main関数の型
main関数の(戻り値の)型は,int とする.
void main とする本も多いが,規格上適切ではない. 正常終了は
return 0;
return 0;
と書き,
異常終了は
return 1;
return 1;
と書くのが一般的.
main関数が返す値,およびexit関数の引数は,
終了ステータス
(exit status)と呼ばれる.
Linuxなら,コマンド実行後,「echo $?」を実行することで その値を確認できる. 「コンパイルが成功したときに限り,ファイルを実行する」が 一つのコマンドで書ける リpp.359-36213
関数定義の順番
呼び出す関数は,呼び出す前に(プログラムファイルの上の
ほうで)宣言されていなければならない.
宣言や定義がない場合は,
int
int
関数名
関数名
();
();
とみなして呼
び出しを試みる.
対策
呼び出す順序に注意して関数を並べる. 関数プロトタイプを使用する.14
関数プロトタイプ
(Function prototype)
構文
:
型名
型名
関数名
関数名
(
(
引数の型の並び
引数の型の並び
);
);
「引数の型の並び」は,「引数(型と変数)の並び」でもよい.こ のとき変数名は無視される. 一般に,各関数の定義より前(上)に記述する. 例: int swapcase(intint swapcase(int **, int *);, int *); 関数プロトタイプを用いることで,
関数定義の順番を気にすることなくプログラムを記述できる.順番を気にすることなく 関数の入出力が明確になる. セミコロンを忘れずに 「関数原型」 ともいう 入pp.278-279 リpp.350-35115
変数
(Variable)・識別子・オブジェクト
識別子
(Identifier): 変数名,関数名,型定義名など
の「名前」
識別子とオブジェクト
(Object)の違い
識別子は,プログラムファイル(静的)で記述される「ラベル」 オブジェクトは,プログラム実行中(動的)に生成される「実体」 同一の識別子に対して複数のオブジェクトが生成されることも ある. 宣言
宣言
により,変数ならそのオブジェクト,関数ならその実行
コードが,記憶域(メモリ)上に割り当てられるとき,
その宣言を特に
定義
定義
という.
関数プロトタイプや,externを用いた変数や関数の宣言, 構造体などの独自型定義は,この意味で定義ではない. 入p.223 リp.37, pp.102-10316
変数を宣言・定義する際の注意点
型は何か?
グローバルかローカルか?
autoかstaticかexternか?
リpp.104-10617
グローバル変数とローカル変数
各変数は,定義された位置によって,有効範囲
(scope)を
持つ.
グローバル変数 • あらゆるブロックの外で定義された変数. • 有効範囲はファイル末尾まで. ローカル変数 • ブロックの中で定義された変数. • 有効範囲はブロック終了まで. グローバル - global - 大域的 - あらゆるブロックの外 ローカル - local - 局所的 - あるブロックの中 「ブロックの中」とは, { と }で 挟まれた領域のこと 入pp.280-281 リpp.111-114 関数の仮引数も ローカル変数18
識別子の宣言に関するルール
グローバルに同一の識別子を複数宣言できない.
一つのブロック内に,同一の識別子を複数宣言できない.
関数はブロック内で定義できない.必ずグローバル区間での
定義となる.
GCC(演習室のコンパイラ)では,関数の中に関数を定義できる が,使用しないこと! ブロック内に変数を定義するときは,それより外にある同一
の識別子と重複してもよい.
ただし,外にある同一の識別子は参照できない. モジュール化に関して有用なルール. リp.114, p.22519
型の属性
記憶域クラス
extern, static, auto, register
型修飾子
const, volatile
例
:
extern void function1(const char *);
extern void function1(const char *);
int x(void) {static int c=0; ...}
int x(void) {static int c=0; ...}
20 プログラムが複数のソースファイルで 構成されることがある.大規模プログ ラミングでは当たり前だが,本授業で は実例を出さない.
記憶域クラス(1)
auto
auto
: そのオブジェクトの生存期間は自動記憶域期間で
ある.
積極的にautoを書くことはない. static
static
: そのオブジェクトの生存期間は静的記憶域期間
である.
必要なときに使う. extern
extern
: 他の場所で宣言された識別子を使用する.
分割コンパイルで不可欠. externとstaticは,関数に 対しても指定できる. リp.10721
記憶域クラス(2)
静的記憶域期間 (
static変数)
staticを指定した変数と,グローバル変数が該当する. プログラムの実行に先立ち,オブジェクトが生成され,初期値が設定 される.プログラム終了まで破棄されない. 初期値を指定しないオブジェクトには 0 が代入される. 初期値は,コンパイル時に計算可能な定数式でなければならない. 自動記憶域期間 (
auto変数)
autoを指定した変数,staticやexternの指定なくブロック内で定 義した変数と,関数の仮引数が該当する. 宣言文を実行するたびに,オブジェクトが生成され,初期値があれば 毎回初期化される.ブロックを終えると,破棄される. 初期値を指定しないオブジェクトの初期値は不定. 初期値は任意の計算式でよい. 入pp.226-227 リp.110, pp.115-12022
有効範囲?記憶域クラス?
ここで問題
(小テストではありません) グローバルなstatic変数は,定義《 できる ∥ できない 》. グローバルなauto変数は,定義《 できる ∥ できない 》. ローカルなstatic変数は,定義《 できる ∥ できない 》. ローカルなauto変数は,定義《 できる ∥ できない 》. ローカルな
static変数の用途
ブロック内で情報を保存しておき,あとでまた実行するときに利 用する. 動的に確保することなく,配列領域を戻り値とする. auto変数では扱いきれない大容量オブジェクト(100万個の int配列など)を取り扱う. リpp.118-11923
配列の
auto変数(1)
関数の中で配列変数を定義すれば,関数処理の中で確保さ
れ,関数処理が終わると破棄されるような配列が作られる.
void print_message(void) void print_message(void) { {char message[] = "Wakayama";
char message[] = "Wakayama";
printf("%s
printf("%s¥¥n", message);n", message); }
}
message
関数print_messageの中
'W'
'a'
'k'
'a'
'y'
'a'
'm' 'a'
'¥0'24
配列の
auto変数(2)
関数の仮引数に配列変数を書けば,その配列と型が適合す
るポインタ変数になる.
voidvoid print_message(charprint_message(char message[])message[]) {
{
printf("%s
printf("%s¥¥nn", message);", message); } } 要素数(多次元配列の場合は左端)は無視される. 要素数なし・初期化なし(不完全型)でもよい.
message
関数print_messageの中'W'
'a'
'k'
'a'
'y'
'a'
'm' 'a'
'¥0'宣言の形は不完全型 実体はポインタ変数
25
変数の有効範囲の補足
破棄されるまでは,ブロックの外からでも
(ポインタなどで間
接的に
)オブジェクトの参照や値の書き換えができる.
⇒ ポインタによる参照渡しが可能となる.
関数内の
auto変数に対応するオブジェクトは,関数処理が
行われるたびに生成される.
⇒ 再帰関数が構成できる.
'W'
'a'
'k'
'a'
'y'
'a'
'm' 'a'
'¥0'message
何らかの関数の中wakayama
message[0] = 'w'; message[0] = 'w'; としてよい リp.12126
staticの補足
グローバル区間で,変数や関数の定義に
staticをつける
と,その識別子の有効範囲は,宣言時からそのファイル終了
までとなる(ファイル有効範囲).
staticをつけなければ,
他のファイルからも参照され得る.
ローカルな
static変数がある関数は,再入可能(リエントラ
ント,
reentrant)でない.
再入可能再入可能であれば,同じ値の引数で関数を呼び出せば,関数 内の処理,そして戻り値が必ず同じになる.複数のプログラム ルーチン(再帰呼び出しを含めて)から,同時かつ非同期に呼 び出すことができる. 不純関数ともいう. リp.112, p.11827
型修飾子
const
const
: 対象となるオブジェクトの値を変えることができな
い.参照は可能.
volatile
volatile
: 処理系が知らないうちに値を変える可能性が
ある.参照は可能.
constの使用例:
char *strcpy(char *dest, const char *src);char *strcpy(char *dest, const char *src);
*dest は 関数内実行中 左辺値にできる *src は 関数内実行中 左辺値にできない dest, srcともに 関数内実行中 左辺値にできる リp.109, p.516
28