第 1 章 : 序論

コンピュータに仕事をさせるための手順

C言語で記述されたプログラムは、そのままではコンピュータで実行することができません。 C言語で書いたプログラム(ソースコード)を、コンパイル(Complile)、リンク(Link)と呼ばれる処理を行い、コンピュータで実行可能な機械語のプログラム(実行コード)を作成する必要があります。

  • ソースコード : C言語などのプログラミング言語でプログラムを記述したもの
  • オブジェクトコード : ソースコードをコンパイルし機械語に翻訳したもの
  • 実行コード : オブジェクトコードを結合し、実行に必要な情報を付与したりして、コンピュータで実際に実行可能とした機械語のコード

例題 1-1 : プログラムの作成・コンパイル・実行

C 言語でプログラムを作成してコンパイルを行って実行コードを生成し、実行するまでの手順を体験してみましょう。

この先たくさんのプログラムを作成していきます。 どのファイルがどの課題のものなのか分からなくなくならないように、まずはプログラミング実習用のディレクトリを作成し、 そのディレクトリの下に、課題ごとのディレクトリを作成するとよいでしょう。

端末
$ cd ~
$ mkdir programming
$ cd programming
$ mkdir ex_1_1
$ cd ex_1_1
$ pwd
~/programming/ex_1_1

ここではホームディレクトリにプログラミング実習用のディレクトリ programming を作成し、さらにその下に、この例題用のディレクトリ ex_1_1 をつくりました。

作成した例題用のディレクトリ内でプログラムを作成していきましょう。 初めにソースコードを作成します。 エディタ(Emacs, Vim, VSCode など)で次のプログラムを作成を作成し、ファイル名を hello.c として保存してください。 なお、 C 言語のソースコードのファイル(ソースファイル)の拡張子は .c とする必要があります。

hello.c
#include <stdio.h>

int main(void) {
  printf("Hello, World!\n");

  return 0;
}

gcc コマンドを用いてソースファイルを指定し、コンパイル(とリンク)を行います。 端末で以下のように入力しましょう。

端末
$ gcc hello.c -o hello
$ ls
hello  hello.c

gcc コマンドの -o オプションでは、コンパイルしてできる実行コードのファイル名を指定しています。 ファイルの一覧を取得する ls コマンドで実行コード hello があることを確認しましょう。

コンパイルができたら、実際に実行コード hello を実行してみましょう。 端末で以下のように入力し、実行コード hello を実行します。

プログラムに誤りがなければ、標準出力に Hello, World! と表示され、改行されます。

端末
$ ./hello
Hello, World!
$

Tip

ソースコードに構文エラー(文法上の誤り)があると、コンパイラはコンパイルに失敗し、次のようなエラーメッセージやヒントを表示します。 エラーメッセージをよく確認し、ソースコードを修正したうえで再度コンパイルを行いましょう。

端末
$ gcc first.c -o first
first.c: In function ‘main’:
first.c:4:42: error: expected ‘;’ before ‘return’
    4 |   printf("This is my first C program!\n")
      |                                          ^
      |                                          ;
    5 | 
    6 |   return 0;
      |   ~~~~~~      

C言語のプログラミングおよびそのコンパイルと実行の手順に慣れるためにもう一つ例題に取り組みましょう。

例題 1-2 : プログラムの作成・コンパイル・実行 (その2)

以下のソースコード second.c を作成しましょう。 (programming ディレクトリの下に新たに ex_1_2 ディレクトリを作成して、ex_1_2 ディレクトリで作業を行うとよいでしょう。)

second.c
#include <stdio.h>

int main(void) {
  printf("This is\nmy second project\n");
  printf("WoW!");
  printf("Cool!\n");
  return 0;
}

gcc で second.c をコンパイル(とリンク)し、実行コード second を作成しましょう。

端末
$ gcc second.c -o second
$ ls
second  second.c

実行コードを実際に実行し、どのような実行結果となるかを確認しましょう。

端末
$ ./second
This is
my second project
WoW!Cool!

gcc にはコンパイラとリンカの両方の機能が備わっています。 プログラミング基礎で扱うような小規模なプログラムの開発では、 例題 1-1 で行ったように gcc でソースコードから直接実行コードを作成しても問題ありませんが、複数のソースコードを必要とする大規模な開発では、コンパイルとリンクを分けて行うことが多いです。

参考のために、コンパイルとリンクを分けて実行コードを作成する方法を体験しておきましょう。

例題 1-3 : (optional) コンパイルとリンク

まず、この例題用のディレクトリを作成し、そのディレクトリに移動します。 次に示す、2つのソースコード main.cmyfunc.c を作成してそれぞれ保存してください。

main.c
#include <stdio.h>

void myfunc(void);

int main(void) {
  printf("Hello, Here main.c!\n");
  myfunc();
  return 0;
}
myfunc.c
#include <stdio.h>

void myfunc(void) {
  printf("Here, myfunc.c\n");
}

gcc の -c オプションを用いて、2つのソースコードに対してコンパイルのみを行い、オブジェクトコードを作成します。

端末
$ gcc -c main.c myfunc.c
$ ls
main.c  main.o  myfunc.c  myfunc.o

ls でファイルを確認すると、2つのオブジェクトコード main.omyfunc.o が作成されているのがわかります。

次にリンカを用いて、オブジェクトコードを結合し実行コードを咲く作成します。

リンカは複数のオブジェクトコードやライブラリを結合して、コンピュータ上で直接実行できる実行コードを作成するものです。

以下のように gcc で2つのオブジェクトコード main.omyfunc.o を指定してリンクし、-o オプションで指定した実行コード main を作成しましょう。

端末
$ gcc main.o myfunc.o -o main
$ ls
main  main.c  main.o  myfunc.c  myfunc.o

実行コード second を実行すると次のように表示されるはずです。

端末
$ ./main
Hello, Here main.c!
Here, myfunc.c
$

演習

演習 1-1 : 10までカウント

次のソースコード count10.c をコンパイルして実行し、プログラムの動作を確認しましょう。

count10.c
#include <stdio.h>

int main(void) {
  int count;

  for (count = 1; count <= 10; count++) {
    printf("%d ", count);
  }

  printf("end!\n");

  return 0;
}

端末に表示された実行結果の部分をコピーして、テキストファイルを作成し、ファイル名 count10_log.txt として保存してください。

端末
$ ./count10
1 2 3 4 5 6 7 8 9 10 end!
$ 

ソースコード count10.c と実行結果 count10_log.txt の2つのファイルを課題へ提出してください。

第 2 章 : C 言語の基礎

フローチャート

例題 2-1 : フローチャートによるプログラムの設計

プログラムの設計段階では、処理の流れを整理して正しく処理を行えるかどうかを検討するために、フローチャートを作成するとよいです。

フローチャートの例を示します。

フローチャートは次の表に示すような要素を組み合わせて、処理の流れを表します。

要素説明
端子 : プログラムの開始や終了を表す
準備 : 使用する変数の宣言などを行う
処理 : 計算や変数の代入などをおこなう
データ : プログラムへの入出力を表す
判断 : 条件を判断し処理を分岐する

例で示したフローチャートをもとにプログラムを作成すると以下のようになります。

フローチャートとプログラムがどのように対応しているか、確認してみてください。

sample.c
#include <stdio.h>

int main(void) {
  int m, n;
  
  m = 3;
  n = 7;

  while (n > 0) {
    m = m + 1;
    n = n - 1;
  }

  printf("%d\n", m);

  return 0;
}

なお、このプログラムを実行すると端末に 10 と表示されます。


C 言語によるプログラミング

例題 2-2 : Hello, World!

次のプログラムは、標準出力 (ターミナル) へ Hello, World! と表示するプログラムです。

ソースコード中の // は単一行コメントの開始を表す記号で、// から右側行末まではコメントとして扱われます。コンパイラはコメント部分は無視してコンパイルを行います。 (ですので、コメント部分はプログラムに記入する必要はありません。)

プログラムを作成し、コンパイルを行って実行してみましょう。

hello.c
#include <stdio.h>  // (1)

int main(void) {    // (2-1)
  printf("Hello, World!\n");  //(3)

  return 0; // (4)
} // (2-2)

プログラムの各部分について説明していきます。

#include <stdio.h>  // (1)

#include はプリプロセッサディレクティブと呼ばれるもののひとつで、指定したファイルをソースコードに組み込むときに使用します。 ここでは、 stdio.h というファイル(ヘッダファイル)が指定されています。 stdio.h では標準入出力を処理する関数などが宣言されており、 このヘッダファイルを読み込むことで、 端末に指定した文字列を表示する printf や、 端末からの入力を受け取る scanf などの関数を使えるようになります。

int main(void) {    // (2-1)
  ...(中略)
} // (2-2)

C 言語で作成したプログラムは、実行すると(通常)はじめに main 関数が呼び出されます。 ここではその main 関数を定義しています。

関数の定義では、関数名と関数に渡す値(引数)の型、関数が返す値(返り値)の型を指定します。 このプログラムでは、関数名として main 、引数の型として void 型(空であること示す型)、返り値の型として int 型(整数型)が指定していまうs。 なお、main 関数の返り値の方は int 型となります。 (main 関数に引数を渡すこともできますが、詳細は 10 章で説明します。)

関数の本体で行う処理を {} で囲まれた部分に記述します。

  printf("Hello, World!\n");  //(3)

printf は引数で指定した文字列を標準出力へ出力する命令です。 引数で渡された " (ダブルクォート)で囲まれた文字列を出力します。 文字列中の \n は改行文字を表し、出力時はこの部分で改行が行われます。

  return 0; // (4)

return 文は関数(ここでは main 関数)の実行を終了し、関数の呼び出し元に返す値を設定する命令です。

main 関数は正常に終了すると整数値 0 を返します。

このプログラムをコンパイルし、実行すると次のように Hello, Wold! と表示し改行されます。

端末
$ gcc hello.c -o hello
$ ./hello
Hello, world!
$

Note

man コマンドを使うとシステムで提供されている C 言語の関数や、ファイルなどに関する情報が得られます。 stdioprintf などについて man コマンドを使って調べてみましょう。

端末
$ man stdio
STDIO(3)          Linux Programmer's Manual          STDIO(3)

NAME
       stdio - standard input/output library functions

SYNOPSIS
       #include <stdio.h>

       FILE *stdin;
       FILE *stdout;
       FILE *stderr;

DESCRIPTION
       The  standard  I/O library provides a simple and effi‐
       cient buffered stream I/O interface. (以下略)

演習

演習 2-1

次のプログラムの Ayashi Hanako の部分を自分の名前に変更したプログラムを作成し、コンパイルし実行して、その動作を確認しましょう。

name.c
#include <stdio.h>

int main(void) {
  printf("Hi, ");
  printf("Ayashi Hanako");
  printf("!\nHow are you?\n");

  return 0;
}

演習 2-2

実行すると端末に以下のように表示されるプログラムを作成してください。 作成したプログラムをコンパイルして動作を確認しましょう。

端末
Have fun with 

 CCC
C
C
 CCC PROGRAMMING!

演習 2-3

次のプログラムを作成し、実行しましょう。

fizzbuzz.c
#include <stdio.h>

#define MAX_COUNT 42

int main(void) {
  int count = 1;

  while (count <= MAX_COUNT) {
    if (count % 15 == 0) {
      printf("FizzBuzz\n");
    } else if (count % 3 == 0) {
      printf("Fizz\n");
    } else if (count % 5 == 0) {
      printf("Buzz\n");
    } else {
      printf("%d\n", count);
    }
    count = count + 1;
  }

  return 0;
}

第 3 章 : 変数とデータ型

C 言語では数値や文字などのデータを保持しておく変数を用いることができます。また、変数およびデータには、そのデータの種類を定める型(type)があります。

C言語で用いる主なデータ型を以下の表に示します。

