C 言語これくらい覚えよう
かつらだ

ま さ し

桂田 祐史

2017 年 9 月 29 日, 2021 年 1 月 15 日
この文書は http://nalab.mind.meiji.ac.jp/~mk/labo/text/ に最新版を置きます。
入出力、繰り返し、関数を自分で定義する、ベクトルと行列、これくらいできれば…

目次
1

勉強の仕方

2

2

C コンパイラー23

簡単な数値計算プログラミングで良く使う機能
3.1 最短の C プログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Hello world . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3 整数の入出力と「5 則」演算 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4 浮動小数点数の入出力と四則演算 . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.5 数学関数ライブラリィの利用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.6 for 文による繰り返し . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.7 for 文による繰り返し (2) 応用: 級数の和 . . . . . . . . . . . . . . . . . . . . . . . .
3.8 while による繰り返しと if 文による分岐 (場合わけ) . . . . . . . . . . . . . . . . . .
3.9 ユーザー定義の関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.10 簡単なファイル入出力 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.11 ベクトルの扱い — 配列とポインター . . . . . . . . . . . . . . . . . . . . . . . . . .
3.11.1 方針 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.11.2 prog10.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.11.3 prog11.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.11.4 prog11-C99.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.11.5 prog11-C++.C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.12 行列の扱い — 2 次元配列とポインターのポインター . . . . . . . . . . . . . . . . .
3.12.1 方針 (私の見解) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.12.2 prog12.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.12.3 prog12a.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.12.4 prog12b.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4
4
5
6
8
10
11
13
14
16
17
19
19
20
20
21
22
23
23
24
25
26

4

練習問題
4.1 比較的簡単なもの . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 ベクトルの扱いの練習 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

27
28
29

1

A 関数へのポインターのプログラム例
A.1 比較的ありがちな汎関数の定義 . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
A.2 コマンドを登録して呼び出す . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

31
31
32

勉強の仕方

1

(新装開店を目指し準備中 — もうそろそろ現象数理学科の学生向けの案内を書くべきだ)C コンパイラー

2

C 言語で書かれたプログラムをコンピューターが理解できる形式 (機械語, machine language) に翻訳するプログラムを C コンパイラー (C compiler) と呼ぶ。
(普通は単にコンパイルするだけでなく、必要なライブラリィとリンクすることで、いわゆる実行
形式のファイルを作る。)
現象数理学科 Mac では、Apple の用意した Xcode と呼ばれるソフトウェアをインストールしてあり、そこに Clang+LLVM のコンパイラー cc が含まれている。
現象数理学科 Mac では、グラフィックス・ライブラリィ GLSC を利用する C プログラムのコンパイルのために cglsc コマンドというのを用意してある。それは中から cc を呼び出しているので、結局は Clang+LLVM を利用することになる。
cc の使い方は、UNIX で伝統的な cc の使い方と互換性がある。よく使うオプションなどは「C
言語で数値計算プログラミング」1. コンパイル、実行の仕方1 で解説しておいた。
(脱線) アセンブリー言語に翻訳してみる 機械語はビット列なので人間には読みにくいが、機械語とほぼ一対一に対応するアセンブリー言語という形式に変換すると、どのような機械語に変換されるのかが (まあまあ) 分かりやすい。cc -S とすると、C 言語のプログラムをアセンブリー言語に変換出来る。
アセンブリー言語のプログラムを機械語に変換することを「アセンブルする」と言い、アセンブ
ルするためのプログラムをアセンブラーと呼ぶ。実は cc はアセンブラーの機能も備えている。
以下の例では、小さな C プログラム prog.c を、アセンブリー言語に翻訳したプログラム prog.sを作り、それを表示した後、prog.s をアセンブル＆リンクして実行形式 a.out を作って実行している。

1

http://nalab.mind.meiji.ac.jp/~mk/labo/studying-C/Programing-in-C/node5.html

2





(以下で bash-3.2$ はプロンプトで、これはユーザーが入力するものではない。)
bash-3.2$ ls
prog.cbash-3.2$ cat prog.c#include <stdio.h>
int main(void){
int a,b,c,d;a = 1; b = 2; c = 3;
d = a * b + c;
printf("%d*%d+%d=%d\n", a, b, c, d);return 0;}
bash-3.2$ cc -S prog.c
bash-3.2$ ls
prog.c prog.s
bash-3.2$ cat prog.s(中略)bash-3.2$ cc prog.s
bash-3.2$ ls
a.out* prog.c prog.s
bash-3.2$ ./a.out
1*2+3=5
bash-3.2$

次に (macOS 上の cc で作成した場合の) prog.s の内容を掲げる。1 × 2 + 3 という計算をして、
printf() を呼び出しているのが分かるだろうか？

3


prog.s — アセンブリ言語プログラムの例_main:

.section__TEXT,__text,regular,pure_instructions.macosx_version_min 10, 13.globl _main## -- Begin function main
.p2align
4, 0x90## @main.cfi_startproc## BB#0:
pushq

%rbp

Lcfi0:.cfi_def_cfa_offset 16
Lcfi1:.cfi_offset %rbp, -16
movq%rsp, %rbp
Lcfi2:.cfi_def_cfa_register %rbp
subq$32, %rsp
leaq
L_.str(%rip), %rdi
movl
$0, -4(%rbp)
movl$1, -8(%rbp)
movl$2, -12(%rbp)
movl$3, -16(%rbp)
movl-8(%rbp), %eax
imull-12(%rbp), %eax
addl
-16(%rbp), %eax
movl%eax, -20(%rbp)
movl-8(%rbp), %esi
movl-12(%rbp), %edx
movl
-16(%rbp), %ecx
movl-20(%rbp), %r8d
movb
$0, %alcallq_printf
xorl%ecx, %ecx
movl
%eax, -24(%rbp)
movl%ecx, %eax
addq
$32, %rsp
popq
%rbp
retq
.cfi_endproc## 4-byte Spill## -- End function__TEXT,__cstring,cstring_literals## @.str"%d*%d+%d=%d\n".section
L_.str:.asciz.subsections_via_symbols


3

簡単な数値計算プログラミングで良く使う機能

3.1

最短の C プログラム

C のプログラムは関数定義の集合である。特に main() という名前の関数は必ず存在する必要がある (ここから実行が開始される)。そこで最短の C プログラムは次のようになる。ここでは説明のための注釈 (/* と */ で括られた部分) を書いたのであまり短くはないが。

4


prog01.c/* prog01.c --- (注釈は除いて) 最短の C プログラム */int main(void){
return 0;}
コンパイルと実行
bash-3.2$ cc -o prog01 prog01.c
bash-3.2$ ./prog01
bash-3.2$(あるいは cglsc prog01.c)← 確かに何もしない

