プログラムは数字を計算するだけではなく文字を操作することも できます。この章では文字列の管理や表示の方法について解説します。
数字の値を記憶する型の変数があると同様に文字の値を記憶する型の 変数があります。それがcharです。ここで「文字の値」とは各文字に あらかじめ決められた1byteの値です。現在はこの値は ASCIIによって 定められたコードである ASCII codeであるのが普通です。 例えば、文字 A の ASCII code の値は10進数では65です。 16進数では41です。プログラム中では 'A' とすることでこの ASCII code を表すことができます。
簡単な例を示します。
このプログラムの出力結果は 65 41 A です。 printf関数の%cは文字形式で出力することを意味します。 つまり与えられた変数の数値をASCII codeとして対応する文字にして 変換して表示するのです。#include<stdio.h> int main(void) { char c; c = 'A' ; printf("%d %x %c\n", c, c ); return(0); }
char型変数では1文字しか扱えません。複数の文字が連なったもの、
つまり文字列を扱うには配列変数を使います。char型の配列を用意して、
そこに順番に文字を格納していくのです。
配列の初期化を思い出してください。
int data[5] = {0,10,20,30,40};これでint型配列dataが、data[0]に0が、data[4]に40が、という 具合に初期化されます。
これと同様に文字型配列も初期化できます。
char string[5] = {'a','b','c','d','e'};これで配列 string は abcde になったわけです。
しかしこれは記述が面倒です。次のように簡単に記述することもできます。
char string[6] = "abcde";"abcde"の文字列はプログラマの見えない別な 所にあらかじめ保管されていて、それがここで内部コピー関数に よって配列にコピーされるのです。 この時、配列stringの6文字目に'\0'という特殊な文字が自動的に代入 されます。これはnull terminater と呼ばれる文字列の終端を表す文字 なのです。
次のように文字列を代入することはできません。
char string[6];しかし次のようにすると"代入"できます。
string = "abcde";
char* string;前者のstringが配列変数の先頭ポインタであるのに対して 後者のそれは自由なポインタです。"abcde"の文字列の先頭アドレスを ポインタに代入することで結果的に配列に文字列が格納されたことと 同様になるのです。ただしこの文字列を書き換えたり追加したり することは危険です。期待通りに行われる保証は全くありません。
string = "abcde";
文字列をprintfで出力するときにはこうします。
printf("%s\n", string );配列の名前そのものは、その配列の先頭アドレスを表すことを 思い出してください。%sとすると、先頭アドレスから順に文字を 表示していって'\0'は表示しないで終了します。このためにも null terminater '\0' が必要なのです。
2次元配列を使うことにより複数の文字列を一挙に扱うことができます。
char strings[5][10] = {"Red","Blue","Green","Yellow","Pink"};strings[][]の1番目の数字は文字列の数であり、2番目の数字は 個々の文字列の長さの最大値です。この文字列を表示するには 次のようにします。
int i; for( i=0 ; i<5 ; i++ ){ printf("%s\n", strings[i] ); }
ポインタ配列を利用して複数の文字列を管理することができます。
char* ptr[5] = {"Red","Blue","Green","Yellow","Pink"};2次元配列にコピーしたものと全く同様に扱うことができます。 ただしこれらの文字列を書き換えたり追加したり することは危険です。期待通りに行われる保証は全くありません。
一般に配列のすべての要素の値を他の配列の要素に一度にコピーする
ことはできません。要素一つ一つをコピーしていくしかありません。
文字列のコピーも同じです。そのため専用の文字列操作関数がいくつか
string.hに用意されています。
char* strcpy( char* str1, const char* str2 );
str1はコピー先の文字配列の先頭アドレスです。
str2はコピー元の文字配列の先頭アドレスです。
返り値はコピー先の文字配列の先頭アドレスです。
char a[10],b[10],c[10]="abcde"; strcpy( a, c ); strcpy( b, "fghij" );
char* strcat( char* str1, const char* str2 );
str1は連結先の文字配列の先頭アドレスです。
str2は連結元の文字配列の先頭アドレスです。
返り値は連結先の文字配列の先頭アドレスです。
この関数はstr1の配列の'\0'のところからstr2の文字列を コピーしていきます。したがって、str1はstr2の文字列を 付け足せるのに十分な配列でなければなりません。
使用例:char a[10]; strcpy( a, "abc" ); strcat( a, "def" );
int strcmp( const char* str1, const char* str2 );
str1,str2は比較する文字配列の先頭アドレスです。
返り値は比較の結果です
この関数は2つの文字列が辞書順にしてどちらが先かを調べます。 str1の方が後ならば正の値が返り、str1の方が前ならば負の値が 返ります。同じ文字列ならば 0 の値が返ります。その値は具体的には 最初の異なる文字のascii codeの引き算です。
使用例:char a[10],b[10]; strcpy( a, "abc" ); strcpy( b, "acd" ); if( strcmp( a, b ) == 0 ){ printf("a is equal to b. \n"); }
char* strncpy( char* str1, const char* str2, int n );
char* strncat( char* str1, const char* str2, int n );
int strncmp( const char* str1, const char* str2, int n );
文字列を入出力する関数の代表格であるprintf関数とscanf関数には 実にさまざまな細かい設定があります。それらの紹介と他の 文字列入出力関数を紹介します。
int printf( const char* format [, arg1,arg2,... ] );
formatは書式を制御する文字列です。
arg1,arg2,...は出力する変数の値です。
返り値は出力した文字数です。
第1引き数である固定文字列が表示されます。
'%'以降の文字は書式制御文字として第2引き数以降の引き数に 対して特別な意味を持ちます。printf("hello, world!\n");
'%'の後ろの'd'の持つ意味は、第2引き数のaの値がsigned int型として 10進数でここに表示されるということです。この'd'のような 文字を型指定文字と呼びます。int a = 10; printf("a = %d \n", a );
unsigned int a = 10; printf("%d %u %o %x %X \n", a,a,a,a,a ); 結果:10 10 12 a A
float f = 3.14; printf("%f %e %E %g %G \n", f,f,f,f,f ); 結果:3.14 3.14e+00 3.14E+00 3.14 3.14
int *p; printf("%p \n", p ); 結果:0000
double d = 3.14; printf("%lf\n", d ); 結果:3.140000
小数に対するこの指定の文字数には符号、小数点、e、指数部も 含まれます。int a = 10, b= 100; float f = 3.14; char str[10] = "abc"; printf("%5d %5d %5f %5s\n", a, b, f, str ); 結果: __10 __100 _3.14 __abc
float f = 3.14159; char str[10] = "abcde"; printf("%7.2f %7.3s\n", f, str ); 結果: ___3.14 ____abc
int size = 5; int a = 10; float f = 3.14159; printf("%*d\n", size, a ); printf("%*.*f\n", size, size, f );
int a = 10; printf("%-4d %+d % d \n", a,a,a ); 結果:10__ +10 _10
int scanf( const char* format[, arg1,arg2,... ] );
formatは書式を制御する文字列です。 arg1,arg2,...は入力する変数のアドレスです。 返り値は正常に読み込まれた項目数です。
キーボードから入力した文字列がformatの書式に従って後続の引数に 格納されます。formatの書式はprintfとだいたい同じですが、 さまざまな規約があります。
char str[10]; scanf("%s", str ); int a; if( scanf("%d", &a ) != 1 ){ printf("data mismach!! \n"); rewind(stdin); } int a,b; if( scanf("%d %d", &a,&b ) != 2 ){ printf("data mismach!! \n"); rewind(stdin); }
実践的な例を示します。物理などの実験では、計測器がファイルに 出力した大量のデータから必要なデータだけを取り出してそれを 別のファイルに格納する作業が必要になります。
ファイルの操作には一般には次章で説明する特別な技術が 必要なのですがここではOSのリダイレクション機能を用いて それを簡単に行います。
ある実行ファイル filter を次のように実行します
filter < original.dat > result.datこうするとデータファイル original.dat に格納されている内容が OSによって自動的にfilterプログラムに打ち込まれて、scanf()関数や gets()関数よってプログラムに読み込むことができます。またこの プログラムがprintf()関数などで出力する結果は画面に表示されずに result.dat という名前のファイルが作られてそこに格納されます。
こうすることで original.dat の内容を読んで適切に処理して 結果を result.dat に格納することができます。
例えば original.datの内容が次の様に一行に4つの小数がそれぞれ カンマで区切られて並んでいるとして、これの各行の第1成分と第2成分 のみを取り出してresult.dat に格納したいとしましょう。
123.4, 23.5, 58.0, 589.0 125.8, 21.0, 52.5, 623.1 128.3, 19.6, 46.2, 656.2 131.5, 16.2, 42.3, 685.6プログラム filter.c を次の様に作れば期待の処理ができます。
#include <stdio.h> int main( void ) { double d1,d2; while( scanf("%lf, %lf, %*lf, %*lf\n", &d1, &d2 ) != EOF ){ printf("%4.1lf %4.1lf\n", d1, d2 ); } return(0); }
scanf()関数の第一引数の文字列の書式を original.dat の一行の
書式と同じにします。つまり4つの小数がカンマとスペースで区切られて
最後に改行があるという書式です。4つの項目のうち第3,第4項目については
値を変数に格納する必要がないので読み飛ばすことを指定する*を付けます。
この各行ごとの操作をscanfが読み込みできなく
なるまで続けます。これでできあがりです。