全角文字の並びを逆順にする方法とは?

このQ&Aのポイント
  • C言語で全角文字の並びを逆順にする方法について説明します。
  • 全角文字の上位バイトかどうかを判定する関数を作成し、文字の並びを逆順にする関数を作成します。
  • ただし、実行時エラーが発生する問題もあります。解決方法を教えていただきたいと思います。
回答を見る
  • ベストアンサー

全角文字を含んだ文字の並びを逆順にするには?

こんにちは。 C言語において、以下のような2つの関数を利用して、全角文字を含んだ文字列の並びを逆順にする事を考えました。 ちなみにコンパイラは、borlandのC++コンパイラを使っています。 文字コードがShift_JISの場合、全角文字の上位バイトが0x81~0x9F、0xE0~0xFCの範囲に収まるという事を聞いたので、まず、以下のように、渡された文字が、全角文字の上位バイトかどうかを判定する関数を作りました。 ____________________________________________________________ int is_2byte(unsigned char c){ return ( ( (c >= 0x81) && (c <= 0x9f) ) || ( (c >= 0xe0) && (c <= 0xfc) ) ); } ____________________________________________________________ 次に、上の関数を利用し、以下のように、渡された文字列の、文字の並びを逆順にする関数を作りました。 ____________________________________________________________ void rev_str(char *str) { char temp[1000]; char tmp; int i, j; for(i=strlen(str)-1, j=0; i>=0; i--, j++){ temp[j]=str[i]; if(is_2byte(temp[j])){ tmp=temp[j-1]; temp[j-1]=temp[j]; temp[j]=tmp; } } temp[j]='\0'; strcpy(str, temp); } __________________________________________________ この関数を、あいうえお順に試して行った所、ひらがなの 「あいうえお かきくけこ さしすせそ たちつてと なにぬねの はひふへほ まみむめ」 という文字列は、逆順にする事ができたのできました。 しかし、「も」以降の文字を含ませた途端、実行時エラーが発生し、プログラムがストップしてしまいます。 また、カタカナの場合は、「メ」以降の文字を含ませた場合に、実行時エラーとなります。 何が問題なのかが全然分からないので、困っています。 何かいい方法を知っておられる方がいらっしゃれば、是非アドバイスを頂きたいと思います。 では、よろしくお願い致します。

質問者が選んだベストアンサー

  • ベストアンサー
回答No.9

No.2 です。 No.7 のプログラムの訂正: #include <string.h> void rev_str(char *str) { char *p; char w; /*交換用ワーク*/ for (p=str; *p != '\0'; ++p) if ( is_2byte(*p) ) { w = *p; *p = p[1]; p[1] = w; ++p; /*抜けていました*/ } strrev(str); /*文字列の並びを char 単位で逆にするライブラリ関数*/ }

MetalLover
質問者

お礼

御指摘ありがとうございます。 では、修正して試させていただきます。

MetalLover
質問者

補足

上記の関数を試したところ、上手く行きました。 YanenoSuzumeさんには、何度も御回答いただき、大変助けられました。 本当にありがとうございました。

その他の回答 (10)

  • sha-girl
  • ベストアンサー率52% (430/816)
回答No.11

> if(is_2byte(str[i]) && j-1>=0 && i+1 <= strlen(str) ) > という行がありますが、これは > if(is_2byte(str[i]))で十分だと思うのですが、如何でしょうか? j-1 >= 1 は char buf[2] = "\xe0"; のようなデータがきた場合にメモリー破壊を回避する為の対処です。 (0xE0,0x00というデータです) if(is_2byte(str[i]))だと 以下のようなプログラムを実行するとメモリー破壊がおきます。 char buf[2] = "\xe0"; rev_str( buf ); ※i+1 <= strlen(str)の部分は冗長的でした。 もちろん正しいSJISのデータが来ることが前提であれば、このチェックは不要です。

MetalLover
質問者

お礼

御指摘ありがとうございます。 >j-1 >= 0 は >char buf[2] = "\xe0"; >のようなデータがきた場合にメモリー破壊を回避する為の対処です。 なるほど、そういったケースもあり得ますよね。 思いつきませんでした。

  • sha-girl
  • ベストアンサー率52% (430/816)
回答No.10