型名説明
char8 ビットの整数値(127~-128)を表す。
通常はその値を文字コード(ASCII)とする文字('a', '2', '@'など)を表すのに使用される。
int32 ビットの整数値 (2147483647 ~ -2147483648) を表す。(実習室の環境の場合)
unsigned char8 ビットの符号なし整数値 (0 ~ 255) を表す。
unsigned int32 ビットの符号なし整数値 (0 ~ 4294967295) を表す。
float32 ビットの浮動小数点数(指数部 8 ビット, 仮数部 23 ビット)を表す。
最大値 (約) \(3.40\times 10^{38}\) ~ (正の)最小値 (約) \(1.18\times 10^{-38}\), 有効桁数 6 桁程度
double64 ビットの浮動小数点数(指数部 11 ビット, 仮数部 52 ビット)を表す。
最大値 (約) \(1.80\times10^{308}\) ~ (正の)最小値 (約) \(2.20\times 10^{-308}\), 有効桁数 15 桁程度

変数の宣言・変数への代入

例題 3-1 : 変数の宣言と値の代入

次のプログラムは変数を用いたデモプログラムです。 変数を用いるときは、型と変数名を指定して宣言します。 宣言した変数には値を代入することができます。

variables1.c
#include <stdio.h>

int main(void) {
  // (1) 変数の宣言
  char character;
  int i, num;
  double temperature;

  // (2) 変数への値の代入
  character = 'z';
  i = 42;
  num = i + 10;   // num <- 52 (= 42 + 10)
  temperature = -3.4;

  // (3) 変数の値の表示
  printf("%c\n", character);  
  printf("%d\n", i);
  printf("%d\n", num);
  printf("%f\n", temperature);

  return 0;
}

プログラムの説明をしていきます。

変数を用いる際には、まず変数の型(データ型)と変数名を示して宣言する必要があります。

  // (1) 変数の宣言
  char character;
  int i, num;
  double temperature;

これにより、char 型の変数 character と、int 型の変数 i および num、 double 型の変数 temperature が使えるようになりました。 なお、変数を宣言しただけではその変数の値は不定となります。

変数へ値を代入するときは代入演算子 = を使います。

  // (2) 変数への値の代入
  character = 'z';
  i = 42;
  num = i + 10;
  temperature = -3.4;

代入演算子 = の左辺には、代入先となる変数を置き、 右辺には代入する値や式を持ってきます。

character = 'z'; が実行されると、char 型の変数 character に 1 文字の値 'z' が代入されます。 同様に i = 42; が実行されると、int 型の変数 i に整数値 42 が代入されます。

代入演算子の右辺に式を持ってくることもできます。 num = i + 10; を実行すると、まず右辺の足し算が計算されます。 いま、変数 i には 42 が代入されているので、i + 10 の計算結果は 52 となります。この計算結果の値が、変数 num に代入されます。

変数の値を表示するには printf を用いるとよいです。 printf 関数の詳しい使い方については 4 章で説明します。

  // (3) 変数の値の表示
  printf("%c\n", character); // char 型の変数 character の値を文字として表示 
  printf("%d\n", i);         // int型の変数 i の値を10進数表示で表示
  printf("%d\n", num);       // int型の変数 num の値を10進数表記で表示
  printf("%f\n", temperature);  // double型の変数 temperature の値を浮動小数点数として表示

プログラムの実行結果を示します。 上から順に char 型の変数 character、int 型の変数 inum、double 型の変数 temperature の値が表示されます。

端末
z
42
52
-3.400000

例題 3-2 : 変数の宣言時での代入(初期化)

変数の宣言時と同時に変数の値を代入することもできます。 これを変数の初期化といいます。 変数の初期値が決まっている変数に対しては、変数の初期化を行うとよいでしょう。

変数の初期化を用いたプログラムを示します。

variables2.c
#include <stdio.h>

int main(void) {
  // 変数の宣言時に値を設定
  char character = 'P';
  int num = -57;
  double temperature = 12.34;

  printf("%c\n", character);  
  printf("%d\n", num);
  printf("%f\n", temperature);

  return 0;
}

プログラムの実行結果を示します。

端末
P
-57
12.340000

例題 3-3 : 代入による変数の上書き

変数への代入は 1 度だけでなく複数回行うことができます。

次のプログラムでは、int 型の変数 ij に対して、 変数宣言時に初期化を行った後、 それぞれの変数に対して 2 回代入を行っています。 初期化、1 回目の代入、2 回目の代入それぞれが行われた直後で 変数 ij の値がどのように変化しているか、 プログラムを実行して確かめてください。

variables3.c
#include <stdio.h>

int main(void) {
  // (1) 初期化
  int i = 10;
  int j = 20;
  printf("(1) initializations \n");
  printf("i = %d\n", i);  
  printf("j = %d\n", j);

  // (2) 代入
  i = 30; 
  j = i; 
  printf("(2) assignments \n");
  printf("i = %d\n", i);  
  printf("j = %d\n", j);

  // (3) さらなる代入
  i = i + 2;   // i <- 32 (= 30 + 2)
  j = 3 * j;    // j <- 90 (= 3 * 30)
  printf("(3) more, \n")
  printf("i = %d\n", i);  
  printf("j = %d\n", j);

  return 0;
}

プログラムの実行結果を示します。 変数 ij の値がどのように変化しているか、 プログラムと対応させて確認してください。

端末
(1) initializations
i = 10
j = 20
(2) assignments
i = 30
j = 30
(3) more,
i = 32
j = 90

さて、2 回目の代入では代入演算子 = の左辺と右辺に同じ変数が現れています。

  // (3) さらなる代入
  i = i + 2;
  j = 3 * j;

このような場合は、代入が行われる前の変数の値を使って右辺の式が評価され、 その値が、左辺の変数に代入されることになります。 例えば、i = i + 2; が行われる前では、i には値 30 が格納されていますが、 i = i + 2; が実行されると i の値は 32 となります。

なお、このプログラムの処理の流れをフローチャートで示すと、 以下のようになります。

flowchart


例題 3-4 : キャストによる型の変換

変数への代入を行うときは、左辺の変数と右辺の値のデータ型が一致している必要があります。

int 型の変数に double 型の値を代入したり、逆に double 型の変数に int 型の値を代入したりしようとする場合は、キャストと呼ばれる機能を用いて、一時的にデータの型を変換します。

次のプログラムは、キャストによって、int 型の値を double 型に変換したり、 その逆を行っています。

cast.c
#include <stdio.h>

int main(void) {
  int i = 256;
  double x = -12.34;
  double d_i;
  int i_x;

  // (1) int 型の値を double 型に変換
  d_i = (double)i;
  printf("i   = %d\n", i);
  printf("d_i = %f\n", d_i);

  // (2) double 型の値を int 型に変換
  i_x = (int)x;
  printf("x   = %f\n", x);
  printf("i_x = %d\n", i_x);

  return 0;
}

(1) の d_i = (double)i での代入では、 int 型の変数 i の値を double 型に変換して d_i に代入しています。

一方、(2) の i_x = (int)x での代入では、double 型の変数 x の値 -12.34を int 型に変換して i_x に代入しています。 この時、小数点以下は切り捨てられ、整数値 -12i_x に代入されます。

プログラムの実行結果を示します。

端末
i   = 256
d_i = 256.000000
x   = -12.340000
i_x = -12

演習

演習 3-1

プログラム semicircle.c は半径 r の半円の面積を求めて 表示するプログラムです。 このプログラムは次のフローチャートをもとに作成しました。

プログラム中、変数 r の値を自分が好きな実数値に変更して、プログラムを実行し、動作結果を確認してください。

semicircle.c
#include <stdio.h>

int main(void) {
  double r, area;

  r = 3.0; // この値を変更する。

  area = 3.14 * r * r / 2;

  printf("The area of semicircle of ");
  printf("radius %f is %f.\n", r, area);

  return 0;
}

演習 3-2

以下に示すプログラムは、 初項 \( a_1 = 1 \)、漸化式 \( a_{n+1} = 2 a_n + 1\ (0 \le n) \) で定まる数列 \(a_n\) を 初項から第 10 項まで計算して表示するプログラムです。 プログラムを実行して、動作結果を確認してください。

sequence.c
#include <stdio.h>

#define N_MAX 10

int main(void) {

  int n = 1;
  int a = 1;

  do {
    printf("n = %d : %d\n", n, a);
    a = 2 * a + 1;
    n++;
  } while (n <= N_MAX);

  return 0;
}

このプログラムの処理の流れをフローチャートで示すと以下のようになります。

第 4 章 : 標準入出力

標準出力

コンソールなどの標準出力へ文字列を出力するときは printf 関数を使います。 文字列の中に書式を指定して、数値や文字を表示することができます。

例題 4-1 : printf の使い方

次のプログラムは printf を用いて、Hello World と表示して改行するプログラムです。 printf の引数に、表示したい書式文字列 "Hello, World\n" を指定します(\nは改行を表します)。 なお、文字列はダブルクォーテーション"で囲みます。

hello_world.c
#include <stdio.h>

int main(void) {

  printf("Hello, World!\n"); // Hello, World と表示される
  return 0;
}

実行結果は次の通りです。

端末
Hello, World!

例題 4-2 変数に格納された値の表示

printf では、変数に格納された値や式の値を 書式文字列に埋め込んで表示することもできます。 書式文字列中に出力変換指定 (%c, %d, %f など) を埋め込むと、 printf の第 2 引数以降に指定した式や変数の値を その場所に埋め込んで表示することができます。

printf_sample0.c
#include <stdio.h>

int main(void) {
  char atmark = '@';
  int ans = 42;
  double x = 3.1, y = 2.7;

  printf("%c is cute character.\n", atmark);  // @ is cute character. と表示
  printf("The answer is %d.\n", ans);         // The answer is 42. と表示
  printf("(%f, %f)\n", x, y);                 // (3.100000, 2.700000) と表示 

  return 0;
}

最初の printf では、書式文字列 "%c is cute character.\n"%c の部分へ、char 型の変数 atmark に格納されている値 '@' が埋め込まれて @ is cute character. と表示され改行されます。 なお、出力変換指定 %c は文字 (Ascii文字) を表示するときに用います。

  printf("%c is my favorite character.\n", atmark); 
  // @ is my favorite character. と表示

2 番目の printf でも同様に、書式文字列 "The answer is %d.\n"%d の部分へ、int 型の変数 ans の値 42 が埋め込まれて、 The answer is 42. と表示されます。 %d は整数値を十進数で表示するときに用いる出力変換指定です。

  printf("The answer is %d.\n", score);      
  // The answer is 42. と表示

最後の printf では、書式文字列 "(%f, %f)\n" に二か所出力変換指定 %f が使われています。 初めの %f のところには double 型の変数 x の値が埋め込まれ、 2 番目の %f のところには double 型の変数 y の値が埋め込まれます。 %f は小数点数表示を行うための出力変換指定です。

  printf("(%f, %f)\n", x, y);                
  // (3.100000, 2.700000) と表示 

プログラムの実行結果です。

端末
@ is cute character.
The answer is 42.
(3.100000, 2.700000)

よく使う代表的な出力変換指定を以下の表に示します。

変換指定子扱う型・値出力例 (printf の呼び出し)
%d符号付き 10 進整数 (int)printf("%d\n", -42);-42
%x / %X16 進整数(小文字 / 大文字)printf("%x\n", 255);ff
%f固定小数点実数 (double)printf("%f\n", 3.14);3.140000
%e / %E指数表記printf("%e\n", 3.14);3.140000e+00
%c1 文字 (char)printf("%c\n", 'A');A

例題 4-3 : 表示桁数の指定

出力変換指定子で、表示する数値の桁数などを指定することができます。 また、16 進数や指数形式での表示を指定することもできます。

次のプログラムを実行して、プログラム中のコメントを参考に実行結果を確認してください。

printf_sample1.c
#include <stdio.h>

