WisdomSoft - for your serial experiences.

2.5 式と演算子

加算や減算と言った基本的な算術演算、どちらの値が大きいか(小さいか)などを調べる関係演算、複数の条件を組み合わせる論理演算など、プログラムの基本となる様々な計算方法を解説します。

2.5.1 算術演算子

コンピュータは人間のような曖昧で抽象的な判断を苦手としますが、計算するとこに関しては私たち人間よりもずっと得意です。人間による作業ならば何年もかかるような難解で膨大な量の計算も、現代のコンピュータは一瞬で答えを出します。

平均的な家庭用コンピュータでも 1 秒間に数百億回~数千億回の計算が可能で、高性能なコンピュータでは 1 秒間に 1 兆回を超える計算も可能です。本書執筆時点で世界最高の計算能力があるコンピュータは中国の天河 1 号ですが、このスーパーコンピュータは 1 秒間に最大で 1000 兆回を超える計算を可能とします。どんなに計算が得意な数学チャンピオンでも、現代の最新コンピュータの計算速度にかなう者はいません。

このような膨大な計算を行うコンピュータの性能を利用するにもプログラムが必要です。コンピュータが正しく計算を行うには、プログラムから計算させる値や計算方法を指示しなければなりません。

プログラム内で何らかの計算を行うには(expression)を作ります。C++ 言語における式の書き方は数学と若干異なりますが、基本的な書き方や記号は共通しています。例えば 1 に 2 を加えるという計算は C++ 言語でも 1 + 2 と記述できます。通常、計算式は(operand)と演算子(operator)で構成されています。計算のためには、まず式を作ります。

2項演算子
左項 演算子 右項

演算子(operator)とは「加算」や「減算」といった値の操作方法を表す記号やキーワードのことで、一般的に使われている + や - など数学記号のことだと考えてください。項1と項2は、演算子による計算に用いられる値です。例えば A + B という式があるとき A と B が項であり + 記号が加算を意味する演算子になります。

ただし、コンピュータでは除算を表す ÷ 記号と乗算を表す × 記号を使いません。除算には / 記号を、乗算には * 記号を使います。これら四則演算に用いられる演算子のことをまとめて算術演算子とも呼びます。C++ 言語で使われる算術演算子を表1に示します。

表1 算術演算子
演算子 意味
+ 加算
- 減算
* 乗算
/ 除算
% 剰余

これらの算術演算子のように、計算のために左右に合わせて 2 つの項が必要になる演算子のことを2項演算子(binary operator)と呼びます。この他にも、項を 1 つだけ要求する単項演算子(unary operator)や、3 つの項を必要とする3項演算子(ternary operator)というものも存在します。同じ記号を使っていても、項数によって働きが異なります。

式には、必ず結果の受け取り先が必要です。式の結果を他の式の項として用いたり、計算結果を cout に出力するなど、計算結果は必ずどこかに渡されなければなりません。例えば、次のように演算子による計算だけがあり、結果を処理しない文には意味がありません。

5 + 7 ;

これは 5 + 7 の計算結果をどうするかが記述されていないため、計算結果がどこかに保存されることも表示されることもありません。上の式は、構文上は許可されますが結果に意味を持ちません。例えば、画面に結果を表示させたいのであれば次のように結果を cout に渡す必要があるのです。

std::cout << 5 + 7 ;

これなら 5 + 7 の計算結果が cout に渡され画面に表示されます。式は、これまでリテラルを書くことができた場所に同じように書くことができます。

式を実行して結果となる値を取り出すことを評価と呼びます。例えば「5 + 7 を評価すると結果は 12 になる」などのように使われます。

コード1
#include <iostream>

int main()
{
	std::cout << "10 + 3 = " << 10 + 3 << "\n";
	std::cout << "10 - 3 = " << 10 - 3 << "\n";
	std::cout << "10 * 3 = " << 10 * 3 << "\n";
	std::cout << "10 / 3 = " << 10 / 3 << "\n";
	std::cout << "10 % 3 = " << 10 % 3 << "\n";
	return 0;
}
実行結果
コード1 実行結果

