プログラミング講習会 - WordPress.com · Visual Studio 2008...

Post on 08-Aug-2020

4 views 0 download

Transcript of プログラミング講習会 - WordPress.com · Visual Studio 2008...

プログラミング講習会 2011

慶応義塾大学ロボット技術研究会編

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

2

目 次

1. はじめに ...................................................................................................................................... 3 1.1. C 言語の概要と特徴 ............................................................................................................. 3 1.2. コンピュータ内部でのデータ .............................................................................................. 3 1.3. C 言語の実行 ........................................................................................................................ 4

2. C 言語の基本 ............................................................................................................................... 5 2.1. 文字を表示する .................................................................................................................... 5 2.2. 定数 ...................................................................................................................................... 8 2.3. 変数 .................................................................................................................................... 10 2.4. 変数を使った出力方法 ........................................................................................................ 11 2.5. 入力方法 ............................................................................................................................. 13 2.6. プリプロセッサの働き ....................................................................................................... 14

3. 演算・分岐・ループ ................................................................................................................. 16 3.1. 演算処理 ............................................................................................................................. 16 3.2. 条件分岐 ............................................................................................................................. 23 3.3. ループ処理 ......................................................................................................................... 29 3.4. 配列 .................................................................................................................................... 35

4. 関数 ........................................................................................................................................... 40 4.1. 関数の基本 ......................................................................................................................... 40 4.2. プロトタイプ宣言 .............................................................................................................. 44 4.3. 再帰処理 ............................................................................................................................. 44 4.4. ライブラリ関数 .................................................................................................................. 45 4.5. 変数の有効範囲 .................................................................................................................. 47

5. ポインタ .................................................................................................................................... 51 5.1. ポインタ変数...................................................................................................................... 51 5.2. アドレス演算子と間接演算子 ............................................................................................ 52 5.3. 関数とポインタ変数 ........................................................................................................... 54 5.4. 配列とポインタ変数 ........................................................................................................... 55 5.5. メモリの動的確保と解放 ................................................................................................... 56

6. 簡易デバッグ作業 ..................................................................................................................... 58

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

3

1. はじめに 私たちの身の周りには多くのコンピュータが存在し,また,多くの分野においてコンピュータ

が利用されています。そして,人間がコンピュータを利用するにあたって,コンピュータに指示

を与えています。しかし,指示を与える際,コンピュータは私たちの用いている言語を理解する

ことはできません(コンピュータが理解できる言語を,私たちの言語と区別して「機械語」と呼

んでいます)。そのため,人間がコンピュータに指示を与えるためには,コンピュータが理解でき

る方法で,コンピュータに行わせる処理を一つ一つ与えていかなければなりません。 プログラム言語は,人間が用いる言葉をコンピュータの理解できる言葉に,そしてコンピュー

タが用いている言葉を人間が理解できる言葉に変換することができる通訳者の役割を果たしてい

ます。BASIC,FORTRAN,Java といった様々なプログラム言語がありますが,今回はマイコ

ンでよく使われている C 言語について触れていきます。

1.1. C 言語の概要と特徴 C 言語は,1972 年にデニス・リッチー,ブライアン・カーニハンらによって開発されたプログ

ラミング言語です。UNIX というオペ―レーティングシステム(OS)を構築する際に使用され,

UNIX が普及するにつれて普及していきました。C 言語には,書き方に以下のような特徴があり

ます。 ⅰ)自由形式

FORTRAN などとは異なり,自由形式という形態をとっています。これは,書く位置を気にせ

ず記述できるということです。 ⅱ)小文字記入

C 言語では,英字小文字と英字大文字を区別します。原則として,C 言語では小文字記入が原

則となります。 ⅲ)文の単位の区切りは;(セミコロン) 文の区切りには;(セミコロン)を用います。これを忘れてコンパイルエラーになることが多々

あるので注意しましょう。 ⅳ)プログラムの最初は main( )

main 関数は,C 言語では必須の関数です。”main”は関数名を示しており,( )内には引数を記入

します(引数についての内容は 2.8.)。

1.2. コンピュータ内部でのデータ C 言語を学ぶ前に,コンピュータの中でどのようにしてデータが扱われているかについて触れ

ておきましょう。

・デジタル情報の最小単位はビット(bit)です。1 ビットを用いると 2 通りの状態が表現でき,

一般に「0」と「1」を用いて表記されます。 ・複数のビットを組み合わせることにより情報を処理します。例えば 10 ビット用意すれば,

210 = 1024 通りの情報が表現できます。このようにビットの組合せで文字や数字を表現しま

す。 ・バイト(byte)はコンピュータにおいて情報の大きさを表すための単位で,1 バイトは 8 ビ

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

4

ットを表します。コンピュータのメモリ(CPU が直接アクセスできる記憶領域)は 1 バイト

毎にアドレスが割り当てられています。 ・C 言語では定数・変数といったデータは基本的にバイト単位で扱われます。

1.3. C 言語の実行 C言語を利用してプログラムを作成した後,それが正しく作られているかを判断する必要があ

ります。そのためには,実際にプログラムを実行して,それが実行可能かどうかを見分けること

になります。このとき,私たちが作ったプログラムをソースプログラムといい,それを機械語に

翻訳することをコンパイルといいます 1。もし文法に誤りがあればコンパイルは失敗し,エラーメ

ッセージが表示されます。コンパイルが誤りなく終了すると,機械語に変換されたオブジェクト

プログラムが作成されます 2

さて,こうしてコンパイルが成功し,作成された実行プログラムを実行すると,処理結果が得

られます。しかし,無限ループや意図しない値が出るなど,実行過程でエラーが生じることもあ

ります。このようなプログラムの誤りをバグ(虫)といい,誤りを訂正する作業をデバッグ(虫

取り)といいます。

。その後,オブジェクトプログラムは編集結合されて実行プログラム

が作成されます。その場合には,ライブラリファイルが組み込まれます。

上記のことを行うコンパイラにはさまざまな種類がありますが,今回はデバック機能が充実し

ている Visual Studio2008 を使用します。

~プログラム作成の流れ~

1 C 言語ではあらかじめコンパイルして実行ファイルを作りますが,このほかにソースコードを逐次解釈しながら実行するイン

タプリタ形式の言語もあります。Perl,Python,Ruby 言語などがこれに当たります。 2 コンパイラは入力するプログラミング言語と対象となる CPU やオペレーティングシステム(OS)によるオブジェクトコー

ド形式によって、違う形式のオブジェクトを生成します。したがって,例えば Linux 上でコンパイルしたプログラムは Windows上では動きません。環境に応じてコンパイラを選びましょう。

01000110 11011001 01010110 10100101

1 ビット 4 バイト

ソースの記述

コンパイル

実行ファイル

プリプロセス

オブジェクトファイルの統合

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

5

2. C 言語の基本 本章では画面への文字の表示の仕方や変数を用いた演算の方法を紹介し,C 言語の基本を学ん

でいきます。

2.1. 文字を表示する 論より実行ということで早速簡単なプログラムを書いて実行してみましょう。 まず,[スタート]→[すべてのプログラム]→[Microsoft Visual Studio 2008]から Visual Studio

2008 を起動させます。 [ファイル]→[新規作成]→[プロジェクト]をクリックすると[新しいプロジェクト]のウインドウ

が開きます。左欄のプロジェクトの種類から[Visual C++]をダブルクリックし,[Win32]を選択し

ます。右のテンプレートは[Win32 コンソールアプリケーション]を選択します。 Visual Studio 2008 では,まず[ソリューションフォルダ]を作成し,その中にソースプログラム

などが入る[プロジェクトフォルダ]が作成されるといったファイル管理構造になっています。 ファイルの場所は基本的に任意ですが大学 PC の場合自分のドライブ(H:¥)以外にすると PC

をシャットダウンさせるとデータは消えてしまうので注意が必要です。ここでは下記の ex01 表示

プログラムを作るのでソリューション名を 20100424,プロジェクト名は ex01 として[OK]をクリ

ックします。 [Win32 アプリケーション ウィザード]が開くので,[次へ]をクリックし,アプリケーションの

種類は[コンソールアプリケーション],追加のオプションは[空のプロジェクト]を選択して[完了]です。右のソリューションエクスプローラーに ex01 が追加されます。ソリューションエクスプロ

ーラーが表示されていない場合は[表示]→[ソリューションエクスプローラー]で表示させて起き

ましょう。 次に[プロジェクト]→[新しい項目の追加]で[新しい項目の追加]が開きます。左のカテゴリから

コードを選択し,テンプレートの[C++ファイル]を選択して,ファイル名に[ex01.cpp]と入力しま

す(ファイルの拡張子は.c でも OK)。[追加]をクリックで ex01.cpp が開くので,いよいよプログ

ラムの記述に入ります。以下の表示プログラムを入力してみてください。

ex01 表示プログラム できましたか? これでソースプログラムは完成です。次はいよいよ実行ファイルの作成(コンパイル)です。 [ビルド]→[コンパイル](または[Ctrl]+[F7])でコンパイルを行います。入力したプログラムに

文法的問題がなければ,下段に表示される出力に

/* Test Program */ #include <stdio.h> int main(void) {

printf(“Hello World!!¥n”); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

6

と表示されコンパイル成功です。もし入力したプログラムに文法的問題があれば,そのエラーが

検出された行が表示されるので確認しましょう。(コンパイラが指摘してくれるのはあくまでプロ

グラムの文法的間違いだけです。プログラム内容の間違いを指摘してくれるわけではないので,

コンパイルが成功したからといって実行してそのプログラム自分の考えた通りに動いてくれると

は限らないので注意してください。) ちなみに,Visual Studio で行番号を表示させるには[ツール]→[オプション]からオプションを

開いて,左欄の[テキストエディタ]→[C/C++]→[全般]を選択し,右欄に出た行番号にチェックを

つければ OK です。 コンパイルが成功したら,いよいよ実行してみましょう。[デバッグ]→[デバッグなしで開始](あ

るいは[Ctrl]+[F5])で確認画面が出るので[はい]を選択します。

上記のように表示されたら,成功です。もしできなければ,それはソースプログラムのどこか

にバグがあるということです。コンパイル失敗の時と同じように,頑張って探しましょう!(バ

グ探しやコンパイル失敗の原因探しは,プログラマにとって宿命のことです。面倒かもしれませ

んが,こうしたことに慣れておくと,後々に大きな利益となります。) さて,ではex01 の解説です。実行結果を見ると,1 行目に書いた「/* Test Program */」という

文字列が表示されていないことに気が付きますね。このように「/*」「*/」の間に書かれた文字は

コンパイル時に無視されます 3

最初の 2 行目の”#include <stdio.h>”は,「ヘッダファイル」を呼び出している文です。ただ,

今説明しても難しいと思うので,「おまじない」とでもしておきましょう。

。これをコメントといい,ソース内の任意の位置に入れることがで

きます。短いプログラムの時はさほどありがたみは感じませんが,長いものになるとさっと読ん

だときに何をしているかが分からなくなることが多々あります。必要に応じてここではどんな処

理をしているのか,ということをコメントに書いておくとよいでしょう。

次の int main(void) は,メイン関数と呼ばれるものです(1.1.参照)。int や void といった聞き

慣れない単語がありますが,なぜこれが必要かについては関数について扱うときに勉強しましょ

う。ここで大切なのは,int main(void){…}ととりあえず書いておけばプログラムが実行できると

いう点です。 以上をまとめると,プログラムの基本的な形は,

で,□内の「本文」と記された部分に色々な命令を書くことになります。今回のプログラムでは,

3 C++では「// コメント」としても可。

#include<stdio.h> int main(void){ 本 文 }

Hello World!! 続行するには何かキーを押してください...

========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ==========

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

7

下図のように printf という構文と,return という構文になります。

ex02 main 関数内の構文

printf という部分は,関数の 1 つです。この printf という部分の中にある”(ダブルクォーテー

ション)で囲まれた部分が画面に表示されます。「¥n」の部分は,これがセットで「改行」を意

味します。 この行の最後にある;(セミコロン)は文法の区切りを表しています。これがないと,コンパイ

ラはどこまでが 1 つの命令なのか分からなくなり,エラーを出してしまいます。構文の最後には

必ず「;」。心に刻んでおきましょう。 return という部分は,関数そのものに値を与えるもので,返り値と呼ばれるものです(詳しい

説明は 2.8.)。 ex02 のように,printf(“ ”);とするだけで,簡単に表示できることが分かりましたか? 他にも,

プログラム中に出てきた値を表示するなどの機能もあります(後々説明します)。最後に一題出題

します。以下のプログラムが書ければ,この章はきちんと理解できていると思います。

演習問題 下記の文章が表示されるプログラムを作成せよ。

「これはゾンビですか?」 「はい,魔法少女です。」

演習問題をやるときには別のプログラムファイルを作りましょう。ここでは,先ほど作成した

ソリューションフォルダ 20100424 に work01 という新しいプロジェクトを追加することにしま

す。先ほどと同様に[ファイル]→[新規作成]→[プロジェクト]から[新しいプロジェクト]ウインドウ

を開きます。ここで[ソリューション]を[ソリューションに追加]に変更します。あとは先ほどと同

様にプロジェクト名を work01 にして、…中略…[空のプロジェクト]を選択して[完了]です。ソリ

ューションエクスプローラーに work01 が追加されたと思います。 さて今 ex01 が太字で選択されている(スタートアッププロジェクトになっている)と思います。

