プログラミング演習1 :: 課題3

平成16年6/17, 24, 7/1.レポート提出〆切:7/8.
担当:趙(9-301)

ひと休み::一回目

● Cのバージョン

Cは,1970年代初期にBell LabsのDennis Ritchieによって設計されたプログラミング言語である.最初はUNIXを記述するのが目的だったが,その後UNIXと一緒にオープン化され,世界あらゆるところまで使われ(ちょっと大げさ),いままで最もポピュラーのプログラミング言語となった.

誕生から1980年代初期までは所謂早期Cの時代だった.当時C標準がなく,コンパイラが仕様を決めることになっていた.これでは困るので,標準化が進められ,1978年に,Brian KernighanとDennis Ritchieの名作"The C Programming Language"(和訳「プログラミング言語C」)が出版され,早期Cの事実標準として使われていた(K&R Cと呼ばれる).1989年に,"標準"の意味においてはじめての標準,ANSI C(米国標準)ができた.ANSI CがほぼそのままISO/IEC C(世界標準)となり,日本でも採用されJIS C(日本標準)となった.これはよくC89,あるいはANSI Cと呼ばれている."The C Programming Language"の第二版(1988年.ANSI Cが公表される一年前)はこのバージョンに準処している.演習も主にこれを勉強する.

C89はその後ISO/IECによって改訂され,1995年にC95(C89 with Amendment 1とも呼ばれるが)となった.そして1999年に大幅の改訂版としてC99が誕生した.さらにC99は,2003年に小規模ながら改訂が行なわれた(やはりC99と呼ばれている.2000年問題を避けたいため?).

バージョンによって仕様が変わるところがあるので,時々コンパイラの対応状況を調べる必要が出てくる.そのための方法は次回で触れる.ちなみに現時点のMicrosoft Visual C/C++はC89に対応しているが,C95やC99に対応していない.一方,FCLKが収録したgccはC99に対応している(まだ完全ではない).

● 複合代入演算子や,++や,--等の演算子について,煩わしいと思うことがない?

これにはわけがある.実行速度に関して,"x++"をコンピュータの指令に直訳すると"x=x+1"より(ハードウェア的に)速いことがあったから(他同様).もちろんプログラムが簡略になっていることもあるので,長年間使われてきた(普通の意味では読みにくくもなっているが).後発のJavaでもこれらを採用している.今は,コンパイラの最適化等によって当初の理由(スピード)がほとんどなくなったし,煩わしいと思ったら使わないでよい.分りやすいプログラムを作るには使わないほうがいい(特にCプログラマーでない人が見る場合).

● 再帰呼び出しの原理

講義データ構造とアルゴリズムに出ているあのデータ構造"スタック"を使って実現している.則ち,関数が呼び出された時点の状態をスタックで保持するわけ.再帰呼び出しは,プログラムは簡単になるが,普通効率が悪い(必要のない値まで保存しているといったオーバヘッドがある).そのため,しばしば(速度を上げるため)自前でスタックを実装して再帰呼び出しをなくす必要がある.

☆ フィボナッチ数を計算する再帰呼び出しプログラムを自前のスタックで実装してみよう.トライした人は是非報告して下さい.

因にフィボナッチ数を計算するだけなら,再帰呼び出しを使う必要がない.反復制御だけを使うほうがはるかに効率良い.再帰呼び出しの真価は,後日クィックソートを勉強するときに見ることができる.

● コマンドラインって何に使うの?

コマンドラインについて疑問を持つ人が少くない.ずっとマウスクリックに慣れてきたWindowsな人にとっては当然.ここで,よくコンピュータで仕事をするなら多くの場合はコマンドラインのほうが効率良いということを伝えておこう.以下少し紹介する.

コンピュータ初期の時代に,マウスもグラフィカルなディスプレーもなかった.当時の人々は,文字ベースのディスプレー(端末,WindowsのコマンドプロンプトまたはDOS窓のようなもの)を見ながら,キーボードを叩いていた.それがコマンドラインの始まりだった.当然ながら,当時の人々も単調作業が嫌いだから,いろいろ工夫をして仕事効率を高めようとしていた(だから「コマンドラインが効率良い」と言えるわけ).今回,wildcardとパイプを見てみよう.

まずコマンドプロンプトを出しておく.C:\Windows\System32のようなたくさんのファイルがあるフォルダに移る(cdコマンドで).

Wildcard

コマンド dir で現在のフォルダにあるファイルのリストを見てみよう.これはエクスプローラから見たのと同じであろう.これだけなら面白くない.

では,dir *.exe はどう? 拡張子がexeのファイルが全てリストされたよね.また,dir e*.* なら,eで始まるファイルがリストされる.でもやっぱりエクスプローラで拡張子またはファイル名で並べばなんとかできるものだなぁ.

では,dir ?e*.* はいかが? これは,二番目の文字がeであるファイルをリストするコマンドなのだが,エクスプローラではできない.

