2000 年度「計算機基礎論3B」 2000-11-10

課題1が終わっていない人はそちらに取り組んでください。

課題1が終わった人は、 ホームページを書くときに使った <P> ... </P> だとかディレクトリの話だとかはもうこの実習では使いませんから、 忘れてしまっても構いません。

プログラミング

コンピュータに慣れてきたところで、 いよいよプログラミングについて学びましょう。 数学科の学生であるみなさんはすでに論理的・ 分析的にものごとを考える訓練ができているので、 プログラミングを学ぶことはそれほど困難ではないと思います。

コンピュータが読んで実行するファイルは「実行ファイル」と呼ばれますが、 これらは 0 と 1 の並びで、人間が読んでもよくわかりません。

※ cat するとわけがわからないものが画面に出てなかなか止まらないことがある。 やめたほうがいい。

このような実行ファイルを人間が書くことは困難ですので、 「ソースファイル」と呼ばれるファイルに 「プログラム言語」で命令を書いておき、 それを実行ファイルに翻訳してやります。

この翻訳をすることを 「コンパイル(compile)する」と言います。 コンパイルは、 「コンパイラ(compiler)」と呼ばれるプログラムがやってくれます。

プログラム言語にはいろいろありますが、 この実習では、ソースファイルは「C言語」で書くことにします。 C言語で書かれたソースファイルから実行ファイルへの翻訳を行なうのは 「Cコンパイラ」と呼ばれるプログラムです。

よって、次に学ぶべきことは

の二つです。

C言語の参考書

C言語の教科書としては B.W.カーニハン/D.M.リッチー著・石田晴久訳 「プログラミング言語 C  第 2 版 ANSI 規格準拠」(共立出版株式会社) が定番です。 略称を「K&R2」といいます。 ちょっと前に「訳書訂正版」が出ました。

この本はC言語の開発者によるもので、 これ一冊読めばC言語のすべてがわかるといってもいいすぎではありません。 私も、 プログラムを書くときはこの本の原書をわきに置いてときどき参考にしています。 訳書は翻訳にやや難があると思います。

なぜC言語を選んだか

この節は興味のない人は飛ばしても構いません。

いくつもあるプログラム言語の中から、 なぜC言語を選んだかを説明しておきます。

まず第一に、私が全貌を把握していて教えられるのはC言語だけだから。

また、unix 自体がC言語で書かれており、 unix のコマンドのほとんどはC言語で書かれています。 このように、C言語はいろいろなことができる、本格的な、汎用の言語です。 一方、C言語はよく規範化されています。 ANSI C という規格があって、 それに準拠したコンパイラが出まわっています。 その規格に合うように書いたプログラムはそれらのどのコンパイラでもコンパイルできます。 同じプログラムが unix でも Windows(あるいは DOS)でも Mac でも動くわけです。 K&R2 はこの規格に準拠した教科書です。

また、C言語のあとから出てきた C++, Java, JavaScript などの言語は、 Cの構文などをかなりまねしています。 それだけCはスタンダードになっている、ということです。 もしもみなさんがそれらに乗りかえる場合でも、 C言語をやっておくことは有利だと思います。

私の知っているかぎりでほかの言語についても書いておきます。 Fortran は数値計算では伝統があります。 この分野ではいままでに書かれた大量のプログラムが利用できるのが強みでしょう。 ひとむかしまえは教育用には Pascal, 実用にはC言語、と言われたこともあるようですが、 Cが Pascal に対してひけをとっていた点は ANSI C で解決したといえると思います。 細かいことですが、 Pascal は割る数×商+余りが割られる数に等しくないことがあります。 BASIC は、よく工夫された処理系もあるようですが、 処理系ごとにまちまちという感じがします。

Cコンパイラ gcc

C言語のソースファイルは 「hello.c」のように最後に 「.c」をつけるのが標準的です。

ソースファイル hello.c をコンパイルするなら、 「gcc hello.c」とします。 実行ファイルは a.out という名前で作られます。 元から a.out があればその内容は失われます。 「a.out」とすればそれが実行されます。 必要なら mv で a.out 以外の名前に変更しても (この実習の範囲では)実行ファイルの動作に変わりはありません。

よって、以下のプログラミング実習の手順をまとめるとこうなります。

  1. テキストエディタでソースファイル …….c を作成する。
  2. Cコンパイラ gcc でソースファイルをコンパイル。 成功すれば次へ。失敗したら最初へ戻る。
  3. 実行ファイル a.out を動かす。 成功すれば終わり。失敗したら最初へ戻る。