そこで work01 を右クリックして[スタートアッププロジェクトに設定]して太字にしてください。

この状態で先ほどと同様に[プロジェクト]から work01.cpp を追加してください。(スタートアッ

ププロジェクトの変更をし忘れてそのまま ex01プロジェクトにwork01.cppを追加するとコンパ

イル時にエラーが出るので注意してください。) 以上が Visual Studio の基本的な使い方です。この後にあるマイコン講習会のコンパイラも同じ

ような感じなのでこの講習会を通してコンパイラに慣れていきましょう。

#include <stdio.h> int main(void) {

printf(“Hello World!!¥n”); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

8

2.2. 定数 普通定数というと,0 とか 24 とか,数学的に決まった値が頭に思い浮かぶと思います。C 言語

の定数も基本的に数学的な定数と同じですが,若干違う部分が存在します。これからそれについ

て見ていきましょう。 ⅰ)数値定数 想像が付くと思いますが,数値定数とは,数学的な定数にあたります。その中でも 24 のような

整数と,12.56 のような実数とに分けられます。整数と実数の違いはほとんどありませんが,宣

言型や,printf や scanf に使われる,書式表現というものに影響します(下記参照)。 ⅱ)文字定数 ここからは数学的な定数とは違います。文字定数とは,A や s,1 のような,1文字を表現する

定数のことです。表記上は,文字定数は’(シングルクォーテーション)で囲まれます。例えば,

a という文字定数を表すのは,’a’ ということになります。 ⅲ)文字列定数 文字定数とは違い,2文字以上の文字列を表現する定数のことです。文字列定数は,文字定数

とは違い,”(ダブルクォーテーション)で囲まれます。’ を使うとエラーになるのでご注意を 4

〈例〉

例を以下に挙げておきます。

“LANTIME”,“===C===”,”スペクトル” さて,ここで定数については終わり! とはいきません。これだけでは,定数は使いこなせな

いからです。その問題として,書式表現が挙げられます。 書式表現とは,値や文字を入力したり出力したりするときに,その型を指定する方法です。し

かし,これだけだと分かりにくいので,以下のようなプログラムの書き方をすれば良いと思って

下さい。

ex03 書式表現

これを実行すると,以下のように表示されます。

ex03 において,%d と書かれた部分が書式表現と呼ばれるものです。printf の行の最後にある

100 が,数値定数にあたります。100 というのは整数なので d が用いられています。 では同じように,実数,文字定数,文字列定数でも同じように書いてみましょう。

4 1 文字を表現するときにダブルコォーテーションを用いるのは OK です。ただし,コンパイラでは「文字」と「文字列」は厳

密に区別されており,メモリーへの格納の仕方が異なります。詳しくは配列の項目で勉強します。

100

#include <stdio.h> int main(void) {

printf(“%d¥n”,100); /* 整数表示 */ return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

9

ex04 実数,文字定数,文字列定数の書式表現 これを実行すると,以下のように表示されます。

ex04 のように,%f が実数の,%c が文字定数の,%s が文字列定数の書式表現になります。 さて,printf の書き方は前回の 2.1 章で話しました。しかし,今回は前回の形とは少し違うこ

とに気がつきましたよね? 今回は,前回とは違って,定数,変数の表示する printf の形になっ

ています。printf で定数や変数を表示する方法は,以下のようになります。

”(ダブルクォーテーション)で囲まれた「書式文字列」と書かれた部分が表示されるのは,上

で述べた通りです。今回は,”の後に,(カンマ)が入っています。実はこれが定数や変数を表示す

るときに使うものです。そして,この後の「変換対象引数」と書かれたところに表示したい定数

や変数が来ます。ex04 を見て下さい。一番上の printf では,定数の 12.56 が,の後の変換対象引

数にあります。12.56 の書式表現は%f なので,書式文字列には%f が入っています。文字定数,文

字列定数についても同様です。以上出てきた書式表現を表 1 にまとめました。

表 1 書式表現の種類 書式表現 型 %d 整数 %f 実数 %c 文字定数 %s 文字列定数

演習問題 下記の文章が表示されるプログラムを,printf の定数表示を利用して作成せよ。

199X AD 12 Warewareha uchujin da.

printf(“書式文字列”,変換対象引数);

12.56 c PRACTICE

#include <stdio.h> int main(void) {

printf(“%f¥n”,12.56); /* 小数表示 */ printf(“%c¥n”,’c’); /* 文字表示 */ printf(“%s¥n”,”PRACTICE”); /* 文字列表示 */ return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

10

2.3. 変数 例えば,電卓のように四則演算するプログラムを考えてみましょう。2 つの数の和を計算する

には,最初に入力された数字と,次に入力された数字を覚え両者を足すという思考を行います。

しかし,その入力される数字は問題によって様々で,これまで習った定数だけではこのような問

題には対処できません。そこで,どのような数字にも対応できるようにプログラム上に変数を用

意し,それに必要な値を代入する形で所望の動作を実現します。足し算を行うプログラムの場合,

2 つの変数𝑥1と𝑥2を用意しておき𝑓(𝑥) = 𝑥1 + 𝑥2を計算する命令を用意しておけば,入力される数

字の値に関わらず,和が計算されることになります。 それでは,C 言語上で変数を使う方法について説明しましょう。これを用いるためには,「今か

ら変数を使うよ」ということをコンパイラに教えるために変数定義を行います。定数と異なり,

変数の場合はその中身にどのような情報が入ってくるか分かりません。したがって,その情報量

も実際に入力されるまで決まりません。そこで,変数を用いる際には,コンピュータのメモリ内

に情報を保管するための領域を予め確保する必要があります。また,その領域をどの変数が利用

しているかを分かるようにするために変数に名前を付ける必要があります。以下に,整数を利用

する際に用いる変数の定義方法の例を挙げます。

上のように,整数や実数といった変数の型を宣言した後に,変数の名前を宣言します。int は整

数を扱う場合に用いられ,宣言するとメモリの未使用領域から 4 バイト分の領域が確保され変数

a が使えるようになります。このとき,未使用領域のメモリには何が保存されているか分からな

いため,a の中身は不定です。これを避けるため,2 行目では変数 b に初期値を代入しています。

3 行目のように,同じ型の変数であれば 1 度に複数個定義することもできます。

変数は,同様に作った他の変数と区別するために被らない名前を宣言しなければなりません。

このとき,大文字と小文字は区別されます(1.2 より)。ここで,C 言語では既に付けられた予約

語があり,その名前と同一の名前を付けることはできません。また,最初に数字が来る名前もで

きません。 〈例〉変数としては使えないもの

int void 2010program また,作る際にその変数の書式表現を指定します。プログラム中ではそれ以降では変換しない

限りずっと,その書式表現を維持します。下に宣言の型の一覧を載せておきます。色々と実験し

てみましょう。

アドレス

010D090B

010D090C

010D090D

010D090E

010D090F

010D0910

メモリ

元々この個

所にあった

内容 a

int a; /* int 型変数 a の定義。初期値は不定。 */ int b=100; /* int 型変数 b の定義。初期値 100。 */ int c, d=100; /* 「,」を使えば複数個の定義もできる。 */

アドレス

0000B105

0000B106

0000B107

0000B108

0000B109

0000B10A

メモリ

100 b

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

11

表 2 変数の型 変数の型 バイト数 範囲

整数

int 4(機種依存) -2147483648~2147483647 unsigned int 4(機種依存) 0~4294967295

long 4 -2147483648~2147483647 unsigned long 4 0~4294967295

char 1 -127~128 unsigend char 1 0~255

short 2 -32768~32767 unsigned short 2 0~65535

実数

float 4 -3.4× 1038~3.4× 1038 double 8 -1.7× 10308~1.7× 10308

文字型

char 1 - また,以下に書式表現の表を載せておきます。変数の型に合わせて,書式表現を間違えないよ

うに入力しましょう。桁指定の方も例に挙げておきました。時間があれば,実験してみて下さい。

表 3 型と書式表現 型 書式(表現) 書式(桁指定) 例 整数 %d %桁数 d %8d

%ld %桁数 ld %10ld 実数 %f %全桁数.小数点以下桁数 f %8.3f

%lf %全桁数.小数点以下桁数 lf %10.2lf 文字 %c %桁数 c %5c

文字列 %s %桁数 s %5s

2.4. 変数を使った出力方法 以下では変数を使ったプログラムについて見ていきましょう。まずは,変数の中身をどのよう

にして画面に表示するかについて考えます。既に学んだように,printf 関数は出力を担う関数で

あり,その他定数も表示できます。しかし,今回勉強した変数も,printf 関数で表示できるので

す。以下に,printf 関数を用いて変数を表示する例を示します。

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

12

ex05 printf 関数による変数の表示

ex05 を実行すると,以下のように表示されます。

ここで表示された 8 は整数型で宣言した変数 a に格納されていた値で,printf 関数内のカンマ

の後に変数を入れることで表示できます。同様に,複数の変数を表示することも可能です。

ex06 printf 関数による複数の変数の表示

7 行目のように,printf 関数内の%d は,最初の%d が a と,次の%d が b と,最後の%d が c と

対応しています。左から対応していくことは重要なので,忘れないようにして下さい。 ところで,printf 関数内では%d などの書式表現とは別の書式制御があります。ex06 では,「¥n」

が使われています。これ以外にも,下のようなものがあります。

表 4 制御文字 制御文字 機能 制御文字 機能

¥n 改行 ¥a 警告ベル(ビープ音) ¥r 復帰 ¥¥ ¥表示 ¥t 水平タブだけ移動 ¥’ ‘ 表示 ¥v 垂直タブだけ移動 ¥” “ 表示 ¥f 改ページ ¥? ? 表示

#include <stdio.h> int main(void) {

int a,b,c; /* 整数型変数を 3 つ定義 */ a = 8; b = 12; c = -1; printf(“%d %d %d¥n”,a,b,c); return 0;

}

8

#include <stdio.h> int main(void) {

int a; /* 整数型変数の定義 */ a = 8; /* 変数に数値代入 */ printf(“%d¥n”,a); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

13

¥b 1 文字後退 ¥0 NULL(ヌル) これで出力を担う関数である printf 関数については終わりますが,次に,入力を担う scanf と

いう関数を紹介します。 2.5. 入力方法

scanf 関数は,標準入力装置(大抵はキーボード)からデータを入力するために用いる関数です。

scanf 関数には,入力をどのような形式で行うかを指示する書式制御と,入力するデータを格納す

る変数名を記入します。しかし,このとき特に注意しなくてはならないこととして,scanf 関数で

記入する変数名は今まで学習してきた変数名と性質を異にしていることが挙げられます。 上述した通り,変数はメモリの中に一定の領域が割り振れることによって出来ています。scanf

関数は,確保された領域にキーボードから入力された情報を放り込むという動作をします。この

時に必要になる情報は何でしょうか? 例を挙げて考えてみましょう。あなたは郵便屋さんです。

いま,A さんの家に手紙を届けようとしています。このときに郵便屋が任務を遂行するために必

要なのは A さんの家の住所ですね。これさえあれば手紙の内容に依らず手紙を届けることができ

ます。実は scanf 関数(例では郵便屋)も同じで,変数のアドレス(A さんの住所)さえ分かれ

ば手紙の内容(キーボードから入力された情報)に関わらず,変数に情報を格納することができ

ます。 C 言語で変数のアドレスを表すためには変数名の前に&(アンド又はアンパサンド)を付加しま

す。&変数名は,入力するデータを格納する領域の名前を表すのではなく,その領域が存在する

アドレスを表しています。 表 5 scanf 関数の使い方の例

関数名 scanf 関数 目的 キーボードで入力する 一般形式 scanf(“ 書式制御 ” , &変数名 , &変数名 , &変数名 , …); 例 scanf(“ %d ” , &ex01);

scanf(“ %d %d ” , &ex01, &ex02);

書式制御部分には,入力する値のデータ型に応じた書式表現を記入します。表 5 例を挙げると,

「%d」では整数を入力し,「%d %d」では2つの整数を空白で区切って入力します。もし整数で

はなく実数で入力したい場合は,「%d」を「%f」に変更しましょう。 もう一つ大事なのは,格納する型も整数なら整数,実数なら実数の型で宣言しておく必要があ

ると言うことです。

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

14

ex07 scanf 関数による値の格納

ex07 を実行すると,キーボードで入力した 2 つの値が表示されます。その内,前者は整数型,

後者は実数型になっているはずです。もし整数型で実数を入れると少数以下は切り捨てられ,実

数型で整数を入れると少数以下は全て 0 が表示されます(ただし,今回のプログラムに限っては

最初の a の値を実数で入れた場合は,それが%f に対応するとコンピュータが考えて,整数と少数

以下を勝手に分けてしまいます)。 2.6. プリプロセッサの働き 一通り,C言語の書き方を見てきたところで,「おまじない」で済ませてきたところの種明かし

をしておきましょう。今までの例では,全て 1 行目に#include<stdio.h>という記述があります。

このように,行頭から#で始まる行はC言語ではなくプリプロセッサの機能を呼び出しています。

プリプロセッサはコンパイラがソースコードをコンパイルする前に,予めソースコードに対して

何らかの処理を行う働きをしています 5

。処理の種類には以下のようなものがあります。