int main(void) {
  int score = 91;
  double value = 123.45678;

  printf("12345678901234567890\n");
  printf("%4d\n", score);   // 整数 4 桁で表示
  printf("%6.2f\n", value); // 実数 全体 6 桁(小数点含む),小数点以下 2 桁で表示
  printf("%x\n", score);    // 整数 16進法で表示
  printf("%e\n", value);    // 実数 指数形式で表示

  return 0;
}

プログラムの実行結果です。

端末
12345678901234567890
  91
123.46
5b
1.234568e+02

ここで紹介したもの以外にも出力変換指定子は存在します。 printf および出力変換指定の詳細な使い方については以下のリンク先などを参照してください。


標準入力

コンソールなどの標準入力から入力された値を受け取り、 変数に格納したい場合、 scanf 関数を用います。

例題 4-4 : scanf の使い方

次のプログラムは、標準入力から入力された 1 文字(1バイト文字)を 受け取り、その文字を "The character you entered : " に続けて 表示するものです。

scanf_sample0.c
#include <stdio.h>

int main(void) {
  char character; // 1 文字を格納する変数 character を宣言
  
  scanf("%c", &character); // 変数 character に入力された文字を格納

  printf("The character you entered : %c\n", character);

  return 0;
}

scanf は以下のように書式文字列と変数のポインタを渡して用います。 この scanf が実行されると、端末から入力された 1 文字が char 型の変数 character に格納されます。 (変数 character は事前に char character; と宣言されていることに注意してください。)

  scanf("%c", &character) // 変数 character に入力された文字を格納

第 1 引数の書式文字列には入力する値の形式を入力変換指定で指定します。 文字(1バイト文字)の場合、入力変換指定は %c となります。 入力変換子と出力変換しはほぼ同じものとなります。

第 2 引数には、入力された値を格納する変数のポインタを渡します。 ポインタとは、その変数が主記憶装置 (メモリー) 上のどこに割り当てられているか その場所(アドレス)を示す値のことです (詳しくは 3 学年で学びます)。 変数名の前に & をつけるとその変数のポインタが得られます。 ここでは、char 型の変数 character に入力された文字を格納したいので、 変数 character のポインタ &characterscanf の第 2 引数に渡しています。

scanf では入力された値を格納したい変数に & をつけてポインタを渡すことを忘れないようにしましょう。

プログラムの実行結果です。 1 行目はユーザーからの入力です。

端末
Q
The character you entered : Q

例題 4-5 : 実数値の入力

scanf を使って、double 型の変数へ入力された値を格納する場合、 入力変換子は %lf を使います。(%f ではないことに注意)

次のプログラムは、2 つの実数値が入力されると、 それらを底辺と高さとする長方形の面積を計算して出力するプログラムです。

rectangle.c
#include <stdio.h>

int main(void) {
  double width, height;
  double area;

  printf("Enter width height : \n");

  scanf("%lf %lf", &width, &height); // 2 つの実数値を入力

  area = width * height;
  printf("Area of the rectagle : %f\n", area);

  return 0;
}

scanf では入力を複数受け取って、それぞれを別の変数に格納することもできます。

  scanf("%lf %lf", &width, &height);

この scanf の文が実行されると、標準入力からの入力待ちとなり、 ユーザーから 2 つの値が順に入力されると、 はじめに入力された値は変数 width に格納され、 次に入力された値は変数 height に格納されます。

実行結果の例は次の通りです。 2 行目の 2.3 と 3 行目の 10.0 がユーザーからの入力です。 scanf により、それぞれの値が double 型の変数 widthheight に格納されます。

端末
Enter width height : 
2.3
10.0
Area of the rectagle : 23.000000

参考として、プログラムの処理の流れを表したフローチャートを示します。 プログラムへのデータの入力や出力は平行四辺形のデータを用いて記述しましょう。

flowchart


コメント

プログラム中にコメントを記述することができます。 コメントはプログラムの説明やメモなどを記述するために使います。 また、コメントはプログラムの実行には影響しません。

例題 4-6 : コメントの使い方

コメントは、// から行末までの部分をコメントとして扱います。 また、/* から */ までの部分をコメントとして扱うこともできます。

comment.c
#include <stdio.h>

/* これは
    複数行にわたる
    コメントです。 */

int main(void) {
  // これはコメントです。
  printf("Hello, World!\n"); // これもコメントです。
  return 0;
}

演習

演習 4-1

半径を入力すると、円の面積を計算して表示するプログラムを作成しましょう。 入力する半径の値は実数値(浮動小数点数)とし、円周率は 3.14 とします。 出力される円の面積の値も実数値(浮動小数点数)となることに注意しましょう。 出力される円の面積は小数点以下 3 桁まで表示するようにしてください。

プログラムの処理の流れをフローチャートを作成して検討し、 そのフローチャートに基づいてプログラムを作成してください。

実行例を示します。 1 行目はユーザーからの入力(半径)で、2 行目がプログラムからの出力(円の面積)です。

端末
5.0
78.500

演習 4-2

標準入力から文字( char 型の値)を 3 つ受け取ったら、 受け取った 3 つの文字を横方向へ逆順に横に並べて表示するプログラムを作成し、 動作を確認してください。

実行例を以下に示します。 1 ~ 3 行目はユーザーからの入力で、4 行目はプログラムの出力です。

端末
a
b
c
cba

演習 4-3

次は掛け算の九九の表を表示するプログラムです。 プログラムを作成して実行し、その動作を確認しましょう。

table99.c
#include <stdio.h>

int main(void) {
  for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= 9; j++) {
      printf("%3d", i * j);
    }
    printf("\n");
  }

  return 0;
}

第 5 章 : 演算子

算術演算子

例題 5-1 : 算術演算

算術演算として加算 + 、減算 - 、乗算 * 、除算 / 、剰余 % などが使えます。 これらを用いたデモプログラムを次に示します。

arithmetic.c
#include <stdio.h>

int main(void) {
  int a = 8, b = -2, c = 3;
  printf("a + b = %d\n", a + b);  // -> 6
  printf("(a - b) * c + 12 = %d\n", (a - b)  * c + 12); // -> 42
  printf("a mod c = %d\n", a % c);  // -> 2 (a を c で割った余り)

  double pi = 3.14, e = 2.71;
  printf("pi + e = %f\n", pi + e);  // -> 5.850000
  printf("pi / e = %f\n", pi / e);  // -> 1.168672
  printf("pi / a = %f\n", pi / a);  // -> 0.392500

  return 0;
}

加減算と乗除算が混じった式では、数学での数式と同様に、 乗算 * と除算 / は加算 + や減算 - よりも優先度が高く先に計算されます。 また、これらの算術演算は左結合性を持ちます(左から順番に計算されます)。 括弧 ( ) を用いて、(a - b) * c + 12 の式のように計算の優先順位を制御することができます。

実行結果を示します。

端末
a + b = 6
(a - b) * c + 12 = 42
a mod c = 2
pi + e = 5.850000
pi / e = 1.158672
pi / a = 0.392500

例題 5-2 整数同士の割り算

算術演算では、整数型の値同士の演算を行った結果の値は整数型となります。 それ以外の組み合わせでは演算結果の値は実数型となります。 例えば int 型の値と double 型の値の演算結果は double 型の値となります。

特に、整数同士の割り算については注意が必要です。 整数同士の割り算の結果は整数となり、小数点以下の値は切り捨てられます。

次のプログラムを実行して動作を確かめましょう。

division.c
#include <stdio.h>

int main(void) {
  int a = 8, b = 3;

  int x1 = a / b; // -> 2
  double x2 = (double) (a / b); // -> 2.000000
  double x3 = (double) a / b;   // -> 2.666667

  printf("x1 = %d\n", x1);
  printf("x2 = %f\n", x2);
  printf("x3 = %f\n", x3);
  
  return 0;
}

まず、int x1 = a / b; の部分についてみていきます。 a = 8b = 3 は共にint型の整数ですので、その割り算 a / b は数学的には 2.66666... となりますが、小数点以下が切り捨てられて 2 と評価されます。 したがって、x1 には 2 が代入されます。

次に、double x2 = (double) (a / b); の部分についてみていきます。 a / b2 となりますが、(double) によって値が double 型に 変換され(キャスト)、変数 x2 には 2.0 (double型の値)が代入されます。

最後に、double x3 = (double) a / b; の部分についてみていきます。 aint 型ですが、(double) によって値が double 型に変換されます。 したがって、ここでの割り算は実数としての割り算が行われます。その結果は double 型となり、x3 には 2.666667 が代入されます。 このように、整数同士の割り算を実数として行いたい場合は、 分子もしくは分母の少なくともどちらかの値を実数型にキャストする必要があります。

プログラムの実行結果は次のようになります。

端末
x1 = 2
x2 = 2.000000
x3 = 2.666667

論理演算子

例題 5-3 : ビット単位の論理演算

次のプログラムは、ビット単位の論理演算を行うプログラムです。

bitwiseOp.c
#include <stdio.h>

int main(void) {
  unsigned char a = 0x0F; // 00001111
  unsigned char b = 0x3A; // 00111010

  unsigned char a_and_b = a & b;  // -> 00001010 = 0A
  unsigned char a_or_b  = a | b;  // -> 00111111 = 3F
  unsigned char a_xor_b = a ^ b;  // -> 00110101 = 35
  unsigned char not_b   = ~b;     // -> 11000101 = C5

  printf("a & b = %02X\n", a_and_b);
  printf("a | b = %02X\n", a_or_b);
  printf("a ^ b = %02X\n", a_xor_b);
  printf("~b    = %02X\n", not_b);
  
  return 0;
}

abunsigned char 型(符号なし 8 bit 整数)の変数で、 それぞれ 0x0F0x3A という 16 進数表示で表される値が代入されています。 0x は 16 進数を表す接頭辞です。 0x0F0x3A は 2 進数表示で表すとそれぞれ 0000111100111010 になります。

a & bab のビットごとの論理積( AND )を表します。 a = 00001111b = 00111010 のビットごとの論理積は 00001010 なります。 したがって、a & b の結果は、 16 進数表示で表すと 0x0A となります。

なお、演算結果を表示している printf("a & b = %02X\n", a_and_b); の 書式文字列にある出力変換指定 %02X は、a_and_b の値を 16 進数で 2 桁表示することを表します。 %02X02 は 2 桁表示(先頭0埋め)を表し、X は 16 進数表示を表します。

a | bab のビットごとの論理和( OR )を表します。 また、a ^ bab のビットごとの排他的論理和( XOR )を表します。 ~bb のビットごとの否定( NOT )を表します。

プログラムの実行結果は以下のようになります。

端末
a & b = 0A
a | b = 3F
a ^ b = 35
~b    = C5

参考までに a, b およびそれらのビット単位での論理演算の結果について 2 進数表示で検算したものを示します。 ビットごとに論理演算が行われていることを確認してください。

    a = 0000 1111 = 0x0F
    b = 0011 1010 = 0x3A
a & b = 0000 1010 = 0x0A (ビットごとのAND)
a | b = 0011 1111 = 0x3F (ビットごとのOR)
a ^ b = 0011 0101 = 0x35 (ビットごとのXOR)
~b    = 1100 0101 = 0xC5 (bのビットごとのNOT)

比較演算子

例題 5-4 : 比較演算

数値や変数の値の比較には、比較演算子を用います。 演算結果は真偽値(真 1 または偽 0)となります。

次のプログラムは比較演算子を用いたプログラムです。

comparisionOp.c
#include <stdio.h>

int main(void) {
  int three = 3;

  printf("three == 3 : %d\n", three == 3); // -> 1 (true)
  printf("three != 3 : %d\n", three != 3); // -> 0 (false)
  printf("three > 3  : %d\n", three > 3);  // -> 0 (false)
  printf("three >= 3 : %d\n", three >= 3); // -> 1 (true)
  printf("three < 3  : %d\n", three < 3);  // -> 0 (false)
  printf("three <= 3 : %d\n", three <= 3); // -> 1 (true)

  return 0;
}

