Unix C プログラミング入門資料(高崎金久)   3.整数演算,制御構造,数値データの入出力 目次 1.今回のテーマについて 2.プログラムの例 3.プログラムの解説 4.文字列->数値変換函数を使う入力方法 -------------------------------------------------------------------------- 1.今回のテーマについて 今回は次の問題を例題として説明を進めます. *問題* 整数(正であることを要求する)を2つ入力し,それらの最大公約数を 計算して表示するプログラムをつくること. これは計算機を利用して解決する問題の典型と言えます.この問題を解決するため に必要なのは以下のことでしょう. 1)整数データの入出力方法 2)整数データを格納するために必要な変数 3)アルゴリズムを実現するための構文(代入,演算,制御構造) 計算機が扱うデータの種類にはさまざまなものがあります.前回までは文字列を扱 って来ましたが,上の問題においては数値データが主な対象です.数値データには 大別して整数と実数があります.計算機においては整数や実数も文字列と同様に一 定の大きさのメモリー領域に一定の形式で符号化されますので,数学的な意味での 数とは異なります.プログラミングではこのような計算機内部での数値表現の特性 を常に意識する必要があります. C言語では整数や実数(浮動少数点数と呼ぶのが正確ですが)を表わすデータ型と して複数の型が用意されています.整数(データ型としては short, int, long な どがある)の場合,符号化に使うビットの数で表わせる整数の範囲が決まっていま す.浮動少数点数(データ型としては float, double などがある)の場合は特定 の国際標準規格が採用されていて,やはり表わせる数の範囲が決まっています. 数値データの入出力では伝統的に書式付き入出力函数 scanf, printf が用いられ て来ました.今でも出力については printf を使うのが普通です.しかし入力に関 しては,fgets により文字列として入力し,atoi などのユーティリティ函数で数値 データへ変換する,という方法も用いられます.以下ではこの両方について説明し ます. 今回の問題ではアルゴリズムも重要な要素となります.整数の最大公約数を求める 効率的なアルゴリズムとして,古来から「ユークリッド互除法」が知られてきまし た.これは与えられた2つの整数 a, b から出発して割り算を繰り返すことにより 求める答を得る方法です.このようなアルゴリズムの実現のためには a)プログラムの実行内容を条件に応じて変えること(条件分岐) b)一定の処理を何回も繰り返して実行すること(繰り返し) などが必要です.これを実現するための構文上の要素が「制御構造」です.C言語 ではいろいろな制御構造が用意されています.ここではそのうちでももっとも基本 的な if 文と while 文の使い方を説明します. 2.プログラム例 次のこのプログラムは上の問題に対するプログラムの一例です. 1:/************************************************************** 2: 与えられた整数の組に対してユークリッド互除法によって最大公約数 3: を求める.前処理として,与えられた2つの整数の符号と順序を入れ 4: 替えて a >= b >= 0 という状況にしておく. 5: **************************************************************/ 6: 7:#include 8: 9:int main(void) 10:{ 11: int a = 0, b = 0, c = 0; 12: 13: printf("正整数の組 a, b に対してその最大公約数をもとめます\n\n"); 14: printf("a = "); scanf("%d", &a); 15: printf("b = "); scanf("%d", &b); 16: if (a <= 0 || b <= 0){ 17: printf("正整数ではありません\n"); exit(1); 18: } 19: 20: if (a < b){ 21: c = a; 22: a = b; 23: b = c; 24: } 25: c = a % b; 26: while(c != 0){ 27: a = b; 28: b = c; 29: c = a % b; 30: } 31: 32: printf("最大公約数は %d です\n", b); 33: return 0; 34:} 3.プログラムの解説 9行目および33行目:前回までの例と main 関数の書き方が変わっています. * 9行目に「void main(void)」-->「int main(void)」 * 33行目に「return 0;」という文が付け加わっている これは main 関数を独立の関数として再利用する(あるいは独立の関数を main 関数としてテストする)ことを念頭においた工夫なのでです(7章で説明します) が,前回までのように「void main(void)」にして「return 0;」を省いた形でも 構いません. 11行目:ここでは3つの整数型変数 a, b, c を宣言して,同時に初期化してい ます.C言語には標準的整数型として次の6種類が使えます. *整数型の種類* 符号付き整数 : short, int, long 符号無し整数 : unsigned short, unsigned int, unsigned long (unsigned int は unsigned と略記できます.)符号付きのものは正負の値,符 号無しは正(0も含む)のみの値をとれます.また short, int, long の順にメモ リー上に占める領域が大きくなり,それだけ大きい範囲の整数を表わすことができ ます.ただし,一般に short は2バイト(1バイトは8ビット)以上,long は4 バイト以上,int はこのどちらかと同じ,という基準があるだけで,実際の大きさ はシステム・処理系によって異なります.文字列同様,整数でも同じ型の変数はま とめて宣言できますし,宣言時に「=」で初期化することができます. 14ー15行および32行:ここで scanf, printf による数値データの入出力を 行っています.「%d」は10進整数を入主力するための変換仕様で,int型整数変 数をデータの格納場所として引数に指定します.注意すべきは scanf の場合で, 引数として渡す変数名の前に「&」(アドレス演算子と呼びます)を付けなければ なりません.これは忘れやすいことですので,気を付けて下さい. *scanf, printf による int 型データの入出力の一般形 scanf("...%d...", ...,&x,...); /* x は int 型変数 */ printf("...%d...", ...,x,...); /* x は int 型変数 */ *int 以外の整数データの場合 %hd -- short %ld -- long %hu -- unsigned short %lu -- unsigned long %u -- unsigned %hx -- short16進表示 %lx -- long16進表示 %x -- int16進表示 scanf による入力の場合はどのような変換仕様の場合も変数にアドレス演算子を付 ける必要があります.これは scanf が変数の内容を変更するからです.これはC プログラミングでの函数呼び出しの内部的仕組と関係があります.函数が変数の内 容を変更するためには変数のメモリー上のアドレスを知る必要があります.変数名 にアドレス演算子を付けると,その変数のアドレスと解釈されます.scanf のよう に変数の内容を変更する函数はそのようなアドレスを引数として受け取るわけです. 例外は配列変数の場合です.文字列を scanf で入力するときはアドレス演算子を 付けないで配列変数名を直接に引数としました.実はC言語では配列変数名は変数 そのものではなくてその先頭アドレスを意味します.そのため,配列変数名を渡す だけでよかったのです. この辺りはC言語の間違えやすい点です.あとで述べるような,数値データを文字 列として入力して数値データに変換する方法では,アドレス演算子という厄介なも のを使わなくてすみます. 16ー18行および20ー24行:これらは if 文によって条件分岐をしています. 一般に,if 文は次の形をとります. *if 文の一般形: if 条件式 実行文; 条件式は while 文でも用いられるもので,等式(== を含む)や不等式(<, <= などを含む)およびそれらを論理結合子(&&, ||, !)で結んだものの形をとるの が普通ですが,真理値(ブール値)をとる函数で構成されることもあります. *条件式の構成要素 a == b a と b は等しい a != b a と b は等しくない a < b a は b より小さい a > b a は b より大きい a <= b a は b 以下である a >= b a は b 以上である P && Q P かつ Q(論理積) P || Q P または Q(論理和) ! P P でない(否定) 実行文は本来は一つの文ですが,{ } でブロックを作れば複数の文を与えることが できます.上のプログラムではまさにそういう例になっています. 17行目にでてくる「exit(1);」はプログラムの実行自体を中止する命令です. *プログラムの実行中止 exit(n); /* n はリターンコード(システムの側に返す値) */ ちなみに,21行から23行は変数の内容を入れ替える一般的な方法を示しています. c は退避用の変数として用いられています.ブロックの先頭では変数が宣言できます ので(しかもそのような変数はブロック内でのみ有効)20行ー24行を if (a < b){ int c = a; a = b; b = c; } というように書くこともできます.退避用の変数はその場限りで使うものなので, この書き方の方がむしろ望ましいとも言えます. 25ー30行:これがユークリッド互除法の本体です.「%」というものが出てきま したが,これは割算して余りを取り出す,という演算です.整数演算は次のような ものがあります.割算は小数点以下を切り捨てることに注意して下さい. *整数演算 x + y -- 加法 x - y -- 減法 x * y -- 乗法 x / y -- 除法(余りは捨てる) x % y -- 余り ここでは while 文が用いられています.これは条件式が満たされる限り実行文を 繰り返し実行するものです. *while 文の一般形: while 条件式 実行文; if 文と同様,ブロックにすることで複数の実行文を実行することができます. 4.文字列->数値変換函数を使う入力方法 数値データを入力するもう一つの方法として,文字列入力函数と文字列から数値へ の変換函数を組み合わせる方法を述べます. *文字列->整数変換函数 atoi(string) --- 文字列を int 型に変換してその値を返す atol(string) --- 文字列を long 型に変換してその値を返す 実数への変換函数もありますが,これは次の回で説明します. 以下に,これを用いて上のプログラムの最初の部分(7ー15行)を書き換えたも のを示します.これで変換函数の使い方は理解できるはずです. #include #include int main(void) { int a = 0, b = 0, c = 0; char buffer[20]; printf("正整数の組 a, b に対してその最大公約数をもとめます\n\n"); printf("a = "); fgets(buffer,20,stdin); a = atoi(buffer); printf("b = "); fgets(buffer,20,stdin); b = atoi(buffer); (以下は同じ) *変更点 1.「#include 」という行.これは変換函数を使うためのもの.(実は これは atoi を使うときには不要だが,ほかの変換函数を使うときには必要なので 入れておく習慣をつけておく方がよい.) 2.文字列入力用に「buffer」という変数を用意した. 3.最後の2行が文字列の入力を行っている部分. * fgets の使い方 fgets(string, n, input); string: 文字列変数 n: 読み込む長さ(バイト), input: 読み込み先(ファイルなど)