2.6.1. 文字の置換 例えば,プログラムの中で円周率π を使いたいという場合を考えます。必要な時に 3.1415…と

逐一書くのは面倒ですね。そういうときに,便利なのがプリプロセッサを用いた文字の置換です。

以下のようにして使うことができます。

上の例の場合,円周率を表す記号定数を PI,置換定数を 3.1415 として

とすれば,ソース中の PI は全て 3.1415 に置換されます。このように,記号定数には大文字を用

いて,置換される文字列であることを明示しておくのが一般的です。 2.6.2. ソースファイルの取り込み

これまで何気なく printf 関数や scanf 関数を使ってきましたが,本来,画面に数字を表示する

ためのプログラムやキーボードから情報を受け取るプログラムもどこかで作らなければこれらの

5 P.4 のプログラム作成の流れを参照してください。

#define PI 3.1415

#define 記号定数 置換定数

#include <stdio.h> int main(void) {

int a; /* 整数型の定義 */ float b; /* 実数型の定義 */ scanf(“%d %f”,&a, &b); /* キーボードからの入力 */ printf(“a=%d b=%f¥n”,a,b); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

15

関数は利用できません。しかし,必ずと言っていいほど利用されるこれらの関数をプログラムす

る度に自作していては非常に時間と労力を無駄にしてしまいます。そこで,これらの関数は予め

別のファイルで提供されており,それを作っているプログラムに取り込むことで誰でも簡単に入

出力ができるようになっています。この「別ファイルを取り込む」という動作が今まで最初に書

いていた#include<stdio.h>という命令です。一般に,

とすることで,別ファイルを読み込みます。もう少し詳しく言うと,#include…とすると,プリ

プロセッサが必要なソースファイル(ヘッダファイルと言う)を探し出し,この行をそのファイ

ル全体で置き換えるという動作をします。ファイルの場所の指定方法は 2 通りあり,システムが

提供したファイルを利用する場合は#include <ヘッダファイル名>で,自分で作ったファイルを利

用する場合は,編集しているファイルと同じディレクトリ内にヘッダファイルを置いて#include ”ヘッダファイル名”とします。システムで用意されているヘッダファイルはstdio.h6

以上がプリプロセッサの代表的な機能です。プリプロセッサはコンパイル前に処理を行います。

したがって,C 言語の文法とは関係ありません。文末に;が付いていないことに注意しましょう。

の他にもたく

さんあります。これについては 4.4 節を参照してください。

演習問題 下記の流れを再現するプログラムを作成せよ。

「3つの数字を記入して下さい」という表示の後,キーボードから3つの数字を入力する。 ↓

1つ目の数字を,整数型で表示する。 ↓

2つ目の数字を,実数型で表示する。 ↓

3つ目の数字を,少数部分を切り捨てて表示する。 ↓

「プログラム終了」を表示して終わる。

6 standard input output の略

#include <ヘッダファイル名> /* usr/include/内にあるヘッダファイル読み込み */ #include “ヘッダファイル名” /* カレントディレクトリのヘッダファイル読み込み*/

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

16

3. 演算・分岐・ループ 2 章までで,入力と出力ができるようになりました。しかし,これだけではプログラムを作る

メリットがありません。入力された情報に手を加え欲しい情報を作り出す作業が必要になります。

これら実現するための方法について触れていきます。

3.1. 演算処理 データを加工する上で最も単純な処理は,演算処理と呼ばれるものです。それについて見てい

きましょう。

3.1.1. 算術演算と代入演算 C 言語で利用する基本的な演算子は,算術演算子です。他の言語と同様に,数値の四則演算を

するときに用います。また,四則演算の他に剰余演算というのも存在します。

表 6 算術演算子の種類

算術演算子の種類 a = 10, b = 3 の場合 算術演算 演算子 例 演算結果 加算 + a + b 13 減算 - a - b 7 乗算 * a * b 30 除算 / a / b 3 剰余 % a % b 1

この算術演算子を用いて計算結果が得られますが,プログラムでは計算結果をそれ以降でも利

用するので,実際には代入演算子を用いて,変数に格納しなくてはなりません。

この例では左の”sum”という変数に,右の式の結果を代入しています。ここで注意しなくてはな

らないのは,代入演算子 = の右辺と左辺の値が必ずしも等しい値にはならないということです。

C 言語での = は等号を表しているのではなく,右辺の値を左辺にある変数に代入することを意味

しているのです。次の例を考えてみましょう。

数学の問題ならば,b = 0 が答えになりますがコンピュータの頭ではこの方程式は解いてくれま

せん。この場合,最初に a + b の値が求められ,その結果は一時的に保管されます。そしてその

後,保管されていた値が変数 a に代入されます。元々,a = 1,b = 2 だった場合,この一行を実

行すると a = 3 という結果が得られます。 さて,変数の型が違う者同士で演算するとどうなるでしょうか。次の例を実行して下さい。

a = a+b;

sum = a+b+c+d;

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

17

ex08 代入演算子の右辺と左辺の値

ex08 を見てみると,c = a + b から,c は 9.6 となる気がします。しかし,実行すると以下のよ

うになると思います。

こうなる理由として,c が int 型であることが挙げられます。int 型は整数型なので少数以下を

表現できず,結果として 9.6 の小数部分である 0.6 が切り捨てられ,9 と表示されたわけです。 原則として,同じ型同士の演算結果は演算処理した型と同じ型の値が得られ,異なる型同士の

演算処理では,2 つの値の内,より広い範囲を持つ型の変数または定数の型に合わせた演算結果

が得られます。整数と実数であれば,実数が数値の範囲が広いため実数型で結果が現れます。

表 7 整数,実数型の組の演算処理と演算結果 型別の演算 演算 a b a + b

整数 演算子 整数 a + b 5 4 9 整数 演算子 実数 a + b 5 4.0000 9.0000 実数 演算子 整数 a + b 5.0000 4 9.0000 実数 演算子 実数 a + b 5.0000 4.0000 9.0000

ところで,代入演算子とは,既に格納されている変数の値に演算処理を行って,その処理によ

って計算された値を再度格納する処理を行います。なので,演算の中でも累計演算をする場合に

限って,代入演算子には算術演算子の代わりをするものがあります。表 8 に主な代入演算子を列

挙します。

表 8 代入演算

c=9

#include <stdio.h> int main(void) {

int a,c; /* a と c は整数型 */ float b; /* b は実数型 */ a = 6; b = 3.6; c = a + b; printf(“c=%d¥n”,c); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

18

代入演算子の種類 a = 10, b = 3 の場合の演算

後の a の値 代入演算 演算子 演算式 加算 += a += b (a = a+b より) 13 減算 -= a -= b (a = a-b より) 7 乗算 *= a *= b (a = a*b より) 30 除算 /= a /= b (a = a/b より) 3 剰余 %= a %= b (a = a%b より) 1

プログラムで行われる処理の中で,累計処理は頻繁に利用される処理の一つです。例えば,店

舗ごとの販売額を集計して全社の総販売額を計算するときには,「総販売額 = 販売額 + 店舗別

販売額」といった計算式を用います。この計算式は,この命令が行われる前までに既に集計がな

されている総販売額に新たな入金があった店舗の販売額を加算して,その結果を再度総販売額に

格納する式です。 しかし,C 言語では代入演算子を用いて「総販売額 += 店舗別販売額」と書くことが可能なの

です。プログラム作成やプログラム実行に関して言えば,代入演算子を用いた方法が効率的で,

プログラム作成に慣れてきたら,代入演算子を用いることが望ましいと思います。

3.1.2. インクリメント演算・デクリメント演算 代入演算を用いた類型処理と同様に,プログラム処理で頻繁に利用される処理が,値に 1 を増

減する処理です。1 増減する処理を C 言語ではインクリメント演算・デクリメント演算と言い,

プログラム中に「++」「--」と書いて表します。「++」と「--」のどちらの場合も,変数名の前後

に演算子を書くことができます。演算子を前に書いても後ろに書いても,1 を増減する処理は同

じです。 表 9 インクリメント・デクリメント演算子

インクリメント・デクリメント演算子の種類 a = 10 のときの, 演算後の a の値 演算 演算子 例

加算 ++変数名 ++a; 11 加算 変数名++ a++; 11 減算 --変数名 --a; 9 減算 変数名-- a--; 9

しかし,インクリメント演算子及びデクリメント演算子を,下の表 10 のように他の変数に代入

する処理と組み合わせて用いる場合には,演算子の前後によって,演算処理を行ってから代入す

るか,代入した後で演算処理をするかという違いがあります。演算子が前にある場合は先に演算

してから代入,演算子が後にある場合は代入した後に演算と覚えるとよいでしょう。

表 10 インクリメント・デクリメント演算子と代入処理

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

19

計算式( i = 1, a = 0 ) 実行後の a 実行後の i a = ++i; 2 2 a = i++; 1 2 a = --i; 0 0 a = i--; 1 0

それでは,実際に確かめてみましょう。

ex09 インクリメント・デクリメント演算子の演算処理

ex09 を実行すれば,表 10 と同じ結果になることが分かると思います。要は,1 の演算が先に行

われるのか,代入演算が先に行われるかの違いだけなのです。

3.1.3. キャスト演算 上の表 7 で,データの型の組み合わせにより,演算結果のデータの型が決定されることを表し

ました。しかし,決められた演算規則で処理された場合には,期待する結果が得られない場合が

あります。例えば,整数同士で除算を行った場合は,演算結果も整数となり,小数点以下の値は

切り捨てられることになります。しかし,整数同士の除算で少数以下の値が出ることはよくある

ことです。 このような場合には,どちらかの整数を演算前に実数化し,実数と整数の演算に変換すれば,

演算結果は実数として得られることになります。キャスト演算子は,処理される値の型を変更す

ることにより,データ型を設定することができるというものです。 では,以下のプログラムを見ていきましょう。まずは実行して,その結果を出して下さい。

#include <stdio.h> int main(void) {

int a=1,b=1,c=1,d=1,e=1,f=1,g=1,h=1; a = ++e; b = f++; c = --g; d = h--; printf(“a=%d e=%d¥nb=%d f=%d¥n”,a,e,b,f); printf(“c=%d g=%d¥nd=%d h=%d¥n”,c,g,d,h); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

20

ex10 キャスト演算子 ex10 を実行すると,以下のような結果になるはずです。

変数 c の結果が実際の計算と異なった理由は,3.1.1 の ex08 で説明したことと同様です。しか

し,ここで変数 d に注目して下さい。変数 d の答えは,a/b = 20/6 の解答と一致しています。こ

れがキャスト演算を用いた結果であり,こうして計算することにより,int と float などの,型の

違いに気を使わずに計算することができるようになるのです。 3.1.4. ビット演算子 1.2 節で述べたように,コンピュータ内部ではビット単位での演算を行っています。これを直接

扱えるように,C 言語ではビット毎の論理演算が行えます。まず,論理演算について触れておき

ましょう。論理演算はブール代数とも呼ばれ,0(偽)と 1(真)の 2 通りの入力に対して 1 つの

値を返す演算です。出力の仕方には以下のようなものがあります。

表 11 真理値表 論理積(AND)

論理和(OR)

排他的論理和(XOR)

入力 1 入力 2 出力

入力 1 入力 2 出力

入力 1 入力 2 出力

0 0 0

0 0 0

0 0 0 0 1 0

0 1 1

0 1 1

1 0 0

1 0 1

1 0 1 1 1 1

1 1 1

1 1 0

論理演算はデジタル回路ではほぼ必ず用いられており,コンピュータの中でもこれらの組合せに

よって高度な演算を行っています。さて,C 言語上で論理演算を行うには表 12 に挙げたビット演

算子を用います。

c=3.000000 d=3.333333

#include <stdio.h> int main(void) {

int a=20,b=6; float c,d; c = a/b; /* c には整数型同士の割り算 */ d = (float)a/b; /* d には実数型に変換後割り算 */ printf(“c=%f d=%f¥n”,c,d); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

21

表 12 ビット演算子

記号 意味 a=011, b=010 の時の演算結果

& ビット単位の論理積 a&b = 010 | ビット単位の論理和 a|b = 011

^(ハット) ビット単位の排他論理和 a^b = 001 ~(チルダ) ビット単位の補数 ~a = 100, ~b = 101

<< ビット単位の左シフト a << b = 1100 >> ビット単位の右シフト a >> b = 0

上記の記号を用いると,ビット毎に論理演算が行われます。上の例で論理積・論理和・排他論

理和の演算結果が表 11 に示した演算方法に則っていることを確認しましょう。~(チルダ)を用

いるとビット毎に反転(0 なら 1,1 なら 0)が起こります。<<と>>はビットシフトといい,これ

らの記号の右側の変数の値だけビットが移動します。表 12 では b = 010(2 進数表記)となって

いますので,これを 10 進数に直すと b = 2 になります。a << b とした場合,a = 011 というビッ

トを b の分すなわち 2 だけ左側にずらすことになるので,a = 1100 となります。 それでは,以上のことを次の例を通して確認してみましょう。

ex11 ビット演算 演算結果は 10 進数で表記されますが,それぞれ 2 進数に変換すると表 12 の結果と一致してい

ることが確認できると思います。 ちなみに,C 言語では 10 進数表記の他に 8 進数表記と 16 進数表記が可能で,変数で用いる場

合それぞれ数値の頭に 0,0x を付けることになります。

