C言語のビット演算について調べているけど、良く分からないな…
いくつか種類があるみたいだけど、分かりやすく説明して欲しい🤔
今回は、こちらの悩みを解決します。
本記事の内容
・そもそも、ビットとは?
・C言語におけるビット演算子とは?
・論理演算子を解説
・シフト演算子を解説
・複合代入演算子を解説
この記事を書いている自分は情報系大学生で、三ヶ月ほどでC言語の基礎を完璧に理解しました。勉強中に分からないことが多く、一日中プログラミングしていたこともあります。
今では、ブログでC言語について解説しています。
今回はそんな経験をもとに解説していこうかと。
当サイトでは、覚えるべきところを全てまとめているので、画面を撮影して何度も読み返すことをおすすめします。
それでは、解説していきます!
※注意:かなり長いです…
そもそもビットとは?
ビットについて、簡単にまとめます。
✔ビットとは
- 1ビットは、「0」か「1」のどちらかが入る小さな箱
- 1バイト=8ビット
- 1バイトで表現できる情報は256個まで(8桁の2進数で表現するため)
つまり、8ビット(1バイト)というのは、8つの「0」と「1」で表現された数字であり、8桁の2進数であると言えます。
そのため、8ビットで表現できるのは、256通りに制限されます。
この辺をおさえておけばOK
解説する際に、前後の変化を分かりやすくするため、10進数を2進数に変換するプログラムを使います。
✔10進数を2進数に変換するプログラム
- データ型に「unsigned char」を使う
- 計算して2進数として得られた結果は、1桁ずつ配列に代入
データ型にunsigned charを使う理由は、ビット演算では、8ビットの変数でないと正しい結果は得られないためです。
配列が分からない方は下の記事をそうぞ~
10進数を2進数に変換するプログラム
#include<stdio.h>
#define N 8
int main()
{
unsigned char a, b, c;
int i, l[N], m[N], n[N];
a = 53;
b = 96;
for (i = 0; i < N; ++i)
{
l[i] = a % 2;
m[i] = b % 2;
n[i] = c % 2;
a /= 2;
b /= 2;
c /= 2;
}
printf("aの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d",l[i-1]);
}
printf("¥nbの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", m[i-1]);
}
printf("¥ncの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", n[i-1]);
}
}
※この段階では「c」に何も代入していないのでエラーが出てしまいます。
C言語におけるビット演算子とは?
ビット演算子とは、ビットを計算したり、操作するための演算子です。
ビット演算子は全部で3種類あります。
✔ビット演算子の種類
- 論理演算子
- シフト演算子
- 複合代入演算子
上から順に解説していきますね。
✔余談:3種類全て覚えましょう
複合代入演算子は論理演算子とシフト演算子のまとめみたいなものです。
複合代入演算子を覚えれば、他の2つを覚えなくていい!
という考えは間違っています。
論理演算子、シフト演算子でしかできないことがあるので、これら3種類全部覚えましょう。
【ビット演算】論理演算子
論理演算子の使い方と意味
まず初めに、これらがビット演算子で使われる記号です。
演算子 | 演算名 | 意味 |
---|---|---|
& | 論理積 | 2つの値の論理積 |
| | 論理和 | 2つの値の論理和 |
^ | 排他的論理和 | 2つの値の排他的論理和 |
~ | 1の補数 | ある値のビットを全て反転 |
上から順に解説していきますね。
【論理演算子】論理積(&)とは?
論理積とは、どちらも1の桁のみが1になり、それ以外の桁は0になる演算ですね。
どちらも1が代入されている➡1
それ以外➡0
ということですね。
「&」は「2つの値の論理積をとる」という意味で、例えば
c = a & b;
は「a」と「b」の値の論理積を「c」に代入するという意味ですね。
【論理演算子】論理積を使ったサンプルコード
#include<stdio.h>
#define N 8
int main()
{
unsigned char a, b,c;
int i,l[N], m[N], n[N];
a = 53;
b = 96;
c = a & b;
for (i = 0; i < N; ++i)
{
l[i] = a % 2;
m[i] = b % 2;
n[i] = c % 2;
a /= 2;
b /= 2;
c /= 2;
}
printf("aの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", l[i - 1]);
}
printf("¥nbの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", m[i - 1]);
}
printf("¥ncの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", n[i - 1]);
}
}
これを出力してみます。
3桁目のみ、Cには1が代入されていますね。aとbどちらにも1が代入されているからですね。
【論理演算子】論理和( | )とは?
論理和とは、どちらも0の桁のみが0になり、それ以外の桁は1になる演算ですね。
「 | 」とは、「2つの値の論理和を取る」という意味です。
例えば、
c = a | b;
は「a」と「b」の値の論理和を「c」に代入するという式ですね。
【論理演算子】論理和を使ったサンプルコード
#include<stdio.h>
#define N 8
int main()
{
unsigned char a, b,c;
int i,l[N], m[N], n[N];
a = 53;
b = 96;
c = a | b;
for (i = 0; i < N; ++i)
{
l[i] = a % 2;
m[i] = b % 2;
n[i] = c % 2;
a /= 2;
b /= 2;
c /= 2;
}
printf("aの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", l[i - 1]);
}
printf("¥nbの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", m[i - 1]);
}
printf("¥ncの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", n[i - 1]);
}
}
これを出力してみます。
「c」で0が表示される桁は「a」と「b」のどちらでも0が表示されている場合で、他の桁は全て1ですね。
【論理演算子】排他的論理和(^)とは?
排他的論理和とは、2つの値が異なるなら1、2つの値が同じなら0になる演算ですね。
「^」は「2つの変数の排他的論理和を取る」という意味です。
例えば、
c = a ^ b;
は、「a」と「b」の値の排他的論理和を「c」に代入するという式です。
【論理演算子】排他的論理和を使ったサンプルコード
#include<stdio.h>
#define N 8
int main()
{
unsigned char a, b,c;
int i,l[N], m[N], n[N];
a = 53;
b = 96;
c = a ^ b;
for (i = 0; i < N; ++i)
{
l[i] = a % 2;
m[i] = b % 2;
n[i] = c % 2;
a /= 2;
b /= 2;
c /= 2;
}
printf("aの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", l[i - 1]);
}
printf("¥nbの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", m[i - 1]);
}
printf("¥ncの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", n[i - 1]);
}
}
「c」で1が表示される桁は「a」と「b」の値が異なる場合で、逆に「a」と「b」の値が同じ場合は0になりますね。
【論理演算子】全てのビットを反転する(~)
ビットを反転するとは、1つひとつの桁の値が反転するという意味ですね。(0➡1、1➡0のように0と1が入れ替わるということです)
「~」は全てのビットを反転する演算子です。
※この演算子は他の3つと異なり、1つの値があれば使用できます。そのため、今まで使用していた変数「c」は使用せず、「a」のビットを反転した結果を「b」に代入します。
例えば、
b = ~a;
は「a」のビットを反転して「b」に代入するという式ですね。
【論理演算子】全てのビットを反転するサンプルコード
#include<stdio.h>
#define N 8
int main()
{
unsigned char a, b, c;
int i, l[N], m[N], n[N];
a = 53;
b = ~a;
for (i = 0; i < N; ++i)
{
l[i] = a % 2;
m[i] = b % 2;
a /= 2;
b /= 2;
}
printf("aの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", l[i - 1]);
}
printf("¥nbの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", m[i - 1]);
}
}
「a」で0の桁は「b」では1になり、「a」で1の桁は「b」では0になっていることが分かるかと思います。
【ビット演算】シフト演算子
シフト演算子の使い方と意味
まず初めに、これらがシフト演算子で使われる記号です。
演算子 | 演算名 | 意味 |
---|---|---|
×>>〇 | 右シフト | ×を右に〇ビットシフトする |
×<<〇 | 左シフト | ×を左に〇ビットシフトする |
この2つの演算子の違いは、右と左どちらにシフトするかなので、まとめて解説します。
”シフトする”とは?
ビット演算子では、ビットをずらすことを「シフトする」と呼びます。
例えば、
11001001
という8ビットの値を右に2ビットシフトすると、
00110010
のようになります。
全ての桁が2ビットずつ右にずれていますね。
※このとき、一番左の2ビットには0が入ります。(元々、左側の桁が存在しないからです。)
シフト演算子を使ったサンプルコード
8ビットの値を右に2ビットシフトするプログラムです。
✔シフト演算子を使ったサンプルコード
・「a」を右に2ビットずらした値を「b」に代入
・「a」と「b」を2進数に直して出力
最後に、2つの違いを比較しましょう。
#include<stdio.h>
#define N 8
int main()
{
unsigned char a, b;
int i, l[N], m[N], n[N];
a = 53;
b=a >> 2;
for (i = 0; i < N; ++i)
{
l[i] = a % 2;
m[i] = b % 2;
a /= 2;
b /= 2;
}
printf("aの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", l[i - 1]);
}
printf("¥nbの2進数表示:");
for (i = N; i > 0; --i)
{
printf("%d", m[i - 1]);
}
}
「b」の左側の2ビットには「0」が代入され、「b」は「a」を右に2ビットシフトした結果であることが分かりますね。
【ビット演算】複合代入演算子
複合代入演算子の使い方と意味
複合代入演算子の使い方と、演算名と、意味についてまとめてみます。
使い方 | 演算名 | 意味 |
---|---|---|
a&=b | 論理積 | a=a&b |
a |=b | 論理和 | a=a | b |
a^=b | 排他的論理和 | a=a^b |
a>>=2 | 右シフト | a=a>>2 |
a<<=2 | 左シフト | a=a<<2 |
たとえば「a&=b」の意味が「a=a&b」というのは、aとbの論理積をaに代入しているということですね。
✔余談:a+=bと似ている話
一時期、a+=bというものを勉強したかと思いますが、似ていますよね。
実は、あれも複合代入演算子という名前が付いています。ビット演算で使用する複合代入演算子とは異なるので注意!
複合代入演算子ではできないこと
便利な複合代入演算子ですが、出来ないことがあります。
✔複合代入演算子ではできないこと
- a=b&c
- すべてのビットを反転する演算
すべてのビットを反転するには、「~」を使う以外の方法はありません。「~」は論理演算子の一つです。
先ほど紹介した記事で使い方を紹介しているので、参考にしてみてください。
複合代入演算子を使ったサンプルコード
サンプルコードを用意しました。
#include<stdio.h>
#define N 8
int main()
{
unsigned char a1,a2, b;
int i, l[N], m[N],n[N];
a1 = 53;
a2 = a1;
b = 96;
a2 &= b;
for (i = 0; i < N; ++i)
{
l[i] = a1 % 2;
m[i] = b % 2;
n[i] = a2 % 2;
a1 /= 2;
b /= 2;
a2 /= 2;
}
printf(" a1:");
for (i = N; i > 0; --i)
{
printf("%d", l[i - 1]);
}
printf("¥n b:");
for (i = N; i > 0; --i)
{
printf("%d", m[i - 1]);
}
printf("¥na1&=b:");
for (i = N; i > 0; --i)
{
printf("%d", n[i - 1]);
}
printf("¥n");
}
最初は、
a2 = a1;
となって同じ値になりますが、
a2 &= b;
で複合代入演算子を使用していますね。
つまり、「a1」と「a2」の変化を比べることができます。
また、上手く配列とfor文を使って2進数表示しているところもポイント!
それでは、出力してみましょう。
まとめ:ビット演算について0から解説する
✔ビット演算子の種類
- 論理演算子
- シフト演算子
- 複合代入演算子
もう一度言いますが、ビット演算子は3種類覚えましょうね~
確かにビット演算子は種類が多く、分かりずらいことが多いです。
そういうときは、このサイトでやっているように、2進数表示して計算後の変化を可視化して理解しましょう。
今回は以上です!
次は、条件演算子を勉強しましょう!
ではでは~👋
コメント