なお、警告が出ても良ければ、

これこそ本当の最短 C プログラム？ (ちょっと文法違反しているけど)main(){}

という 8 文字のプログラムに切り詰めることも出来る。
なお、最近の C 言語では、// から行末までは注釈という、1 行注釈が使えるようになったので、上のプログラムの 1 行目は// prog01.c --- (注釈は除いて) 最短の C プログラム


のように書くことも出来る。
蛇足 (main() 中の return の意味) return 0; はどういう意味があるかというと、OS に 0 という値を返している。実際、上のプログラムの実行直後にecho $status と打つと 0 と表示される。
プログラムがどういう状況で終了したかを OS に知らせる目的で使うことができる。通常 0 は正常に終了したことを示す習慣である。

3.2

Hello world

次が有名な2 “Hello world プログラム” である。

prog02.c/* prog02.c --- 有名な Hello world プログラム */#include <stdio.h>
int main(void){
printf("Hello, world.\n");return 0;}
コンパイルと実行
bash-3.2$ cc -o prog02 prog02.c
bash-3.2$ ./prog02
Hello, world.
bash-3.2$2(あるいは cglsc prog02.c)

上で紹介したカーニハン&リッチーの本の最初のプログラム例である。

5• printf() は標準出力 (通常は画面と結合) への書式付き出力を行う関数である。
• 二つの引用符 " " で囲まれた部分は文字列となる。文字列中の \n は改行文字を表す。(なおバックスラッシュ \ は、JIS コードでは円記号 Y に相当する。)

printf() による文字列の標準出力への出力printf(文字列);
例えば
printf("こんにちは \n");• printf() の「宣言」をするために stdio.h というファイルを #include という命令を用いてインクルードする (stdio=standard input and output の略, h は header ﬁle の頭文字)。その実体は /usr/include/stdio.h というファイルである。
問

一つの printf() で、縦に





あ
い
う
え
お




と書くにはどうしたら良いか？

3.3

整数の入出力と「5 則」演算

次に掲げるプログラムは、二つの整数を読み込んで、それらの和、差、積、(整数の) 商、余りを表示するプログラムである。

prog03.c/* prog03.c --- 整数の入出力と 5 則 (?) 演算 */#include <stdio.h>
int main(void){
int a, b;
printf(" 二つの整数を入力して下さい: ");scanf("%d%d", &a, &b);
printf(" 入力された整数は %d, %d です。\n", a, b);
printf(" 和=%d, 差=%d, 積=%d, 商 (の整数部分)=%d, 余り=%d\n",a + b, a - b, a * b, a / b, a % b);
return 0;}

C 言語で扱える値には「型」というものがある。

6• (ある範囲の3 ) 整数値を記憶するために int という型が用意されている (整数は英語で integer,
形容詞は integral)。
• int 型の変数を宣言 (定義) するには

int 変数名のリスト;
例えば
int a;
int b, c, d; /* カンマで区切って名前を並べる */




とする。

• 標準入力 (普段はキーボードからの入力に結合) から整数型の変数に値を入力するには、素朴には scanf() という関数を使う (scanf() は、実はトラブル・メーカーなのだが、とりあえず無視)。
• 標準出力 (普段は端末画面への出力に結合) に整数型の式の値を出力 (表示) するには、printf()という関数を使う。
• scanf(), printf() で「書式 (format)」を指定する場合、整数型のデータには普通 %d を用いる (d は decimal (10 進数) の略。実は 8 進数、16 進数などで入出力できる)。

print() で整数を 10 進数で出力)

printf("値は %d\n", 整数型の式);
例えば
printf("a=%d, b=%d\n", a, b);
printf("a の 3 倍=%d\n", 3 * a);
scanf() で 10 進数表記された整数を整数型変数に入力

scanf("%d", &整数型変数);
例えば
scanf("%d", &a);
scanf("%d%d", &b, &c);

&変数名 として、変数へのポインター (アドレス) を scanf() に渡すのがミソ。scanf() の使い方は、とりあえずパターンとして覚えてしまおう。
• 整数型の式を作る演算子として、和 +, 差 -, 積 *, 整商 /, 剰余 % がある。
• int は整数を記憶するための変数であるが、値の範囲は限定されている。
現在の多くの C コンパイラーでは、int を記憶するのに 32 ビット使っている。負の数を表す
ために 2 の補数表現というのを使うのがふつうで、値の範囲は −231 ∼ 231 − 1 となる。

232 = 4294967296,

231 = 2147483648.

Mac 環境の cc (中身は Clang+LLVM らしい) では、−231 ≤ n ≤ 231 − 1 を満たす整数 n. 231 = 2147483648 ≒21 億 = 2.1 × 109 であり、10 進 9 桁程度。これ以外に、通常 64 ビットの long long int 型 (絶対値 9.22 × 1018 = 922 京
位まで表現可能) もある (定数を表すには、2147483649LL あるいは 2147483649ll のように “LL” や “ll” をつける。
printf() や scanf() の書式では、“%lld” のようにする。)。さらに int128 という型もあるが、まだ十分に規格が整備されていないので使いにくい。
37

232 = 約 42 億, 231 = 約 21 億 というのは覚えておくと良い。long long int という型は記憶するのに 64 ビットを使う。264 = 約, 263 = 約,
264 = 18446744073709551616 ≒ 1.84467 × 1019 = 約 1800 京,

3.4

263 = 9223372036854775808.

浮動小数点数の入出力と四則演算C 言語で整数以外の実数を扱うには、浮動小数点数 (ﬂoating point numbers) という型を用いる。

prog04.c/* prog04.c --- 実数の入出力と四則演算 */#include <stdio.h>
int main(void){
double a, b;
printf(" 二つの実数を入力して下さい: ");scanf("%lf%lf", &a, &b);
printf(" 入力された実数は %f, %f です。\n", a, b);
printf(" 和=%f, 差=%f, 積=%f, 商=%f\n", a + b, a - b, a * b, a / b);
return 0;}• 精度、範囲により三種類の型 ﬂoat, double, long double がある。最近、実質的標準となっている IEEE754 規格を採用したシステムでは、精度は 10 進法に換算して、float が 7 桁, doubleが 16 桁弱である。値の範囲は float が 10−38 ∼ 1038 , double が 10−308 ∼ 10308 である。
• 私のおすすめは「浮動小数点数は double だけ使う」である (理由は省略する)。

8


書式指定ミニマム• printf() で double データを小数形式で出力するには %f という書式を使う。