もちろん、複数の演算子を組み合わせた 5 + 3 - 4 というような計算も可能です。しかし、複数の演算子を組み合わせた複雑な式を書くときは、演算子の優先順位に注意しなければなりません。例えば、次のような計算はどのような結果になるでしょうか。

3 + 2 * 5

左から順に計算するのであれば 3 + 2 から計算し、その結果から 5 * 5 が計算されて 25 となりそうですが、これは間違いです。私たちは小学校で、足し算や引き算よりも、掛け算や割り算を先に計算するということを教わりました。C++ 言語も、この算術法則に従って加算や減算演算子よりも乗算・除算・剰余演算子の方が優先的に計算されるようになっています。よって、上記の式は 2 * 5 が先に計算され、その結果から 3 + 10 が計算されるので、最終的に得られる値は 13 となります。

もし、加算や減算を先に計算させたいのであれば、優先的に計算したい式を ( ) で括ります。これも数学の記法と同じです。

(3 + 2) * 5

上記の式であれば ( ) で括られている 3 + 2 から計算されるので 25 という結果が得られるでしょう。

コード2
#include <iostream>

int main()
{
	std::cout << "2 + 3 * 5 = " << 2 + 3 * 5 << "\n";
	std::cout << "(2 + 3) * 5 = " << (2 + 3) * 5 << "\n";
	return 0;
}
実行結果
コード2 実行結果

コード2では、項の値が同じ 2 つの式を計算しています。最初の式は括弧を使わずに 2 + 3 * 5 を計算した結果を表示しています。この結果は 3 * 5 が優先して計算されるため 17 となります。これに対して 2 番目の式は加算演算を括弧でくくり (2 + 3) * 5 としているため、括弧内の式の計算が優先されます。結果は 25 になっていることが結果から確認できます。

同じ四則演算子でも加減算と乗除算によって優先順位が異なるため加算演算子 + と減算演算子 - をまとまて加減演算子(additive operators)、乗算演算子 * と除算演算子 / 及び 剰余演算子 % をまとまて乗除演算子(multiplicative operators)と呼んで区別します。

プログラミング言語では、これら算術演算子以外にもプログラムを制御するための演算子を用いることがあります。演算子にはすべてに優先順位が定められており、優先順位の高い演算子から順に処理されます。算術演算子以外の場合でも、優先して処理させたい演算子を括弧でくくることで、演算子の処理順序を制御できます。

2.5.2 符号と負数

これまでの整数リテラルで表してきた値はすべてが 0 よりも大きな数、すなわち正の数でした。これに対し 0 よりも小さな負の数を表すことも可能です。例えば、減算演算子を応用して負数を作り出すことができます。減算した結果が 0 よりも小さくなれば、結果は負数になります。

2 - 7

上記の式は 2 から 7 を引いているため、結果は -5 となります。実際にコードに書いて結果を出力すれば、結果が負数になっていることを確認できるでしょう。

整数リテラルは常に正の数しか表現できないため、正の数を負数に変換するには単項 - 演算子(unary negation operator)を使わなければなりません。単項 - 演算子は減算演算子と同じ - 記号を使いますが、受け取る項の数が違います。減算演算子は左右に 2 つの項を受け取る 2 項演算子でしたが、単項 - 演算子は、その名の通り右に 1 つの項しか受け取らない単項演算子の一種です。コンパイラは、同じ - 記号が使われていても演算子の項の数から減算演算子なのか単項 - 演算子なのかを区別できます。

単項 - 演算子の計算結果は、与えられた項の符号を反転させた値です。例えば、整数リテラル 3 を負数に変換するには -3 と記述します。整数リテラルで表されている正の数 3 を単項 - 演算子に与えることによって符号を反転させ、その結果として -3 という負数を得ています。-3 という整数リテラルではなく、3 という値の符号を反転させるという操作を表しています。

単項 - 演算子によって整数リテラルの符号が反転されることを確認するには、次のようなプログラムを書いて実行すると分かりやすいでしょう。

コード3
#include <iostream>

int main()
{
	std::cout << "-3=" << -3 << "\n";
	std::cout << "-(-3)=" << -(-3) << "\n";
	return 0;
}
実行結果
コード3 実行結果

