文字の表現

編集

コンピューターでは、文字を数値で表現しています。最も一般的な文字のコード体系がASCIIコードで、英数字や一部の記号を0から127の番号で表しています。例えば、'A'は65、'a'は97と割り当てられています。

文字リテラルは、シングルクォート' 'で囲んで表記します。

char c = 'A'; // cに65が代入される

文字型と文字の入出力

編集

文字を格納する型がchar型です。1バイト(8ビット)の大きさを持ち、ASCIIコードの範囲(-128から127)の値を扱えます。

getchar関数は標準入力から1文字を読み込み、その文字のASCIIコード値を返します。putchar関数はASCIIコード値に対応する文字を標準出力に出力します。

char c = getchar(); // 標準入力から1文字読み込む
putchar(c); // 読み込んだ文字を出力

文字列リテラル

編集

文字列リテラルはダブルクォート"で囲んで表記します。終端に\0(ヌル文字)が自動で付加されます。

char str[] = "Hello"; // str = {'H', 'e', 'l', 'l', 'o', '\0'}

文字列操作関数

編集

C言語には、文字列を操作する多くの関数が用意されています。

strlen関数は文字列の長さ(ヌル文字を除く)を返します。strcpy関数は文字列のコピー、strcat関数は文字列の連結をします。strcmp関数は文字列の比較を行います。

char str1[100] = "Hello";
char str2[100];
int len = strlen(str1); // len = 5
strcpy(str2, str1); // str2 = "Hello"  
strcat(str1, " World"); // str1 = "Hello World"
int comp = strcmp(str1, str2); // comp > 0

この他にも、strncpy、strncat、strstr、strchrなど便利な関数があります。

配列と文字列

編集

文字列は実態として文字の配列です。ヌル文字で終端されているという点が、他の配列と異なる点です。

char arr[] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 文字列リテラル"Hello"と同値
char *ptr = arr; // ptrはarr[0]の番地を指す

配列とポインタの概念を理解することが、文字列の操作で重要になります。

文字列リテラルの格納場所

編集

文字列リテラルはプログラム実行時に、コード領域(テキストセグメント)に静的に格納されます。一方、変数に格納された文字列はデータ領域(datastatic領域、スタック、ヒープ)に置かれます。

文字列リテラルは読み取り専用なので、格納先のコード領域の内容を書き換えることはできません。

可変長文字列の動的メモリ確保

編集

固定長の配列ではなく、必要な長さのメモリ領域を動的に確保して文字列を扱うこともできます。malloc関数でヒープ領域からメモリを確保し、その領域に文字列をコピーします。

char *str = malloc(strlen(src) + 1); // srcのコピー先メモリを確保
strcpy(str, src); // srcをコピー
// 文字列strの利用が終われば
free(str); // 確保したメモリを開放

可変長の文字列を扱う際は、動的メモリ確保の概念を理解する必要があります。

文字定数と文字列リテラル

編集

文字定数

編集

Cにおける文字定数(character constant)とは、シングルクォート()で囲まれた1つ以上の文字から成る定数のことを指します。

主な特徴は以下の通りです。

  1. 文字定数はシングルクォート()で囲まれた文字または文字列で表現されます(例: 'a', '賢')。
  2. エスケープシーケンス(バックスラッシュ\から始まる特殊文字)を使うことで、印字不可能な文字や特殊文字を表現できます(例: '\n', '\x41')。
  3. プレフィックス(u8, u, U, L)を付けることで、UTF-8文字定数、UTF-16文字定数、UTF-32文字定数、ワイド文字定数を表現できます。
  4. 文字定数の型と値は以下のようになります:
    • プレフィックスなし: 型はint、値は対応する実行文字セットの文字コード値
    • u8プレフィックス: 型はunsigned char、値はUTF-8のコードポイント値
    • u, Uプレフィックス: 型はchar16_t、char32_t、値は対応するUnicodeコードポイント
    • Lプレフィックス: 型はwchar_t、値は実装定義の拡張文字セット
  5. 複数文字からなる文字定数の値は実装定義です。

文字定数は単一の文字や制御文字、Unicode文字を表現するための定数となっており、プレフィックスにより様々な文字エンコーディングを扱えるようになっています。文字操作を行う上で基本的な表記法となります。

Cにおけるエンコーディング
C言語におけるエンコーディングについて説明します。