このプログラムでは int 型の変数 three には 3 が代入されています。 比較演算子 == を使った three == 3 の比較では、 右辺と左辺の値が等しいかどうかが評価されます。 もちろん両者の値は等しいので、この比較の結果は真 ( True ) を表す整数値 1 となります。 (== ではなく = と書くと、代入演算となりますので注意してください。)

一方で、!= は右辺と左辺が等しくないときに真 ( True ) を返す比較演算となります。 three != 3 の比較では、今、右辺と左辺の値が等しくなっているので、 この比較の結果は偽 ( False ) を表す整数値 0 となります。

プログラム中で用いた比較演算子の一覧を示します。

比較演算子(C言語)意味(数学表記)
==\( = \)
!=\( \neq \)
>\( > \)
>=\( \geq \)
<\( < \)
<=\( \leq \)

プログラムの実行結果を示します。

端末
three == 3 : 1
three != 3 : 0
three > 3  : 0
three >= 3 : 1
three < 3  : 0
three <= 3 : 1

int 型の変数 three の値は 3 ですので、 実行結果の比較演算のうち three == 3three >= 3 および three <= 3 の3つは真となり、 比較演算の結果は真を表す 1 となっています。 その他の比較演算は成り立たず偽となるため、 比較演算の結果は偽を表す 0 となります。

例題 5-5 : 論理演算 

論理演算子を用いると、複数の比較演算を組み合わせて複雑な条件を表現することができます。 変数や数値に対して論理演算を行うときは、整数値 0 を偽 ( False ) とし、 それ以外の値を真 ( True ) として扱います。

次のプログラムは論理演算子を用いたプログラムです。

logicalOp.c
#include <stdio.h>

int main(void) {
  int three = 3;

  printf(" three == 3 : %d\n", three == 3); // -> 1 (true)
  printf(" three > 3  : %d\n", three > 3);  // -> 0 (false)

  printf(" (three == 3) && (three > 3) : %d\n", (three == 3) && (three > 3)); // -> 0 (false)
  printf(" (three == 3) || (three > 3) : %d\n", (three == 3) || (three > 3)); // -> 1 (true)
  printf(" !(three == 3)               : %d\n", !(three == 3));               // -> 0 (false)

  return 0;
}

&& は論理積( AND )、|| は論理和(OR)、! は論理否定(NOT)を表します。 && は左辺と右辺の両方が真 ( True ) のときに真 ( True ) を返します。 || は左辺と右辺のどちらかが真 ( True ) のときに真 ( True ) を返します。 ! は右辺の真偽を反転します。

ビット単位の論理演算子との違いに注意してください。

このプログラムの実行結果を示します。

端末
 three == 3 : 1
 three > 3  : 0
 (three == 3) && (three > 3) : 0
 (three == 3) || (three > 3) : 1
 !(three == 3)               : 0

増減演算子

例題 5-6 : インクリメント・デクリメント

整数型の変数の値を 1 だけ増減させるには、 増分演算子(インクリメント) ++ 減分演算子(デクリメント) -- を用います。

次のプログラムを実行し、 インクリメントやデクリメントが行われるたびに 変数 a の値がどのように変化するか確認してください。

increment_decrement.c
#include <stdio.h>

int main(void) {
  int a = 57;

  printf("a = %d\n", a); // -> 57
  a++; // a = a + 1 と同等 (++a も可)
  printf("a = %d\n", a); // -> 58
  a--; // a = a - 1 と同等 (--a も可)
  a--;
  printf("a = %d\n", a); // -> 56

  return 0;
}

a++ が実行されると変数 a の値が 1 増えます。 すなわち、代入 a = a + 1 と同じです。 デクリメント a-- も同様です。

プログラムの実行結果を示します。

端末
a = 57
a = 58
a = 56

例題 5-7 : 前置・後置の違い

増減演算子は、変数の前に置くことも後ろに置くこともできます。 どちらも、変数の値を 1 増減させるという働きは同じです. ただし、変数への代入と一緒に用いた場合、代入と増減演算の評価順に違いが現れます。

次のプログラムは、その違いを確認するプログラムです。

pre_suff.c
#include <stdio.h>

int main(void) {
  int n, m;

  n = 42;
  printf("n = %d\n", n);
  m = n++; // m = n; n = n + 1; と同等 -> m = 42, n = 43
  printf("m = %d, n = %d\n", m, n);

  n = 42;
  printf("n = %d\n", n); // -> 42
  m = ++n;  // n = n + 1; m = n; と同等 -> m = 43, n = 43
  printf("m = %d, n = %d\n", m, n);

  return 0;

}

まず、増減演算子を変数の後に置いた場合をみていきます。 変数 m への代入 m = n++; では、 はじめに変数 m に変数 n の値が代入されます(m = n)。 その後、変数 n の値が 1 増えます(n = n + 1)。

次に、増減演算子を変数の前に置いた場合をみていきます。 変数 m への代入 m = ++n; では、 はじめに変数 n の値が 1 増えます(n = n + 1)。 その後、変数 m に変数 n の値が代入されます(m = n)。

プログラムの実行結果は次の通りです。

端末
n = 42
m = 42, n = 43
n = 42
m = 43, n = 43

これらの結果は大変紛らわしいので、原則として増減演算は単体で用いることを推奨します。


シフト演算子

例題 5-8 : シフト演算

シフト演算子 <<>> は、ビット単位での左シフトと右シフトを行います。 演算子の左辺にはシフトする値、右辺にはシフトするビット数を指定します。

次のプログラムはシフト演算子を用いたプログラムです。

shiftOp.c
#include <stdio.h>

int main(void) {
  unsigned char a = 0xAF; // -> 10101111

  unsigned char a_shifted_left  = a << 2; // -> 10111100 = BC
  unsigned char a_shifted_right = a >> 2; // -> 00101011 = 2B

  printf("a      = %02X\n", a);
  printf("a << 2 = %02X\n", a_shifted_left);
  printf("a >> 2 = %02X\n", a_shifted_right);

  return 0;
}

a << 2a の値のビットを左に 2 ビットシフトした値となります。a 自身の値は変化しません。 今 a の値は2進数表記で 10101111 (16 進数表示で 0xAF ) となっているので、 これを左に 2 ビットシフトすると 10111100 (16 進数表示で 0xBC ) となります。 (左にあふれたビットは無視され、右には 0 が埋められます。)

同様にa >> 2a の値のビットを右に 2 ビットシフトします。

プログラムの実行結果は次のようになります。

端末
a      = AF
a << 2 = BC
a >> 2 = 2B

代入演算子

例題 5-9 : 代入演算

代入演算子 = は左辺の変数へ右辺の値を代入するものですが、 算術演算子と組み合わせて演算と代入を同時に行うこともできます。

次のプログラムは加算演算や乗算演算を代入と組み合わせて行うプログラムです。

assignmentOp.c
#include <stdio.h>

int main(void) {
  int num = 57;
  printf("num = %d\n", num); // -> 57

  num += 3; // num = num + 3 と同じ
  printf("num = %d\n", num); // -> 60

  num *= 2; // num = num * 2 と同じ
  printf("num = %d\n", num); // -> 120

  return 0;
}

プログラム中の num += 3;num = num + 3; と同じ動作です。 同様に、num *= 2;num = num * 2; と同じ動作となります。

プログラムの処理の流れを示すフローチャートを示します。 変数 num の値がどのように変化するかを実行結果と合わせて確認してください。

flowchart

プログラムの実行結果です。

端末
num = 57
num = 60
num = 120

他の算術論理演算も代入と組み合わせて使うことができます。 どのようなものがあるかについては、授業資料や以下のリンク先などを参照してください。


条件演算子

例題 5-10 : 条件演算

条件演算子 ?: を用いると、条件によって値を選択することができます。 次のプログラムは条件演算子を用いたプログラムです。

relu.c
#include <stdio.h>

int main(void) {
  double val_input, val_output;

  scanf("%lf", &val_input);

  val_output = (val_input > 0) ? val_input : 0;

  printf("val_output = %f\n", val_output);
  
  return 0;
}

(val_input > 0) ? val_input : 0 の部分が 条件演算子 ?: を用いているところです。 ? の左側に示された条件 val_input > 0 が真 ( True ) であれば、 : の左側にある値 val_input を返し、偽 ( False ) であれば : の右側の値 0 が返されます。

プログラムの実行例を示します。 1 行目はプログラムへの入力で、2 行目がプログラムの出力です。

  • 正の値 3.14 を入力した場合
端末
3.14
val_output = 3.140000
  • 負の値 -1.414 を入力した場合
端末
-1.414
val_output = 0.000000

演習

演習 5-1

3 人の年齢を入力すると、 その人たちの年齢の平均を出力するプログラムを作成しましょう。

入力する年齢は 0 以上の整数値です。 出力される平均年齢は小数点以下 2 桁まで表示するようにしてください。

プログラムの処理の流れをフローチャートを作成して検討し、 そのフローチャートに基づいてプログラムを作成してください。

期待される実行例を示します。 1 ~ 3 行目はプログラムへの入力で、3 人の年齢が整数値で入力されています。 4 行目はプログラムの出力で、3 人の年齢の平均が小数点以下 2 桁まで表示されています。

端末
17
4
41
20.67

演習 5-2

次のプログラムはテストの点数として整数値を入力すると、 点数が 0 点以上かつ 100 点以下であれば有効として valid score と表示し、 そうでない場合は無効として invalid score と出力するプログラムです。 このプログラムを実行し、様々な数値を入力して動作を確認してみましょう。

score.c
#include <stdio.h>

int main(void) {
  int score;

  scanf("%d", &score);

  if (0 <= score && score <= 100) {
    printf("valid score\n");
  } else {
    printf("invalid score\n");
  }

  return 0;
}

第 6 章 : 分岐処理

if 文

ある条件が成り立つ場合のみ特定の処理を実行したい場合は、if 文を使います。 if 文は、条件式と、条件式が真のときに実行する文を指定します。 条件式が偽の場合は、何も実行されません。 if 文は、次のような構文で記述します。

if (条件式) {
  処理; // 条件式が真のときに実行する文
}

条件式には通常、==!= などの比較演算子を使って、数値や文字を比較する式を指定します。

フローチャートを使って、if 文の処理の流れを示すと次のようになります。

flowchart

指定した条件が成り立つ場合と成り立たない場合で処理を分岐させたい場合は、else を使います。 else の後に続く文は、条件式が偽のときに実行されます。 else を使った if 文は、次のような構文で記述します。

if (条件式) {
  処理1; // 条件式が真のときに実行する文
} else {
  処理2; // 条件式が偽のときに実行する文
}

フローチャートを使って、if-else 文の処理の流れを示すと次のようになります。

flowchart

例題 6-1 : if 文

次のプログラムは if 文を使って、入力された数値が正の数かどうかを判定し、 正の数であれば positive number と表示し、 そうでなければ何も表示しないプログラムです。

positive.c
#include <stdio.h>

int main(void) {
  int number;

  scanf("%d", &number);

  if (number > 0) { // 条件式
    printf("positive number\n"); // 条件式が真のときに実行する文
  }

  return 0;
}

if 文で指定されている条件式は number > 0 です。 この条件式が真となる場合、すなわち mumber の値が正となる場合は、printf("positive number\n"); が実行されて positive number と表示されます。

一方、条件式が成り立たない場合、すなわち、mumber がゼロまたは負の数の場合は処理がスキップされ、なにも表示されません。

このプログラムの処理の流れを示すフローチャートを次に示します。

flowchart

プログラムの実行結果を示します。 1 行目は、入力された数値を表しています。

  • 正の数を入力した場合 : positive number と表示されます。
端末
42
positive number
  • 負の数を入力した場合 : 何も表示されません。
端末
-1

例題 6-2 : if-else