#3ではわかりやすいかと思い、元ソースをあまりいじらずサンプルコードを提示しましたが、 一時領域を使いたくないのであればこんな感じです。 void rev_str(char *str) { char tmp; char *p1 , *p2; int n , i , src_len; src_len = strlen( str ); if ( src_len == 0 ) return; /* 2バイト文字を入れ替えておく */ p1 = str; while(*p1) { if (is_2byte(*p1) ) { p1++; if (*p1 == '\0') { /* 2バイト文字が途中で切れた */ break; } tmp = *p1; *p1 = *(p1-1); *(p1-1) = tmp; p1++; } else { p1++; } } /* 逆順にする */ n = src_len / 2; p1 = str; p2 = &str[src_len - 1]; for( i = 0 ; i < n ; p1++ , p2-- , i++ ) { tmp = *p2; *p2 = *p1; *p1 = tmp; } } ※SJISコードであることからWindows環境だと思いますが、もしVisual Studioを使うなら strrevは非推奨なのでご注意ください。 http://msdn.microsoft.com/ja-jp/library/ms235404.aspx

MetalLover
質問者

お礼

御丁寧にありがとうございます。 是非参考にさせていただきます。 あと、No3で提示された関数において、 if(is_2byte(str[i]) && j-1>=0 && i+1 <= strlen(str) ) という行がありますが、これは if(is_2byte(str[i])) で十分だと思うのですが、如何でしょうか? (実際、様々な文字列でテストしてみて、問題ありませんでした。)

回答No.8

No.2 です。 処理系によっては、ライブラリ関数が用意されている場合もあります。 例: LSI-C #include <jstring.h> char *jstrrev(char *s); 1バイトコードと2バイトコードが混在する 文字列s を文字単位で逆に並べかえます。 文字コードの判定では #include <jctype.h> int iskanji(char c); 全角1バイト目 int iskanji2(char c); 全角2バイト目

MetalLover
質問者

お礼

貴重な情報、ありがとうございます。 Borand C++ Compilerでは、jstrrev関数が存在しました。 一度この関数を試してみます。

MetalLover
質問者

補足

すみません。 僕の勘違いでした。 jstrrev関数は、含まれていませんでした。

回答No.7

No.2 です。 No.4 Tacosan さん > いったんバイト単位でひっくり返しておいて, > そのあとでおもむろに後ろから見ていけば > テンポラリ領域は不要だと思う. No.5 wormhole さん > 便宜上、ASCII文字をA、漢字の1バイト目をK1, 2バイト目をK2とします。 > > A K1 K2 > > これを反転すると > > K2 K1 A …ではなくて、 A K2 K1 だと思います。 A K1 K2 ... のような文字列を A K2 K1 ... のように変形しておいてから、 後ろから出力するわけですね。 プログラムは次のようになります。 #include <string.h> void rev_str(char *str) { char *p; char w; /*交換用ワーク*/ for (p=str; *p != '\0'; ++p) if ( is_2byte(*p) ) { w = *p; *p = p[1]; p[1] = w; } strrev(str); /*文字列の並びを char 単位で逆にするライブラリ関数*/ }

MetalLover
質問者

お礼

御丁寧な御説明、ありがとうございます。 記述して頂いた関数、試させていただきます。

回答No.6

No.2 です。 > strの最終文字は、2バイト文字の上位バイトではあり得ないので、 > is_2byte(temp[j])が1を返すのは、jが1以上のときだと思うのですが、 > 間違っていますでしょうか? Shift_JIS 2バイト文字のコード範囲は 上位(1バイト目) 0x81~0x9F, 0xE0~0xEF 下位(2バイト目) 0x40~0x7E, 0x80~0xFC ですので、 is_2byte(temp[0]) が 0以外(1 とは限らない)を返すことがあります。 一般に、入力データにはエラーが含まれていることも多いので、 > is_2byte(temp[j])が1を返すのは、jが1以上のときだと思うのですが、 というような思い込みでプログラムを書くのは危険で、 デバッグに苦労することになります。 「どんなデータが入力されるかわからない」と想定して 対策を講じておくことが、後々、技能の向上に役立つと思いますよ。

MetalLover
質問者

お礼