a = 010; /* a は 8 進数表記。10 進数では a = 8。 */ b = 0x0F; /* b は 16 進数表記。10 進数では b = 15。 */

#include <stdio.h> int main(void) {

int a,b; a = 3; /* a=3 を 2 進数にすると a=011 */ b = 2; /* a=2 を 2 進数にすると a=010 */ printf(“a&b = %d¥na|b= %d”,a&b,a|b); printf(“a<<b = %d¥na>>b = %d”,a<<b,a>>b); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

22

3.1.5. 演算子の優先順位 C 言語では,演算処理を行うために,算術演算子を始めとし,代入演算子,インクリメント・

デクリメント演算子を勉強してきました。しかし,この他にも >, <, = = のような,大小関

係を比較するための関係演算子や,判定条件を記述するのに用いる論理演算子など,様々な演算

子が存在します。そのため複雑な計算式を作成したときには,どの演算処理が優先されて行われ

るかを理解しておく必要があります。 表 13 に演算子の処理の表を載せておきます。順位が高いほど処理が早くなります。

表 13 演算子の優先順位 順位 演算子の種類 演算子記号

高い ↑ ↓ 低い

1 括弧 ( ) 2

インクリメント デクリメント 正負 アドレス

++ -- + - &

3 乗除・剰余 * / % 4 加減 + - 5 関係 < <= => > 6 論理(積) && 7 論理(和) || 8 代入 = += -= *= /= %=

演習問題 下記のプログラムを作成せよ。 (1) キーボードから入力した5つの整数の合計を出し,その平均値を求めるプログラム。

(2) 上底 a,下底 b,高さ c の台形の面積 S を求めるプログラム。

(3) 下に示す連立方程式 x1,x2の解を求めるプログラム。ただし,係数 a,b,c,d 及び y1,y2

はキーボードから入力できる整数とする。

+=+=

212

211

dxcxybxaxy

ただし,無限解や解なしを考えずに作成してよい(係数に値を代入する際,解が無限,ま

たは解なしにならないように設定すること)。

(4) 底面の半径 r,高さ h の円錐の体積 V を求めるプログラム(円周率はπ=3.1415 とせよ)。

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

23

3.2. 条件分岐 プログラムの手順(アルゴリズム)には,一連の処理を連続して行う順次処理,条件を判断し

てその結果によって処理を変える分岐処理,同一の処理を繰り返して実行するループ処理(繰り

返し処理)があります。このうち,本節では条件分岐について見ていきます。

3.2.1. 分岐処理(if 文)

分岐処理の中でも最も単純なものが,条件を満たしたときのみ処理を行うプログラムです。次

ページの図 1 に示されているように,処理の途中で処理を行うか行わないかの条件を提示して,

条件が満足されれば処理を行い,条件が満足されなければ何も処理を行わないプログラムです。 例えば,成績処理を行う場合に 50 点未満であれば不合格処理を行い,50 点以上であれば処理

は何もしなくてよいという場合です。

図 1 条件が真のときのみ処理を行う分岐処理の流れ図

図 1 のようなプログラムを書く場合,下の表 14 を参考にすると良いと思います。if の後に条件

を小括弧( )で囲み,条件が満たされたときの処理を中括弧{ }に囲まれた範囲に記入するのが基本

の形となっています。 表 14 分岐処理(if 文)の使用方法

用途 分岐処理(条件が満たされたときのみの処理) 一般形式 if (条件) { 処理 } 例

if (number < 50) {printf(“不合格”); printf(“%d”, number);} if (number >= 50) printf(“合格”);

例にあるように,条件が成立したときの処理は中括弧で囲むのが普通ですが,分岐後の処理が

一つしかない場合に限って,中括弧で囲まなくてもできます。また,条件式に用いる演算子に 2項演算子というものがあり,2 つの定数・変数・式を比較するものを言います。今回の例では,<, >= が 2 項演算子に相当します。

if 文では条件の正誤を判定しているので,判定結果は真か偽のどちらかで,処理後には必ずどち

らかに判定されます。その条件文には,等値演算子や関係演算子を用いることができます。等値

演算子は 2 つの値が等値であるか非等値であるかを判定するもので,関係演算子は 2 つの値の大

小関係を比較判定するものです。

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

24

表 15 等値演算子と関係演算子

条件 演算子 例 等しい = = number = = 100 等しくない != number != 100 小さい < number < 50 小さいか等しい < = number < = 50 大きい > number > 50 大きいか等しい > = number > = 50

これらの演算子はいずれもその条件を満たせば 1(真)を返します。

この例の場合,a==b は正しいですから,c には 1 が代入されます。 しかし,全ての条件が 2 つの値の等値関係や大小関係で比較するだけで作れるという訳ではあ

りません。これらの条件を組み合わせて,更に複雑な条件文が必要な場合もあると思います。 例えば,塩を 2kg 運ぶとします。塩を量るときに,誤差を考えて±100g まで運ぶことにすると,

そのときの条件は,重さが 1.9kg 以上かつ 2.1kg 以下ということになります。このような条件文

を組み合わせるための演算子として,論理演算子があります。 論理演算子には,「かつ」「そして」を表す論理積,「または」「あるいは」を表す論理和,「でな

い」を表す否定があります(3.1.4.節参照)。 表 16 論理演算子

条件 演算子 例 論理積 && number > = 50 && number < 75 論理和 || number < 0 || number > 100 否定 ! ! number

論理積は 2 つの条件を「かつ」「そして」で結び付け,論理和は 2 つの条件を「または」「ある

いは」で結び付けます。否定は一つの条件を「でない」とし,否定するものとして扱われます。 条件 1 が「資産 1 億ドル以上」,条件 2 が「アメリカ人」の場合は,論理積は「資産 1 億ドル以

上のアメリカ人」となり,論理和は「資産 1 億ドル以上か,アメリカ人」となります。否定は,

条件 1 なら「資産 1 億ドル未満」で,条件 2 なら「アメリカ人以外」となります。 では,実際にプログラムを書いてみましょう。

int a = 1, b =1, c; c = (a==b);

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

25

ex12 分岐処理(if 文)

ex12 を実行すると,入力が 50 以上の数値なら

と表示され,一方で入力が 50 未満なら

と表示されると思います。これが if 文の分岐処理なのです。 さて,ここまでは if 条件文の最も単純なものでしたが,次は if の条件が成立しなかったときの

処理を考えていきます。

3.2.2. 分岐処理(if~else 文) 条件が真でも偽でも両方とも処理を行うために必要な文が,if~else 文です。条件が真のときは

if (条件) の後に処理を行い,条件が偽のときは else の後で処理を行います。

表 17 分岐処理(if~else 文)の使用方法

用途 分岐処理(条件が満たされたときのみの処理) 一般形式 if (条件) { 処理群 1 } else { 処理群 2 } 例

if (number < 50) {printf(“不合格”);} else {printf(“合格”);}

Input your ten. :42 ten=42 不合格

Input your ten. :68 ten=68 合格

#include <stdio.h> int main(void) {

int number; printf(“Input your ten. :”); scanf(“%d”,&number); /* 変数 number に入力情報を代入 */ /* 50 点未満 */ if(number < 50) {printf(“ten=%d 不合格¥n”,number);} /* 50 点以上 */ if(number >= 50) {printf(“ten=%d 合格¥n”,number);} return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

26

図 2 条件が真・偽のいずれも処理を行う分岐処理

図に記されているように else の後には条件は記述せず,if で記された条件を満たさないものが

全て処理されます。これを使うと ex12 は次のように書きかえることができます。

ex13 条件分岐(if-else 文) 上の例と ex12との違いを考えてみましょう。例えばnumber = 48の場合,ex12ではnumber<50

かどうかを判定し,真なので printf 文で表示を行います。その後 number >= 50 かどうかを判定

し,偽なので if 文の中身は飛ばします。一方 ex13 では number<50 かどうかを判定し,表示を行

った後は else 文の中身を飛ばします。つまり,ex12 で 2 回あった条件判定が ex13 では 1 回にな

っています。この程度の長さのプログラムでは速さに差は感じませんが,条件分岐が何度も行わ

れるようなプログラムでは時間差が大きくなる場合があります。同じ条件分岐でも,使う構文に

よって速さが変わることがあるということを覚えておいてください。

3.2.3. 分岐処理(if~else if 文) ここまでに 2 つの処理に分ける分岐処理を扱ってきましたが,分岐処理には 2 つ以上の処理に

分岐することが要求されることもあります。例えば,成績の評価で A,B,C,D 判定を出す場合

などです。こうしたときには if 以外の分岐処理である switch 文が用いられますが,一方で if~else if 文も一般的に用いられます。

if~else if文は下の図3のような形をとっています。図3では3つの処理群に分岐していますが,

else 処理には if 処理を行えるので分岐数は限りなく増やすことができます。

#include <stdio.h> int main(void) {

int number; printf(“Input your ten. :”); scanf(“%d”,&number); /* 変数 number に入力情報を代入 */ if(number < 50) { /* 50 点未満 */

printf(“ten=%d 不合格¥n”,number); }else{ /* 50 点未満ではない→50 点以上 */

printf(“ten=%d 合格¥n”,number); } return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

27

図 3 3 つ以上の分岐処理

この分岐処理方法では,条件が真のときの処理は再分岐できず,条件が偽のときのみ再分岐可

能であることに注意しなければなりません。そのため,条件文の記述においては,else 処理の中

に再分岐が可能となるように条件分を記述しておかなければなりません。 では実際に,if~else if 文が使われたプログラムを見ていきましょう。

ex14 分岐処理(if~else if 文)

これを実行すると,以下のような分岐になると思います。

=

判定以上

判定未満以上

判定未満以上

判定未満

ABC

D

number

,85,8570,7050

,50

こうした分岐を示す if~else if 文の基本構造は,下のようになります。

表 18 分岐処理(if~else if 文)の使用方法

用途 分岐処理(条件が満たされたときのみの処理) 一般形式 if (条件) { 処理群 1 }

else if (条件) { 処理群 2 } ………… else { 処理群 n }

#include <stdio.h> int main(void) {

int number; printf(“Input your ten. :”);

scanf(“%d”,&number); if(number < 50) {printf(“ten=%d 不合格¥n”,number);} else if(number < 70) {printf(“ten=%d C 判定¥n”,number);} else if(number < 85) {printf(“ten=%d B 判定¥n”,number);} else {printf(“ten=%d A 判定¥n”,number);} return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

28

例 if (number >= 85) {printf(“優”);} else if(number >= 60) {printf(“良”);}

else if(number >= 50) {printf(“可”);} else {printf(“不可”);}

if~else if 文では,最初の if 文を素通りしたもの(偽であったもの)が次の if 文で振るいにか

けられるため,3.2.1 節で紹介した論理積を使うことなく,論理積を表すことができます。 さて,ここでは分岐数がせいぜい 3,4 の話なので if~else if 文を用いて書いてきました。しか

し,この分岐がこれ以上増えたときには,if~else if 文で書くと非常に大変になってしまいます。

そこで,分岐が多数存在する場合には switch 文というのを使います。

3.2.4. 分岐処理(switch 文) switch 文は,分岐処理の中でも何らかの形で順位付けができる場合には便利です。 例えば,ex13 では合格者の処理に関しては 50 点以上 70 点未満を 1,70 点以上 85 点未満を 2,

85 点以上を 3 というように,番号を 0~10 の間で割り振れればよいと思います。 そのための方法として,以下の方法が考えられます。

こうすれば,点数の上限が 100 なので,番号は 0~10 の間をとることになります。そして,番号

が 0~3 のときは C 判定,4~6 のときは B 判定,7 以上は A 判定と処理すればよいことになりま

す。同様に考えれば,大きい数の分岐処理も switch 文で簡単に扱えることになります。 switch 文では,( )内の値によって,それが示す case 番号へと処理が分岐します。分岐した後,

その処理群を行い,更に次の処理へと実行が進められます。そこで次の処理を中断する命令とし

て break 文が書かれています。 表 19 分岐処理(switch 文)の使用方法

用途 分岐処理(条件が満たされたときのみの処理) 一般形式 switch (変数名) {

case 1: { 処理群 1 } break; case 2: { 処理群 2 } break; case 3: { 処理群 3 } break; : case k: { 処理群 k } break; : default: { 処理群 n }

}

番号 = (点数 - 50)/5 (ただし,番号と点数は int 型)

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

29

例 switch (number) { case 1: {sum += a;} break; case 2: {sum *= b;} break; case 3: {sum /= c;} break; default: {printf(“data error.”);}

}

図 4 switch 処理による分岐処理

switch 処理中の break は大きな役割を持っています。というのも,仮に変数が case 1 を指して

いたとすると,もし break が書かれていなければ,case 1 の処理群 1 の処理が終わった後に続い

て,case 2 の処理へと移ってしまうからです。また,プログラム実行時において,指定された範

囲内の値ではない値が誤って入力される場合があり,そのような場合ではプログラムが異常処理

を行ってしまいます。そのため switch 処理では default 文を記し,入力ミスの場合でも処理が継

続される仕組みを備えています。前ページの表 17 の例で言えば,number に 1,2,3 以外の値で

ある 17 が入力されれば,それは default 文へと移動し,data error. が出力されます。 3.3. ループ処理 ここまでは分岐処理を見てきましたが,これからはループ処理を見ていきます。条件分岐にい