コード3では、まず最初に整数リテラル 3 を負数にする単純な -3 という値を出力しています。この結果は、表記通り -3 と表示されています。その次に、-3 の結果をさらに単項 - 演算子に与えるために -(-3) という式を出力しています。単項 - 演算子の働きは符号を反転するものであって、絶対的に負数にするという意味ではないことに注目してください。すなわち、単項 - 演算子の項が負数であれば、結果は正の数になるのです。これを実行すると -(-3) の計算結果は 3 であることが確認できます。

単項 - 演算子に対して、単項 + 演算子(unary plus operator)も存在します。単項 + 演算子は、単項 - 演算子と同じように 1 つの項を受ける演算子ですが、符号は変化せず与えられた項がそのまま結果となります。正の数に変換するなどの効果はなく、+5 の結果は 5 であり、+(-5) の結果は -5 です。この演算子を積極的に利用する理由はありません。

コード4
#include <iostream>

int main()
{
	std::cout << "+5=" << +5 << "\n";
	std::cout << "+(-5)=" << +(-5) << "\n";
	return 0;
}
実行結果
コード4 実行結果

コード4を実行すると、単項 + 演算子に与えた値が正でも負でも、その結果の符号は変化しないことが確認できます。+5 の結果は 5 のままであり、+(-5) の結果は -5 となっています。このように単項 + 演算子の結果は与えられた項のままになります。

2.5.3 等価演算子と関係演算子

プログラミングの世界では値を計算するだけではなく、2 つの値を比較することができます。この操作は、値を比較した結果に応じてプログラムの流れを変更するなどの制御に応用されます。プログラムの流れを変更する方法については後述するので、この場では値の比較方法とその結果について説明します。

2 つの値が等しいかどうかを調べるには等価演算子(equality operators)を使います。この演算子は、算術演算子と同じように左右に 2 つの項を受けます。

表2 等価演算子
演算子 意味
== 等しい(equal to)
!= 等しくない(not equal to)

値が等しいかどうかを調べるには == 演算子を、等しくないかどうかを調べるには != 演算子を用います。演算子の結果は、条件が満たされていれば true を、そうでなければ false を返します。

10 == 10

上の演算は、左右の項 10 と 10 が等しいかどうかを調べます。見た通り 10 と 10 は等しいため結果は true、すなわち 1 となるでしょう。

誰が見ても 2 つの整数リテラル 10 と 10 が等しいのは明確なので、上記のようにリテラル同士を演算子で調べることはありません。実際にはユーザーが入力した値やネットワークやファイルから読み込んだ値など、コンパイル時点では確定できない動的な値に対して比較を行います。

コード5
#include <iostream>

int main()
{
	std::cout << (10 == 1) << "\n";
	std::cout << (10 == 10) << "\n";
	return 0;
}
実行結果
コード5 実行結果

コード5は、単純に == 演算子の機能を調べるために 10 == 1 と 10 == 10 の結果を出力しているだけの簡単なプログラムです。実行結果を見れば  == 演算子の左右の項の値が異なっていれば false (0)が返され、等しければ true (1)が返されていることが確認できます。双方の値が等しくないかどうかを調べる != 演算子に書き変えると、結果が逆になるので試してみてください。

このプログラムでは、等価演算子の式 10 == 1 や 10 == 10 を ( ) で括っていますが、これは演算子の優先順位の都合により必須です。まだ説明していませんが、標準出力を表す cout へデータを出力することを表す << 記号も演算子の一種です。この演算子の優先順位が等価演算子よりも高いため、括弧を使わずに

std::cout << 10 == 1 << "\n";

と記述した場合は

(std::cout << 10) == (1 << "\n");

と解釈されてしまい、このような演算は型の不整合のためコンパイルエラーが発生します。

値の大小関係を調べるには関係演算子(relational operators)を使います。この演算子も、前述の等価演算子と同じように 2 つの項を受け、演算子の持つ条件が満たされていれば true を返し、そうでなければ false を返します。

表3 関係演算子
演算子 意味
< より小さい(less than)
<= より小さい、または等しい(less than or equal to)
> より大きい(greater than)
>= より大きい、または等しい(greater than or equal to)