printf("a=%f\n", a);printf("b=%f, c=%f\n", b, c);• scanf() で、double 型の変数に入力する場合の書式は %lf (他にもあるがこれ一つで十分) を使う (l はエル L の小文字)。


scanf("%lf", &a);
scanf("%lf%lf", &b, &c);

入力と出力で書式が %lf, %f と食い違うのは、歴史的経緯でしかたない。

• その他に指数形式で出力する %e, 小数形式と指数形式のうちで「コンパクト」な方を (そのときの式の値に応じて) 選ぶ %g がある。
• 表示の桁数 (n), 小数点以下の数字の桁数 (m) を指定するには %n.mf とする。




printf("%20.15f\n", a); (幅 20 桁、小数点以下 15 桁、小数形式で表示する)




例えば



double x=1.2345678901234567;
printf("12345678901234567890\n");printf("%8.4f\n", x);

とすると

となる。








int と double の使い分け方

int で表すことの出来る値は double でも表すことが出来る場合が多いが、配列の添字など doubleは使えない場合もあり、きちんと使い分けるべきである。
(1) 整数でない半端な値が出て来る可能性があれば当然 double(2) 配列の添字は (絶対) int(3) 集合の要素数、番号、繰り返しの回数など、本来整数値しか取り得ない場合は (普通は) int
時々 int では表現不可能だが、double では表現できるような値を扱うために double を使う、ということはある。— しかし、最近では long long int 型が安心して使えるようになったのでa 、この用法は時代遅れかもしれない。
a
細かいことを言うと、long long int は (普通) 64 ビット、double の仮数部は (IEEE754 の場合) 53 ビットなので、long long int の方が 11 ビット分多い。



9

3.5

数学関数ライブラリィの利用

通常数学に現われる初等関数や、平方根関数ライブラリィに用意されている。

√√, n 乗根 n , 絶対値 | | などは、C 言語では数学• 使用する数学関数の(1) 返す値の型(2) 引数 (argument) の型を宣言するためには math.h をインクルードするのが手っ取り早い。一度 /usr/include/math.hを見てみることを勧める。

• リンクするには -lm オプションが必要である4 。

prog05.c/* prog05.c --- 数学 (関数) ライブラリィにある関数の呼び出し */#include <stdio.h>#include <math.h>/* この行に注目 */int main(void){
double x;
printf("一つの実数を入力してください: ");scanf("%lf", &x);/* ルート (非負の平方根) */printf("sqrt(%g) =%g\n", x, sqrt(x));/* 三角関数 sin */printf("sin(%g)=%g\n", x, sin(x));/* e を底とする指数関数 */printf("exp(%g)=%g\n", x, exp(x));/* 自然対数 */printf("log(%g)=%g\n", x, log(x));/* 常用対数 (10 を底とする対数) */printf("log10(%g) =%g\n", x, log10(x));/* 双曲線関数 hyperbolic sin) */printf("sinh(%g) =%g\n", x, sinh(x));/* 巾乗 (power) --- ここでは 3 乗根 */printf("pow(%g,%g)=%g\n", x, 1.0/3.0, pow(x, 1.0/3.0));/* 逆三角関数 Arctan */printf("atan(%g) =%g\n", x, atan(x));/* 絶対値 */printf("fabs(%g) =%g\n", x, fabs(x));/* 整数部分 (-∞に向かっての切り捨て = いわゆる Gauss の括弧) */printf("floor(%g) =%g\n", x, floor(x));/* ∞に向かっての切り上げ */printf("ceil(%g) =%g\n", x, ceil(x));/* 最も近い整数への丸め (≒四捨五入) */printf("rint(%g) =%g\n", x, rint(x));
return 0;
}




4

ライブラリィは ”libname.a” という名前のファイルとして格納されているのが普通で、この中に入っているコードをリンクするには -lname というコマンドライン・オプションをコンパイラーに指定する。数学関数ライブラリィの
名前は libm.a なので (やけに短い名前ですね)、それをリンクするには -lm とすることになる。

10


コンパイルと実行% cc -o prog05 prog05.c -lm% ./prog05(結果省略)

(-lm が必要。l はエル L の小文字。)


整数/整数 に注意




1を計算するのに 1.0/3.0 としているのに注意。 1/3 は整除で 0 になっ3てしまう。 (double)1/(double)3 とすべきかもしれないが、入力が面倒だし、読みづらいので 1.0/3.0 あるいは 1.0/3 などにすることを奨める。
(細かい注) 1.0 という表記はコンピューターでなければ、常識的には有効数字 2 桁の数という意味になる。だからあまり誉められた書き方でないかもしれない。 1. とする人もいるが…
まあ好みの問題でしょうか。
上のプログラムで
円周率 π, 自然対数の底 e など、どうやって用意する？