御回答ありがとうございます。 上位バイトの範囲と下位バイトの範囲は、一部重なっているという事に、気が付きませんでした。 御指摘されたおかげで気が付きました。

  • wormhole
  • ベストアンサー率28% (1619/5653)
回答No.5

>僕は馬鹿なので、文章を読んだだけでは、いまいちピンとこないのです。 文章を読んでわからないのであれば ノートに図をかくなりなんなりしながらわかるように 整理すればいいだけです。 便宜上、ASCII文字をA、漢字の1バイト目をK1, 2バイト目をK2とします。 A K1 K2 これを反転すると K2 K1 A これから先はご自分でご確認ください。 手順は#4の方が書かれていますよね。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.4

いったんバイト単位でひっくり返しておいて, そのあとでおもむろに後ろから見ていけばテンポラリ領域は不要だと思う.

MetalLover
質問者

お礼

ご回答ありがとうございます。 申し訳ございませんが、可能なら具体的なコードを記述していただけませんでしょうか? 僕は馬鹿なので、文章を読んだだけでは、いまいちピンとこないのです。

  • sha-girl
  • ベストアンサー率52% (430/816)
回答No.3

>しかし、「も」以降の文字を含ませた途端、実行時エラーが発生し、プログラムがストップしてしまいます。 >また、カタカナの場合は、「メ」以降の文字を含ませた場合に、実行時エラーとなります。 SJISの「も」は0x82E0です。 下位バイトに「E0」が含まれているおり、下位バイトにあるにもかかわらずis_2byteにひっかかっています。 ※「メ」も0x8381で同じ理由です つまりSJISの場合、先頭からみていかないと2バイトコードかどうかの判定が出来ないということです。 void rev_str(char *str) {  char temp[1000];  char tmp;  int i, j;  for(i=0, j=strlen(str)-1; i<strlen(str); i++, j--){   if(is_2byte(str[i]) && j-1>=0 && i+1 <= strlen(str) ){    temp[j]=str[i+1];    temp[j-1]=str[i];    i++;j--;   }   else{    temp[j]=str[i];   }  }  temp[strlen(str)]='\0';  strcpy(str, temp); }

MetalLover
質問者

お礼

鋭いご指摘、ありがとうございます。 記述していただいた関数を、一度試してみて、結果を報告します。 (今、手元に自分のPCがないので、少しお待ち下さい。)

MetalLover
質問者

補足

上記の関数、試させていただきました。 見事に上手く行きました。 本当に感謝します。

回答No.2

このプログラムでは、 j=0 のときに temp[-1] を書きつぶしているのではないですか? プログラム中で確保していない領域ですので、他に影響が出ると思いますが。

MetalLover
質問者

お礼

ご指摘ありがとうございます。 strの最終文字は、2バイト文字の上位バイトではあり得ないので、 is_2byte(temp[j])が1を返すのは、jが1以上のときだと思うのですが、 間違っていますでしょうか?

  • wormhole
  • ベストアンサー率28% (1619/5653)
回答No.1

下位バイトの範囲は調べられましたか? 調べた上で、is_2byte()で上位バイトかどうか判定できると思われますか?

MetalLover
質問者

お礼

ご回答ありがとうございます。 >下位バイトの範囲は調べられましたか? 調べ回ったところ、 「SJIS で JISX0201 以外の文字は1文字2バイトで表します。 上位バイトは 0x80-0x9F, 0xE0-0xFC 下位バイトは 0x40-0x7E, 0x80-0xFC の範囲を取り、合計 11468 文字 定義できます。」 という文を見つけました。 下位バイトが 0x40-0x7E, 0x80-0xFC の範囲にあるかどうかを判定すれば、2バイト文字を検出できるかも知れませんね。 一度試してみます。