例えば < 演算子は左項の値が右項の値より小さければ true を、そうでなければ false を返します。

< 演算子と <= 演算子の比較範囲の違いに注意してください。< 演算子の場合は、左項が右項よりも小さいかどうかを調べるものなので、双方の値が等しければ false になります。しかし <= 演算子であれば双方の値が等しい場合も true になります。

コード6
#include <iostream>

int main()
{
	std::cout << "5 < 4 = " << (5 < 4) << "\n";
	std::cout << "5 < 5 = " << (5 < 5) << "\n";
	std::cout << "5 < 6 = " << (5 < 6) << "\n";
	std::cout << "\n";
	std::cout << "5 <= 4 = " << (5 <= 4) << "\n";
	std::cout << "5 <= 5 = " << (5 <= 5) << "\n";
	std::cout << "5 <= 6 = " << (5 <= 6) << "\n";
	return 0;
}
実行結果
コード6 実行結果

コード6の結果から < 演算子と <= 演算子の違いを確認できます。どちらも値が小さいかどうかを比較する演算子なので左項の値が右項の値よりも小さければ true を返し、逆に左項の値が右項の値よりも大きければ false を返します。従って 5 < 4 と 5 <= 4 の結果はどちらも false になり、5 < 6 と 5 <= 6 の結果はどちらも true です。しかし、値が等しい場合は異なり 5 < 5 は false になりますが、5 <= 5 は true になります。

2.5.4 論理演算子

等価演算子や比較演算子を使うことによって値の関係を調べることができました。一般的なプログラムでは、これらの演算子を使ってプログラムの流れを決める条件を作ります。例えば、商品の購入手続きで入力された注文個数と在庫数を比較し、在庫が足りれば商品を発送する手続きを行い、そうでなければ入荷処理を行うという処理が考えられます。もちろん、実際のオンライン販売システムは、あらゆる状態の可能性を考慮して、ずっと複雑な処理が行われています。

条件が複雑になれば、異なる複数の条件を組み合わせた処理が求められることもあります。不動産データの中から「家賃 80,000 円未満で、かつ築年数 5 年以内」の物件を検索する処理を想像してみてください。このとき、家賃と築年数という 2 つの値による条件を組み合わせています。このような条件の組み合わせには論理演算子(logical operators)を用います。

表4 論理演算子
演算子 意味
&& 論理積(logical AND)
|| 論理和(logical OR)
! 論理否定(logical negation)

論理積演算子 && は「A かつ B」という条件を表します。このような条件を AND とも呼び、その名の通り 2 つの条件の両方を満たさなければなりません。これに対して論理和演算子 || は OR とも呼ばれる「A または B」という条件を表します。論理否定演算子 ! は NOT とも呼ばれる「A でなければ」という条件の否定を表します。

論理積演算子と論理否定演算子は 2 項演算子ですが、論理否定演算子 ! は与えられた値を反転するだけの 1 項演算子です。

最初に論理積演算子を見てみましょう。論理積演算子は左右に項を取る 2 項演算子で、左右の値が共に true であれば true、そうでなければ false という結果を返します。項のいずれかに false があれば、結果は常に false となります。最終的に、論理積演算子に与えられる項の組み合わせと結果は、次のいずれかになります。

true && true	//true
true && false	//false
false && true	//false
false && false	//false

一方の項でも false であれば、結果は false になります。論理積演算子の結果が true になるのは、双方の項が true のときのみです。よって、この演算子は左右の項に与えられた条件が双方ともに満たされているか動かを調べるために用いることができます。

コード7
#include <iostream>

int main()
{
	std::cout << "true && true=" << (true && true) << "\n";
	std::cout << "true && false=" << (true && false) << "\n";
	std::cout << "false && true=" << (false && true) << "\n";
	std::cout << "false && false=" << (false && false) << "\n";
	return 0;
}
実行結果
コード7 実行結果

コード7は && 演算子の項の組み合わせによって、どのような結果が得られるかを調べるプログラムです。上記のとおり、双方の項が true の場合のみ結果が true (1)となり、そうでなければ false (0)になっています。通常、論理積演算子は複数の条件がすべて満たされているかどうかを調べるときに用います。