簡単な応用として、円周率 π = 3.1415926535 · · · や自然対数の底 (Napier の数) e =2.7182818284 · · · が必要な場合に#include <math.h>double pi, e;...
int main(){...
/* main() の先頭付近で pi, e を初期化 */pi = 4.0 * atan(1.0);e = exp(1.0);のようなコードを書くというのがあります。int main() の前で宣言し (グローバル変数になります)、プログラム開始早々に代入文を実行して値を設定するわけです。


余談 いわゆる特殊関数が使いたい場合は、ライブラリィを探すことになるであろう。その場合、Cよりは C++ の方が見つかりやすいかもしれない。
練習問題

3.6

実係数の 2 次方程式 ax2 + bx + c = 0 (a, b, c ∈ R, a ̸= 0) を解くプログラムを作れ。

for 文による繰り返し

ある条件が満たされている間の繰り返しには for() が便利である。



for (最初にすること; 継続の条件; 各繰り返しの最後にすること)
文;

括弧 { } でくくることにより、複数の文をまとめた複文が使える。

11











for (最初にすること; 継続の条件; 各繰り返しの最後にすること) {
文 1;
文 2;
...
文 n;
}



よくあるパターンとして、「n 回の繰り返し」をするために次のように書くことがある。
n 回の繰り返しfor (i = 0; i < n; i++) {
文;
文;
}



応用として、「n = 1, 2, . . . に対して 2n , 2−n を計算して表示せよ」という問題を解いてみよう。
xn := 2n , yn := 2−n とおくと、数列 {xn }, {yn } は

x0 = 1,xn = 2xn−1(n = 1, 2, . . . ),y0 = 1,yn = yn−1 /2 (n = 1, 2, . . . )という漸化式で定義できることに注目し、それを用いて計算してみよう。

prog06.c/* prog06.c --- for 文による繰り返し (1) */#include <stdio.h>
int main(void){
int i, n;double x, y;
printf("自然数を一つ入力してください: ");scanf("%d", &n);x = 1.0; y = 1.0;
for (i = 1; i <= n; i++) {/* x をその 2 倍で、y をその 1/2 倍で置き換える */x = 2 * x; y = y / 2;/* 次の %g を %f や %e に変えて試してみよう */printf("2 の%d 乗=%g, 1/2 の%d 乗=%g\n", i, x, i, y);}return 0;
}




12


prog06 の実行結果

% ./prog06
自然数を一つ入力してください: 20
2 の 1 乗=2, 1/2 の 1 乗=0.5
2 の 2 乗=4, 1/2 の 2 乗=0.25
2 の 3 乗=8, 1/2 の 3 乗=0.125
2 の 4 乗=16, 1/2 の 4 乗=0.0625
2 の 5 乗=32, 1/2 の 5 乗=0.03125
2 の 6 乗=64, 1/2 の 6 乗=0.015625
2 の 7 乗=128, 1/2 の 7 乗=0.0078125
2 の 8 乗=256, 1/2 の 8 乗=0.00390625
2 の 9 乗=512, 1/2 の 9 乗=0.00195312
2 の 10 乗=1024, 1/2 の 10 乗=0.000976562
2 の 11 乗=2048, 1/2 の 11 乗=0.000488281
2 の 12 乗=4096, 1/2 の 12 乗=0.000244141
2 の 13 乗=8192, 1/2 の 13 乗=0.00012207
2 の 14 乗=16384, 1/2 の 14 乗=6.10352e-05
2 の 15 乗=32768, 1/2 の 15 乗=3.05176e-05
2 の 16 乗=65536, 1/2 の 16 乗=1.52588e-05
2 の 17 乗=131072, 1/2 の 17 乗=7.62939e-06
2 の 18 乗=262144, 1/2 の 18 乗=3.8147e-06
2 の 19 乗=524288, 1/2 の 19 乗=1.90735e-06
2 の 20 乗=1.04858e+06, 1/2 の 20 乗=9.53674e-07%

繰り返しの命令には、他に while () ... や do ...

3.7

while () などがある。

for 文による繰り返し (2) 応用: 級数の和

級数の和Sn = a1 + a2 + · · · + anを計算するための定跡がある。

S0 = 0と約束すればSi = Si−1 + ai

(i = 1, 2, . . . , n)という漸化式が成り立つ。これから次の手順を得る。
∑
Sn = ni=1 ai の計算手順

(1) 部分和 Si を記憶しておく変数 s を定義する。
(2) s = 0;
(3) s = s + ai ; を i = 1, 2, . . . , n について繰り返す。


次のプログラムは、与えられた自然数 n に対して 1 + 2 + · · · + n を計算して表示するもので
ある。
2

13

2

2


prog07.c/** prog07.c --- for 文による繰り返し (2)*/#include <stdio.h>
int main(void){
int i, n;double s;
printf("自然数を一つ入力してください: ");scanf("%d", &n);s = 0.0;for (i = 1; i <= n; i++) {/* 次の文では s に i を加えている。これは s = s + i * i; とも書ける。 */s += i * i;}printf(" 1 から %d までの自然数の平方の和=%g\n", n, s);return 0;
}




n
∑
1

を計算するプログラムを自力で書いてみると良い。ヒk!
k=0
ント: 一般項 ak = 1/k! も漸化式で計算すると都合が良い。
「漸化式を使って計算しよう」5 という資料を準備しておいた。
後の練習問題にしておいた、Sn =while による繰り返しと if 文による分岐 (場合わけ)

3.8

ある条件が満たされている間、文を繰り返し実行するために、while がある。

while (条件式)
文;







条件式を作るためには、例えば以下のような比較演算子が使える。

== (等しい)
<
(小さい)
>
(大きい)

!= (等しくない)<= (以下≦)>= (以上≧)条件式が成り立っているかどうかで、次に何を実行するか、場合わけをするために、if という命
令がある。


(1)

if (条件式) 文;









(2)


5



if (条件式)
文 1;
else
文 2;



http://nalab.mind.meiji.ac.jp/~mk/keisanki2-2005/zenkashiki/14
prog08a.c/** prog08a.c --- 実係数 2 次方程式を解く*/#include <stdio.h>#include <math.h>int main(void){
double a, b, c, D, sqrtD, re, im, x1, x2;
printf("2 次方程式 a x^2+b x+c=0 を解く。\n");printf("a, b, c: ");scanf("%lf%lf%lf", &a, &b, &c);
D = b * b - 4 * a * c;if (D > 0) {sqrtD = sqrt(D);x1 = (- b + sqrtD) / (2 * a);x2 = (- b - sqrtD) / (2 * a);printf("相異なる 2 つの実数解: %20.15g, %20.15g\n", x1, x2);}
else if (D == 0.0) {printf("ただ一つの実数解 (重根): %20.15g\n", - b / (2 * a));}else {
re = - b / (2 * a);im = sqrt(- D) / (2 * a);printf("相異なる 2 つの虚数解: %20.15g ±%20.15g ｉ\n", re, im);}return 0;}

prog08.c/* prog08.c --- while による繰り返しと if 文による分岐 (場合わけ) *//** 与えられた自然数が偶数ならば 2 で割り、そうでなければ 3 倍して 1 を* 加えるという操作を繰り返すと、最後には必ず 1 になる、らしい* (簡単そうだが証明されていない)。
* このことを実験するプログラム。
*/#include <stdio.h>
int main(void){
int n;
printf(" 自然数を入力してください: ");scanf("%d", &n);
while (n != 1) {printf("%d\n", n);
if (n % 2 == 0)n = n / 2;else
n = 3 * n + 1;}
printf("%d\n", n);return 0;
}




15

3.9

ユーザー定義の関数

自分で関数が定義できる。

• 関数宣言 (関数の返す値、引数の型を指定する) の書き方は、


関数の返す値の型名 関数名 (引数の型のリスト);
例えばdouble square(double);あるいはdouble square(double x);• 関数の定義は、まず


関数の返す値の型名 関数名 (引数の型 引数名 の繰り返し)
{
....
....
}





の形をしていて、値はreturn 式;

という形の実行文で返す。例えば




例えば



double square(double x){
return x * x;} sin x(x ̸= 0)f (x) :=x 1
(x = 0)で定義される関数を定義するには次のように書けば良い。

16

