2023 年度「数学特論」 2023-12-13

§7.0 バッチファイル

Windows の場合。 たとえば cc.bat という名前で 「gcc -Wall -Wextra %1.c -o %1.exe」という内容のファイルを作っておき、 hello.c をコンパイルする際は「cc hello」とすると、 実際には 「gcc -Wall -Wextra hello.c -o hello.exe」 が実行され、 できた実行ファイルは a.exe ではなく hello.exe となる。 実行するには「hello」と打ち込めばよろしい。

この、拡張子 .bat をもつファイルをパッチファイルと言う。

-Wall-Wextragcc の「オプション」で、 これをつけると、つけない場合よりも多くの警告を出してくれる。 -o もオプションで、できあがる実行ファイルの名前を指定するもの。

Linux の場合はバッチファイルとは呼ばない。 名前は自由で、内容は 「gcc -Wall -Wextra $1.c -o $1」とする。 使い方などは担当教員に尋ねられたい。

§7.1 大文字に変換するプログラム(その1)

(7.1.1)

#include <stdio.h>
#include <ctype.h>

int main() {
  int c;

  while ((c = getchar()) != EOF) {
    putchar(toupper(c));
  }
}

(7.1.2) 上のプログラムは、小文字は大文字に変換し、それ以外の文字はそのまま出力する。 関数 int toupper(int c) はそのための関数である。 これを使うには ctype.h#include が必要である。

(7.1.3) このプログラムに「」を入力すると、別の漢字に変わる。 これは、この文字のコードの 2 バイト目が、ASCII の小文字と同じコードだからである。 §6.3 参照。その意味では、このプログラムはアルファベット専用である。

§7.2 課題1

§7.1 のプログラムを元に、小文字に変換するプログラムを書け。 使う関数は int tolower(int c) である。

レポートの本文冒頭に、氏名を、なるべく大学に届けるある通りの文字で書け。 次に、プログラムソースファイルを、添付ファイルではなく、本文に貼りつけよ。 それから、このプログラムを使って、大文字が小文字になると確かめた実験結果、 および、(いわゆる)日本語でうまくゆかない例を貼れ。

件名は「??? kadai1」(←全て半角文字、小文字、 ??? は自分の履修者番号、その後ろに半角スペース一つ、 kadai1 の間にはスペースを入れない)とせよ。

§7.3 入出力リダイレクト

(7.3.1) これは、C 言語の機能ではなく、Windows のソフトの一つであるところの、 コマンドプロンプトの機能である。 Linux の端末でも、ほぼ同様のことができる。

(7.3.2) いままで「a」と打ち込んで実行ファイルを動かしていたが、 「a > file.txt」と打ち込んで実行すると、 「a」では画面に出力されていたものが、 ファイル file.txt に収まる。 ただし、もともとファイル file.txt があれば、 その内容は消えてしまう。 これを「出力リダイレクト」という。

実験:§7.1 の実行ファイルで試してみよ。 (注意:大切なファイルを失わないように。 たとえば、これらの実験に使うファイルの名前は x.txt, y.txt, z.txt に限る、 と自分で決めておくなど。)

(7.3.3) 「a >> file.txt」と打ち込んで実行すると、 「a」では画面に出力されていたものが、 ファイル file.txt の最後に追加される。 ファイル file.txt が存在しなければ、 「a > file.txt」と同じである。 これも「出力リダイレクト」という。

実験:同様に試してみよ。

(7.3.3) 「a < file.txt」と打ち込んで実行すると、 ファイル file.txt の内容をキーボードからの入力の代わりとする。 これを「入力リダイレクト」という。

実験:同様に試してみよ。file.txt としては、 いままで書いてきた C 言語のソースファイルでもよい。 (不等号の向きを間違えないこと! 心配な人はソースファイルのコピーを使うとよい。)

(7.3.4) 「a < file.txt > file2.txt」あるいは「a < file.txt >> file2.txt」と、 入力リダイレクトと出力リダイレクトを組み合わせて使うことも可能である。