これに対し、論理和演算子 || は項のいずれかが true であれば結果は true になります。論理和演算子の結果が false になるのは、双方の項が false の場合のみです。論理和演算子に与えられる項の組み合わせと結果は、次のいずれかになります。

true && true	//true
true && false	//true
false && true	//true
false && false	//false

このように、論理和演算子は組み合わせる条件のいずれかが満たされていれば true が返されます。

コード8
#include <iostream>

int main()
{
	std::cout << "true || true=" << (true || true) << "\n";
	std::cout << "true || false=" << (true || false) << "\n";
	std::cout << "false || true=" << (false || true) << "\n";
	std::cout << "false || false=" << (false || false) << "\n";
	return 0;
}
実行結果
コード8 実行結果

コード8の結果をコード7と比較して見れば、論理積演算子と論理和演算子の違いを確認できます。このプログラムはコード7で使われていた論理積演算子 && を論理和演算子 || に置き換えただけです。論理積演算子とは異なり、どちらか一方の項に true があれば、結果は true(1)になっていることが確認できます。論理和演算子は、複数の条件のうちいずれかの条件が満たされていれば true になります。

論理否定演算子は右項だけを受け取る 1 項演算子で、項を反転した結果を返します。与えられた値が true であれば false を返し、false であれば true を返します。論理否定演算子は NOT とも呼ばれ、その名の通り「~でなければ」という文脈で条件を否定します。

!true	//false
!false	//true

等価演算子や比較演算子などの結果を論理否定演算子に与えることで、結果を反転できます。例えば、値が等しくないかどうかは A != B と書くことができましたが !(A == B) と書くこともできます。

コード9
#include <iostream>

int main()
{
	std::cout << "!true=" << !true << "\n";
	std::cout << "!false=" << !false << "\n";
	return 0;
}
実行結果
コード9 実行結果

コード9は、論理否定演算子 ! を使ってブーリアン型のリテラルを反転させています。単純に true を渡せば false が返され、false を渡せば true が返されていることが結果から確認できます。

2.5.5 ビット単位の演算子

論理演算子は true (0 以外の値)であるか false (0)であるかの 2 つの状態に対して操作を行いましたが、同様の論理演算をビット単位で行うビット単位の演算子(bitwise operators)も用意されています。ビット単位の演算子は、値の 2 進数表現の各桁(すなわちビット)ごとに論理演算を行います。

表5 ビット単位の論理演算子
演算子 意味
& ビット単位の論理積
| ビット単位の論理和
^ ビット単位の排他的論理和
~ 1の補数(ビットごとの補数)

ビット単位の論理積演算子 & は、値を 2 進数で考えたときの各桁を比較します。桁の値 1 が true に、0 が false に相当するものとして、比較する双方の値が 1 であれば結果は 1 となり、そうでなければ 0 が出力されます。論理演算子の結果は true か false のいずれかでしたが、ビット単位の論理演算子の結果は各桁の出力を合成した数値です。

一般的な整数のサイズは 32 ビットなので 32 桁の 2 進数値として考える必要がありますが、この場では簡単にするために 8 ビットの整数をビット単位の論理演算子で計算するものとして考えてみましょう。例えば、整数 45 と 28 の 2 進数表現は 0010 1101 と 0001 1100 になります。これをビット単位の論理積演算子で計算すると、双方が 1 の桁のみ 1 となり、そうでない桁はすべて 0 になります。従って、結果は 0000 1100 となり  10 進数で 12 という結果が返されます。

コード10
#include <iostream>

int main()
{
	std::cout << "45 & 28 = " << (45 & 28) << "\n";
	return 0;
}
実行結果
コード10 実行結果

ビット単位の論理積演算子の結果は、常に計算する 2 つの値から抽出されたビットになります。よって、値が増えることはありません。2 進数から特定の桁だけを取り出す操作に用いられます。

一方、ビット単位の論理和演算子 | は、比較する桁の値が双方ともに 0 であれば 0、そうでなければ 1 となります。ビット単位の論理積演算子とは逆に 2 進数の桁を組み合わせる操作に用いられます。