if 文の後に else を続けることで、 条件式が偽の場合に実行する文を指定できます。

次のプログラムは、入力された数値が正の数かどうかを判定し、 正の数であれば positive number と表示し、 そうでなければ zero or negative number と表示するプログラムです。

positive_negative.c
#include <stdio.h>

int main(void) {
  int number;

  scanf("%d", &number);

  if (number > 0) {
    printf("positive number\n");
  } else {
    printf("zero or negative number\n");
  }

  return 0;
}

if 文の条件式 number > 0 が成り立つときは、 printf("positive number\n"); が実行されます。 条件式が成り立たないとき、すなわち、number がゼロまたは負の数のときは、 else の後に続くブロック内の文 printf("zero or negative number\n"); が実行されます。

このプログラムの処理の流れを示すフローチャートを次に示します。

flowchart

プログラムの実行結果を示します。 1 行目は、入力された数値を表しています。

  • 正の数を入力した場合 : positive number と表示されます。
端末
42
positive number
  • 負の数を入力した場合 : zero or negative number と表示されます。
端末
-1
zero or negative number
  • ゼロを入力した場合 : zero or negative number と表示されます。
端末
0
zero or negative number

例題 6-3 : else if

if 文の else のあとに if 文 ( if-else 文) を続けることで、 最初の条件式が偽の場合に、 さらに別の条件式を指定して処理を分岐させることができます。

次のプログラムは、入力された数値が正の数か、負の数か、ゼロかを判定し、 それぞれの場合に応じて、positive number , negative number , zero と表示するプログラムです。

positive_negative_zero.c
#include <stdio.h>

int main(void) {
  int number;

  scanf("%d", &number);

  if (number > 0) {
    printf("positive number\n");
  } else if (number < 0) {  // 最初の条件式が偽のときに実行される
    printf("negative number\n");
  } else {
    printf("zero\n");
  }

  return 0;
}

最初のif 文の条件式 number > 0 が成り立つときは、printf("positive number\n"); が実行されます。 この条件式が成り立たないときは、else に続く if 文が実行されます。 すなわち 2 番目の条件式 number < 0 が成り立つか成り立たないかが評価されて、 条件が成り立つときは printf("negative number\n"); が実行され、 条件式が成り立たないときは、最後の else に続く文 printf("zero\n"); が実行されます。

このプログラムの処理の流れを示すフローチャートを次に示します。

flowchart

プログラムの実行結果を示します。 1 行目は、入力された数値を表しています。

  • 正の数を入力した場合 : positive number と表示されます。
端末
42
positive number
  • 負の数を入力した場合 : negative number と表示されます。
端末
-1
negative number
  • ゼロを入力した場合 : zero と表示されます。
端末
0
zero

例題 6-4 : 複雑な条件式

if 文の条件式には、複数の条件を組み合わせることができます。 次のプログラムは、室温として実数値を入力したときに、 入力された数値が 15.0 以上 25.0 未満であれば、 comfortable と表示し、 そうでなければ uncomfortable と表示するプログラムです。

room_temperature.c
#include <stdio.h>

int main(void) {
  double temperature;
  
  printf("temperature? ");
  scanf("%lf", &temperature);
  
  if (15.0 <= temperature && temperature < 25.0) {
    printf("comfortable\n");
  } else {
    printf("uncomfortable\n");
  }

  return 0;
}

if 文の条件式 15.0 <= temperature && temperature < 25.0 は、 論理積( AND ) && で結合された 2 つの条件式 15.0 <= temperaturetemperature < 25.0 が両方とも成り立つときに真となります。 すなわち、temperature が 15.0 以上 25.0 未満のときに真となります。

条件式を 15.0 <= temperature < 25.0 のようには記述しないことに注意してください。 このように記述すると意図した通りには動作しません。

このプログラムの処理の流れを示すフローチャートを次に示します。 flowchart

プログラムの実行結果を示します。 1 行目は入力された数値(室温)を表しています。

  • 21.5 を入力した場合 : 条件式が真となるので comfortable と表示されます。
端末
temperature? 21.5
comfortable
  • 30.0 を入力した場合: 条件式が偽となるので、uncomfortable と表示されます。
端末
temperature? 30.0
uncomfortable

switch 文

条件によって分ける処理の分岐が多い場合、switch 文を使うとプログラムを簡潔に書くことができます。 整数値を取る制御式の値によって、処理を分岐させることができます。

switch (制御式) {
  case 値1:
    処理1;  // 制御式の値が 値1 のときに実行する処理
    break;  // switch 文を抜ける
  case 値2: 
    処理2;  // 制御式の値が 値2 のときに実行する処理
    break;  // switch 文を抜ける
  default:  
    処理3;  // 制御式の値が 値1, 値2 のいずれでもないときに実行する処理
    break;  // switch 文を抜ける
}

制御式には整数値や文字をとる変数や式を指定します。 制御式の値が case で指定した値と一致する場合に、 その case に続く処理が実行されます。 各 case での処理の最後に break を記述することで、switch 文を抜けることができます。 break がない場合は、次の case の処理が続けて実行されます。

default: は、制御式の値が case で指定した値と一致しない場合に、 実行される処理を指定するためのラベルです。 (default: は省略することもできます。)

フローチャートを使って、switch 文の処理の流れを示すと次のようになります。 flowchart

例題 6-5 : switch 文

次のプログラムは、char 型の値を入力して、 入力された値に応じて曜日を表示するプログラムです。

各曜日 Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday の 頭文字の大文字 M, T, W, F, S を入力すると 該当する曜日を表示し、それ以外の文字を入力すると invalid と表示します。

weekday1.c
#include <stdio.h>

int main(void) {
  char day_ch;

  scanf("%c", &day_ch); 

  switch (day_ch) { // 制御式 day_ch の値によって分岐
    case 'M':
      printf("Monday\n");
      break;
    case 'T':
      printf("Tuesday or Thursday\n");
      break;
    case 'W':
      printf("Wednesday\n");
      break;
    case 'F':
      printf("Friday\n");
      break;
    case 'S':
      printf("Saturday or Sunday\n");
      break;
    default:
      printf("invalid\n");
      break;
  }

  return 0;
}

このプログラムでは、 scanf によってユーザーから入力された曜日の頭文字が char 型の変数 day_ch に格納されます。 switch 文では制御式としてこの day_ch を指定しています。

例えば、day_ch の値が T である場合を考えましょう。 このとき、ラベル case 'T': の場所に処理がジャンプし、 printf("Tuesday or Thursday\n"); が実行されます。 続いて、break; があるので、switch 文を抜けます。

もし、day_ch の値が t であった場合には、 いずれの case にも当てはまらないので、 default: に処理がジャンプし、 printf("invalid\n"); が実行され、続く break; により switch 文を抜けます。

プログラムの処理の流れをフローチャートに示すと次のようになります。

flowchart

プログラムの実行結果を示します。 1 行目は入力された文字を表しています。

  • M を入力した場合 :
端末
M
Monday
  • T を入力した場合 :
端末
T
Tuesday or Thursday
  • t を入力した場合 :
端末
t
invalid

例題 6-6 : switch 文の fall-through

例題 6-5 のプログラムでは、 各曜日 Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday の 頭文字の大文字 M, T, W, F, S を入力すると 該当する曜日を表示してくれました。 次のプログラムは、各曜日の頭文字の小文字 m, t, w, f, s の入力にも対応できるよう拡張したものです。

weekday2.c

#include <stdio.h>

int main(void) {
  char day_ch;

  scanf("%c", &day_ch);

  switch (day_ch) { // 制御式 day_ch の値によって分岐
    case 'M':  
    case 'm': 
      printf("Monday\n"); 
      break;  
    case 'T':   // day_ch の値が T のときはここに処理がジャンプ
    case 't':   // day_ch の値が t のときはここに処理がジャンプ
      printf("Tuesday or Thursday\n");  // day_ch の値が T または t のときに実行
      break;                            // switch 文を抜ける
    case 'W':
    case 'w':
      printf("Wednesday\n");
      break;
    case 'F':
    case 'f':
      printf("Friday\n");
      break;
    case 'S':
    case 's':
      printf("Saturday or Sunday\n");
      break;
    default:
      printf("invalid\n");
      break;
  }

  return 0;
}

このプログラム中の switch 文では、例題 6-6 と同様に char 型の変数 day_ch を制御式として指定しています。

day_ch の値が t の場合には、処理が case 't': にジャンプするので、 Tuesday or Thursday と表示され、続く break により switch 文での処理は終了します。

もし、変数 day_ch の値が T である場合にはラベル case 'T': の場所に処理がジャンプします。 このラベル case 'T': の後には、命令が無く、特に break もありません。 したがって、処理は次の case 't': の場所に移ります。 ここには、printf("Tuesday or Thursday\n"); という命令があるので、Tuesday or Thursday と表示されます。 続いて、break があるので、switch 文を抜けます。

このように、case の後に break がない場合では、処理は次の case に続きます。 このような動作を fall-through と呼びます。 fall-through を利用することで、同じ処理を複数の case に対して指定することができます。

このプログラムの処理の流れをフローチャートに示すと次のようになります。 flowchart

このプログラムの実行例を示します。 1 行目は、入力された文字を表しています。

  • t を入力した場合 :
端末
t
Tuesday or Thursday
  • T を入力した場合 :
端末
T
Tuesday or Thursday

大文字の T と小文字の t のどちらを入力した場合も、 Tuesday or Thursday と表示されているのがわかります。


演習

演習 6-1

M県S市のとある水族館は、入場者の年齢によって入場料が決まります。 年齢と入場料の対応は以下の表のとおりです。

券種年齢入場料(円)
大人13歳以上65歳未満2400
子供13歳未満1200
シニア65歳以上1800

年齢を入力すると、入場料を出力するプログラムを作成してください。 なお、年齢は0以上の整数で入力されるものとします。

プログラムの処理の流れをフローチャートを作成して検討し、 そのフローチャートに基づいてプログラムを作成してください。

期待される実行結果の例を示します。 1行目はプログラムへ入力する年齢を示しています。

  • 13 歳の場合 : 大人料金
端末
13
2400 yen
  • 10 歳の場合 : 子供料金
端末
10
1200 yen
  • 65 歳の場合 : シニア料金
端末
65
1800 yen

演習 6-2

switch 文を使って、次のようなプログラムを作成しましょう。

プログラムを実行すると "Enter your order number: Coffee (0), Tea (1) or milk (2)" と表示し、 ユーザーからの入力を待ちます。入力は整数値です。 入力された値に応じて、次のように表示します。

  • 0 の場合 : Coffee
  • 1 の場合 : Tea
  • 2 の場合 : Milk
  • それ以外の場合 : Water

プログラムの処理の流れをフローチャートを作成して検討し、 そのフローチャートに基づいてプログラムを作成してください。

期待される実行結果の例を示します。 1 行目はプログラムからの出力で、2 行目はユーザーからの入力を示しています。 3 行目はプログラムからの出力です。

  • 1 を入力した場合 : Tea と表示
端末
Enter your order number: Coffee (0), Tea (1) or milk (2)
1
Tea
  • 5 を入力した場合 : Water と表示
端末
Enter your order number: Coffee (0), Tea (1) or milk (2)
5
Water

演習 6-3

次のプログラムを作成して、実行結果を確認してください。

fizzbuzz.c
#include <stdio.h>

int main(void) {
  int max_count;

  printf("Enter the maximum count: ");
  scanf("%d", &max_count);
  
  for (int count = 1; count <= max_count; count++) {
    if ((count % 3 == 0) && (count % 5 == 0)) {
      printf("FizzBuzz\n");
    } else if (count % 3 == 0) {
      printf("Fizz\n");
    } else if (count % 5 == 0) {
      printf("Buzz\n");
    } else {
      printf("%d\n", count);
    }
  }

  return 0;
}

第 7 章 : 繰り返し処理

この章では、繰り返し処理を行うための文である while 文、do-while 文、および for 文について学びます。

