文字列の中に全角スペースで区切った単語が複数あります。
全角スペースを検索することにより、単語ごとに出力したかったのですが上手く動作しませんでした。
出力する単語によっては文字化けが発生します。
文字化けしないようにするにはどうしたらよいでしょうか?
(文字コードはEUCです。)
>||
int main()
{
int i;
char words[100] = "飛行機 電車 train ";
char *pt1, *pt2
pt1 = pt2 = words;
for(i=0; i<3; i++){
pt2 = strstr(pt1, " ")
*pt2 = "\0";
printf("%s\n",pt1);
pt1 = pt2 + 2;
}
return 0;
}
||<
文字列を処理する関数で全角を利用するのに注意が必要であることは調べてわかったのですが、実際にどのように処理すればいいのかが調べきれませんでした。よろしくお願いします。
strstr関数は半角1文字だけ検索するので予想通りに動作しなかったと思います。
解決方法としては
1.文字列の間を「半角スペースにする」
2.pt2 = strstr(pt1, " ")の部分を
int j;
for (j = 0; j < strlen(pt1); ++j) {
if (0 == strncmp(pt1, " ", 2) {
pt2 = pt1;
break;
}
++pt1;
}
という風に全角文字列で比較するように変更する
のどちらかですかね。構造的には1の方が簡単ですね。
このコードをそのまま動かしたらコンパイルすら出来ないと思いますが・・・。
まぁそこら辺は適当に修正して、コンパイルしてみました。
まず修正するべきは、for文の中にある「*pt2 = "\0";」で、これは「*pt2 = '\0';」で意図する結果がでると思います。
動作&コンパイル環境、その状況などがわからないので文字化けについてはコメントしづらいです。
すみません。
家だとコンパイルする環境を作ってないので、適当にコードを書いてしまいました。
知りたいのは文字化けのとこと、文字列を処理する関数での全角の扱い方ですので、よろしくお願いします。
ありがとうございました。
http://www.ncad.co.jp/~komata/c-kouza21.htm
こちらにstrstrではないですがstrtokを使ってEUCを処理するとハマるパターンについて書かれています。
文字化けするのはEUCのコードの中に0xa1が含まれていて、全角スペース(0xa1a1)と間違ってしまうケースですね。strstrだとたまたま0xa1a1という並びがあると文字の途中でも切れ目だと認識してしまうため文字化けします。
マルチバイト文字列を扱うのは結構大変です。標準的な(UNIX系な)Cであれば
http://always-pg.com/c/runtime_rd/string/mbstowcs.html
あたりにあるようにマルチバイト文字列をワイド文字列(2バイト)に変換して、ワイド文字列を扱うwcsstrなどで検索する必要があります。
http://always-pg.com/c/runtime_rd/string/strstr.html
Windows限定だと_mbsstrが使えそうですが。
http://www.microsoft.com/JAPAN/developer/library/vccore/_crt_str...
URLの方は明日、読ませてもらいます。
ありがとうございました。
ちなみに環境はLINUX+REDHATです(バージョンは分かりません)。gccでコンパイルしています。
1バイト見て文字種別を判断し、それが「2バイト文字の第1バイト」を示す値なら、
続く1バイトを拾って「2バイト文字の第2バイト」として扱う必要があります。
(同様に、3バイト文字領域を示すなら、続く2バイトを拾う)
「飛行機 電車(以下略)」という文字列は、バイナリ16進で1バイトずつ表すと
C8,F4,B9,D4,B5,A1,A1,A1,C5,C5,BC,D6 …と並んでいるはずです。
C8を見て2バイト文字の第1バイトであると判断し、続くF4とあわせて「飛」です。
F4は使ってしまったので、次はB9でこれも2バイト文字の第1バイトになりますから
次のD4と合わせて「行」、同様にB5,A1で「機」、A1,A1で全角スペースです。
問題のプログラムは、単語の区切りとして全角スペース=A1:A1を探していますが、
文字の区切りを考慮せずにstrstr()で探索しているため、先のような文字列では
期待している全角スペースにたどり着く前に「機」の第2バイトと全角スペースの
第1バイトのペアで、A1:A1を検知してしまうため失敗します。
1バイト文字か2バイト文字のみで構成される前提で、こんなかんじかと。
main() { unsigned char words[100] = "飛行機 電車 train "; unsigned char *pt1,*pt2 int i; pt1 = pt2 = words; for( i=0;i<3;i++ ){ if( strcmp(pt2," ") ){ pt2+= ( *pt2>0x80 )? 2:1; }else{ *pt2=0; printf("%s\n",pt1); pt2+=2; pt1=pt2; } } }
コンパイルもしていないので、動くかどうかわからんですけど。
試してみます。
詳しい説明ありがとうございました。
文字化けする理由は「飛行機」の「機」の文字の euc-jp でのコードが 0xb5, 0xa1 で、全角スペースが 0xa1, 0xa1 だからです。これが繋がっているので「機」の第2バイトの位置で一致してしまいます。(「機(全角スペース)」は 0xb5, 0xa1, 0xa1, 0xa1 になっているため)。
ということで、正確に扱いたい場合は wchar_t 型に変換してから切り分けた方がいいと思います。
例) gcc の場合
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <stdlib.h>
#include <wchar.h>
int main()
{
char words[] = "飛行機 電車 train ";
wchar_t wc_words[20], *wp1, *wp2;
int i;
setlocale(LC_ALL, "");
mbstowcs(wc_words, words, 20);
wp1 = wc_words;
for (i = 0; i < 3; i++) {
if ((wp2 = wcschr(wp1, L' ')) != NULL) {
*wp2 = L'\0';
printf("%ls\n", wp1);
wp1 = wp2 + 1;
}
}
return 0;
}
wchar_t型というのは知りませんでした。
試してみます。
ありがとうございました。
参考までにdungeon-master さんのは
1.strcmp(pt2," ")→memcmp(pt2," ")
2.for( i=0;*pt2;i++ )→while(*pt2)
にすれば正常に動作します
・・・がEUCには3バイトコードもあるので、wordsの中に
不規則に文字が入るのであれば以下のほうが宜しいかと
#include <stdio.h> void main() { unsigned char *pt1,*pt2,pt3[256]; unsigned char words[] = "飛行機 電車 train "; int i; for(pt1=pt2=words;*pt2;){ if(*(short int*)pt2== *(short int*)" " /* 0xa1a1 と同じ */ ){ //!memcmp(pt2," ",2)の評価と同じ i=pt2-pt1; memcpy(pt3,pt1,i); pt3[i]=0; printf("%s\n",pt3); pt1=pt2+2; } if(*pt2==0x8f)pt2+=3; else if(*pt2>=0xa1||*pt2==0x8e)pt2+=2; else pt2++; } }
文字列の語尾に必ず全角スペースが入っているとは限らないのであれば
#include <stdio.h> void main() { unsigned char *pt1,*pt2,pt3[256]; unsigned char words[] = "飛行機 電車 train"; int i; for(pt1=pt2=words;*pt2;){ if(*(short int*)pt2== *(short int*)" "/* 0xa1a1 と同じ */ ){ //!memcmp(pt2," ",2)の評価と同じ i=pt2-pt1; memcpy(pt3,pt1,i); pt3[i]=0; printf("%s\n",pt3); pt1=pt2+2; } if(*pt2==0x8f)pt2+=3; else if(*pt2>=0xa1||*pt2==0x8e)pt2+=2; else pt2++; } if(*pt1) printf("%s\n",pt1); }
の方が綺麗かもしれません
さらに、全半角対応にするならば
#include <stdio.h> void main() { unsigned char *pt1,*pt2,pt3[256]; unsigned char words[] = "飛行機 電車 train 船 クルマ 自転車"; int i,j; for(pt1=pt2=words;*pt2;pt2+=j){ if(*pt2==0x8f)j=3; else if(*pt2>=0xa1||*pt2==0x8e)j=2; else j=1; if(*(short int*)pt2== *(short int*)" " ||*pt2==' ' /* 0xa1a1 と同じ */ ){ //!memcmp(pt2," ",2)の評価と同じ i=pt2-pt1; memcpy(pt3,pt1,i); pt3[i]=0; printf("%s\n",pt3); pt1=pt2+j; } } if(*pt1) printf("%s\n",pt1); }
となります
参考
今日はみなさんの回答を参考に以下のようにしてみました。
>||
int main()
{
unsigned char words[100] = "飛行機 電車 train ";
unsigned char *pt1,*pt2;
int i;
pt1 = pt2 = words;
while(1){
pt2 += ( *pt2>0x80 ) ? 2 : 1;
if( strncmp(pt2, " ", 2)==0){
*pt2 = '\0';
pt2 += 2;
printf("%s\n",pt1);
pt1 = pt2;
}
else if(*pt2 == '\0'){
break;
}
}
return 0;
}
||<
動作させるとまだ怪しげな感じがします。(実際の処理はサイズの大きなCSVファイルを読み込んで1行ずつ処理させています。)
aki73ixさんの回答はいま見たところなので、明日試してみようと思います。
ありがとうございました。
>|| ~ ||< でソースを表示させたいのですが、、、、何故ちゃんと表示されないのだろう。
すみません。
URLを見させて頂きましたが、あまり質問内容とは関係ないかと思います。文字コードの判定について書かれていると思うのですが、使用している文字コードはEUCですので、特に文字コード判定云々については気にしていません。
でも、ありがとうございました。
すみません、追記します(ポイント結構です)
文字コードについてリンクおかしいようでしたので、説明書いたページを補足します。
「文字コードについて」という部分の
説明を見ると
>・・・がEUCには3バイトコードもあるので、wordsの中に
という部分もお分かりかと思いますし、ASCII文字も考慮しなければなりませんし、ダンプとあわせてみれば何が悪いのか理解早まると思われます。
"飛行機 電車 train "という文字列は
C8 F4 B9 D4 B5 A1 A1 A1 C5 C5 BC D6 A1 A1 74 72 61 69 6E A1 A1
となっているはずです。
以上、ご参考まで
参考になります。
ありがとうございました。
文字列の間はわけあって「全角スペース」しか使えないです。
2の方を明日試してみます。ありがとうございました。