くつか方法があったのと同様に,ループ処理にも用途によっていくつか構文があります。

3.3.1. ループ処理(while 文)

ループ処理を行う場合に,多く用いられるその構造が,以下の図 5 に示されています。

図 5 while 文の前判定処理 前判定を用いたループ処理構造の場合には,処理を行う前に判定を行うため,ループの判定条

件によっては繰り返されるべき処理が全く行われないときもあります。例えば,出庫処理を行わ

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

30

せようとしたときに出庫対象が最初から 0 であるようなケースであり,現実の処理にはこのよう

なケースは稀ではないので,前判定で出庫件数が 0 であるかどうかを判定することも必要とされ

ます。前判定ループ処理は while 文と呼ばれる構文を用います。

表 20 ループ処理(while 文)の使用方法

用途 前処理判定の繰り返し制御 一般形式 while (条件){

処理群; }

では,練習プログラムを書いてみましょう。

ex15 ループ処理(while 文)

ex15 を見て分かるように,while 文の基本構造は,

のような形をとっており,繰り返し処理はこの条件が満たされたときに発生します。そして,

繰り返すたびにインクリメント演算子によって i の値が 1 ずつ増えていき,それが n より大きく

なったときにループを脱します。 このときに気をつけなければならないのは,whileの条件式です。もしインクリメント演算子を

忘れていたりして,条件式を脱する可能性のない処理だけでwhile文の中身を構成したりすると,

そこで無限ループという状態に陥ってしまう可能性ができてしまいます 7

7 もし無限ループに陥ってしまった場合,Ctrl+C で処理を中止させることができます。

。内容はきちんと写して

間違えないようにして下さい。さて,これを実行すると以下のようになります。

while (条件式) { }

#include <stdio.h> int main(void) {

int sum=0,i=0,n; printf(“Input number ¥‘n¥’ :”);

scanf(“%d”,&n); while(i<=n) { /* i が n 以下ならば下 2 行を行う */

sum += i; /* 変数 sum に i を加える */ i++; /* i を 1 大きくする */

} printf(“1~n までの和 = %d¥n”,sum); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

31

この n の値によってループ回数が変わりますが,そのとき i の値は 1~n まで変わることが分かり

ます。ex15 を見てみると,ループするたびに全ての i が sum に加算代入されるため,最初に 10を入れた場合は,sum の値は 1+2+3+…+9+10 となり,結果 55 となる訳です。 3.3.2. ループ処理(do while 文) 前判定でループ処理を行った while 文に対して,図 6 のように処理を実行した後に再度処理を

行うかを判定する構造に do while 文があります。

図 6 do while 文の後判定処理 do while 文では,最初に処理が実行されるので,条件に関わらず,最低一回の処理が行われま

す。そのことを十分に注意してください。

表 21 ループ処理(do while 文)の使用方法

用途 後処理判定の繰り返し制御 一般形式 do {

処理群; } while (条件);

例 do { scanf(“%d”,&number); printf(“Your number is %d¥n”,number);

} while (number > = 0 && number < = 100);

起点の中括弧の前に do を入れ,締めの中括弧の後に while(条件)を入れるのが基本の形です。

このとき注意すべき点は,while(条件)の後にセミコロンを入れることです。忘れないようにし

っかりと打ちましょう。それでは例文を見ていきます。

Input number ‘n’ :10 1~n までの和 = 55

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

32

ex16 ループ処理(do while 文) 書いていて気づいたかもしれませんが,これはプログラム ex15 の改変したもので,最初に代入

する n の値の範囲が設定されています。もしこれが do while 文ではなく while 文の場合は,n の

値が最初は決まっていないため判定ができず,バグが発生してしまいます。このように,代入値

の範囲を設定する際などに do while 文は力を発揮します。 3.3.3. ループ処理(for 文) 条件を用いてループの終了を指示する方法よりも,回数によるループの終了を指示する方法の

方が良い場合もあります。そのときによく使われるのが,for 文と呼ばれるものです。while 文よ

りも初期値,条件,増減値処理が分かりやすくなっています。

図 7 for 文の処理形式

for 文の構成は,以下の表 22 のようになります。

#include <stdio.h> int main(void) {

int sum=0,i=0,n; do { /* 以下 2 行は最低 1 回処理される */

printf(“Input number ¥‘n¥’(0 < n < 15) :”); scanf(“%d”,&n); } while(n<=0||n>=15); /* n が条件を満たしていればもう一度 */

while(i<=n) { sum += i;

i++; }

printf(“1~n までの和 = %d¥n”,sum); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

33

表 22 ループ処理(for 文)の使用方法

用途 繰り返しのための制御変数を指定する繰り返し制御 一般形式 for (初期値 ; 条件 ; 増減値処理) {

処理群; }

例 for ( i = 1 ; i < 10 ; i++) { sum += i; }

for 文では,while 文と違って処理群部分にループの条件を書かなくてもループを制御できると

ころが大きなポイントです。初期値部分では,変数の最初の値を決めることができ,最初に 1 回

だけ行われます。その後,前判定で条件の処理を行います。これが成立していればループし,成

立していなければそのまま for 文を通過します。 それでは for 文の例を挙げてみましょう。

ex16 ループ処理(for 文) ex17 は ex16と同一の働きをするプログラムです。while 文の代わりに for 文が使われています。

このプログラムでの留意点は以下のことのみです。 ①for 文の最初の i = 0 は初期値を表していて,これが最初に一回だけ処理されます。 ②次の i < = n の部分は条件で,前判定で行われます。 ③最後の i++は増減値処理で,for 文の最後までいったときにこの処理が行われます。 何がどの部分にあるかを間違えないように,しっかりと覚えましょう。 さて,同じ働きをするプログラムだけだとつまらないので,最後にもう一つだけプログラムの

例を挙げます。これはちょっと複雑ですが,間違えないように打ってください。

#include <stdio.h> int main(void){

int sum=0,i=0,n; do { /* 0<n<15 となる n を入力してもらう */

printf(“Input number ¥‘n¥’(0 < n < 15) :”); scanf(“%d”,&n);

} while(n<=0||n>=15); for(i=0;i<=n;i++) { /* i が n 以下なら繰り返し */

sum += i; } printf(“1~n までの和 = %d¥n”,sum); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

34

ex18 二重ループ処理(for 文) これを実行すると以下のようになります。内側のループから順次処理されていることを確認し

ましょう。

二重のループの処理を行うには,ex17 のように while 文,do 文,for 文のループの処理群内に

再度 while 文,do 文,for 文を用いれば可能です。これにて分岐処理,ループ処理の話はおしま

いです。お疲れ様でした!

演習問題 以下のプログラムを作成せよ。 (1) 2 つの整数 a,b の値をキーボードから入力し,もし a+b よりも a*b の方が大きければ a+b

< a*b を表示し,もし a*b よりも a+b の方が大きければ a+b>a*b を表示し,同じなら

a+b=a*b を表示するプログラム。

Input number ‘n’( 0 < n < 10 ):-2 Input number ‘n’( 0 < n < 10 ):11 Input number ‘n’( 0 < n < 10 ):4 1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 4 8 12 16 20 24 28 32 36 8 16 24 32 40 48 56 64 72

#include <stdio.h> int main(void) {

int a=1,i,j,n,x=0; do {

printf(“Input number ¥‘n¥’(0 < n < 10) :”); scanf(“%d”,&n);

} while(n<=0||n>=10); for(i = 1;i<= n;i++) {

for(j=1;j<=9;j++) { x = a*j; printf(“%4d ”,x);

} printf(“¥n”); a *= 2;

} return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

35

(2) 0~100 に制限された整数をキーボードから入力し,もし 40 より小さければ NG,40 より

大きければ OK を表示するプログラム。 (3) キーボードから整数 n の値を入力し,n!の値を求めるプログラム。

(4) 以下の表示をするプログラム(for 文、while 文それぞれやってみよう)。

(5) 1 から 100 まで数えていったときに,素数だけを表示するプログラム。

3.4. 配列 プログラム処理を行う事例のうち,最も多く使用されるのは,多数のデータを用いた集計処理

です。学校での成績処理や,企業の給与処理などなど,多量のデータ処理が必要となる場合がよ

くあります。このような場合に,int Aoyama,Asai,……というように,たくさんのデータ 1 つ

1 つに変数の名前をつけて宣言していては,宣言だけでも大変な作業になってしまいます。 そこで,例で挙げた点数や基本給といった同種の多量のデータを扱う場合に,配列という方法

を使います。このとき,配列も変数と同様の宣言ができます(int,float など)。しかし,変数名

が一つの記憶場所(領域)を確保するのに対して,配列名は一つの配列名で複数の記憶場所を確

保できる点が,配列名と変数名の一番の違いと言えます。

図 8 変数(左)と配列(右)

上の図 8 でも分かるように,変数 sum には一つの記憶領域が確保されているのに対して,配列

sum では 3 つの記憶場所が確保されています。実際に sum という配列名を用いた場合には,3 つ

の記憶場所のうち,どの記憶領域を処理の対象にするのかを区別しなければなりません。その場

合,最初の記憶領域を 0 番目とし,そこから数えて 1 つ目を 1,2 つ目を 2…と見ていきます。そ

して,実際にそれを表記する際には配列名の後に大括弧[]を用いて,sum[0],sum[1],sum[2]…と表していきます。 プログラムでは具体的にどのような形になるのか,以下の二つのプログラムを比較して見てい

きましょう。例題に挙げるのは,「5 人の生徒が何点取ったかを入力するためのプログラム」です。

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

36

ex19 変数名による入力処理

ex20 配列名による入力処理 どうでしょうか? たった 5 つでも変数名の方は面倒に見えてきませんか? これがクラス全

員分の成績処理ともなると,配列名を使用した方が圧倒的に楽に処理できるはずです。 さて,それでは配列の宣言に移ります。C 言語では,配列を使用する以前に,メモリに領域を

確保するため配列名とその大きさを宣言する必要があります。宣言の方法は変数名の宣言と同じ

ようにすることができますが,配列の大きさを同時に宣言しなくてはなりません。

表 23 配列の宣言方法

用途 配列の宣言 一般形式 データの型 配列名 [大きさ] [大きさ]……… 例 int number[5],number2[5];

int sum[100][20]; float average[8];

上の例で int number[5],number2[5]は整数型の宣言で,配列名 number 及び number2 にはそ

れぞれ 5 つずつのデータを格納することができる領域を確保できます。

#include <stdio.h> int main(void) {

int number[5],i; for(i=0;i<5;i++) {

scanf(“%d”,&number[i]); } return 0;

}

#include <stdio.h> int main(void) {

int number1,number2,number3,number4,number5; scanf(“%d”,&number1); scanf(“%d”,&number2); scanf(“%d”,&number3); scanf(“%d”,&number4); scanf(“%d”,&number5); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

37

また,sum[100][20]は二次元配列というもので,合計で 100×20 のデータを格納することがで

きます。次元数は,二次元以上も可能です。 最後の例は,実数型の宣言になっています。このような場合にも,[]で囲まれた添字(サフィッ

クス)は整数なので,間違えないようにして下さい。 宣言したら後は変数と同様に扱えます。ただ,C 言語では配列を宣言すると記憶場所が 0 から

配置されることに注意して下さい。即ち,int number[5]と宣言した場合,最初の記憶場所から見

ていくと,number[0],number[1],number[2],number[3],number[4]の五つの整数型の記憶

場所が確保されるということになります。 尚,同じ配列名の場合,初期値設定時に同時に複数の記憶場所に値を代入することができます。 〈例〉

int a[4] = {1,6, 7, 2} → a[0]=1,a[1]=6,a[2]=7,a[3]=2 その際,以下の注意点があります。 ① 列の大きさより設定した値の数が少ない場合,残りの領域には 0 が代入されます。

int a[4] = {4, 3, 2} → a[0]=4,a[1]=3,a[2]=2,a[3]=0 ② 配列の大きさが付加されていない場合,初期値の数=配列の大きさになります。

int a[ ] = {9, 3, 5, 7} → int a[4] = {9, 3, 5, 7} と同値 ③ 二次元以上の配列の場合,右側の[]からまとまりが作られて処理されます。

Int a[3][2] = {{500, 100}, {200, 300}, {150, 750}} それでは配列を使ったプログラムを書いていきましょう。

ex21 配列のプログラム

今回の number 配列において最初に宣言したのは number[5]なので,5 つしかアドレスが割り

当てられていません。したがって,それ以降のプログラムでは number[5]は使えないということ

に注意して下さい。 それでは,次に二次元配列のプログラムを書いてみます。

#include <stdio.h> int main(void) {

int number[5]; printf(“Input number ¥’n1¥’ :”); scanf(“%d”,&number[0]); printf(“Input number ¥’n2¥’ :”); scanf(“%d”,&number[1]); printf(“Input number ¥’n3¥’ :”); scanf(“%d”,&number[2]); number[3] = number[0] + number[1] + number[2]; number[4] = number[0]*number[1]*number[2]; printf(“n1+n2+n3=%d, n1*n2*n3=%d¥n”,number[3],number[4]); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

38

ex22 二次元配列のプログラム

ex22 を見ると,二次元配列 number[4][4]に入力する値は 4×4 の計 16 個が許されていることが

