Edited at

【PHP入門講座】 文字列

More than 5 years have passed since last update.


目次に戻る


前書き

すっ飛ばしてきた部分をちゃんと補填するため、いよいよ本格的に言語構造について詳しく学んでいきます。 「あ、ここってこういうことだったのか!」 と納得していただける点があれば嬉しい限りです。


PHPでの「文字列」


2進数, 8進数, 16進数とは

私たちが日頃触っている数字は 10進数 です。


10進数

0, 1, 2, 3, 4, 5, 6, 7, 8, 9


10個1桁の数字が続いた後、 10 というように2桁の領域に入りますよね。この事から10進数と呼ばれます。10進数以外のものは以下のようになります。


2進数

0, 1



8進数

0, 1, 2, 3, 4, 5, 6, 7



16進数

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F


16進数に関しては、アルファベットを導入して2桁で表現できる領域を拡張しています。ポケモンをやり込んでいる人ならば、個体値の範囲を表すのに使っている


32進数

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V


の概念も分かるはずですね(笑)


1ビットとは

01 かを表す情報のことです。2進数の1桁に相当します。


1バイトとは

1バイト= 8ビット というように定義されています。2進数だと8桁に相当します。


2進数で表した1バイトの範囲

00000000 ~ 11111111


これだと見にくいので、10進数や16進数がよく用いられます。ここでは0以上の整数のみを対象にする 符号無し整数 の考え方が適用されます。


8進数で表した1バイトの範囲

000 ~ 377



10進数で表した1バイトの範囲

000 ~ 255



16進数で表した1バイトの範囲

00 ~ FF



PHPで言う「文字」とは何か

1バイトを使って表されるもののことです。文字の最小単位は1バイトです。これより小さく切り分けることは出来ません。まずは最も基本的な1バイトコード、別名 ASCIIコード について触れます。以下のリンクを参照してください。

ASCII文字コード : IT用語辞典

http://e-words.jp/p/r-ascii.html

簡単にグループ分けすると、


  • 半角数字

  • 半角アルファベット

  • 半角記号

  • 制御用の特殊文字

この4つに分けることが出来ます。

しかしこれだけだと、日本語・韓国語・中国語・ロシア語・アラビア語などを扱うことは当然出来ませんよね?そこで1~2バイト並べて1文字を表現しようという発想に帰着することになります。 Shift_JISEUC-JP などは日本語専用の文字コードですが、Webページの国際対応が重要な今、日本語しか用いることが出来ないというのは致命的な欠点ですよね。

そこで UTF-8 という、全ての言語に対応した文字コードが登場してきました。現在ではWebページの80%以上がこのコードで書かれていると言われています。UTF-8では1~4バイト並べて1文字を表現します。当然1文字あたりのサイズは先に述べた日本語専用のコードよりも多く必要としますが、情報通信速度が向上し、記憶領域が大量に確保できるようになった今の時代であるからこそ、受け入れられるものと考えられるでしょう。あとで理由も記述しますが、PHPの一般的な関数はUTF-8しか正しく扱うことが出来ません。よって今からPHPでプログラミングを行うならば、UTF-8を選択しない理由は無いと言っても過言ではないでしょう。UTF-8コード表を示しますので、以下のリンクを参照してください。

UTF-8コード表(1)

http://www.seiai.ed.jp/sys/text/java/utf8table.html

UTF-8コード表(2)

http://www.seiai.ed.jp/sys/text/java/utf8table2.html

例えば は16進数表記で E3 81 82 として3バイトで表されます。


PHPで言う「文字列」とは何か

文字が並べられたもののことを指します。例えば は16進数表記でE3 81 82 E3 81 83 として6バイトで表されます。しかし、厳密にはこれだけでは説明不足です。このページ内の「PHPの文字列にはどんなバイトコードも入れられる」にてその理由を説明しています。


エスケープ文字 \

さて、また エスケープ という言葉が出てきました。一番最初に


... この処理を、HTML特殊文字を 「エスケープする」 といいます。一口にエスケープするといっても様々なエスケープがあり、何のためにエスケープするのかが重要です。


と述べましたが、ここでは違う意味のエスケープを扱います。まず、

2種類のクオートを書きます。「"」「'」

と出力するコードを書いてみます。

echo '2種類のクオートを書きます。「"」「'」';

echo "2種類のクオートを書きます。「"」「'」";

何やらQiita上で文法エラーを示す赤枠が出ていますね。当然です、文字列を括る文字自体を文字として出力しようとしているのですから。ここでエスケープを行います。

echo '2種類のクオートを書きます。「"」「\'」';

