①配列とポインタによる文字列操作
初心者がつまづきやすいこの問題は、まず
配列の初期化と代入は別物であるという認識から始めようと思う。
#include<stdio.h> main(){ char a[]="ABCDEF"; int i; for(i=0;a[i]!=0;i++) printf("%c",a[i]); printf("\n"); } 結果 ABCDEF
非常に脆弱性を抱えているがひとまず置いてこの方法は成功する。
ポインタでも同様である。
#include<stdio.h> main(){ char *a="ABCDEF"; int i; for(i=0;a[i]!=0;i++) printf("%c",a[i]); printf("\n"); } 結果 ABCDEF
だが、
#include<stdio.h> main(){ char a[6]; int i; a="ABCDEF"; for(i=0;a[i]!=0;i++) printf("%c",a[i]); printf("\n"); }
代入するとコンパイルエラーとなる。
もう一度言うが初期化と代入は別物である。
では上のエラーコードの配列をポインタに直してみたらどうか?
#include<stdio.h> main(){ char *a; //*配列からポインタへ*// int i; a="ABCDEF"; for(i=0;a[i]!=0;i++) printf("%c",a[i]); printf("\n"); } 結果 ABCDEF
成功した。これはポインタの「アドレス」に代入しているため可能となる。
また、int型にも応用できるようにするためfor文でループさせていたが
printf内において、文字列なら%sで一度に全て出せる。
#include<stdio.h> main(){ char a[]="ABCDfsadfEF"; printf("%s",a); printf("\n"); } 結果 ABCDfsadfEF
もう一つ、C言語には、文字列方という型が存在しないというのはよく耳にするが、
文字列定数という概念は存在する。
初めに言うと文字列定数もポインタである。
メモリの項id:computermonkey:20051210 でも述べたように定数は宣言時に変数とは違うセグメントに配置される。
#include<stdio.h> main(){ char *p; p="ABCDEF"; printf("p=%s,*pc=%p\nABCDEF=%p\n",p,*p,"ABCDEF"); } 結果 p=ABCDEF,*pc=00000041 ABCDEF=0040A146
こんな感じで。
決して、
#include<stdio.h> main(){ char *p; p="ABCDEF"; if(*p=="ABCDEF") {puts("success");} }
こうやってはいけない。常に偽になる。
pとABCDEFの値を真にするには
#include<stdio.h> main(){ char *p; p="ABCDEF"; if(*p==*("ABCDEF")) {puts("success");} } 結果 success
こうすべきである。
さて、これらをふまえ文字列操作をする。
簡単に行うにはstrcpy関数を使えばよいが細部を知るためにあえて使わないことにする。
#include<stdio.h> void mystrcpy(char *a,char *b); #define N 10 main(){ int i; char carray[]="ABCDEFGH"; char carray2[]="jgjdfgjdfgjdghhgk"; mystrcpy(carray2,carray); for(i=0;carray2[i]!=0;i++) printf("carray2[%d]=%c\n",i,carray2[i]); } void mystrcpy(char *a,char *b) { int i; for(;b[i]!=0;a++,b++) *a=*b; a[i]='\0'; } 結果 carray2[0]=A carray2[1]=B carray2[2]=C carray2[3]=D carray2[4]=E carray2[5]=F carray2[6]=G carray2[7]=H
これは適当な文字列の入ったcarrayとcarray2から、strcpy関数と同じ機能を持たせたmystrcpyへ文字列を引き渡し、carray2の中身を表示するプログラム。
mystrcpy内ではbの値をaの値に代入することで文字列をコピーする。
しかしループ判定がnullの時にループ終了するので、最後にnull文字が入らない。
だから最後にa[i]='\0'でnullを挿入する。
入れない場合を見てみる。
#include<stdio.h> void mystrcpy(char *a,char *b); #define N 10 main(){ int i; char carray[]="ABCDEFGH"; char carray2[]="jgjdfgjdfgjdghhgk"; mystrcpy(carray2,carray); for(i=0;carray2[i]!=0;i++) printf("carray2[%d]=%c\n",i,carray2[i]); } void mystrcpy(char *a,char *b) { int i; for(;b[i]!=0;a++,b++) *a=*b; } 結果 carray2[0]=A carray2[1]=B carray2[2]=C carray2[3]=D carray2[4]=E carray2[5]=F carray2[6]=G carray2[7]=H carray2[8]=f carray2[9]=g carray2[10]=j carray2[11]=d carray2[12]=g carray2[13]=h carray2[14]=h carray2[15]=g carray2[16]=k
このようにもとのデータが出てきてしまう。
ループを配列で書いたmystrcpy関数をポインタで書くとこうなる。
void mystrcpy(char *array2,char *array) { for(;*array!=0;) *array2++=*array++; *array2=0; }
これでも上記と同様になる。
Cの聖書と言われるANCII Programming Cでカーニハンの有名な文字列移動コードはこんなのである。
void mystrcpy(int *array2,int *array) { while(*array++=*array2++) ; }
ここまでやるのはやり過ぎだと氏も仰ってたが非常に美しいコードである。
whileのループがnullになれば自動的に終了というわけだが、
分からなければ気にしないでもよい。
色々な書き方があるということだ。