第25回
データ構造(4)~文字列をポインタで扱う

文字を演算する

Cでは半角の英数記号――いわゆる1バイト文字をchar型の値として扱い、それの連なったものが文字列になります。文字を『文字コードを保持する値』として扱うことで、演算によって大文字/小文字の変換ができます。

ポインタは文字列に有効

Cのポインタは、配列を扱う際に強力な武器となります。前回説明したように、「アドレスと型を保持する」ポインタの機能は、特に文字列の扱いに有効です。

数値型の配列では、多くの場合先頭から順に1つずつ要素を参照したり、何番目の要素かを指定して値を参照するような処理がほとんどです。そのため、添字で配列の要素を指定する方法で問題なく対処できます。

しかし、文字列──char型配列の場合は「何文字前」とか「何文字後ろ」といった相対的な位置の指定が必要となるため、配列の要素を指定するより『今着目している要素から何文字分前後するか』という操作の方が効率的になるのです。

とは言え、Cでは文字列もchar型の配列──つまり数値の並びとして扱われます。数値も文字も同じように扱われるのがCの特徴です。Cという言語のレベルでは、値はすべて数値です。それを文字コードという「文字を表す番号」として扱うのはCの都合ではなく、あくまでプログラマーの都合ということになります。

大文字と小文字の『差』

文字列とポインタの説明に入る前に、Cでの文字の扱いについて触れておきましょう。

Cはchar型の値を特別に文字として扱うわけではありません。ある値を『数値として認識するのか、それとも文字として認識するのか』は、プログラマーの意図によります。そして、それゆえに「文字の演算」が可能となります。

つまり、文字を数値として演算し、その演算結果をまた文字として扱う──といったことができるのです。

1つの文字を表す文字コードを数値として扱い、それを演算の対象にできるのはCの大きなメリットです。例えば、大文字の'A'の文字コードは16進数で41(0x41)、小文字の'a'は16進数で61(0x61)です(表1)。文字コード表では、ABC...abc...と、それぞれ続いていくので、
大文字と小文字の値の差は16進数で20(0x20)
という規則が当てはまります。すると
小文字を大文字に変換するには文字コードから0x20を減算
大文字を小文字に変換するには文字コードに0x20を加算
という処理が一般化できます。

01234567
0 NUL DLE SP
1 SOH DC1
2 EXT DC2
3 EOT DC3
4 EOT DC4
5 ENQ NAK
6 ACK SYN
7 BEL ETB
8 BS CAN
9 HT EM
A LF SUB
B VT ESC
C FF FS
D CR GS
E SO RS ~
F SI US _ DEL
表1:ASCII文字コード表

1文字の大文字/小文字変換

引数に指定した1文字(1バイト=char型変数)を小文字と仮定して大文字に変換する関数"toupper"は、リスト1のように記述できます。同様に、引数を大文字と仮定して小文字に変換する関数"tolower"はリスト2のように記述できます。

また、Cでは#defineプリプロセッサ指令で文字列を置き換えることができ、引き数付きのマクロを定義できるので、リスト3のようなマクロとすることも可能です。

リスト3のようなマクロでは、ソース中の
toupper(c);
のような記述が、コンパイラを通す前の段階で
(c -= 0x20);
というコードに置き換えられます。このような変換の形を「インライン展開」と呼びます。

ただし、このようにすると引数に''で囲んだ文字定数は使えません。
toupper('a');
というソースコードが
'a' -= 0x20;
と展開され、文字定数に値を代入することになるのでコンパイル時にエラーが発生します。あくまで変数を引数とすることが前提となります。

リスト1:小文字を大文字に変換する関数
char toupper(char c)
{
  return (c - 0x20);
}

リスト2:大文字を小文字に変換する関数
char tolower(char c)
{
  return (c + 0x20);
}

リスト3:大文字/小文字変換のマクロ
#define toupper(c) (c -= 0x20)
#define tolower(c) (c += 0x20)