プログラミングが一発で成功することは極めてまれです。 根気よくやりましょう。

最初のCプログラム

以下のプログラム例で、 最初と最後の「===」の行はプログラムには含まれません。 プログラムの最初と最後をはっきりさせるためのものです。

※ 全角文字の「¥」(円マーク)と「\」(バックスラッシュ)は別物だが、 これらに対応する半角文字はコンピュータの世界では同じものと扱われる。 キーボードの刻印、打ったときに画面に出る文字、どちらが出ても同じである。

※ 「/」(スラッシュ)と「\」(バックスラッシュ) は全角でも半角でも別物なので間違わないように。 右利きの人にとって書きやすいほうが「スラッシュ」、 それと反対なのが「バックスラッシュ」、 と覚えるとよい。

== hello.c =================================================
#include <stdio.h>

main() {
    printf("hello, world\n");
}
============================================================

1行目。ほとんどのプログラムはこれを書きます。 「stdio.h という名前のヘッダファイルをここで include せよ」という命令ですが、 今日はこれ以上説明しません。「決まり文句」と思っておいて結構です。

2行目。#include <...> の次は一行あけると見やすいです。

3行目。ここから main 関数が始まります。 行末の開き中カッコ「{」は5行目の先頭で閉じており、 4行目を囲んでいます。

4行目。これがプログラムの本体です。 この行は行頭に4つのスペースをおいています。 それは人間にとってプログラムを読みやすいものにするためです。 このような「字下げ」を「インデント(indent)」とも言います。

printf は画面出力の関数です。 「関数(function)」といっても、 その意味するところは数学の関数より「機能」に近いでしょう。

※ 「print」ではなく最後に f がついている。注意。

出力されるものは、printf の後ろの小カッコの中にある「" "」で囲まれた部分、 すなわち「hello, world\n」です。 そのうち、「hello, world」は文字通り出力されますが、 最後の「\n」は二文字で「改行」を表わす約束です。 「\」と「n」ではありません。

よって、 ここは「hello, world と画面に出力してそのあと改行せよ」という意味になります。

最後のセミコロン「;」はCにおける文の終わりの印です。 必ずつける必要があります。

※ 上の例のような、 一見奇妙な中カッコのつけ方およびインデントについて説明しておく。 実はこれを

   main() { printf(...); }
と書いても
   main()
   { printf(...); }
と書いてもよいのである。

しかし、長年の経験により、 最初にあげたスタイルが最も使いやすいスタイルの一つであることがわかっている。 よって、 すでに自分でC言語を使っていて自分なりのスタイルを確立している場合を除いて、 まずはこのスタイルをまねしてみることをすすめる。

このスタイルをすすめる理由の一つは、 プログラム本文を増やす場合に printf の次の行に行単位で文を加えるだけですむことである。 下の二つのスタイルだと、「;」と「}」の間に文を加えなくてはならない。 一般に、エディット作業は行単位のほうが楽なのである。

また、中カッコが複雑に入れ子になった場合でも、 どの開きカッコとどの閉じカッコが対応しているのかを見るのが容易である。 そのような例はそのうち出てくる。

なお、ここですすめているスタイルは、 普通のCの教科書に出ているスタイルとも少々違うが、 私はこれがベストと確信しているので、これを使うことにした。

練習

hello.c という名前のファイルで上のサンプルと同じ内容をもつものを作りましょう。 コピー&ペーストしても、 見ながら自分で打ち込んでもいいでしょう。 できたら「gcc hello.c」としてそれをコンパイルしてみてください。 何も出力されなければ成功、 何かが出力されればエラーです。 エラーになった場合はどこが違うかよく調べてソースファイルを訂正し、 成功するまでくり返すこと。 実行ファイルができたら「a.out」として実行してください。

次に、別の言葉(「hello, 自分の名前」とか) が出力されるプログラムを書いてみましょう。

※ 「書いてみましょう」といっても、 ソースファイルを書いて終わりではない。 書いて、コンパイルして、自分の思うように動いて、 それで初めて「書いた」といえるのだ。

※ その別のプログラムを作る際には hello.c を書き換えてもよいし、 hello.c をコピーしたファイルを書き換えてもよい。 そのあたりは各自で考えること。