分かります。また前述したように,一次元配列 number[5]で宣言した配列は文中では number[5]は使用できないのと同様に,二次元配列 number[4][4]で宣言した配列は文中では number[4][4]が使えないことに注意して下さい。 最後に文字配列について少しだけ触れておきます。文字データを処理対象とした場合には普通

ポインタの活用が行われていますが,配列を用いて処理することもできます。 [university]とか[college]などの文字データは,1 文字ごとに 1 つの記憶領域を必要とします。

下の図 9 のように university,college はそれぞれ 10 文字と 7 文字必要とされています。ただし,

文字データは最後に¥0 文字を必要とするため,それぞれ 1 文字分加えて 11 文字,8 文字の記憶

領域を準備しなければなりません。

図 9 文字の格納

文字データの初期値の設定は,文字列で設定する場合と,複数の文字を組み合わせて設定する

場合とがあります。

#include <stdio.h> int main(void) {

int i,j,number[4][4]; for(i=0;i<4;i++) {

for(j=0;j<4;j++) { printf(“Input number n[%d%d] :”,i+1,j+1); scanf(“%d”,&number[i][j]);

} } printf(“¥n”); for(i=0;i<4;i++) {

printf(“ ”); for(j=0;j<4;j++) { printf(“%3d ”,number[i][j]); } printf(“¥n”);

} return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

39

〈例〉 char name[11] = {“university“} char name[11] = {‘u’,’n’,’i’,’v’,’e’,’r’,’s’,’i’,’t’,’y’,’¥0’}

文字列で初期設定を行う場合には最後に¥0 が自動的に設定されますが,文字を組み合わせて設

定する場合には最後に必ず¥0 を設定しなくてはなりません。

演習問題 以下のプログラムを作成せよ。 (1) ある店での一月から十二月までの店に訪れた客の人数及び売り上げは,以下の表のようにな

った。このとき,その店での一月当たりの来客数と売り上げを求めるプログラム。 月 1 2 3 4 5 6 来客数 4,368 3,467 4,945 2,754 2,492 2,146 売上 5,142,000 3,945,000 6,008,000 2,197,000 2,743,000 2,325,000 月 7 8 9 10 11 12 来客数 2,281 2,989 2,232 3,019 3,855 4,521 売上 2,098,000 3,195,000 2,644,000 3,391,000 4,289,000 5,899,000

(2) 受験者 10 人が受けた 3 つの教科について,受験者たちの中で総合点が最も高かった人,そ

れぞれの教科ごとの点が最も高かった人及び平均値を表示するプログラム。

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

40

4. 関数 同じ処理を繰り返したり,長いプログラム中で処理をひとまとめにしたりと何かと役に立つの

が関数です。そういえばこれまで出てきた main 関数,printf 関数なども関数ですね。本章では

関数について詳しく解説します。 4.1. 関数の基本

C 言語では,全ての手続きが関数の形で表現されています。例えば,main(void)で始まる手続

きも,printf や scanf 内で記述された手続きも,全て関数の形で処理されているのです。

図 10 関数の呼び出し

関数の呼び出しを図で表すと,上の図 10 のようになっています。知っての通り,最初に起動す

るのは main 関数なので,main 関数の上から順に処理されていきます。途中で関数 a の呼び出し

が起これば,mian 関数中での処理を中断して関数 a 内へと移動し,関数 a 内のプログラムを上

から処理していきます。関数 a 内の処理が終われば main 関数内で中断していたプログラムが再

開し,処理されます。プログラム中の関数は,これを繰り返して処理されていくことになります。 関数の一般的な宣言の仕方は,以下のようになります。ただ,これだけでは分かりにくいと思

うので,下に ex23 を用意しておきました。早速書いてみましょう。

表 24 関数の宣言方法

用途 関数の宣言 一般形式 データの型 関数名(データ型 引数, データ型 引数……) 例 void main(void)

int addition(int x, int y)

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

41

ex23 関数の宣言

できましたか? これを起動すると,以下のようになります。

このことから,このプログラムは入力した a と b の和を求めるプログラムであることが分かりま

す。それでは一体どこが和を処理するプログラムになっているのでしょうか。 まず,main 関数にはそれらしきものがないことが分かります。そこで,addition 関数を見てみ

ましょう。addition 関数の下の方に,z = x + y の一行があるのが分かりますか? 実は,このプ

ログラムで和を計算したのは,addition 関数内の z = x + y なのです。 先ず最初に main 関数内の scanf 関数によって,整数 a 及び b の値をキーボードから入力しま

す。その後,

という一文に辿り着いています。見た感じ,ここが一番怪しい! と思いますよね? その通り

で,この部分が関数を呼び出している部分なのです。最初に宣言した「addition」という関数名

と同じ名前がプログラム中で出てきた時点で,プログラムはそれを関数として処理しようとしま

す。そのため,main 関数内でのプログラム処理は一時的に中断され,addition 関数へと移るこ

とになるのです。結果 addition 関数が処理され,和が計算されることになりました。 さて,当たり前のように main 関数から処理が始まると書いていますが,ex23 では先に addition関数が記述されていますね。main 関数から処理するのであれば main 関数を上に書けばいいじゃ

c = addition(a, b);

Input a :12 Input b :9 a + b = 21

#include <stdio.h> int addition(int x, int y) { /* 関数 addition の定義 */

int z; z = x + y; return z;

} int main(void) {

int a,b,c; printf(“Input a :”); scanf(“%d”,&a); printf(“Input b :”); scanf(“%d”,&b); c = addition(a, b); /* 関数の呼び出し */ printf(“a + b = %d¥n”,c); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

42

ないかという声があるでしょう。しかし,それを実際にやるとエラーになってしまいます。これ

はコンパイラの仕様で,コンパイラはまず main 関数からコンパイルを実行していきます。この

時に,main 関数の後ろに addition 関数を書いてしまうと,main 関数内で addition 関数を呼び

出すところで「addition ってなんだ?」とコンパイラが困ってしまうのです。したがって additionというのは関数だよとコンパイラに予め教えておくために,main 関数の前に別の関数を記述する

必要があるのです。 4.1.1. 引数

ex23 の addition(a,b)という部分。addition が関数だということは分かりましたが,その後ろの

(a, b)という部分は何でしょう? これは引数というもので,関数処理において最も大切な事項に

なります。 引数とは関数を呼び出す際に,関数に持っていくデータのことで,大体の関数で使用されます。

基本的に関数と関数はそれぞれが独立しているため,最初の関数内で宣言した x, y などの変数,

それに格納された値は,別の関数内では使うことができません。しかし,場合によっては関数に

データを持って行きたいと思う時があると思います。そのような時,引数を使うことになります。 引数は,関数名の後の小括弧( )内に入れて使うことになります。引数を用いれば,ある関数で

出した二つの値 a,b の和を,別の関数で求めることもできるようになります。前記の ex23 のプ

ログラムでは,(a,b)が引数部分であり,これによって,addition 関数内に a と b の二つの値を持

ち込むことができ,和を計算できたということなのです。

図 11 関数の引数

ここで忘れてはならないのは,引数を持っていく前に,関数の宣言で引数の数の分だけ変数を

宣言しておく必要があるということです。例えば ex22 では,

と宣言されています。このように引数の数だけ受ける場所がないと,コンパイル失敗になってし

まうので注意して下さい。 4.1.2. 戻り値 しかしここで,まだ足りないものがあることに気づきましたか? 引数の処理はあくまで関数

内にデータを持ち込むだけの処理であって,元の関数に値を戻すことはできません。したがって,

int addition(int x, int y){…}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

43

このままでは和を求めてもそれを元の関数に持っていくことができません。そこで,戻り値とい

うものを使用します。戻り値は関数自体に値を持たせるもので,1 つの関数を呼び出した際に 1つ値を与えることができます(複数個指定することはできません)。そのために必要なのは,関数

の最後にある

という部分で,それは関数そのものに値を与えさせるものです。上では return の後にある z が戻

り値として返され,結果,

の右辺である addition(a,b)の部分に zが代入され,それが cに代入されるということになります。

その値の型は,最初に宣言した int addition(int x, int y)の頭の「int」に依存していて,例えばこ

れが float addition(int x, int y)となっていれば,関数自体が持てる値は実数型となります。 また,関数自体に値を持たせる必要がない,という場合もあると思います。そのようなときに

使われるのが void というものです。これは値を持たないことを表すものであり,引数部分に voidを書くこともあります。例えば

という例の場合,関数myprintでは整数型の引数 2 つを受け取って戻り値はなし,関数myinputでは引数はないが,整数型の戻り値があることになります。戻り値がない場合,returnは省略で

きます 8

図 12 関数の戻り値

8 main 関数を定義するときは int main(void)でした。戻り値が int 型で指定されているので return 0;が必要だったわけです。

void myprint(int x,int y){…} int myinput(void){…}

c = addition(a,b);

return z;

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

44

4.2. プロトタイプ宣言 関数を作った場合,main 関数よりも前に記述する必要があることは前述しました。しかし,や

っぱり main 関数を 1 番最初に書きたいというニーズもあります。それに答えるのがプロトタイ

プ宣言です。コンパイラはプログラム中にどんな関数があるのかを予め知っておけばよいのです

から,どんな関数があるのかということだけを予め書いてしまおうという作戦です。プロトタイ

プ宣言を用いて,ex23 を記述します。

ex24 プロトタイプ宣言を用いた例 2 行目がプロトタイプ宣言です。ここでは仮引数は省略できます(もちろん,省略せずに int addition(int x,int y)としても構いません)。関数の記述する順番が ex22 と異なっている点に注目

して下さい。 4.3. 再帰処理 処理手順を関数として表現した場合,関数から他の関数を呼び出し,処理手順を構造化して大

規模なプログラムを作成できます。また,多数の関数からプログラムが構築されているため,大

規模なプログラムであっても,プログラムの作成や保守改良を,全体を考えることなく,それぞ

れの関数だけで行えるという利点があります。 さらに C 言語では,関数から関数への呼び出しが,他の関数を呼び出せるだけでなく,自分自

身も呼び出せるという特徴があります。これを再帰的用法と言います。 以下,例のプログラムを記述します。

#include <stdio.h> int addition(int ,int); /* プロトタイプ宣言 */ int main(void) {

int a,b,c; printf(“Input a :”); scanf(“%d”,&a); printf(“Input b :”); scanf(“%d”,&b); c = addition(a, b); /* 関数の呼び出し */ printf(“a + b = %d¥n”,c); return 0;

} int addition(int x, int y) { /* 関数 addition の定義 */

int z; z = x + y; return z;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

45

ex25 関数の再帰的用法 この再帰的用法は何度でも行えるため,処理結果を再度利用して次の結果が得られるという問

題に対しては,特に有効な手段として用いることができます。例えば,預貯金における複利計算

がその例に挙げられます。 上の ex25 においては,最初に整数の引数 n が f 関数に入り,変数 x に n が代入されます。その

後,x の値が 1 より大きければ(1 でなければ)f(x - 1)*x が戻り値になり,x の値が 1 ならば 1が戻り値になります。もし n = 5 であれば x = 5 であるので,f(4)*5 が戻り値になるわけです。さ

て,ここで再度 4 という引数を持って,f 関数が呼び出されます。 これを繰り返すことを考えると,このプログラムは

を計算することになります。なので,結果として 12345 ・・・・=m

を処理し,結果 m には 5!の値が代入されることになります。再帰処理では一見複雑に見える問題

を少ない記述量で解決できるという利点があります。一方次々と関数が呼び出されている間も計

算結果を保持していなければならないためメモリ内の領域を多く使うという欠点があります。

4.4. ライブラリ関数 関数の中には,誰もが簡単に利用することができるものが既に準備されています。この関数の

ことをライブラリ関数と言います。既に何度も利用した printf 関数や scanf 関数は入出力のため

のライブラリ関数の一つです。 各ライブラリ関数はヘッダにおいて宣言され,#include 処理において指示することで利用可能

return f(4)*5 → return f(3)*4 → return f(2)*3 → return f(1)*2 → return 1

#include <stdio.h> int f(int x) { /* 関数 f の定義 */

if(x > 1) { return f(x-1)*x; /* 再帰呼び出し */ }else {

return 1; }

} int main(void) {

int n,m=1; printf(“Input n :”); scanf(“%d”,&n); m = f(n); /* 関数の呼び出し */ printf(“n! = %d¥n”,m); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

46

になります。例えば stdio.h には printf 関数や scanf 関数が準備されています。 標準ヘッダとしては,<stdio.h>以外に, <assrt.h> , <ctype.h> , <errno.h> , <float.h> , <limits.h> , <locale.h> , <math.h> ,

<setjmp.h> , <signal.h> , <sydarg.h> , <stddef.h> , <stdlib.h> , <string.h> , <time.h> があります。 この中でも,<ctype.h>は文字種別の判定や文字変換を行う関数を宣言し,<math.h>は数学関

数を宣言するためのヘッダです。また,エラーの報告を行う処理(マクロ)を宣言するものとし

て<errno.h>があり,種々の限界やパラメータを宣言するものとして<limits.h>があります。 以下,ライブラリ関数の一覧を載せます。必要ならば,下の関数を使用してください。

表 25 ライブラリ関数一覧

関数(ヘッダ) 用途 関数 用途 <stdio.h> 標準入出力関数 printf 標準出力 scanf 標準入力 getchar 一文字入力 putchar 一文字出力 gets 標準出力から一行バッファ

へ書き込み putc ストリームへの一文字書き

込み

sprintf 文字列のバッファ出力 sscanf 文字列より読み込み <ctype.h> データ変換関数 tolower 大文字を小文字に変換 toupper 小文字を大文字に変換 atoi 文字列を int 型に変換 <errno.h> 文字操作関数 isdigit 10 進数かチェック isprint 表示文字かチェック <float.h> 文字列操作関数 memcopy 文字の複写 strcpy 文字列の複写

strspn 文字列の検索 strcat 文字列の追加 strlen 文字列の長さ strcmp 文字列の比較 <limits.h> プログラム診断関数 assert プログラムに診断機能付加 <stdlib.h> メモリ関数 malloc メモリ領域の確保 free メモリの解放 <math.h> 数学関数 abs 絶対値 sin, cos, tan 三角関数 exp, log, log 10

指数及び対数関数 pow べき乗関数

sqrt 平方根関数 rnd 乱数発生 ceil 引数より大きい最小の整数 fmod 剰余

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

47

<setjmp.h> ファイル操作関数 rename 名前の変換 fclose ファイルクローズ

fopen ファイルオープン <signal.h> 時間関数 time 現在の暦時刻の決定

それでは,実験として f(x) = x2 + 4x + 6 を求めるプログラムを作成してみましょう。

ex26 ライブラリ関数

この中にある pow(n,2)はべき乗数を表すもので,今回では n2を表しています。これは上記の

表 25でも分かるように数学関数に属しているので,最初に#include <math.h>を宣言しています。

4.5. 変数の有効範囲 これまで主に学んできたプログラム中には main 関数のみしかないものが多くありました。この

場合,main 関数ないで定義した変数は main 関数のどこでも使うことができました。しかし,関

数が複数ある場合はどうでしょうか? たとえば,ex23 の addition 関数の中で,main 関数内で

定義した変数 a を使うことはできるのでしょうか? この節では変数の有効範囲について触れて

いきます。 4.5.1. 変数の有効範囲の原則 基本的に,ある関数内で定義した変数は,別の関数からは参照することができないというのが原

則になっています。すなわち,他の関数の中で使われている変数の名前を全く気にすることなく,

関数毎に自由に変数を使うことができます。例えば,別の関数の中で同じ名前の変数を使うこと

も可能なわけです。この例を挙げてみましょう。

#include <stdio.h> #include <math.h> /* math.h のインクルード */ int main(void) {

float n.f; do {

printf(“Input n :”); scanf(“%f”,&n); f = pow(n,2)+4*n+6; /* ライブラリ関数の呼び出し */ printf(“n! = %f”,f); printf(“¥nIf you want to end, you input 1. :”); scanf(“%f”,&n);

} while(n != 1.0); printf(“end.¥n”); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

48

ex27 変数の有効範囲の確認

ex27は,ex23の addition関数における変数の名前をmain関数内のそれと同じにしたものです。

実行結果は ex23 と同一になります。コンパイルの際,main 関数で変数 a, b, c のメモリ領域を確

保したあと,addition 関数内の変数 a, b, c のメモリ領域は別の場所に取られます。したがって名

前が同じでも,メモリ上のアドレスが異なっているので問題なく実行できるわけです。 ここで注意が必要なのは,別の関数内で定義した変数を今使っている関数の中で使うことはでき

ないという点です。例えば ex27 の addition 関数を以下のようにするとエラーになります。

人間からすれば,変数 a, b は main 関数にあるやつだろと叫びたくなりますが,コンパイラは

そうは認識してくれません。基本的に,関数内ではその関数で定義した変数しか使えないという

ことを念頭に置いておきましょう。

4.5.2. 大域変数 しかし,何度も何度も同じ変数を使うのに,いちいち値の受け渡しをするのは面倒だという場面

も登場します。そこで用いられるのが大域変数(グローバル変数)です。これは,関数の外で変

数の定義を行っておくと,プログラムの全ての場所からその変数にアクセスできるというもので

す。

int addition(int x, int y) { /* 関数 addition の定義 */ int z; z = a + b; /* main 関数で定義した a, b は使えない*/ return z;

}

#include <stdio.h> int addition(int a, int b) { /* 関数 addition の定義 */

int c; /* 変数名は main 関数内のものと同じ */ c = a + b; return c;

} int main(void) {

int a,b,c; printf(“Input a :”); scanf(“%d”,&a); printf(“Input b :”); scanf(“%d”,&b); c = addition(a, b); /* 関数の呼び出し */ printf(“a + b = %d¥n”,c); return 0;

}

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

