Perl プログラミング - 基本的な変数の使い方
PROGRAM
基本的な変数の使い方。
目次
- 変数とは … どういったものを変数と言うのかのお話です。
- 変数を使ってみる … 実際にプログラムで変数を使ってみるお話です。
- 変数を操作する … 計算などをするための演算子に関するお話です。
- 変数の有効範囲 … 変数には、その変数を使える場所、と言うものが存在します。
- 使う変数を指定する … 打ち間違えなどで困ったことにならないようにするお話です。
- 使用できるリテラル … 変数に代入したりする際に使用するリテラル (定数) についてのお話です。
- 特殊変数について … 特別な意味を持つ変数やリテラルについての簡単なお話です。
- ビット演算 … パソコンにおける事実上の最小単位、ビットに関するお話です。
- 型グロブ … 同じ名称の変数を一網打尽、そんな感じのお話です。
変数とは
変数とは、値をしまっておく入れ物、といったら良いでしょうか…。
多くの方が義務教育の中で数学 (代数学) を学んでいると思います。そのとき、"x に 10 を代入して…" とか、"y = (5 + 3) * x" という式の y の値を手順を追って計算したりした記憶があるのではないでしょうか。このときの、x や y がまさに変数です。プログラムでは、変数を使って、式やその値をいろいろと変えていくことで、いろいろな処理を行います。
あまりイメージが沸かないかもしれませんけど数学で例えるなら、たとえば三角形の面積を求めるためには次のような式を組み立てると思います。
S = (a * h) / 2
これは、底辺を a として、高さを h としたときの、三角形の面積 S を求める式です。
プログラム的に解釈すると、底辺と高さをそれぞれ変数 a と h に渡してあげることで、変数 S に三角形の面積を得られる、という感じになります。このような式が組み立てられれば、後はいろいろな三角形について面積を求めることが出来るようになります。
上記の例では変数の中身はそれぞれひとつの数字です。これが一番簡単な変数の形であり、Perl ではこれを "スカラー変数" と呼びます。スカラー変数には数値のみならず文字列も保存することができます。
変数の種類はこれだけではなく、Perl には他にも "配列" と "HASH" というものも存在します。ひとつの変数で一度にたくさんの値を取り扱えたりするので慣れないうちは難しいかもしれないですけど、プログラミングをする上では欠かせない存在です。たとえば数学にも、配列とかベクトルとかいうように、ひとつの変数にたくさんの値が入ってしまうものがありますよね。まあこちらは普通に生活するうえでは無くても問題ないような気もしますけど、より踏み込んだ数学を行うには無くてはならないものだと思います。
変数を使ってみる
先ほども述べたように、変数にはいくつか種類が存在するのですけど、スカラー変数が何においても基本となっているのでここではスカラー変数についてのお話を中心に進めてみます。それ以外の変数の細かな性質などについてはまた別の機会に触れます。
Perl では変数にはアルファベットで好きな名前を指定することができます。そしてその名前の前に特定の記号をつけて、その変数がスカラーとか配列とかといった、どの種類のものであるかも指定します。その特定の記号は、スカラーなら "$" 、配列なら "@" 、HASH なら "%" となります。たとえばそれぞれにおいて variable という名前を使う場合、感じとしては次のようになります。
# スカラー変数の場合
$variable
# 配列変数の場合
@variable
# HASH 変数の場合
%variable
これだけでは何にもならないのですけど、とりあえずこのような感じの単語というか文字列が出てきたら、それは変数であるということを覚えておきましょう。なお、これらの変数、名前はみんな同じ "variable" ですけどそれぞれ別の変数となります。たとえば同じ "佐藤くん" でも、種類 (人) が違えばまったくの別人ですよね。
なお、変数名の名前の付け方は、基本的にアルファベットの大文字と小文字となります。ただしアンダーバーを含めてもよく、さらに2文字目からは数字も名前に含めることができます。アルファベットの大文字と小文字は区別されるので、たとえば $a と $A は別のものとして取り扱われます。
では、ここからはスカラー変数について、実際にどのように使うかを見ていきます。
Perl ではスカラー変数に、数値と文字列を自由に代入することができます。どちらともたいした違いは無いのですけど、たとえば数値の 100 を代入してみようと思います。代入文の書き方は、まず最初に代入したい変数を書いて、続けて "=" を、そしてその右側に代入したい値を書くことになります。Perl では文末はセミコロン ( ; ) をつけるという約束事があるので、それも忘れないように注意します。
# スカラー変数 variable に、数値 100 を代入する。
$variable = 100;
これで、スカラー変数 $variable に 100 という値が代入されました。
続いて文字列を代入する例も見てみましょう。
Perl では、文字列は引用符でくくる必要があるのですけど、使用する引用符はシングルクォーテーション ( ' ) とダブルクォーテーション ( " ) の二通りがあります。二つには大きな違いがあるのですが、細かくは下でまた述べることにします。
とりあえずは、「変数を展開したいならばダブルクォーテーション、そうでなければシングルクォーテーション」 という感じになるかと思います。まだ説明していないのでわからなくても自然ですけど、わからなければ、というか、特に必要がなければシングルクォーテーションが個人的にお勧めです。ですので、今回の例ではシングルクォーテーションを使ってみることにします。
# スカラー変数 variable に、文字列 "string" を代入する。
$variable = 'string';
このように、代入したい文字列の両側に引用符をつけてあげれば大丈夫です。このあたりについてはまた、"リテラル" 関連のお話のところで整理します。
変数を操作する
変数もただ値をしまっておくだけではもったいないですので、保存した値をいろいろと取り扱う方法を見てみましょう。
プログラムでは一般的に、変数の値をいろいろと操作することを "演算" といいます。その演算を、どのように行うかを示した記号を "演算子" と言います。言葉ではなんだか難しいかもしれないですけど、たとえば 「りんごが3つ、みかんが2つ。合計いくつ?」 という問題の場合、3個と2個を足す行いが演算で、足し算ですから "+" が演算子となります。
演算の書き方は、数学のときとほぼ同じです。
# 全部でいくつかを $sum に保存するとします。
# りんごが3つ
$ringo = 3;
# みかんが2つ
$mikan = 2;
# 合計いくつ?/ ここが今回の本題の "演算" です!
$sum = $ringo + $mikan;
# 一応…、画面に計算結果を表示しておきます…。
print "合計 $sum 個。";
一連したプログラムとして書いてみましたけど、今回の重要なところは次の一行です。
$sum = $ringo + $mikan;
$ringo と $mikan の値を足してそれを $sum に保存するという演算です。処理の流れとしては、まず右辺 "$ringo + $mikan" が計算されて、その結果が左辺に指定された変数 $sum に保存されるという感じになります。注意する点としては、いわゆる代入文になっているところでしょうか。代入せず単純に "$ringo + $mikan;" だけだと、計算結果が記録されずに消えてしまうので気をつけましょう。
さて、「りんごが3つありました。そこに新たに2つ、りんごを買い足しました。」 というのをプログラムで書いてみましょう。
今回は先ほどのように合計を求めるというのとは少し意味合いが違います。りんごの個数を $ringo という変数に保存しておくわけなのですけど、最初の $ringo の個数は 3 つ。それに 2 つ買い足すわけですから、最終的には $ringo の個数が 5 となっているのが理想です。
これをプログラムで書くと次のようになります。
# りんごが3つありました。
$ringo = 3;
# そこに新たに2つ、りんごを買い足しました。
$ringo = $ringo + 2;
# 一応…、画面に計算結果を表示しておきます…。
print "りんごは $ringo 個になりました。";
重要となるところは次の一行です。
$ringo = $ringo + 2;
このあたりから、少し慣れてこないと難しくなってくるのではないかと思います。
数学的に、一般的にこの式を見ると、「$ringo と $ringo + 2 は等しい」 と言っているようですけど、決してそう言っているわけではありません。Perl では、上でさらっと説明してきたように、 "=" という記号は比較ではなく、右辺の値を左辺に代入すると言う意味になっています。上までであまり疑問を持たなかった方が多いかと思いますけど、それは、変数名が違っていたために、最終的に等しくなったからではないでしょうか。
この式の解釈は次のようになります。
先ほど述べたプログラムの処理の流れを思い出してほしいのですけど、まず右辺が計算された後、左辺にその結果が代入されます。つまり、右辺と左辺は最終的には繋がるものの、基本的にはそれぞれ独立しているのです。ですので、まずは右辺…、$ringo に保存されている現在のりんごの個数 3 に 2 を足します。この結果は 5 ですよね。
そしてこの結果を左辺の $ringo に代入すると…、最終的には $ringo の値は 5 になりました。
さて、りんごの個数のお話ですけど、 Perl の場合は次のような書き方もあります。
$ringo += 2;
見慣れない形で最初は何のことかわからないかもしれないですけど、これは 「左辺に指定した変数の値に、右辺の値を加える。」 という演算子です。先ほどのりんごのお話の場合、「現在のりんごに、2つ買い足した。」 という意味合いですので、これを使った方がプログラムの意味的にもしっくり来ると思います。
# りんごが3つありました。
$ringo = 3;
# そこに新たに2つ、りんごを買い足しました。
$ringo += 2;
# 一応…、画面に計算結果を表示しておきます…。
print "りんごは $ringo 個になりました。";
いかがでしょう。
やっていることは先ほどとまったく一緒なのですけど、表記が変わりました。はじめはなんだか不思議な感じがするかもしれないですけど、慣れてくると、ぱっと見たときにこちらの方が、「変数の値に 2 を加えるのね!」 と一目で理解できるのでお勧めです。
このような感じで、変数を操作することができます。上で紹介した演算子は、代入演算子 "=" と、算術演算子の "+" と "+=" だけですけど、これ以外にも演算子はいくつかありますので、その中で主に変数の計算に使用する演算子を紹介してみます。
■ 算術演算子
演算子 | 例 | 意味 |
---|---|---|
+ | $a + 1 | $a に 1 を足し合わせた値を計算します。 |
- | $a - 2 | $a から 2 を引いた値を計算します。 |
* | $a * 3 | $a に 3 をかけあわせた値を計算します。 |
/ | $a / 4 | $a を 4 で割った値を計算します。 |
% | $a % 5 | $a を 5 で割った余りを求めます。 |
** | $a ** 6 | $a を 6 乗した値を計算します。 |
. | $a . 'string' | $a に文字列 string を連結した文字列を取得します。 |
■ 代入演算子
演算子 | 例 | 意味 |
---|---|---|
= | $a = 1 | $a に 1 を代入します。 |
+= | $a += 2 | $a の値に 2 を加えます。$a = $a + 2 と同等です。 |
-= | $a -= 3 | $a の値から 3 を引きます。$a = $a - 3 と同等です。 |
*= | $a *= 4 | $a の値に 4 をかけます。$a = $a * 4 と同等です。 |
/= | $a /= 5 | $a の値を 5 で割ります。$a = $a / 5 と同等です。 |
%= | $a %= 6 | $a の値を、元の値を 6 で割った余りとします。$a = $a % 6 と同等です。 |
**= | $a **= 7 | $a の値を 7 乗します。$a = $a ** 7 と同等です。 |
.= | $a .= 'string' | $a に文字列 string を連結します。$a = $a . 'string' と同等です。 |
++ | $a++ | $a の値を取得した後で、$a の値に 1 を加えます。 |
++$a | $a の値に 1 を加え、その値を取得します。$a += 1 と同等です。 | |
-- | $a-- | $a の値を取得した後で、$a の値から 1 引きます。 |
--$a | $a の値から 1 を引いて、その値を取得します。$a -= 1 と同等です。 |
これらの演算子は一度に複数利用することが出来ます。
$a = (10 + $b / 2) * 3 + $c**2;
計算のされ方は、算数のときに習ったのとほぼ一緒です。括弧が優先され、累乗、乗除と計算されて、加減算…、というような流れになります。なお剰余を求める "%" は掛け算とかと同じ優先度となっているようです。また ++ や -- は、累乗よりもひとつ高い優先順位です。
それと、演算子 "++" と "--" が二通りの使い方があるので説明しておきます。
この演算子は変数名にくっつけて利用し、それぞれ、その変数の値に 1 を加えたり引いたりすることが出来ます。ただし、くっつける方向によって意味がわずかに変わってくるのでした。
たとえば $a++ と ++$a 。
どちらも最終的には $a の値に 1 が加わるのですけど、その途中での値に違いが出てきます。といってもわかりにくいと思うのですが、たとえば計算の途中で、$a++ とか ++$a に出会ったとします。そのとき、その値はいくつか、そこに違いが出てきます。最初に演算子が現れる ++$a の方は、$a の値に 1 が足されたものが、++$a の値となります。ところが、$a++ の方は、足される前の $a の値が、$a++ の値として利用されるのでした。念を押しておきますけど、最終的にはどちらも同じように $a の値に 1 が足され、保存されます。
これが良く見て取れる例が、 print 命令を使ったプログラムです。
print 命令をつかって $a++ と ++$a と、$a そのものの値の表示を行って比較すると、その途中の差が見えてきます。
# まずは $a++ での実験です。最初に $a の初期値は 1 とします。
# 結果は 1 -> 2 です。
$a = 1;
print $a++;
print $a;
# 続いて ++$a での実験です。こちらも最初に $a の初期値を 1 とします。
# 結果は 2 -> 2 です。
$a = 1;
print ++$a;
print $a;
上のプログラムでは改行を挟んでいないので実際に実行すると見にくいのですけど、最初の $a++ の実験では、1 → 2 の順番で表示されます。そして次の ++$a の実験では二つとも 2 が表示されるのでした。
最初の方は ( $a++ ) として見たときには 1 を足される前の値なので、まずは 1 が表示されます。そして実際には $a の値がひとつ増加しているわけですから、次の print の時にはその値が表示されます。もうひとつのほうは ( ++$a ) としてみたとき既に 1 が足されたものとされているので、最初から 2 が表示されます。
ビット演算
パソコンは、いわゆるスイッチの、ON/OFF の集まりで数字を表現しています。
スイッチひとつだと ON か OFF の二通り。もうひとつスイッチを増やすと ON & ON 、ON & OFF、 OFF & ON 、 OFF & OFF の四通り。さらに増やすと…、とやっていくと、数学の数列を覚えているとわかりやすいのですけど、スイッチの個数を n 個とすると、2 の n 乗の組み合わせ (正確には "順列") だけ、スイッチの状態を変えることが出来ます。これを利用して、パソコンは数値を記憶しています。
ちなみに余談ですけど、スイッチ 8 個で 256 通り、スイッチ 16 個で 65,536 通りという、何かと同じみな数字となります。また、2 の 10 乗は 1024 なので、パソコンではこれを一区切りにしたりします。 1kB = 1,024 Byte なんていうのも見たことある人がいるのではないでしょうか。
このスイッチのひとつひとつを、"ビット" と呼びます。そしてこれがパソコンの値を構成する最小単位であり、これを操作する演算がビット演算です。
さて実際にどういった感じで動くかを知るには、二進数というものを知っておくのがいいのでした。通常、世間一般に使われているのは十進数です。十進数というのは、一桁に 0 から 9 までの 10 通りの数字が入る数字のことです。普通、0 からはじまって 9 までは一桁、その次は 10 と二桁になりますよね。単純に生まれてからずっとこれを使っているせいでしょうけど、人にとってはこれがとっても都合がいいのです。
けれどパソコンにとっては、先ほども述べたように、ON/OFF の集まりで数字をあらわしています。すなわち 0 と 1。スイッチがもうひとつ増えれば 00, 01, 10, 11 、もう一つ増えれば 000, 001, 010, 011, 100, 101, 110, 111 。このような感じであらわしてみると、なんだかひとつの位が 0 と 1 のみの数字にみえますよね。この考え方が2進数です。0, 1 と続いて、その次は桁が上がって 10 となる、そんな数です。慣れないうちは混同してしまうかもしれないですけど、十進数の 10 と二進数の 10 はぜんぜん違う値なので気をつけてください。二進数の 10 は 0 -> 1 の次、すなわち、十進数では 0 -> 1 -> 2 と、 2 に相当します。
さて、一般世間では数に限りと言うのは基本的に無いのですけど、パソコンの場合はスイッチの集まりですからその集めたスイッチ以上の数は表現することが出来ません。
たとえばスイッチが 8 個しかなければ、二進数で八桁、最大で 256 通りまでしか表現することが出来ないということになります。でも逆に言うと、0 の桁も含めて桁数が八桁に固定されるとも言えます。そしてこれが、ビット演算について時に重要だったりするのでした。
では、ビットの演算についてみてみましょう。
ただし、いきなり Perl の話に入るのではなく、"論理演算" と言うものについて、まずは見ていきます。論理演算とは、2 つのビットを掛け合わせたときにどうなるか、という感じのルールみたいなものです。AND とか OR とかいくつかルールが存在してそれに応じて相応のビットが算出されます。論理演算のルールについては EZ-NET ディクショナリ: 論理演算 にまとめてみましたのでそちらをご覧ください。
1 ビット単位ではなく、一般に複数桁の数値に対して論理演算を行う場合は、その数値を二進数とみたときの各桁 (各ビット) ごとに演算を行います。
各桁ごとに論理演算を行うので、基本的には桁数が揃っている必要があります。ただし、先ほども述べたように重要なのは見た目の桁数ではなくて、スイッチの数ですので、普通は、といいますか Perl では特に、スカラー変数同士は同じ器ですから、気にしなくて大丈夫です。
ではいよいよ、実際にどのようなビット演算があるのかを見てみましょう。
■ ビット演算子 (論理演算)
演算子 | 例 | 意味 |
---|---|---|
& | $a & 1 | $a と 1 との論理積 AND を取得します。 |
| | $a | 2 | $a と 2 との論理和 OR を取得します。 |
~ | ~$a | $a の否定 NOT を取得します。 |
これらは、2つの数値をそれぞれを二進数と見立てて、それぞれの桁 (ビット) 同士に論理演算を行うというものです。このほかにももう少し、ビット関連の演算があるので紹介してみます。
■ ビット演算子 (シフト演算)
演算子 | 例 | 意味 |
---|---|---|
<< | $a << 1 | $a のビットを左へ 1 つずらした値を取得します。あふれた部分はカットされ、右側は 0 で埋められます。 |
>> | $a >> 2 | $a のビットを右へ 2 つずらした値を取得します。入りきらなくなった部分はカットされ、左側は 0 で埋められます。 |
■ ビット演算子 (代入演算)
演算子 | 例 | 意味 |
---|---|---|
&= | $a &= 1 | $a と 1 の論理積を求め、それを $a の新たな値とします。$a = $a & 1 と同等です。 |
|= | $a |= 2 | $a と 2 の論理和を求め、それを $a の新たな値とします。$a = $a | 1 と同等です。 |
<<= | $a <<= 3 | $a のビットを左へ 3 つずらします。$a = $a << 1 と同等です。 |
>>= | $a >>= 4 | $a のビットを右へ 4 つずらします。$a = $a >> 2 と同等です。 |
さて、これらはどういうときに使うかと言うと、各ビットに役目を決めてそれを取り出したり設定したりというときなどに重宝します。たとえば、あまり実用的ではないですけど、本のページ番号を記録するための変数を用意して、それぞれのビットに次のような役割を決めたとしましょう。
ビット | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
決めた役割 | 書庫番号 | 本の番号 | ページ番号 | 参照フラグ | ||||
その桁に 相当する十進数 |
2**7 (128) |
2**6 (64) |
2**5 (32) |
2**4 (16) |
2**3 (8) |
2**2 (4) |
2**1 (2) |
2**0 (1) |
なお、ここではビット数は 8 ビットであるとします。そして、おまけですけど、その桁が十進数でいくつに相当するかも書いておきました。1 になっている部分の相当する値を足し合わせていくと、二進数から十進数に簡単に変換することが出来ます。 最初の書庫番号が、本が保存されている書庫を区別するための情報です。そして次の本番号は、その書庫に保管されている本を区別するための番号。ページ番号はその本の何ページ目かを記録する場所で、最後の参照フラグは、そのページを見たか見ないか、見たなら 1 、見ないなら 0 を設定するフラグ (チェックシートみたいな感じ) です。
今回は 8 ビットと桁が少ない前提なので 0 から 255 までの数値となるわけなのですけど、たとえばまず、次のように書庫番号を決めてみましょう。なお、何かとそれぞれにおいて、ビットがすべて 0 のものは除外しておいた方がいい場合が多い気がするので、ここでもそうしておきます。
書庫 | 値 (十進数) | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
LIB_A | 64 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
LIB_B | 128 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
LIB_C | 192 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
さて、本の番号は、各書庫でそれぞれ 1 から 7 までの通し番号とします。0 は前提の通り除外するとして、なぜ 7 までかは次の表をご覧ください。理由としては、本の番号に使用できるビットが 3 つだからです。なお、書庫番号を示す部分は 0 としてあります。
本の番号 | 値 (十進数) | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
1冊目 | 8 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
2冊目 | 16 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
3冊目 | 24 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 |
4冊目 | 32 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
5冊目 | 40 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
6冊目 | 48 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
7冊目 | 56 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 |
ページ番号のほうは 2 ビットなので、 1 から 3 ページまで、を指定することが出来ます。
さて、では LIB_A の1冊目という値を作ってみましょう。ポイントとなるのは、必要な情報以外は 0 でビットが埋められているというところです。書庫の情報ならば先頭の 2 ビット以外が 0 ですし、本番号の方は指定する際には必ず真ん中の 3 ビットだけで指定します。
LIB_A を意味する値は先ほどの表から、 64 です。そして本の1冊目は 8 。これらはお互い、使用しているビットが別ですのでひとつの値の中に混ぜてしまって問題ありません。混ぜるときには、論理演算のうちの論理和 (OR) を使用します。
# $value に結果を保存するとします。
my $value;
# 書庫番号 $library は 64 、本番号 $book は 1。
my $library = 64;
my $book = 8;
# 論理和でそれらを混ぜ合わせれば、両方の情報を含んだ値が得られます。
$value = $library | $book;
実際にどのような値になるのか、表で見てみましょう。
値 | 値 (十進数) | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
$library | 64 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
$book | 8 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
$library | $book | 72 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
論理和 OR の性質を利用して、また必要な部分以外が 0 となっていることを利用して、有効な値が設定されている部分が各々のビットに干渉することなく合成しています。十進数の値をみると単純に足した値ともなっているので、今回の場合は足し算でもいいのですけど、足し合わせているわけではなくて重ね合わせているという意味合いを強く出すためにも論理和で計算することをお勧めします。また、今回のようにきっちりと 分離している場合は、加算でも論理和でもこのようにどちらでも正しいのですけど、論理和には論理和ならではの使い方もしっかりとあります。それはまた少し下でお話します。
さて、書庫番号はあらかじめ決めたのでいきなり 64 でもそういうものと思える、少なくとも慣れてしまえばそう思えるのですけど、本番号の方は決めていないし、しかも通し番号なので、出来るならば 1 と指定した方がわかりやすく感じる場合もあると思います。そこで、シフト演算を使ってみることにします。シフト演算とはビットをずらす計算のことで、これを利用することで自由にビットをずらすことが出来ます。
ビットは、慣れないとなんだか小分けされた区分のようでわかりにくいかもしれませんけど、歴とした数値ですから、十進数で 1, 2, 3 と進めば、ビット (二進数) も 001, 010, 011 と進みます。ですので、本番号 1 から 7 を、 3 ビット左へ動かしてあげればそれが、今回の約束の中で使用できる値となります。もちろん 7 以下に抑えておかないと、書庫番号に干渉してしまうので注意です。
5冊目の場合 | 5 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|
3つ左へずらせば、本番号 | 40 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
では、これを利用して先ほどのプログラムを少し書き換えてみましょう。
また、書庫番号みたいなものは定数として文字で扱うと見易さが増すと思うのでそれもついでにそうしてみます。定数とはいっても実際は、わかりやすい変数名を用意して、それに該当する値をあらかじめ保存しておくという感じですけど。
# 書庫番号を文字で扱えるように、定義しておきます。
$LIBRARY_A = 64;
$LIBRARY_B = 128;
$LIBRARY_C = 256;
# $value に結果を保存するとします。
my $value;
# 書庫番号 $library は LIBRARY_A (64) 、本番号 $book は 1。
# 書庫番号の指定を (1 << 3) とすることで、少し複雑になってしまっていますけど、1 冊目であることが目で見えるようにしています。
my $library = $LIBRARY_A;
my $book = (1 << 3);
# 論理和でそれらを混ぜ合わせれば、両方の情報を含んだ値が得られます。
$value = $library | $book;
同じようにしてページ番号も指定してみましょう。今回は 2 ページを、先ほどの値に追加することにします。やりかたは今までと同じように論理和での結合です。1 ビット左へずらすのも忘れないようにしつつ、次のようにプログラミングを行います。
# 書庫番号を文字で扱えるように、定義しておきます。
$LIBRARY_A = 64;
$LIBRARY_B = 128;
$LIBRARY_C = 256;
# $value に結果を保存するとします。
my $value;
# 書庫番号 $library は LIBRARY_A (64) 、本番号 $book は 1。
# 書庫番号の指定を (1 << 3) とすることで、少し複雑になってしまっていますけど、1 冊目であることが目で見えるようにしています。
my $library = $LIBRARY_A;
my $book = (1 << 3);
# 論理和でそれらを混ぜ合わせれば、両方の情報を含んだ値が得られます。
$value = $library | $book;
# 論理和でさらに、ページ番号を含めています。直接 $value へ設定してみました。
# 2 ページという情報を、1 ビット左へずらして、それを論理和で $value に混ぜ合わせています。
$value |= (2 << 1);
値 | 値 (十進数) | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
元の値 | 72 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
2ページ | 4 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
混ぜ合わせ後 | 76 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
これでとりあえず、どの書庫のどの本の、そして何ページ目かまでが 76 という値の中に凝縮されました。
では最後のビット、そのページを見たか見ないか、それを扱う場面を見てみましょう。この部分が一番、論理演算が有効に活用できる場面です。今回では一番下のビットが 1 ならば、そのページは見た、逆に 0 であればそのビットは見ていないという風にします。さっそく、先ほどの値 76 に、見たことを示すビットをセットしてみましょう。
今回は今までのような数字ではなく目印と言う意味合いではありますけど、考え方は一緒です。見たことを示すビット 1 を数字と見立てれば、先ほどのようにプログラムを組むことが出来ます。
# 書庫番号を文字で扱えるように、定義しておきます。
$LIBRARY_A = 64;
$LIBRARY_B = 128;
$LIBRARY_C = 256;
# $value に結果を保存するとします。
my $value;
# 書庫番号 $library は LIBRARY_A (64) 、本番号 $book は 1。
# 書庫番号の指定を (1 << 3) とすることで、少し複雑になってしまっていますけど、1 冊目であることが目で見えるようにしています。
my $library = $LIBRARY_A;
my $book = (1 << 3);
# 論理和でそれらを混ぜ合わせれば、両方の情報を含んだ値が得られます。
$value = $library | $book;
# 論理和でさらに、ページ番号を含めています。直接 $value へ設定してみました。
# 2 ページという情報を、1 ビット左へずらして、それを論理和で $value に混ぜ合わせています。
$value |= (2 << 1);
# ビットの最後に見たことを示す 1 を重ね合わせます。
$value |= 1;
値 | 値 (十進数) | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
元の値 | 76 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
見た! | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
混ぜ合わせ後 | 77 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
このように今までとなんら変わることなく、値を設定することが出来ました。
値自体に注目しても、今まで通り、わざわざ論理和を使わなくても足し算で同じ結果が得られます。けれど、今回の場合はだからと言って足し算を使うのは間違いに繋がる場合が多いのです。たとえば今回の結果 77 、これを何らかの理由でもう一度、「見た!」 とマークする場合を考えて見ます。
値 | 値 (十進数) | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
元の値 | 77 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
見た! | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
論理和を使った場合 | 77 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
まずは論理和でマークをつけなおした場合です。論理和の性質から、このように既に 「見た!」 というマークがついているところへ再び重ね合わせることが出来ます。論理和を通した後の値は元の値と変わらず、しっかりと正しい値を維持することが出来ているのでした。ところが、加算にて行っていた場合は、1回目は正常な 77 が得られていても、2回目は当然 78 が得られることとなります。
値 | 値 (十進数) | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
元の値 | 77 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
見た! | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
論理和を使った場合 | 78 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 0 |
値が違うこと自体が問題ですけど、その意味合いをみると、もっと重大であることがわかります。
というのも、1 を足した時点で二進数 (ビット) が桁上がりを起こし、次の桁の値も変化しているのです。これはつまり、本来 "2 ページ目を見た" という意味だったものが、"3 ページ目を見ていない" というものに置き換わってしまっているのです。事実関係が仮にそうであったとしても、言っていることが大きく異なってしまうため、この些細なミスが原因でバグ修正に多大な苦労を強いられることもあるので注意しましょう。
このように、加算で行おうとした場合は既にビットがセットされているかを調べた上で足す足さないを判断しないといけないところを、論理和を使えばそういった配慮をすることなく操作が出来るので、こういった目的での利用に特に威力を発揮します。
また、セットするばかりではなく、リセットする、すなわち "読んでいない" とマークする方法についても見てみましょう。
これにはビットを 0 にする必要があるのですけど、ここで論理積 AND が役に立ちます。この論理演算は、両方のビットが 1 のときに 1 を示すというものです。その性質を利用して、0 にセットしたいビットだけを 0 に、それ以外を 1 にセットした値を用意して、それを AND で演算してあげれば、目的のビットを 0 にすることが可能です。
言葉だけだと難しいので、まずは例を見てみましょう。
値 | 値 (十進数) | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
元の値 | 77 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
見ていないことを示す値 | 254 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
論理積を使った場合 | 76 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
論理積をとると、片方が 1 の部分は必ず、もう一方のビットの状態がそのまま反映されます。なので、1 を指定している部分は元の値をそのまま受け継ぐことが出来ます。そして、 0 が指定されている部分は、元の値がどのような状態であっても必ず 0 となります。このようにして、どのような状況でも最下位ビットを 0 にすることが出来るのでした。
さて、最下位ビットを 0 にするために、上記では 254 という値を使用しました。
これは、今回は 8 ビットなので一番下のリセットしたいビットのみを 0 とした値を用意してみたら 254 になったというものです。しかしながら Perl のスカラ変数は 8 ビットではないので、上記のように 254 を使った場合は上手くいかない恐れがあります。もっとも、すべての情報が 8 ビットで収まるように約束事を決めてあるので問題はないですけど、それでも余計な誤動作は避けるように組むのが理想的です。
しかしながら、必要とあるごとにこれを算出するのも一見面倒です。そもそも "1" と 8 ビットの場合は "254" を使い分けていると、後でプログラムを見たときにその関係がわかりづらくなってしまったりします。
そこで利用できるのが、否定 NOT という論理演算です。
この演算を使うと、ビットの状態 0 と 1 をちょうど逆にした値を得ることが出来ます。「ビットをセットしたい場合は、セットしたい部分だけを 1 にしてそれ以外を 0 にした値を使う」 と、「ビットをリセットしたい場合は、リセットしたい部分だけを 0 にしてそれ以外を 1 にした値を使う」 という状況にまさにうってつけなものなのでした。これを使うことで、ビットがいくつであろうと正確な値が取得できるし、第一、そのあたりの計算はプログラムにお任せです。
では、これを利用してプログラミングをして見ましょう。やることは、ビットを変化させる値を変数で用意して、それを使って状態をとりあえず変化させて見るという感じです。状態を変化させたい値は既に $value に保存されているものとします。
# 読んだかどうかを変化させるのに使う値。READ_ALREADY は既に読んだことを示すのに使います。
# READ_NOT_YET はまだ読んでいないことを示すのに使います。内容はちょうど、READ_ALREADY のビットを反転させたものです。
$READ_ALREADY = 1;
$READ_NOT_YET = ~$READ_ALREADY;
# $value には既に値が保存されているものとします。
# まずは読んだ状態に設定してみます。
$value |= $READ_ALREADY;
# 今度はまだ読んでいないという状態にしてみます。
$value &= $READ_NOT_YET;
このようにすることで、最初の $READ_ALREADY のところだけ手間をかけてあげれば、あとは見ためもすっきりと意味を含んだプログラムになると思います。
さて思いっきり余談になるのですけど、NOT を使うことでたとえ何ビットであっても…、とお話しました。
では実際に、 Perl ではどれくらいの大きさが、スカラー変数に与えられているのでしょう。それをさらっと調べるときにも、この NOT が役に立ったりします。ビットがすべて 0 、すなわち 0 という値の NOT をとってあげると、すべてのビットが 1 の値を得ることが出来ます。
# まずは一応、変数に 0 を保存してみます。
$value = 0;
# それを反転して表示してみます。
print ~$value;
さて、こうしてみると 18446744073709551615 という値を得ることが出来ました。あとは2進数でどれくらいに相当するかを調べればいいわけです。筆算でやってみたり、プログラムでやってみたり、いろいろあるでしょうけど、ここでは Windows に入っている 「電卓」 で計算してみましょう。
スタートメニューの中にアクセサリとして 「電卓」 がインストールされていると思います。それを起動したら、システムメニューの 「表示」 から 「関数電卓」 を選択します。すると枠が広がって大きな電卓になるので、そこへ先ほどの数値を入力します。そうしたら 「2 進」 というボタンを押してあげると、その数値が二進数でいくつに該当するかが簡単に確認できます。
桁が多すぎて数えるのも大変だったのですけど、桁数は 64 でした。すなわち 64 ビットです。桁数を数えるのさえ面倒な場合は、文字数が表示されるテキストエディタへコピーしてみると簡単です。というか、面倒だったのでそうやって数えてみました。
[ もどる ]