簡単な計算を行なうCプログラム

これから、だんだんと複雑な計算を行なうプログラムを書いてゆきますが、 いずれも、数学的にはほとんど自明なものばかりです。 自明だからこそ、 数学的に計算したものと比べることでプログラムが正しかったことがわかります。

== tasizan.c ===============================================
    /* たし算  1990-04-28, written by Iwase */

#include <stdio.h>

main() {
    int a, b, sum;              /* 変数の宣言 */

    a = 2;                      /* 代入 */
    b = 3;
    sum = a + b;

    printf("%d たす %d は %d です.\n", a, b, sum);
}
============================================================

1行目。 「/*」と「*/」とで囲まれた部分はコメントです。 何を書いてもコンパイルしてできる実行ファイルには関係ありませんので、 自由にメモを書くことができます。 ここでは、何をするプログラムか、いつ誰が作ったかを書いています。

「変数の宣言」の行。 変数を使う場合、前もって「宣言」しておく必要があります。 ここでは「変数 a, b, sum は int 型」と宣言しています。 変数名には小文字を使うのが普通です。 「int 型」は「整数型」の一つで、この名前は整数 integer に由来しています。

「代入」の行。 「a = 2」は、「a は 2 に等しい」という平叙文ではなく、 「a に 2 を代入せよ」という命令文です。 その下の2行も同様です。 プログラムは特に指定しなければ上から下へ実行されるので、 sum = a + b; を実行したあとの sum の値は 5 になります。

printf 文の行。 記号「%d」は、順に後ろの変数の値に置き換わって画面に出力されます。 ここでは、 一つ目の %d は a の値で、 二つ目の %d は b の値で、 三つ目の %d は sum の値で置き換わります。

よって、出力は「2 たす 3 は 5 です.」のあと改行、となります。

※ int 型は整数を表わすが、 コンピュータの世界は有限なのですべての整数を表わせるわけではない。 gcc における各整数型の変数が表わせる整数の範囲を次に示す。

整数型とその範囲
型名最小最大
int -21474836482147483647
short -32768 32767
long -21474836482147483647

よって、普段は int を使えばよいだろう。

※ ほかに、float 型、double 型、long double 型という型がある。 これらは 3.141592 のような小数を表わす型であり、 まとめて「浮動小数点型」と呼ばれる。 float 型よりも double 型のほうが、 double 型よりも long double 型のほうが、表わせる数の範囲が広くなり、 精度もよくなるが、コンパイラによっては等しいこともある。 浮動小数点型を使ったプログラム例はすぐあとに出てくる。

※ %d の代わりに別のものを書くと数の出力法が変わる。 代表的なものをいくつか K&R2 などから引用しておく。

int 型変数を出力する場合
%d 十進整数として出力
%6d 十進整数として出力、少なくとも6文字幅に

double 型変数を出力する場合
%f 浮動小数点数として出力
%6f 浮動小数点数として出力、少なくとも6文字幅に
%.2f 浮動小数点数として出力、小数点の後は2桁
%6.2f 浮動小数点数として出力、少なくとも6文字幅で、 小数点の後は2桁

※ 演算子についてまとめておく。 ベキ乗を表わす演算子はC言語にはないことに注意。

+たし算
-ひき算 
*かけ算 
/わり算(int では小数点以下切り捨て)
%余り(例:10 % 3 は 1)

※ 演算の優先順位は数学の場合と同じ。必要ならば小カッコ「( )」を使う。 中カッコ「{ }」、大カッコ「[ ]」をこの目的に使ってはならない。

※ 「-」は c = -b/a のように「符号を変える」の意味でも使える。

== tasizan2.c =================================================================
#include <stdio.h>

main() {
    int a, b;

    printf("数を入れてください.\n");
    scanf("%d", &a);                                  /* 数の入力 */
    printf("もう一つ数を入れてください.\n");
    scanf("%d", &b);
    printf("%d たす %d は %d です.\n", a, b, a+b);
}
===============================================================================

「数の入力」の行。 ここでプログラムは止まってキーボードからの入力を待ちます。 変数名 a の前に「&」がついて &a となっていることに注意してください。 この「&」の意味は(たぶん)この実習では説明しません。

分岐を含むCプログラム

== warizan.c ==================================================================
#include <stdio.h>

