文字
文字は文字列の構成要素である。文字体系の記号は、アルファベット、数字、句読点、スペース文字など、すべて"文字"と呼ばれる。紛らわしいが、文字の構成要素も"文字"と呼ばれる。
文字の配列が文字列を構成する。配列については前の章で説明した。文字列については2章後に説明する。
他のデータと同様に、文字もビットで構成される整数値として表される。例えば、小文字の'a'
の整数値は97、数字の'1'
の整数値は49だ。これらの値は、ASCII規格が設計されたときに割り当てられた単なる慣例である。
多くのプログラミング言語では、文字はchar
型で表現され、256個の異なる値しか保持できない。他の言語でchar
型に慣れている人は、この型では多くの文字体系の記号をサポートするには不十分であることをご存じだろう。Dの3種類の文字型について説明する前に、まずコンピュータシステムにおける文字の歴史について見てみよう。
歴史
ASCIIテーブル
ASCIIテーブルは、現在のシステムに比べてコンピュータのハードウェアが非常に制限されていた時代に設計された。ASCIIテーブルは7ビットを基本としているため、128個の異なるコード値を持つことができる。この数の異なる値があれば、基本的なラテンアルファベットの26文字の大文字と小文字、数字、よく使われる句読点、および一部の端末制御文字を表現するのに十分だ。
例として、文字列 "hello"
の文字のASCIIコードは次の通りだ(読みやすくするためにコンマを挿入している):
104 | 101 | 108 | 108 | 111 |
上記の各コードは、文字列 "hello"
の 1文字を表している。例えば、'l'
の2文字には2つの108値が対応している。
ASCIIコードのコード数は後に8ビットに拡張され、拡張ASCIIコード表となった。拡張ASCIIコード表には256の異なるコードが含まれる。
IBMコードページ
IBM Corporationは、拡張ASCIIテーブルの128から255までのコードを1つ以上の文字体系に割り当てる一連のテーブルを定義した。これらのコードテーブルにより、より多くのアルファベットの文字をサポートすることが可能になった。例えば、トルコ語のアルファベットの特殊文字は、IBMのコードページ857の一部だ。
ASCIIよりもはるかに便利であるにもかかわらず、コードページにはいくつかの問題と制限がある。テキストを正しく表示するには、そのテキストがもともとどのコードページで記述されているかを知っている必要がある。これは、ほとんどの他のテーブルでは、同じコードが別の文字に対応しているためだ。例えば、テーブル857で'Ğ'
を表すコードは、テーブル437では'ª'
に対応する。
単一の文書で複数のアルファベットをサポートする難しさに加え、128文字を超える非ASCII文字を含むアルファベットは、IBM テーブルではまったくサポートできない。
ISO/IEC 8859コードページ
ISO/IEC 8859コードページは、国際標準化の結果生まれたものだ。文字にコードを割り当てる方法は、IBMのコードページと似ている。例えば、トルコ語のアルファベットの特殊文字は、コードページ8859-9に記載されている。これらのテーブルには、IBMのテーブルと同じ問題や制限がある。例えば、オランダ語の二重文字ijは、これらのテーブルには一切記載されていない。
Unicode
Unicodeは、これまでの解決策のすべての問題と制限を解決している。Unicodeには、現在および過去の多くの言語の文字体系の 10 万以上の文字と記号が含まれている。(新しい文字は、表に追加するために常に検討されている。) これらの文字にはそれぞれ固有のコードが割り当てられている。Unicodeでエンコードされた文書には、別々の文字体系のすべての文字を、混乱や制限なく含めることができる。
Unicodeエンコーディング
Unicodeは、各文字に一意のコードを割り当ててる。8ビットの値で表現できる文字数よりもUnicode文字の数が多いので、一部の文字は2つ以上の8ビットの値で表現する必要がある。例えば、'Ğ'
(286)のUnicode文字コードは、ubyte
の最大値よりも大きい。
電子メディアで文字がどのように表現されるかをエンコーディングと呼ぶ。上記で、文字列 "hello"
がASCIIでエンコードされる方法を見てきた。ここでは、Dの文字型に対応する3つのUnicodeエンコーディングについて説明する。
UTF-32:このエンコーディングは、各Unicode文字に32ビット(4バイト)を使用する。UTF-32エンコーディングの "hello"
はASCIIエンコーディングと類似しているが、各文字は4バイトで表される:
0 | 0 | 0 | 104 |
0 | 0 | 0 | 101 |
0 | 0 | 0 | 108 |
0 | 0 | 0 | 108 |
0 | 0 | 0 | 111 |
別の例として、のUTF-32エンコーディングは "aĞ"
は次の通りだ:
0 | 0 | 0 | 97 |
0 | 0 | 1 | 30 |
注釈:UTF-32のバイトの順序は、コンピュータシステムによって異なる場合がある。
'a'
および'Ğ'
は、それぞれ1バイトと2バイトの有効バイトで表現され、他の5バイトの値はすべて0である。これらの0は、各Unicode文字が4バイトずつを占めるようにするためのフィラーバイトと考えることができる。
基本ラテン文字に基づく文書では、このエンコーディングは常にASCIIエンコーディングの4倍のバイト数を使用する。文書内の文字の大部分がASCII相当文字である場合、それらの各文字の3つの埋め込みバイトにより、このエンコーディングは他のエンコーディングに比べて無駄が多くなる。
一方、すべての文字を同じバイト数で表現することには利点もある。例えば、次のUnicode文字は常に正確に4バイト先にある。
UTF-16:このエンコーディングは、ほとんどのUnicode文字を16ビット(2バイト)で表現する。16ビットでは約65,000個の値しか表現できないため、あまり使用されない35,000個のUnicode文字は、追加のバイトを使用して表現する必要がある。
例えば、 "aĞ"
はUTF-16で4バイトでエンコードされる:
0 | 97 |
1 | 30 |
注釈:UTF-16のバイトの順序は、コンピュータシステムによって異なる場合がある。
UTF-32と比較すると、このエンコーディングはほとんどの文書でより少ないスペースで済むが、一部の文字は2バイト以上で表現する必要があるため、UTF-16は処理が複雑になる。
UTF-8:このエンコーディングでは、1文字につき1から4バイトを使用する。ASCIIテーブルに同等の文字がある場合、その文字はASCIIテーブルと同じ数値コードで1バイトで表現される。その他のUnicode文字は2、3、または4バイトで表現される。ヨーロッパの文字体系の特殊文字のほとんどは、2バイトで表現される文字のグループに含まれている。
欧米諸国のほとんどの文書では、UTF-8が最も少ないスペースで済むエンコーディングである。UTF-8のもう1つの利点は、ASCIIを使用して作成された文書を、変換せずにUTF-8文書として直接開くことができることだ。また、UTF-8では、すべての文字が意味のあるバイトで表現されるため、フィラーバイトによるスペースの無駄がない。例えば、"aĞ"
は次のようになる:
97 | 196 | 158 |
Dの文字型
文字を表すD型には3種類ある。これらの文字は、上記の3種類のUnicodeエンコーディングに対応している。基本型の章から引用する。
型 | 定義 | 初期値 |
---|---|---|
char | UTF-8コードユニット | 0xFF |
wchar | UTF-16コードユニット | 0xFFFF |
dchar | UTF-32コード単位とUnicodeコードポイント | 0x0000FFFF |
他のプログラミング言語とは異なり、Dの文字は異なるバイト数で構成される場合がある。例えば、'Ğ'
はUnicodeでは2バイト以上で表現しなければならないため、char
型の変数には収まらない。一方、dchar
は4バイトで構成されるため、Unicodeの任意の文字を格納することができる。
文字リテラル
リテラルは、ソースコードの一部としてプログラムに記述される定数値だ。Dでは、文字リテラルは単一引用符で囲んで指定する。
ダブルクォートは文字には使用できない。ダブルクォートは文字列を指定する際に使用され、これは後で説明するからだ。'a'
は文字リテラルであり、 "a"
は単一の文字からなる文字列リテラルである。
char
型の変数は、ASCIIテーブルにある文字しか保持できない。
コードに文字を挿入する方法はいくつかある:
- 最も自然な方法は、キーボードで入力することだ。
- 別のプログラムやテキストからコピーする。例えば、Webサイトや、Unicode文字を表示するための専用プログラムからコピーして貼り付けることができる。(ほとんどのLinux環境では、そのようなプログラムの1つとして、ターミナルで
charmap
と入力して起動する"文字マップ"がある。) - 文字の短い名前を使う。この機能の構文は
\&character_name;
。例えば、ユーロ記号の名前はeuro
で、プログラムでは次のように指定できる。この方法で指定可能なすべての文字のリストは、名前付き文字のリストを参照。
- 文字を10進のUnicode値で指定する:
- ASCIIテーブルの文字コードを、
\value_in_octal
または\xvalue_in_hexadecimal
の構文で指定する: wchar
に対して\ufour_digit_value
の構文を使用し、dchar
に対して\Ueight_digit_value
の構文を使用して文字のUnicode値を指定する(u
とU
を比較しよう)。Unicode値は16進数で指定する必要がある:
これらの方法は、文字列内の文字を指定する場合にも使用できる。例えば、次の2行は、同じ文字列リテラルを持っている。
制御文字
一部の文字は、テキストのフォーマットにのみ影響し、それ自体には視覚的な表現はない。例えば、出力を新しい行に継続することを指定する改行文字には、視覚的な表現はない。このような文字は、制御文字と呼ばれる。一般的な制御文字のいくつかは、 \control_character
構文で指定できる。
構文 | 名 | 定義 |
---|---|---|
\n | 改行 | 表示を新しい行に移動する |
\r | キャリッジリターン | 現在の行の先頭に表示を移動する |
\t | タブ | 表示を次のタブストップに移動する |
例えば、自動的に新しい行を開始しないwrite()
関数は、\n
文字ごとに新しい行を開始する。次のリテラル内で\n
制御文字が現れるたびに、新しい行が開始される。
出力:
最初の行
2行目
3行目
シングルクォートとバックスラッシュ
シングルクォート文字自体はシングルクォート内に記述できない。コンパイラは2つ目のシングルクォートを1つ目のシングルクォートの終了文字と解釈するからだ:'''
。最初の2つは開始と終了のシングルクォートとなり、3つ目は無視され、コンパイルエラーが発生する。
同様に、バックスラッシュ文字は制御文字とリテラル構文で特別な意味を持つため、コンパイラはそれをそのような構文の開始と解釈する:'\'
。コンパイラは閉じシングルクォート文字を探すが、見つからず、コンパイルエラーを発生させる。
これらの理由から、シングルクォートとバックスラッシュ文字は、前のバックスラッシュ文字でエスケープされる:
構文 | 名前 | 定義 |
---|---|---|
\' | シングルクォート | シングルクォート文字を指定する: '\'' |
\\ | バックスラッシュ | バックスラッシュ文字を指定可能: '\\' または "\\" |
std.uni モジュール
std.uni
モジュールには、Unicode文字の処理に便利な関数が含まれている。このモジュールについては、そのドキュメントを参照。
is
で始まる関数は、文字に関する特定の質問に答える。結果は、答えが"いいえ"の場合はfalse
、"はい"の場合はtrue
となる。これらの関数は、論理式で役立つ。
isLower
: これは小文字の文字か?isUpper
: 大文字の文字か?isAlpha
: これはUnicodeアルファベット文字か?isWhite
: 空白文字か?
to
で始まる関数は、既存の文字から新しい文字を生成する。
toLower
: 指定された文字の小文字バージョンを生成するtoUpper
: 指定された文字の大文字バージョンを生成する
これらの関数をすべて使用するプログラムを以下に示す。
出力:
ğは小文字か? | true |
---|---|
Şは小文字か? | false |
İは大文字か? | true |
çは大文字か? | false |
zはアルファベットか? | true |
€はアルファベットか? | false |
改行は空白文字か? | true |
アンダーバーは空白文字か? | false |
Ğの小文字は | ğ |
İの小文字は | i |
şの大文字は | Ş |
ıの大文字は | I |
ıとiに対する限定的なサポート
'ı'
、'i'
の小文字と大文字は、一部のアルファベット(例: トルコ語アルファベット)では一貫してドット付きまたはドットなしになる。他のほとんどのアルファベットではこの点で一貫性がない: ドット付きの'i'
の大文字はドットなしの'I'
になる。
コンピュータシステムはASCIIテーブルから始まったため、伝統的に、'i'
の大文字は'I'
、'I'
の小文字は'i'
となっている。そのため、この2つの文字には特別な注意が必要だ。次のプログラムは、この問題を示す。
出力は基本ラテン文字アルファベットに従っている:
iの大文字は | I |
---|---|
Iの小文字は | i |
文字は、通常、Unicode文字コードによって大文字と小文字に変換される。この方法は、多くのアルファベットでは問題になる。例えば、アゼルバイジャン語やケルト語のアルファベットでは、'I'
の小文字が'i'
になってしまうという同じ問題がある。
ソートにも同様の問題がある。'ğ'
や'á'
のような多くの文字は、基本ラテン文字でも'z'
の後にソートされることがある。
文字の読み取りに関する問題
DのUnicode文字の柔軟性とパワーは、入力ストリームから文字を読み込む際に予期しない結果を引き起こす可能性がある。この矛盾は、文字という用語の複数の意味に起因する。これについてさらに説明する前に、この問題を示すプログラムを見てみよう:
このプログラムをUnicodeを使用しない環境で実行すると、非ASCII文字も正しく読み込まれて表示されるかもしれない。
一方、同じプログラムをUnicode環境(例:Linuxターミナル)で実行すると、出力に表示される文字が入力した文字と異なる場合がある。これを確認するため、UTF-8エンコーディングを使用するターミナル(ほとんどのLinuxターミナル)に非ASCII文字を入力しよう:
文字を入力してください: ğ
読み込まれた文字: ← 出力に文字はない
この問題の原因は、'ğ'
のような非ASCII文字は2つのコードで表されており、入力からchar
を読み込むと、そのうちの最初のコードのみが読み込まれるためだ。この単一のchar
ではUnicode文字全体を表現できないため、プログラムは表示するための完全な文字を持っていない。
文字を構成するUTF-8コードが実際に1つのchar
ずつ読み込まれることを示すため、2つのchar
変数を読み込み、それらを順番に表示しよう:
プログラムは入力から2つのchar
変数を読み込み、読み込んだ順に表示する。これらのコードが同じ順序でターミナルに送信されると、ターミナル上でUnicode文字のUTF-8エンコーディングが完了し、今回はUnicode文字が正しく表示される:
文字を入力してください: ğ
読み込まれた文字: ğ
これらの結果は、プログラムの標準入力と標準出力はchar
ストリームであることとも関連している。
文字列の章で後で見るように、UTFコードを個別に扱うのではなく、文字を文字列として読み込む方が簡単だ。
DのUnicodeサポート
Unicodeは大規模で複雑な標準規格だ。Dはその非常に有用なサブセットをサポートしている。
Unicodeエンコードされた文書は、以下の概念のレベルから構成される。下位から上位の順に:
- コードユニット: UTFエンコーディングを構成する値は、コードユニットと呼ばれる。エンコーディングおよび文字自体に応じて、Unicode文字は1つ以上のコードユニットで構成される。例えば、UTF-8エンコーディングでは、文字
'a'
は1つのコードユニットで構成され、文字'ğ'
は2つのコードユニットで構成される。Dの文字型
char
、wchar
、およびdchar
は、それぞれ UTF-8、UTF-16、およびUTF-32コード単位に対応している。 - コードポイント: Unicode規格で定義されているすべての文字、数字、記号などは、コードポイントと呼ばれる。例えば、
'a'
と'ğ'
のUnicodeコード値は、2つの異なるコードポイントだ。エンコーディングによっては、コードポイントは1つまたは複数のコード単位で表される。上記のように、UTF-8エンコーディングでは、
'a'
は1つのコード単位で表され、'ğ'
は2つのコード単位で表される。一方、'a'
と'ğ'
は、UTF-16およびUTF-32エンコーディングの両方で1つのコード単位で表される。コードポイントを扱うDの型は、
dchar
だ。char
とwchar
は、コードユニットとしてのみ使用できる。 - 文字:Unicode規格で定義され、日常会話で"文字"または"文字"と呼ばれるものはすべて文字である。
Unicodeでは、この文字の定義は柔軟であり、それが複雑さを生じている。一部の文字は、複数のコードポイントで構成することができる。例えば、文字"
'ğ'
"は2つの方法で指定することができる。- 単一のコードポイントとして
'ğ'
'g'
と'˘'
(結合 breve)の2つのコードポイントとして
人間の読者には同じ文字として認識されるが、単一のコードポイント"
'ğ'
"は、連続する2つのコードポイント"'g'
"と"'˘'
"とは異なるものである。 - 単一のコードポイントとして
要約
- Unicodeは、すべての文字体系のすべての文字をサポートしている。
char
はUTF-8エンコーディング用である。一般の文字を表すには適していないが、ASCIIテーブルをサポートしている。wchar
はUTF-16エンコーディング用である。一般の文字の表現には適していないが、複数のアルファベットの文字をサポートできる。dchar
はUTF-32エンコーディング用である。32ビットであるため、コードポイントも表現できる。