while 文

while 文は設定した条件が真である間処理を繰り返し行うための文です。 次のような場合によく利用されます。

  • あらかじめ繰り返しの回数が決まっていない場合
  • 繰り返しの回数が実行時に決まる場合
  • 繰り返しの回数がユーザーの入力によって決まる場合

while 文の構文は次の通りです。

while (継続条件式) { // 継続条件式が真である間、処理を繰り返す
  処理;  // 繰り返し実行される処理
}

while 文の処理の流れを示したフローチャートは次のようになります。

flowchart

  • 継続条件式 : 繰り返し処理を行うかどうかを判断するための条件式です。例) n <= 42

継続条件式が真である間、処理が繰り返し実行されます。 継続条件式が偽となると while 文の実行を終了します。

なお、while 文は、処理の前に継続条件式が評価されるため、 継続条件式が最初から偽であった場合、処理は一度も実行されません。

例題 7-1 : while 文

次のプログラムは、while 文を利用したプログラムです。 このプログラムでは、まずユーザーから整数値の入力を受け付けます。 その値が 42 以下である限り、値を 2 倍にして表示する処理を繰り返します。 2 倍する処理を繰り返して値が 42 を超えると、Exit と表示してプログラムを終了します。

double_while.c
#include <stdio.h>

int main(void) {
  int n;
  scanf("%d", &n); // ユーザーから整数値を入力

  while (n <= 42) { // n が 42 以下である限り繰り返す
    n *= 2; // n を 2 倍にする n <- n * 2
    printf("n = %d\n", n);
  }

  printf("Exit\n"); // 42 を超えたら while のループを抜け Exit と表示

  return 0;
}

while 文の継続条件式は n <= 42 です。 この条件式が真である間、n の値を 2 倍にして表示する処理が繰り返されます。

プログラムの処理の流れを示したフローチャートは次のようになります。

flowchart

実行結果例を示します。 1 行目は、ユーザーからの整数値の入力を示しています。 2 行目以降はプログラムからの出力です。

  • 8 を入力した場合:
端末
8
n = 16
n = 32
n = 64
Exit

入力値が 8 の場合、最初の 2 倍の処理で 16 が表示され、次の 2 倍の処理で 32 が表示されます。 その後、2 倍の処理で 64 が表示され、この時点で 42 を超えたため while 文のループを抜けて、Exit と表示されプログラムが終了します。

  • 43 を入力した場合:
端末
43
Exit

入力値が 43 の場合、最初から継続条件式 n <= 42 が偽となるため、while 文の処理は一度も実行されず、すぐに Exit と表示されてプログラムが終了します。


do-while 文

do-while 文は、while 文と似ていますが、処理の流れが異なります。 do-while 文は、少なくとも一度は処理を実行したい場合に利用されます。 do-while 文は、次のような場合に利用されます。

  • まずは処理を一度実行し、その後に継続条件式を評価して、処理を繰り返すかどうかを決定したい場合

do-while 文の構文は次の通りです。

do {
  処理; // 繰り返し実行される処理
} while (継続条件式); // 継続条件式が真である間、処理を繰り返す

do-while 文の処理の流れを示したフローチャートは次のようになります。

flowchart

  • 継続条件式 : 繰り返し処理を行うかどうかを判断するための条件式です。例) n <= 42

継続条件式が真である間、処理が繰り返し実行されます。 継続条件式が偽となると do-while 文の実行を終了します。 なお、do-while 文は、処理の後に継続条件式が評価されるため、 少なくとも一度は処理が実行されます。

例題 7-2 : do-while 文

次のプログラムは、do-while 文を利用したプログラムです。 このプログラムでは、まずユーザーから整数値の入力を受け付けます。 入力された値を 2 倍にして表示し、その値が 42 以下である限り、 再度 2 倍にして表示する処理を繰り返します。

double_do_while.c
#include <stdio.h> 
int main(void) {
  int n;

  scanf("%d", &n); // ユーザーから整数値を入力

  do {
    n *= 2; // n を 2 倍にする n <- n * 2
    printf("n = %d\n", n);
  } while (n <= 42); // n が 42 以下である限り繰り返す

  printf("Exit\n"); // 42 を超えたら do-while のループを抜け Exit と表示

  return 0;
}

プログラムの処理の流れを示したフローチャートは次のようになります。

flowchart

実行結果例を示します。 1 行目は、ユーザーからの整数値の入力を示しています。 2 行目以降はプログラムからの出力です。

  • 8 を入力した場合:
端末
8
n = 16
n = 32
n = 64
Exit

入力値が 8 の場合、最初の 2 倍の処理で 16 が表示され、次の 2 倍の処理で 32 が表示されます。 その後、2 倍の処理で 64 が表示され、この時点で 42 を超えたため do-while 文のループを抜けて、Exit と表示されプログラムが終了します。

  • 43 を入力した場合:
端末
43
n = 86
Exit

入力値が 43 の場合、最初の 2 倍の処理で 86 が表示されます。 その後、継続条件式 n <= 42 が偽となるため、do-while 文のループを抜けて、Exit と表示されてプログラムが終了します。


for 文

for 文は、繰り返し処理を行うための文です。 決まった回数の繰り返し処理を行う場合によく利用されます。

for 文は、初期設定式、継続条件式、再設定式、処理の 4 つの部分から構成されます。

c
for (初期設定式; 継続条件式; 再設定式) {
  処理;
}

処理の流れを示すフローチャートは次のようになります。

flowchart

  • 初期設定式 : 繰り返し処理を行う前に一度だけ実行される式です。 例) int i = 0
  • 継続条件式 : 繰り返し処理を行うかどうかを判断するための条件式です。例) i < 10
  • 再設定式 : 繰り返し処理のたびに実行される式です。 例) i++

for 文では、初期設定式を実行した後、継続条件式を評価します。 継続条件式が真であれば、処理が実行され、その後再設定式が実行されます。 その後、再び継続条件式が評価され、真であれば処理が繰り返されます。 継続条件式が偽となると、for 文の実行を終了します。

例題 7-3 : for 文

for 文は、決まった回数の繰り返し処理を行う場合によく利用されます。

繰り返しの回数をカウントするための制御変数を用意して、その変数を初期設定式で初期化し、繰り返しが行われるたびに再設定式でカウント値を 1 ずつ増やしていき、 継続条件式でカウント値が繰り返しの回数に達しているかどうかを判断するという方法がよく用いられます。

次のプログラムは、for 文を利用して 1 から 10 までの整数の和を、途中の計算結果を表示しながら求めるプログラムです。

sum.c
#include <stdio.h>

int main(void) {
  int sum = 0;
  int i;

  for (i = 1; i <= 10; i++) {
    sum += i;
    printf("i = %d, sum = %d\n", i, sum);
  }

  printf("sum = %d\n", sum);

  return 0;
}

例題のプログラムでは、for 文の初期設定式、継続条件式、再設定式はそれぞれ次のようになります。

  • 初期設定式 : i = 1
  • 継続条件式 : i <= 10
  • 再設定式 : i++

for 文のはじめでカウント値を保持する変数 i を 1 に初期化し、1 回繰り返し処理が終わるごとに再設定式でカウント値を 1 ずつ増やしていきます。 継続条件式 i <= 10 が真である間、すなわちカウント値が 10 以下の間は処理が繰り返されるので、ちょうど 10 回の繰り返し処理が行われます。

プログラム全体の処理の流れを示したフローチャートは次のようになります。 for 文の初期設定式、継続条件式、再設定式、処理の部分がそれぞれフローチャートの どこに対応するかを確認してください。

flowchart

実行結果は次の通りです。

端末
i = 1, sum = 1
i = 2, sum = 3
i = 3, sum = 6
i = 4, sum = 10
i = 5, sum = 15
i = 6, sum = 21
i = 7, sum = 28
i = 8, sum = 36
i = 9, sum = 45
i = 10, sum = 55
sum = 55

Tip

for 文の初期設定式の部分でカウント用の変数を宣言し初期化することができます。 先ほどのプログラムは次のように書き換えることができます。

sum.c
#include <stdio.h>

int main(void) {
  int sum = 0;

  for (int i = 1; i <= 10; i++) { // 初期設定式で変数 i を宣言し初期化
    sum += i;
    printf("i = %d, sum = %d\n", i, sum);
  }

  printf("sum = %d\n", sum);

  return 0;
}

このよう初期設定式で宣言された変数は、その for 文の内でのみ有効な変数となります。


例題 7-4 : for 文 (制御変数に double 型の変数を利用)

例題7-3 では、for 文で繰り返し回数をカウントする制御変数として int 型の変数を利用しました。 ここでは、for 文の制御変数に double 型の変数を利用して、実数値の範囲で繰り返し処理を行う例を示します。

次のプログラムは、for 文を利用して、\(x\) の値を -1.0 から 2.0 まで 0.3 ずつ増やしながら、 \(y = x^2\) の値を計算して表示するプログラムです。

square.c
#include <stdio.h>

int main(void) {

  double x; 
  double y;

  for (x = -1.0; x <= 2.01; x += 0.3) { // x の初期値を -1.0 に設定し、2.01 以下の間 0.3 ずつ増やす
    y = x * x;                // y を計算
    printf("%f %f\n", x, y);  // x と y の値を表示
  }

  return 0;
}

プログラム中の for 文において

  • 初期設定式 : double x = -1.0
  • 継続条件式 : x <= 2.01
  • 再設定式 : x += 0.3

と設定することで、\(x\) の値を -1.0 から 2.0 まで 0.3 ずつ増やしながら処理を繰り返します。

プログラムの処理の流れを示したフローチャートは次のようになります。

flowchart

実行結果は次の通りです。 1 列目の \(x\) の値が -1.0 から 2.0 まで 0.3 ずつ増加し、2 列目の \(y\) の値が \(y = x^2\) の計算結果となっています。

端末
-1.000000 1.000000
-0.700000 0.490000
-0.400000 0.160000
-0.100000 0.010000
0.200000 0.040000
0.500000 0.250000
0.800000 0.640000
1.100000 1.210000
1.400000 1.960000
1.700000 2.890000
2.000000 4.000000

例題 7-5 : 繰り返しの中での条件分岐

繰り返し処理の中で条件分岐を利用することで、特定の条件に応じて処理を変えることができます。 次のプログラムは、1 から 20 までの整数のうち、3 の倍数と 5 の倍数を表示するプログラムです。

multiples.c
#include <stdio.h>
int main(void) {
  int i;

  for (i = 1; i <= 20; i++) {  // i は 1 から 42 までの整数
    if (i % 3 == 0 || i % 5 == 0) { // i が 3 の倍数または 5 の倍数である場合
      printf("%d\n", i); // i を表示
    } 
  }

  return 0;
}

プログラムの処理の流れを示したフローチャートは次のようになります。

flowchart

実行結果は次の通りです。 1 から 20 までの整数のうち、3 の倍数と 5 の倍数のみが表示されています。

端末
3
5
6
9
10
12
15
18
20

例題 7-6 : 二重ループ

for 文の処理の中でさらに for 文を利用することで、 いわゆる二重ループを実現することができます。

次のプログラムは、for文による二重ループを利用して、 正の整数 (m, n) を入力すると、(m) 行 (n) 列の長方形の領域を * で描画するプログラムです。

rectangle.c
#include <stdio.h>

int main(void) {
  int n, m;

  scanf("%d %d", &m, &n); // ユーザーから m, n の値を入力

  for (int i = 0; i < m; i++) {   // 1 行目から m 行目までを描画するためのループ
    for (int j = 0; j < n; j++) { // 各行ごとに n 個の * を描画するためのループ
      printf("*"); // * を表示
    }
    printf("\n"); // 各行の描画が終わったら改行
  }

  return 0;
}

外側の for 文は 1 行目から順に \(m\) 行目までを描画するためのループです。 行をカウントする制御変数として int 型の i を用意し、その値は 0 から \(m - 1\) まで 1 ずつ増えていきます。

