1998 年度「計算機基礎論3B」 1998-12-04

はじめに

きょうからプログラミングを始めます。 最後に、前回の課題に関連した注意があります。

プログラミング

既存のコマンドを使ってコンピュータを動かす方法はすでに学びました。 プロンプトの出ているところでその名前(それに加えて必要ならば引数も) を打ち込めばいいのでした。 また、 シェルスクリプトを書けば既存のコマンドを組み合わせて実行することができました。

しかし、 自分でコマンドを作れれば、 コンピュータをより自由に動かすことができます。 それが「プログラミング」です。 数学科の学生であるみなさんはすでに論理的・分析的にものごとを考える訓練ができているので、 容易にプログラミングを学ぶことができると思います。

コマンドはコンピュータが読んで理解し実行するものなので、 人間が読んでもよくわかりません。 たとえば「mule /usr/bin/ls」とすれば ls の中味が見えますが、 まず内容はわかりません。 そのようなファイルを「バイナリファイル」と呼ぶときがあります。 それに対し、 いままで習った「テキストファイル」は人間が読んで理解できるようなものでした。

シェルスクリプトではないコマンドはバイナリファイルであり、 人間が読むものではありませんから、直接これを書くことは困難です。 そこで、「ソースファイル」と呼ばれるテキストファイルに命令を書いておき、 それをバイナリファイルであるところの「実行ファイル」に翻訳してやります。 この「実行ファイル」は前に述べた「コマンド」と同様に実行することができます。

 ※ 「コマンド」と言われるとすでにできあがって定番になっているプログラムのような気がする。 「プログラム」というのはソースファイルや実行ファイルのこと。厳密には何を指すのかよくわからない。

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

よって、プログラミングのやり方をまとめるとこうなります。

  1. エディタを使ってソースファイルを作成する。
  2. コンパイラでそれをコンパイルする。成功すれば次へ、失敗すれば最初に戻る。
  3. 実行ファイルを動かしてみる。成功すれば完成、失敗すれば最初に戻る。

この実習では、ソースファイルは「C言語」で書くことにします。 それを「Cコンパイラ」で翻訳して実行ファイルを作るわけです。

すると、次に学ぶべきことは

  1. C言語のソースファイルの書き方
  2. Cコンパイラはどうやって動かすか
の二つです。

Cコンパイラ gcc の使い方

C言語のソースファイルは「hello.c」のように最後に「.c」をつけるのが標準的です。 ただし、mule はそのようなファイルに対し 「これはC言語のソースファイルだぞ」 と判断しインデント(後述)に関して特別扱いをするようです。 扱いにくいと思う人は、 別の名前でエディットしコンパイラにかける前に改名するとか、 シェルスクリプトを作るとか、工夫してみてください。

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

最初のCプログラム

以下のプログラム例で、 最初と最後の「===」の行はプログラムには含まれません。 プログラムの最初と最後をはっきりさせるためのものです。 また、これらのプログラムはサブディレクトリ ~iwase/prog に置いてあります。 「cp -r ~iwase/prog .」でカレントディレクトリの下にディレクトリごとコピーできます。 (「-r」は「そのディレクトリの中味を再帰的に」の意味のオプション。)

== 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)」といっても、 その意味は数学の関数よりは「機能」に近いでしょう。

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

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

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

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

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

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

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

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

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

練習問題

上の hello.c と同じ内容のファイルを自分のディレクトリに作り、 コンパイルしてください。 エラーになった人はどこが違うかよく調べてソースファイルを訂正し、 成功するまでくり返すこと。 実行ファイルができたら、実行してみてください。

C言語の参考書

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

この本はC言語の開発者によるもので、 これ一冊読めば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 型のほうが、表わせる数の範囲が広くなり、 精度もよくなる。(コンパイラによっては等しいこともある。)

浮動小数点型を使ったプログラム例はすぐあとに出てくる。

この実習では int と 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 に等しかったら」という意味ではない。 「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 を使うには math.h の include が必要である。 また、コメントにもつけておいたように、「-lm」をつけてコンパイルする必要がある。

このように使える関数をいくつかあげておく。x や y は double で、 計算結果も double である。

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

いろいろやってみよう

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

 ※ もしも

    printf("...
          ...");
のように "..." の中に改行を入れるとコンパイラを通らない。 この場合は、画面上で二行以上になってもいいからとにかく一行に printf 文をおさめるか、 あるいは "..." を短く切り分けて複数の printf 文で出力するようにする。

「4分の3ぐらいまできたら改行する」というのは普通の文章を書く場合のことであって、 プログラムを書いているときはそうもいかない場合があるのである。

日本語コードの混在したファイルと mule, nkf について

mule は、 JIS 漢字の行と日本語 EUC の行が混在したファイルを読み込んだ場合でも、 すべての行がきちんと見えるように表示する (i.e. 画面に書くときは正しく日本語 EUC に変換する)ことに気づきました。

そのファイルをエディットしてセーブした場合は全部日本語 EUC になるみたいです。

nkf も、JIS 漢字の行とと日本語 EUC の行が混在したファイルを読み込んだ場合、 各行ごとにどのコードかを判断してコード変換を行なうようです。

とにかく、JIS 漢字以外の間違った日本語コードでメールを送らないよう気をつけてください。


岩瀬順一 <iwase@math.s.kanazawa-u.ac.jp>