関連するQ&A

  • 全角文字のチェック

    現在C言語を勉強しております。 そこで、キーボードから入力された文字列のチェックを行う関数を作成したいのですが、実装方法が分かりません・・・。 以下に仕様と私の作成したソースを貼ります。 【許可する文字】は、 全角のひらがな 全角の英語(大文字も小文字もOK) 全角の数字 です。 チェックする関数を作成したいのですが、以下ではうまくいかず・・・ 教えてくださいorz 比較の仕方がおかしいのかな・・・とは思っているんですが。 関数は、引数として渡された文字列に許可以外の文字が含まれている場合は1を、そうでなければ0を返す、という仕様にしたいです。 int checkName(char *str) { int i; for(i = 0; str[i] != '\n'; i++){ /* 奇数バイトをチェック */ if(str[i] != 0x82){ return 1; }else{ i++; /* ひらがな以外の場合 */ if(str[i] < 0xa0 && str[i] > 0xf1) /* 英語(大文字)以外の場合 */ if(str[i] < 0x60 && str[i] > 0x79) /* 英語(小文字)以外の場合 */ if(str[i] < 0x81 && str[i] > 0x9a) /* 数字の場合 */ if(str[i] < 0x50 && str[i] > 0x58) /* 許可する文字に該当しなかった場合は1を返す */ return 1; } } return 0; } 上記ソースの「(str[i] != 0x82)」は比較の仕方としておかしいでしょうか? 以上、よろしくお願いいたします。

  • 全角文字の判定

    現在C言語を勉強しております。 そこで、キーボードから入力された文字列のチェックを行う関数を作成したいのですが、実装方法が分かりません・・・。 以下に仕様と私の作成したソースを貼ります。 【許可する文字】は、 全角のひらがな 全角の英語(大文字も小文字もOK) 全角の数字 です。 チェックする関数を作成したいのですが、以下ではうまくいかず・・・ 教えてくださいorz 比較の仕方がおかしいでしょうか? 関数は、引数として渡された文字列に許可以外の文字が含まれている場合は1を、そうでなければ0を返す、という仕様にしたいです。 int checkName(char *str) { int i; for(i = 0; str[i] != '\n'; i++){ /* 奇数バイトをチェック */ if(str[i] == 0x82){ i++; /* ひらがなの場合 */ if(str[i] >= 0xa0 && str[i] <= 0xf1) printf("ひらがな\n"); return 0; /* 英語(大文字)の場合 */ if(str[i] >= 0x60 && str[i] <= 0x79) printf("英語大\n"); return 0; /* 英語(小文字)の場合 */ if(str[i] >= 0x81 && str[i] <= 0x9a) printf("英語小\n"); return 0; /* 数字の場合 */ if(str[i] >= 0x50 && str[i] <= 0x58) printf("数字\n"); return 0; } return 1; } return 0; } 以上、よろしくお願いいたします。

  • 文字の並び替え

    今、全角文字と半角文字が混在した文字列の並び替えをしています。 また、皆様のお力をおかしください。 例:CBABD → ABBCD 色々調べて以下の関数を使うことはわかったのですが、これをどう使えば、並び替えることが出来るのかがわかりません。教えてください。 例があれば、とてもうれしいのですが・・・ int ZenkakuHantei(unsigned char c) { return (((c >= 0x81) && (c <= 0x9f)) || ((c >= 0xe0) && (c <= 0xfc))); }

  • JISの全角判定処理について

    お世話になっております。 C++初心者です。 今、JISの全角判定処理を作っているのですが、 フォームのイベントでKeyPressに関数をセットしてあるのですが、 関数のフォーマットが void *********(char &Key); ※[*]は任意の文字列 になっており、半角ならこの関数を1回通り、 全角なら2回通ります。(引数の変更はできません) そこで下記のような関数を作成しました。 void pressKeyFullPitch(char &Key){   static bool fullFlag = false;   if(fullFlag){     fullFlag=false;     Key='*'     return;   }   if( Key >= 0x81 && Key <= 0x9f) || (Key >= 0xe0 && Key <= 0xfc ){     fullFlag = true;     Key = '*';   }      } 全角で入力された2byte文字は2byteとも[*]にするようにしています。 ここて関数内にstatic boolを宣言していますが、 static変数は関数終了時にも値を保持しているため あまり使わないほうがいい気がするので質問させていただきます。 ※すいません、経験が少ないのでstatic変数について知識が足りないかもしれません。 この場合、static変数を使わずうまく処理できる方法が ありましたら、お願いします。

  • CString型 全角半角を意識せずに「1文字」ずつ取り出す

    CString型の文字列に格納されている文字を1文字ずつ取り出したいです。 ただし半角なら1バイト単位で、全角なら2バイト単位で、という風に分離したいです。 半角だけなら、str[0] str[1]...という風に取り出せますが、 全角が混じっていると、1バイト目、2バイト目と分離されてしまいます。 その文字が半角か全角かを判断して、半角なら1バイト、全角なら2バイト同時に取り出すロジックを、下記のような感じの関数として作りたいです。 CString ripString(CString str,int index){ //ソースとなる文字列、n文字目 /*~処理~*/ return 文字列; } たとえば"あaいbうcえdおe"という文字列を入れると、 CString str="あaいbうcえdおe"; ripString(str,0) →結果 "あ" ripString(str,1) →結果 "a" ripString(str,2) →結果 "い" ripString(str,3) →結果 "b"  ・  ・  ・   こういうことをするのに良い方法はありますか? 1バイトごとのそれぞれの文字自身が、 ・半角文字なのか ・全角文字の前1バイトなのか ・全角文字の後1バイトなのか これをプログラム的に判別する方法があればいいのですが・・・悩んでいます。

  • c言語の文字列の逆順のプログラムがわかりません

    文字列を逆順して出力するプログラミングがわかりません。 #include <stdio.h> #include <string.h> void reverse(char *moji, char *gyaku); int main(void) { char x[30]; char y[30]; puts("文字を入力してください。\n"); scanf("%s", x); reverse(x, y); printf("逆順すると%sです。\n", y); return (0); } void reverse(char *moji, char *gyaku) { int i, len; len = strlen(moji); gyaku = moji + len - 1; for(i = 0; i < len; i ++){ putchar((int)*gyaku); gyaku--; } } 理想とする実行結果は 文字を入力してください。 abcdefg 逆順するとgfedcbaです。 なんですが、 上記のソースを実行すると 文字を入力してください。 abcdefg gfedcba逆順すると(謎の漢字)です。 となります。 どこがおかしいんでしょうか? よろしくおねがいします。

  • VBで全角文字の上位コードを取得するには?

    今までC言語で以下のようなソースで動いていたプログラムがあります。 int main(void) {  FILE *fp1;  char str1[]="ABCあ"; char str2[]="XXX";  fp1 = fopen("aaaa.txt", "w");  fprintf(fp1, "%-4.4s%3s\n",str1,str2);  fclose(fp1); } 上のプログラムの実行結果は「ABC9XX」になります。 これは、printf関数で変数str1の4文字目が全角文字の「あ」であるのに %-4.4sが指定されているため、「あ」の上位コードと str2の1文字目のXの文字コードが連結されてしまい、 文字化けしてしまっているようです。 「ABCあ」の文字コード A=41H, B=42H, C=43H, あ=82H,AOH 「XXX」の文字コード X=58H, X=58H, X=58H 出力結果 ABC9XXの文字コード A=41H, B=42H, C=43H, 9=82H,58H, X=58H X=58H 今回このプログラムをVBに置き換えることになったのですが、 出力されるデータは今までプログラムとまるで同じにしてほしい (つまり、今まで文字化けしてるところは 文字化けした形で出力してほしい)の依頼があったため、 Format関数やLeftB関数を使って なんとかプログラムを書いてみたのですが、 うまく動作しません。 うまい対処法があれば教えてください。 宜しくお願いします。

  • プログラム

    文字列を逆順にする関数を作ってるのですがうまくできません。 void reverse(char *str, int count=0) { int i; char *s="momonga"; s+=strlen(s); count=strlen(s); for(i=count; i<0; i--, str++, s--){ *str++=*s++; } } int main() { char s1[20]; int c; reverse(s1,c); printf("%momongaを逆順にすると%sで文字数が%dです",s1,c); return 0; } このプログラムの間違ってる所をおしえてください。

  • 文字数の逆順

    C++で入力文字の文字数を関数を使って逆順にするにはどのようなプログラムを作ればよいでしょうか。 入力文字がabcd 文字数4個 4321と出力したい。

  • 漢字の第1バイト検証

    今、文字判定関数について勉強しているのですが、 unsingned char uch にgetchar()の値を入れ if(uch >= 0x81 && uch <= 0x9f || uch >= 0xe0 && uch <= 0xfc) とうコードで、なぜ漢字コードは「0x81~0x9f」と「0xe0~0xfc」の 二つの範囲があるのか解りません。 よろしくお願いします。

専門家に質問してみよう