2006 年 5 月 23 日 第 2 版 東海大学 総合情報センター 清水計算機室 発行 CONTENTS 1. C 言語とは.......................................................1 9. 制御構造................................................32 9-1. if 文......................................................32 9-2. while 文...............................................35 9-3. for 文...................................................36 9-4. do~while 文........................................37 9-5. switch 文.............................................38 9-6. continue 文..........................................40 9 章演習問題...............................................42 2. プログラムを作る前に....................................1 2-1. 流れ図の基本と処理記号......................1 2-2. 判断記号と複合条件.............................3 2-3. アルゴリズム........................................6 2 章演習問題.................................................7 3. プログラム作成の流れ....................................9 10. 標準入出力関数..........................................43 10-1. print の使い方...................................43 10-2. scanf の使い方...................................45 10 章演習問題.............................................48 4. プログラムの構造.........................................10 4-1. 最小の C プログラム...........................10 4-2. 文字列を出力する...............................11 4-3. 数値計算と数字の入出力....................12 4 章演習問題...............................................14 11. 関数の作り方..............................................49 11-1. 関数..................................................50 11-2. 値の返し方.......................................52 11-3. void 型関数........................................52 11-4. 関数間のデータ渡しの方法..............53 11-5. ローカル変数とグローバル変数.......56 11-6. 記憶クラスの種類.............................57 11 章演習問題.............................................60 5. プリプロセッサ............................................15 5-1. #include..............................................15 5-2. #define................................................15 5-3. #undef.................................................15 6. 定数と変数....................................................16 6-1. 定数....................................................16 6-2. 変数....................................................17 6-3. 変数の初期化......................................18 6 章演習問題...............................................19 12. ポインタ.....................................................61 12-1. 変数とポインタ................................61 12-2. 配列とポインタ................................63 12 章練習問題.............................................65 7. 演算子...........................................................20 7-1. 代入演算子.........................................20 7-2. 算術演算子.........................................20 7-3. インクリメント・デクリメント...........21 7-4. 関係演算子.........................................21 7-5. 論理演算子.........................................22 7-6. ビット演算子......................................22 7-7. シフト演算子......................................24 7-8. 複合代入演算子..................................25 7 章演習問題...............................................26 13. 構造体と共用体..........................................67 13-1. 構造体の定義と応用.........................67 13-2. 構造体の入れ子................................69 13-3. 構造体へのポインタ.........................70 13-4. 共用体..............................................71 13 章演習問題.............................................73 14. 標準ライブラリ関数...................................74 14-1. 標準ライブラリ関数一覧..................74 14-2. ファイルのオープンとクローズ.......77 14-3. ファイルの入出力............................79 14-4. コマンドラインから引数を渡す方法...81 8. 配列..............................................................27 8-1. 1 次元配列...........................................27 8-2. 文字と配列.........................................28 8-3. 2 次元配列...........................................29 8-4. 配列の初期化......................................29 8 章練習問題...............................................31 付録 1 JIS による流れ図記号.............................83 0 1. C言語とは C 言語は 1973 年、米国 AT&T ベル研究所で設計されたプログラミング言語で UNIX など多くの OS 上で普及しています。C 言語は手続き型・関数型言語の1つで次のような特徴を持ちます。 1) C 言語の演算子としてコンピュータのハードウェアに近い機能をもつ 2) プログラミングに必要な基本的な命令だけであり、命令の種類が少ない 3) 豊富なデータ型をもつ 4) プログラムは関数の集まりとして作成される 2. プログラムを作る前に コンピュータに計算させたり、図や表を描かせたりするためには、私たち人間がコンピュータにその 計算の手順を教える必要があります。その手順を人間が書き記したものがプログラムです。記述のルー ルはプログラミング言語によって異なりますがここでは C 言語について学んでいきます。 2-1. 流れ図の基本と処理記号 プログラムがどのような手続き処理で行うかを図で示します。この図のことをフローチャート(流れ 図)といいます。フローチャートが完成した時点でプログラム作成の大半が終了したといっても過言で はないでしょう。ここで最も重要な流れ図の記号を 3 つ説明します。 表 2-1 基本的な流れ図記号 記号 名称 説明 端子 流れ図のはじめと終わり、サブルーチン の入り口と出口などを示す。 処理 演算などあらゆる種類の処理を示す。 線 制御の流れを示す。流れの向きを明示 する必要があるときは矢印をつける。 100 円のチョコレートを 5 個買ったときの消費税込みの金額を求めてみましょう。普通は次のように 計算します。 100 円 × 5 個 = 500 円 コンピュータで処理するには この 500 円をいったんどこかへ 500 円 × 1.05 = 525 円 記憶しておかなければならない 図 2-1 税込金額の計算 100 円×5 個で金額を計算しますが、求めた 500 円に 1.05 をかけたものが税込み金額です。この式の ように計算して得られた値を次の計算式で使う場合には、その値をどこかに記憶しておく必要がありま す。流れ図やプログラムでは変数というものに値を記憶します。 1 中学生の頃に学んだ方程式の x や y は値をもっていました。流れ図でも x や y という変数を用いれば、 それに値を入れて記憶させることができます。先の税込み金額の計算を流れ図にしてみましょう。 はじめ 「金額」に、100×5 を入れる 金額←100×5 「金額」が 500 になっているので、 税込金額←金額×1.05 500×1.05 の値を「税込金額」に入 れる おわり 図 2-2 税込金額の計算する流れ図 端子記号を書いて、流れ図の最初と最後がわかるようにします。最初の端子記号の中には「はじめ」 「START」など始まりを意味する言葉を、最後の端子記号の中には「おわり」 「END」など終わりを意 味する言葉を書きます。 処理記号の中には、一般的に演算式や代入式を書きます。 矢印(←)を「入れる」と説明していますが、正確には左側の値を右側の年数に転記(コピー)しま す。C 言語などを使う場合、代入は右から左なので流れ図の矢印も右から左にすることが多いようです。 先の流れ図は「100 円のチョコレートを 5 個買うときの税込金額」を正しく求めることができます。 しかし、消費税率が上がってしまい「7%の税率で、98 円のチョコレートを 5 個買うときの税込み金額」 を正しく求めることができるでしょうか? 98 円 × 5 個 = 490 円 490 円 × 1.07 = 524.3 円 少数がでる 図 2-3 税込金額の計算 その 2 少数の切捨てを行っていないので、少数が出てしまいます。 プログラムは、なるべく汎用的(いろいろな画面で使える)ようにしておきたいものです。定価や数 量がどんな値でも正しく計算ができ、たとえ消費税の税率が変わっても容易に修正できるようにしてお きます。 図 2-2 の流れ図を変更しました。ただし、ここでだけ通用する関数として「INT(x)」は『x の小数点 以下を切り捨てる関数』と定義します。 2 はじめ 税率←0.07 「単価」が 98、「数量」が 5 になっ ているので、 金額←98×5 「金額」が 490 になる 「税率」が 0.07 なので INT(490×0.07)→消費税 「消費税」が 34 になるので 税込金額←490+34 「税込金額」は 524 になる 単価←98 数量←5 金額←単価×数量 消費税←INT(金額×税率) 税込金額←金額+消費税 おわり 図 2-4 税込金額の計算する流れ図 その 2 このような流れ図にしておけば、必要に応じて税率などを書き換えることが容易で、外部条件の変化 に対応しやすいプログラムになります。 2-2. 判断記号と複合条件 インターネットのオンラインショップを利用することが多くなりました。米や芋までネットで買うよ うになりましたが送料がかかるのが玉に傷です。そこで一定金額以上を買うと送料をサービスしてくれ るところでまとめ買いをしてみたいと思います。今回は 3000 円以上のときは送料が無料になる流れ図 を考えます。 流れ図ははじめをあらわす端子記号からスタートして上から下に処理が流れていきますが「3000 円以 上のときは」というような条件で処理を分岐させたいときには、次のような判断記号を用います。 表 2-2 判断記号 記号 名称 判断 説明 1つの入り口から入り、条件を評価して、複 数の出口の1つをえらぶことを表す 2) 条件の値で分岐する場合 1) 条件の真偽で分岐する場合 Yes 変数>10 No > 変数:10 < = ① ② ① 図 2-5 判断の流れ図記号 3 ② ③ 2)はコロン(:)の左右に変数や定数を書き、2 つの値を比較したときの大小関係で進むところが変 わります。2 分岐だけではなく 3 分岐もできます。 今回は、単価と数量をキーボードから入力して、支払い金額をディスプレイに表示するとともに、レ シートに印刷したいので次のような個別データ記号を用います。 表 2-3 個別データの流れ図記号 記号 名称 説明 手操作入力 キーボードなど、手で操作して入力するデー タを表す。 表示 ディスプレイなどに表示するデータを表す。 書類 プリンタなどで印字するデータを表す。 単価 金額 金額 ・キーボードから入力し ・「金額」という変数の内 ・「金額」という変数の内 た値を「単価」という変数 容を表示する。 容を印字する。 に設定する。 100 が 入 力 さ れ た ら 「金額」の内容が 100 「金額」の内容が 100 なら 100 が表示される。 なら 100 が印字される。 「単価」が 100 になる。 図 2-6 流れ図記号の説明 まず、購入金額が 3000 円以上のときは送料無料の条件で支払い金額の計算式を考えてみます。送料 は消費税込みとします。 ・金額が 3000 円未満の場合 金額+金額×税率+送料 条件によって計算式が違う場合に は、判断記号で処理を分岐させる ・金額が 3000 円以上の場合 金額+金額×税率 送料はいらない 図 2-7 支払い金額の計算式 条件は「3000 円以上か、3000 円未満か」ですから、判断記号で二分岐します。流れ図を次に示しま す。 4 はじめ ① 税率 ← 0.05 送料 ← 500 限度額 ← 3000 例1 100 100 数量 20 30 金額 ← 単価×数量 2000 3000 消費税 ← 金額×税率 100 150 支払い金額 ← 金額+消費税 2100 3150 ② ③ ≧ 例2 単価 金額:限度額 < ④ 2600 支払い金額 ← 支払い金額+送料 支払い金額 支払い金額 2600 3150 2600 3150 おわり 図 2-8 税込金額の計算する流れ図 その 2 判断記号で「金額」と「限度額」の 3000 を比較して、3000 未満のときだけ④で送料を加えます。 流れ図の右側に「単価」が 100 で「数量」が 20 と 30 にしてトレースした場合の代入文右側の値を示 します。右辺の値とは「金額←単価×数量」なら矢印(←)の左側の金額に設定された値のことです。 「単価」が 100 で「数量」が 30 の場合は「金額」が 3000 になるので、④は通らずに送料が足されて いないことがわかります。 図 2-8 では「単価」と「数量」に具体的な値を入れて変化の様子を追跡していますが、この作業をト レースといいます。値を書き出した表をトレース表と呼びます。 5 2-3. アルゴリズム 流れ図は、コンピュータに指示を与えるための処理手順を図で示したものでした。その処理手順のこ とをアルゴリズムと呼びます。JIS 規格では、アルゴリズムを「明確に定義された有限個の規則の集ま りであって、有限回適用することにより問題を解くもの」と定義しています。つまり無限に計算し続け るようなものはアルゴリズムではありません。 同じ目的のアルゴリズムでも、優れたものや劣るものがあります。一般に良いアルゴリズムの条件は 次の通りです。 1) 論理的に正しいものであること 2) わかりやすく書かれていること 3) 効率が良いこと 一言で言えば「正しくて速い」ものが良いアルゴリズムです。 例えば 4×10 をかけ算を使わずに計算する方法を考えてみましょう。どんな方法があると思います か? 表 2-4 4×10 の計算方法案 案1 4を10回足す 案2 10を4回足す 案3 10を左シフトする G ← 4 G ← G+4 G ← G+4 G ← G+4 G ← G+4 G ← G+4 G ← G+4 G ← G+4 G ← G+4 G ← G+4 G ← 10 G ← G+10 G ← G+10 G ← G+10 G ← 10 G ← G<<2 足し算の繰り返しでかけ算を行う場合、4 を 10 回足すよりも、10 を 4 回足したほうが計算回数が少 なくなります。また、2 のべき乗倍は、シフト演算で表現できるので簡単に 4 倍することができます。 シフト演算はまた後で詳しく紹介します。 同じ目的の処理を行うプログラムでも採用するアルゴリズムの違いで大きな差が出ることがわかりま した。 最適なアルゴリズムというのはいつも同じではありません。いろいろな条件を考えて、どのアルゴリ ズムを採用すべきなのかを検討しなければなりません。流れ図の穴埋め問題には関係ありませんが、せ っかく流れ図を学ぶなら、こういうことを考えながら学んでほしいのです。それが、プログラミングセ ンスを磨くことにつながります。 6 2 章の練習問題 1) 次の図をトレースしなさい。 はじめ A ← 80 B ← 100 C ← 70 ① ② G ← A+B+C ③ H ← INT(G÷3) ④ A ← A-H B ← B-H C ← C-H A B C G H ① ② ③ ④ おわり 2) 次の流れ図は 1 から 10 までの合計を求めるものである。流れ図中の色網をかけた空欄を埋 めなさい。トレース表を作成し、プログラムの動きを確認しなさい。 はじめ ① 合計 ← 0 数 ← 10 ② 合計 ← 合計+数 はじめ ① ② 数:1 > ③ ④ > 数 ← 10 合計 ← 数 数:0 ③ a ④ b ≦ おわり おわり 7 ≦ 3) 次の流れ図はキーボードから入力された金額の合計金額を求めるものである。金額の入力が 終わったら-1 を入力すると合計金額が表示される。 なお、入力エラーはないものとする。入力データ表のような金額が入力されたときのトレー ス表を埋めなさい。 ・金額入力データ はじめ ① ② 合計金額 ← 0 2回目 300 3回目 200 終わり -1 金額 = ≠ ④ 100 金額を入力 金額:-1 ③ 1回目 合計金額 ← 金額+合計金額 合計金額 おわり 8 合計金額 3. プログラム作成の流れ コンピュータに何か仕事をさせるためには、まず仕事の手順を反映させたソースプログラムを書か なければなりません。C プログラムを実行させるためには次の手順が必要です。 1) エディタでソースプログラムを書く 2) コンパイラ、または、アセンブラでオブジェクトファイルを作る 3) リンカでオブジェクトファイルを合成して実行可能ファイルにする エディタ ソースファイル コンパイラ(アセンブラ) ライブラリ オブジェクトファイル 1 オブジェクトファイル オブジェクトファイル 2 オブジェクトファイル 3 リンカ 実行ファイル 図 3-1 C プログラムの作成手順 C 言語は人間にとってわかりやすい(読みやすい)特徴を持ちます。しかし残念なことに C プログラ ムのままではコンピュータに理解させることはできません。コンピュータが C プログラムを実行するに は、C 言語の命令を機械レベルの命令に翻訳するプログラムが必要です。このようなプログラムをコン パイラといいます。 どのようなコンパイラを使用する際にもまずはそのコンパイラの言語でプログラムを書かなくてはい けません。C 言語でプログラムを書く際には、C で書かれたものはソースコードと呼ばれます。コンパ イラの役割は私たちの書いたソースプログラムを取り込み、それをコンピュータが理解し実行できる命 令に翻訳することです。このようにしてコンパイラが出力するものを実行可能コードといいます。 コンパイラ 元のプログラムをコンピュータ固有のアセンブラ言語に翻訳したもので、この新しいファイルはさら にシステムのアセンブラに渡されます。アセンブラはこのファイルをオブジェクトコード(中間コード) と呼ばれる形式に変換します。 リンカ コンパイラを通過したソースはオブジェクトコードに変換されますが、そのままでは実行することは できません。このオブジェクトコードをさらに変換して実際に実行する形のプログラムを作ります。こ の役割をするのはリンカと言います。リンカは OS 固有のようなものです。オブジェクトコードでは実 際にメモリにロードされてもプログラムとして固まっていないので、実行できません。そこで、メモリ にロードされた時の状態を考えてコードを配置し直すのがリンカの役割です。 9 4 プログラムの構造 4-1. 最小のCプログラム 実際に簡単な C 言語のプログラムを紹介します。 main() { } これは C の文法規制に違反しない最も小さなプログラムです。これをコンパイルすれば、1 人前のプ ログラムとして処理されます。ただし実行しても何も行われません。実行文は書いていないので当然で す。しかしながらこのプログラムは C の文法をいくつか体現しています。 ①プログラムは関数で構成される C 言語は関数の集まりで構成されます。関数とは、(関数に対して)必要なデータを渡すことにより、 処理が行われ結果を返すものです。 入力 1 関 数 出力値 入力 2 図 4-1 関数のイメージ 先ほどのプログラム例で紹介した『printf』も関数のひとつです。関数には必要ならば引数(ひきす う)を書きます引数が必要なければ空のかっこ()を書きます。例えば foo という関数で引数の例をあ げると次のようになります。 foo() foo(int a) foo(int x, int y) 引数なし 引数 1 個 引数 2 個 *引数については後ろの章で詳しく説明します。そんなものがある、程度の認識で構いません。 ②プログラムにはmain関数がひとつ必要である C はいくつもの関数が集まって構成されますが、関数には自由に名前をつけることが可能です。しか し一番初めに実行したい関数だけは特別に「main」という関数名を用いる約束になっています。 main 以外の関数はいくつあっても構いませんし、なくても大丈夫です。 ③関数は { で始まり } で終わる 実行文はこの{}の範囲内でかかれていなければいけません。ある関数が呼ばれると処理は { から始ま ります。そして最後の } に出会うと終わります。 10 4-2. 文字列を出力する では今度は C プログラムに実際に仕事をさせてみましょう。文字列“abcde”を出力してみます。 main() { printf("abcde¥n"); } ①文には; (セミコロン)が必要である セミコロンがついていないとそれは文として認められません。コンパイルするとエラーとなります。 コンパイラはこのセミコロンを文の「終端マーク」として監視し、それに出会うごとにひとかたまりの 文としてコード変換していくのです。 たとえば main() { a = 30; b = 20; c = 10; } は正しい記述ですが、これを 1 行にまとめると main() { a = 30; b = 20; c = 10; } もまた正しい文です。これが 3 つの文であることが;によって認識されます。なお、ここでは文のみ を表現しましたが、 正確には「式の文」にはセミコロンが必ず必要 ということになります。 ②printfは関数である 先ほど紹介した「何かを出力する」printf 関数ですが、 「何か」に相当する引数(ひきすう)として” abcde¥n”というデータを渡したということを示しています。 表示したい文字列 abcde\n printf 表示した文字数を返す 表示させたい書式 (なし) 図 4-2 printf 関数のイメージ 11 では今度は文字列を複数行出力してみましょう。 /* test program */ main() { printf("abc"); printf("XYZ¥n"); printf("1234¥n7¥n9"); } 今までみたプログラムと違うところがあります。¥n の存在です。実行文の中の 1 行目では¥n がなく、 3 行目には 2 つあります。実行結果はどうなるのでしょうか。 abcXYZ 1234 7 9 ③改行(¥n)はいくついれてもよい この「¥n」は文字列中の何処に記述してもかまいません。当然「¥n¥n」とすると空行 2 行が出力さ れます。こういった¥n などをエスケープ文字列と呼びます。 ④コメントは/*と*/で囲む main よりも上に記述されている /* test program */ 上記の表現はコメントです。コンパイラはコメントに出会うとこれを無視します。「/*」はコメント のはじまり、 「*/」はコメントの終わりを指示します。 「/*」 と 「*/」 は同じ行にある必要は ありません。何行かにまたがってコメントを書くこともできます。 そのほかの表現としてもスラッシュ記号を 2 つ並べて「//」、その当該行のみコメントにすることもで きます。 4-3. 数値計算と数字の入出力 プログラムの主体となるのはなんといっても数値計算です。今度はこれについて考えてみましょう。 次のプログラムは 2 数の和を求めるものです。 main() { int a; a = 10 + 20; printf("ans = %d¥n", a); } 実行結果は 12 ans = 30 特別なことをしているわけではないのでこのプログラムの動きは予測できるでしょう。しかし重要な ルールが 2 つでてきます。 ①変数は宣言してから使用する このプログラムで使用されている変数は a です。変数については 5 章で詳しく説明しますが、a は 方程式の x のようなものだと考え、整数が格納できるものだと思ってください。 重要なことは C 言語では、すべての変数はそれを使用する前にあらかじめ宣言されていなくてはいけ ないということです。 ②変数の値を出力するには書式指定が必要である 値の出力用に使われている printf ですがこれは format つき print ということです。フォーマット (書式)つきプリント関数の意味です。ここで書式とは ・データを何文字幅で出力するか ・データは 10 進数なのか、8 進数なのか、または文字なのか を指定するものです。 プログラムで使われた printf("ans = %d¥n", a); の意味を考えてみましょう。まず ans = と表示します。そのつぎに%d があります。これは「対応する変数の内容を 10 進数で表示しなさい」 という指示です。したがって対応する変数 a の値が 10 進数で出力され ans = 30 となります。 変数 a の値を 10 進数で出力する printf(" n a n s = %d ¥n そのまま出力する 図 4-3 printf 文の役割 13 ", a); 改行する 4 章の練習問題 1) 自分の名前を画面に表示させてください。改行位置などは自由ですが、自分のスタイルを決 めておいて下さい。 2) ¥n を使わずに、printf 関数を 3 つ使用して下さい。 3) 画面に1行おきに 3 つ「Hello, World!」と表示させて下さい。 14 5. プリプロセッサ プログラムをコンパイルするとき、コンパイル前にプリプロセス(前処理)という処理が行われます。 プリプロセスを行うために内部で動作しているプログラムを、プリプロセッサといいます。 5-1. #include #include の基本形式は #include <stdio.h> この記述は『コンパイル時に stdio.h ファイル(あるいは file)を読み込みなさい』という意味です。 このように記述することによりコンパイル時に stdio.h ファイルが読み込まれます。h がついたファイル をヘッダファイルと呼び、C 言語のコンパイラが必ず持っているヘッダファイルを、標準ヘッダといい ます。 標準ヘッダには、printf 関数や scanf 関数などのライブラリ関数の関数プロトタイプが入っています。 ですから、stdio.h をインクルードしないと、これらの関数が使用できないのです。 ですから初心者はあまり難しいことは考えずに プログラムの先頭に「#include <stdio.h>」と書く ということを覚えておいてください。これは決まり文句です。 5-2. #define #define ディレクティブは、 ソース上の文字の並びを、他の文字の並びに置き換えるマクロ置換の役 割を持ちます。「ソース上の文字の並び」というのが重要です。'a'や"abc"のような意味の文字ではあり ません。#define の形式は次のようになります。 #define (記号定数名) (置換後の文字の並び) #define より後ろにある「記号定数」で指定された文字の並びは、 「置換後の文字の並び」に置き換え られます。 #define MAX 10 と書いた場合には、これより後ろにある MAX はすべて 10 に置き換えられます。 5-3. #undef #define とセットで使われるのが、#undef ディレクティブです。これは、#define の効果を打ち消す 役割を持ちます。形式は次のようになります。 #undef (記号定数名) 指定された記号定数は、#undef より後ろでは無効になります。これは、ある関数内でのみ有効な記号 定数を作りたいときなどに便利です。同じ名前の記号定数を、有効にしたり無効にしたりを何度も繰り 返しても構いません。 15 6. 定数と変数 C 言語で扱われるデータは大きく「定数」と「変数」に分けられます。 定数(じょうすう):値を変えないもの 変数:任意の値をとりうるもの 6-1. 定数 定数にも型があり、それぞれの型によって記述方法が異なります。 定数は具体的に下記のように分類できます。 表 6-1 定数の分類表 2進数 先頭に"0"をつけて表記 整数定数 10進数 通常の10進数と同じ表記 数値定数 16進数 先頭に"0x"をつけて表記 実数定数 小数点以下が扱える数 一文字のこと 文字定数 文字列定数 複数文字のこと 表記例 00001100 12 0x0C 16.25 'A' "ABCDEFG" 10011100 156 0x9C 1.0E-03 'B' "computer" *1.0e-3 は 1.0×10-3(0.001)のことです。C 言語では実数をこのようにも表記します。 MEMO 2 進数とは数を 0 と 1 だけで表現した数 16 進数とは数を 0 から 9 と A から F で表現した数 10進数 2進数 0 0 1 1 2 10 3 11 4 100 5 101 6 110 7 111 8 1000 9 1001 10 1010 10進数 2進数 11 1011 12 1100 13 1101 14 1110 15 1111 16 10000 10進数 16進数 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 A 16 10進数 16進数 11 B 12 C 13 D 14 E 15 F 16 10 6-2. 変数 変数とは、名前の通り「変化する数」です。プログラム実行中のある時点で 100 だったものが、他の ある時点では 200 になっているかもしれない。そういう数です。「数」といいましたが、実際には数に 限りません。 「A」とか「AAA」とかいう文字や文字列も変数になり得ます。 変数を使うためには、まず変数を宣言しなくてはなりません。つまり「これからこういう変数を使い たい」という意思表明をするということです。変数を宣言するには、 「変数の型」と「変数名」を記述し ます。変数の宣言をすることでコンピュータのメモリ上に実際に作業エリアが用意されます。 表 6-2 変数の主な型 型指定 char int long float double データ型 バイト幅 扱える数値の範囲 文字型 1 -127 ~ 128 整数型 2 -32768 ~ 32767 倍長整数型 4 -2147483648 ~ 2147483647 単精度実数型 4 3.4E-38 ~ 3.4E+38 倍精度実数型 8 1.7E-308 ~ 1.7E+308 注)バイト幅と表現範囲は、代表的な値を示す。OS やコンパイラによって異 なる。 例えば float data; と記述するとメモリ上に「data」という変数名の実数を扱う 4 バイトの作業エリアが用意されますし、 int num; と記述すればメモリ上に「num」という変数名の整数を扱う 2 バイトの作業エリアを用意します。 また、特定の整数変数が常に正の値をもつことが前もってわかっている場合があります。例えばその 変数が何かをカウントする目的で使用される場合などです。このような場合に、型の前に unsigned を 付けて符号なしの変数を宣言することができます。 unsigned int vol; このような宣言の場合、扱える数値の範囲は-32768 ~ +32767 から 0 ~ +65535 に変化します。int を unsigned として宣言することで最大の正の値のサイズが 2 倍以上になります。 *注意点として変数名として使える文字の種類には決まりがあります。 ①先頭の文字は英字(a~z、A~Z)または下線(_)でなければならない。 ②2 文字目以降は英字、下線、数字である。 ③大文字と小文字を区別する。 ④先頭から最低 31 文字までが有効である。 17 6-3. 変数の初期化 宣言時に値を代入することを初期化と呼びます。変数は宣言時に必要な値で初期化するか、宣言後に 必要な値を代入しなければなりません。宣言しただけでは、不定値(ゴミ)が入っているからです。 具体的に見ていきましょう。 int a; float b; このままでは宣言しただけなので不定値が入ってます(図 6-1)。 4 バイト 2 バイト ・・・ (不定値) a に割り振られた作業エリア b に割り振られた作業エリア ・・・・・・・・・・(不定値) 図 6-1 変数宣言した時の作業エリアの状態 仮に初期値を 0 にしたい場合には、変数名に続けて「*** = 定数;」を記述します。もちろん定数 は変数を初期化するための数です。 int a = 0; float b = 100.0; 4 バイト 2 バイト a に割り振られた作業エリア b に割り振られた作業エリア 図 6-2 変数宣言して値を代入した時の作業エリアの状態 18 6 章の練習問題 1) コメントを参考に次のプログラムの空欄部を埋めて、プログラムを完成させなさい。 #include <stdio.h> int main( void ) { ; /* ; /* ; /* ; /* ; /* ; /* 変数 変数 変数 変数 変数 変数 a と b を単精度実数型で宣言 */ c を倍精度実数型で宣言 */ seki を倍長整数型で宣言 */ i を整数型で宣言し、180 で初期化 */ j を整数型で宣言し、500 で初期化 */ ch を文字型で宣言し、文字定数 'S' で初期化 */ a = 62.5; b = 23.3; c = a * b; seki = ( long )i * j; printf( "ch = %c¥n", ch ); printf( "c = %f¥n", c ); printf( "seki = %ld¥n", seki ); /* ch を出力 */ /* c を出力 */ /* seki を出力 */ return 0; } 2) 次のプログラムを実行すると、画面に何と表示されるか答えなさい。 #include <stdio.h> int main() { int num = 4; int ans; ans = ( (10 * num) - (num * 2) ) / num; printf( "答えは%d¥n", ans ); return 0; } 3) char 型の変数に適当な文字を代入した後、その変数を printf 関数(%d を使う)を使って表 示させてみて下さい。 19 7. 演算子 演算子はプログラム中の式の中で使用するもので、各種の演算を指示するために使います。C には 40 種類以上の演算子がありますが、ここではとくに基本的なものについて学習します。 7-1. 代入演算子 代入も1つの式になります。等号の右側の値を左側の変数に代入します。数学の = とは逆ですので 注意してください int a,b; a = 3; 変数 a に定数 3 を代入 a = b; 変数 a に定数bを代入 a = a + 2; 変数 a に、自分自身に 2 を足したものを代入 なお、次のような代入はできません。 int a; 1 = a; a + b = 3 定数に変数は代入できません 式に変数や定数を代入できません 7-2. 算術演算子 演算子は加算、減算等各演算を表しており、その使われ方は日常とまったく同じです。 表 7-1 算術演算子の役割 演算 加算 減算 乗算 除算 剰余算 演算子 + * / % a a a a a 例 +b -b *b /b %b a a a a a 意味 に b を加える から b を引く に b をかける を b で割る を b で割った余り 例えば次の式は完全な C の文です。 int a; a = 1 + 2; この文の作用は、定数 1 と 2 を足し、その結果を(整数型)変数 a に代入するというものです。 20 7-3. インクリメント・デクリメント 変数を増減させるには代入式を使えば良いことがわかりました。しかし、カウンターなど単に1つ増 やしたい時のためにもっと簡単な表記法があります。それはインクリメントです。 表 7-2 インクリメント・デクリメント演算子の役割 演算 演算子 インクリメント ++ デクリメント -- 例 a++ ++a a---a a a a a 意味 に 1 を加える(後置演算) に 1 を加える(前置演算) から 1 を引く(後置演算) から 1 を引く(前置演算) なぜ前置と後置があるのでしょうか。実際に例文を用いて結果を比較してみましょう。 前置演算では先に処理(++ 、--)を行ってから数値の代入を行います。 int a, n; n = 2; a = ++n; この式の場合、結果は a = 3 と n = 3 となります。 後置演算では先に数値の代入を行ってから処理(++、--)を行います。 int a, n; n = 2; a = n--; この式の場合、結果は a = 2、n = 1 となります。 7-4. 関係演算子 関係演算子とは二つの式を比較して真偽を判定するものです。 表 7-3 関係演算子の役割 演算 大なり 小なり 大なり又は等値 演算子 > < >= 演算 小なり又は等値 等値 非等値 演算子 <= == != 使い方としては次の例のようになり、 「変数 a が 3 より小さいか」、を判定し2つの関係が正しければ 『真』、間違っていれば『偽』と判定されます。 int a = 10; a < 3; C 言語では真を 1、偽を 0 として出力する決まりがあり、前のプログラム例では変数 a が 10 で 3 より も大きいため偽と判断されます。 21 7-5. 論理演算子 論理演算子は 2 つ以上の条件式を結びつけるものです。 表 7-4 論理演算子の役割 演算 論理積(AND) 論理和(OR) 否定(NOT) 演算子 意味 && 両方の条件が真ならば 真 || どちらか一方の条件が真ならば 真 ! 否定条件 例として整数型変数 e1、e2 があるとき、演算結果は次のようになります。 表 7-5 論理演算結果 e1 e2 0 0 1 1 0 1 0 1 論理和(OR) || 0 1 1 1 論理積(AND) && 0 0 0 1 否定(NOT) ! 1 0 0 1 7-6. ビット演算子 C 言語のビット処理用演算子は、文字及び整数データを 2 進数形式で、明示的なビット列として取り 扱う能力を提供します。論理演算子との違いは 1 ビットごとに演算を行うことです。 表 7-6 ビット演算子の役割 演算 論理積(AND) 論理和(OR) 否定(NOT) 排他的論理和(XOR) 論理積(AND) 演算子 & | ~ ^ 表 7-7 論理演算の処理 論理和(OR) 論理否定(NOT) 対応するビットのどちら 対応するビットの両方が か一方でも1があれば1 1のときだけ1になる。 になる。 排他的論理和(XOR) ビットが0のとき1、1のと 対応するビットが異なる き0になる。 ときに1になる。 22 AND 演算の使い方をみてみましょう。 #include <stdio.h> int main(void) { int num1 = 0x01; int num2 = 0x00; printf( "%x & %x = %x¥n", num1, 0x01, num1 & 0x01 ); printf( "%x & %x = %x¥n", num2, 0x01, num2 & 0x01 ); return 0; } ビット単位の演算なので、2 進数で考えます。しかし、C 言語には 2 進数を表現する構文がないので、 多くの場合 16 進数を使います。1 つ目の printf 関数では、 0x01 & 0x01 という AND 演算を行ってい ます。共に1になっている部分のみ、結果も1になります。2 つ目の printf 関数では、一方だけが1の 場合には、結果は 0 です。これは 2 つ目の printf 関数で試しています。 00000001 00000000 00000001 (AND) 00000001 (AND) 00000010 (結果) 01011010 (結果) 図 7-3 ビットごとの AND 演算 ビット演算は、慣れるまでは非常に分かりにくいと思います。2進数で考えるだけでも厄介なのに、 C言語では2進数を表現できないのも問題でしょう。頭の中で考えられるようになるまでは、紙にでも 書きながら使ってみて下さい。ビット演算は確実に使いこなせるようになるべきです。 ビット演算の利点は、処理の速さにあります。XOR 演算のところで見た、0 と 1 を交互に繰り返す処 理も、分岐命令が無くなる分、高速です。ビット演算をうまく使うと、分岐命令を無くしたり、他の命 令の数も減らすことが可能です。 23 7-7. シフト演算子 ビットの列を、左方向や右方向にずらす操作をシフトといいます。例えば、8 ビットの 2 進数 01001001 を左方向に1ビットシフトすると、10010010 となります。左方向にシフトすることを 左シフト、右方 向にシフトすることを右シフトと呼びます。左シフトしたとき、空いた下位部分には自動的に 0 が入り ます。右シフトしたときに、空いた上位部分は、元の数が正数であれば 0 が入ります シフト演算を行うには、シフト演算子を使います。左シフトのときは << 、右シフトのときは >> と いう演算子を使います。 表 7-7 シフト演算子の役割 演算 右シフト 左シフト 演算子 a >> a >> n a << a << n 意味 変数aを右へ1ビットシフトする 変数aを右へnビットシフトする 変数aを左へ1ビットシフトする 変数aを左へ1ビットシフトする 例を見て見ましょう。 int a = 20; int left, right; left = a << 1; right = a >> 2; この結果 left は 40 となり 0 0 0 1 0 1 0 0 (元の値:20) 0 0 1 0 1 0 0 0 (left:1 ビット左シフトした結果) 図 7-3 左シフト演算の結果 right は 5 となります。 0 0 0 1 0 1 0 0 (元の値:20) 0 0 0 0 0 1 0 1 (right:2 ビット右シフトした結果) 図 7-4 右シフト演算の結果 実はシフト演算は、乗算や除算の代わりになります。1ビット左にシフトすると、元の数の 2 倍の値 になり、右にシフトすると元の数の 2 分の1になります。より一般化すると、n ビット左シフトすると、 2 の n 乗倍されます。 24 負の数を右にシフトする場合は、符号ビットがそのまま右へシフトされ、さらに符号ビットには負を 表す 1 が残ります。(算術シフトの場合) 1 1 1 1 0 1 1 0 (元の値:-10) 1 1 1 1 1 1 0 1 (右に 2 ビットシフトした結果) 図 7-5 負の数を右シフトした結果 7-8. 複合代入演算子 「a = a + b;」のような単純代入演算子を用いた演算は、「a += b;」のように、複合代入演算子を用い た形に書き換えることができます。 表 7-8 複合代入演算子の例 複合代入演算子を 使った例 a += b; a -= b; a *= b; a /= b; a %= b; a &= b; a ^= b; a |= b; a <<= b; a >>= b; 一般記法による例 a = a + b; a = a - b; a = a * b; a = a / b; a = a % b; a = a & b; a = a ^ b; a = a | b; a = a << b; a = a >> b; 25 7 章の練習問題 1) ( a + b ) * c / b を計算するプログラムを作りなさい。 ①a = 5.36、b = 8.47、c = 5.789 とする。 ②計算結果は、printf( ”結果 = %f¥n”, 「計算結果が格納されている変数」 ); を用いて 画面出力しなさい。 2) ある数 A とある数 B を XOR 演算し、その結果と B とを更に XOR 演算するとどうなるか考 えて下さい。そして、実際にプログラムを作って確かめて下さい。 3) 468 円の買い物をして 1 万円札を出したときのお釣りの札と硬貨の枚数をもとめなさい。 計算結果は、printf( "○○の枚数 = %d¥n", 「計算結果が格納されている変数」 ); それぞれ画面出力しなさい。 <実行結果> 五千円札の枚数 千円札の枚数 五百円玉の枚数 百円玉の枚数 五十円玉の枚数 十円玉の枚数 五円玉の枚数 一円玉の枚数 =1 =4 =1 =0 =0 =3 =0 =2 26 を用いて 8. 配列 今回学ぶ配列は変数が集まったタンスのようなものです。タンスを一言で説明すると、引き出しが集 まったものです。その引き出しに相当するものが変数であり 1 つの値をいれておくことができます。タ ンスの中身はどんなデータ型でも構いません。int 型配列、char 型配列などをつくることができます。 タンス セーター 配列 要素という スカート 値 シャツ 値 値 図 8-1 配列のイメージ 8-1. 1 次元配列 タンスの場合、 「3 番目の引き出し」という風にしてどの引き出しかを指定することができます。配列 では、配列名の後ろに添字をつけて、何番目の要素であるかを示します。ただし、C 言語では配列の先 頭要素は「0 番目」です。そのため、要素数 10 個の配列の場合、一番後ろの要素は9番目ということに なります。 『得点』配列 配列名(添字) 要素を指定する 得点(0) 80 得点(1) 55 得点(2) 100 図 8-2 配列の表し方 次の例を見てみましょう。配列の宣言は変数の型宣言と同じように行います。 #include <stdio.h> int main(void) { int a[3]; int total; 要素数 3 の int 型配列の宣言 a[0] = 2; a[1] = 4; a[2] = 9; total = a[0] + a[1] + a[2]; printf( "%d¥n", total ); 変数 total の内容の表示 } 27 実行すると“15”が表示されます。 よくやってしまうことですが、添字が本当に有効な範囲を示しているか、よく確認して下さい。要素 数が 10 個のとき、 10 以上の添字が全て不正です。また、どんなサイズであろうと、負数の添字も不正 です。 8-2. 文字と文字列 人間は「A」という字を表すのに A と記述しますが、コンピュータは A を 65 というコードで表しま す。この A に対応する 65 というコードを「ASCII コード」といいます(付録 2 参照) 。文字コードには たくさんの種類がありますが、コンピュータ用の英数字のコードとして最も広く用いられているのは ASCII コードです。 人間 コンピュータ(文字コード) A 65 B 66 図 8-3 文字コード 文字配列も変数と同様に宣言が必要です。 char str[5]; b[0] --- b[1] --図 8-4 b[2] b[3] --- --- b[4] 不定値 ---- 文字配列の宣言 (文字データ) メモリ上の 1 バイトに格納される。'(シングルクォート)で囲みます。 char moji = 'A'; (文字列データ) メモリ上の複数バイトに格納され、終端には終了コード「¥0」がつきます。” (ダブルクォート)で囲 みます。 char moji[4] = "ABCD"; 28 8-4. 2 次元配列 タンスのように引き出しが 1 列に並んだ配列を 1 次元配列といいます。しかし世の中にはコインロッ カーのように縦にも横にも箱が並んでいるものがあります。配列はこのような構造にすることができ、2 次元配列といいます。 「2 行目の 3 列 目」と指定する 図 8-5 2 次元配列のイメージ 2 次元配列を指定するには縦方向に何番目で、横方向に何番目かを示す必要があるので添字が 2 つ必要 になり、括弧[ ]で区切って添字を書きます。配列 A の 2 行目の 3 列目ならば「A[2] [3]」のように指定 します。 列 A[0][0] A[0][1] A[0][2] A[0][3] A[1][0] A[1][1] A[1][2] A[1][3] A[2][0] A[2][1] A[2][2] A[2][3] 行 図 8-6 3 行 4 列(3×4)の 2 次元配列 8-5. 配列の初期化 配列も、単体の変数と同様、宣言と同時に初期化できます。複数のデータがある場合には{}記号で くくって指定します。 int a[10] = {10,11,12,13,14,15,16,17,18,19}; { }の部分を、初期化リストなどということがあります。初期化リストに書いた初期値の個数が、配列 の要素数に満たない場合、足りない要素は「¥0」で初期化されます。逆に配列の要素数より多い場合に は、コンパイル時にエラーになります。 文字型の配列も初期化も行えます。 char b[10] = {'H','E','L','L','L','O','¥0'}; 29 自動的に 10 文字分の配列を定義し、初期値を設定します。文字列定数を使用して文字型配列を手宣 言する場合には定数の中の(文字数+1)個分の配列が必要となります。これは文字列の終端には必ず『¥0』 が存在するからです。 b[0] H b[1] b[2] E L b[3] L b[4] O b[5] ¥0 b[6] b[7] b[8] b[9] 不定値 ---- ---- ---- ---- 図 8-7 文字型配列の初期化イメージ 文字型配列は次のように初期化することも可能です。 char b[10] = "HELLO"; 文字型配列の初期化を行う場合には要素数を省略することが可能です。 単に char str[]; では、コンパイラは配列の大きさがわからないのでエラーを返します。 MEMO [¥0 : NUL] この値 0 の文字をエスケープシーケンスを用いて '¥0' のように表記します。ま た、この値 0 の文字をよぶときには「NUL」とよびます。 「NUL」は空ポインタ定 数の NULL(ヌルと読むのが一般的です)と区別するために「ナル」と読まれるの が一般的なようです。 文字列の終端には値 0 の文字を付けることをしっかりと覚えて置いてください。 30 8 章の練習問題 1) 整数型配列 input にデータが初期設定されている。この配列 input のデータを下図のよう に、整数型配列 output に代入しなさい。 また、output の内容を printf を用いてすべて表示しなさい。 [0] input 配列 18 [1] 25 [2] 46 [3] 11 [4] 3 [5] 76 output 配列 2) 文字型配列 str に次のデータが初期設定されている。この str のデータを下図のように、英 大文字 ⇒ 英小文字に変換しなさい。ただし、文字コードは ASCII コード(付録 2 参照)とす る。 また、str の内容を printf を用いて画面出力しなさい。 [0] str 配列 A [1] B [0] str 配列 a [2] C [1] b [3] D [2] c [3] d [4] ¥0 [4] ¥0 A は ASCII コード 65(10 進数)で表示されます。 a は ASCII コード 97(10 進数)で表示されます。 ASCII コードのように大文字と小文字のコードの差が一定のときは、'a' - 'A' のようにして コード値を計算することができます。 31 9. 制御構造 C 言語を初め、多くの高水準言語では「順次」 「選択」「反復」の 3 つの制御構造から構成されていま す。 1) 順次:処理が現れた順番に実行する形式 2) 選択:条件によって処理が分岐する形式 3) 反復:処理を繰り返し実行する形式 これまでのプログラムは、main 関数から始まり、順番に命令や関数呼び出しを実行していき、main 関数の終了とともに終わるという、流れが一直線のもの(順次)ばかりでした。今回からは、処理の流 れを途中で枝分かれさせてみます。ただし、C 言語のルール上、スタートとゴールが main 関数の開始 と終了であることは変わりありません。 9-1. if文 もっとも単純な分岐はキーワード if を使う方法であり、次のような形式で実行します。 if (条件) (文); この条件が真ならば この文を実行する 条件とは「変数 a が 10 よりも大きいなら」、 「変数 a と変数 b の値が同じなら」といったものです。 条件が真でない場合には文は実行されずプログラムはこの部分をスキップして先へ進みます。この文は 1 行でもかまいませんし、下の例文のようにすればいくつかの文を書くこともできます。 if (条件) { 文 1; 文 2; 文 3; } この条件が真ならば 文 1~3 を実行する 実際に if 文の使い方を見てみましょう。ある学校の生徒たちに歴史の試験を行い、点数が 95 点より も高い成績だと A という評価を与えることにします。文字型変数 grade で生徒の評価を表すとことにし ます。次に条件文を C 言語で表現するには原則として関係演算子を使います。6 章でも学びましたが評 価方法をまとめておきます。 表 9-1 関係演算子による評価方法 式 a == b a != b a<b a>b a <= b a >= b 真になる場合 aはbに等しい aはbに等しくない aはbよりも小さい aはbよりも小さい aはb以下である aはb以上である 32 整数型変数 score で点数を表すとするとプログラムを書くことができます。 int score = 98; char grade; if (score >= 95) grade = 'A'; もちろんこの生徒の評価は A となります。 しかし評価が A に満たない生徒についてはどうなるのでしょうか。if による制御構造の有効範囲は else を使って拡張することができます。 if (条件) 文 1; else 文 2; この条件が真ならば この文 1 を実行し それ以外は 文 2 を実行する これである条件によって分けられた 2 つのうち 1 つを選択することができるようになりました。クラ スの中に A をとる生徒と規定の点数に満たなかった学生の 2 種類に分けることができます。 if (score >= 90) grade = 'A'; else grade = 'F'; しかしこれでは A を取れない学生はすべて落第になってしまう厳しい基準です。そこで if の部分を修 正して必要なだけ細かく基準を設けることができます。もし実際の成績を決める基準が次のようになっ ているとすると 表 9-2 点数による評価基準 点数 90 - 100 75 - 89 60 - 74 0 - 59 評価 A B C F プログラムは次のようになります。 if (score >= 90) grade = 'A'; else if (score >= 75) grade = 'B'; else if (score >= 60) grade = 'C'; else grade = 'F'; 33 それぞれ else-if のところで選択項目の中から 1 つを選択している点に注意しましょう。else-if のうち 1 つでも条件を満たしていれば、その後の else-if はすべてスキップされます。最後の else には if はつい ておらず、ここまでたどりついた場合には 60 点より低い学生であり、F と判定されます。 if、if-else、else-if を流れ図にしておきました。図 8-1 をみて頭の中を整理してみましょう。 if 文 真 if-else 文 真 if 文 偽 if 文1 偽 文2 else-if 文 真 偽 if 文1 真 else-if 文2 真 偽 else-if 文3 偽 else 文 図 9-1 if 文パターンの流れ図 次に実際のプログラム中でよく用いられる論理演算子を使った if 文にも少し慣れておきましょう。 && (AND)、||(OR)を使えば条件式を論理的に結合し、それが真か偽かを調べることができます。先の 試験の点数であれば『数学と国語の試験は 80 点以上だが、英語が 60 点未満』などといったグループを 抽出することができます。 条件式 真になる場合 a > b && b == c a は b よりも大きく、かつ b は c に等しい c != d || d > b c は d に等しくないか、 または d は b よりも大きい 図 9-2 論理演算子を使った条件式 34 MEMO [if (a == b) と if (a = b) の違い] int a = 0; if (a == 0) printf("a は 0 と等しい¥n"); else printf("a は 0 と等しくない¥n"); というプログラムで、if (a = 0) と記述したとすると、コンパイルはエラーとはな らず、表示結果は「a は 0 と等しくない」になってしまうのです。 C 言語において ≠0 : 真 =0 : 偽 という決まりがあるのです。 つまり、if (a == 0) を if (a = 0) と記述すると、 a = 0 の代入文が働き、a は 0 になってしまいます。 9-2. while文 if 文を使った条件文についてはこれまでに説明したとおりです。ここではもうひとつの重要な構造、 ループ(反復)について説明をします。C でもっとも基本的なループ構造は while であり、次のように 使います。 while (条件) (文); この条件が真ならば この文を実行する 「条件式」には「処理を繰り返すのはどんなときか」を記述します。この条件式が「真」になれば、 「繰り返す処理」を実行します。 「偽」になったら、繰り返すのをやめて while 文を終了させます。わか りにくいので流れ図にしてみましょう。 ① 「条件式」を調べる。「真」なら②へ。「偽」なら④へ ① 条件式 偽 ② 「繰り返す処理」を実行。③へ 真 ②③ ③ 「繰り返す処理」が全て終了したら①へ戻る 文 ④ while 文を終了させて、次の処理へ進む ④ 図 9-3 while 文の動き 35 1 番最初に「条件式を調べる」を行っている点に注意して下さい。 「条件式」が「真」にならないと「繰 り返す処理」には進まないのですから、場合によっては「繰り返す処理」は1回も実行されないことが あります。 次のプログラムは 1 から 10 までの合計を求めるものです。 main() { int total = int count = while(count { count++; total += } } 0; 0; < 10) count; 「count」は 0 から始まるので 1 増やしてから「total」に加えます。その後も「count」を 1 つずつ増 やし、10 になるまで処理を繰り返します。 9-3. for文 前回、while について繰り返し処理を行いましたが、それをもっと計数的な繰り返し処理に適応した ものに for 文があり、次の形式で記述します。 for (初期化式; 継続条件式; 再初期化式) (文); for 文も流れ図にしてみましょう。 ① ① 「初期化式」を実行。②へ進む 初期化式 ② 「継続条件式」を調べる。 ② 継続条件式 「真」なら③へ 「偽」なら⑥へ 偽 ③ 「文」を実行。④へ ④ 「文」が全て終了したら⑤へ 真 ③④ ⑤ ⑤ 「再初期化式」を実行。②へ戻る 文 ⑥ for 文を終了させて、次の処理へ進む 再初期化式 ⑥ 図 9-4 for 文の動き 「初期化式」とは、最初に for 文が処理されるときに1度だけ実行される式です。 「継続条件式」の意 味は while 文のものと同じで、「真」のときループします。 「再初期化式」は「文」が最後まで終了した ときに実行される式で、ループする度(「文」が終了する度)に実行されます 36 先の 8 章で学んだ配列を用いた簡単なプログラムを書いてみましょう。 main() { int i; int data1[10], data2[10]; for(i = 0; i < 10; i++) { data1[i] = i; data2[i] = data1[i] * 2; } } このプログラムでは data1[ ]配列に 1~10 の値が代入され、data2[ ]配列にはその 2 倍である 2、4、6、 8…20 が代入されます。今回のプログラム例ではループをカウントする変数として「i」を用いています が、昔からの慣習でとくになにもない限り「i」を用いるのが一般的です。 9-4. do~while文 while と for による反復を学習しましたが、2 つとも前判定反復です。これはループに入る前に「継続 条件式」を実行するものです。一方ここで学習する後判定型ループはループの後で継続条件を判定する ので必ず 1 回は処理を実行します。 前判定型ループ ループ 継続条件 処理 条件式 始端に継続条 真 件がある 文 ループ 後判定型ループ ループ 終端に継続条 文 件がある 偽 処理 継続条件 ループ 条件式 真 図 9-6 前判定型と後判定型ループ 37 偽 do~while 文の形式は以下のようになります。 do { (文); }while(継続条件式); 条件式の最後にセミコロン(;)があることを忘れないでください。do while 文を使う場面はあまりあ りませんが、使う場面は「繰り返す処理であり、最低でも1回は実行されないといけないとき」です。 while 文で 1 から 10 までの合計を求めるプログラムを作りましたが、今度は do~while を用いて作って みましょう。 main() { int total = 0; int count = 10; do{ total += count; count--; }while(count < 1); } 今度は count が 10 から始まるので、まず total に count を加えて、count から 1 を引きます(デクリ メント)。その後もループの中で count から 1 を引きながら count が 0 になるまで繰り返します。 9-5. Switch文 分岐を作るには if 文を使うのが基本でした。しかし、if 文では一度に 2 方向にしか分岐させることが できません。もし 5 方向くらいに分岐させたいと思ったら、何個も if 文を書かなくてはいけません。 そこで、C 言語には、一度に 2 方向以上分岐させることができる命令が用意されています。それが switch 文です。 条件式の値が 条件式 定数式 1 定数式 2 定数式 m それ以外 定数式 1 と等しければ、文 1 定数式 2 と等しければ、文 2 文1 文2 ・・・・・ 文m 文x 定数式 n と等しければ、文 n それ以外ならば、文実行 図 9-7 switch 文の流れ図 switch 文は、 「条件式」に応じて分岐します。この「条件式」と「定数式」が一致しているかを調べ、 一致している部分の文を実行します。文はいくつあってもよく、if 文のように{ }で囲む必要はありませ ん。 38 switch 文の形式は以下のようになります。 switch (条件式) { case 定数式 1: 文 1; break; case 定数式 2: 文 2; break; ~~~~~~~~~~ case 定数式 m: 文 m; break; default: 文; break; } default は、「値」がどの case 値にも一致しないときに実行される部分で、default ラベルと呼ばれま す。default ラベルは不要であれば省略でき、default ラベルがないときに、どの case 値にも一致しな かったら、どの文も実行されません。 ラベルごとに、break 文を書きます。これが、そのラベル内で行われる処理の終わりを表しています。 break 文には、各 case ラベルや default ラベルの命令文から抜け出し、switch 文を終了させる役割があ ります。これ以外では、switch 文の終わりの } まで辿り着いたときに switch 文は終了します。 break 文は省略してもエラーにはなりません。しかし break 文がないと、case 命令文は終了すること なく、そのまま次の case 文(あるいは default ラベル)にまで侵入して、そこに書かれている命令文を 実行していきます。 int num= 20; switch( num ) { case 10: printf( case 20: printf( case 30: printf( break; default: printf( break; } "10¥n" ); "20¥n" ); "30¥n" ); "¥n"); このようなプログラムを作成した場合、num の値は 20 ですから、画面には 20 30 と表示されます。 39 気を取り直して case 文を使ったプログラムをみてみましょう。 #include <stdio.h> int main(void) { int num; printf( "1~4の数字を入力して下さい¥n" ); scanf( "%d", &num ); switch( num ) { case 4: printf( "*" ); case 3: printf( "*" ); case 2: printf( "*" ); case 1: printf( "*" ); break; default: printf( "入力された数字が不正です¥n" ); break; } return 0; } ここでは初めて「scanf」関数が出てきましたが、これは『キーボードから入力された数字を整数型変 数 num に代入する』という役割を持ちます。つまりこのプログラムはキーボードから入力された数字 を判定して、入力された数字分の“*”を表示するプログラムになります。 「case 1」以外に break 文が 入っていないのがポイントです。 9-6. continue文 continue 文は、現在のループ内処理を打ち切って、「条件式」の判定に戻る(do while 文では「条件 式」の判定に進むというべきか)命令です。つまり、 「ループ中の処理」から「条件式」へジャンプする 訳です。もう少し正確な continue 文の意味は、 「ループ中で continue 文以降の処理を省略する」です。 使い方は次のようになります。 while( 条件式 ) { continue; } 少々動きがわかりにくいのでプログラム例から動きを見てみましょう。 40 #include <stdio.h> main() { while(1){ …… if(…)continue; while(1){ if(…)continue; …… break; } …… break; } } while または for、switch 文の最後 に制御を移す(途中の処理をスキッ プする) 通常 if 文と併用され、ループ処理の中で、ある条件が成立する場合、それ以下の処理を行わずにスキ ップさせたりします。continue 文は、制御の流れをやや分かりづらくするので、それほど多用されるこ とはありません。 MEMO [無限ループをつくってみよう] 永遠に続くループ処理。while を使ってつくることができます。 while( 1 ) { } 何故 while(1) が無限ループになるのか? 1. C 言語では =0:「偽」、≠0:「真」 である 2. 「1」は「≠0」、つまり「真」 3. while(1) は「常に真」、つまり「無限ループ」 実際は break 文を使って「ある条件」で脱出できるようにしたりします。実際の プログラム中でもよく使われる常套手段といっていいでしょう。 41 9 章の練習問題 1) 次の流れ図に基づいてプログラムを作成しなさい。 ① ② はじめ はじめ 変数 a、b の宣言 total(変数) ← 0 i(変数) ← 1 キーボードから変数 a、b に数字を入力 ループ i <= 100 a は b よりも 大きい total ← total+i NO i ← i+1 YES ループ “a は b よりも大 “b は a よりも大 きい”の表示 きい”の表示 おわり total の表示 おわり 2) キーボードから入力したキーが、アルファベットの大文字ならば、小文字に変換して、画面 に出力しなさい。 それ以外の文字なら、そのまま画面に出力しなさい。ただし、文字コードは ASCII とする。 3) キーボードから整数を入力させ、その数を「2 倍」「3 倍」~「10 倍」した数を画面に表示 させるプログラムを作って下さい。ただし、このプログラムはキーボードから負数が入力され るまでの間は何度も繰り返されるものとします。 プログラムが完成したら流れ図も書いてください。 42 10. 標準入出力関数 printf と scanf は重要な関数です。いままで簡単な説明だけで printf、scanf を使ってきましたが、こ こで入門者として覚えたいことを正確に学習します。 10-1. printの使い方 printf は書式つきで文字列や変数値を出力する関数です。printf 関数の形式は以前から出てきている とおりです。 printf (書式指定文字列, 変数並び); 書式指定文字列には 2 重引用符(”)で囲まれた文字列か文字配列変数です。この文字列の中には次の 3 種の文字列を書くことができます。 ①変換文字列 <%で始まる> ②エスケープ文字列 ③一般文字列 <¥で始まる> <それ以外> 変換文字列は、対応する変数をどのような型に変換して表示するかを指示するものです。たとえば既 出の“%d”は対応する変数を 10 進数に変換して表示するものでした。変換文字列は%に「変換文字」 と呼ばれる英 1 文字を付加したものです。よく使われる変換文字列は次のようになります。 表 10-1 よく使われる変換文字列 変換文字列 意味 %o 8進数で出力する %d 10進数で出力する %x 16進数で出力する %f 浮動小数点として出力する %c 文字として出力する %s 文字列として出力する %e 指数形式で出力する printf の変換文字列の機能を確認するプログラムをつくってみましょう。 43 #include <stdio.h> main() { int a; float b; char str[]="print test"; a = 1230; printf("8 進 :%o¥n", a); printf("10 進 :%d¥n", a); printf("16 進 :%x¥n", a); b = 1234.56; printf("f :%f¥n", b); printf("s :%s¥n", str); } 実行結果です。 8 進 :2316 10 進 :1230 16 進 :4ce f :1234.560000 s :print test 変換文字列にはオプション指定子をつけることができます。オプション指定子は見栄えを良くするた めの補助というものです。その中でも重要なのがフィールド幅指定子と精度指定子です。以下が良く使 われるオプション指定です(図 10-1)。 数値の出力幅の指定 % フィールド幅 精度 実数の小数点以下の桁数 図 10-1 printf のオプション指定 44 変換指定文字 表 10-2 フィールド指定子の働き(変数の値が 123 のとき) 指定 %d %5d %-10d %2d 出力 説明 必要最低限な幅で出力される 先行スペース文字を入れて5文字で出力される 後にスペース文字を入れて10文字で出力される 指定が小さくても必要幅は確保される 123 □□123 123□□□□□□□ 123 注)表中の”□”はスペースです 表 10-3 精度指定子の働き(変数の値が 654.321 のとき) 指定 %f %12f 出力 654.321 □□654.321000 %9.2f □□□654.32 説明 標準の幅で出力される 小数点を入れて12桁で出力される 小数点以下の桁数は標準値となる 小数点を入れて9桁、小数点以下2桁で出力される 注)表中の”□”はスペースです 10-2. scanfの使い方 scanf は printf の逆の動作、すなわち書式つきの入力を行う関数です。書式指定文字列も変換指定子 も共通しているところが多くあります。scanf の形式は次のとおりです。 scanf (書式指定文字列, 変数並び); 書式指定文字列は、二重引用符で囲まれた文字列か文字列変数です。ここで「変数並び」となってい ますが、正確には変数のアドレス(変数の住所)を書きます。char、int、float などの場合には変数名 の頭に「&」 (アンパサンド)をつけるとその変数のアドレスがとりだせることを覚えてください。 たとえばキーボードから“111”という数字が入力されたとします。しかしプログラムはこの数字が 10 進なのか 8 進なのか文字なのか決定することはできません。それを決定するのが scanf の変換文字列 です。とくに覚えておきたい変換文字列は次のようなものです(表 10-4)。 表 10-4 scanf の変換文字列 変換文字列 %o %d %ld %x %f %lf %c %s 意味 8進数で読む 10進数で読む long型変数に10進数を読む 16進数で読む 浮動小数点(float型)として読む 浮動小数点(double型)として読む 1文字を読む 文字列を読む 45 変数にキーボードから数値を入力してみましょう。 main() { int num; printf("1 文字入力してください :"); scanf("%d", &num); } これは整数型変数 num を宣言してキーボードから入力された数字を num に格納するというものです。 動きをみてみましょう。 ①整数型変数 num の宣言 メモリ num 用にメモリに 2 バイト領域が確保される 1000 num 用に割り振られたアドレス→ 1002 1004 どのアドレスが割り振られるか はコンパイル時に勝手に行うの でユーザが意識する必要はない 1008 1010 ②キーボードから入力 1 2 ENTER を入力したとします メモリ ③num のアドレスに“12”を格納する 1000 num 用に割り振られたアドレス→ 1002 1004 なぜ「&」が必要なのか? 1008 もしなければ”1002”という住所がわか 1010 らず宛先不明となってしまうからです int 型は 2 バイトなのでメモリ番地が 2 個おきになっています 46 12 次は文字列を入力して読み取る場合です。 main() { char str[5]; printf("文字列を入力してください :"); scanf("%s", str); } ①文字型配列の宣言 str 配列用にメモリが確保される メモリ番地 str [0] 1000 str [1] 1001 str [2] 1002 どのアドレスが割り振られるか str [3] 1003 はコンパイル時に勝手に行うの str [4] 1004 str 用に割り振られたメモリ領域→ メモリ でユーザが意識する必要はない ②キーボードから入力 A B C D ENTER を入力したとします ③str のアドレスに“ABCD”を格納する scanf("%s", str); なぜ「&」が必要ないのか? 「str」といった場合、“配列 str の先頭ア メモリ番地 メモリ str [0] 1000 A str [1] 1001 B str [2] 1002 C str [3] 1003 D str [4] 1004 ¥0 ドレス(1000)を参照”という意味になる からです。 アドレスについては 12 章 ポインタで詳しくやります。このようなことをしている、というイメー ジをつかんでください。 scanf は空白類(スペース、改行、タブ)を読み込むことができません。scanf はスペースがくると読 み込みを停止します。そのため“This is a pen”といった文字列を読み込むことはできません。この場 合読み込まれるのは“This”だけです。空白も含めて読み込みたい場合には gets()という関数を用いま す。 47 10 章の練習問題 1) ( a + b ) / ( c * b ) を計算するプログラムを作りなさい。 ただし、a = 53.6、b = 84.7、c = 57.89 とする。 また、計算結果は、printf( )関数を用いて小数形式と指数形式の両方で画面出力しなさい。 2) 文字型配列 str[80] を宣言し、この str に好きな文字列をキーボードから入力しなさい。 入力したら、出力して内容を確認しなさい。 3) 次のような形式で計算式を入力させ、答えを画面に表示するプログラムを作りなさい。 “整数 演算子 整数” 「整数」と「演算子」の間には、それぞれ空白があり、「演算子」には+-*/の 4 つとします。 48 11. 関数のつくり方 4 章でも説明しましたが、C プログラムは「関数」という小さなプログラムの集まりで構成されます。 main() も実は関数ですし、printf() や scanf() も関数です。 一般にプログラミング言語では、メインプログラムのほかに、 ①サブルーチン ②ファンクション(関数) というプログラム単位を用います。ある規模以上のプログラムを main()だけで作成すると、ステップ 数の多い理解しにくいプログラムができあがってしまいます。ですから、プログラムはいくつかの関数 に分けて作成する方が、コンパクトで理解しやすいものとなります。 また、同じような処理を複数の個所で行っている場合、サブルーチンとしてその処理をまとめてしま うと全体のステップ数も減ることになります。 一方のファンクションとは、それを呼び出すと何らかの処理(計算)をしてくれて、その値を返して くれるプログラムです。 ①と②の違いはただ値を返すか、返さないだけで『それを呼び出すと何かの仕事をしてくれる』という 意味では同じです。そこで C 言語では両者をまとめて“関数”と呼ぶことにしました。ですから C には 値を返す関数と返さない関数が存在します。 一般の言語 メインルーチン プログラム サブルーチン ファンクション C 言語 プログラム メインルーチン 関数 図 11-1 C 言語の関数の概念イメージ 49 11-1. 関数 関数の説明は簡単に行いましたが、関数を使うことを「関数を呼ぶ」という言い方をします。流れ図 では、関数を呼ぶときに次の定義済み処理記号を用います。 表 11-1 定義済み処理記号 記号 名称 説明 定義済み処理 別の場所に定義された処理を表す。 プログラムの関数にあたる。 流れ図で見てみましょう。 はじめ ①A = 10 で戻る ●計算処理 入口 0 → A A + 10 → A 計算処理 出口 計算処理 ②A = 20 で戻る おわり 図 11-2 定義済み処理記号の流れ図 「計算処理」の流れ図が別の場所に定義してあります。定義済み処理の流れ図の端子記号は処理のは じめや終わりではないので一般に「入口」 「出口」などを書きます。変数 A に 10 をセットして「計算処 理」を呼ぶと A に 10 が足されて A=10 で戻ってきます。もう一度「計算処理」を呼ぶと A に 10 が再 度足されて A=20 で戻ってきます。次は流れ図を C のプログラムにしてみましょう。 #include <stdio.h> int keisansyori(); main() { int a = 0; a = keisansyori(a); a = keisansyori(a); } int keisansyori(int x) { int y; y = x + 10; return(y); } 50 まず関数 keisansyori の先頭部を見て見ましょう。これが表記していることは次のとおりです。 引数として、int 型の変数 x を使います この関数の戻り値は int 型です (仮引数)と呼ぶ int keisansyori(int x) この関数名は keisansyori です 図 11-2 関数の意味 keisansyori 関数の中では受け取った引数に 10 を足すことを行っています。一方の呼び出し側(main 関数)の記述は次のような意味があります。 関数 keisansyori を呼ぶ(コールする) 処理結果は a に格納します a = keisansyori(a); 変数 a を、関数 keisansyori に渡す(実引数) 関数との間でやりとりするデータのことを引数(ひきすう)と呼びます。引数は関数呼び出し側でも 関数定義側でも書きます。このとき関数定義側ではどんな引数を使って呼び出しがくるか予想ができま せん。そこで関数 keisansyori では仮に引数を x として処理方法を記述しています。 そこでこれを仮の引数という意味で 仮引数 と呼んでいます。 また呼び出し側(今回は main 関数)では、実際に値をもつ引数(a のこと)で keisansyori 関数を呼 び出しています。そこでこれを実際の引数という意味で 実引数 と呼んでいます。 この仮引数と実引数は型をあわせるようにしてください。実引数が int 型ならば仮引数も int 型にし ます。 51 11-2. 値の返し方 関数でもし値を返す(関数 → 呼び出し側へ)必要があるときは、return 文を使います。return 文 の例を見てみましょう。 return return return return a; (a); (0); (a + b); 変数 a の値を返す 括弧()をつけるのが一般的 直接値を書いてもよい 数式を代入することも可 return 文を書くことで強制的に呼び出した関数側に戻ることができますが、戻り値が不要ならば値を 書かずに return と書きます。 return 文は必要ならば、ひとつの関数の中にいくつ書いてもかまいません。処理の過程で return 文 に会うとそこで処理を打ち切り呼び出し元に戻ります。 11-3. void型関数 return 型は、返す関数のデータ型です。ですからデータ型の数だけ return があることになります。 ところがこれでは表現されない return 型があります。それは返す値がない(必要ない)場合です。 C の関数には値を返さないものがあります。その場合には 「この関数は値を返しません」 ということを表現しなくてはいけません。それには void 関数というものを使います。 #include <stdio.h> void test(); 関数 test は戻り値がない void 関数であることを宣言する main() { test(340); } void test(int a) { printf("%d¥n",a); } ただ引数を表示するだけの関数 当然ながらプログラムの実行結果は 340 になります。return 文を使っての値戻しがありません。 void とは「空の」という意味を持っています。関数の型としても、プロトタイプ宣言としてもこの void を使います。 52 11-4. 関数間のデータ渡しの方法 関数を実際に記述するためには『どうやってデータを渡すか』をマスターしなければいけません。そ の中で重要なのは①数値を渡す方法と、②文字列を渡す方法です。 数値を渡す方法は今までのサンプルプログラムの中でも用いてきた方法です。2 つの値を足すプログ ラムを例に見て見ましょう。 #include <stdio.h> double wa(); main() { double a = 11; double b = 22; double sum; sum = wa(a,b); } double wa(double x, double y) { double z; z = x + y; return(z); } 値が渡された後は、変数 a と x、変数 b と y はまったくの無関係となります。その後関数 wa の処理 が始まります。 呼び出し側 sum = wa(a,b) 関数(wa)側 値の渡し 仮引数 a 11.11 x 11.11 b 22.22 y 22.22 実引数 z = x + y を計算 sum 33.33 z 33.33 値の返却 図 11-1 値による渡し 53 関数にデータを渡したいのは単に数値だけとは限りません。配列を渡すことも実際のプログラムでは よくあることです。特に文字配列を渡す方法は重要です。配列を渡す場合には「アドレス渡し」とも呼 ばれ、その変数がおかれているアドレス(メモリ上の住所)を渡します。 たとえば char str[1000] という配列があったとします。これをすべて渡すのは大変です。char 型配列を 1000 回も代入しなく てはいけません。そこでアドレス渡しでは 呼び出し側関数 呼び出される関数 str の先頭アドレスだけあげるからあとはよろしく 図 11-2 アドレス渡しの方法 という方法をとっています。 しかし str の先頭のアドレスだけ教えられても情報は不足しています。呼び出される関数側では str の配列の長さがわかりません。しかし実際には str の最後尾には“¥0”が格納されており、文字列の終 わりの目印になることができます。 実際に文字列配列を渡して画面に表示してみましょう。 #include <stdio.h> void strprint(); main() { static char str[10] = "abcde"; strprint(str); } void strprint(char a[]) { printf("%s¥n", a); } 上記プログラムで仮に配列 str[ ]のメモリアドレスが 1000 番地から格納されていたときのデータ渡し のイメージは次のようになります。 54 呼び出し側 呼び出される関数側 渡されたアドレス str[]を 1000 番地から確 を 参 照 し に行 く こ 保したとする とで配列の中身 がわかる str a 1000 1000 不定値 a b c d e ¥0 - - - - 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 番地 番地 番地 番地 番地 番地 番地 番地 番地 番地 図 11-3 アドレスによる渡しのイメージ アドレス渡しでは、呼び出し側から呼び出される関数側へ単に先頭アドレス“1000”が渡されるだけ で実際の配列の中身が渡されていません。呼び出される関数側では渡されたアドレスをもとに実際の配 列の値を参照するのです。 MEMO [自分自身を呼び出そう] ある関数内で、その関数自身を再度呼び出すことを再帰呼び出し(リカーシブコ ール) といいます。再帰呼び出しの構文自体は非常に簡単です。普通の関数呼び出 しと同じです。 int func(void) { return func(); /* 自分自身を呼び出す */ } 再帰呼び出しの方法は上の通りです。しかし、これでは問題があります。この関 数を呼び出すと、プログラムはそのうち停止してしまいます。つまり、最初の func 関数の中で、次の func 関数を呼び出し、さらに次の func 関数を・・・のようにな ってしまうのです。 そのため、再帰呼び出しでは関数の呼び出しがどこかで止まり、呼び出し元に戻 ってこられるように作らなくてはなりません。 55 11-5. ローカル変数とグローバル変数 C の変数はローカル変数(局所変数)とグローバル変数(広域変数)に大別できます。それは変数の 通用範囲を決定します。 ローカル変数 グローバル変数 表 11-1 ローカル変数とグローバル変数 関数内で定義され、その関数内でのみ参照することができる 今まで学習していた変数はすべてローカル変数 関数外で定義され、どの関数からでも参照することができる グローバル変数の名前の重複は許されない グローバル変数の書き方は“関数の外側で、かつすべての関数よりも前に記述する”ことです。この ルールに従えばコンパイラはこれをグローバル変数として認識してどの関数からでも参照することがで きます。 #include <stdio.h> void goukei(int x); int g; グローバル変数 g の宣言 main() { int a; g = 20; a = 10; goukei(a); 変数 a の 通用範囲 } 変数 g の 通用範囲 void goukei(int x) { int i,sum = 0; for (i = 1; i<=x; i++) sum = sum +i; printf("合計 = %d¥n", sum); 変数 i, sum の通用範囲 printf("g = %d¥n", g); } 結果は次のように表示されます。 合計 = 55 g = 20 変数 g の値は main 関数のところで 20 になっているので goukei 関数で表示しても 20 になります。 56 一方のローカル変数は定義されている関数の中でしか通用しません。ですから main 関数内で定義さ れている変数 a を関数 goukei の中で使用することはできません。もしローカル変数の値を使いたいと きは引数として違う関数に渡してやります。 グローバル変数はどの関数からも参照できるので、一見便利そうですが、変数の衝突が起こりやすく、 どこからでも変数の値が変えられるかわかりにくいため、安全性が低くなります。 ですから、グローバル変数をあえて使うのは、 プログラム全体を統括する変数 プログラム全体の状況を記憶する変数 にとどめておいたほうがよいでしょう。 11-6. 記憶クラスの種類 グローバル変数・ローカル変数という概念上の区分けを説明しましたが、C 言語の変数にはもうひと つの分け方があります。それは記憶クラスによる区分けです。 表 11-2 記憶クラスによる区分け 関数内部で宣言され、宣言された関数の中でのみ使用可能。(= ローカ ル変数) 自動変数 [auto] 外部変数 [extern] 静的変数 「static] 関数実行中のみメモリ上のスタックに確保され、関数の実行が終了する と、メモリ上から削除される。 関数が呼ばれるたびに初期化を行う 明示的に初期化を行わないと、初期値は不定値になってしまう。 前章までの学習で使用していた変数はすべてこの自動変数。 関数外で定義され、定義以降のどの関数からでも使用可能。(= グロー バル変数) プログラム開始処理の前に一度だけ初期化を行う。 プログラム実行中に常に同じ場所に配置され値を保持。 明示的に初期化を行わない場合は、初期値は 0 になる。 プログラム実行中に常に同じ場所に配置され値を保持 → 値を保持した いときに使用。 関数内部で宣言され、宣言された関数の中でのみ使用可能。 プログラム開始処理の前に一度だけ初期化を行う。 明示的に初期化を行わない場合は、初期値は 0 になる。 実は今まで何気なく使ってきた変数ですがすべて自動変数です。自動変数の宣言は関数の実行が終了 するたびにメモリから削除されるためにメモリの有効活用をすることができます。 「自動的に作成されて、 自動的に削除される」ので自動変数です。 57 自動変数の宣言は auto int a; 自動変数の宣言はデータ型の前に auto をつけておこないます。これは他の char 型でもどのデータ型 でも同じです。ここで重要な省略ルールがあります。 『auto 型の “auto”の記述は省略しても良い』 というものです。つまりいままでの変数宣言には “auto”が隠れていたのです。 外部変数は関数外で定義されてどの関数からでも参照できるグローバル変数のことです。外部変数は プログラムが実行されている間は常にメモリに保存されており自動的に削除されることはありません。 関数の外に書かれているので外部変数といいます。 静的変数は、自動変数と外部変数の中間的な関数です。 『関数の中で定義して、その関数の中だけで使用できる。しかもその関数の処理が終わっても変数は 消滅しない』 という性質があります。静的変数は static をつけて宣言します。 #include <stdio.h> void max1(); void max2(); main() { max1(100); max1(50); max1(70); max2(100); max2(50); max2(70); } void max1(int c) { int max = 0; if (c > max) max = c; 今までの値よりも大きかったら最大値に設定 pritnf ("max = %d¥n", max); } void max2(int d) { static max = 0; if (d > max) max = d; 今までの値よりも大きかったら最大値に設定 pritnf ("最大値 = %d¥n", max); } 58 実行結果は次のようになります。 max = max = max = 最大値 最大値 最大値 100 50 70 = 100 = 100 = 100 max1 ではそれまでの最大値が保持できない max2 ではそれまでの最大値が保持できている max1 では max が auto 変数であるために呼ばれるたびに 0 に初期化されてしまい常に仮引数 c の値 が最大値として設定されています。そのためにこれまでの過去の最大値を保持できません。 max2 関数では max が static 関数であるために、過去の値を保持することができます。この static 変 数 max はコンパイル時に一度だけ 0 に初期設定されます。 59 11 章の練習問題 1) 次のプログラムの空欄部を埋めて、プログラムを完成させなさい。 /* 文字列を表示する関数 */ #include <stdio.h> void func(); int main( void ) { char str[] = "COMPUTER"; func ( return 0; ); } void func( ) { printf( "%s¥n", sr ); } 2) 次のプログラムの下線部を別関数にしてプログラムを作成しなさい。 #include <stdio.h> int main( void ) { int a, b , c; a = 20; b = 10; c = a * b; printf( "c = %d¥n", c ); return 0; } 3) キーボードから秒数を正数(正の整数)で入力し、それを「○時間○分○秒」の形に変換し て表示するプログラムを作って下さい。ただし、最低1つは自作関数を使いなさい。 60 12. ポインタ ポインタとは、「変数のアドレスを記憶する変数」と定義することができます。 C 言語の特徴にポインタが使用できることがあげられますが、ポインタから C 言語がわからなくなった という話もよく耳にします。ポインタが何を意味するのかひとつひとつ理解していきましょう。 12-1. 変数とポインタ 今までの章でも頻出していますが、ポインタを理解するにはまず「アドレス」とはまず何かを理解し ましょう。例えば int a = 123; と宣言した場合、図 12-1 のように、「メモリ上のある番地(下図では 1000 番地)に変数 a としての領 域を確保し、その領域に 123 を格納する」ということになります。 2 バイト 1000 変数aに割り振られたアドレス (何度も言うようだが実際の番地 はコンパイル時に決定される。 ここでは仮に 1000 とする) 123 1002 1004 図 12-1 アドレスのイメージ ポインタとはアドレス変数、つまり変数のアドレスを記憶する変数のことです。まだポインタに慣れ ないうちは「ポインタ」をすべて「アドレス変数」と読み替えてもいいかもしれません。 先ほどの int a = 123 ① の例で言えば 変数 a の値は 123 です という表現をすることができます。しかしもうひとつの表現として ② 1000 番地の値は 123 です と表現することができます。ポインタはこの「1000 番地の~」といった番地を記憶する変数なのです。 このポインタを操作するために次の二つの演算子が用意されています。 表 12-1 ポインタ演算子 & * 変数のアドレスをとりだす そのポインタの指示するアドレスにある値(中身)をとりだす 61 このポインタ演算子を使った基本的な記述を見てみましょう。 main() { int a = 123; int b; int *p; a; &a; p = &a; *p; b = *p; } 先ほどと同じく変数 a は 1000 番地に置かれていたとします。 変数宣言後の 5 行はどのような意味をもつのでしょうか。 1) a 変数 a の値は 123 です 2) &a 変数 a のアドレスは 1000 です 3) p = &a ポインタ p の値は 1000 です 4) *p ポインタ p の値は 123 です 5) b = *p b にポインタ p の値 123 を代入します つまり、この後に a、b、*p を出力するとすべて同じ値である 123 が出力されます。このときのメモリ の状態を見てみましょう. 2 バイト 変数aに割り振られたアドレス 123 1000 2000 変数bに割り振られたアドレス 123 3000 a にあるデ ータを b に 代入 p で示す アドレス ポインタpに割り振られたアドレス 9000 1000 番地 図 12-1 メモリの内容イメージ 62 12-2. 配列とポインタ ポインタは、変数のアドレスを格納するよりは、配列のアドレスを格納する方がずっと使用頻度は高 くなります。ポインタは色々な種類があり、int 型のポインタ、char 型のポインタなどがあります。 main() { char s[] = "ABCDE"; char *p; p = s; printf("s = %s, p = %s¥n",s,p); *p = 'm'; *(p+1) = 'n'; printf("s = %s¥n",p); } この実行結果は s = ABCDE, p = ABCDE s = mnCDE まず、最初の printf です。ここでは char 型配列 s[]を宣言しています。10 章でも説明したように、ここ で s は s[0]~s[5]という配列の先頭アドレスを示しています。そのため、配列の時は p = s; とするだけで配列 s[]の先頭アドレスが p に代入されます。単一変数のときはアドレスを示す“&”を 付けましたが配列は必要ありません。注意してください。 この時点でポインタ p と配列 s は厳密には次の違いがあります。 配列名 s →s[]の先頭アドレスを示す。s 自身の値を変更することはできない。 ポインタ p →s[]の先頭アドレスを示す。p 自身の値を変更することができる。 配列名 s 1000 1000 コピー s[0] s[1] s[2] a b 1000 番地 ポインタ p この値は変更できる s[3] s[4] s[5] s[6] s[7] s[8] s[9] c d e ¥0 - - - - 1001 1002 1003 1004 1005 1006 1007 1008 1009 番地 番地 番地 番地 番地 番地 番地 番地 番地 図 12-2 ポインタと配列名と配列用その関係 63 続く 2 個目の printf ですが、p を使って任意のアドレスを示すことができることを示しています。 *p p の示すアドレスにある値(s[0]にある A) *(p+1) p の示すアドレスに+1 した場所にある値(s[1]にある B) *(p+2) p の示すアドレスに+2 した場所にある値(s[2]にある C) となります。このポインタ情報を使って文字列を書き換えているのです。 実は「ポインタに 1 を加える」ということは 「ポインタの指し示すアドレス+1 番地」ではなく 「ポインタの指し示すアドレス+データの型サイズ」になります。 よって+1と書かれていても、char、int、float などの違いによってアドレスの増え方が異なるので す。 ポインタを使わないと、配列表現のために添字用変数を必要とします。しかしそれはたいした負担で はありません。どちらも表現可能なときどちらを使うかは好みの問題です。 表 12-2 配列名とポインタによるデータ指定方法の違い データ 配列名 p=s A s[0] *p B C D E ¥0 不定 不定 不定 不定 s[1] s[2] s[3] s[4] s[5] s[6] s[7] s[8] s[9] *(p+1) *(p+2) *(p+3) *(p+4) *(p+5) *(p+6) *(p+7) *(p+8) *(p+9) 64 12 章の練習問題 1) 次のプログラムの実行結果を答えて下さい。 #include <stdio.h> int main(void) { int array[5] = { 1, 2, 3, 4, 5 }; int *p; int i; for(i=0; i<5; ++i) { p = &array[i]; printf( "%d ", *p ); } printf( "¥n" ); return 0; } 2) 次のプログラムの空欄を埋めなさい。 #include <stdio.h> int main( void ) { int a = 611, *ptr; ptr = &a; /* ポインタの値設定 */ printf( "変数 a のアドレス = %p¥n", ); printf( "変数 a の値 = %d¥n", ); printf( "ポインタ ptr の値 = %p¥n", ); printf( "ポインタ ptr の指す値 = %d¥n", ); return 0; } 実行結果例 変数 a のアドレス = 0F98 変数 a の値 = 611 ポインタ ptr の値 = 0F98 ポインタ ptr の指す値 = 611 65 12 章の練習問題 3) ポインタを用いて、整数型の配列 101 個に、下図のように、順にその配列の添え字番目ま での総和を格納しなさい。配列の中身の表示も行いなさい。 a[0] ←0 a[1] ←0+1 a[2] ←0+1+2 a[3] ←0+1+2+3 a[99] a[100] ← 0 + 1 + 2 + 3 … + 98 + 99 ← 0 + 1 + 2 + 3 … + 98 + 99 + 100 66 13. 構造体と共用体 13-1. 構造体の定義と引用 複数のデータをまとめて扱うには配列を用いましたが、配列では同じ型のデータしかまとめて扱う事 はできません。 実際にプログラムを組んでいると、異なる型のデータをまとめて扱いたい場合がしばしばあります。 たとえば、学生の成績をまとめるときに、int 型の学生番号と、char 型配列の氏名と、double 型の点数 をまとめて扱えれば便利です。この章で学習する「構造体」は幾つかの異なる型のデータをまとめて 1 つのデータ型として扱うものなのです。 構造体は次のように宣言します。 struct 構造体タグ名 { データ型 メンバ名; データ型 メンバ名; : : }変数名; 「struct」というキーワードを付けることによって、構造体を宣言しているのだということをコンパ イラに伝えます。変数名は構造体タグ名と呼ばれ、自由に名前を付けることができます。上記の宣言は 構造体の様式を宣言しているだけですのでメモリ領域の確保は行っていません。 メモリ中に領域を確保するには今までと同じように宣言をする必要があります。 構造体の型枠の宣言 構造体の宣言 struct seiseki { int number; char name[20]; double score }test; struct seiseki std01 std01 number name[20] 2 バイト 20 バイト 図 13-1 構造体配列の例 67 score 8 バイト 構造体も一般の変数と同様、宣言時に初期化をおこなうことができます。 struct seiseki std01 = { { 9876, "TOKAI JIRO", 64.7 }, }; 構造体配列になっても初期化をおこなうことができます。 各配列要素ごとに{}で区切って記述します。 struct seiseki std02[20] = { { 9876, "TOKAI JIRO", 64.7 }, { 6543, "SHIMIZU TARO", 79.1}, { 4321, "SYONAN MIHO", 97.5 }; 今度は構造体の中身を参照してみましょう。 構造体の各メンバは、「構造体変数名.メンバ名」のようにピリオド(.)をつけて指定します。 std02 配列の SHIMIZU TARO さんの点数を表示してみましょう。 printf ("score = %d", std02[1].score); 実際にプログラムを見て見みましょう。 #include <stdio.h> struct seiseki { int no; char name[20]; double average; }; /* 学生番号 */ /* 氏名 */ /* 平均値 */ int main(void) { int i; struct seiseki seito1 = { 5, "KASAHARA", 83.5 }; struct seiseki seito2[20] = { { 1, "SAKURAI", 78.6 }, { 2, "NAGANO", 57.3 }, { 3, "TAKESHITA", 66.4 }, }; printf("%d %s %5.1f¥n¥n",seito1.no,seito1.name,seito1.ave rage); for(i = 0; i < 3; i++) { printf("%d %s %5.1f¥n", seito2[i].no, seito2[i].name, seito2[i].average); } return 0; } 68 実行結果は 5 KASAHARA 83.5 1 SAKURAI 78.6 2 NAGANO 57.3 3 TAKESHITA 66.4 2[i] になります。 ) 13-2. 構造体の入れ子 構造体のメンバには、通常の変数宣言で可能な宣言なら何でも記述できます。ですから、メンバに構 造体型の変数があっても構いません。構造体の中に構造体がある状態になるので、構造体のネスト(入 れ子)ということになります。ネストする構造体は、他の構造体になります。自身と同じ構造体をネス トすることはできません。 struct test{ int math; int japanese; int science; int social; } struct number{ char name[20]; struct test; }first; 上のプログラムの構造体の状態を図示すると次のようになります(図 13-2)。 number name[20] math test japanese science social 図 13-2 構造体の入れ子状態 69 構造体中に含まれる構造体の中のメンバを参照する場合には、構造体名をピリオド(.)で区切って指 定します。 number.test.science 13-3. 構造体へのポインタ 構造体配列をポインタで扱う手順は、一般の配列をポインタで扱う場合と全く同じです。ただし、構 造体へのポインタを参照するためには、 「構造体->メンバ名」のようにアロー演算子(->)を使う点に注 意して下さい。 #include <stdio.h> #include <string.h> main() { struct body{ char name[30]; int height; int weight; } kdata struct kdata *kp; kp = &kdata; 構造体へのポインタの宣言 ポインタに構造体の先頭アドレスを設定 strcpy(kp->name, "TOKAI TAROU"); kp->height = 184; kp->weight = 75; } kp kdata name[30] height weight ポインタ型の変数 構造体へのポインタを記憶できる 図 13-3 構造体へのポインタ 入れ子の構造体を、ポインタを使って指定する場合は、一見「number->test->math」のようにアロ ー演算子を続けて用いそうですが、これは間違いです。 test のメンバである math はポインタではな いのでアロー演算子ではなく、ドット演算子で結んでやらなければなりません。ですから、 「number->test.math」が正解となります。 70 13-4. 共用体 union(共用体)は、同一のデータ領域を複数個の異なるデータ型が共用するようにしたものです。 共用体は、宣言の「struct」が「union」になるだけで、それ以外は宣言の仕方や使い方などは構造体と 全く同じです。しかし、共用体は各メンバがすべて同じアドレスから割り振られている点が構造体とは 異なります。 union タグ名{ データ型 メンバ 1; データ型 メンバ 2; }変数名 共用体の使用手順は構造体と変わりありません。かならず共用体の型枠を宣言してから宣言を行いま す(メモリ領域の確保)。たとえば utest という共用体タグ名で次のような共用体を宣言すると、メモ リ上は図のようになります。 union utest{ char a; int b; long c; } union utest kyouyou a の領域 b の領域 c の領域 図 13-4 共用体の領域共有イメージ この変数 kyouyou のサイズは、含まれるメンバの中で最も大きいメンバのサイズ以上あります。ただ し、先程言ったように、実際に幾つかは分かりません。上の 3 つのメンバは領域を共有するのです。 各メンバへアクセスする方法は、構造体でメンバにアクセスする方法と全く同じです。つまり、ドッ ト演算子を使います。また、共用体のポインタ経由でアクセスする場合は、アロー演算子を使います。 71 共用体へ代入する場合には、使用したメンバの型に合わせた値を代入します。int 型メンバに代入する なら、代入元の値も int 型で解釈できなくてはなりません。 試しにサンプルプログラムを見てみましょう。 #include <stdio.h> union uTEST { long num; double d; char str[10]; }; int main(void) { union uTEST test; test.d = 3.2; printf( "%f¥n", test.d ); 3.2 を出力 printf( "%ld¥n", test.num ); 3.2 を long 型としてみて出力 return 0; } test.d の出力は 3.2000000 と出力されますが、test.num がどう出力されるかは環境(CPU などの違 い)によって異なります。 構造体を使えばよさそうな共用体ですが、利点もあります。 1) 構造体に比べて、メモリを節約できる 2) ある領域を分割して扱ったり、まとめて扱ったりできる 72 13 章の練習問題 1) 次に示す社員情報を構造体として作成し、内容を表示して確認せよ。 尚、データ表示は個数ではなく汎用性を考えて、社員番号「0」まで繰返して行うこと。 社員番号は必ず 5 桁で表示する。 社員番号 780 4004 87022 93042 95005 99009 0 氏名 神保直樹 相原彰子 本郷幸子 三上葵 佐々木翠 長崎宏美 役職 課長 主任 勤続年数 21 15 12 6 4 1 0 基本給 346780 223640 208760 176530 166700 150140 0 2) 次に示す生徒情報を構造体として作成し、それぞれの科目の平均点を構造体のポインタを用 いて求めなさい。 生徒番号 国語 数学 理科 社会 1001 85 74 63 90 1002 78 65 70 62 1003 89 92 88 76 1004 32 48 66 25 1005 92 76 81 98 -1 0 0 0 0 <実行結果例> 国語 平均 = 75.20 数学 平均 = 71.00 理科 平均 = 73.60 社会 平均 = 70.20 73 14. 標準ライブラリ関数 C 言語自体には入出力や文字列処理をおこなう機能はなく、これらはすべて標準ライブラリ関数によ り実行されます。コンパイラ本体はなるべく小さく、必要なものを外部機能に置く、という C 言語の思 想です。 例えば test.c というプログラムをコンパイルし、test.exe という実行ファイルを作る様子は次のよう になります。3 章でも学習しましたがもう一度復習してみましょう。 ソースプログラム(test.c) コンパイル 標準ライブラリ stdio.h のこと オブジェクトモジュール (ほかにもいろいろなヘッ ダファイルが存在する) リンク 実行ファイル(test.exe) 図 14-1 標準ライブラリの役割 14-1. 標準ライブラリ関数一覧 標準ライブラリにどのような関数があるかを一通り見ておくと役に立つでしょう。これらの関数は UNIX、WINDOWS といった特定の OS やマシン環境に左右されない仕様となっています。 表の中で見出しのあとに書かれているファイル名(stdio.h など)は関数を使うためにインクルードす る必要があるヘッダファイルです。 74 表 14-1 入出力関数(stdio.h が必要) 関数 clearerr fclose feof ferror fflush fgetpos fgetc fgets fopen fprintf fputc fread freopen fscanf fseek fsetpos ftell fwrite getc perror printf putc putchar puts remove rename rewind scanf setbuf setvbuf sprintf sscanf tmpfile tmpnam ungetc vfprintf vprintf vsprintf 説明 指定したファイルのエラーフラグとEOFフラグを0にする ファイルのクローズ ファイルのエンド検出 ファイルエラー検出 バッファに入っているすべての文字ファイルの書き出し ファイルポインタの位置情報を読み込む ファイルから1文字入力 ファイルからの文字列入力 ファイルのオープン ファイルへの書式付出力 ファイルへの1文字出力 ファイルからのブロックリード ファイルの再オープン ファイルからの書式付入力 ファイルポインタを移動 fgetposで読み込んだ位置情報で、ファイルポインタを指定 現在のファイルポインタの位置を返す ファイルへのブロックライト ファイルから1文字入力 stderrへエラーメッセージを出力 書式付出力 ファイルへの1文字出力(マクロ版) 1文字出力 文字列出力 ファイルを削除 ファイル名を変更 ファイルポインタを先頭へ移動 書式付入力 バッファリングの制御 バッファリングの制御 書式付データを文字列変数に書き込む 書式付データを文字列変数から読み込む テンポラリファイルを作成 衝突しないファイル名を作成 ファイルへの1文字戻し ポインタで引数並びを指示する fprintf ポインタで引数並びを指示する printf ポインタで引数並びを指示する sprintf 75 表 14-2 一般ユーティリティ関数(stdlib.h が必要) 関数 abort abs atexit atof atoi atol bserach calloc div exit free getenv labs ldiv malloc qsort rand realloc srand strtod strtol strtoul system 説明 プロセスを強制中断する int型データの絶対値 プログラム終了時点での関数実行 文字列をdouble型に変換 文字列をint型に変換 文字列をlong型に変換 バイナリサーチ 配列のためのメモリブロック確保 商と余りの計算 処理終了 メモリブロック解放 環境変数の値の取得 long型データの絶対値 long型データの商と余りを計算 メモリブロック確保 クイックソート 整数の疑似乱数を返す メモリブロック再確保 疑似乱数の発生系列を変更する 文字列をdouble型に変換 文字列をlong型に変換 文字列をunsigined long型に変換 OSのコマンド実行 表 14-3 時間処理関数(time.h が必要) 関数 asctime clock ctime difftime gmtime localtime mktime strftime time 説明 時間情報を文字列に変換 実行開始からの経過時間を返す システムクロック時間を文字列に変換 time1とtime2の差を秒単位で計算 グリニッジ標準時間 値の秒情報を時刻格納構造体に変換 構造体時間情報を秒情報に変換 日付と時間情報を書式する 現在の時間を1970年1月1日00:00:00からの経過秒で返す 76 表 14-4 文字列処理関数(string.h が必要) 関数 memchr memcmp memmove memcpy memset strcat strchr strcmp strspn strerror strlen strncat strncmp strncpy strpbrk strrchr strspn strstr strtok 説明 指定した文字列から指定した文字を検索 範囲を指定できる文字列比較 コピー領域が重なっても正しくコピーする 指定した数の文字を、あるバッファからあるバッファへコピー 指定文字列の指定バイト数を、指定した文字で初期化 文字列の連結 文字列を先頭からサーチし、指定文字列を探す 文字列の比較 指定した複数の文字を検索 エラー番号に対応するエラーメッセージを返す 文字列の長さを得る 文字数を指定できる文字列凍結 文字数を指定できる文字列比較 文字数を指定できる文字列複写 指定文字列に含まれるどれかの文字が現れる位置を探す 文字列を後ろからサーチし、指定文字を探す 「指定した複数の文字」以外の文字を探索 文字列の中から指定文字列を検索 文字列からトークンを切り出す 14-2. ファイルのオープンとクローズ プログラムでファイル入出力をするとき、いきなりファイルを開いて中の文字を読んだり書いたりす ることはできません。ファイルの入出力は以下の手順でおこなわれます。 FILE 型 構 造 体 ファイルポインタ宣言 へのポインタ ファイルのオープン エラー 終了 ファイルの読み(書き) ファイルが開けない ファイルクローズ ファイルが存在しない 図 14-1 ファイル操作手順 ファイルは画面出力とは異なり出力結果の保存が可能ですので、覚えておくと何かと重宝します。 77 FILE 構造体では ・入出力の現在位置(ファイル位置指示子) ・ファイルの終端に達したかの情報(ファイル終了指示子) ・エラー情報(エラー指示子) ・関連するバッファへのポインタなど ファイルの入出力を行う上での必要不可欠な情報を管理しています。 ですから、ファイル入出力を行う際には必ず、このファイルポインタを fopen()によって取得しなけ ればなりません。また、入出力操作の完了とともに必ず fclose()でファイルクローズしなければなりま せん。ファイルを開くだけのサンプルプログラムを見てみましょう。 #include <stdio.h> int main(void) { FILE *fp; char s[256]; ファイルポインタの宣言 if ((fp = fopen("smpl.txt", "r")) == NULL) { printf("file open error!!¥n"); exit(1); ① } fclose(fp); } ①の部分はやや複雑ですが 1) ファイルポインタ fp を使って、“smpl.txt”というファイルを読み込み専用(r)で開く 2) 開けなければ “file open error!”の表示をして、exit (1)の値を返す 3) ファイルポインタ fp をクローズする ということになります。 fopen と fclose について詳しく見ていきましょう。 ファイルの作成やオープンするためには「fopen」関数を使用します。オープンするファイル名やオー プンしたファイルをどのように扱うかを引数に渡します。形式は次のとおりです。 FILE *fopen(char *filename, char *openmode) filename で示されるファイルを、openmode で示されるモードでオープンする。 ファイルが正しくオープンされれば、ファイルポインタ(FILE 構造体)が返される。 エラーのときは NULL(0)が返される。 オープンモードは以下のとおりです(表 14-5)。 78 表 14-5 モード r 動作 読み込みモード w 書き込みモード a 追加書き込みモード fopen のオープンモード 説明 ファイルが存在しなければエラーを返す ファイルが存在しなければ新規作成する ファイルがあればサイズを0にする ファイルが存在しなければ新規作成する ファイルがあればファイルの最後に追記する ファイルをオープンしたら、クローズしなければなりません。一度に開くことができるファイルの数 は限られていますし、fprintf などの書き込み操作を行った時点ではディスクに書き込まず、バッファに 書き込まれます。クローズ処理(実際にはフラッシュ処理)を行ったときにはじめてディスクに書き込ま れるからです。 fclose の形式は次のとおりです。 int *fclose(FILE *fp) fclose で、オープンされていた fp ファイルポインタで示されるファイルをクローズする。エラーのと きは EOF が返される。 14-3. ファイルの入出力 ファイルの入出力に関する関数をまとめます(表 14-6、表 14-7)。 表中の「EOF」は「End Of File」の略で、ファイルの終了を意味する略語です。 「EOF」は「stdio.h」 の中で#define で定義されており、実際には“-1”を示します。 表 14-6 関数名 書式 機能 戻り値 関数名 書式 機能 戻り値 関数名 書式 機能 戻り値 ファイルの読み込み関数 fgetc int fgetc(FILE *fp); ファイルポインタfpから1文字読み込む EOF:エラーもしくは、ファイル終端 それ以外:読み込んだ文字 fgets char *fgets(char *string,int n,FILE *fp); ストリームfpからbufferのサイズnバイト分をbufferに1行読み込む NULL:エラーもしくは、ファイル終端 それ以外:読み込んだ文字列の格納場所 fscanf int fscanf(FILE *fp,const char *format, ...); ファイルポインタからデータを読み込み解釈する。 EOF:エラーもしくは、ファイル終端 それ以外:読み込んだ項目数 79 表 14-7 関数名 書式 機能 戻り値 関数名 書式 機能 戻り値 関数名 書式 機能 戻り値 ファイルの書き込み関数 fputc int fputc(int character,FILE *fp); 文字characterをストリームfpに書き込む EOF:エラーもしくは、ファイル終端 それ以外:書き込んだ文字 fputs int fputs(const char *string,FILE *fp); 文字列stringをストリームに出力する。改行文字'\n'は付加されない ファイルを現在位置へ、pからNULL文字までファイルに書き込む ファイルポインタを書き込んだ分進める 文字列の最後にはNULLを自動的に格納する fprintf int fprintf(FILE *fp,const char *format, ...); フォーマットformatを指定してテキストをストリームfpに出力する 書き込んだバイト数だけファイルポインタを進める 次のサンプルプログラムはファイルを読み込んで 1 行ずつ出力するプログラムです。 #include <stdio.h> int main(void) { FILE *fp; char filename[40],s[256]; printf("ファイル名を入力してください:"); gets(filename); if ((fp = fopen(filename, "r")) == NULL) { エラー処理 printf("file open error!!¥n"); exit(1); } while(fgets(s,256,fp)!=NULL) ファイルが終わるまで 1 行読み込み printf("%s",s); fclose(fp); } このプログラムはキーボードからファイル名を入力させていますが、間違ったファイル名が入力され ると下記のように出力されます。 ファイル名を入力してください:sample.txt file open error!! ファイル名が正しく入力されればファイルの内容が出力されます。 80 次は test.txt ファイルを追加書き込みモードでオープンし、キーボードから入力された任意の文字列 を追加書き込みする例です。 #include <stdio.h> int main(void) { char buf[11]; FILE *fp; int i; puts( "10 文字以内の文字列を入力して下さい: "); fgets( buf, 10, stdin ); fp = fopen( "test.txt", "w" ); if( fp == NULL ){ puts( "test.txt が開けません" ); return 1; } for(i=0; buf[i]; ++i){ fputc( buf[i], fp ); } fclose( fp ); return 0; } 今度のプログラムではファイルが開けない場合のエラー処理が少し異なります。先ほどとの違いを見 比べてください。10 文字以内なので配列 buf の添え字は 11 まで必要になります。 また 10 文字以上入力されたときのエラー処理を考えてみるのも面白そうです。 14-4. コマンドラインから引数を渡す方法 14-3 で学んだ入出力のプログラムでは、 ①コマンドを入力する ②入力ファイルを指定する (変数に格納する) ③出力ファイルを指定する (変数に格納する) というように会話型でおこなってきました。この方法でも構いませんが、一般にはこのようなプログ ラムの場合、必要なファイル名を A> test input output プログラムファイル名 入力ファイル名 出力ファイル名 のようにコマンドラインから直接入力する方法がとられます。熟練者にはそのほうがスムーズですし、 会話がないほうがバッチファイルにも対応しやすい利点があります。このような処理を実現するために はキーボードなどから入力したコマンド文字列をプログラムに引き渡してくれるメカニズムが必要とな ります。 81 14-3 のプログラム(ファイルを読み込んで出力するプログラム)をコマンドライン引数対応にしてみ ましょう。 #include <stdio.h> 決まり文句の仮引数 main(int argc, char *argv[]) { FILE *fp; char s[256]; if(argc!=2){ printf("引数の数が違います¥n"); } 引数の数のチェック if ((fp = fopen(argv[1], "r")) == NULL) { 引数を使ってファイル printf("ファイルがオープンできません¥n"); をオープンする exit(1); } while(fgets(s,256,fp)!=NULL) ファイルの内容を画面 printf("%s",s); に出力する fclose(fp); } コマンドラインから引数を渡す場合の main 関数の頭書きは main(int argc, char *argv[]) とします。これは決まり文句です。この決まり文句を書くことにより、プログラム本体の中で引数の データを使うことができます。 A> program argv[0] ブランク argv[1] 図 14-2 ・・・ %d ¥n output.txt input.txt ブランク argv[2] ブランク argv[3] コマンドライン引数の役割 argv[0]には 0 番目の引数文字列が格納され、argv[1]には 1 番目の引数文字列が格納されます。argc には引数の数(図 14-2 の場合なら 3 個)が格納されます。 82 付録 1 JIS による流れ図記号 記号 名称 入出力 説明 情報を入力可能にする入力機能、または処理済 みの情報を出力する出力機能を表す。 書類 プリンタなどで印字するデータを表す。 処理 あらゆる種類の処理機能を表す。 定義済み処理 サブルーチンやモジュールなど別の場所で定義さ れた処理を表す。 準備 処理する前の準備として、初期値設定などを表 す。 判断 記号中に定義された条件にしたがって、唯一の出 口を決める判断機能を表す。 ループ端 ループのはじめと終わりを表す。記号の2つの部 分は同じ名前を持ち、始端または終端の記号の 中に終了条件を表記する。 順次アクセス記憶 順次アクセスのみ可能なデータを表す。媒体とし ては磁気テープ、カートリッジテープなど。 直接アクセス記憶 直接アクセスのみ可能なデータを表す。媒体とし ては磁気ディスク、フロッピーディスクなど。 手操作入力 手で操作して情報を入力するあらゆる種類の媒 体上のデータを表す。キーボード、マウスなど。 表示 人が利用する情報を表示するあらゆる種類の媒 体上のデータを表す。ディスプレイなど。 端子 プログラムの開始、終了、停止、中断など、流れ 図の端子を表す。 結合子 流れ図のほかの場所への出口、または他の場所 からの入り口を表す。 線 記号を結びつけて、データまたは制御の流れを表 す。 83 付録 2 ASCII コード表(16 進表示です) → ト ッ 下 位 3 ビ →上位3ビット 0 1 2 3 4 5 6 7 0 NUL DLE SP 0 @ P ` p 1 SOH DC1 ! 1 A Q a q 2 STX DC2 " 2 B R b r 3 ETX DC3 # 3 C S c s 4 EOT DC4 $ 4 D T d t 5 ENQ NAC % 5 E U e u 6 ACK SYN & 6 F V f v 7 BEL ETB ' 7 G W g w 8 BS CAN ( 8 H X h x 9 HT EM ) 9 I Y i y A LF/NL SUB * : J Z j z B VT ESC + ; K [ k { C FF FS , < L \ l | D CR GS - = M ] m } E SO RS . > N ^ n ~ F SI US / ? O _ o DEL 84
© Copyright 2024 Paperzz