49

ex28 大域変数

これを実行すると次のようになります。

注意すべきなのは,main 関数,myprint 関数ともに変数 g の定義は行われていな点です。関数

外で変数 g を定義したことにより,どちらの関数内でも定義なしでこれを使うことができたとい

うことを理解してください。

4.5.3. 静的変数 いきなりですが,次のプログラムを実行するとどうなるでしょうか?

関数は,main 関数と myprint 関数の 2 つです。まず myprint 関数を見ると,整数型変数 i が定義され初期値は 0 になっています。次の行で i に 1 加えられ,それを表示します。最後に i を戻り値にしています。この状態では,myprint 関数が呼び出されるたびに変数 i の初期化が行わ

れ,何度呼び出しても戻り値は i = 1 で変わることはありません。しかもこの場合,main 関数内

で myprint の戻り値が 10 未満なら繰り返せと命令しているわけですから,このプログラムを実

#include <stdio.h> int myprint(void){ /* 関数 myprint の定義 */ int i=0; i++; printf("%d ",i); return i; } int main(void) { while(myprint( )<10); /* myprint の戻り値が 10 未満ならループ */ return 0; }

7*7 = 49 The gravitational acceleration is 9.800000 m/s.

#include <stdio.h> float g=9.8; /* 大域変数 g の定義 */ void myprint(void) { /* 関数 myprint の定義 */ printf(“The gravitational acceleration is %f m/s.¥n”,g); } int main(void) { int a; a = g*5; printf(“7*7 = %d¥n”,a); myprint( ); /* 関数の呼び出し */

return 0; }

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

50

行すると無限ループに陥ってしまいます。これを防ぐためにはどうすればよいでしょうか?

myprin 内で毎回 i の値が 0 に初期化されるのが原因ですので,int i; にして定義だけにすればよ

いと考えるかも知れません。しかしそれでは i の初期値が定まらないのでコンパイラはエラーを

返してきます。 そこで、関数が初めて呼び出されたときだけ初期化したいという要望を実現するのが静的変数

です。静的変数は一度確保されたメモリ領域を使い続けます。したがって、2 度目以降その関数

が呼び出された場合でも変数がアクセスするメモリ領域は同じなので前に保存した値がそのまま

使えます。静的変数は変数定義の前に static をつけることによって実現できます。

それでは,上のプログラムに静的変数を導入し,実行してみましょう。

ex29 静的変数を使った例

静的変数によって,myprint が初めて呼び出されたときのみ i = 0 となります。2 回目以降はこ

の行は実行されないため,i の値は 1 ずつ増え 10 になったところでループを脱します。したがっ

て,実行結果は以下のようになります。

演習問題 下記のプログラムを作成せよ。

(1) 順列(パーミュテーション)を示す nPk及び,組み合わせ(コンビネーション)を示す nCk

を求めるプログラム。 (2) 月に 1 割ずつ利子を付けるとする。このとき,n ヶ月後にどれだけの利益を得られるかを示

すプログラム。尚,頭金の額も入力できるようにすること。

(3) 三角形の三辺を入力したとき,その面積を求めるプログラム。

1 2 3 4 5 6 7 8 9 10

#include <stdio.h> int myprint(void){ /* 関数 myprint の定義 */ static int i=0; /* 静的変数 i の定義 */ i++; printf("%d ",i); return i; } int main(void) { while(myprint( )<10); /* myprint の戻り値が 10 未満ならループ */ return 0; }

static 変数の型 変数名;

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

51

5. ポインタ パソコンに内蔵されているメモリには,それぞれの領域を示す番地が存在しています。それが

アドレスと呼ばれているもので,あらゆるプログラムを動かす毎に,このアドレスに「0」や「1」の値が入れられます。C 言語を動かすときも例外ではなく,色々なものにアドレスが割り当てら

れます。その中でも,変数は型によって一定の大きさのアドレスが割り当てられ,そこに値を入

れられるようになっています。このアドレスを扱うために用いるのがポインタです。これを用い

ると,関数での値の受け渡しがスムースに行えたり,可変長のデータを扱えるようになったりし

ます。本章ではこのポインタについて見ていきましょう。

5.1. ポインタ変数 ポインタ変数はアドレスの値を記憶することのできる変数です。これを定義するには通常の変数

定義に加えて,変数名の前に*(アスタリスク)を付けます。

表 26 ポインタ変数の定義

用途 ポインタ変数の定義 一般形式 変数の型 *変数名; 例 int *p;

上の例の場合,ポインタ変数 p が定義されます。ポインタ変数を定義するときには*が必要です

が,その後の処理を記述する際はその他の変数と同じく p と書くだけで使えます。見た目は一般

の変数ですが,実際はアドレスを指示しているだけであるということに注意しましょう。

図 13 ポインタ変数の概念図

さて,ポインタはアドレスを得るために定義しているのに,なぜ変数の型まで宣言しなければな

らないのでしょうか? これは,変数によってメモリ上の領域の大きさが異なることに起因して

います。メモリには 1 バイト毎にアドレスが割りすられていますが,図 13 の右側のように,例え

ば整数型変数の場合,その領域は 4 バイトにまたがります。ポインタ変数は指し示す変数が存在

アドレス

0010D129

0010D12A

0010D12B

0010D12C

0010D12D

0010D12E

メモリ

100 整数型変数

アドレス

0000B105

0000B106

0000B107

0000B108

0000B109

0000B10A

メモリ

0010D12A ポインタ変数

p

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

52

する領域の最初のアドレスを記憶しています。したがって変数の型が何かということが分かって

いないと,どこまで指した先の変数領域なのかが分からないため,変数の型指定が必要なのです。

5.2. アドレス演算子と間接演算子 前節で,今まで使った普通の変数の他に,変数のアドレスのみを記憶するポインタ変数の定義の

仕方が分かりました。ここでは,ポインタ変数にアドレスを代入する方法について学びましょう。 とはいっても,ソース上でポインタ変数に対し直接アドレスを記述して代入するということはで

きません。なぜなら仮にアドレスをソース上で指定したとしても,実行する際には別のプログラ

ムによってその領域は使われているかも知れないからです。変数が使用するアドレスは,プログ

ラムを実行した際にメモリの未使用領域から動的に決定されます。したがってポインタ変数にア

ドレスを代入するには,他の変数のアドレスを記憶するという方法が取られます。変数のアドレ

ス情報を取り出すには次のように変数の前にアドレス演算子&を前置します。

それでは,ポインタ変数とアドレス演算子を用いた例を挙げましょう。

ex30 ポインタ変数への代入

これを実行すると,以下のようになります。

ポインタの話に行く前に,printf 構文の中で書式表現に%p が使われています。これは,アドレ

スを示すために用いられますが,普段アドレスを表示することは滅多にないので覚えておく必要

はありません。 ex30 では p は整数型ポインタ変数として,a は整数型変数として定義されています。次の行で

アドレス演算子を用いて a のアドレスを p に代入しています。よって p の指すアドレスと,a の

アドレスは一致します。実行結果を見ると両者のアドレスが同じになっていることが確認できま

す。興味深いのは,実行する度にアドレスが変化しているということです。このことから,変数

定義の度に確保されるメモリ領域が決定されていることが分かります。 さて,変数からアドレスを取得する方法は分かりました。今度はポインタ変数が指し示すアドレ

スから値を取り出す方法です。これには間接演算子*を使います。次のようにポインタ変数の前に

p = 0033FB9C &a= 0033FB9C

#include <stdio.h> int main(void){ int *p,a; /* ポインタ変数 p と整数型変数 a の定義 */ p = &a; /* p に a のアドレスを代入 */ printf("p = %p¥n",p); /* p の指すアドレスを表示 */ printf("&a = %p¥n",&a); /* a のアドレスを表示 */ return 0; }

&変数名

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

53

*を置くことにより,それが指す先の値を取り出すことができます。

それでは例を見てみましょう。

p のみポインタ変数で,残りの a と b は整数型変数です。&a とすれば,変数 a の保存されてい

るアドレスが取得できるので,2 行目でポインタ変数 p に a のアドレスを保存しています。*p と

すれば,ポインタ変数 p の指し示すアドレスに保存されている値を取り出せるので,それを b に

代入しています。結局このプログラムではポインタ変数を介して間接的に b に a の値を代入した

ことになります。

図 14 ポインタへの代入の流れ 図 14 に上の例の処理の流れを示しました。

① 変数 a のアドレスをポインタ変数 p に代入し, ② ポインタ変数 p が指す内容を ③ 変数 b に代入する

という流れになっていることを確認してください。①~③のうちポインタ変数の役割は①,②で

す(③は代入演算子の働き)。図の通り,ポインタ変数はアドレスを覚え,指し示すことしかでき

ません。特に,ポインタ変数を定義しても,アドレスを記憶する以外のメモリ領域は確保されて

いないことに注意が必要です。次の例はエラーになります。