例えば 8 ビットの整数 0010 1101 と 0001 1100 をビット単位の論理和演算子で計算すると、一方の桁が 1 であれば 1 となり、双方の桁が 0 の場合のみ 0 になります。従って、結果は 0011 1101 となり 10 進数で 61 という結果が返されます。

コード11
#include <iostream>

int main()
{
	std::cout << "45 | 28 = " << (45 | 28) << "\n";
	return 0;
}
実行結果
コード11 実行結果

ビット単位の論理和演算子の結果は、常に計算する 2 つの値を加えたビットとなります。よって、値が減ることはありません。

ビット単位の排他的論理和演算子 ^ は XOR 演算子とも呼ばれ、比較する各桁の一方が 1、もう一方が 0 の場合のみ 1 となり、双方が 1 または 0 の場合は 0 となる計算です。つまり、双方の値が異なっていれば 1 となる計算です。このような論理演算を排他的論理和または XOR と呼びます。8 ビットの整数 0010 1101 と 0001 1100 をビット単位の排他的論理和演算子で計算すると、結果は 0011 0001 となり 10 進数で 49 という結果が返されます。

コード12
#include <iostream>

int main()
{
	std::cout << "45 ^ 28 = " << (45 ^ 28) << "\n";
	return 0;
}
実行結果
コード12 実行結果

ビット単位の論理和演算子の結果は、ビットの反転処理になります。従って、排他的論理和の結果を同じ値でもう一度計算すると元の値に復元される性質をもちます。

ビット単位の論理演算子は、各ビットに独立した意味を持つ複数の要素の組み合わせに応用できます。特にビット単位の論理積演算子はビットの抽出、ビット単位の論理和演算子はビットの結合のために頻繁に利用されます。

図1 ビット単位の論理演算
図1 ビット単位の論理演算

複数の要素を組み合わせた値の代表的な例は色です。一般的なフルカラーのグラフィックシステムでは、赤要素、緑要素、青要素の強さの組み合わせで様々な色を表す RGB 形式を採用しています。画面上の 1 点の色を表す時、これら 3 つの色要素を個別の数として扱うよりも 1 つの整数として扱った方が受け渡しや管理が容易になります。例えば、各色要素が 8 ビットとすると 24 ビットの整数で 1 つの色を表現できます。下位 8 ビットは青要素、次の 8 ビットが緑要素、上位 8 ビットが赤要素というように、個別の意味を持ったビットの組み合わせからなる 1 つの値を作れます。

このような、各ビットごとに異なる意味を持った値を構築するときにビット単位の論理和演算子が役に立ちます。赤要素の値 0xFF0000 と、緑要素の値 0x00C000 と、青要素の値 0x0000CB を組み合わせるには、次のように記述します。ちなみに、整数リテラルに 16 進数を使っているのは 16 進数の 1 桁が 4 ビットに対応しているので、4 ビットや 8 ビット単位の値を表すのに適しているためです。

0xFF0000 | 0x00C000 | 0x0000CB	//0xFFC0CB

この演算の結果は 0xFFC0CB となり 10 進数では 16761035 という値になります。

逆に、特定のビットだけを抽出するような操作を行うにはビット単位の論理積演算子を使います。上記の演算によって作られた値 0xFFC0CB から緑要素だけを取り出すには、次のように記述します。

0xFFC0CB & 0x00FF00	//0x00C000

この演算の結果は 0x00C000 となり 10 進数では 49152 という値になります。ビット単位の論理積演算では一方のビットが 0 であれば結果は 0 になるため、抽出したいビットが 1 を、それ以外のビットが 0 の値との論理積を求めることで、特定のビットだけを取り出すことができます。このような処理はマスクと呼ばれ、画像から特定の色要素を取り除くために使われています。

コード13
#include <iostream>

int main()
{
	std::cout << (0xFF0000 | 0x00C000 | 0x0000CB) << "\n";
	std::cout << (0xFFC0CB & 0x00FF00) << "\n";
	return 0;
}
実行結果
コード13 実行結果

