PowerPoint

C プログラミング入門
基幹7 (水5)
08: 関数
Linux にログインし、以下の講義ページ
を開いておくこと
http://www-it.sci.waseda.ac.jp/
teachers/w483692/CPR1/
2016-06-01
1
例題:一般角の正規化を複数回行う
角度の正規化 (第5回参照) を 2 つの角に対し
て行いたい
a の正規化
a と b の正規化
...
...
int main(void)
int main(void)
{
{
変数 b について
も正規化したい
double a = 1030;
double a = 1030, b = -703;
while(a >= 360) { a -= 360; }
while(a >= 360) { a -= 360; }
while(a < 0)
while(a < 0)
{ a += 360; }
{ a += 360; }
while(b >= 360) { b -= 360; }
printf("%f\n", a);
...
while(b < 0)
ほぼ同じコードを何度も
書かなければいけない ...
{ b += 360; }
2
関数化
処理の固まりを新しい関数にする
コードの重複が減り、読みやすくなる
a と b の正規化 関数版
a と b の正規化
...
...
int main(void)
int main(void)
{
{
double a = 1030, b = -703;
変数 b について
も正規化したい
double a = 1030, b = -703;
a = NormalizedAngle(a);
while(a >= 360) { a -= 360; }
b = NormalizedAngle(b);
while(a < 0)
{ a += 360; }
while(b >= 360) { b -= 360; }
printf("%f, %f\n", a, b);
...
while(b < 0)
こういう関数を自分で作る
...
ことができればいい
{ b += 360; }
3
関数の定義
関数定義の構文
任意の個数指定可能
引数がない場合はキー
ワード void を与える
戻り値の型 関数名(型1 仮引数名1, ...) {ブロック}
異なる引数の同名関数を定義することはできない
関数を定義するために使う変数を
仮引数 (かりひきすう) という
/// 一般角 angle [deg] を
/// [0, 360) に正規化した値を返す
double NormalizedAngle(double angle)
{
while(angle >= 360) { angle -= 360; }
while(angle < 0)
return angle;
}
{ angle += 360; }
関数の実行結果(評価
値)は double 型の値
関数の仕様をコメントで
説明する
double 型の値を一つ受
け取る関数で、その仮引
数名を angle としてい
る (名前は任意)
仮引数は変数と同じなの
で、変更してもよい。
4
戻り値を持たない関数
戻り値の型を void とする
ブロック末尾の return を省略可能
/// Hello, world を単に表示するだけの関数
void printHelloWorld(void)
{
printf("Hello, world!\n");
return;
}
末尾にある場合は、書かなくてもよい
省略することはできない
5
引数の不要な関数
仮引数に void を書く
省略はできない
/// Hello, world を単に表示するだけの関数
void printHelloWorld(void)
{
printf("Hello, world!\n");
return;
引数を取らない関数を表す。
省略することはできない
}
C++ では引数のない関数は (void) の代わりに () だけで定義
してもよい。
6
main 関数
main() も関数の一つ
OS から呼び出されたと考える
戻り値はプログラムの実行がどのように終了
したかを OS に報告するステータスコード
0 が正常終了、それ以外は異常終了
int main(void)
引数は無し
{
...
int main(int ac, char** av);
...
return 0;
}
main 関数の引数は仕様で決められてい
る。 void 以外に、以下のものが許され
ている。これは、今後の講義で解説する
正常終了
<stdlib.h> には, EXIT_SUCCESS, EXIT_FAILURE というマク
ロ定数が用意されていて、それぞれ 0 と 1 である。 return
0; の代わりに return EXIT_SUCCESS; などとして使うこと
ができる。
7
例題のコード全体
最初の例題を書き換えた完全なコード
呼び出し時に与え
プログラムは main() から始まる る値を実引数とい
う
#include <stdio.h>
double a = 1030, b = -703;
/// 一般角 angle [deg] を
main 関数とは別の場所
double
に関数定義を書く
na, nb; // 正規化後の角度
/// [0, 360) に正規化した値を返す
na = NormalizedAngle(a);
double NormalizedAngle(double angle)
nb = NormalizedAngle(b);
{
while(angle >= 360) { angle -= 360; }
printf("Angle %f -> %f\n", a, na);
while(angle < 0)
printf("Angle %f -> %f\n", b, nb);
{ angle += 360; }
return angle;
}
return 0;
}
int main(void)
{
計算結果として返され
る戻り値型 double の
値を代入
あとで説明する通り、この例は意図的に
main の前にユーザー定義関数を書いている
8
関数の呼び出しと値渡し (call by value)
値渡し:引数の値が仮引数にコピーされる
...
/// 一般角 angle [deg] を
/// [0, 360) に正規化した値を返す
double NormalizedAngle(double angle)
関数の自動変数は呼び出しの時に作られ、
終了時点で削除される
NormalizedAngle
NormalizedAngle
angle
angle
{
while(angle >= 360) { angle -= 360; }
while(angle < 0)
{ angle += 360; }
return angle;
}
int main(void)
1030
310
自動変数は削除
される
コピー
コピー
-703
新しく自動
変数が作成
される
main
{
double a = 1030, b = -703, na, nb;
310
1030
na = NormalizedAngle(
a );
nb = NormalizedAngle( -703
b );
...
a
b
1030
-703
na
310
??
nb
??
9
変数名の重複
関数ごとに変数名は独立している
スコープはそれぞれの関数ブロック
#include <stdio.h>
/// 一般角 a [deg] を
/// [0, 360) に正規化した値を返す
double NormalizedAngle(double a)
他の関数のことを気にせず、自由に変
数名を使える。
ただし、大域変数の名前はスコープが全体なので
隠蔽してしまう(詳細略)
{
if(a >= 360) ...
}
int main(void)
{
関数 NormalizedAngle
の変数 a
関数 main の変数 a
互いに何の影響
も及ぼさない
int a = 1030, na;
na = NormalizedAngle(a);
...
10
関数の再帰呼び出し
関数が自分自身を呼び出すことを再帰呼び出
し (recursive call) という
呼び出しの度に、局所変数は別のものとして
作られる
階乗の漸化式
𝑛 ⋅ 𝑛 − 1 !, 𝑛 > 1
𝑛! =
1, 𝑛 = 1
/// 階乗
int fact(int n)
{
if(n == 1) return 1;
else
}
再帰呼び出しをしない条件が必ず存在する
return n*fact(n-1);
再帰呼び出し
再帰呼び出しが関数の末尾
にある場合は、必ずループ
による計算に等価変換でき
る。詳しくは末尾再帰など
で調べるとよい。
11
標準ライブラリ関数
printf() などの関数プロトタイプはリファレ
ンスサイトで調べることが可能
関数プロトタイプを書いていないのに、なぜ
コンパイルエラーにならないのか…?
ヘッダファイルに書かれている
標準ライブラリ関数の例
... は省略ではなくて、 C の文
法の一部であり、可変個の引数を
指定できることを表す
関数プロトタイプ
宣言されているヘッダファイル
int abs(int j);
stdlib.h
double sin(double x);
math.h
int printf(const char *format, ...);
stdio.h
int rand(void);
stdlib.h
12
関数のスコープ
変数と同様、関数の宣言からファイルの終わ
りまでがスコープ (=使用できる範囲)
#include <stdio.h>
関数 Normaliz
edAngle のス
コープ
/// 一般角 angle [deg] を
/// [0, 360) に正規化した値を返す
double NormalizedAngle(double angle)
{
...
}
int main(void)
ファイル終端
{
...
na = NormalizedAngle(a);
}
main を最後に
書いて、ほかの
関数をすべて前
に書くスタイル
のコードは多い
13
関数プロトタイプ宣言 (prototype)
前方宣言によりスコープを広げる
関数プロトタイプ宣言
#include <stdio.h>
#include <stdio.h>
ここでは未定義
エラーとなる
double NormalizedAngle(double a);
int main(void)
int main(void)
{
{
定義ブロックを
書かずにセミコ
ロンで終わる
...
...
na = NormalizedAngle(a);
na = NormalizedAngle(a);
}
}
/// 一般角 a [deg] を
/// 一般角 a [deg] を
/// [0, 360) に正規化した値を返す
/// [0, 360) に正規化した値を返す
double NormalizedAngle(double a)
double NormalizedAngle(double a)
{
...
}
呼び出しより後
ろに定義がある
スコープ
問題なく呼び出せる
{
...
}
14
コンパイラの気持ちになって考える
 コンパイラは、プログラムが文法通りに正しくかかれている
かをチェックする。
 チェックは、ソースコードにかかれている順で1度のみ行わ
れる。
 関数の呼び出しをチェックする際に、その関数がどういうも
のかを知る必要がある。
 関数を使う前に定義されていれば問題ない。
 自分で作った関数の定義が使う場所より後にある場合は、プロトタイプ
宣言を書く。
 標準ライブラリの関数 (printf) のことは知らないので、 <stdio.h>
ファイルを読み込んで、教える。このファイルにはプロトタイプ宣言が
列挙してある。
もちろん、自分で調べて自分で書いてもよいが、それは大変面倒である。
ヘッダファイルはこの作業を簡単にするためにある。
15
ヘッダファイル
関数プロトタイプなどをまとめたもの
普通、関数の定義そのものは含まれていない
#include によって, その位置に内容が展開される
各種ライブラリ(標準でないものも含む)は、
必ずヘッダファイルが提供される
関数の使い方等の情報が書かれていることも多い
関数のリファレンスにはどのヘッダか必ず書かれ
ている
stdio.h などを必ず読み込まなければいけないというわけではない。その代わりに、使おうとする関数の
プロトタイプ宣言を自分で書いてもよい。ただし、ヘッダファイルに書かれる内容は環境により変わる可
能性があるので、自分で正しいものが書ける保証はない。
16
これまでの知識でできないこと
配列を引数として渡す, 戻り値として返す
文字列を扱う
呼び出し元の変数を直接書き換える
例: 2 つの変数の値を入れ替える関数
例: scanf() はそのようなことを行う関数の一つ
⇒ポインタにより実現(次回以降説明)
17