内側の for 文は各行ごとに \(n\) 個の * を描画するためのループです。 何列目の * を描画するかをカウントする制御変数として int 型の j を用意し、その値は 0 から \(n - 1\) まで 1 ずつ増えていきます。

プログラムの処理の流れを示したフローチャートは次のようになります。

flowchart

実行結果の例を示します。

1行目はユーザーからの \(m, n\) の値の入力を示しています。 2 行目以降がプログラムからの出力です。

  • \(m = 4, n = 7\) を入力した場合 :
端末
4 7
*******
*******
*******
*******

4 行 7 列の長方形の領域が * で描画されています。


break 文と continue 文

繰り返し処理の中で、特定の条件に応じてループを終了したり、次の繰り返しに移るための文として、break 文と continue 文があります。

例題 7-7 : break 文

break 文は、繰り返し処理を即座に終了するための文です。

次のプログラムは、ユーザーが入力した文字を 2 回表示する処理を 5 回繰り返し行うプログラムです。 ただし、入力された文字が q の場合は、break 文によってループを終了します。

break.c
#include <stdio.h>

int main(void) {
  int i;
  char c;

  for (i = 0; i < 5; i++) { // 5 回繰り返す
    scanf(" %c", &c); // ユーザーから文字を入力 ( %c の前にスペースを入れておくとよい)
    printf("i = %d, c = %c\n", i, c); // 入力された文字を表示

    if (c == 'q') { // 入力された文字が 'q' の場合
      break; // ループを終了
    }

    printf("i = %d, c = %c\n", i, c); // もう一度入力された文字を表示
  }

  printf("Exit\n"); // ループを抜けた後に Exit と表示
  return 0;
}

プログラムの処理の流れを示したフローチャートは次のようになります。

flowchart

q が入力された場合、break 文によって for 文から抜け出します。 そのため、q の入力に対しては 2 回目の表示は行われません。

実行結果は次の通りです。 1 行目は、ユーザーからの文字の入力を示しています。 2 行目以降はプログラムからの出力です。

  • a, l, p, h, a を順に入力した場合 :
端末
a
i = 0, c = a
i = 0, c = a
l
i = 1, c = l
i = 1, c = l
p
i = 2, c = p
i = 2, c = p
h
i = 3, c = h
i = 3, c = h
a
i = 4, c = a
i = 4, c = a
Exit

この実行例では 5 回 ( i = 0 から i = 4 ) の繰り返しが行われ、 各入力値が 2 回ずつ表示されます。

  • 途中で q を入力した場合 (a, l, q を入力した場合) :
端末
a
i = 0, c = a
i = 0, c = a
l
i = 1, c = l
i = 1, c = l
q 
i = 2, c = q
Exit

入力値が q の場合、break 文によってループが終了します。 そのため、q の入力に対しては 2 回目の表示は行われず、Exit と表示されてプログラムが終了します。


例題 7-8 : continue 文

continue 文は、現在の繰り返しを中断し、次の繰り返しに移るための文です。

次のプログラムは、例題 7-7 と同様に、 ユーザーが入力した文字を 2 回表示する処理を 5 回繰り返し行うプログラムです。 ただし、入力された文字が s の場合は、2 回目の表示を行わずに次の繰り返しに移ります。

continue.c
#include <stdio.h>
int main(void) {
  int i;
  char c;

  for (i = 0; i < 5; i++) { // 5 回繰り返す
    scanf(" %c", &c); // ユーザーから文字を入力 ( %c の前にスペースを入れておくとよい)
    printf("i = %d, c = %c\n", i, c); // 入力された文字を表示

    if (c == 's') { // 入力された文字が 's' の場合
      continue; // 現在の繰り返しを中断し、次の繰り返しに移る
    }

    printf("i = %d, c = %c\n", i, c); // もう一度入力された文字を表示
  }

  return 0;
}

プログラムの処理の流れを示したフローチャートは次のようになります。

flowchart

実行結果例を示します。

  • l, a, s, e, r を順に入力した場合 :
端末
l
i = 0, c = l
i = 0, c = l
a
i = 1, c = a
i = 1, c = a
s
i = 2, c = s
e
i = 3, c = e
i = 3, c = e
r
i = 4, c = r
i = 4, c = r

入力値が l, a, e, r の場合は、それぞれ 2 回目の表示が行われていますが、 s の場合は 1回目の表示の後 continue 文によって現在の繰り返しが中断され、2 回目の表示は行われません。


演習

演習 7-1

1 以上の自然数 \(n\) に対して、\(E_n\) を \[ E_n = \left(1 + \dfrac{1}{n}\right)^n \] と定義します。

この時、正の整数値 \(n\) を入力すると、\(E_n\) の値を計算して表示するプログラムを作成してください。 プログラムの処理の流れをフローチャートを用いて設計・検討し、それに基づいてプログラムを作成しましょう。

実行例を示します。 1 行目はユーザーからの \(n\) の値の入力を示しています。 2 行目は計算結果の表示です。

  • \( n = 1 \) の場合 :
端末
1
E_1 = 2.00000
  • \( n = 5 \) の場合 :
端末
5
E_5 = 2.488320

演習 7-2

演習 6-2 では、プログラムを実行すると、"Enter your order number: Coffee (0), Tea (1) or Milk (2)" と表示し、 ユーザーが整数値を入力すると、以下のように対応する飲み物の名前を表示するプログラムを作成しました。

  • 0 の場合 : Coffee
  • 1 の場合 : Tea
  • 2 の場合 : Milk
  • それ以外の場合 : Water

ここでは、オーダーの入力と対応する飲み物の名前の表示を繰り返し行うプログラムを作成してください。 ただし、ユーザーから 0, 1, 2 以外の値が入力されたときは、Water と表示して、繰り返しを終了してください。

プログラムの処理の流れをフローチャートを用いて設計・検討し、それに基づいてプログラムを作成しましょう。

実行例を示します。

端末
Enter your order number: Coffee (0), Tea (1) or Milk (2)
0
Coffee
Enter your order number: Coffee (0), Tea (1) or Milk (2)
2
Milk
Enter your order number: Coffee (0), Tea (1) or Milk (2)
5
Water

ユーザーの入力が 0, 1, 2 のいずれか場合はそれぞれ対応する飲み物の名前が表示され、再度オーダーの入力を促すメッセージが表示されます。 0, 1, 2 以外の 5 を入力した場合は Water と表示され、繰り返しが終了します。


演習 7-3

正の整数値 \(n\) を入力すると、以下の実行例のように、高さ \(n\) 幅 \(n\) の直角三角形の領域を文字 * で描画するプログラムを作成してください。 プログラムの処理の流れをフローチャートを用いて設計・検討し、それに基づいてプログラムを作成しましょう。

実行例を示します。 1 行目は、ユーザーからの \(n\) の値の入力を示しています。

  • \( n = 6 \) の場合 :
端末
6
*
**
***
****
*****
******

演習 7-4

1 以上の整数 \(n\) を入力すると、1 から \(n\) までの整数を順に表示するプログラムを作成してください。 ただし、次の条件に従って表示内容を変えてください。

  • 3 の倍数の時は "Fizz" と表示
  • 5 の倍数の時は "Buzz" と表示
  • 3 の倍数かつ 5 の倍数の時は "FizzBuzz" と表示
  • それ以外の時はその整数値を表示

プログラムの処理の流れをフローチャートを用いて設計・検討し、それに基づいてプログラムを作成しましょう。

実行例を示します。 1 行目は、ユーザーからの \(n\) の値の入力を示しています。 2 行目以降はプログラムからの出力です。

  • \( n = 16 \) の場合 :
端末
16
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16

演習課題

課題 1. 扇形の面積

正の実数 \(r\) と 0 以上 360 以下の実数 \(\theta\) を入力すると、 半径が \(r\) で中心角を \(\theta [^{\circ}]\) とする扇形の面積を計算して、 出力するプログラムを作成してください。 ただし、円周率 \(\pi\) は 3.14 として扇型の面積を計算してください。 中心角は度数法で与えられているに注意しましょう。

期待されるプログラムの実行例を示します。 1 行目と 2 行目はユーザーからの入力で、 1 行目では \(r\) の値、2 行目では \(\theta\) の値を入力しています。 3 行目がプログラムの出力で扇形の面積を求めて出力しています。

端末
12.3
60.0
79.175100

課題 2. 自販機のお釣り

ジュースなどの飲料品を販売している自動販売機を考えます。 この自動販売機に 1000 円札を投入し、170 円のジュースを購入したとすると、お釣りとして 830 円が返ってきます。 この時、お釣りの硬貨の枚数を最小限となるようにお釣りが返されると、 500 円硬貨 1 枚、100 円硬貨 3 枚、10 円硬貨 3 枚 が返されます。

このように、投入金額と購入商品の金額を入力を入力すると、お釣りとして返される硬貨の枚数が最小限となるように計算して出力するプログラムを作成しましょう。

入力される投入金額と購入商品の金額は、10 円以上 1000 円以下とし、 10 円単位の整数値で入力されるものとします。投入金額は購入商品の金額以上とします。

出力としては、お釣りとして返される 500 円硬貨、100 円硬貨、50円硬貨、10 円硬貨の枚数をそれぞれ出力してください。

期待されるプログラムの実行例を 2 つ示します。 それぞれ 1 行目と 2 行目はプログラムへの入力で、 1 行目が投入金額、2 行目が購入商品の金額の入力を示しています。 3 行目から 6 行目がプログラムの出力で、 上からおつりとして返される 500円硬貨、100 円硬貨、 50 円硬貨、 10 円硬貨の枚数を表します。

  • 実行例 1 : 投入金額 1000 円、購入商品の金額が 170 円の場合
端末
1000
170
500: 1
100: 3
 50: 0
 10: 3
  • 実行例 2 : 投入金額 510 円、購入商品の金額が 160 円の場合
端末
510
160
500: 0
100: 3
 50: 1
 10: 0

課題 3. 二次方程式の解

実係数 \(b, c\) を持つ \(x\) についての二次方程式 \(x^2 + b x + c = 0\) は、 その判別式 \( D = b ^ 2 - 4 c\) の値によって以下に示すような解を持ちます。

  1. \(D > 0\) の場合 : 2 個の実数解 \(x_1, x_2\) \[x_1 = \dfrac{-b + \sqrt{D}}{2}, \ x_2 = \dfrac{-b - \sqrt{D}}{2}. \]

  2. \(D = 0\) の場合 : 1 個の実数解(重解) \(x_1\) \[x_1 = \dfrac{-b}{2}. \]

  3. \(D < 0\) の場合 : 2 個の複素数解 \(x_1, x_2\) \[x_1 = \dfrac{-b}{2} + \mathrm{i} \dfrac{\sqrt{-D}}{2}, \ x_2 = \dfrac{-b}{2} - \mathrm{i} \dfrac{\sqrt{-D}}{2}. \] (\(\mathrm{i}\) は虚数単位)

2 個の実数値 \(b, c\) を入力すると \(x\) についての二次方程式 \(x^2 + b x + c = 0\) の解を、 以下に示す実行例のようにすべて求めて表示するプログラムを作成してください。 なお、平方根の計算については、平方根の計算を参照してください。

実行例を示します。 それぞれ 1 行目がプログラムへの \(b, c\) の値の入力を表します。 2 行目以降がプログラムの出力です。

  • 実行例 1 : 2 個の複素数解を持つ場合
端末
3.0 1.0
x1 = -0.3819660
x2 = -2.6180340
  • 実行例 2 : 1 個の実数解(重解)を持つ場合
端末
2.0 1.0
x1 = -1.0000000
  • 実行例 3 : 2 個の複素数解を持つ場合
端末
2.0 3.0
x1 = -1.0000000 + i  1.414214
x2 = -1.0000000 - i  1.414214

Tip