main() {
    double a, b;

    printf("数を入力してください.\n");
    scanf("%lf", &a);                           /* %lf に注意!*/
    printf("もう一つ数を入力してください.\n");
    scanf("%lf", &b);
    if (b == 0) {                               /* if による分岐 */
        printf("0 では割れません.\n");
    } else {
        printf("%f 割る %f は %f です.\n", a, b, a/b);
    }
}
===============================================================================

「%lf に注意!」の行。 double 型に値を入力するときは「%lf」(エル、エフ)と書きます。

「if による分岐」の行からの数行。

    if (条件1) {
        文1
    } else {
        文2
    }
は次のように動作します。 条件1をまずチェック。 それが真ならば文1を実行し、文2は飛ばして先へ進む。 条件1が偽ならば文1は飛ばし、文2を実行して先へ進む。

    if (条件1) {
        文1
    } else if (条件2) {
        文2
    } else if (条件3) {
        文3
    } else {
        文4
    }
となっていたら次のように動作します。 条件1が真ならば文1だけを実行する。 条件1が偽で条件2が真なら文2だけを実行する。 条件1も条件2も偽で条件3が真ならば文3だけを実行する。 条件1も条件2も条件3も偽なら文4だけを実行する。 if の数がいくつであっても、文のうち一つだけが実行されます。

条件1が真ならば文1を実行し、 偽ならば何も行わない、という場合は

    if (条件1) {
        文1
    }
となります。

※ if (...) の中で使える記号は、数学上の記号と少々異なる。

C言語での記号<><=>===!=
数学上の記号

「==」(等号二つ)には特に注意すること。 「=」は代入の記号だったので比較の意味では使えない。 「if (b = 0)」も文法的には正しいのでコンパイラはメッセージを出さないが、 「もし b が 0 に等しかったら」という意味ではない。

他のプログラム言語では「=<」「=>」 と書いても「≦」「≧」の意味になることがあるが、 C言語はそうではない。

「if (a+b < c+d)」のように if の小カッコ内で計算をすることもできる。

※ 入出力についてまとめておく。

入力出力
int scanf("%d", &a); printf("%d", a);
double scanf("%lf", &a) printf("%f", a);

いろいろな数学関数

== sine.c =====================================================================
    /* 「gcc sine.c -lm」としてコンパイルすること */
#include <stdio.h>
#include <math.h>   /* sin */

#define PI 3.14159265

main() {
    printf("%d 度の正弦は %f です.\n", 30, sin(30*PI/180));
}
===============================================================================

「#define ...」の行は、 「以下で PI が出てきたら 3.14159265 で置き換えろ」という意味です。 ここでは PI は一回しか出てきていないのであまり意味がありませんが、 何度も出てくるときはこの方法を使うとタイピングの量が減るし、 間違いも起こりにくくなります。 それに、 「なぜここでこの定数が出てくるか」がプログラムを読む人にわかりやすくなります。 このような定数は大文字で書く習慣です。

※ 関数 sin を使うには、 プログラムの3行目にあるように、math.h の include が必要である。 また、コメントにもつけておいたように、 「-lm」(エル、エム)をつけてコンパイルする必要がある。 このように使える関数をいくつかあげておく。x や y は double で、 計算結果も double である。

※ 3行目の「/* sin */」というコメントは、 関数 sin を使うから math.h を include したんだぞ、という意味でつけてある。

いろいろやってみよう

ここまでに習ったことを組み合わせると、 「2×2行列の積」「2×2行列の逆行列」 「二元の連立一次方程式を解く」「二次方程式を解く」 「等差数列の初項と公差、第何項目かを入力するとその項の値を出力する」 「等比数列の初項と公比、第何項目かを入力するとその項の値を出力する」 などのプログラムが作れるはずです。興味のある人は どんどんやってみましょう。

※ もしも

    printf("...
          ...");
のように "..." の中に改行を入れるとコンパイラを通らない。 この場合は、画面上で二行以上になってもいいからとにかく一行に printf 文をおさめるか、 あるいは "..." を短く切り分けて複数の printf 文で出力するようにする。 「 日本語なら30文字ぐらい、アルファベットなら60文字ぐらいまできたら改行する」 というのは普通の文章を書く場合のことであって、 プログラムを書いているときはそうもいかない場合がある。

※ 直前のコマンドを繰り返すときは「!!」でよい。 覚えておくとプログラムが正しく動くかどうかのチェックの際などに便利だ。


岩瀬順一