実は,?や*は,コマンドラインでマッチするファイル名に置き換えられるマジック("wildcard"という)なのだ.?は一文字,*は0個以上の文字にマッチする.コマンド dir は,それを受け取って適切のファイルに展開してリストしてくれる.それと同様に,コマンド copy (コピーする)にも,del (削除)にも使える.例えば,拡張子 tmp のファイルを削除するには,del *.tmp を実行すればよい.del ?e*.* は二番目の文字がeであるファイルを削除する.

練習問題:del slide-*-*.jpg はなにをするの? (因にコマンドは コマンド名 /? で使い方が分る.例えば,del /?)

パイプ

パイプ(pipe)は,コマンドラインのもうひとつ強力な機能.

標準入出力をファイルで入れ換えるリダイレクトはすでにデバッガの時に見た.パイプによって,標準入出力を別プログラムの出入力で入れ換えることもできる.例:最大公約数を求めるプログラムを gcd,フィボナッチ数を求めるプログラムを fibonacci としよう.ただし,二つとも標準入力から入力を読み込み,結果を標準出力に送るとする.gcdの出力をfibonacciの入力にするには,gcd | fibonacci でよい(| はパイプのことを示す).パイプによって異なるプログラムの組み合わせが簡単になり,既存のプログラムを使ってより大きな仕事ができるようになる.(それでもWindowsのコマンドプロンプトはUNIXのに較べ非常に貧乏であるが.)

コマンドラインでの操作はよくCUIといい,対してグラフィカルの操作はGUIという.両方共ユーザからの入力を持って作業するのであるが,一般に,CUIの特長は,ユーザが使い方を覚えていると仮定し,複雑の機能を実現できる.一方GUIは,ユーザが提示された情報に従うから入力できる範囲が限定される.CUIは修得に時間がかかるが,よくコンピュータで仕事をする人にやさしい.一方GUIはユーザ入力を限定してしまうが,初心者に優しい.(GUIでなくてはならない処理を除いて)現状では,GUIはユーザに提示できる情報が多いが,ユーザができることが少いという情况にある.

ひと休み::ニ回目

Cバージョンのテスト

前に述べたように,Cの標準はいくつかのバージョンが存在している.プログラミングをする時,しばしばコンパイラの対応情况を調べる必要が出てくるから,ここで前処理を利用した調べ方を示す.

ISO C対応のコンパイラは,__STDC__を定義する義務がある.さらにC95以後のバージョンに対応するものは,__STDC_VERSION__を定義しなければならない.具体的に,以下のプログラムのようにコンパイラの対応状況を調べることができる.

/* コンパイラのISO C対応情况を調べるプログラム */
#include <stdio.h>

int main(void) {
#ifdef __STDC__
#if defined(__STDC_VERSION__) && __STDC_VERSION__>=199901L
  /* C99以後に対応している */
  printf("コンパイラは,C99に対応している(バージョン:%ld).\n", __STDC_VERSION__);
#elif defined(__STDC_VERSION__) && __STDC_VERSION__>=199409L
  /* C95に対応しているが,C99に対応していない */
  printf("コンパイラは,C95に対応している(バージョン:%ld).\n", __STDC_VERSION__);
#else
  /* C89だけに対応している */
  printf("コンパイラは,C89に対応している.\n");
#endif
#else
  /* ISO Cに対応していない */
  printf("コンパイラは,ISO Cの標準に対応していない.\n");
#endif
}

なお,FCLKに収録しているgccは,オプション -ansi または -std=c99 で ANSI C または C99 対応となるようにコンパイルしてくれるが,デフォールトはgcc専用モード(つまりISO C準処ではないモード)となる.

ひと休み::三回目

中間変数を使わない値交換

整列に当たりデータを交換する必要がある.普通こんな感じ(ただし,tmp,a[i]とa[j]はint型とする):

tmp  = a[j];
a[j] = a[i];
a[i] = tmp;
で行っている.質問は,中間変数のtmpがなしに,a[i]とa[j]だけで交換することが可能なのか?

答えは可能.ポイントは,交換したい対象は数値であること.下記の回答を見る前に,一度考えてみよう.

昨年の授業中にTAの中島圭輔君(現在応数M2)がこのことを教えてくれた(当時びっくりした.気がつかなかったので ^_^).それを載せると,

a[i] = a[i] - a[j];
a[j] = a[i] + a[j];
a[i] = a[j] - a[i];
という感じ.もちろん a[i]=a[i]+a[j]; a[j]=a[i]-a[j]; a[i]=a[i]-a[j]; もOK.

おもしろいと思わない?ただし,問題点もあるので,考えてみよう.

ifを使わないで最大値を計算する

Q: if文を使わないで,入力の二つint数の最大値を出力する C プログラムを書け.

これもおもしろい.因みにぼくは大学二年生の時,およそ10分で答えを見つけた.あなたは?


0)ホーム,1)一回目,2)二回目,3)三回目 Last modified: Wed Jun 30 18:07:51 JST 2004