prog09.c6
/* prog09.c --- ユーザー定義の関数 */#include <stdio.h>#include <math.h>/* 次の文が関数 f の「宣言」 */double f(double);
int main(void){
int i, n;double a;printf(" 自然数を入力してください: ");scanf("%d", &n);a = 1.0;for (i = 0; i < n; i++) {a /= 2;
printf("1-sin(%g)/%g=%g\n", a, a, 1.0-f(a));}
return 0;}/* 以下、関数 f() の定義 */double f(double x){
if (x == 0.0)
return 1.0;
else
return sin(x) / x;
}


3.10



簡単なファイル入出力
input.data



2 3





というように 1 行に 2 つの整数が記録されているファイルを読み込んで、その 2 数の和を計算し、
その結果を


output.data

5





のようにファイルに記録するにはどうすれば良いか。

6

http://nalab.mind.meiji.ac.jp/~mk/labo/text/cminimum-prog/prog09.c

17


prog13.c/** prog13.c --- fopen(), fclose(), fprintf(), fscanf() を使ったファイル入出力* コンパイルは gcc -o prog13 prog13.c*/#include <stdio.h>
int main(void){
int a, b, sum;
FILE *in, *out;
in = fopen("input.data", "r");/* 本当はここで in が NULL でないかチェックすべき */fscanf(in, "%d%d", &a, &b);
fclose(in);sum = a + b;
printf("%d と %d の和は %d\n", a, b, sum);out = fopen("output.data", "w");fprintf(out, "%d\n", sum);fclose(out);
return 0;}

なお、fopen() に失敗することも多い。エラー・チェックをするように修正すると次のようになる。

18

prog13check.c/** prog13check.c --- fopen(),fclose(),fprintf(),fscanf() を使ったファイル入出力* コンパイルは gcc -o prog13 prog13.c*/#include <stdio.h>
int main(void){
int a, b, sum;
FILE *in, *out;
if ((in = fopen("input.data", "r")) == NULL) {fprintf(stderr, "input.data を読むために開こうとして失敗しました。\n");exit(1);}
fscanf(in, "%d%d", &a, &b);
fclose(in);sum = a + b;
printf("%d と %d の和は %d\n", a, b, sum);if ((out = fopen("output.data", "w")) == NULL) {fprintf(stderr, "output.data を書くために開こうとして失敗しました。\n");exit(1);}
fprintf(out, "%d\n", sum);fclose(out);
return 0;
}




3.11

ベクトルの扱い — 配列とポインター

3.11.1

方針

ベクトルはコンパイル前からサイズ (次元) が分かっていて、それが小さければ、1 次元配列で扱うのが簡単であろう。サイズが大きい場合に自動変数 (関数の中で宣言する変数) として宣言するのは、避けるべきである。
サイズが事前に分からないか、分かっていても大きい場合は、malloc() で動的に割りあてるのが良いであろう。
double a[1000]; // 1000 を変数にすることは出来ない
int main(void){
double b[1000]; // 大きなデータは難点あり。スタックサイズ不足で Seg fault 等double *c;// おすすめc = malloc(sizeof(double) * 1000); // 1000 を変数にも出来るし、大きくても大丈夫
if (c == NULL) {// エラー処理
}
...

19



3.11.2

prog10.cベクトルのサイズ N が事前に分かっていて、小さい場合。
/** prog10.c --- 配列*
コンパイルするには、たとえば gcc -o prog10 prog10.c*/#include <stdio.h>#define N (3)/* n 次元のベクトル x, y の内積を計算する */double inner_product(double x[], double y[], int n){
int i;
double s;s = 0.0;for (i = 0; i < n; i++)s += x[i] * y[i];return s;}
int main(void){
int i;
double a[N], b[N];printf("二つの %d 次元ベクトルの内積を計算します。\n", N);
printf("一つ目のベクトル a の入力\n");for (i = 0; i < N; i++) {printf(" %d 番目の成分=", i + 1); scanf("%lf", &a[i]);}printf("二つ目のベクトル b の入力\n");for (i = 0; i < N; i++) {printf(" %d 番目の成分=", i + 1); scanf("%lf", &b[i]);}printf("内積=%g\n", inner_product(a, b, N));
return 0;}

3.11.3

prog11.c

上のプログラム prog10.c では、N の定義を書き変えることでベクトルの次元を変えることができるが、(C99 以前の C の規格では) プログラムの実行中にベクトルの次元を選ぶことはできない。
そうするには malloc() のような関数を利用して、動的にメモリーを確保する必要がある。ポイン
ターの扱いにも慣れる必要がある。
/** prog11.c --- 「動的な配列」をポインターで実現する*
コンパイルするには、たとえば gcc -o prog11 prog11.c*/#include <stdio.h>#include <stdlib.h>/* malloc() の宣言 *//* n 次元のベクトル x, y の内積を計算する */20

double inner_product(double *x, double *y, int n){
int i;
double s;s = 0.0;for (i = 0; i < n; i++)s += x[i] * y[i];return s;}
int main(void){
int i, N;
double *a, *b;
printf("二つのベクトルの内積を計算します。\n");printf("次元を入力してください: "); scanf("%d", &N);
a = malloc(sizeof(double) * N);b = malloc(sizeof(double) * N);
if (a == NULL || b == NULL) {fprintf(stderr, "ベクトルを記憶するメモリーの確保に失敗しました。\n");exit(1);}
printf("一つ目のベクトル a の入力\n");for (i = 0; i < N; i++) {printf(" %d 番目の成分=", i + 1); scanf("%lf", &a[i]);}printf("二つ目のベクトル b の入力\n");for (i = 0; i < N; i++) {printf(" %d 番目の成分=", i + 1); scanf("%lf", &b[i]);}printf("内積=%g\n", inner_product(a, b, N));/* a, b のために確保したメモリーを解法する* プログラムの最後だからなくても良いが解放の仕方の説明用 */free(a); free(b);return 0;}

3.11.4

prog11-C99.c

C 言語の新しい規格である C99 では、可変長配列 (variable length array) が導入されたので、次のようなプログラムが書ける。
(でも、この機能は C11 ではオプションになったらしく、C++14 では外されたらしい。それと可変長配列はスタックに確保されるので、あまり大きなサイズは使えない (手元の Mac でやったら 8
MB くらいが上限だった)。大きな行列の実現に使うのは良くないだろう。個人的にはこの機能を使うコードは書かないことにする。)/** prog11-C99.c --- 可変長配列と任意位置での変数宣言*
コンパイルするには、たとえば gcc -o prog11-C99 prog11-C99.c*/#include <stdio.h>/* n 次元のベクトル x, y の内積を計算する */21