C言語では、文字やテキストデータのエンコーディング方式が明示的に規定されていません。しかし、以下のようなエンコーディングが扱えるようになっています。

  1. マルチバイト文字
    • 拡張ASCIIやShift_JIS、EUC、Big5などの文字コードが扱えます。
    • char型の配列で表現し、ロケール指定により解釈されます。
  2. ワイド文字
    • Unicode文字などの2バイト以上の文字コードを扱えます。
    • wchar_t型を使用します。サイズは実装によって異なります。
  3. UTF-8エンコーディング
    • C99より、文字列リテラルのu8プレフィックスでUTF-8エンコーディングを使えます。
    • char型の配列で表現されます。
  4. UTF-16/UTF-32エンコーディング
    • C11より、uプレフィックス(UTF-16)とUプレフィックス(UTF-32)が利用可能になりました。
    • char16_t型とchar32_t型で表現されます。

マルチバイト文字やワイド文字を扱う際は、ロケールの設定が重要です。また、プログラムのソースファイルのエンコーディング形式とコンパイラが解釈するエンコーディングが同じである必要があります。

UnicodeをネイティブでサポートするUTF-8、UTF-16、UTF-32の機能が追加されたことで、様々な文字エンコーディングを扱えるようになりました。しかし、C標準自体がエンコーディングを規定していないため、移植性には注意が必要です。

バックスラッシュ・エスケープ

編集

文字列リテラルには、改行などの制御文字や、文字列内で特別な意味を持つ文字を直接埋め込むことはできません。 このような文字を文字列に含めるには、バックスラッシュ・エスケープ[1]を使用する必要があります。 バックスラッシュ(\)は、次の文字に特別な意味を持たせるエスケープシーケンスとして機能します。 なお、日本語環境では、バックスラッシュは円マーク(¥)として表示されることがあります。

バックスラッシュ・エスケープと意味
バックスラッシュ・エスケープ 意味
\n 改行(New line)
現在の印字位置を次の行の先頭位置に移動する
\t タブ(horizontal Tab)
次の水平タブ位置に移動する
\b バックスペース(Backspace)
現在の行で前に移動します。先頭にある場合は不定。
\r キャリッジリターン(carriage Return)
現在の行の先頭位置に移動
\f ページフィード(Form Feed)
次の論理ページの最初に移動
\' シングルクォーテーション(single quotation mark)
一重引用符
\" ダブルクォーテーション(double quotation mark)
二重引用符
\0 ヌル文字(null)
空文字(実際は8進数表記の1ケース)
\\ 円記号(\)
\? クエスチョンマーク
\a ベル音(Alert)
ベル音を鳴らす。印字位置は不変
\v 垂直タブ(vertical Tab)
\xhh 16進拡張(heXadecimal)
16進でhhのコードを持つ文字
\ooo 8進拡張(octal)
8進でoooのコードを持つ文字

文字列

編集

C言語では、char型の配列が、文字列を表現する際に使われます。 文字列を「""(ダブルクォーテーション)」で囲むと、その文字列を表現する配列となります。 文字列は「'\0'」( ヌル文字、 0x00 )で終わります。

配列を用いて文字列を扱う
#include <stdio.h>

int main(void) {
  const char str[] =
      "Hello, World!"; // <code>char</code>型のconstな配列strに文字列"Hello, World!"を格納します。
  printf("%s\n", str); // strを表示します。
}
実行結果
Hello, World!

上の例では、配列strの値は次の表のようになります。

[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13]
'H' 'e' 'l' 'l' 'o' ',' ' ' 'W' 'o' 'r' 'l' 'd' '!' '\0'

また、同様の処理をポインタを用いて記述することができます。

ポインタを用いて文字列を扱う
#include <stdio.h>

int main(void) {
	char *p = 
      "Hello, World!"; //<code>char</code>型へのポインタpに文字列"Hello, World!"のアドレスを格納します。
	printf("%s\n", p); //ポインタpが指す文字列を表示します。
}
実行結果
Hello, World!

文字列の配列

編集

複数の文字列を共通の名前でまとめて扱いたい場合、多次元の配列を用います。

一番右の次元の要素数で、扱う文字列1個あたりの長さ(ヌル文字を含む)を指定し、残りの次元の要素数で、扱う文字列の数を指定します。

たとえば英数字10文字(終端の '\0' も含め)までの長さで、4単語までを格納できる用意したい場合、

char words[4][10];

のようになります。

つまり構文は、

char 配列変数名[文字列の個数][文字列1個あたりの長さ];

です。

文字列の配列のある文字列にアクセスしたい場合、配列の添字に一番右の次元を除く次元を指定します。

#include <stdio.h>

int main(void) {
  char num[3][6] = {"zero", "one", "two"};

  printf("%s \n", num[1]); //文字列の配列の配列の[i]番目の文字列を表示します。
}
実行結果
one

