今回は、演算子について学んでいきます。前回までの記事は新しい知識がどんどん出てきて大変だった方も居たかもしれませんが、今回の記事の内容は前回までの内容がある程度理解できていれば簡単に感じると思います(覚えることは多いですが……)。
演算子というものについては記事の中で順次見ていくので、まずは前回の復習からです!
前回の復習
前回の記事では、型と変数に関連したものを中心に学びました。前半では『宣言』『代入』『初期化』という用語が出てきましたね。それぞれの用語はソースコードを見ながら復習しておきましょう。
#include <stdio.h>
int main() {
int member; //宣言
member = 10; //代入
int id = 150; //初期化
return 0;
}
4行目で変数の宣言を行い、memberという名前の箱を作っています。この箱はint型で宣言されているため箱の中には整数値しか入りません。
5行目では先ほど作ったmemberという箱に10という整数値を代入しています。この時に用いている『=』は代入演算子と呼ばれているもので、今回の記事でも登場します。
6行目は宣言と代入を同時に行なっている部分で、これを初期化と呼ぶんでしたよね。
後半ではprintf関数を用いて変数の中身を出力する方法について勉強しました。フォーマット指定子を用いることで変数の中身を出力できます。今回の記事でも変数の中身を確認するためにフォーマット指定子を用いた出力をたくさん行うので、printf関数の復習をしつつ演算子の勉強をしていきましょう。
前回の練習問題の解答例
A問題
次のソースコードをコンパイルするとエラーが発生します。コードを修正して以下の出力結果が得られるように修正して下さい。
#include <stdio.h>
int main() {
int dimension = 2;
printf("こちら%s次元行きの電車になります\n", dimension);
return 0;
}
出力結果
こちら2次元行きの電車になります
解答例
#include <stdio.h>
int main() {
int dimension = 2;
printf("こちら%d次元行きの電車になります\n", dimension);
return 0;
}
4行目で変数dimensionを初期化しています。それをprintf関数を使って出力しようとしていますがフォーマット指定子が”%s”になっています。これをint型に対応したフォーマット指定子(“%d”)に直してあげることで期待通りの出力が得られます。
B問題
実行結果が以下の出力になるようにソースコードを書いてください。ただし、数値の部分はフォーマット指定子を使用して下さいね。(^^)
わたしの戦闘力は53万です
解答例
#include <stdio.h>
int main() {
int power = 53;
printf("わたしの戦闘力は%d万です\n");
return 0;
}
A問題と同じくprintf関数でフォーマット指定子を用いています。
演算子
前回の復習も終えたので、ここからは今回の話題に入っていきます。
『演算子という言葉をどこかで聞いたことがありますか?』
プログラミング初心者の読者の方にはあまり馴染みのない言葉だとは思います。でもほとんどの皆さんは演算子というものを知っているのです。演算子と呼ばれるものについて説明をする前に、演算子が使われているサンプルコードを見てみましょう。
#include <stdio.h>
int main() {
int candy = 5;
int cookie = 8;
int sum = candy + cookie;
printf("%d\n", sum);
return 0;
}
実行結果
13
このソースコードで注目して欲しいのは6行目の部分です。4行目、5行目で初期化して作った変数を用いて何かしらの処理をしています。どんな処理をしているのかを出力結果から推測してみましょう。
出力結果は13となっていることから、7行目自転で変数sumの中には13という値が入っていたことがわかります。この13という値はどこからきたのでしょうか。勘のいい人や素直な人は『変数の中身同士を足した結果が変数sumに代入されているんでしょ?』と思ったことでしょう。正解です!congratulations!!
演算子というのは小学校なんかで習った加減乗除みたいな値同士を計算する役割をもつ記号列のことを指します。つまり算数で足し算を習った人は演算子を使った計算を勉強したことがあるわけですね。
早速プログラミングで使われる演算子について見ていくわけですが、種類が多くて覚えるのが大変なので、いくつかのグループ分けて紹介していきます。
グループに関しては以下のようにまとめてみました。(まとめ方には人によってややバラツキが見られますがこれが一番わかりやすい分け方だと思います)
演算子の種類分け | 具体例 |
---|---|
算術演算子 | +, -, *, /, %, ++, — |
ビット演算子 | &, |, ^, <<, >>, ~ |
代入演算子 | =, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= |
比較演算子 | ==, !=, <, >, <=, >= |
論理演算子 | &&, ||, ! |
その他の演算子 | ?(条件演算子), ->(アロー演算子), キャスト演算子, sizeof演算子 |
今の段階では、『比較演算子』と『論理演算子』と『その他にある条件演算子』に関して勉強するのは少し早いので、if文を勉強する時の記事で説明することにします。
今回の記事では残りの『算術演算子』、『ビット演算子』、『代入演算子』に絞って学んでいきましょう。
算術演算子
算術演算子に分類したのは以下の演算子になります。
演算子 | 構文 | 備考 |
---|---|---|
+ | a + b | aとbの和を取ります |
– | a – b | aとbの差を取ります |
* | a * b | aとbの積を取ります |
/ | a / b | aとbの商を取ります |
% | a % b | a割るbの余りを取ります |
++ | a++ | aの値を1大きくします |
–– | a-– | aの値を1小さくします |
表の上から3つに関しては小学校の足し算、引き算、掛け算でほとんど同じなので、説明は不要でしょう。
4つ目の割り算については少し特殊で、構文のところに示した変数a, bが共にint型のときは、商を計算し、割り切れるまで計算を行ったりはしません。しかし、片方でも浮動小数点型(float型, double型とかのこと)であるならば、小数点以下まで計算を実行してくれます。
5つ目の『%』については、a÷bをした時のあまりを計算してくれる演算子になります。かなりの頻度で表れる演算子なので必ずココで押さえておきましょう。
6つ目と7つ目の『++』と『-–』は一つの変数に対して処理を行うので、単項演算子と呼ばれる種類に入ります。『++』をインクリメント、『-–』をデクリメントと呼ぶことが多いので、名前も一緒に覚えておいた方が良いでしょう。『++』を用いると変数の中にある値が1大きくなり、『-–』を用いると変数の中にある値が1小さくなります。
ここまでに登場した演算子を使ったサンプルコードをおいておきます。たくさん書いて覚えてください。
#include <stdio.h>
int main() {
int a = 20;
int b = 15;
int c = a + b; //変数cにa+bの結果を代入している
printf("+演算子を使った場合: %d\n", c);
int d = a - b; //変数dにa-bの結果を代入している
printf("-演算子を使った場合: %d\n", d);
int e = a * b; //変数eにa×bの結果を代入している
printf("*演算子を使った場合: %d\n", e);
int f = a / b; //変数fにa÷bの商を代入している
printf("/演算子を使った場合: %d\n", f);
int g = a % b; //変数gにa÷bの余りを代入している
printf("%%演算子を使った場合: %d\n", g);
a++;
printf("a++をした場合: %d\n", a);
b--;
printf("b--をした場合: %d\n", b);
return 0;
}
実行結果
+演算子を使った場合: 35
-演算子を使った場合: 5
*演算子を使った場合: 300
/演算子を使った場合: 1
%演算子を使った場合: 5
a++をした場合: 21
b--をした場合: 14
算術演算子で紹介したインクリメントとデクリメントには面白い性質があります。それは前置と後置の両方が可能というものです。試しに以下のコードをコピペして実行してみてください。
#include <stdio.h>
int main() {
int a = 1;
int b = 1;
a++;
++b;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
出力結果
2
2
このような出力結果が得られたと思います。このソースコードの6行目と7行目は本質的には同じ処理をしています。
それではなぜ2通りの書き方があるのでしょうか。その違いを理解するために次のサンプルコードを実行してみてください。
#include <stdio.h>
int main() {
int a = 1;
int b = 1;
printf("%d\n", a++);
printf("%d\n", ++b);
return 0;
}
出力結果
1
2
出力結果に注目してください。6行目と7行目の出力結果が違います。なぜこのような差異が発生するのかというと、演算子と出力における処理の優先度の違いに起因するためです。6行目の書き方の場合は『出力が先に行われ、その後に変数aに1足す』という処理を行なっています。一方で、7行目の書き方の場合は『変数aに1を足してから、出力が行われる』という処理を行っています。この違いが出力結果の差異の原因です。
この結果からも演算子の計算には優先度があることが納得できたと思います。演算子の優先度については、何回か書いているうちに大体把握できるので、詳しい優先度のお話は割愛します。どうしても不安な場合は、『括弧”(“, “)”』で囲みましょう。
ビット演算子
ビット演算子に分類したのは以下の演算子になります。
演算子 | 構文 | 備考 |
---|---|---|
& | a & b | aとbを2進数表記したときの論理積(AND)を取ります |
| | a | b | aとbを2進数表記したときの論理和(OR)を取ります |
^ | a ^ b | aとbを2進数表記したときの排他的論理和(XOR/EOR)を取ります |
<< | a << b | aを左にbビットシフトします(10進数上でaを倍する) |
>> | a >> b | aを右にbビットシフトします(10進数上でaを倍する) |
~ | ~a | aを2進数表記したときの否定(NOT)を取ります |
これらのビット演算子を学ぶ前に2進数について簡単な説明をしておきます。2進数というのは0と1の2種類の数字を用いてあらわされた数値のことです。現在我々が日常生活で用いている0~9の10種類の数字を用いる表し方は10進法といい、10進法を用いて表された数値を10進数といいます。10進数の数値を2進数に変換するには2で割る方法が知られています。
例えば10進数で13という数値を2進数に直すと1101になります。2進数の格桁をで重みをつけて計算してあげれば確認できます。(2進数について数学などで勉強したことがある人ならわかると思います。)
ビット演算子というのは変数中の数値を2進数に置き換えて、それぞれのビットごと(桁ごと)に演算を行うものになります。それぞれの演算子をこれから見ていきますが、変数の中にある数値が2進数であることを意識して読み進めてください。
1つ目は、『論理積(&)』です。この演算子は、2つの変数から1bitずつとってきた時、両方とも1だった場合のみ1をかえし、それ以外の場合は0を返す演算子です。
表にするとこんな感じですね。
変数aから1bit | 変数bから1bit | a & b |
---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
サンプルコードで示すとこんな感じです。(今回だけ自作関数を使ってコードを書いていますがまだ習っていないので眺めるかコピペで大丈夫です。自作関数については第12回の記事で扱います)
#include <stdio.h>
void gen2(int x);
int main() {
int a = 13;
printf("aを2進数表示にすると: ");
gen2(a); //aを2進数にして表示させています
printf("\n");
int b = 10;
printf("bを2進数表示にすると: ");
gen2(b); //bを2進数にして表示させています
printf("\n");
int c = a & b;
printf("a&bを2進数表示にすると: ");
gen2(c); //cを2進数にして表示させています
printf("\n");
return 0;
}
/*2進数で表示する自作関数*/
void gen2(int x) {
if(x == 0) {
return;
}
gen2(x / 2);
printf("%d", x % 2);
return;
}
実行結果
aを2進数表示にすると: 1101
bを2進数表示にすると: 1010
a&bを2進数表示にすると: 1000
aとbを2進数に直した時の4桁目が両方とも1になっていて、そのほかの桁はどちらか一方または両方とも0になっているのでa&bをするとその桁は0になります。ちなみに、10進数で出力すると、以下のようになります。こちらのコードは自分で書いて試してみましょう。
#include <stdio.h>
int main() {
int a = 13;
printf("a: %d\n", a);
int b = 10;
printf("b: %d\n", b);
int c = a & b;
printf("a&b: %d\n", c);
return 0;
}
実行結果
a: 13
b: 10
a&b: 8
10進数の8は2進数で1000と表されることから、『論理積(&)』の演算について理解できたと思います。
2つ目は、『論理和(|)』です。この演算子は、2つの変数から1bitずつとってきた時、両方とも0だった場合のみ0をかえし、それ以外の場合は1を返す演算子です。
表にするとこんな感じですね。
変数aから1bit | 変数bから1bit | a | b |
---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
先ほどの論理積の時と同様に、2進数の場合の出力があった方が理解しやすいと思うので、載せておきます。
#include <stdio.h>
void gen2(int x);
int main() {
int a = 13;
printf("aを2進数表示にすると: ");
gen2(a); //aを2進数にして表示させています
printf("\n");
int b = 10;
printf("bを2進数表示にすると: ");
gen2(b); //bを2進数にして表示させています
printf("\n");
int c = a | b;
printf("a|bを2進数表示にすると: ");
gen2(c); //cを2進数にして表示させています
printf("\n");
return 0;
}
/*2進数で表示する自作関数*/
void gen2(int x) {
if(x == 0) {
return;
}
gen2(x / 2);
printf("%d", x % 2);
return;
}
実行結果
aを2進数表示にすると: 1101
bを2進数表示にすると: 1010
a|bを2進数表示にすると: 1111
aとbを2進数で表現した時、aとbのそれぞれの桁の数字は共に0になるところがないため、論理和をとると、全ての桁が1になっているのがわかります。
10進数で出力すると以下のようになります。こちらのコードも自分で書いて試してみましょう。
#include <stdio.h>
int main() {
int a = 13;
printf("a: %d\n", a);
int b = 10;
printf("b: %d\n", b);
int c = a | b;
printf("a|b: %d\n", c);
return 0;
}
実行結果
a: 13
b: 10
a|b: 15
10進数の15は2進数で1111と表されることから、『論理和(|)』の演算についても理解できたと思います。
3つ目は、『排他的論理和(^)』です。この演算子はXORとかEORとか呼ばれる事の方が多いので頭の片隅にでも入れておいてくださいね。XORは2つの変数から1bitずつとってきた時、両方とも0または両方とも1だった場合は0をかえし、それ以外の場合は1を返す演算子です。
表にするとこんな感じですね。
変数aから1bit | 変数bから1bit | a ^ b |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
両方の数字が同じなら0、異なるなら1となりますね。
先ほどまでの論理積や論理和の時と同様に、2進数の場合の出力で見ていきます。
#include <stdio.h>
void gen2(int x);
int main() {
int a = 13;
printf("aを2進数表示にすると: ");
gen2(a); //aを2進数にして表示させています
printf("\n");
int b = 10;
printf("bを2進数表示にすると: ");
gen2(b); //bを2進数にして表示させています
printf("\n");
int c = a ^ b;
printf("a^bを2進数表示にすると: ");
gen2(c); //cを2進数にして表示させています
printf("\n");
return 0;
}
/*2進数で表示する自作関数*/
void gen2(int x) {
if(x == 0) {
return;
}
gen2(x / 2);
printf("%d", x % 2);
return;
}
実行結果
aを2進数表示にすると: 1101
bを2進数表示にすると: 1010
a^bを2進数表示にすると: 111
aとbを2進数で表現した時、aとbのそれぞれの桁が一致している桁は4桁目のみであることがわかります。ここでは最上位の桁が0の場合の出力がなくなっているためわかりづらいかもしれませんが、出力結果は『0111』というイメージを持ってもらえれば納得できると思います。
10進数で出力すると以下のようになります。こちらのコードは自分で書いて試してみましょう。
#include <stdio.h>
int main() {
int a = 13;
printf("a: %d\n", a);
int b = 10;
printf("b: %d\n", b);
int c = a ^ b;
printf("a^b: %d\n", c);
return 0;
}
実行結果
a: 13
b: 10
a^b: 7
10進数の7は2進数で111と表されることから、『排他的論理和(^)』の演算についても理解できたと思います。
4つ目と5つ目は『シフト演算子(<<, >>)』です。この演算子は、『a << b』や、『a >> b』のような形で用いることになります。これらについても2進数の表示で見た方がわかりやすいので、2進数で表示したものを見てもらいます。
#include <stdio.h>
void gen2(int x);
int main() {
int a = 8;
printf("aを2進数表示にすると: ");
gen2(a); //aを2進数にして表示させています
printf("\n");
int b = 2;
printf("b: %d", b);
printf("\n");
int c = a << b;
printf("a<<bを2進数表示にすると: ");
gen2(c); //cを2進数にして表示させています
printf("\n");
c = a >> b;
printf("a>>bを2進数表示にすると: ");
gen2(c); //cを2進数にして表示させています
printf("\n");
return 0;
}
/*2進数で表示する自作関数*/
void gen2(int x) {
if(x == 0) {
return;
}
gen2(x / 2);
printf("%d", x % 2);
return;
}
実行結果
aを2進数表示にすると: 1000
b: 2
a<<bを2進数表示にすると: 100000
a>>bを2進数表示にすると: 10
出力結果を見ると、a<<bの演算子を用いた場合の出力は100000となっており、0が2つ増えたのがわかります。一方で、a>>bの演算子を用いた場合の出力は10であり、1000から0が2つ減ったのがわかりますね。
このようにシフト演算子は2進数で考えた時に桁をずらすことができるものであり、それぞれ『左シフト(>>)』と『右シフト(<<)』と呼ばれています。
10進数で見てみるとシフト演算子と掛け算の関係が見えてくると思うので載せておきます。コードをいじって実験してみてくださいね。(練習問題にシフト演算子を使うものを入れておいたのでそちらもどうぞ)
#include <stdio.h>
int main() {
int a = 13;
int b = 1;
printf("a: %d\n", a);
printf("a<<b: %d\n", a << b);
printf("a>>b: %d\n", a >> b);
return 0;
}
実行結果
a: 13
a<<b: 26
a>>b: 6
この演算子は2の補数について勉強しないと真に理解することができないので、ここではこの程度の説明で留めておきます。
6つ目は2進数の各桁の0と1を反転させる演算子です。使い方はすごく簡単で、変数の前に『~』をつけるだけです。ただ、この演算子の存在を知らなくても全然やっていけてるくらい出てこないので、正直忘れちゃっても問題ない演算子です。目で見てふーん程度で大丈夫です。
#include <stdio.h>
int main() {
int a = 13;
printf("a: %d\n", a);
printf("~a: %d\n", ~a);
return 0;
}
実行結果
a: 13
~a: -14
この実行結果についても2の補数表現を知らないと説明できないので割愛します。(あまり見かけないですし……。)
代入演算子
代入演算子に分類したのは以下の演算子になります。
演算子 | 構文 | 備考 |
---|---|---|
= | a = b | aにbの値を入れます |
+= | a += b | a = a + bと同じ |
-= | a -= b | a = a – bと同じ |
*= | a *= b | a = a * bと同じ |
/= | a /= b | a = a / bと同じ |
%= | a %= b | a = a % bと同じ |
&= | a &= b | a = a & bと同じ |
|= | a |= b | a = a | bと同じ |
^= | a ^= b | a = a ^ bと同じ |
<<= | a <<= b | a = a << bと同じ |
>>= | a >>= b | a = a >> bと同じ |
代入演算子については今までに出てきた演算子を理解できていれば新しく覚えることはほとんどありません。プログラミングに慣れていない方は『a = a + b』のような式に抵抗があるとは思いますが、丁寧に見ていくことで処理を追えるようになります。ちなみに、『a = a + b』は、最初に『a + b』の部分が計算されて、その後に『a = (さっきの計算結果)』という代入演算が行われます。(演算子の優先度の話)
キャスト演算子
これは他の演算子とは少し異質なものなので別枠にしました。キャスト演算子というのは、変数に入れたい値の型が変数の型と違う時に、無理やり値の型を変更して変数の型に合わせる演算子のことを指します。以下のように書くことで、double型の値をint型に変換してあげることができます。
#include <stdio.h>
int main() {
int a = 13;
double b = 15;
a = (int)b;
printf("a: %d\n", a);
return 0;
}
実行結果
a: 15
キャスト演算子は型の変換を明示的に行う時に使います。私の環境ではこのキャスト変換を取り除いた場合でもコンパイルエラーが出ず実行できてしまいましたが、人によってはエラーや予期しない動作をする可能性があるので、変数に入れる値と変数の型が異なる場合にはキャスト演算子を明示的につけるように意識しましょう。
また、無理やり型変換を行っているので、値の情報が一部失われる可能性がある点にも注意して下さいね。
演算子には優先度というものがあり、優先度が高いものから順に実行される。優先度を上げたい式がある場合は『(』と『)』で囲ってあげる必要がある。
まとめ
今回の内容は各々の演算子について触れていったので、分量が多くなってしまいました。覚える内容は多かったかもしれませんが、プログラミング特有のわかりづらい部分というのは比較的少なかったと思います。最後に今回の記事の内容を簡単に振り返ってみましょう。
- 演算子というのは変数や値に対して決められた処理を行うものを指す。また演算子の優先度については注意が必要である。
- 算術演算子とは四則演算などに代表される演算子で、インクリメントやデクリメントを除けば大体の人は知ってるような演算子のことである。
- ビット演算子とは変数を2進数とみなして処理を行う演算子のことである。
- 代入演算子については『=』が代入を表していて、残りの部分はそれぞれの演算子と同じ処理と考えると効率良く記憶できる。
- キャスト演算子とは、変数と値の型を揃えるために値や変数の前につける演算子のことである。
次回は配列について勉強していきます。配列はつまづく人が多いので連続で記事を読んでいる方は少しここら辺で復習を挟んでおくと良いと思います。
練習問題
A問題
B問題
C問題
D問題
最後まで記事を見ていただきありがとうございます。また別の記事でお会いできることを祈っております。