double inner_product(double *x, double *y, int n){
int i;
double s;s = 0.0;for (i = 0; i < n; i++)s += x[i] * y[i];return s;}
int main(void){
int i, N;
printf("二つのベクトルの内積を計算します。\n");printf("次元を入力してください: "); scanf("%d", &N);
double a[N], b[N];printf("一つ目のベクトル a の入力\n");for (i = 0; i < N; i++) {printf(" %d 番目の成分=", i + 1); scanf("%lf", &a[i]);}printf("二つ目のベクトル b の入力\n");for (i = 0; i < N; i++) {printf(" %d 番目の成分=", i + 1); scanf("%lf", &b[i]);}printf("内積=%g\n", inner_product(a, b, N));
return 0;}

3.11.5

prog11-C++.C

ちなみに、C++ の場合は、関数 malloc() と free() よりも、演算子 new と delete を使うべきである。例えば次のようなプログラムになる。
/** prog11-C++.cpp --- 「動的な配列」をポインターで実現する*
コンパイルするには、たとえば g++ -o prog11-C++ prog11-C++.cpp*/#include <iostream>#include <cstdlib>
using namespace std;/* n 次元のベクトル x, y の内積を計算する */double inner_product(double *x, double *y, int n){
int i;
double s;s = 0.0;for (i = 0; i < n; i++)s += x[i] * y[i];return s;}
int main(void){22

int i, N;
double *a, *b;cout << "二つのベクトルの内積を計算します。" << endl;
cout << "次元を入力してください: ";cin >> N;a = new double [N];b = new double [N];if (a == NULL || b == NULL) {cerr << "ベクトルを記憶するメモリーの確保に失敗しました。" << endl;
exit(1);}cout << "一つ目のベクトル a の入力\n";for (i = 0; i < N; i++) {cout << i+1 << " 番目の成分= ";cin >> a[i];}cout << "二つ目のベクトル b の入力" << endl;
for (i = 0; i < N; i++) {cout << i+1 << " 番目の成分=";cin >> b[i];}cout << "内積=" << inner_product(a, b, N) << endl;/* a, b のために確保したメモリーを解法する* プログラムの最後だからなくても良いが解放の仕方の説明用 */delete [] a;delete [] b;
return 0;}3.12行列の扱い — 2 次元配列とポインターのポインター

3.12.1

方針 (私の見解)

行列もコンパイル時にサイズ (行の数、列の数) が分かっていて、それらが小さければ、2 次元配列で扱うのが簡単であろう。
そうでない場合、従来の C ではとても悩ましかった。C99 で導入された可変長配列の機能は、行列のサイズが小さい場合には有効であろう。特に従来からあった「C には Fortran の整合配列の機能がないのでライブラリィが作りにくい」という批判は、可変等配列の機能を利用することで解決出来る。しかし、この可変長配列の機能は、新しい C11 の規格ではオプションにされてしまった。
GCC と LLVM では相変わらず使えるようであるが、この機能を使い続けるかどうかは難しい問題
である。
一方、数値計算に現れる大規模行列や 2 次元格子上の数値データなど、大規模なデータを扱うに
は、可変長配列は不適当だと考える。やはり大規模行列や 2 次元格子上の数値データは、malloc()等を使って動的に確保し、ポインターを使って扱うのが適当であろう。

C++ が利用可能な場合、Eigen7 のようなベクトル、行列を扱うためのクラス・ライブラリィが色々利用可能であるので、C++ に乗り換えるのが良いかもしれない。

7

http://eigen.tuxfamily.org

23

3.12.2

prog12.c行列のサイズが分かっていて、それが小さい場合は 2 次元配列で簡単に扱える。
/** prog12.c --- 2 次元配列で行列を*/#include <stdio.h>void display(double A[2][2]){int i, j;for (i = 0; i < 2; i++) {for (j = 0; j < 2; j++)printf("%7.2f ", A[i][j]);printf("\n");}}int main(void){
int i, j;double a[2][2] = {{1,2},{3,4}};double b[2][2], c[2][2];/* b の入力 */for (i = 0; i < 2; i++)for (j = 0; j < 2; j++) {printf("b[%d][%d]=", i, j);scanf("%lf", &b[i][j]);}/* c:=a+b */for (i = 0; i < 2; i++)for (j = 0; j < 2; j++)c[i][j] = a[i][j] + b[i][j];/* a,b,c を表示 */printf("a=\n"); display(a);printf("b=\n"); display(b);printf("c=\n"); display(c);return 0;}

prog12 の実行結果

oyabun% gcc prog12.c
oyabun% ./a.out
b[0][0]=4
b[0][1]=3
b[1][0]=2
b[1][1]=1
a=
1.00
2.00
3.00
4.00
b=
4.00
3.00
2.00
1.00
c=
5.00
5.00
5.00
5.00
oyabun%24

以下は初めて学ぶときには省略しても構わない。理解するには、各プログラムを印刷してじっく
り読み比べることを勧める。

3.12.3

prog12a.c行列のサイズが分かっていなくても、サイズが小さい場合は、可変長の 2 次元配列で簡単に扱える。
/** prog12a-new.c --- C99 の可変長配列で行列を*/#include <stdio.h>/* 行列を表示する --- 整合配列風の引数渡し */void display(int m, int n, double A[m][n]){int i, j;for (i = 0; i < m; i++) {for (j = 0; j < n; j++)printf("%7.2f ", A[i][j]);printf("\n");}}int main(void){
int i, j, k, m, n;/* サイズを決定してから行列を記憶する変数を定義する */printf("m= "); scanf("%d", &m);
n = m;
double a[m][n], b[m][n], c[m][n]; // 注: m*n が大きいと異常終了する/* a の値の設定 */k = 0;for (i = 0; i < m; i++)for (j = 0; j < n; j++)a[i][j] = k++;/* b の入力 */for (i = 0; i < m; i++)for (j = 0; j < n; j++) {printf("b[%d][%d]=", i, j);scanf("%lf", &b[i][j]);}/* c:=a+b */for (i = 0; i < m; i++)for (j = 0; j < n; j++)c[i][j] = a[i][j] + b[i][j];/* a,b,c を表示 */printf("a=\n"); display(m, n, a);printf("b=\n"); display(m, n, b);
printf("c=\n"); display(m, n, c);return 0;
}

25





$ ./prog12a-new
m= 2
b[0][0]=1
b[0][1]=2
b[1][0]=3
b[1][1]=4
a=
0.00
1.00
2.00
3.00
b=
1.00
2.00
3.00
4.00
c=
1.00
3.00
5.00
7.00
$



3.12.4