こうすると、 §7.1 のプログラムは、ファイルの中のアルファベットを大文字に変換するプログラム、 として使えることになる。 ただし、(いわゆる)日本語が混じっていると予期せぬ結果になることもある。

(7.3.5) 実験:上の例で file1.txt が Unicode で書かれていると、 このプログラムは(いわゆる)日本語におかしな影響を起こさないようである。 それを試すには、適当なテキストファイルを作り、そこに適当な(いわゆる)日本語を貼る。 このページからコピペすればよいだろう。アルファベットも含むようにすること。 そして、右下の UTF-8 を ANSI に変えずに保存する。 それから「a < file.txt > file2.txt」を実行。 file2.txt を開いて、(いわゆる)日本語が変わっていないことを確かめる。 あるいは、Windows の、ファイル比較プログラム fc を使ってもよい。 「fc file.txt file2.txt」のように使う。

§7.4 パイプ

(7.4.1) これも、C 言語の機能ではなく、Windows のソフトの一つであるところの、 コマンドプロンプトの機能である。 Linux の端末でも、ほぼ同様のことができる。

(7.4.2) 「a | b」として実行すると、 プログラム a.exe の出力がプログラム b.exe の入力となる。 これを「パイプ」という。

実験: まず、Windows にも Linux にも備えつけられている、sort を試せ。 このプログラムは、入力行をアルファベット順にソートして出力する。 次に、§7.1 あるいは §7.2 で作ったプログラム a.exesort とをパイプでつないで、 すなわち「a | sort」または「sort | a」を実行してみよ。 何をしたことになるか?

(7.4.3) パイプは入出力リダイレクトと合わせても使える。 「a < file1.txt | b > file2.txt」など。 パイプを二つつなぐことも可能である。 「a | b | c」など。

§7.5 入出力リダイレクトの使いみち

次のプログラムはつまらないサンプルである。 起動後、「2.5 3.3」のように入力すると 2.5 x + 3.3 = 0 の解 -1.320000 を出力する。 これをくり返す。「0 2」のように、最初の数が 0 だと終了する。

#include <stdio.h>

int main() {
  double a, b;

  while (1) {                 /* こう書くと「永遠に繰り返せ」の意味になる */
    scanf("%lf %lf", &a, &b);
    if (a == 0) {
      break;                  /* この break は「それを囲む最小の for や while から脱出せよ」の意味 */
    }
    printf("%f\n", -b/a);     /* ax + b = 0 の解の値(だけ)を出力する */
  }
}

ここの gcc で上のファイルをコンパイルし、 キーボードから「2.5, 3.3」と数値をカンマで区切って打ち込むと、 (理由はよくわからないが)無限ループに陥る。

このように使いにくいプログラムであるが、入力リダイレクトを利用することが考えられる。 入力データ

2.5 3.3
4.6 -8.7
0 0

を内容とするファイル file.txt をメモ帳で作り、 「a < file.txt」と実行するのである。 file.txt の内容に間違いがなければ、プログラムは正しく動くはずである。

(もしも file.txt の内容が

2.5 3.3
4.6 -8.7

と間違っていると、無限ループに陥る。)

実際には、次のような使い方が考えられる。 プログラム a.exe はシミュレーションをおこなうもので、 動かすのに一晩かかり、画面に数値で結果を出力するものとする。 プログラム b.exea.exe の結果を入力するとそれを (この授業ではできないが)画像で見やすく表示するものとする。 そして b.exe は一瞬で動くが、 パラメータを変えて何度も実行し、最も見やすいパラメータを選びたい、 と仮定する。 このときは、「a > file.txt」を一晩かけて実行し、 「b < file.txt」を何度も実行すればよい。 (a.exeb.exe とを一つにまとめたプログラムが書けるかもしれないが、 それは動かすのに一晩かかることに注意。)

§7.6 大文字に変換するプログラム(その2)

