第 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;
}