prog12b.cプログラムの実行中にサイズを選べるようにするには、行列を double へのポインターのポイン
ターとして表現し、malloc() を使って動的にメモリーを確保することになるが、手順はやや複雑になる。ポインターに慣れていないと難しいかもしれない。
/** prog12b.c --- ポインターのポインターで行列を実現*/#include <stdio.h>#include <stdlib.h>/* double へのポインターを vector, vector へのポインターを matrix とする */typedef double *vector;
typedef vector *matrix;/* matrix データを表示する */void display(int m, int n, matrix A){
int i, j;for (i = 0; i < m; i++) {for (j = 0; j < n; j++)printf("%7.2f ", A[i][j]);printf("\n");}}/* 行数、列数を指定して matrix を確保 (エラー・チェックつき) */matrix new_matrix(int m, int n){
int i;
vector ap;
matrix a;if ((a = malloc(sizeof(vector) * m)) == NULL) {return NULL;}

26

if ((ap = malloc(sizeof(double) * (m * n))) == NULL) {free(a);return NULL;}
for (i = 0; i < m; i++)a[i] = ap + i * n;return a;}/* matrix データを解放 */void free_matrix(matrix a){free(a[0]);free(a);}int main(void){
int i, j, m, n;matrix a, b, c;
m = 2; n = 2;
a = new_matrix(m, n);b = new_matrix(m, n);c = new_matrix(m, n);if (a == NULL || b == NULL || c == NULL) {fprintf(stderr, "メモリーの確保に失敗しました。\n");exit(1);}/* a の値の設定 */a[0][0] = 1; a[0][1] = 2;a[1][0] = 3; a[1][1] = 4;/* b の入力 */for (i = 0; i < m; i++)for (j = 0; j < n; j++) {printf("b[%d][%d]=", i, j);scanf("%lf", &b[i][j]);}/* c:=a+b */for (i = 0; i < m; i++)for (j = 0; j < n; j++)c[i][j] = a[i][j] + b[i][j];/* a,b,c を表示 */printf("a=\n"); display(m, n, a);printf("b=\n"); display(m, n, b);
printf("c=\n"); display(m, n, c);return 0;
}

4

練習問題
これまで卒研など色々な機会に練習用の問題を出してきた。

27

4.1

比較的簡単なもの[1] 自然数 n を入力されたとき、1 + 2 + · · · + n を計算して、その結果を表示するプログラムを
書け8 。
[2] 実数 a, 自然数 n を入力されたとき、an を計算して、その結果を表示するプログラムを書け9 。
[3] 自然数 k, n を入力されたとき、1k , 2k , · · · , nk を表示するプログラムを書け10 。
[4] a1 = 1, an+1 = 3an + 2 (n ≥ 1) で定まる数列の最初の 100 項を計算して表示するプログラムを書け (n が大きいときは近似値で構わない — 正確に計算するのは面倒である)。
[5] a0 = 1, a1 = 1, an+2 = an+1 + an (n ≥ 0) で定まる数列 (フィボナッチ数列) {an } に対して、
n = 1, 2, . . . , 100 の順に an と an /an−1 を計算して表示せよ。(n が大きいときは近似値で構わない — すべて正確に計算するには、多倍長計算ライブラリィを使うなどの工夫が必要になる。)[番外] an /an−1 の極限は何か？ (簡単な数学の問題)[それでも尋ねてみるか] どの n まで an が正しく計算出来ているか?
[6] n を与えられたとき
S1 (n) :=

n
∑
1
k=1

k

,

S2 (n) :=

n
∑
1
k=1

k2

を計算するプログラムを書け。計算結果は double の精度一杯まで表示せよ。
[番外] lim S2 (n) = π 2 /6 であることが知られている。点 (n, π 2 /6 − S2 (n)) を両側対数目盛でn→∞プロットすることによって、収束の速さを調べよ。

[7] 自然数 n を入力されたとき、Sn :=は漸化式で計算するのが簡単)。

n
∑
1

k!
k=0