上記例のように、char 宣言時の配列は2次元配列の書式であっても(たとえばnum[3][6] のような書式)、 printfなど利用時には次元が一段階だけ下がるので書式は1次元配列でアクセスすることになるので(たとえばnum[1] のような書式)、間違えないように注意せよ(初心者はよく間違えてエラーになり悩む)。


文字列の配列の配列の使用例
#include <stdio.h>

int main(void) {
  char num[][6] = {//文字列の配列に0から9までの数字の英語の名前を格納します。
                   "zero", "one", "two",   "three", "four",
                   "five", "six", "seven", "eight", "nine"};
  for (int i = 0; i < sizeof num / sizeof *num; i++)
    printf("%s ", num[i]); //文字列の配列の配列の[i]番目の文字列を表示します。

  printf("\n");
}
実行結果
zero one two three four five six seven eight nine

また、同様の処理をポインタの配列を用いて記述することができます。

int main(void) {
  char *p[3] = { "zero", "one", "two"  } ;

  printf("%s\n", p[1]); //文字列へのポインタから[i]番目の文字列を表示します。
}
実行結果
one

上記コードのようにポインタ形式で *p[3] で定義した場合の数値「3」の意味は、文字配列の要素数です(文字列長ではありません)。 なので、たとえば"one"を"onevvvvvvvvvvvv"のように長くしても、コンパイラの許可する範囲内の長さならエラーになりません。 また、当然だが要素数を足していって "three", "four" などと足していくと、宣言時の3個ぶんの確保をオーバーするのでエラーになります。 ともかく上記コードのようにポインタ形式で宣言することにより、宣言時の書式の次元とprintfなど利用時の書式が次元が一致するので、用途によっては、ポインタ方式で宣言するのもよいでしょう。

文字列へのポインタの配列の使用例
#include <stdio.h>

int main(void) {
  //ポインタの配列に0から9までの数字の英語の名前を格納する。
  const char * p[10] = {
    "zero",
    "one",
    "two",
    "three",
    "four",
    "five",
    "six",
    "seven",
    "eight",
    "nine"
  };
  //文字列へのポインタから[i]番目の文字列を表示する。
  for (int i = 0; i < sizeof p / sizeof * p; i++)
    printf("%s ", p[i]);
  printf("\n");
}
実行結果
zero one two three four five six seven eight nine

文字列操作の関数

編集

ワイド文字列

編集

C言語はワイド文字列をサポートしています。ワイド文字列はwchar_t型の配列として定義され、(少なくとも)16ビットの値を持ちます。文字列の前にLを付けて次のように書きます。

wchar_t *p = L"Hello world!";

この機能により、256種類以上の文字が必要な文字列が可能になります(可変長のchar文字列も使用可能です)。これらの文字列はゼロ値のwchar_tで終わります。これらの文字列は<string.h>の関数ではサポートされていません。その代わり、<wchar.h>で宣言された独自の関数を持っています。

char8_t, char16_t, char32_t型

編集

ワイド文字は、型のサイズや文字集合とエンコーディングが規定されておらずプラットフォームおよび処理系依存でした。 この問題を統一的な文字集合とエンコーディングの体型を考案し解決すべく、Unicodeが国際規格として登場し、CもC11から言語仕様レベルで対応が始まりました。

尚、JIS規格の、『プログラム言語C』はC99の和訳なのでUnicodeへの言及はありません。

ISO/IEC 9899:2011(通称C11) では、新たに2つの文字型 char16_tchar32_t が導入されました[2]。 これらはそれぞれ UTF-16 と UTF-32 を内部表現とします。 u'c'U'c' あるいは u"str"U"str" のように小文字の u あるいは大文字の U を前置することで、それぞれ char16_tchar32_t の文字定数・文字列リテラルを表現します。

ただし、C23で明文化されましたが、code>char16_t も char32_tコードポイントの値が 1 つのコードユニットとして符号化できる場合に限ります。そうでない場合は2つのコードユニットのペア「サロゲートペア」としてエンコードされます[3]

また、u8 を前置することで UTF-8 の文字列リテラルを表現します[4]が、C11には、char8_t 型は存在せず、従来の char 型で代用することにしました。 これは単純で大きな間違いで、Cではcharの符号の有無を実装依存としており、UTF-8のcode unitの値域は0x80から0xffの範囲にも及んでおり、符号の有無が曖昧なcharでは表現できることが担保できないのです。 このため、C23ではchar8_t 型が定義されました(後述)。

char16_tおよびchar32_tはそれぞれuint_least16_tおよびuint_least32_tのtypedefエイリアスです。

char16_t型のサイズは16ビットよりも大きい可能性があるが、格納される値は16ビット幅です[5]。同様に、char32_t型のサイズは32ビットよりも大きい可能性があるが、格納される値は32ビット幅である[6]

