【PHP入門講座】 文字列

### [目次に戻る](http://qiita.com/mpyw/items/752e19a578cf7d96fc5f)

# 前書き

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

# PHPでの「文字列」

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

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

```text:10進数
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
```

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

```text:2進数
0, 1
```

```text:8進数
0, 1, 2, 3, 4, 5, 6, 7
```

```text:16進数
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
```

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

```text: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ビットとは

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

## 1バイトとは

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

```text:2進数で表した1バイトの範囲
00000000 ～ 11111111
```

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

```text:8進数で表した1バイトの範囲
000 ～ 377
```

```text:10進数で表した1バイトの範囲
000 ～ 255
```

```text:16進数で表した1バイトの範囲
00 ～ FF
```

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

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

**ASCII文字コード ： IT用語辞典**
http://e-words.jp/p/r-ascii.html

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

- 半角数字
- 半角アルファベット
- 半角記号
- 制御用の特殊文字

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

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

そこで **UTF-8** という、<ins>全ての言語に対応した</ins>文字コードが登場してきました。現在ではWebページの80%以上がこのコードで書かれていると言われています。UTF-8では<ins>1～4バイト並べて1文字を表現</ins>します。当然1文字あたりのサイズは先に述べた日本語専用のコードよりも多く必要としますが、情報通信速度が向上し、記憶領域が大量に確保できるようになった今の時代であるからこそ、受け入れられるものと考えられるでしょう。あとで理由も記述しますが、<ins>PHPの一般的な関数はUTF-8しか正しく扱うことが出来ません</ins>。よって今から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で言う「文字列」とは何か

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

## エスケープ文字 `\`

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

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

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

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

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

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

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

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

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

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

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

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

```php
echo 'K';
```

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

- 文字 → `K`
- 8進数コード → `113`
- 16進数コード → `4B`

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

```php
echo "\113";
```

```php
echo "\x4B";
```

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

- 文字 → `あ`
- 8進数コード → `343` `201` `202`
- 16進数コード → `E3` `81` `82`

```php
echo "\343\201\202";
```

```php
echo "\xE3\x81\x82";
```

となります。

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

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

```php
echo "\xE3\xE3\xE3";
```

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

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

`\\\\` や `\xE3` などの1つ1つのまとまりは **エスケープシーケンス** と呼ばれます。PHPには4種類の
文字列表記法がありますが、それぞれに適用されるエスケープシーケンスは異なります。

## シングルクオート `'` 内

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

```php
$var = '文字列';
```

| 記述 | 実際の表示 | 意味                   | 備考           |
|:------:|:--------------:|:--------------------:|:-------------:|
| **`\\\\`** | **\\**          | バックスラッシュ | 直後にシングルクオートがない場合は省略可能 |
| **`\\'`**   | **'**            | シングルクオート |   |

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

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

```php
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言語** ではこのような挙動にはならず、<ins>必ずエスケープする必要があります</ins>。

## ダブルクオート `"` 内

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

```php
$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']}` と書くことも出来る<br />↓ との違いに注意 |
| `$var[hoge]` | $var['hoge'] の値 | 変数展開 | ↑ との違いに注意 |

## ヒアドキュメント内

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

```php
$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']}` と書くことも出来る<br />↓ との違いに注意 |
| `$var[hoge]` | $var['hoge'] の値 | 変数展開 | ↑ との違いに注意 |

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

### NG例

```php
    $str = <<<EOD
    Hi!! \$a is "{$a}".
    EOD;
    echo $str;
```

```php
    echo <<<EOD
    Hi!! \$a is "{$a}".
    EOD;
```

```php
    var_dump( <<<EOD
Hi!! \$a is "{$a}".
EOD);
```

### OK例

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

```php
    $str = <<<EOD
Hi!! \$a is "{$a}".
EOD;
    echo $str;
```

```php
    $str = 
<<<EOD
Hi!! \$a is "{$a}".
EOD
    ;
    echo $str;
```

```php
    echo <<<EOD
Hi!! \$a is "{$a}".
EOD;
```

```php
    var_dump(
<<<EOD
Hi!! \$a is "{$a}".
EOD
    );
```

## Nowdoc内

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

```php
$var = <<<'EOD'
1行目
2行目
EOD;
```
# 確認問題

## 問

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

```
<?php
$var = 'O';
echo "\\\\{$var}\x4B!!\n";
echo "\\\{$var}\x4B!!\n";
```

　
　
　　
　　
　　
　
　
　
　
　
　
　
　
## 答

```text
\\OK!!
\\{O}K!!
```

## 解説

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

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

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

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

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

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

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

```php:PHPコード
<?php
echo str_replace('A', 'B', 'CACA');
```

```text:実行結果
CBCB
```

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

```php:PHPコード
<?php
echo str_replace('ｱ', 'ｳ', 'こ');
```

```text:実行結果
さ
```

この現象を考察してみましょう。それぞれの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` の区間だけで成立する文字コードは存在せず、絶対に<ins>重複が起こらない</ins>ように工夫されているのです。