注意:以下の説明では,文字 と 「文字列」という用語を,厳密に区別して使用している.
コンピュータでの文字の処理においては,アルファベット,漢字,数字,ひらがな,カタカナ,記号などに数値を割り当て,文字と数値とを対応づけて処理が行われる.
この「文字」⇔「数値」の対応は文字コードと呼ばれ,言語や国によって様々な種類が規定されている.
日本語では,JIS,ShiftJIS,EUC,UNICODEなどが採用されている.
一番単純な文字コードは,以下に示すASCII(アスキー)コードと呼ばれるものである.
アスキーコードは英数字や記号に1バイト分の数値を割り当てたの最も基本的なものであり,プログラムで扱いやすい.
例えば,大文字の 'A' の文字コードは 0x40 + 0x01 = 0x41,小文字の 'z'の文字コードは 0x70 + 0x0A = 0x7A である.
左側から2列の最初の32文字(0x00 - 0x1F)は,改行やタブなどの画面制御用コードと呼ばれ,表示可能ではないため空白になっている.
また,0x20は何も印字されていないように見えるが,半角スペース文字である.
詳細なASCIIコード表はこちら(外部サイト)
C言語では,文字(1文字)を格納するための変数が用意されており,char 型 (=character,チャー型,キャラ型,文字型など)と呼ばれる.
char 型は文字を扱うための変数であるが,実体は8ビット符号なし整数であり,0∼255(16進数で0x00-0xff)の整数である.
ソースコード中において,以下の三つの記述は全く同じ値となる.
'A'0x4165
以下のコードを実行し,ソースコード中での文字の扱いを確認しよう.
c,n の値を変更し,画面に表示される文字が変化することを確認しよう. c に整数を加減算をしてみよう.)
'\n'や,タブ'\t' の文字コードが,10進数および16進数でいくつになるか,調べてみよう.#include <stdio.h>
int main(void)
{
int n = 0x5A; // 整数型
char c = 'k'; // 文字型
// %d : 整数(10進数)として表示
printf("n = %d , c = %d \n", n, c);
// %X : 整数(16進数,大文字)として表示
printf("n = 0x%X , c = 0x%X \n", n, c);
// %c : 文字として表示
printf("n = %c , c = %c \n", n, c);
return 0;
}
ここでは,「文字」ではなく「文字列」の説明を行う.
char型は,1文字を格納する変数である.
では,「abc」 や 「Meiji」 など,2文字以上からなる単語や文章,即ち「文字列」をどう扱えばよいだろうか.
実は,C言語では,char型の配列を用いることとなっている.
即ち,「文字列」=「char型の配列」ということである.
Cの配列では,そのサイズはコンパイル時に決めておく必要があるため,以下のような工夫をする.
char 配列を準備.
この特別な記号のことをヌル文字または
ヌル文字はソースコード中で '\0' と表され,ASCIIコードでは文字コード 0x00 が割り当てられている.
ソースファイル中では,以下のように記述され,すべて等価である.
'\0' または '¥0' : 文字としてのヌル文字.普通はこれを使う.0x00 : ASCIIコードでのヌル文字に対応する文字コード.0 と書いても同じ.NULLが使えてしまう場合があるが,正しくない.これはヌルポインタ(nullptr)と呼ばれ,用途が異なる.
ただし,ヌルポインタにも数値の0が割り当てられることが多いため,結果としてヌル文字として使えてしまう場合があるが,文法的には正しくない.
文字列は,以下のようにダブルクォーテーションで囲まれた区間で表される.
"Meiji"
ダブルクオーテーションで括られた部分を文字列定数という.
この表現では,"Meiji" の文字数は 5 文字であるが,最後にヌル文字が自動的に追加されて 5+1=6 文字分の配列が確保される.
このため,配列はヌル文字分を含めたサイズで確保する必要がある.
char str[6] = "Meiji"; // 要素数は 6 以上であればOK. 自動で str[5]=='\0' が設定される.
char str[] = "Meiji"; // サイズを省略した場合,ヌル文字含め,6文字分の配列が自動で設定される
もちろん,配列を余分に確保しておこくとは問題ない.
以下の場合,str[0]=='M', str[1]=='e', ... , str[4]=='i', str[5]=='\0' であり,str[6]以降str[99]までは不定である.
char str[100] = "Meiji"; // str[5]=='\0' となる.それ以降は不定
キーボードからchar型配列に文字列を入力する例を以下に示す.
scanf() 関数において,書式指定文字列に %s を使用する.
例:キーボードから文字列の入力
#include <stdio.h>
int main(void)
{
char str[20]; // ヌル文字を含めて 20 文字分の文字列
printf("Input a string : ");
scanf("%s", str); // %s は文字列用の書式指定
// str は既にアドレスなので,& は不要
// scanfが,文字列の最後にヌル文字を追加する.
// ここで,キーボードから Hello と入力
printf("%s \n", str); // printf関数は自動的にヌル文字の手前まで表示.ヌル文字自体は表示しない.
return 0;
}
実行例:
input a string : ABC
ABC
input a string : 100
100
この例では%sを使用して,文字列として入力するので,「100」と入力した場合は「'1'の文字コードに相当する整数」「'0'の文字コードに相当する整数」「'0'の文字コードに相当する整数」(+ヌル文字)が配列strに格納される.
画面に表示するだけであれば文字でも数値でも同じであるが,演算や大小比較をを行うためには数値でなければならず,文字列のままでは計算できない.
一方,scanf()関数のフォーマット文字列に%dや%fを使用すると,入力された文字列をscanf()関数が文字列を解析して整数や実数に適切に変換する.
scanf()関数で文字列として読み込んだものを,あとから数値に変換する方法,atoi(), strtol() もある.
scanf()のフォーマット指定を,"%s" から "%[^\n]%*c" に変更するとどうなるか.str[]のサイズ(=20文字)よりも長い文字列をキーボードから入力するとどうなるか試してみよう.
キーボード入力ではどのような文字・数値が入力されるかわからない.
特に,文字配列を超える文字数の文字列が入力されるとメモリの不正アクセス(バッファーオーバーフローという)になるので,scanf()では入力される文字数を制限することができる.
このテキストの説明では文字数の制限についてはあまり考慮しないが,実用的なプログラムではこのような入力文字数の制限は重要である.
#include <stdio.h>
int main(void)
{
char str[21]; // 21 文字分の文字列(ヌル文字含む)
printf("Input a string : ");
scanf("%20s", str); // %20s は,20文字で打ち切り
// ここで,キーボードから文字列を入力
printf("%s\n", str);
return 0;
}
C言語の配列は,連続したメモリ領域に配置されることが保証されている.
配列は添字 0 が先頭アドレスに配置され,添字順にアドレス値が順次増加する.
従って,以下のような文字列の設定が可能である.
#include <stdio.h>
int main(void)
{
char a[50]; // 50文字分の文字列
int i;
for(i=0; i<5; i++) { // 5文字分の値を書き込む
a[i] = 'A'+i; // 'A' の実体は数値! 文字コード表参照
}
a[5] = '\0'; // 最後にヌル文字を置く.a[5] = 0x00; と書いてもOK.
printf("%s \n", a);
return 0;
}
この例では,文字列 a[] の,メモリ中の配置は,
となる.(最後に必ずヌル文字が必要.)
printf 関数以降にコードを追加し,配列の各要素をすべて整数として画面に順次表示せよ.実行例: a[0] = 65 a[1] = 66 a[2] = 67 . . . a[48] = ???? a[49] = ????
文字列の最後に必ずヌル文字が入る事を利用して,以下のように文字列要素の全てを1文字ごとに走査(スキャン)できる.
#include <stdio.h>
int main(void)
{
char str[] = "Hello Meiji!"; // 文字列
int i=0;
while( str[i] != '\0' ) {
if(str[i] == 'M') {
printf("Found character M! %d th character = %c", i+1, str[i]); // %c は,1文字表示
}
i++;
}
printf("\n");
return 0;
}
for文を用いる場合は,以下のように書くこともできる.
for(i=0; str[i] != '\0'; i++ ) { ...
ヌル文字'\0' の文字コードは 0x00 = 0 であり,これは「偽」と等価であることを利用して,while, for の条件判定部はさらに省略できる.
while( str[i] ) {
// ヌル文字(=0x00)に到達したら「偽」になるので,このループが終了する
i++;
}
この方法は便利であるが,手動で文字列を生成した場合など,文字列の最後にヌル文字を追加し忘れるとループ処理が正しく動かないので注意を要する.
char 型変数 = 8 bit 整数型
半角英数字を表すには,ASCIIコードで充分であるが,日本語で使用する漢字やカタカナ,ひらがな等の全角文字の場合は文字種類が非常多いため,char型 = 8bit = 256通り,では到底不足する.
いろいろな歴史的経緯があるが,現在日本語コードとしては,JISコード(ISO-2022-JP)(電子メールで使用),Shift JISコード(以前のWindows標準),EUCコード(おもに旧式のUNIX)が使用されてきたが,最近では,世界共通のUNICODE(最近のOSや,このWebページ)など,多くの体系が現在も混在して利用されている.
ここでは扱わないが,漢字などの全角文字では複数バイトで1文字を表すワイド文字型・マルチバイト文字型が使用されている.
以下のプログラムで全角文字の内部表現を確認してみよう.
(.cppファイル自体の文字エンコードによっては,異なる結果となる可能性がある.)
#include <stdio.h>
int main(void)
{
const int N = 30;
// char str[N] = "AAAbcdefg"; // 半角文字
char str[N] = "あああいうえお"; // 全角文字
int i;
for(i=0; i<N; i++) {
int k = (unsigned char)str[i]; // 一旦,別の変数にキャストして代入
printf("str[%d] = %d , 0x%X\n", i, k, k); // 10進数,16進数で表示
}
return 0;
}
実行結果はOSやPCの環境に依存するが,おそらくchar配列数個分で,全角1文字を表現する結果となるはずである.
このようなマルチバイト文字の文字コード判定や使い分け,文字列の比較・変換処理には高度で複雑な処理が必要となるため,ここでは半角の英数字 + 記号のみ取り扱う.
C言語では文字列は,文字型の配列であるため,文字列の操作によくある「複製」や「連結」,「検索」などがやや面倒である.
そこで,文字列操作に特化した便利なライブラリ関数が用意されている.
いくつかを実際に使用してみよう.
詳しくは,C言語ランタイムライブラリ関数のリファレンスを参照.https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/string-manipulation-crt?view=msvc-170
以下は,string.h で定義されている文字列操作用のライブラリ関数である.
(ここに出てくるchar* とは,「文字列の先頭アドレス」を指すポインタ型であり,配列をそのまま渡すことができる.)
文字列操作関数は非常に多岐にわたるが,ここでは4種類のみ紹介する.
strlen()・・・文字列の長さ(Length)を返す.strcpy()・・・文字列を,別の文字列のコピー(copy)するstrcat()・・・2つの文字列を連結(catenate)する.strcmp()・・・2つの文字列を比較(compare)する.str, str1など,文字列定数の例題については,文字列の内容を適宜変更して,変化を確認しよう.int strlen(const char* s);
このプロトタイプ宣言は,「引数は文字列1つ」「戻り値は整数」を表す.
文字列の長さを返す.string lengthの略.
このとき,最後のヌル文字は長さに含まない.
#include <stdio.h>
#include <string.h> // 文字列操作関数の使用に必要
int main(void)
{
char str[] = "Abcdefg";
printf(" string = %s \n", str );
printf(" length is %d \n", strlen(str) );
return 0;
}
char* strcpy(char* dst, const char* src);
このプロトタイプ宣言は,「引数は文字列2つ」を表す.
上記の関数プロトタイプ宣言は,「引数は文字列2つ」「戻り値は文字列の先頭(アドレス)」を表す.以下同様.
文字を src(sourceの意) から dst(destinationの意) にコピーする関数である.string copy.
第1引数dst に既に文字が入っている場合は,上書きされる.
第2引数srcにはconstがつけられているため,こちらはstrcpy()によって変更されることはない.
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[] = "Meiji university";
char str2[100] = "Oh No!"; /* 100文字分の文字列. */
/* str1 から str2 へ文字列をコピー */
strcpy(str2, str1);
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
return 0;
}
char* strcat(char* dst, const char* src);
このプロトタイプ宣言は,「引数は文字列2つ」を表す.
文字列を連結する関数.string catenate.
dst に入っている文字列の末尾に,文字列 src を連結(追加)する.
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[100] = "Meiji";
char str2[100] = " university";
/* str1の最後尾にstr2を追加. */
strcat(str1, str2);
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
return 0;
}
int strcmp(const char* s1, const char* s2);
このプロトタイプ宣言は,「引数は文字列2つ」「戻り値は整数」を表す.
2つの文字列を比較する.string compare.
戻り値は
s1 == s2 の場合, 0s1 != s2 の場合,ゼロ以外の値.
である.
興味があれば,ゼロ以外の値としてどのような値が返ってくるか,自分で調べてみよう.
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[100], str2[100];
printf("str1 = ?"); scanf("%s", str1);
printf("str2 = ?"); scanf("%s", str2);
if( strcmp(str1, str2) == 0) {
printf("str1 = %s, str2 = %s are identical.\n", str1, str2);
} else {
printf("str1 = %s, str2 = %s are different.\n", str1, str2);
}
return 0;
}
文字列として入力された「100」や「-0.5」を,数値として扱うには,工夫が必要である.
例えば,文字列としての「100」(文字コード 0x31 0x30 0x30)は,char型配列3個分に文字として 1,0,0(イチ,ゼロ,ゼロ)が格納されているだけなので,数値の「百」として四則演算などを行うためには,この 文字列を数値に変換する必要がある.
文字列を整数および実数に変換するライブラリ関数( stdlib.h )は,atoi(), atof() であり,それぞれ,ascii to int, ascii to float の略である.
より高度な変換処理を行う strtol(), strtof() もある.
これはstring to long, string to float の略である.
(scanf()で %d, %f を指定する際には,文字 → 数値の変換が内部で行われている.)
#include <stdio.h>
#include <stdlib.h> // atoi用
int main(void)
{
char str[100];
scanf("%s", str); // %s = 文字として入力
int n = atoi(str); // ここで文字列を数値に変換.変換できない場合は0が返る.
printf("character : %s \n", str); // 文字列として表示
printf("number : %d \n", n); // 整数として表示
// printf("x2 = %s \n", str*2); // これはエラー
printf("x2 = %d \n", n*2); // これはOK
return 0;
}
以下の例は,キーボードから繰り返し数値を入力し,"end"が入力されたら終了する. 無限ループで文字列をキーボードから入力し,"end"であれば終了. それ以外は数値に変換し,その値を2倍した数値を表示する.
#include <stdio.h>
#include <string.h> // strcmp用
#include <stdlib.h> // atof用
int main(void)
{
char str[100];
while(1) {
printf("Input a number(type [end] to finish):");
scanf("%s", str); // 文字として入力
if( strcmp(str, "end") == 0 ) {
printf("Finish!\n");
break;
}
float r = atof(str); // 文字列を数値に変換.
printf("number*2 = %f\n", r*2); // 数値として,2倍して表示
}
return 0;
}
上記のプログラムを実行して動作を確認してみよう.
char型変数を用いて文字を表すが,その実体は整数である.'\0' と書いておくことが望ましいです.NULL と書いてはだめですか?scanf() や str???() 系の関数では,文字列に自動的にヌル文字を付加しますが,自分で文字列を設定した場合にヌル文字を忘れると,関数が思いがけない動作をします.
例えば,printf("%s", ...) では,ヌル文字を探してそこまで表示しようとするので,ヌル文字が無いと配列の境界を飛び越えて読み込もうとし,アプリケーションが不正終了する(落ちる)場合があります.
逆に,わざと終端文字を抜いて関数呼び出しして,不正アクセスさせるようなクラッキング方法(バッファーオーバーフロー)もあるので,十分注意が必要です.
strcpy() ではなく,文字数を制限できる strcpy_s() を使うことが推奨されています.
そのほか,printf_s(),scanf_s(),strcat_s() など,_s がつく関数がいくつかあります.
興味があれば調べてみてください.
std::string という便利なクラスがあります.
詳しく知りたい人は,C++言語を勉強すると良いでしょう.
ただし,その場合でも,C言語では文字列がどのように扱われているか,を知っておく必要があります.
char型ではなく,2バイト以上wchar_t型や,char16_t型とchar32_t型を使うことで実現可能です.
strlen()の動作と同じ機能を実現するプログラムを自作してみよう.
(ライブラリ関数の strlen() を使用しないこと!)
ヒント:文字列中のヌル文字の位置(インデックス)を探し,その添字の値が文字数になっているはず.
#include <stdio.h>
int main(void)
{
char str[100] = "Hello Meiji Univ.";
int end_c=0; // 配列に何文字含まれるかを見つける.
// ここで文字数をカウント
printf("Length = %d\n ", end_c);
return 0;
}
実行例
Length = 17
キーボードから文字列を入力すると,その最初の文字と最後の文字を表示するプログラムを作成しよう.
ヒント:strlen() 関数に文字列を渡すと,配列中の文字数がわかる.
#include <stdio.h>
#include <string.h> // strlen()用
int main(void)
{
char str[100]; /* 100文字分の文字列 */
printf("Input a string: ");
scanf("%s", str); /* 文字列入力.strは配列なので,&は不要. */
int n = strlen (str); /* nに文字数が入る */
...
return 0;
}
実行例
Input a string: ABCDEFG
The first char is A
The last char is G
Input a string: Z
The first char is Z
The last char is Z
上の本文中で表示したアスキーコード表と同じものを,2次元の表形式で画面に表示してみよう.
ただし,文字として表示できない改行・タブ・バックスペースなどの制御コードを出力すると表示が崩れるので,ライブラリ関数isprint()関数を用いて表示可能なものだけ表示せよ.
isprint()関数の使用法をまず各自で調べること.
Microsoft社のCライブラリ関数リファレンス(アルファベット順)
https://msdn.microsoft.com/ja-jp/library/634ca0c2.aspx
ヒント:isprint() は,文字 c を渡すと,c が表示可能な文字 (0x20 – 0x7E) である場合,0 以外の値を返す.
つまり,isprint('A') != 0 ,isprint('\n') == 0 となる.
isprint()関数の使用例.変数cの値を'A'や'\n'に変えてみよ.
#include <stdio.h>
#include <ctype.h> /* これを追加すること. */
int main(void)
{
char c='a'; /* 何か文字 */
if( isprint(c) != 0) {
printf("Printable\n");
} else {
printf("Not printable\n");
}
return 0;
}