C++のxorは、ビット単位の排他的論理和(XOR)を行うための演算子です。
一般的には記号の^が使われますが、C++ではxorという代替表記も用意されています。
a ^ b
と、
a xor b
は同じ意味です。
XORは「Exclusive OR」の略で、日本語では「排他的論理和」と呼ばれます。
2つのビットを比較し、異なっていれば1、同じであれば0になるのが特徴です。
| A | B | A XOR B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
C++でxorを使う基本的な方法
^を使ってXORを計算する
C++では、通常は^を使ってXORを記述します。
#include <iostream>
int main() {
int a = 5;
int b = 3;
int result = a ^ b;
std::cout << result << '\n';
}
実行結果は次のとおりです。
6
これは、5と3を2進数で表すと理解しやすくなります。
5 = 0101
3 = 0011
各ビットにXORを適用すると、
0101
^ 0011
------
0110
となります。
0110は10進数では6なので、5 ^ 3の結果は6です。
xorを使って書くこともできる
同じ処理は、xorを使って次のようにも書けます。
#include <iostream>
int main() {
int a = 5;
int b = 3;
int result = a xor b;
std::cout << result << '\n';
}
結果は^を使った場合と同じです。
6
C++ではxorが^の代替トークンとして定義されているため、機能上の違いはありません。
C++では^とxorのどちらを使うべきか
一般的には^が使われる
実際のC++コードでは、
a ^ b
という書き方が一般的です。
xorでも問題なく動作しますが、多くのC++プログラムや技術資料では^が使用されています。
そのため、特別な理由がなければ^を使っておくと、ほかのプログラマーにも意図が伝わりやすいでしょう。
xorはC++の正式な代替表記
C++には、記号で表される一部の演算子について、文字による代替表記があります。
代表例は次のとおりです。
| 記号 | 代替表記 |
|---|---|
&& | and |
| ` | |
! | not |
& | bitand |
| ` | ` |
^ | xor |
~ | compl |
!= | not_eq |
&= | and_eq |
| ` | =` |
^= | xor_eq |
たとえば、
if (a > 0 and b > 0) {
}
は、
if (a > 0 && b > 0) {
}
と同じ意味です。
同様に、
a xor b
は、
a ^ b
と同じ意味になります。
XORの基本的な性質
同じ値をXORすると0になる
XORには、
a ^ a == 0
という性質があります。
たとえば、
0101
0101
----
0000
となるため、
5 ^ 5
の結果は0です。
0とXORすると元の値になる
次の式では、
a ^ 0
結果は必ずaになります。
たとえば、
5 ^ 0
の結果は5です。
これはXORで0と比較したビットは変化しないためです。
同じ値で2回XORすると元に戻る
XORには、
(a ^ b) ^ b == a
という性質もあります。
たとえば、
int a = 5;
int b = 3;
int c = a ^ b;
int d = c ^ b;
とすると、dは再び5になります。
この性質は、データ変換やアルゴリズムなどで利用されることがあります。
XORには交換法則と結合法則がある
XORでは、値の順番を入れ替えても結果は変わりません。
a ^ b == b ^ a
また、
(a ^ b) ^ c == a ^ (b ^ c)
も成り立ちます。
この性質によって、複数の整数を順番にXORするアルゴリズムを簡潔に記述できます。
XORをビット操作で使う方法
特定のビットを反転する
XORは、特定のビットだけを反転させたい場合に便利です。
unsigned int value = 0b0101;
unsigned int mask = 0b0010;
value ^= mask;
計算結果は次のようになります。
0101
^ 0010
------
0111
マスク側が1になっている位置だけが反転します。
XORでは、
0 XOR 0 = 0
1 XOR 0 = 1
0 XOR 1 = 1
1 XOR 1 = 0
となるため、マスクのビットが0なら元の値を維持し、1なら反転します。
この性質から、XORはビットのON・OFFを切り替えるトグル処理によく使われます。
同じマスクでもう一度XORすると元に戻る
たとえば、
unsigned int value = 0b0001;
unsigned int mask = 0b0100;
value ^= mask;
とすると、
0101
になります。
もう一度、
value ^= mask;
を実行すると、
0001
に戻ります。
同じマスクによって繰り返しON・OFFを切り替えられるのがXORの特徴です。
AND・OR・XOR・NOTの違い
ビット演算子の主な用途
C++には、XOR以外にも複数のビット演算子があります。
| 操作 | 演算子 | 主な用途 |
|---|---|---|
| AND | & | ビットの抽出・確認、クリア |
| OR | ` | ` |
| XOR | ^ | 特定ビットを反転する |
| NOT | ~ | 全ビットを反転する |
たとえば、特定のビットを立てたい場合はORを使います。
value |= mask;
特定のビットを反転したい場合はXORを使います。
value ^= mask;
特定のビットをクリアしたい場合には、反転したマスクとANDを組み合わせる方法があります。
value &= ~mask;
目的に応じて演算子を使い分けることが重要です。
XORとORの違い
両方が1の場合の結果が異なる
ORとXORは似ていますが、両方のビットが1の場合の結果が異なります。
| A | B | OR | XOR |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 1 |
| 1 | 0 | 1 | 1 |
| 1 | 1 | 1 | 0 |
ORでは、
1 OR 1 = 1
ですが、XORでは、
1 XOR 1 = 0
です。
C++ではORを、
a | b
XORを、
a ^ b
と書きます。
XORとANDの違い
ANDは両方が1の場合だけ1になる
ANDでは、両方のビットが1の場合のみ結果が1になります。
| A | B | AND | XOR |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 0 |
C++ではANDを、
a & b
XORを、
a ^ b
と記述します。
ANDはビットの確認や抽出、XORはビットの反転などに向いています。
bool型でXORを使う方法
bool同士でも^を使用できる
bool型の値に対して^を使用すると、結果として論理XORのような動作になります。
#include <iostream>
int main() {
bool a = true;
bool b = false;
bool result = a ^ b;
std::cout << std::boolalpha << result << '\n';
}
実行結果は、
true
です。
一方だけがtrueであるため、XORの結果も真になります。
boolの^では整数昇格が行われる
注意したいのは、bool同士に^を使った場合、式そのものの型が必ずしもboolになるわけではない点です。
たとえば、
bool a = true;
bool b = false;
auto result = a ^ b;
とした場合、組み込みのビット演算では整数昇格が行われるため、resultはintになります。
結果の値は0または1です。
次のようにboolへ代入すれば、
bool result = a ^ b;
0がfalse、1がtrueへ変換されます。
bool同士なら!=でも表現できる
2つのboolについて「値が異なっていればtrue」としたい場合は、
bool result = a != b;
と書くこともできます。
bool値に限れば、
a ^ b
と、
a != b
は真偽結果として同じになります。
ただし、
auto x = a ^ b;
auto y = a != b;
では、xはint、yはboolになるため、式の型まで同じではありません。
単純な論理条件として使用する場合には、!=のほうが意図を読み取りやすいケースもあります。
C++には論理XOR専用演算子がない
^はビット単位XOR演算子
C++には、
&&
による論理ANDや、
||
による論理ORがあります。
しかし、これらに相当する論理XOR専用の演算子はありません。
^やxorは、組み込み型では基本的にビット単位の排他的論理和を行う演算子です。
なお、組み込みの^では整数型だけでなく、スコープなし列挙型も対象になります。
xor_eqとは
xor_eqは^=の代替表記
C++には、^=の代替表記としてxor_eqも用意されています。
a ^= b;
と、
a xor_eq b;
は同じ意味です。
たとえば、
int a = 5;
a ^= 3;
と、
int a = 5;
a xor_eq 3;
は同じ計算を行います。
実際のコードでは^=のほうが一般的ですが、xor_eqも正式なC++構文です。
XORを使って重複していない値を探す
1つだけ異なる値を取得できる
XORの性質は、アルゴリズムでも利用されます。
たとえば、「すべての整数が2回ずつ登場し、1つだけ1回しか登場しない」という配列を考えます。
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {4, 1, 2, 1, 2};
int result = 0;
for (int value : numbers) {
result ^= value;
}
std::cout << result << '\n';
}
実行結果は、
4
です。
XORでは、
1 ^ 1 == 0
2 ^ 2 == 0
となります。
そのため、
4 ^ 1 ^ 2 ^ 1 ^ 2
は、交換法則や結合法則を利用すると、
4 ^ (1 ^ 1) ^ (2 ^ 2)
と考えられます。
結果として、
4 ^ 0 ^ 0
となり、4だけが残ります。
XORを使って値を交換する方法
一時変数なしで整数を交換できる
XORの性質を利用すると、一時変数を使わずに2つの整数を交換できます。
int a = 10;
int b = 20;
a ^= b;
b ^= a;
a ^= b;
処理後は、
a = 20
b = 10
となります。
実用コードではstd::swapを使う
XORによる値交換は仕組みを理解する例としては興味深いものの、通常のC++コードでは推奨されません。
現代のC++では、
#include <utility>
std::swap(a, b);
と書いたほうが読みやすく、意図も明確です。
また、XORによる交換は同じオブジェクトを交換しようとすると問題が発生します。
たとえば、
x ^= x;
を実行すると、xは0になります。
そのため、実際のプログラムではstd::swapを利用するのが基本です。
XORによる可逆変換
同じ値でもう一度XORすると元に戻る
XORには、
(data ^ key) ^ key == data
という性質があります。
たとえば、
#include <iostream>
int main() {
int data = 42;
int key = 123;
int transformed = data ^ key;
int restored = transformed ^ key;
std::cout << restored << '\n';
}
実行結果は、
42
です。
この性質によって、XORを使った可逆的なデータ変換ができます。
ただし、単純に固定値とXORするだけでは安全な暗号方式にはなりません。
セキュリティが必要な用途では、十分に検証された暗号アルゴリズムやライブラリを使用する必要があります。
^をべき乗と間違えないようにする
C++の^はべき乗演算子ではない
C++初心者が特に注意したいのが、^をべき乗演算子だと勘違いするケースです。
たとえば、
2 ^ 3
は「2の3乗」ではありません。
2進数にすると、
2 = 10
3 = 11
なので、
10 XOR 11 = 01
となり、結果は1です。
べき乗にはstd::powなどを使用する
べき乗を計算したい場合は、用途に応じてstd::powなどを利用します。
#include <cmath>
double result = std::pow(2.0, 3.0);
この場合、結果は8.0です。
ただし、整数だけで厳密な整数演算を行いたい場合には、用途に応じて整数向けの実装を検討することもあります。
xorを使うときの注意点
演算子の優先順位を意識する
ビット演算子を複数組み合わせる場合は、演算子の優先順位に注意が必要です。
たとえば、
int result = (a ^ b) & mask;
のように括弧を付けることで、どの演算を先に行うのか明確になります。
複雑な式では、演算子の優先順位を暗記するよりも、括弧を使って意図を明示したほうが読みやすいコードになります。
ビット操作ではunsigned型も検討する
ビットパターンそのものを扱う場合は、
unsigned int
などの符号なし整数型を利用すると、処理の意図を明確にしやすくなります。
たとえば、
unsigned int flags = 0b0010;
unsigned int mask = 0b0100;
flags ^= mask;
のように記述できます。
特にシフト演算など、ほかのビット演算と組み合わせる場合は、符号付き整数と符号なし整数の違いも意識するとよいでしょう。
C++のxorを理解するポイント
C++のxorについて、まず覚えておきたいポイントは次のとおりです。
xorは^の代替表記である- XORでは異なるビットが
1、同じビットが0になる a ^ aは0になるa ^ 0はaになる- 同じ値を2回XORすると元の値に戻る
^は特定ビットの反転に利用できるxor_eqは^=と同じ意味である^はべき乗演算子ではない
実際のC++コードでは、
a ^ b
という記号表記が一般的です。
一方、
a xor b
も正式なC++の構文であり、追加のヘッダーを読み込む必要はありません。
XORは、ビットフラグの反転、状態の切り替え、重複データを扱うアルゴリズムなど、さまざまな場面で利用されます。
特にビット操作を理解するうえでは、ANDやORと並んで押さえておきたい重要な演算です。
以上、C++のxorについてでした。
最後までお読みいただき、ありがとうございました。