(7.6.1) Shift_JIS の(いわゆる)日本語の文字は、 第一バイトめが十六進で 81 から 9f までまたは e0 から ef まで、である。 それを利用すると、(7.1.3) で述べた不具合を避けることができる。 十六進の 81 は C 言語では 0x81 と書く。 「&&」は二つの条件を「かつ」で結び、 「||」は二つの条件を「または」で結ぶのだった。 「&&」は「||」よりも優先順位が高いので、 下のプログラムで合っているが、「-Wall」をつけてコンパイルすると、 間違っていませんか、と警告が出る。

#include <stdio.h>
#include <ctype.h>

int main() {
  int c;

  while ((c = getchar()) != EOF) {
    if (c >= 0x81 && c <= 0x9f || c >= 0xe0 && c <= 0xef) {     /* Shift_JIS 第一バイトめなら */
      putchar(c);               /* そのまま出力 */
      putchar(getchar());       /* 次のバイトもそのまま出力 */
    } else {
      putchar(toupper(c));
    }
  }
}

§7.7 行数を数えるプログラム

(7.7.1) キーボードから打ち込んだものの行数を数えることはあまりありそうにないが、 入力リダイレクトを利用し、ファイルの行数を数えるのは使いみちがありそうだ。 さしあたっては、\n を数えればよい。

#include <stdio.h>

int main() {
  int nl, c;

  nl = 0;
  while ((c = getchar()) != EOF) {
    if (c == '\n') {
      nl++;
    }
  }
  printf("%d 行ありました.\n", nl);
}

(7.7.2) ただ、これには問題がある。 ファイルの中には、最後が改行で終わっていないものがあるからだ。 作って、試してみよ。 上のプログラムでは最後の 1 行がカウントされない。

(7.7.3) 次は、そこを改良するため、 最後に入力された文字を変数 c2 に入れるというアイディアで書いたもの。

#include <stdio.h>

int main() {
  int nl, c, c2;

  nl = 0;
  /* ??? */
  while ((c = getchar()) != EOF) {
    c2 = c;
    if (c == '\n') {
      nl++;
    }
  }
  if (c2 != '\n') {
    nl++;
  }
  printf("%d 行ありました.\n", nl);
}

(7.7.4) これでも、ファイルが空、すなわちサイズ 0 のときうまくゆかない。 /* ??? */ と書いたところに何か書き込むとうまくゆくようになる。 変数 c2 の初期化である。考えてみよ。

§7.8 行の長さを出力するプログラム

#include <stdio.h>

int main() {
  int c, len;

  len = 0;
  while ((c = getchar()) != EOF) {
    if (c == '\n') {
      printf("%d\n", len);
      len = 0;
    } else {
      len++;
    }
  }
}

最終行が改行で終わっていないとうまくゆかない。

§7.9 最も長い行の長さを出力するプログラム

#include <stdio.h>

int main() {
  int c, len, longest;

  len = longest = 0;
  while ((c = getchar()) != EOF) {
    if (c == '\n') {
      if (len > longest) {
        longest = len;
      }
      len = 0;
    } else {
      len++;
    }
  }
  printf("一番長い行は %d 文字です.\n", longest);
}

これも、最終行が改行で終わっていないとうまくゆかない。

§7.10 単語の数を数えるプログラム

Kernighan & Ritchie の The C Programming Language, Second Edition (通称 K&R2)の 1.5.4 のプログラムをさらに簡単にしたもの。 この本は、むずかしくない英語で書かれているので、 訳書ではなく原文で読まれることをお勧めする。 これ一冊を読み通せば、(当時の)C 言語をマスターしたと言ってよい。

単語の間に複数のスペースを置く場合があるので、 単にスペースと改行を数えるだけではうまくゆかない。

#include <stdio.h>

#define IN  1           /* 単語の中 */
#define OUT 0           /* 単語の外 */

int main() {
  int c, count, state;

  count = 0;
  state = OUT;
  while ((c = getchar()) != EOF) {
    if (c == ' ' || c == '\n') {
      state = OUT;
    } else {
      if (state == OUT) {
        state = IN;
        count++;
      } else {
        ;
      }
    }
  }
  printf("%d 語ありました.\n", count);
}

岩瀬順一