他にも、複数の状態の組み合わせを表現するビットフラグと呼ばれる演算にも使われます。ビットが 1 であるか 0 であるかをフラグに見立て、1 であればフラグが立っている、すなわち状態が有効であることを表すというものです。

1の補数演算子は、全てのビットを反転させた結果を返す単項演算子です。すなわち 1 のビットは 0 に、0 のビットは 1 になります。例えば 0xFFFFFFF0 という 32 ビットの値を1の補数演算子に与えると 0x0000000F という結果が得られます。

コード14
#include <iostream>

int main()
{
	std::cout << "~0xFFFFFFF0=" << ~0xFFFFFFF0 << "\n";
	std::cout << "~0xF0=" << ~0xF0 << "\n";
	return 0;
}
実行結果
コード14 実行結果

一般的なコンピュータでは、負数を表現するために最上位ビットを符号に用いるため、ビットを反転させると符号が反転するので注意してください。例えば 0xF0 を反転させると 0x0F になることを期待するかもしれませんが、実際には 32 ビットなので 0x000000F0 を反転させ 0xFFFFFF0F という結果になります。最上位ビットが 1 となるため、結果は負数となるでしょう。

負数を表現する方法は処理系に依存する問題です。よって 32 ビット整数 0xFFFFFF0F の出力結果は環境によって異なる可能性があります。しかし、多くのコンピュータでは2の補数を負数に用いるため ~0xF0 の出力結果は -241 となるでしょう。

2 の補数は、すべてのビットを反転(1 の補数)させた結果に 1 を加えれば求められます。0xFFFFFF0F は最上位ビットが 1 なので負数となります。0xFFFFFF0F のビット列を反転させると 0x000000F0 となり、これに 1 を加えると 0x000000F1 となります。0xF1 は 10 進数で 241 となるため、結果と一致することがわかります。

2.5.6 シフト演算子

シフト演算子(shift operators)を使えば値を 2 進数で考えたときの 0 と 1 の並び、すなわちビット列を左右に移動させることができます。この操作のことをビット列をシフトすると呼びます。

図2 左シフト演算のイメージ
図2 左シフト演算のイメージ

例えば 4 ビットの列 0110 を右に 1 シフトすると 0011 になり、左に 1 シフトすると 1100 になります。右シフトしたときに溢れた最下位のビットは破棄され、最上位のビットは 0 で埋められます。左シフトの場合も同じように、溢れた最上位ビットが破棄され、最下位ビットが 0 で埋められます。

表6 シフト演算子
演算子 意味
<< 左シフト
>> 右シフト

シフト演算子には、ビット列を左にシフトする左シフト演算子 << と、右にシフトする右シフト演算子 >> があります。どちらも左項にシフトする値を、右項にシフトする数を指定する 2 項演算子です。

左シフト演算子  << は、画面に文字列を表示するために cout へ出力するときに使う << 演算子と同じです。詳細は後述しますが C++ 言語には演算子の機能を拡張する方法が用意されており、cout を左項とする左シフト演算子は、右項の値を出力するという意味に置き換えられています。C++ 言語が本来持っている左シフト演算子の機能は、ここで説明するようにビット列を左にシフトさせるというものです。

コード15
#include <iostream>

int main()
{
	std::cout << "6 << 1 = " << (6 << 1) << "\n";
	std::cout << "6 << 2 =" << (6 << 2) << "\n";

	std::cout << "6 >> 1 =" << (6 >> 1) << "\n";
	std::cout << "6 >> 2 =" << (6 >> 2) << "\n";
	return 0;
}
実行結果
コード15 実行結果

コード15は、10 進数の整数 6 を左右に 1 または 2 回シフトした結果を表示するプログラムです。10 進数の 6 を 2 進数にすると 0000 0110 であり、これを左に 1 回シフトすれば 0000 1100 すなわち 10 進数の 12 となります。左に 2 回シフトした場合は 0001 1000 となり、この結果は 10 進数で 24 です。

同様に、10 進数の 6 を右に 1 回シフトすると 0000 0011 となり、この結果は 10 進数で 3 です。右に 2 回シフトした場合は 0000 0001 となり、結果は 10 進数で 1 となります。