を計算して結果を表示するプログラムを書け (1/k!

[8] 関数 f : [a, b] → R のグラフを描くプログラムを書け。ただし f を計算する関数をプログラム
中に書くものとする。特に(0 ≤ x ≤ 1/2)
 x
f (x) :=
1 − x (1/2 ≤ x ≤ 1)

 0
(それ以外)の場合に −0.2 ≤ x ≤ 1.2 の範囲のグラフを描け。

[9] (ここまでの応用) 与えられた自然数 n, 実数 x に対して
sn (x) :=n∑(−1)k+1
k=1(2k − 1)!

x2k−1を計算する関数を書き、n = 1, 2, 3, · · · , 10 に対して sn (x) (0 ≤ x ≤ 2π) のグラフを描け(「Taylor 級数のプログラミングには漸化式を」)。

8もちろん n(n + 1)/2 を計算すれば簡単なわけだが、繰り返しの練習なので、正直に 1 から n まで足すこと。
もちろん冪乗を計算する pow() を使えば簡単だが、繰り返しの練習なので…
10
for を二重に用いるプログラムを書こう。
928

[10] R3 における極座標をデカルト座標 (直交座標) に変換するコードは簡単で、

x = r * sin(theta) * cos(phi);y = r * sin(theta) * sin(phi);z = r * cos(theta);

とすれば良い。この逆をする (つまりデカルト座標を極座標に変換する) 関数 d2p() を作って、動くことをチェックせよ11 。
d2p(x, y, z, &r, &theta, &phi);のようにして呼び出せるようにすること。

[11] 常微分方程式の初期値問題を Euler 法、Runge-Kutta 法などの数値解法で近似的に解けることは重要である。以下の初期値問題を解くプログラムを作れ。結果を可視化せよ。
(1) x′ (t) = x(t), x(0) = 1.
x(1) = e となるはずだが、計算で得た値と比較せよ。
(2) x′′ (t) = −g − γx′ (t), x(0) = H, x′ (0) = 0. ただし g > 0, γ ≥ 0 は定数とする。
これは速度に比例する空気抵抗が存在する場合の自由落下を記述する微分方程式である。
実は (抵抗の大きさが速さの自乗に比例する) x′′ (t) = −g − γ|x′ (t)|x′ (t) が正しいという説
もある。
( ) (
)( )
d x
a b
x
=
, x(0) = x0 , y(0) = y0 . ここで a, b, c, d は与えられた定数であ
(3)
dt y
c d
y
る。さまざまな (x0 , y0 ) に対して解軌道を描け。
[12] 方程式 cos x − x = 0 の実数解を、二分法または Newton 法で計算するプログラムを作れ。
(f (x) := cos x − x は狭義単調減少で、f (0) = 1 > 0, f (1) = cos 1 − 1 < 0 であるから、区間(0, 1) にただ 1 つの実数解を持つことが分かる。)

4.2

ベクトルの扱いの練習(ここは大規模工事が必要なので…参考程度に)[5] 長さ n の配列で n 次元のベクトルを表現することができる。n 次元ベクトル ⃗a = (a1 , a2 , · · · , an )の最大値ノルム∥⃗a∥∞ := max |ai |
i=1,2,··· ,nを計算する関数を作ることを目標にする。手始めに、標準入力から入力された 3 次元ベクトルの最大値ノルムを計算する、次のようなプログラムを書き始めた。関数 maxnorm0() を完成させよ。

11

ちょっと短かすぎて悪い名前だが、Decartes 座標 to Polar 座標、のニュアンス。

29





#include <stdio.h>#define N 3
int main(void){
int i;
double a[N], maxnorm0();for (i = 0; i < N; i++)scanf("%lf", &a[i]);printf("maxnorm=%g\n", maxnorm0(a));}
double maxnorm0(....




(関数に配列を引数として渡す方法、最大値の計算法など)[6] (前問の続き) ベクトルの長さ n は、プログラムの実行時に変更したいと考えて、次のようなプログラムを書き始めた。関数 maxnorm() を完成させよ。


#include <stdio.h>#define MAXN 1000
int main(void){
int i, n;double a[MAXN], maxnorm();
printf("input n (<= %d): ", MAXN); scanf("%d", &n);
if (n > MAXN)
exit(1);for (i = 0; i < n; i++)scanf("%lf", &a[i]);printf("maxnorm=%g\n", maxnorm(a, n));}double maxnorm(....


できれば maxnorm() の定義は別のファイルに書いて、分割コンパイルできるようにする。
(再利用可能性の高い関数を書く方法など)[7] 前問のプログラムでは MAXN の定義を書き換えれば、大きな次元のベクトルに対応するプログ
ラムに変更できるが、それでもプログラムに変更が必要なのは面倒である。また、大きな MAXNをあまり大きくすると、無駄である。そこで、ベクトルの長さ n をプログラムの実行時に指定できるようなプログラムが望ましい。これは次のように malloc() を使うように書き換えれ
ば良い。

30





#include <stdio.h>#include <stdlib.h>
int main(void){
int i, n;double *a, maxnorm();
printf("input n: "); scanf("%d", &n);
a = malloc(n * sizeof(double));if (a == NULL) {fprintf(stderr, "メモリーが確保できません\n");exit(1);}/* 以下、プログラムの字面は前問とまったく同じで OK */for (i = 0; i < n; i++)scanf("%lf", &a[i]);printf("maxnorm=%g\n", maxnorm(a, n));}double maxnorm(....


(C++ を勉強しているならば、同じことを C++ で書いてみよ。)
長さ n のベクトルを標準入力から二つ読み込んで、それらの和、内積、ユークリッド・ノル
ムと、二つのベクトルのなす角度を計算するプログラムを作れ。後で、利用できるように、
addvector(), dotproduct(), norm(), angle() のような関数を作ること。
[8] ある理由から
a[0], a[1], a[2], · · · , a[100]の両端 a[0], a[100] を除いた

a[1], a[2], · · · , a[99]の絶対値の最大値を知りたいとする。maxnorm() を使って求めるには、どうすればよいか。

A

関数へのポインターのプログラム例「これくらいは覚えよう」というものではないかもしれないけれど…

A.1

比較的ありがちな汎関数の定義∫ bf (x) dx は、関数 f を変数にしている、いわゆる汎関数である。というわけで、I(f, a, b) :=a

数値積分公式のプログラミングなどでは、関数を引数にするのが自然である。次は台形公式により数値積分を行う、関数 trapezoidal() のプログラム例である。

31





/*
* ex1.c --- 1/x の [1,2] における定積分を複合台形則で計算する*/#include <stdio.h>#include <math.h>typedef double rrfunction(double);
rrfunction f;
double trapezoidal(rrfunction, double, double, int);
int main(){int N;
double I, T;
I = log(2.0);
printf("台形則\n");printf("N
近似値
for (N = 1; N <= (1 << 16); N *= 2) {T = trapezoidal(f, 1.0, 2.0, N);
printf("%5d\t%25.18f\t%e\n", N, T, I - T);}
return 0;

誤差\n");}
double trapezoidal(rrfunction f, double a, double b, int N){
int j;
double h, T;
h = (b - a) / N;
T = (f(a) + f(b)) / 2;
for (j = 1; j < N; j++)T += f(a + j * h);T *= h;return T;}
double f(double x){
return 1 / x;
}


A.2



コマンドを登録して呼び出す

関数へのポインターを変数に代入しておいて、その変数を用いて呼び出す、ということが出来ます。

32





#include <stdio.h>/* 引数を取らない void 型の関数の型を vvfunc と名づける */typedef void vvfunc(void);/* vvfunc へのポインタ変数 a を定義 */vvfunc *a = NULL;
void f(void) { printf("Hello\n"); }void g(void) { printf("Bye\n"); }int main(){a = f;a();a = g;
a();return 0;
}






bash-3.2$ cc -o prog1 prog1.cbash-3.2$ ./prog1
Hello
Bye
bash-3.2$

関数へのポインターを配列に収めておくことも出来ます。

#include <stdio.h>/* 引数を取らない void 型の関数の型を vvfunc と名づける */typedef void vvfunc(void);void f(void) { printf("Hello\n"); }void g(void) { printf("Bye\n"); }int n = 0;
vvfunc *commands[10];void register_command(vvfunc *F){
commands[n++] = F;}void all_commands(void){
int i;for (i = 0; i < n; i++)commands[i]();}int main(){register_command(f);register_command(g);all_commands();
return 0;
}




33





oyabun% cc -o prog2 prog2.c
oyabun% ./prog2
Hello
Bye
oyabun%

工夫すると、個々の関数に何かキー (ここでは文字) を対応させておいて、そのキーで関数を呼び出すことが出来ます。ここまで出来ると色々応用が効きます。


#include <stdio.h>/* 引数を取らない void 型の関数の型を vvfunc と名づける */typedef void vvfunc(void);void f(void) { printf("Hello\n"); }void g(void) { printf("Bye\n"); }/* --------------------------------------------------- */int n = 0;
vvfunc *commands[10];char keys[10];void register_command(vvfunc *F, char c){keys[n] = c;commands[n++] = F;}void command(char c){
int i;for (i = 0; i < n; i++)if (c == keys[i]) {commands[i]();return;}}/* --------------------------------------------------- */int main(){register_command(f, ’H’);register_command(g, ’B’);command(’H’);command(’B’);
return 0;
}






oyabun% cc -o prog3 prog3.c
oyabun% ./prog3
Hello
Bye
oyabun%34