echo "2種類のクオートを書きます。「\"」「'」";

こうすると正しく表示することが出来ます。\\ という文字は、後ろに来る文字の扱いを変える役割を持ちます。このような特殊な文字を メタ文字 といいます。メタ文字である \\ 自身を表したいときは、 \\\\ として2つ連ねればOKです。なお、今回行った \\ によるエスケープは、PHPコードのためのエスケープです。エスケープは幅広い意味を持つ語句なので、目的をしっかり押さえておきましょう。


文字をバイトコードから指定する

echo 'K';

このコードで出力される内容は、先ほどのASCIIコード表によれば、


  • 文字 → K

  • 8進数コード → 113

  • 16進数コード → 4B

となります。ここで別の方法も考えてみましょう。PHP言語では " を用いた場合に限り、バイトコードを直接記述することが出来ます。具体的な記述方法を下に示します。

echo "\113";

echo "\x4B";

8進数コードは \\ に続けて入力します。16進数コードは \\x に続けて入力します。この「x」は6を表す「HEX」から来ています。先ほどの の例ならば、


  • 文字 →

  • 8進数コード → 343 201 202

  • 16進数コード → E3 81 82

echo "\343\201\202";

echo "\xE3\x81\x82";

となります。


PHPの文字列にはどんなバイトコードも入れられる

突然ですが、UTF-8コード表に存在しないおかしなコードを出力してみましょう。

echo "\xE3\xE3\xE3";

ブラウザによって表示は異なりますが、おそらく ? を載せたような文字が現れるでしょう。これはブラウザ側が コードを認識できなかった文字 として処理した結果です。しかし今回問題にして欲しいのはそこではありません。文字列に文字として存在しないコードをセットできているということです。つまり、PHPの「文字列」はどちらかと言えば 「バイト列」 と言うべきです。テキストファイルではない画像ファイルのデータなども文字列に格納できてしまうのです。


文字列の表記法とエスケープシーケンス

\\\\\xE3 などの1つ1つのまとまりは エスケープシーケンス と呼ばれます。PHPには4種類の

文字列表記法がありますが、それぞれに適用されるエスケープシーケンスは異なります。


シングルクオート '

一番基本的なタイプです。

$var = '文字列';

記述
実際の表示
意味
備考

\\\\
*\*

バックスラッシュ
直後にシングルクオートがない場合は省略可能

\\'
'
シングルクオート


「直後にシングルクオートがない場合は省略可能」とは?

以下の1番目、3番目(の一部)のようなケースを指します。

echo '[a]\[b]'; # => [a]\[b]

echo '[a]\\[b]'; # => [a]\[b]
echo '[a]\\\[b]'; # => [a]\\[b]
echo '[a]\\\\[b]'; # => [a]\\[b]
echo '\\'; # => \
echo '\'; # パースエラー

慣れないうちは省略せずに全てエスケープすることをおすすめします。ちなみにこれはPHP言語に限っての挙動であり、 C言語Java言語 ではこのような挙動にはならず、必ずエスケープする必要があります


ダブルクオート "

シングルクオートにさまざまなエスケープシーケンスが付加されたタイプです。変数の値を展開することもでき、用途はかなり広いです。

$var = "文字列";

記述
実際の表示
意味
備考

\\\\
*\*

バックスラッシュ
直後に他のエスケープ可能な文字がない場合は省略可能

\\"
"
ダブルクオート

\\$
$
ドル記号
直後に変数名に使える文字がなければ省略可能

\\n

ラインフィード(LF)

\\r

キャリッジリターン(CR)

\\t

水平タブ

\\v

垂直タブ
PHP5.2.5以降のみ

\\e

エスケープ
PHP5.4.0以降のみ

\113
K
8進数コード指定

\\x4B
K
16進数コード指定

{$var}
$var の値
変数展開

${var} と書くことも出来る

$var
$var の値
変数展開

{$var[0]}
$var[0] の値
変数展開

${var[0]} と書くことも出来る

$var[0]
$var[0] の値
変数展開

{$var['hoge']}
$var['hoge'] の値
変数展開

${var['hoge']} と書くことも出来る
↓ との違いに注意

$var[hoge]
$var['hoge'] の値
変数展開
↑ との違いに注意


ヒアドキュメント内

文字列中に " がいっぱい出てきてエスケープが面倒な時はこれを使いましょう。複数行に書かれた場合でも比較的美しく見せることが出来ます。但し、変数展開の機能だけは残されています。なお、EOD には記号以外の任意の文字列を入れることが出来ますが、 EOD ( End Of Document ) や EOS ( End Of Statement ) が用いられることが多いです。