int *p, a = 1, b; /* 整数型ポインタ変数 p と整数型変数 a, b の定義 */ p = &a; /* p に a のアドレスを代入 */ b = *p; /* b に p の指し示す先(=a のアドレス)の値を代入 */

*ポインタ変数名

アドレス

0000B105

0000B106

0000B107

0000B108

0000B109

0000B10A

メモリ

ポインタ p

アドレス

0010D129

0010D12A

0010D12B

0010D12C

0010D12D

0010D12E

メモリ

1 a

アドレス

001100A8

001100A9

001100AA

001100AB

001100AC

001100AD

メモリ

1 b

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

54

ポインタ変数pを定義した段階で,pの指すアドレスは不定です。またその指し示している領域は

変数定義していないので確保されていません。そこに値を代入しようとしているのでエラーが返

ってきてしまうのです 9

。要はすでに値を格納しようとしている箱が詰まっているのに値を押しこ

めようとしているわけです。きちんと領域を確保してやれば値を代入できます。

ポインタ変数の基礎は理解できましたか? 普通の変数とは趣が異なるのでじっくり考えなが

ら理解に努めてください。

5.3. 関数とポインタ変数 これまで,関数の戻り値は 1 つしか指定できませんでした。しかし,それでは不便な局面も多々

あります。実は,引数にポインタ変数を指定することでこの問題を解決できるのです。数字の入

れ替えを扱った次の例を見てみましょう。

ex31 ポインタ変数の関数受け渡し 9 不定値が元でバグを起こす場合が多々あるので,意図的にポインタに何も代入されてないようにすることがあります。

int *p=NULL;とすることで,p は何も指していないポインタになります。

#include <stdio.h> void myswap(int *a,int *b){ /* ポインタ変数を受け取る */ int tmp; tmp = *a; /* 各アドレスの値を交換 */ *a = *b; *b = tmp; } int main(void){ int a,b; printf("Input a : "); scanf("%d",&a); printf("Input b : "); scanf("%d",&b); printf("Now, a=%d, b=%d¥n",a,b); myswap(&a,&b); /* 引数にアドレスを指定 */ printf("Then, a=%d, b=%d¥n",a,b); return 0; }

int *p, a; /* 整数型ポインタ変数 p,整数型変数 a の定義 */ p = &a; /* p に a のアドレスを代入 */ *p = 10; /* a の領域は確保済みなのでこれは OK */

int *p; /* 整数型ポインタ変数 p の定義 */ *p = 10; /* これは不可能 */

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

55

このプログラムは 2 つの値をキーボードから受け取り,それらを表示します。その後,変数の内

容を交換して再び表示を行います。この変数の内容を交換するという作業にミソがあります。

main 関数内の myswap 関数呼び出し部分を見てください。関数の引数にアドレス演算子を用い

て,変数 a,b のアドレスが指定されていることが分かると思います。これを受け取るために,2行目では引数にポインタ変数が指定されています。定義の時と同様に,変数の前に*を付けます。 さて,注目すべきなのは myswap 関数の戻り値が void,すなわちないという点です。しかしな

がら,実行結果を見ると確かに変数の中身は入れ替わっていました。これこそポインタ変数の効

果です。myswap 関数は変数 a のアドレスと b のアドレスを受け取ります。先ず,a のアドレス

の値を変数 tmp に代入します。そして a のアドレスの値に b のアドレスの値を,さらに b のアド

レスの値に tmp の値を代入します。結果として a のアドレスの値は元々の b の値,b のアドレス

の値は元々の a の値になるので,値が交換されたわけです。a,b のアドレス自体には手は加えら

れてないので,main 関数内で a と b の値を読むと,元々の値が入れ替わっているというわけで

す。 C言語では戻り値を 2 つ以上指定することができないので,myswap関数で変数a,bを受け取り

入れ替えるという手法をとることは出来ません。ポインタを上手に使うことで,複数の戻り値が

あるのと同等 10

なことが出来るということを頭の片隅に入れておきましょう。

5.4. 配列とポインタ変数 実は,配列とポインタ変数は全く同等なものなのです。…なんのこっちゃ? という声が多い

でしょう。次の例を見てください。

ex30 ポインタと配列 実行結果は次のようになります。

ex32 において,p はポインタ変数として,a は配列として定義されています。5 行目を見ると,

p = a とポインタに配列が代入されています。これは一体どういうことでしょうか? 実は,配列

10 scanf 関数も複数の変数に対してキーボードからの入力を受け付けます。したがって戻り値が複数必用な状況になるので,

引数にポインタ変数を使っているのです。

a[0]=1 a[1]=2 a[2]=3 a[3]=4 a[4]=5

#include <stdio.h> int main(void){ int *p,i; /* ポインタ変数 p の定義 */ int a[5]={1,2,3,4,5}; /* 整数型配列 a の定義 */ p = a; /* 配列とポインタは同じ!! */ for(i=0;i<5;i++){ printf("a[%d]=%d ",i,*p); p++; /* 次のアドレスへ */ } return 0; }

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

56

はその先頭要素へのポインタで,a には a[0]のアドレスが記憶されているのです。なので,5 行目

のようにポインタ変数に配列の先頭要素のアドレスを代入できたのです。さて,このプログラム

では for 文を用いて a の各要素を表示しています。ここで用いられているのがポインタの演算で

す。ポインタ変数に値を 1 足すと,次の要素へのアドレスになります。ex32 では要素の数だけポ

インタを 1 ずつ進めて全ての要素を表示しています。

5.5. メモリの動的確保と解放 ポインタを学ぶと,メモリの動的確保が理解できるようになります。メモリの動的確保という

のは,必要な時にメモリに領域を確保するという意味です。これまで,メモリ領域の確保は全て

変数定義の際に行っていました。配列を考えてみましょう。空の配列を定義するとき,一緒に要

素数を指定していましたね(例えば,ex19 を参照)。これによって必要な箱の数だけメモリを確

保していたわけです。しかし,必要な箱の数が分からない場合はどうすればよいでしょうか? も

ちろん,箱の数が分かってからその分だけメモリを確保した方が無駄な場所を取らずに済むので

効率的です。そこで,このような場合はとりあえず配列(=ポインタ変数)を定義しておき,必要

な要素数が分かってからメモリを確保するという手段が取られます。 5.5.1. メモリの動的確保

それでは,実際にメモリの動的確保を行ってみましょう。これには malloc 関数を用います。

malloc 関数は stdlib.h をインクルードしないと使えません。

表 27 malloc 関数

用途 メモリの動的確保 一般形式 #include <stdlib.h>

(ポインタ変数の型)malloc(確保するメモリの大きさ) 例 int *p;

p = (int *)malloc(sizeof(int));

malloc関数は,取得するメモリのサイズを引数にし,取得したメモリ領域の先頭のアドレスへ

のポインタを返します。しかし,malloc関数はさまざまな型のメモリ領域確保に対応するため,

戻り値のポインタの型は指定していません 11

この関数はメモリ領域の確保に失敗した場合,空のポインタ NULL を返します。条件分岐を使

って失敗した場合の処理も付け加えておくとよいでしょう。

。そこでキャスト演算を用いてポインタの型を明示

してやる必要があります。具体的には,malloc関数の前に(ポインタ変数の型)を置き,確保したメ

モリ領域がどの型の変数が使うのかを明らかにします。例では,サイズの指定にはsizeof演算子を

用いています。sizeof(変数の型)とすることでその変数が必要としているメモリ領域の大きさが分

かるのです。

11 このように,型が指定さていないポインタを汎用ポインタといい,void *で表わします。

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

57

5.5.2. メモリの開放 動的に確保したメモリ領域は放っておくとプログラム終了後も確保したままになり,使えるメ

モリ領域を減らしてしまうことになります。使用しなくなった領域は必ず free 関数を用いて解放

するようにしましょう。free 関数は次のように用います。

必ず,「確保した領域への」ポインタ変数を指定しましょう。また,2 回同じ領域を解放したりし

てはいけません。 最後に,メモリの動的確保とメモリの開放を行うプログラム例を挙げておきます。この程度の

プログラムではメモリの動的確保を行う意味があまり見えてきませんが,配列操作や文字列操作

などをしだすと,その有用性が見えてくると思います。

ex33 メモリの動的確保と解放 演習問題

main 関数内で定めた任意の長さの文字列をコピーする関数,char* copy(char*)を作成せよ。

また,実際に copy 関数から受け取った文字列を main 関数内で表示せよ。

#include <stdio.h> #include <stdlib.h> int main(void){ int *p; /* ポインタ変数の定義 */ p = (int *)malloc(sizeof(int)); /* メモリ領域の確保 */ if(p == NULL){ /* 失敗した場合 */ printf(“メモリの確保に失敗しました。”); return 1;

} *p = 10; printf(“%d¥n”,*p); free(p); /* メモリの開放 */ return 0; }

free(確保した領域へのポインタ変数)

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

58

6. 簡易デバッグ作業 プログラムを書いていると,事あるごとにエラーが発生してしまいます。今回の講習会で起こ

りそうな事例について注意点を列挙しておきたいと思います。

・命令の後の;(セミコロン)を忘れていませんか?

→必ず命令の後には ; を付けましょう。 ・どこかで全角文字を使っていませんか?

→特にスペース(空白)部分は全角・半角の見分けがつかないので注意しましょう。 ・変数名・関数名を正しく入力していますか?

→スペルミスをしているとエラーになります。よく確認しましょう。 ・ループの条件は正しいですか?

→おかしな条件だと無限ループに直行です。よく考えて正しい条件を指定するようにしまし

ょう。ループで不具合が生じたら,途中に printf 文を挿入し,状態を確かめるという手段

も有効です。 ・配列のアドレスは存在していますか?

→配列は 0 番から始まります。配列の個数-1 までしかアドレスは存在しないことに注意です。 ・引数・戻り値の型の整合性は取れていますか?

→型の異なる引数や戻り値を使うと動作がおかしくなってしまいます。きちんと確認しまし

ょう。 ・ポインタ変数と一般の変数を混同していませんか?

→ポインタ変数は定義以外では普通の変数と見た目は同じです。ポインタ変数名だと分かる

名前を付けるなどして対応しましょう。 エラーが起こったら先ずコンパイラのメッセージを読み,発生行へ飛んで内容を確認するとい

う手順で 1 つ 1 つ直していくしかありません。根気のいる作業ですが頑張りましょう!

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

59

索 引

¥", 12 #define, 14 #include, 14 %, 16 %c, 9, 11 %d, 9, 11 %f, 9, 11 %s, 9, 11 &, 13, 51 ;, 3, 7 ¥?, 12 ^, 20 ~, 20 ¥", 12 ¥¥, 12 ¥0, 12, 37 8 進数, 21 ¥a, 12 abs, 45 assert, 45 atoi, 45 ¥b, 12 break, 27 case, 27 ceil, 45 char, 11 cos, 45 C 言語, 3 default, 28 do while, 30 double, 11 else, 24 else if, 25 exp, 45 ¥f, 12 fclose, 45 float, 11 fmod, 45

fopen, 46 for, 31 free, 45, 55 getchar, 45 gets, 45 if, 22, 23 int, 11 isdigit, 45 isprint, 45 log, 45 log 10, 45 long, 11 main 関数, 3, 6 malloc, 45, 55 memcopy, 45 ¥n, 7, 12 NULL, 54 pow, 45 printf, 45 printf 関数, 7, 9 putc, 45 putchar, 45 ¥r, 12 rename, 45 return, 42 rnd, 45 scanf, 45 scanf 関数, 12, 13 short, 11 sin, 45 sizeof, 55 sprintf, 45 sqrt, 45 sscanf, 45 static, 49 strcat, 45 strcmp, 45 strcpy, 45

慶應義塾大学ロボット技術研究会 プログラミング講習 2011

60

strlen, 45 strspn, 45 switch, 27 ¥t, 12 tan, 45 time, 46 tolower, 45 toupper, 45 ¥v, 12 void, 42 while, 28 後処理判定の繰り返し制御, 30 アドレス, 13, 50 アドレス演算子, 51 インクリメント演算, 18 オブジェクトプログラム, 4 関係演算子, 23 関数, 39, 50 間接演算子, 51 機械語, 3 キャスト演算, 19 グローバル変数, 47 コメント, 6 小文字記入, 3 コンパイル, 4 再帰, 43 算術演算子, 16 実行プログラム, 4 自由形式, 3 条件分岐, 22 書式表現, 8, 11 数値定数, 8 静的変数, 49 絶対値, 45 ソースプログラム, 4 大域変数, 47 代入演算, 17 代入演算子, 16 定数, 8

デクリメント演算, 18 デバッグ, 4 等値演算子, 23 二次元配列, 36 二重ループ, 33 排他的論理和, 20 バイト, 3 配列, 34, 35 バグ, 4 引数, 41 ビット, 3 ビット演算子, 20 否定, 23 プリプロセッサ, 14 プロトタイプ宣言, 43 平方根, 45 べき乗, 45 ヘッダファイル, 14 変数, 10 変数定義, 10 変数の型, 11 ポインタ変数, 50 前処理判定の繰り返し制御, 29 無限ループ, 29 メモリ, 4, 10 メモリの動的確保, 55 文字定数, 8 文字配列, 37 文字列定数, 8 戻り値, 41 予約語, 10 ライブラリ関数, 44 乱数, 45 累計演算, 17 ループ, 28 論理演算, 20 論理積, 20, 23 論理和, 20, 23