日々の記録。

プログラミングのメモや感じた事などを記録。

C++講座 その3 文字型

char型はcharacter型の略なので、キャラ型と読むのが正解だと思う。たまに50歳代のおっさんとかがチャーとか言っているけど、ちょっと悲しくなってしまう。

以上は余談。

char型は1文字を格納する型。一般的にサイズは1byte。主にASCIIを格納するために利用される。

char ch='a';

ASCII以外の文字コード、例えばひらがなを格納しようとした場合はどうなるか?

1: char ch='あ'; // どうなる???
2: cout << ch;

os x g++でコンパイルした結果は次の通り

  • 1行目 warning: multi-character character constant
  • 1行目 warning: overflow in implicit constant conversion

実行結果(coutの結果)は、?と出力された。

前述の通り、charは一般的に1byteとして扱われるため、日本語などの多バイト文字を扱うことはできない。そのためにC/C++では多バイト文字を扱う場合には、wchar_t型(wide character typeの略?。実際にはtypedef short wchar_t)を利用する。

wchar_t ch = L'あ';
wcout << ch;

このように多バイト対応のソースを書くことによって、他バイト文字列を正常に扱う事ができる。 。。。。。。Java/C#/Rubyなどのプログラミング言語を利用していると、なんとも面倒な感じがする。

文字リテラル

文字定数の事を文字リテラルとも言う。シングルクォーテーションで囲まれた1文字のこと。 接頭辞Lをつけることでwchar_t型の文字リテラルとなり、一般的に2から4byte文字として扱われる。(コンパイラによる)

'C';
L'あ';
L'A';

char / wchar_tの問題点

charとwchar_tでは型が異なるはずなので、関数を用意する場合に2つようの関数を必要がある。

例 printf

// char用
int printf(const char* format, ...);
// wchar_t用
int wprintf(const wchar_t* format, ...);

このように多バイト文字の考慮を行おうとすると、文字を扱う全ての関数/処理について2種類用意する必要がでてくる。 これについてMicrosoft WindowsAPIでの解決方法を紹介する。

Windows API での文字の扱い

VC++などで開発している方はTCHARという型を見かけた事があると思う。 これはイメージとしては次のように定義されている。

#ifdef UNICODE
typedef wchar_t TCHAR;
#define _T(X) L##X
#else
typedef char TCHAR;
#define _T(X) X
#endif

TCHARは、UNICODEが定義されている場合はwchar_t,UNICODE定義されていない場合はcharとして扱われる。

又、関数もTCHAR用の関数群が用意されている(例えば、printfのTCHAR用として_tprintf)。char,wchar_tの代わりにTCHARを用いる事で、文字型を意識せずにプログラミングを行う事ができる。

char用

int main() {
  char ch = 'a';
  printf("%c¥n", ch);
  return 0;
}

wchar_t用

int wmain() {
  wchar_t ch = L'あ';
  printf(L"%c¥n", ch);
  return 0;
}

TCHAR用(char/wchar_t共通)

int _tmain() {
  TCHAR ch = _T('あ');
  _tprintf(_T("%c¥n"), ch);
  return 0;
}

「文字型について意識しなくて良い」という考えから、TCHARや_Tマクロ、TCHAR対応の関数を利用した方が良いと個人的に考えている。(残念ながらC/C++ではTCHARを使ったからといって他バイト文字の問題が解決するわけではないけど・・・。例えばC++標準にはTCHARというtypedefはないため、C++標準関数を利用しようとすると独自にTCHAR用の関数や変数を定義する必要がある。。。)

話は余談になるけど、「〜について気にする必要がある」というプログラミングを行い続けるなら(例「配列長を意識する」「NULLポインタを意識する」「リソースの解放漏れを意識する」等)、「〜を気にしなくて良い」プログラミングを行うようにした方がよい。気にならなくて良い方法を模索すること。

1byteデータとしてのcharの扱い

今までは文字型としてのcharの扱いを紹介してきたけど、C言語では1byteデータを表現するためにも charを使う。例えば、バイナリファイルやソケットからバイナリデータを取得する場合にもcharを利用する必要がある。

例 バイナリファイルから1024バイト読む

void f(FILE* fp) {
  const int count = 1024;
  char buffer[count];
  int result_count = fread(buffer, sizeof(char), count, fp);
}

この場合は、1byteデータを扱いたいのだから、TCHARやwchar_tは利用しないこと。(ソースコードを読むときに「1byteとしてのcharか?文字としてのcharか?」を気にする必要がある)

ちなみに、WindowsAPIの場合、文字型はTCHAR(char/wchar_t)、byteデータはBYTE(unsigned char)としてtypedefされている。

JavaC#での文字型

JavaC#の場合、char型は文字型と決まっていて、ASCIIだろうが他バイト文字だろうがcharに格納できる。

void foo() {
  char a = 'a';
  char b = 'あ';
}

C/C++と異なり文法的な差異がなくなり、文字を扱う事が容易になっている。尚、charは1バイトではなく2バイト長の型として表現される。

1byteデータを扱うためには、byte型(Cでのunsigned char)を利用する。Javaのメソッドの引数等を見ても1byteデータを扱うものはbyte、文字を扱うものはcharと明確に指定されている。

例 1024byteと1024文字を読む例(Java)

void foo() throws Exception {
  // バイナリファイルbinary.datを最大1024byte読む.
  FileInputStream in = new FileInputStream("binary.dat");
  byte[] buffer = new byte[1024];
  int result = in.read(buffer);  // FileInputStream#read(byte[])

  // テキストファイルtext.txtを最大1024文字読む.(1024byteではない事に注意)
  FileReader reader = new FileReader("text.txt");
  char[] text = new char[1024];
  result = reader.read(text); // FileReader#read(char[])
}

最後に

正直なところ、C/C++は文字や文字列の扱いが非常に苦手だと思う。文字列を扱う必要がある場合は、積極的にJavaC#,Rubyなどの言語を利用した方が良いと思う。残念ながらC/C++を使う自体がバグの元になる。 (...といってもJavaのOSSを利用しても日本語や他バイト文字が考慮されていないものも沢山ある。)