$var = <<<EOD

1行目
2行目
EOD;

記述
実際の表示
意味
備考

\\$
$
ドル記号
直後に変数名に使える文字がなければ省略可能

{$var}
$var の値
変数展開

${var} と書くことも出来る

$var
$var の値
変数展開

{$var[0]}
$var[0] の値
変数展開

${var[0]} と書くことも出来る

$var[0]
$var[0] の値
変数展開

{$var['hoge']}
$var['hoge'] の値
変数展開

${var['hoge']} と書くことも出来る
↓ との違いに注意

$var[hoge]
$var['hoge'] の値
変数展開
↑ との違いに注意

なお、ヒアドキュメントの終了サインに関してですが、前後に他の文字が入ってはいけません。半角スペースやタブ文字などもこれに該当します。但し、例外的に後ろに ; を含めるのは認められています。


NG例

    $str = <<<EOD

Hi!! \$a is "{$a}".
EOD;

echo $str;

    echo <<<EOD

Hi!! \$a is "{$a}".
EOD;

    var_dump( <<<EOD

Hi!! \$a is "{$a}".
EOD
);


OK例

インデントしたくなりますが、我慢。

    $str = <<<EOD

Hi!! \$a is "{$a}".
EOD;

echo $str;

    $str = 

<<<EOD
Hi!! \$a is "{$a}".
EOD

;
echo $str;

    echo <<<EOD

Hi!! \$a is "{$a}".
EOD;

    var_dump(

<<<EOD
Hi!! \$a is "{$a}".
EOD

);


Nowdoc内

PHP5.3から追加された方法です。エスケープシーケンスが一切存在しません。ヒアドキュメントのEOD'EOD' とすることによってこれに変化します。

$var = <<<'EOD'

1行目
2行目
EOD;


確認問題


以下の出力結果を答えよ。(答えが分からないように黒一色で書いています)

<?php

$var = 'O';
echo "\\\\{$var}\x4B!!\n";
echo "\\\{$var}\x4B!!\n";

 

 

  

  

  

 

 

 

 

 

 

 

 


\\OK!!

\\{O}K!!


解説

echo "\\\\{$var}\x4B!!\n";

は素直に考えれば答えは分かるはずです。問題は2行目。

echo "\\\{$var}\x4B!!\n";

これは最初の \\\\ でバックスラッシュを表した後、次の \\{ でエスケープシーケンスを構成してしまっています。本来 { が持つ役割を \\ によって消されたので、これは文字として表示されます。その次の $var 部分だけがエスケープシーケンスと見なされ、変数が展開されます。その次の } は対応する { が無くなってしまったため、文字として表示されます。


UTF-8だけが正しく扱われる理由

Shift_JIS の例を見てみましょう。今回は str_replace という関数を使います。この関数は指定した文字列を検索し、別の文字列に置換する関数です。

str_replace

http://php.net/manual/ja/function.str-replace.php


PHPコード

<?php

echo str_replace('A', 'B', 'CACA');


実行結果

CBCB


CACA の中から A が探し出されて B に置換されました。ところがこの関数、先ほど述べたように UTF-8 しか正しく扱うことが出来ません。 Shift_JIS にて下記の現象が起こります。


PHPコード

<?php

echo str_replace('ア', 'ウ', 'こ');


実行結果



この現象を考察してみましょう。それぞれの16進数コードを確認します。 Shift_JIS では半角カタカナは1バイト、平仮名は2バイトで表されます。



  • B1


  • B3


  • 82 B1


  • 82 B3

お分かり頂けたでしょうか。これで平仮名を破壊する形で置換が行われてしまったのです。

一方 UTF-8 のコード分布は下記のようになっています。 - で取り得るバイト値の範囲を示します。


  • 1バイト文字 → 00-7F

  • 2バイト文字 → C0-DF 80-BF

  • 3バイト文字 → E0-EF 80-BF 80-BF

  • 4バイト文字 → F0-F7 80-BF 80-BF 80-BF

各値域を順番に並べると



  • 00-7F → 1バイト文字の接頭符号


  • 80-BF → 接頭符号に続く部分


  • C0-DF → 2バイト文字の接頭符号


  • E0-EF → 3バイト文字の接頭符号


  • F0-F7 → 4バイト文字の接頭符号

Wikipedia - 接頭符号

http://ja.wikipedia.org/wiki/%E6%8E%A5%E9%A0%AD%E7%AC%A6%E5%8F%B7

80-BF の区間だけで成立する文字コードは存在せず、絶対に重複が起こらないように工夫されているのです。