char16_t の使用例
#include <stdio.h>
#include <uchar.h>

int main(void) {
  char16_t c16s[] = u"👿"; // または u"\U0001F47F"
  printf("%zu UTF-16 code units: [ ", sizeof c16s / sizeof *c16s);
  for (size_t n = 0; n < sizeof c16s / sizeof *c16s; n++)
    printf("%#x ", c16s[n]);
  printf("]\n");
}
実行結果
3 UTF-16 code units: [ 0xd83d 0xdc7f 0 ]
この様にUTF-16の配列からは、サロゲートペアはコードポイントを得ることができません。

char8_t型

編集

2022年10月現在策定中のC23では、UTF-8 を格納する方として正式にchar8_t 型が定義されました[7]

UTF-8 文字定数は char8_t 型です。UTF-8 文字定数は、16 進数または 8 進数のエスケープシーケンスで生成されない場合、その値は ISO/IEC 10646 のコードポイント値に等しく、ただしコードポイント値が単一の UTF-8 コードユニットとして符号化されることが条件です。それ以外の場合、UTF-8 文字定数の値は、16 進数または 8 進数のエスケープシーケンスで指定された数値になります。

文字エンコーディング

編集

charwchar_tがどのような文字エンコーディングを表すかは、C言語の標準では規定されていませんが、0x00と0x0000という値は文字列の終わりを示しており文字ではありません。 文字エンコーディングの影響を直接受けるのは、入力コードと出力コードです。その他のコードはあまり影響を受けないはず…という仮定でプロクラムを作るとエンコーディングの種類によっては(例えば ShiftJISでは)1つの文字の2バイト目に '\ の様なエスケープ文字が現れた時、正しくエンコーディング出来ず、場合によってはバッファオーバーラン等を引き起こします。また、ソースコードに文字列を書き込めるようにするためには、エディターもエンコーディングに対応していなければなりません。

符号化方式には大きく分けて3種類あります。

  • 1文字に1バイト。通常はASCIIをベースにしています。文字数は255文字までで、これに0の終端文字を加えたものになります。
  • 可変長のchar文字列で、255種類以上の文字が使用できます。このような文字列は、通常のcharベースの配列として書かれます。これらのエンコーディングは通常ASCIIベースで、UTF-8やShiftJISなどがその例です。
  • ワイド文字列。wchar_tの値の配列です。UTF-16は最も一般的なエンコーディングで、可変長であるため、1つの文字が2つのwchar_tになることもあります。
    • ワイド文字列にUTF-16を選んでしまった場合(WindowsやJavaが該当します)、サロゲートペアの存在が問題になります。サロゲートペアは一部の文字を16bit整数2個で表す機能で、1文字がwchar_tの配列の要素に対応する前提が崩れていまいます。典型的なサロゲートペアでエンコードされた文字にemoji(👿など)があります。

文字列をあつかうユーザ定義関数

編集

ユーザ定義関数の引数に文字列を使いたい場合、例えば下記のようになります。

#include <stdio.h>
#include <string.h>

// 文字列を引数とする関数
void test(const char s[]) {
  puts(s);
}

int main(void) {
  char s[20] = "Never used";
  strcpy(s, "test"); // 文字列をコピーする標準ライブラリー関数

  printf("char 関数の実験\n");
  test(s);
  test(s);
}
実行結果
文字列をあつかうユーザ定義関数の実験
test
test
文字列は配列であるので、関数の定義側の引数も、配列またはポインタにする必要があります。
註記
関数宣言で、
引数を要素数を明示
void test(const char s[20]) {
としても、呼出し側で宣言の要素数を超える文字配列を渡しても標準C言語としてはエラーの対象ではなく、警告されることもないので、間違った期待をさせてしまわないよう
引数を要素数を明示しない
void test(const char s[]) {
とすべきです。

脚註

編集
  1. ^ JIS語では逆斜線表記
  2. ^ C11: WG14/N1570 Committee Draft — April 12, 2011 ISO/IEC 9899:201x. ISO/IEC. (2011-04-12). p. 398,§ 7.28 Unicode utilities. http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf. 
  3. ^ N3054 working draft — September 3, 2022 ISO/IEC 9899:2023 (E) p65, § 6.4.4.4 Character constants 13,14
  4. ^ 文字列リテラル - cppreference.com
  5. ^ char16_t - cppreference.com
  6. ^ char32_t - cppreference.com
  7. ^ N3054 working draft — September 3, 2022 ISO/IEC 9899:2023 (E) p65, § 6.4.4.4 Character constants 12