C言語基礎講座 1.C 言語 C 言語は,最初 UNIX というオペレーティングシステム(計算機の動作を管理する基本ソフト)を作成する ための言語として,1970 年代初頭に Ken Thompson, Dennis Richie らによって作成され,その後改良されな がら利用者を増やしているコンパイラ言語です. C 言語はほとんどのコンピュータシステム用のコンパイラが存在するため,C 言語でプログラムを作ると, 多くのコンピュータで利用することができるようになります.また,単にオペレーティングシステムを記述 するという目的以外にも,計算処理,事務処理等様々なタイプの処理を記述することができる汎用的な言語 にもなっています. C 言語が広く用いられている理由はいろいろありますが,その主な点をあげます. (1)表現が簡潔に記述できる. (2)プログラムの処理の流れを制御する構文が豊富に用意されている. (3)様々なデータの構造を記述する構文が整備されている. (4)演算の種類が豊富にある. (5)アセンブラで記述するような,入出力装置とのやり取りのような動作の記述も可能. (6)ほとんどの計算機上にコンパイラがあり,移植性の高いプログラムが記述できる. (7)ディバッグ用のツール等が豊富に用意されている. 2. 簡単なプログラム 簡単な C 言語のプログラムを実際に作成し,これをコンパイルして機械語のプログラムに変換し,最後に実 行してみましょう. 2.1 作成 プログラムの作成というのは,コンピュータに行わせたい処理を,プログラミング言語を用いて記述する ということです.今回は VC++のコンソールアプリで行います。 1)Microsoft Visual Studio.NET を立ち上げ、新規プロジェクト(test)を作成する。 test.cpp に次のプログラムを入力して下さい. #include "stdafx.h" #include <stdio.h> void main(void) { printf("Hello\n World\n"); } 2.2 コンパイル ビルドメニューより以下の処理を行いますと、コンパイラとリンクが自動的に実行されます。 2.3 実行 作成した実行形式のプログラムを実行してみましょう. うまく" Hello” と ”World"という2行が画面に出れば成功です. プログラムにミスがあり,コンパイルしたときにエラーメッセージが表示されたら,このメッセージをよ く読んで,エディタでプログラムを修正します.たいてい,スペルのミスか,ダブルクォーテーションが抜 けている等といった単純な誤りです. 2.4 プログラムの解説 ここで,このプログラムについて解説しましょう.もう一度プログラムを見て下さい. 1 #include "stdafx.h" 2 #include <stdio.h> 3 void main(void) 4 { 5 printf("Hello\n World\n"); 6 } ここで,先頭の行番号は説明のために付けたものですから,実際のプログラムには含まれません. 1∼2行目は,VC.NET の標準的な入出力に関するプログラムを作成する際に,プログラムの様々な約束事 をまとめたヘッダファイルと呼ばれるファイルです.VC.NET で C 言語のプログラムを作成する場合、とりあ えず,そういう約束事になっているのだと思って下さい. 3行目から6行目は,main という名前の関数を定義しています.C 言語のプログラムというのは,関数か ら構成されています.関数というのは,何か入力を与え(これを入力パラメータといいます),処理を実行 して,それに対する戻り値を得るものです.ここで入力パラメータというのは,関数に対する入力という意 味で,キーボードからの入力では無いことに注意して下さい.キーボードからの入力は,処理にあたります. また,必ずしもすべての関数がこの入力パラメータを備えていたり値を戻す必要はありません.入力パラメ ータが無い関数や,戻り値が無い関数,また入力パラメータも戻り値も無い関数もあります. 関数の定義は,次のような構成になっています. 戻り値の型 関数名(入力パラメータの型 入力パラメータ名) { 処理内容 } ここで型というのは,入力パラメータや戻り値が整数,実数,文字等のどんな形式であるのかということ を指定します.また,1つの関数で戻り値は最高で1つしかありませんが,入力パラメータは複数与えるこ とができます.その場合は,関数名の後ろの括弧の中に,カンマ(,)で分けて入力パラメータを並べていきま す.この型については,3 章でもう一度説明します. 関数の入力パラメータや戻り値が無い場合は,void と書きます.例えば戻り値が無い関数は,戻り値の型 の部分に void と書きます.また,入力パラメータが無い関数は,関数名の後ろの()の中に void と書きま す. 今回の例では,入力パラメータは無く,戻り値は整数(int というのは integer の意味)です.また,この main という関数は,必ず必要です.プログラムが実行されるときには,この main という関数から実行が始 まります.main という関数の戻り値は,ANSI 標準規格というC言語の規格の中で整数値とするよう定められ ています.なお,関数については12章で詳しく述べます. 関数定義に続いて,この関数の処理内容を中括弧({ })で囲んだ中に書きます.今回の例では,処理はただ 一行, printf("Hello\n World\n"); という文だけです. この printf というのは,標準で備わっている関数です.この関数は,入力パラメータで指定されたものを, 画面に表示しなさいというものです.この例では,入力パラメータは"Hello\n World\n"です.ダブルクォー テーション(")で囲まれてたこのパラメータは,文字列を表しています.この文字列中に2箇所ある\n は, ここで改行しなさいという意味を持つ制御符号です. 処理を表す文の終りにはセミコロン(;)を書きます. この処理の部分には,複数の文を書くこともできます.また,各文の後にセミコロンを付けるのを忘れな いかぎり,各文を同じ行に書いてもいいし,異なる行に書いてもかまいません.この例の printf 文は,次の ように複数の文に分けて書いても,その結果はまったく同じです. 例1 printf("Hello\n"); printf("World\n"); 例2 printf("Hell"); printf("o\nWorld\n"); また,各行のはじめにどのくらい空白を入れるかも自由です.適当に空白を入れて,見やすいプログラムを 書くよう心がけて下さい. 3. 変数・計算 3.1 変数名,データ型,宣言 プログラムで処理を実行しているときに,様々な数値や文字を覚えておきたいときがあります.例えば, キーボードから入力された数値を覚えておきたいとか,入力される都度,これまでに入力された値の合計を 集計しておきたいとか,入力個数をカウントしておきたいとかです.こういう覚えのために,変数というも のを使います. 変数というのは,数値や文字といったデータを保持しておく箱を計算機の中に作り,その箱に名前を付け たものだと考えて下さい.箱の名前を変数名といいます.例えば,整数を覚えておく変数を3つ作り,それ ぞれに a,b,c という変数名を付けると,図 3-1 のようになります. 図 3-1 変数 a,b,c 実際には,計算機のメモリに各変数毎に領域が割り当てられ,その領域にデータが保持されます.ですか ら,この箱という概念はあながち外れてはいないのです. 箱はあらかじめ保持するデータの形式が表 3-1 のように決まっています.ただし,数値の範囲は計算機に よって多少異なります. 表 3-1 主な変数のデータ形式 データ形式 箱に入る数値の範囲 宣言時のデータ型 整数 -32768∼32767 int 倍精度整数 -2147483648∼2147483647 long int 実数 1.0+38∼1.0-38 float 倍精度実数 1.0+308∼1.0-307 double float 文字 英数字,記号 char 変数名は次の規則に従って自由に付けることができます. 変数名の命名規則 一文字目が英字(a∼z, A∼Z)かアンダーライン(_)で,2文字目以降は英数字(a∼z, A∼Z, 0∼9)か アンダーライン(_)でなければならない. 名前の長さは 31 文字以内. C 言語であらかじめ使うことが予約されている名前(予約語,int, float, printf 等)は使えない. 英字の小文字大文字は区別される. 変数を使用するときには,最初にその変数名とデータ形式を宣言して箱を用意しなければなりません.宣 言するときには,表 3-1 の最後の項目(データ型)の名称を使います. プログラムを理解しやすくするた めに、変数名には、その機能をよく表わし、ほかの機能と混同しないもの、また誰からもわかりやすい名前 を付けるようにしましょう。 例えば,整数で val1 という名前の変数を使うプログラムは,次のようになります. 3 void main(void) 4 { 5 int val1; 6 処理 7 ・ 8 ・ 9 } (先頭の行番号は説明のために付けたものですから,実際のプログラムには含まれません.以後のプログラム も同様です) 4 行目にあるのが,変数の宣言です.同じ整数型で val2, val3 という変数も使うときには,4 行目を int val1, val2, val3; とカンマで区切って並べることができます.または,その都度 int val1; int val2; int val3; というように分けて書いても同じです.いずれにしても,各文の最後にセミコロン(;)を付けるのを忘れない でください. 3.2 計算−代入記号 変数に値を代入するには代入記号(=)を使います. 例えば, int a,b; a=3; b=5; と書くと,整数型の変数 a,b の箱にそれぞれ 3 と5の値が入ります.つまり,代入記号(=)の右側に書かれた 値を,左側に書かれた変数に代入するわけです. この記号は,C 言語では等号記号として扱われないことに注意して下さい.このことの意味はすぐにわか ります. 3.3 計算−算術記号 整数,実数型の変数を使って,様々な計算を行うことができます.計算には,算術記号(足し算,引き算と いった算術計算を行う記号 +-等)を用います. 主な算術記号を表 3-2 に示します. 表 3-2 主な算術記号 意味 記号 例 加算 + 3 + 5 a + 4 a + b 減算 - 3 - 5 a - 4 a - b 乗算 * 3 * 5 a * 5 a * b 除算 / 3 / 5 a / 5 a / b 除算の余り % 3 % 5 a % 5 a % b 例えば,3 + 5 は計算結果として 8 という値が求まります.a + 4 は,a という変数の箱に現在入っている 値に 4 を足した値が求まります.a + b は,a と b という変数の箱に現在それぞれ入っている値を足した値が 求まります. 乗算は,×という記号がキーボードに無いので代りにアスタリクス(*)を使います. 整数同士の計算では,求まる値も整数になります.このとき注意しなければならないのは除算で,結果が 割り切れないときには小数点以下が切り捨てられます.例えば 3/5 で求まるのは 0 です.なお,5/0 のよう に除数が 0 の計算をすると,エラーになります. 除算の余りというのは,整数同士の計算でのみ意味があります.例えば,3 % 5 では 3 が求まります.5 % 3 では 2 が求まります. 3.4 計算−演算順序 算術記号を用いた計算を組み合わせて,さらに複雑な式を書くことができます. 例えば, 3 + 5 * a 2 * a / b a * 7 + b * 9 のようにです.このとき,計算の順序は,常識に従います.つまり,乗除算は加減算に優先して行われ,乗 除算同士,加減算同士では左から右へと計算が実行されます.剰余の優先順序は乗除算と同じです. また,優先順序を制御するために,括弧を付けることもできます.例えば, (3 + 5) * a 2 * (a / b) a * (7 + b) * 9 のようにです. 3.5 計算−再び代入記号 3.2 で紹介した代入記号の使い方では,右辺は単なる定数ですが,この右辺に 3.3 と 3.4 で紹介した計算 式を書くことができます. 例えば, val1 = (3 + 5) * a; val2 = 2 * (a / b); のようにです. この場合の処理は,次の順序で行われます. 1. 右辺の計算を行う. 2. その結果を左辺の変数に代入する. このルールに従うと,次のような式を書くことができます. a = a + 1; 代入記号を数学でいうところの等号記号(つまり,式の左辺と右辺が等しいということを表している)と 考えると,この式は矛盾した式だということになります. しかし,この考えは間違いです.C 言語では = という記号はあくまでも代入記号です.改めて上記のルー ルに戻ってこの式がどう処理されるのかを見てみましょう. まずルール(1)に従い,式の右辺が計算されます.その結果,現在変数 a の値(変数 a の箱に保存されてい る値)に 1 を加算した値を得ます.そして,この値をルール(2)に従い,変数 a に代入します.つまり,変数 a の箱に保存します. 結局,この式では変数 a の値が1つ増えることになります. 同様に,次の式を考えましょう(ここで,変数 a,b,c は整数型で宣言されているとします). a = a - 3; b = b * 7; c = c / 2; この結果,a の値は3減り,b の値は7倍になり,c の値は半分になります(小数点以下は切り捨てられます). 3.6 計算−特別な演算子 前節で紹介した変数の値を1つ増やすという処理(インクリメント)は,実は何かの回数を数え上げると いった,様々なプログラムでよく用いられる処理です.同様に,a=a-1 という変数の値を1つ減らす処理(デ ィクリメント)もよく用いられます. そこで,C 言語ではこのよく用いられるインクリメント,ディクリメントをもっと短い文字列で記述でき る様に特別な演算子を用意しています. a=a+1; は a++; と書いても同じことです.同様に a=a-1; は, a--; と書いても同じことです. 他の記述を短くする書き方も紹介しましょう. a=a+3; は a+=3; と書いても同じです.この 3 の部分は別に他の数字でも他の変数名でも同じです.同様に,+を-*/に変えて も同じです. 例えば, a=a*b; は a*=b; と書くことができます. 4.画面出力 4.1 printf 文 3 章で学んだ変数を使って四則演算をしてみましょう.次のプログラムを作ります. 1 #include "stdafx.h" 2 #include <stdio.h> 3 void main(void) 4 { 5 int a,b; 6 int wa,sa,seki,jo; 7 a=10; 8 b=25; 9 wa=a+b; 10 sa=a-b; 11 seki=a*b; 12 jo=a/b; 13 printf("add=%d, sub=%d, mul=%d, div=%d\n",wa, sa, seki, jo); 14 } (例によって先頭の行番号は説明のために付けたもので,実際のプログラムには含まれません.) このプログラムでは,wa, sa, seki, jo という4つの変数に,変数 a, b の足し算,引き算,かけ算,わ り算の計算結果がそれぞれ入ります.そして,この4つの変数の値を最後に 12 行目の printf 文で画面に表 示します. この printf 文は,2 章で使った printf 文と同じなのですが,括弧の中に書いた入力パラメータが,カン マ(,)で区切って合計5つある点が異なります.下に各パラメータにそれぞれ下線を付けて示します。 printf("add=%d, sub=%d, mul=%d, div=%d\n", wa, sa, seki, jo); 1つ目のパラメータ("add=%d, sub=%d, mul=%d, div=%d\n")には,2章の printf 文の入力パラメータと 同じ様に,画面に表示したい文字列をダブルクォーテーション( " )で囲んで指定します. この文字列の中に,%d という文字列が見えます.この%で始まる文字列のことを,書式といいます.%d は, 「10進数の整数を表示せよ」という書式を表します.この1つ目のパラメータ中には,この書式の指定が 4個所ありますね.つまり,4個の整数をそれぞれ add=や sub=といった文字の後ろに付けて表示するように 指示されています.この4個の整数というのが,2つ目から5つ目までの4つの入力パラメータで指定され た値です.この結果,画面には次のような表示が出ます. add= 35, sub=-15, mul= 250, div= 0 4.2 書式 この書式は,ここで示した%d 以外にもいろいろあるので,表 4-1 にまとめましょう. 表 4-1 書式 書式 意味 サイズ指定形式 %d 符号付き整数 %4d %f 符号付き実数 %6.3f %c 文字 %s 文字列 %6s %x 符号なし整数で 16 進表示 %4x サイズ指定というのは,値を何文字分の幅で表示するのかを指定するものです.例えば%4d というのは, 整数を4文字分の幅で表示します.4桁よりも少ない桁数の文字は右に詰めて表示されます.実数のサイズ 指定は,%6.3f というように書き,この場合6文字分の幅で,そのうち小数点以下の桁数を3桁にして表示し ます.このサイズ指定は省略が可能で,その場合はシステムが適当にサイズを決定します. 基本的には表 4-1 の書式を覚えていれば十分です。しかし、上記以外にも、さまざまな書式が存在します. printf 文の書式の詳細 書式の一般形 書式の一般形は、次のとおりです。 % - fw . dd l(エル) 変換文字  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ここで % と変換文字は省略できませんし、各要素は上記の順で記述しなければいけません。 % と変換文字の間の下 線を引いた部分は、オプション指示子と呼び、必要に応じて設定できます。オプション指示子には次のものがありま す。 オプション指示子 fw フィールド幅の桁数(自然数の値を指定) 例 - 結果:□□28 (□は空白を示す) (マイナス) fw で指定した桁数内で左詰めで表示 例 . printf("%4d", 28); printf("%-4d", 28); 結果:28□□ (ピリオド) フィールド幅 fw と精度 dd を分割する dd 精度の桁数(0、自然数の値を指定)、フィールド幅 fw の中で、小数点以下 dd 桁まで表示する。次の例では、 小数点以下 2 桁の指定なので、5.956 は小数第 3 位で四捨五入されて 5.96 になる。 例 l printf("%6.2f", 5.956); 結果:□□5.96 (エル) 変換するデータ型が long 整数であることを示す。 例 printf("%10ld", 123456789); 結果:□123456789 変換文字 変換文字には次のものがあります。 d, i 10 進数の符号つき整数に変換する。 u 10 進数の符号なし整数に変換する。 x, X 16 進数の符号なし整数に変換する。x の場合は文字 abcdef を用い、X の場合は文字 ABCDEF を用いて変換す る。 o 8 進数の符号なし整数に変換する。 f [-]ddd.ddd の形式で、10 進数の実数に変換する。 e, E [-]d.ddde+dd の形式で、10 進数の実数に変換する。e の代わりに E を用いると、E を数字につけて指数を 表示する。 c 符号なし文字に変換する。 s 文字列に変換する。 p ポインタの値として変換する。 4.3 制御符号 printf 文の最初のパラメータ中に\n という文字が見えます.これは,ここで復帰改行しなさいという制御 符号です.制御符号にはこの\n 以外にもいろいろあります.制御コードを表 4-2 にまとめます. 表 4-2 制御符号 制御符号 意味 \ a ベル \ b 一文字後退 \ f 改ページ \ n 復帰改行 \ r 復帰(同じ行の左端に移動) \ t 水平タブ \ v 垂直タブ \ \ \自身 \ ' シングルクォーテーション( ' ) \ " ダブルクォーテーション( " ) \ 0 ヌル文字 この制御符号は,printf 文の最初のパラメータ以外にも,文字列中に入れて使用することが出来ます.な お,\という文字は,日本語のキーボードを使っているパソコンでは円記号(¥)を使います. 5 キーボード入力 5.1 scanf 文 4 章のプログラムでは,10 と 25 という2つの整数の四則演算結果を得ることができました.しかし,様々な 整数値に対する四則演算結果を得るためには,6,7 行目の代入文をその都度変更して,コンパイルをやり直 す必要があります 整数値はキーボードから入力して指定できるようにすれば,その都度プログラムを変更する必要は無くな ります.今日は,このキーボードからの値の入力について学びましょう.例えば,変数 a が整数型として宣 言されているとします.そして,次の文を実行してみます. scanf("%d",&a); この文を含んだプログラムをコンパイルして実行すると,この文まで処理が進んだところで処理が止まり, キーボードからの入力待ちになります.ここで,キーボードから値を入力して(この例では整数値を入力) 最後に Enter キーを打つと,入力された値が変数aに入ります.例えば 234 と入力して Enter キーを打つと, 変数 a の箱に 234 が入ります. この scanf 文は,括弧の中に2つの入力パラメータが指定されています.1つ目のパラメータ(この例で は "%d" )は入力される値の書式をダブルクォーテーションで囲んで指定します.この書式は,表 4.1 で説 明したものと同じです.今回の例は書式指定が%d ですから,入力された値を整数値として取り扱うことを示 しています. 2 つ目のパラメータは,入力された値を格納する変数を指定します.ここで,変数名の前に&が付いていま す.変数名の前に&を付けると,変数の箱が計算機のメモリのどこにあるのか,メモリ中の場所を示す番地を 表します.scanf 文では,このパラメータで指定されたメモリに,キーボードから入力された値を格納する のです.ちょっと考え方が厄介ですが,scanf では変数名の前に&を付けるのだということを忘れないで下さ い. 実は scanf では同時に複数の変数にキーボードから入力された値を格納させることができます.例えば, 次の文を見て下さい. scanf("%d %d",&a, &b); この場合,キーボードから2つの整数値を空白を挟んで入力し,最後に Enter キーを押すと,2つの整数値 がそれぞれ変数 a と b に入ります. ◆ 演習5 ◆ Key ボードより a,b の整数データを要求し、 a/b の結果をディスプレイに表示させよ。 * 演習5の回答例 #include "stdafx.h" #include <stdio.h> void main(void) { int a,b,c; printf("aの値を入力してください \n"); scanf("%d",&a); printf("bの値を入力してください \n"); scanf("%d",&b); c=a/b; printf("a / b = %d\n",c); } 6.条件文 6.1 条件文とは何か 5 章のプログラムはうまくできましたか.ここで,ちょっと意地悪質問です.2つの変数 a,b の値のうち, b の値が 0 のとき,プログラムはどうなるでしょうか.実際にやってみて下さい. これは,このプログラムの中にある次の文で発生します. jo=a/b; そう,b の値が 0 なので,0 での除算を実行しようとしてエラーを発生したのですね. そこで,b の値が 0 だったら,この除算を実行しない様に,プログラムを改良する必要が出てきます.つ まり,条件を指定して,その条件が満たされたかどうかで処理内容を変える必要があります. 6.2 if 文 このことを行うのに便利な文として,if 文というのがあります.if 文は次のように書きます. if(条件) 処理①; else 処理②; 条件のところには,例えば「変数 b の値が 0 と等しい」というような,論理を書きます(書き方はすぐ後で 説明します).そして,その論理が真のときに行う処理(処理①)と,偽のときに行う処理(処理②)をそ れぞれ書きます.論理が真のときには,処理①を実行しますが,処理②は実行しません.同様に論理が偽の ときには,処理①は飛ばして処理②を実行します. else と処理②とを省略して,次のように書くことも可能です. if(条件) 処理①; この場合は,条件が真のときには処理①を実行するが,偽のときには処理①を飛ばすということになります. 6.3 複文(ブロック文) 処理①と処理②の部分には,計算式や代入文,printf 文といった文を1つ書きます.ここで,条件が真や 偽のときに複数の文を実行したい(例えばエラーメッセージを printf 文で表示した後,代入文を実行したい 等)ときはどうすればよいのでしょうか.つまり,処理①や②の部分で複数の文を指定したいときです. このようなときには,この複数の文を中括弧({,})で囲みます.すると,この複数の文を中括弧で囲 んだものを複文(またはブロック文)というのですが,この複文は1つの文とみなして,処理①や②のとこ ろに書くことが許されます. 例えば次のように書きます. if(変数 b の値が 0 である) { printf( [Error] ); jo=0; } else jo = a / b; この場合,条件は「変数 b の値が 0 である」という論理式です(条件の書き方はもうすぐ説明します).こ の論理が真(つまり変数 b の値が 0 である)ならば,printf 文でエラーを表示して,除算は実行せずに変数 jo の値を 0 にします.この論理が偽(つまり,変数 b の値が 0 でない)ならば,安心して除算を実行します. この複文ですが,皆さんは既に何回も複文を書いています.そう,いつも main という行の後ろに{と}で囲 んで main 関数の処理内容を書いていますよね.これも複文だったのです. 6.4 条件の書き方 お待たせした条件の論理式の書き方を説明しましょう. 例えば, 先ほどの例の中にある「変数 b の値が 0 である」という論理式は次のように書きます. b == 0 この==というのは,その左辺と右辺に書かれた値(変数ならその変数がいま保持している値)の関係を指定 する関係演算子と呼ばれます.主な関係演算子を表 6.1 に示します. 表 6.1 主な関係演算子 関係演算子 意味 == 左辺と右辺は等しい > 左辺は右辺より大きい >= 左辺は右辺と等しいか大きい < 右辺は左辺より大きい <= 右辺は左辺と等しいか大きい != 左辺と右辺は等しくない この関係演算子を用いて表現された論理式を,さらに表 6.2 に示す論理演算子を用いてより複雑な式にする ことができます. 表 6.2 主な論理演算子 論理演算子 意味 ! 否定(not) && かつ(and) ¦¦ または(or) また,括弧を使って論理演算の順序を変更することもできます. 例えば,「変数 a の値が 0 以上5以下である」という論理式は次のように書けます. (0 <= a ) && (a <= 5) この場合,関係演算子のほうが論理演算子より優先順序が高いので,括弧はなくてもよいのですが,あった ほうがわかりやすいので書きました.論理式の中の優先順序というのはけっこうややこしいので,できるだ け括弧を使って誤解の無いように書く習慣をつけましょう. また,関係演算子の左辺や右辺には,変数や定数以外に計算式を書くこともできます.例えば,「変数 a の値は変数 b の値の 3 倍より大きい」という論理は,次のように書きます. a > b * 3 ◆ 演習6 ◆ キーボードから2つの整数を入力し,その四則演算結果を表示せよ.ただし,除数が 0 であるときにはそ の旨表示して,除算結果は 0 とせよ. #include "stdafx.h" #include <stdio.h> void main(void) { int a,b,c; printf("aの値を入力してください \n"); scanf("%d",&a); printf("bの値を入力してください \n"); scanf("%d",&b); if(b==0) { c=0; }else { c=a/b; } printf("a / b = %d\n",c); } 7.フローチャート 7.1 アルゴリズム if 文を使うと,プログラムの処理の流れが,あるときにはこっちに進み,あるときには別のほうに進みと いうように,だんだん複雑になってきます.そうなってくると,ただプログラムを漫然と眺めていても,処 理の流れがよくつかめないという事態が生じます.そのようなときに,プログラムの処理の流れを図的に表 現してもっと見やすくする方法があれば便利です. コンピュータを用いてある処理をさせるプログラムを作ろうというときには,処理の手順というものを考 えます.たとえば下記例題の四則演算のプログラムでは,次のような処理の手順になります. キーボードから2つの整数を入力し,その四則演算結果を表示せよ.ただし,除数が 0 であるときには その旨表示して,除算結果は 0 とせよ. 1. キーボードから2つの整数を入力させ,それぞれ変数 a,b に入れる. 2. 変数 a,b の値の加算,減算,乗算の結果をそれぞれ変数 wa, sa, seki に入れる. 3. 変数 b の値が0かどうか調べる.0 だったら処理 4 に進み,0 でなければ処理 5 に進む. 4. エラーメッセージを画面に出力し,変数 jo に 0 を入れる.処理 6 に進む. 5. 変数 a,b の除算結果を変数 jo に入れる. 6. 変数 wa,sa,seki,jo の値を画面に出力する. ある目的を正しく行わせるためのこういった処理の流れを,アルゴリズムといいます.上記のアルゴリズ ムは単純なものですが,目的が難しくなると,アルゴリズムも複雑になってきます.例えば,2つの整数の 最大公約数を求めるアルゴリズムは,もっと複雑になるでしょう.このようなアルゴリズムを考えるときに, 処理の流れを図的に表現してみやすくする方法が必要になってきます. ここでは,プログラムの処理の流れを図的に表現するフローチャートというものを紹介します. 7.2 フローチャート 例えば,四則演算プログラムをフローチャートで表すと,次のようになります. 図 7-1 四則演算の結果を表示するアルゴリズム 7.3 処理・判断 フローチャートでは,四角のボックスの中に処理を書きます.if 文のように判断して処理の流れが変わる ところは,ひし形を描き,中に条件を書きます.処理の流れは矢印で表します.簡単ですよね.四角形のボ ックスの中にどのぐらいの処理を詰め込むかは特にルールは無いので,見やすいように工夫してみて下さい. もとのプログラムに比べて,ずいぶん処理の流れが追いやすくなりましたよね. 7.4 処理の流れ 複雑な処理の流れを持つプログラムを作成するときには,このフローチャートを書いて検討してからプロ グラムを打ち始める習慣をつけるといいでしょう.練習として,「キーボードから2つの整数を入力し,そ の大きいほうの値を画面に表示せよ.」というアルゴリズムの処理の流れを考えてみましょう. 私が考えた処理の流れを,図 7-2 に示します. 図 7-2 入力した2つの整数のうち,大きいほうを表示するアルゴリズム ここで,変数 a,b,m はどれも整数型の変数です.キーボードから入力された2つの整数値は,それぞれ変 数 a,b に入ります.そして,この2つの変数のうち,大きいほうの値を変数 m に入れて,最後にこの m の値 を表示します. ◆ 演習7 ◆ キーボードから3つの整数値を入力し,3 つの値のうち真ん中の値を表示せよ.同じ値が 2 つ入力されてい るときには中間の値がないことを表示すること.フローチャートを書いてからプログラムを作ること. 例)1 番目の入力値:4 2 番目の入力値:2 3 番目の入力値:9 → 中間値は4 8.繰り返し文 8.1 はじめに これまで作成してきたプログラムは,どれも処理が一方通行でした.ちょっと分岐はありましたが,処理 が上から下へ流れて終りでした.でも,処理の中には,ある条件が満たされている限り,ある処理を繰り返 し実行したいということがあります. 例えば,九九の5の段を表示するプログラムを考えてみましょう. 最初に 5×1 の結果を表示し,次に 5×2 の結果を,次には 5×3 というように,最後 5×9 の結果を表示する まで,計算して表示するという処理を繰り返します. もう少し具体的に考えましょう.整数型の変数 k,n の2つを使うこととします.n の値を最初は 1 にします.そして,次の処理を実行しては n を 1 増加させ,n の値が9以下である限り繰り返し実行するのです. k=5*n; printf("%d",k); フローチャートでこの部分を書くと,次のようになります. 図 8-1 九九の5の段を表示する処理の流れ このフローチャートが示すように,今回の処理では上から下への矢印だけでなく,下から上へ戻る矢印が あるのが特徴です. 8.2 for 文 このフローチャートで示す処理を実現する,たいへん便利な for 文というものがC言語にはあります.for 文は次のような構成になっています. for(式 1; 論理式 ; 式2){ 処理; } この文により,どのような処理が行われるのかを,フローチャートを使って示します. 図 8-2 for 文の処理の流れ 処理の部分に複数の文を書くのであれば,if 文のときに説明した複文を使います.このフローチャートと 先ほどのフローチャートを対応させて,式1,式2,論理式,処理の部分に何が相当するのかを考えれば, 結局九九の5の段を表示するプログラムを簡単に書くことができます. 8.3 while 文 繰り返しのための文は,for 文以外にも,while 文と do while 文があります.while 文の書き方と処理は 以下の通りです. while(論理式){ 処理; } 図 8-3 while 文の処理の流れ 8.4 do while 文 do while 文の書き方と処理は以下の通りです. do{ 処理; }while(論理式); 図 8-4 do while 文の処理の流れ 8.5 入れ子になった for 文 九九の表について,もう少し話を進めましょう.今のプログラムでは5の段のみを表示するものでしたが, これを改良して1の段から9の段まですべてを表示するようにしたいものです.これは,5の段のプログラ ムにある for 文の処理の中にある, k=5*n; を, k=m*n; とし(ただし,変数 m は整数型として宣言されているとします),まず m を 1 として 1 の段を計算し,次に m を 2 として 2 の段を計算するというように,m の値を1から9まで1ずつ増加させながら,この for 文を実 行し,最後に改行すればいいわけです.「mの値を1から9まで1ずつ増加させながらある処理を実行する」 という部分は,まさに for 文そのものです.つまり,この場合,「変数mの値を1から9まで1ずつ増加さ せながら処理 A を実行する.その後改行する」.そして,この処理 A というのは,「変数 n の値を1から9 まで1ずつ増加させながら,処理 B を実行する」というものです.最後に,処理 B は k=n*m; printf("%d",k); です. この場合,変数 m を用いた for 文の処理の中に,変数 n を用いた for 文が入っているという,入れ子構造 になっています. ◆ 演習8◆ 次のような九九の表を表示するプログラムを for 文を使って作成せよ. 1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 10 15 20 25 30 35 40 45 6 12 18 24 30 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81 9.ファイルとの入出力 これまでのプログラムでは,必要なデータはすべてキーボードから入力していました.しかし,プログラ ムを動かすたびにデータをいちいちキーボードから入力するのは面倒なうえに入力ミスの危険があります. また,様々なデータをフロッピーディスクでもらったり,インターネットを通じて入手できるようになると, 必要なデータは既にファイルに入っている場合も少なくありません.せっかくファイルに入っているのです から,これを改めてキーボードから入力するのではなく,このファイルに入っているデータを直接プログラ ムで読み込めるようにしましょう. また,せっかくプログラムで作成したデータも,ただ画面に表示する だけではそのデータを利用するときに不便です.データをファイルに出力できるようにしましょう. 9.1 概要 例えば,あるクラスの学生の試験点数が,学籍番号順に入っているファイルがあるとします.例えば次のよ うなファイルです. 95 37 78 37 最初の学籍番号の人は 95 点,次の人は 37 点というように入っています.このとき,このクラスの平均点 を求めるプログラムを考えます.平均点を求めるには,クラスの生徒数と総得点を求める必要があります. ここではこの2つの数をそれぞれ num, sum という整数型の変数を使って求めることにします.全体のフロー チャートは次のようになるでしょう. 図 9-1 クラスの平均点を求めるプログラム まず最初に,2つの変数 num, sum の値をゼロにしておきます.そろばんで読み上げられていく値の合計を 求めるときに,「ご和算で願いましては」と最初に言われたら玉をすべてゼロにするのと同じですね.これ から加算していく変数の初期値をセットします. あとは,ファイルのデータを読み切るまでは,順に点数 を読み込んではこの2つの変数を加算していきます.読み込んだデータは,ここではファイルの1人分の内 容を val という名前の整数型の変数に格納するものとしています. ファイルのデータをすべて読み終わったら,平均点を表示して終わります. この処理から,ファイルの入出力を行うために,次の処理が必要であることがわかります. ファイルから1つデータを読み込んで変数に格納する ファイルのデータをすべて読み込んだのかどうかを判断する また,次の処理も必要であることは容易に想像できます. なんという名前のファイルから読み込むのか(または書き込むのか)を指定する ファイルに1つデータを書き込む これから,これらの処理を行うために,C 言語でどのような関数が用意されているのかを順に説明します. 9.2 ファイルポインタ その前に,C 言語でファイルを操作する場合に使用するファイルポインタというものを説明します. 9.1 の例では,ファイルから順にデータを読み込むわけですが,どこまで読み込んだのか,次はどこから読み出 すのかといったことをどこかに覚えておく必要があります.また,ファイルにデータを書き込む場合にも, 次はどこに書き込むのかといったことを覚えておく必要があります. このとき,何という名前のファイル のどの場所といったことを,書き込みや読込みを行う都度にいちいち指定するのは大変です.C 言語ではこ ういった面倒なことを,ファイルポインタというもので簡単に行えるようになっています. プログラムで読み込んだり書き込んだりするファイル毎に1つのファイルポインタを用います.ファイル ポインタは,次のようにして用意することができます. FILE *fp; fp というのがファイルポインタの名前です.名前は変数名の命名規則に従って,自由に付けることができま す.例えば, FILE *input_file; といったようにです. 9.3 何という名前のファイルから読み込むのか(または書き込むのか)を指定する(fopen, fclose) この処理は,結局 9.2 で説明したファイルポインタと実際のファイルとを結び付けるという処理になりま す.例えば,seiseki.dat というファイルに成績データが格納してあって,このファイルを読み込むために ファイルポインタと結び付けるには,fopen という関数を使って,次のように書きます. fp = fopen("seiseki.dat","r"); ただし,fp というのはファイルポインタ名とします. fopen は2つの文字列からなるパラメータを持ちます.最初のパラメータはファイルの名前を指定する文 字列です. 2つ目のパラメータは,そのファイルを読み込み用に指定するのか,書き込み用に指定するの かといったファイルに対する処理方法を指定するのに使う文字列です.このパラメータの指定方法を表 9.1 に示します. 表 9.1 fopen の主な処理方法指定 パラメータ文字 説明 r そのファイルからデータを読み込む.ファイルが 存在しなければエラーになる.r は read の頭文字 を意味する. w そのファイルにデータを書き込む.ファイルが存 在しなければ新規に作成され,存在していれば前 のファイルは消去されて改めて作成される.w は write の頭文字を意味する. < そのファイルにデータを追加書き込みする.ファ イルが存在しなければ新規に作成される.存在し a ていればそのファイルの最後にデータが追加し て書き込まれる. a は append の頭文字を意味する. fopen という関数の戻り値をファイルポインタに代入してやると,以後はこのファイルはファイルポイン タを通して処理されます. 読み込み用にファイルを使用するときには,当たり前ですが既にこのファイルが存在している必要があり ます.もし存在しない場合はエラーとなり,ファイルポインタに NULL という値(通常は 0)が入ります.で すから,例えば次のようにしてエラーのチェックを行う必要があります. fp = fopen("seiseki.dat","r"); if(fp == NULL) { printf("File not found!\n"); exit(1); } なお,exit(1)というのは,プログラムを終了する命令です. fopen 関数を用いてファイルポインタと結 び付けられたファイルに対する処理がすべて終わったら,最後にファイルポインタとの結びつきを開放して あげる必要があります.それを行うのは fclose 関数です.fclose 関数は,入力パラメータとして現在何か のファイルに結び付けられているファイルポインタを指定します.すると,そのファイルとの結びつきは開 放され,このファイルポインタはまた fopen 関数を用いて別のファイルと結び付けることが出来ます. 例えば、ファイルポインタ fp との結びつきを開放する場合には、次のようにします。 fclose(fp); 9.4 ファイルから1つデータを読み込んで変数に格納する(fscanf) ファイルからデータを1つ読み込んで,val という名前の整数型変数に格納するには,次の関数を用いま す. fscanf(fp,"%d",&val); ここで,fp は 9.2 で説明したファイルポインタで,既に fopen 関数で目的のファイルと関連付けられてい るものとします. fscanf 関数は,scanf 関数とよく似ています.scanf 関数がキーボードから入力された データを変数に格納していたのに対して,fscanf 関数は最初のパラメータで指定したファイルポインタに関 連付けられたファイルから,データを入力して変数に格納します.この最初のファイルポインタのパラメー タを除くと,他のパラメータの意味は scanf と同じです. 9.5 ファイルに1つデータを書き込む(fprintf) ファイルにデータを書き出す関数を説明します.ここで,ファイルポインタ file_out は次のように宣言さ れてかつ fopen 関数で次の様にファイルと関連付けられているとします. FILE *file_out; file_out = fopen("outdata.dat","w"); つまり,ファイルポインタ file_out は,outdata.dat というファイルを書き込み用として関連付けられてい ます. このとき,ファイルへの書き込み関数 fprintf は次のように使用します. fprintf(file_out,"%d",val); ここで,fprintf 関数は,1つ目のパラメータとしてファイルポインタを指定することを除いて,printf 関数と同じです. 9.6 ファイルのデータをすべて読み込んだのかどうかを判断する(feof) 先に示したフローチャートの例で示すように,読み込み用のファイルを用いるプログラムでは,ファイル のデータをすべて読み込んだのか,まだ読むデータが残っているのかを調べる必要が多くあります.このた めには,次の関数が便利です. feof(fp) ここで,fp はいま読み込んでいる最中のファイルに関連付けられたファイルポインタです.この関数は論 理関数といって,戻り値は真または偽です.ファイルのデータをすべて読み終わっていれば真,まだデータ が残っていれば偽を返します.if 文や繰り返し文の条件の部分にこの関数を利用した論理式を書くことで, 例えばファイルからデータをすべて読み切るまである処理を繰り返し実行するといった処理を実現できます. ◆ 演習9◆ 本章のフローチャートで示したクラスの試験結果の平均点を求めるプログラムを完成させよ. #include "stdafx.h" #include <stdio.h> int main(void) { FILE *fp; int num,sum,val; fp = fopen("seiseki.dat","r"); if(fp == NULL) { printf("File not found!\n"); return(-1); } while(!feof(fp)) { fscanf(fp, "%d", &val); sum = sum + val; num = num + 1; } fclose(fp); printf("平均点 = %d\n", sum/num); } 10. 配列 10.1 なぜ配列が必要? 9 章で用いた学生の試験データを使って,下に示すような度数分布表を作ってみましょう. 0 - 9: 3 *** 10 - 19: 5 ***** 20 - 29: 8 ******** 30 - 39: 5 ***** 40 - 49: 12 ************ 50 - 59: 9 ********* 60 - 69: 14 ************** 70 - 79: 20 ******************** 80 - 89: 17 ***************** 90 - 99: 12 *********** 100: 2 ** このプログラムでは,各点数範囲の人数を数えるための変数を用意する必要があります.例えば,0∼9 点 の範囲の人数を数え上げるための変数,10∼19 点の範囲の人数を数え上げるための変数というようにして, 最後は 100 点の人の人数を数え上げる変数まで合計 11 個の変数を用意します.例えば次のようにです. int c0,c1,c2,c3,c4,c5,c6,c7,c8,c9,c10; プログラムではファイルから一人ずつデータを読み出していき,その点数がどの点数範囲に入っているの かを調べます.そして,その範囲用の数え上げの変数の値を1つ増やします.この部分は例えば次のように 書きます(ここでは,変数 val にファイルから読み出してきた点数が入っているとします). if((0 <= val) && (val <= 9)){ c0 = c0 + 1; } else { if((10 <= val) && (val <= 19)){ c1 = c1 + 1; } else { if((20 <= val) && (val <= 29)){ c2 = c2 + 1; } else { if((30 <= val) && (val <= 39)){ c3 = c3 + 1; } else { if((40 <= val) && (val <= 49)){ c4 = c4 + 1; } else { if((50 <= val) && (val <= 59)){ c5 = c5 + 1; } else { if((60 <= val) && (val <= 69)){ c6 = c6 + 1; } else { if((70 <= val) && (val <= 79)){ c7 = c7 + 1; } else { if((80 <= val) && (val <= 89)){ c8 = c8 + 1; } else { if((90 <= val) && (val <= 99)){ c9 = c9 + 1; } else { c10 = c10 + 1; } } } } } } } } } } それから,最初に c0∼c10 の各変数をゼロにクリアしておく必要もあります. これでプログラムはできるのですが,このプログラムに問題があります.それは,このカウント用の変数 についてです.点数が 100 点満点でそれを 10 点刻みでカウントする場合は,11 個の変数ですんだのですが, 例えば 800 点満点でそれを 10 点刻みでカウントする場合は 81 個の変数が必要になります.この 81 個の変数 をすべてクリアするプログラム,ファイルから読み込んできたデータをもとに対応する変数をカウントアッ プするプログラム,ともに何十行,何百行ものプログラムになってしまいます. 10.2 配列 c0∼c10 は同種の目的を持った変数です.前に,変数はそれぞれ1つの箱だという話をしたのですが,こ の場合は 11 個の箱を使っています.ここで,いくつかの箱をまとめて管理できる,便利な配列というものを 紹介しましょう. 例えばこの 11 個の箱(この例では整数型)は,次のように宣言すると,まとめて管理できることになりま す. int c[11]; こうすると,次のようなまとまった 11 個の箱ができます. 図 10-1 int c[11] の結果作られる変数の箱 この 11 個の箱は,一つ一つを区別するために添え字という 0∼10 の番号が付けられています.例えば,最 初の箱は c[0],次の箱は c[1]というようにです. 10.3 添え字 それぞれの箱は,普通の変数の箱とまったく同じです.ですから,次のような式を書くことができます. c[3] = 5; c[4]=c[4]+1; c[6]=c[2]+c[4]; 普通の変数と違うのは,添え字番号を付けるということだけです.逆に言うと,添え字の番号を用いて箱 を指定できるということです. 10.4 配列の効用 ここで,この添え字番号の指定に,整数型の変数が使えます.例えば,変数 j は整数型として宣言されて いるとします.このとき,次のような書き方ができます. j=5; c[j]=c[j-2]+1; この場合, c[5]=c[3]+1; と書いたのと,同じになります. この,添え字に変数が使えるというのが,配列の最大の武器です.このことの凄さを見てみましょう. 例えば,配列 c をすべてクリアする処理は,次のように書けます. for(j=0 ; j<=10 ; j=j+1){ c[j]=0; } どうです.簡単でしょう.しかも,配列の箱の数が 100 個になっても,for 文の j<=10 を j<=99 とするだ けですみます(配列の添え字は 0 から始まるので,箱の数が 100 個になった場合は,c[0]∼c[99]となる点に 注意して下さい). 添え字の凄さは,次の問題を通して,もっと痛感できます. 10.5 2 次元配列 これまで見てきました添字が 1 つの配列を 1 次元配列といいます。それに対して添字を 2 つ持つ配列を 2 次元配列といいます。同様に添字を n 個持つ配列を n 次元配列といいます。 2 次元配列を用いると表などの各欄の要素や、数学の行列を簡単に表すことができます。 m×n の 2 次元配列の宣言の方法は次のようにします。 int matrix[m][n]; 例えば次のような表の各要素を配列で表す場合を考えます。 学籍番号・科目 科目 1 科目 2 1 80 40 2 60 70 3 90 95 この場合添字と番号を合わせるように宣言する場合 int seiseki[4][3]; のようにします。 2 次元配列は添字を 2 つ指定することによって、データを入れておく箱を決定することになります。 上の表の例で各値を代入するには次のようにします。 seiseki[1][1] = 80; seiseki[1][2] = 40; seiseki[2][1] = 60; seiseki[2][2] = 70; seiseki[3][1] = 90; seiseki[3][2] = 95; また 2 次元配列でも 1 次元配列と同じように添字に変数が使えますので、このデータを表示するには for (i=1; i<=3; i++){ printf("%10d ¦", i); for (j=1; j<=2; j++){ printf("%6d", seiseki[i][j]); } printf("\n"); } のようになります。 このサンプル全体は次のようになります。 /**** 表の作成 サンプル ****/ #include <stdio.h> /** プロトタイプ宣言 **/ int main(void); /** main 関数 **/ int main(void){ /** 変数の宣言 **/ int i, j; int seiseki[4][3]; /** 値の代入 **/ /** 2 次元配列 **/ seiseki[1][1] = 80; seiseki[1][2] = 40; seiseki[2][1] = 60; seiseki[2][2] = 70; seiseki[3][1] = 90; seiseki[3][2] = 95; /** 表示 **/ printf("学籍番号\科目 ¦ 科目 1 科目 2\n"); printf("------------------------------\n"); for (i=1; i<=3; i++){ printf("%10d ¦", i); for (j=1; j<=2; j++){ printf("%6d", seiseki[i][j]); } printf("\n"); } } ◆ 演習 10◆ 配列を使って,度数分布表を作成するプログラムを完成させよ. [ヒント] このプログラムで最も面倒なのは,ファイルから読み込んだ点数から,配列のどの添え字番号の箱の値を カウントアップするのかを決定することです.本章で例としてあげたような,沢山の if 文を書かなくてはな らないのでしょうか.いえいえ,今度は読み込んだ点数から添え字の番号を求めればいいので,もっと簡単 にできます.つまり,読み込んだ点数が 0∼9 点であればカウントアップすべき配列の添え字番号として 0 を 求める,点数が 10∼19 点であれば添え字番号として 1 を求める,というぐあいにすればいいのです.読み込 んだ点数が変数 val に入っているとして, カウントアップすべき配列の添え字番号は, val/10 で求まります. 11.アルゴリズム(ソーティング) 11.1 概要 コンピュータでの処理の中で,例えば点数の大きい順に並びかえるとか,タイムの早い順に並びかえると いった処理が多くみられます.こういった,ある基準に従って順番を決め,その順に並び替えるという処理 のことを,ソーティングといいます.本章では,ソーティングのプログラムを作っていきましょう.最終的 な目的は,10,11 章で用いてファイルに入っている点数を,数の大きい順に並べ替えるということです. 11.2 配列への格納 ソーティングの準備として,まずはファイルに入っている点数をすべて配列に読み出しましょう.ファイ ルに入っている生徒の数が何人なのかは正確にはわからないにしても,高々200 人であるとし,次の配列を 用意します. int v[200]; そして,ファイルから順に点数を読み出しては v[0]から順に格納していきます.このとき,読み出した生 徒の数を整数型の変数 num に入れて管理するようにしましょう. つまり,最初は num の値は 0 (まだひとりも読み出していない)とし,読み出したら v[num]にその値を格 納し,num の値を1つ増やします.この処理を,ファイルからすべてのデータを読み尽くすまで続けます. 読んでいる最中は,常に変数 num にこれまで読み出した人数が入っていて,その点数は読み出した順に v[0],v[1],・・・,v[num-1]に入っています. 11.3 最大値 11.2 で配列 v[0],v[1],・・・,v[num-1] に生徒の点数が入りました.次にこの配列の中から,最高点が 入っている箱の添え字番号を見つけるプログラムを作成しましょう.ここで,最高点数が複数ある場合は, 添え字番号の最も小さいものを見つけることにします. さて,どうすればこの最高点数を見つけることができるでしょうか.ここで,人間だったらどうするのか を考えてみましょう.個人個人の点数が書き込まれているカードがクラス全員分あり,この中から最高点の カードを見つけることを考えます. カードの枚数が 10 枚程度であれば,すべてのカードを机の上に並べて見渡し,すぐに最高点のカードを選 べるでしょうが,カードの枚数が 100 枚程度になると,こんな大雑把な選びかたでは不安です.もっと確実 な方法を考えましょう. ここで,紙と鉛筆を用意します.横には積んだカードを置きます.1枚目のカードが見えているので,こ のカードに書かれた点数とその点数は1枚目であるという意味で 1 という数を,紙に書いておきます. そして,カードを一枚めくり,2枚目のカードが見えるようにします.もし,この2枚目のカードの点数 が紙に書かれている点数より大きいなら,点数を書き直すとともに,その点数のカードが何枚目であるのか の数も2に直しておきます.2枚目のカードの点数が紙に書かれた点数より小さいか等しいなら,何もしま せん. そして,またカードをめくって同じことをします.つまり,紙にはいつもこれまでにめくっていったカー ドの中で最も大きな点数とそのカードが何枚目であるのかが書かれているようにするのです.そして,次に めくって現れた点数が,これまでの最高点よりもさらに大きいときにだけ,紙を書き直すのです. こうして,最後のカードの処理を終えたとき,紙に残った点数が最高点であり,そのカードは何枚目のも のであるのかも紙に残っているというわけです.これなら,時間はかかるかもしれませんが,確実に最高点 を決定できます. このことを,カードを配列に置き換えて考えてみると,次のようなフローチャートになります. 図 11-1 最高点を求める処理 なお,このフローチャートにおいて,紙に書いた最高点数は m_v,その最高点数のカードの添え字番号を m_i という変数を利用して実現しています. 11.4 ソーティング さあ,いよいよソーティングです.配列 v[0]∼v[num-1]に入っているデータを並べ替え,最高点が v[0], 次に大きな点が v[1]というように,配列に順にデータが入るようにしましょう.ソーティングにはいろいろ な方法があるのですが,ここでは比較的単純な次のフローチャートで示す方法(選択法・selection sort)を 紹介しましょう. 図 11-2 ソーティング処理 変数 m の値は最初は 0 です.そして,v[0]∼v[num-1]の中の最高点が入っている添え字番号を求めてその 値を変数 m_i に入れ,最後に v[0]と v[m_i]の値を入れ替えます.つまり,全体の中の最高点数の値を v[0] に入れたわけです.次に m の値を1つ増やして1にし,v[1]∼v[num-1]の中の最高点を求めます.つまり, 全体の最高点が入っている v[0]を除いた次の最高点を求めるのです.そして,その値と v[1]の値とを入れ替 えます.この作業を m の値が num-2 になるまで繰り返します. v[m]∼v[num-1]の中で最高点が入っている添え字番号を見つけるプログラムは,11.2 章で作成したプログ ラムをちょっと修正するだけですぐできますね. 最後に,一つだけ注意を.v[m]と v[m_i]の値を入れ替える処理ですが,v[m_i]の値を v[m]に入れて,次に v[m]の値を v[m_i]に入れるのだということで,次のように考えてしまいませんか? v[m]=v[m_i]; v[m_i]=v[m]; 一見すると正しそうですが,これでは両方の値が v[m_i]に入っていた値になってしまいます.つまり,2 行目で v[m]の値を v[m_i]に入れようとしているのですが,1行目を実行した時点で v[m]の値は v[m_i]の値 になってしまっているのです.何が悪かったのでしょうか.それは,次の行で v[m]の元々の値を利用するこ とがわかっているのに,v[m]の値を変化させてしまう点です.ですから,1行目を実行する前に,v[m]に入 っていた値をどこか別の変数に保管しておき,2行目ではその保管しておいた値を v[m_i]に代入すればいい のです.保管用の変数を d として(むろんこの変数は整数型変数として宣言するひつようがあります),次 のように書けばいいのです. d = v[m]; v[m]=v[m_i]; v[m_i]=d; 12.関数 12.1 関数の例 2 章で,C 言語は関数からできていて,C のプログラムを作成するというのは,この関数を作成することな のだという話をしました.実際にこれまでみなさんと main という名前の関数を作ってきました.本章では, 他の関数を作って main や他の関数から使用してみたいと思います. 例えば,n! つまり n*(n-1)*(n-2)*…*2*1 を求める関数を factria と名づけ,この関数を作成することを 考えましょう.この関数は,整数値 n を入力パラメータとして与えると,整数 n!を結果として返すという形 にします.この関数を定義するプログラムは例えば次のように書けます. 1 int factria(int n) 2 { 3 int val,k; 4 5 val=1; 6 for(k=n; k>1; k--) val=val*k; 7 return val; 8 } 最初の番号は説明のための番号です. 12.2 関数の書き方 前のプログラムの1行目に示すように,関数を定義するときにはまず関数名と関数名の前にその戻り値(こ の場合は関数名は factria で戻り値は int つまり整数)を指定し,関数名に続けて括弧内に入力パラメータ 列を指定します.この例では入力パラメータは整数型の変数が 1 つであり,この関数内では n という変数名 で参照されます. この行に続けて,この関数の中身を中括弧("{","}")で挟んで記述します.これまでに書いてきた main 関 数の記述の方法とまったく同じです.注意する点としては,関数の中で利用する変数(この例では整数型の 変数 val,k)は,その関数の中で宣言して使用しなければならず,また関数の中で宣言した変数はその関数 以外では参照できないということです.たとえ他の関数(例えば main)で同じ名前の変数を宣言して使用し たとしても,この関数の変数とはまったく違うものとして扱われます.具体的には違う箱が用意され,別々 の値を持つことになるのです. この例では,変数 val に n!の値を計算して求めています.そして最後に 7 行目でこの関数を呼んだ元の関 数に val の値を戻り値として戻し,この関数の実行を終了します.この return 文は後ろに戻り値を書き,関 数の処理を終えることを示しています. この関数を使って,例えば1∼7 までの階乗の値を表示するプログラムは次のようになります. #include <stdio.h> /* 関数のプロトタイプ宣言*/ int factria(int n); int main(void) { int k; for(k=1; k<=7; k++) printf("%d\n",factria(k)); } int factria(int n) { int val,k; val=1; for(k=n; k>1; k--) val=val*k; return val; } なお,return 文は関数の最後の行にしか書けないということはなく,関数を終了させたい任意の場所に書 くことができます.また,2つ以上の return 文が一つの関数の中に存在してもかまいません.例えばある条 件に当てはまるかどうかを判定し,当てはまるなら戻り値を1,当てはまらなければ戻り値を0とする関数 を考えるとき,次のような関数の形が考えられます. int 関数名(入力パラメータ) { if(条件判定) return 1; else return 0; } 12.3 変数の有効範囲,注意 上のプログラムでは関数 main 内で,入力パラメータ値を 1 から7まで変化させながら関数 factria を呼ん でいます.このとき,main の中では変数 k を使っていますが,この k の値と関数 factria 内の変数 k の値と はまったく独立しています.この結果,関数 factria から戻ってきたときには,k の値は関数 factria を呼 び出す前の値を保っています. 関数の定義は ANSI 標準規格に従って書いてあります.この規格では main の関数の前にプロトタイプ宣言 をしてプログラムの中でどんな関数を使うか、関数の一覧を書きます.これは,コンパイラがプログラムを 最初から読んでいくときに,main の中で関数 factria を呼ぶことを発見するわけですが,この時点ではコン パイラはこの関数がどんな入力パラメータを持ち,戻り値の型が何であるのかを知らないため,ちょっと混 乱します.この混乱を防ぐためのものです. 12.4 再帰 この章で考えてきた n! を計算する factria という関数は変数 n を与えて n*(n-1)*(n-2)*…*2*1 を計算 しましたが。これを n*(n-1)! (n と n-1 の階乗の積)と考えることができます。すると、階乗の計算は関 数 factria で計算できましたのでこの関数を利用することができます。すると、n! を計算する次のような 関数を書くことができます。 #include <stdio.h> /* 関数のプロトタイプ宣言*/ int rfactria(int n); int main(void) { int k; for(k=1; k<=7; k++) printf("%d\n",rfactria(k)); } int rfactria(int n) { if (n > 0) return n * rfactria(n-1); else return 1; } このように関数の中から自分自身の関数の呼び出しを含むことを再帰といい、再帰を用いたプログラムを再 帰プログラムといいます。 ◆演習7の解答例 1.大きな処理の流れを考えよう プログラムの大きな処理の流れを箇条書き風に書いてみます [1]キーボードから3つの整数値を入力して,それぞれを変数 a,b,c に入れる [2]変数 a が3つの値のうちの中間値であれば変数 a の値を表示する. [3]変数bが3つの値のうちの中間値であれば変数bの値を表示する. [4]変数cが3つの値のうちの中間値であれば変数cの値を表示する. [5]以上のどれでもなければ 中間値無し と表示する. ここで,[2]の処理を考えてみましょう. 変数a,b,cのなかで,変数aの値が中間値であるかどうかを調べます.変数aの値が中間値であるとい うのは,次のどちらかの場合です. (1)b が一番大きく,次に a ,一番小さいのは c である (2)c が一番大きく,次に a ,一番小さいのはbである (1)を条件式で書くと次のようになります b>a && a>c つまり,a より b の方が大きく,かつ c より a の方が大きいということです. (2)を条件式で書くと次のようになります c>a && a>b つまり,a より c の方が大きく,かつ b より a の方が大きいということです. 変数a,b,cのなかで,変数aの値が中間値であるかどうかを調べる条件式は,この(1)又は(2)が真とな ればよいので上記2つの条件式を ¦¦ で結んで (b>a && a>c) ¦¦ (c>a && a>b) となります. 同様に,変数a,b,cのなかで,変数bの値が中間値であるかどうかを調べる条件式は (a>b && b>c) ¦¦ (c>b && b>a) 変数a,b,cのなかで,変数cの値が中間値であるかどうかを調べる条件式は (a>c && c>b) ¦¦ (b>c && c>a) となります. 2.フローチャートを書こう 1.で考えた大きな処理の流れをフローチャートで次のように表現してみます.みなさんは,このフローチ ャートで処理の流れを追い,正しいかを確かめてください. 3.プログラムを書こう 2.のフローチャートをもとに,プログラムを書いてみましょう.if 文が複雑に入り組んでいますから注意 してください. #include "stdafx.h" #include <stdio.h> int main(void) { int a,b,c; scanf("%d %d %d", &a, &b, &c); if((b > a && a > c) ¦¦ (c > a && a > b)) printf("%d\n", a); else if((a > b && b > c) ¦¦ (c > b && b > a)) printf("%d\n", b); else if((a > c && c > b) ¦¦ (b > c && c > a)) printf("%d\n", c); else printf("中間値は無い。\n"); } ◆演習 10 の解答例 プログラム例 #include "stdafx.h" #include <stdio.h> int main(void) { int i,j,val; int c[11]; FILE *fp; for(i=0 ; i<=10 ; i++) c[i]=0; fp = fopen("seiseki.dat","r"); if(fp == NULL) { printf("File not found!\n"); exit(1); } while(!feof(fp)) { fscanf(fp, "%d", &val); c[val/10] = c[val/10]+1; } fclose(fp); for(i=0 ; i<=9 ; i++) { printf("%2d - %2d:%3d ", i*10, i*10+9, c[i]); for(j=1 ; j<=c[i] ; j++) printf("*"); printf("\n"); } printf(" 100:%3d ", c[10]); for(j=1 ; j<=c[10] ; j++) printf("*"); printf("\n"); }
© Copyright 2024 Paperzz