平方根の計算

平方根の計算を計算するときは、 標準ライブラリ math.h で提供されている double sqrt(double x) 関数を使うことができます。 この関数は、引数に与えた値の平方根を計算して返します。引数は 0 以上の値である必要があります。

sqrt 関数の使い方を示すプログラムを以下に示します。 sqrt 関数を使うためには、#include <math.h> をプログラムの先頭に書く必要があります。 このプログラムは、実数 \(2.0\) の平方根\(\sqrt{2.0}\) の値を計算して出力します。

sqrt.c
#include <stdio.h>
#include <math.h> // sqrt 関数を使うために必要なヘッダファイル

int main(void) {
    double x = 2.0;
    double y = sqrt(x); // x の平方根を計算
    printf("sqrt(%f) = %f\n", x, y);
    return 0;
}

math.h をインクルードしたプログラムをコンパイルするときは、 以下のように -lm オプションを付けてコンパイルする必要があります。 -lm オプションは、数学ライブラリをリンクするためのオプションです。

端末
$ gcc sqrt.c -o sqrt -lm

実行結果を示します。

端末
$ ./sqrt
sqrt(2.000000) = 1.414214

課題 4. ネイピア数の近似

1 以上の自然数 \(n\) に対して、\(E_n\) を \[ E_n = \left(1 + \dfrac{1}{n}\right)^n \] と定義します。

1 以上の自然数 \( m \) を入力すると、\(n = 1\) から \(m\) まで順に \(E_n\) を計算して、以下の出力例のように 1 列目に \(n\) 、2 列目に \(E_n\) の値を出力する プログラムを作成してください。

なお、この課題においては、累乗の計算をするときに pow を使うのは禁止とします。

期待する出力例を示します。 1 行目は \(m\) の値の入力です。ここでは \(m = 10\) を入力した例を示しています。  2 行目以降がプログラムの出力です。

端末
10
  1  2.0000000
  2  2.2500000
  3  2.3703704
  4  2.4414062
  5  2.4883200
  6  2.5216264
  7  2.5464997
  8  2.5657845
  9  2.5811748
 10  2.5937425

余力がある人は gnuplot を用いて、 \(n\) を横軸、\(E_n\) を縦軸にしてプロットしたグラフを作成してみましょう。

plot


課題 5. 円周率の近似

一辺の長さが 1 の正方形のタイルを \(n^2\) 枚並べて、図のように一辺が \(n\) の正方形 \(OABC\) の領域を作ります (図は \(n=5\) の場合を示しています)。 さらに、頂点 \(O\) を中心とする半径 \(n\) の円弧 \(\overset{\frown} {AC}\) を描きます。

図形

このとき、扇形の領域 \(OAC\) (境界を含む) に完全に含まれるタイル(図中のオレンジ色のタイル)の枚数を \(t_n\) とし、 扇形よりも外の領域 (境界を含む) \(BCA\) に完全に含まれるタイル(図中の緑色のタイル)の枚数を \(u_n\) とします。

円周率を \(\pi\) とすると、扇形 \(OAC\) の面積は \(\dfrac{\pi n^2}{4}\) となりますが、 図のタイルが覆う部分の面積に注目すると次の不等式が成り立ちます。 \[ t_n < \dfrac{\pi n^2}{4} < n^2 - u_n.
\] 整理すると次式のような円周率の下界と上界を与える式が得られます。 \[ \dfrac{4 t_n}{n^2} < \pi < \dfrac{4 (n^2 - u_n)}{n^2}. \]

さて、\(n\) を入力すると、上の式に基づいて円周率の近似値としての下界値と上界値を計算して出力するプログラムを作成しましょう。 プログラムができたら、\(n\) として 10, 100, 1000 を入力した場合の実行結果を示してください。


課題 6. 離散力学系

Gumowski-Mira 写像1 と呼ばれるものの一種についてプログラムを作成します。

1

Gumowski I., Mira, C. Recurrences and discrete dynamic systems. Lecture notes in mathematics no. 809, Springer Berlin (1980)

\(n\) を \(1\) 以上の整数として、二つの実数のペア \((x_n, y_n)\) の列を、以下に示す漸化式によって定義します。 ここで \(\mu\) は実数の定数値とします。

  • 初項 : \(n = 1\) \[ x_1 = 0, \quad y_1 = 0.5. \]

  • 漸化式 : \(n \ge 2\) \[ x_{n} = y_{n-1} + 0.008 y_{n-1} (1 - 0.05 y_{n-1}^2) + \mu x_{n-1} + \dfrac{2 (1 - \mu) x_{n-1}^2}{1 + x_{n-1}^2}, \] \[ y_{n} = -x_{n-1} + \mu x_{n} + \dfrac{2 (1 - \mu) x_{n}^2}{1 + x_{n}^2}. \]

このとき、\(\mu\) の値を入力すると、この漸化式に従って、\(x_n, y_n\) の値を \(n = 1\) から順に \(n = 10000\) までを計算して出力するプログラムを作成してください。 以下の実行例のように、1 列目に \(x_n\) 、2 列目に \(y_n\) の値を出力してください。

期待される出力例を示します。 1 行目は \(\mu\) の値の入力です。 2 行目以降がプログラムの出力で、 1 列目に \(x_n\) 、2 列目に \(y_n\) の値を順に出力しています。

  • \(\mu = -0.43\) を入力した場合の出力例
端末
-0.43
0.000000 0.500000
0.503950 0.362537
0.727956 0.173650
0.852636 0.109349
 ... 中略 ...
9.143216 3.586085
2.490937 -7.751278
-6.235065 2.978418

プログラムの出力をリダイレクトして、出力結果をファイルに保存し、 そのファイルを gnuplot を用いてプロットしたグラフを作成してみましょう。 横軸を \(x_n\) 、縦軸を \(y_n\) として点 \((x_n, y_n)\) をプロットします。

plot


課題 7. 石取りゲーム Nim

石取りゲーム Nim をプレイするプログラムを作成してください。

Nim のルール

Nim は 2 人のプレイヤーが交互に石を取り合うゲームです。 ゲームの開始時には、石の山が 3 つあり、それぞれの山には 3, 4, 5 個の石が置かれています。 (山の数と石の数は任意の値に変更しても構いません。) プレイヤーは 1 回に一つ山を選び、その山から 1 つ以上の石を取る必要があります。 プレイヤーは交互に石を取り合い、最後の石を取ったプレイヤーが勝ちとなります。

ゲームの進行例

2 人のプレイヤーをそれぞれ、プレイヤー A と プレイヤー B として、 ゲームの進行例を示します。

プレイヤー A が先番です。 初期配置では、各山の石の数は以下のようになっています。 (山には番号が付けられています。) また、プレイヤー A に石を取り除く山の番号と取り除く石の数を入力するように促します。

1 :***
2 :****
3 :*****
player A : Enter the pile number and the number of stones to remove.

プレイヤー A は、山の番号と取り除く石の数を入力します。 ここでは、2 番の山から石を 3 個取り除くとしして、2 3 と入力します。 そうすると、山の石の数は以下のように変化します。 (1 行目は、プレイヤー A の入力)

2 3
1 :***
2 :*
3 :*****
player B : Enter the pile number and the number of stones to remove.

次は、プレイヤー B の番です。 3 番目の山の石を 5 個すべて取り除くとして、3 5 と入力したとします。 山の石の数は次のようになります。

3 5
1 :***
2 :*
3 :
player A : Enter the pile number and the number of stones to remove.

次にプレイヤー A は、1 番目の山から石を 2 個取り除くとして、 1 2 と入力すると、山の石の数は次のようになります。

1 2
1 :*
2 :*
3 :
player B : Enter the pile number and the number of stones to remove.

今度はプレイヤー B が 2 番目の山から石を 1 個取り除くとして、 2 1 と入力すると、山の石の数は以下のようになります。

2 1
1 :*
2 :
3 :
player A : Enter the pile number and the number of stones to remove.

最後にプレイヤー A が 1 番目の山から 1 個の石を取り除くとして、 1 1 と入力すると、すべての石が取り除かれますので、 プレイヤー A が勝ちとなります。

1 1
1 :
2 :
3 :
player A, you won!

このようにプレイヤー A が勝ったことを示すメッセージを出力して、 ゲームは終了となります。

注意点

プレイヤーの入力は、山の番号と取り除く石の数を空白(もしくは改行)で区切って入力するものとします。 プレイヤーからの入力の値が不適切な場合、 例えば、山の番号が存在しない場合や、山に残っている石の数より多くの石を取り除こうとした場合などでは、 プレイヤーへ再度入力を促すようにしてください。


レポートの提出について

レポート課題では、以下に示す 5 つを提出してください。

  1. 設計指針
  2. フローチャート
  3. ソースコード
  4. 実行結果例
  5. 検証

それぞれについて、記載すべき内容や注意事項を以下に示します。

1. 設計指針

設計指針では、プログラムの外部設計と処理の概要を示してください。

1.1 外部設計

外部設計はユーザから見えるプログラムの振舞いです。 本実習では特にプログラムへの入力と出力について記述してください。

  • 入力 : プログラムの起動時および実行中に、ユーザーがどのようなデータを入力する必要があるのかを、 入力するデータそれぞれについて、その内容や型(整数なのか実習なのか文字なのかなど)などを説明してください。 必要に応じて入力するデータの制約条件(正の数である必要があるや、小文字の半角英数字である必要があるなどの条件)を示してください。

  • 出力 : プログラムが出力するデータについて、その内容や型などについて説明してください。 必要に応じて出力データがどのように表示されるのか、そのレイアウトを示してください。

1.2 処理の概要

プログラムが、入力されたデータをもとに、どのような手順やアルゴリズムによって出力データを得るのか その処理の概要を文章で説明してください。必要に応じて数式や図などを用いてください。(処理の詳細はフローチャートで示しますので、ここでは概要を説明してください。)

注意

  • 設計指針はテキストファイルもしくは PDF ファイルで提出してください。
    • テキストファイルの場合はファイル名の拡張子を .txt としてください。例 : guideline.txt
    • PDF ファイルの場合はファイル名の拡張子を .pdf としてください。例 : guideline.pdf

2. フローチャート

プログラムの処理の流れを検討し、プログラムの論理的な詳細設計となるフローチャートを作成してください。

注意

  • フローチャートは 5mm 方眼 A4 サイズのレイアウト用紙に書いてください。
  • 作成できたら教員または技術職員に確認を受けてサインをもらってください。
  • Microsoft Office Lens などのアプリを用いて用紙全体を撮影し、必要に応じて傾きや色の補正を行って読みやすくしたうえで、PDF ファイルに変換して提出してください。
  • フローチャートは PDF ファイルで提出してください。ファイル名の拡張子は .pdf としてください。例 : flowchart.pdf

3. ソースコード

作成した C 言語のプログラムを提出してください。 提出する前に、きちんとコンパイルでき、正常に実行できることを確認してください。

注意

  • ソースコードはテキストファイルで提出してください。ファイル名の拡張子は .c としてください。例 : exercise.c

4. 実行結果例

作成したプログラムを実際に実行したときの動作のログを提出してください。

必要に応じて複数の結果例を示してください。 例えば、入力によって分岐があるようなプログラムでは、それぞれの分岐でプログラムが適切に動作をしているかを確認するために、 複数の入力の値に対しての実行結果例を示すとよいでしょう。

注意

  • 実行結果例はテキストファイルで提出してください。ファイル名の拡張子は .txt としてください。例 : log.txt

5. 検証

実行結果例を分析し、作成したプログラムが正しく動作していることを説明してください。 必要に応じて数式や図などを用いてください。

注意

  • 検証はテキストファイルもしくは PDF ファイルで提出してください。
    • テキストファイルの場合はファイル名の拡張子を .txt としてください。例 : analysis.txt
    • PDF ファイルの場合はファイル名の拡張子を .pdf としてください。例 : analysis.pdf