全角の文字列を指定文字数で2つの文字列に分割する。

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
namari
記事: 111
登録日時: 7年前

全角の文字列を指定文字数で2つの文字列に分割する。

#1

投稿記事 by namari » 6年前

例えば、message"1234567891011121314"のような文字列があったとして(全角半角混合)、
これを指定文字数(例えば7)で分割して、mess1の"1234567"とmess2の"891011121314"に分割したいです。
また、指定文字数が30(実際の文字数より大きい)の時は、mess1に"1234567891011121314"、mess2に""(空っぽ)が入るような関数がほしいのです。

_tcsncpy_sを使う方法は前半は抜き出してmess1に入れても、mess2の方法は分からず、
mess2を右からの文字数で抜き出そうとしても、文字列が短すぎると、mess1とmess2に重なりができてしまいます。
一体どうすればいいのでしょうか。
代入するmessageはchar型なのですが、全角半角混合でcharの関数のままいじると1バイトだけ読み込むせいで端にゴミがうまれてしまうので、
おそらくtcharかwcharを使うべきなのはわかりますが…。

アバター
みけCAT
記事: 6311
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: 全角の文字列を指定文字数で2つの文字列に分割する。

#2

投稿記事 by みけCAT » 6年前

素直に必要な文字数だけコピーすればいいと思います。

コード:

#include <stdio.h>
#include <wchar.h>

void bunkatu(const wchar_t* in,wchar_t* mess1,wchar_t* mess2,int mozisu) {
	int i;
	for(i=0;i<mozisu;i++) {
		*mess1=*in;
		if(*in==L'\0')break;
		mess1++;in++;
	}
	*mess1=L'\0';
	while((*(mess2++)=*(in++))!=L'\0');
}

int main(void) {
	wchar_t buffer[100]=L"1234567891011121314";
	wchar_t mess1[100],mess2[100];
	bunkatu(buffer,mess1,mess2,7);
	fputws(mess1,stdout);putwc(L'\n',stdout);
	fputws(mess2,stdout);putwc(L'\n',stdout);
	bunkatu(buffer,mess1,mess2,30);
	fputws(mess1,stdout);putwc(L'\n',stdout);
	fputws(mess2,stdout);putwc(L'\n',stdout);
	return 0;
}
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

pg78445
記事: 10
登録日時: 7年前

Re: 全角の文字列を指定文字数で2つの文字列に分割する。

#3

投稿記事 by pg78445 » 6年前

文字コードで全角文字判定して処理することもできます。
このコードの場合\0で文字列終了部分を変えているだけなので文字列\0以降にデータが残っていることに注意してください。

コード:

#include <stdio.h>
#include <string.h>

void separate(int charNum, const char *input, char *mess1, char *mess2);

int main(void)
{
	char message[100] = "01234567891011121314";
	char mess1[100];
	char mess2[100];

	separate(7, message, mess1, mess2);
	printf("mess1=%s, mess2=%s\n", mess1, mess2);

	separate(20, message, mess1, mess2);
	printf("mess1=%s, mess2=%s\n", mess1, mess2);

	separate(30, message, mess1, mess2);
	printf("mess1=%s, mess2=%s\n", mess1, mess2);

	getchar();

	return 0;
}

// 全角文字の間を分割したら文字化けするのでしないように全角文字も1文字と見なします
void separate(int charNum, const char *input, char *mess1, char *mess2)
{
	int charCount = 0;
	int byteNum = 0;
	unsigned char *check_doublebyte = (unsigned char *)input;

	while (charCount < charNum){
        // 全角第1バイトかどうか判定(SHIFT-JIS)
		if ((check_doublebyte[byteNum] >= 0x81 && check_doublebyte[byteNum] <= 0x9e) ||
			(check_doublebyte[byteNum] >= 0xe0 && check_doublebyte[byteNum] <= 0xef)){

			if (input[byteNum + 1] == '\0'){
				byteNum++;
				break;
			}
			else{
				byteNum += 2;
			}
		}
		else{
			byteNum++;
		}

		if (input[byteNum] == '\0'){
			byteNum--;
		}

		charCount++;
	}

	strcpy(mess1, input);
	strcpy(mess2, input);

	mess1[byteNum + 1] = '\0';
	mess2[0] = mess2[byteNum + 1];
}

アバター
みけCAT
記事: 6311
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: 全角の文字列を指定文字数で2つの文字列に分割する。

#4

投稿記事 by みけCAT » 6年前

pg78445さん
そのコードでは、うまく動かないはずです。
https://ideone.com/Xk4WAe (入力を半角文字のみにしてテスト)
58行目、61行目が怪しいです。
ちゃんと自分でテストしましたか?
pg78445さんの手元の環境ではこれで正常に動作するのであれば、
その処理系(OS、コンパイラ)を教えてください。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
usao
記事: 1598
登録日時: 7年前

Re: 全角の文字列を指定文字数で2つの文字列に分割する。

#5

投稿記事 by usao » 6年前

素直に先頭から1文字ずつ調べては結果領域に代入してみました.

コード:

inline
bool IsSJIS_LeadByte( unsigned char c )
{   return ( 0x81<=c && c<=0x9F ) || ( 0xE0<=c && c<=0xFC );    }

//Srcの先頭n1文字をpDst1に,残りをpDst2に入れる.
//全角文字も1文字とカウントする.
//※pDst1,pDst2は十分なサイズの領域を指している前提.
void SJIS_str_split( const char *Src, size_t n1, char *pDst1, char *pDst2 )
{
    const char *iSrc = Src;
    pDst1[0] = pDst2[0] = '\0';
    char *pDst = ( n1>0 ? pDst1 : pDst2 );

    size_t counter = 0;
    while( *iSrc != '\0' )
    {
        if( IsSJIS_LeadByte( (unsigned char)(*iSrc) ) )
        {
            *pDst++ = *iSrc++;
            *pDst++ = *iSrc++;
        }
        else
        {   *pDst++ = *iSrc++;  }

        if( ++counter == n1 )
        {
            *pDst = '\0';
            pDst = pDst2;
        }
    }
    *pDst = '\0';
}

//
void SplitAndShow( const char *Message, size_t n1, char *mess1, char *mess2 )
{
    std::cout << "n1 = " << n1 << " :" << std::endl;
    SJIS_str_split( Message, n1, mess1, mess2 );
    std::cout
        << "mess1= \"" << mess1 << "\"" << std::endl
        << "mess2= \"" << mess2 << "\"" << std::endl
        << std::endl;
}

//
int main( int argc, char **argv )
{
    const int N = 20;
    char Message[N] = "Cat「にゃーん!!」";
    char mess1[N] = { '\0' };
    char mess2[N] = { '\0' };

    std::cout << "Message= \"" << Message << "\"" << std::endl << std::endl;

    SplitAndShow( Message, 0, mess1, mess2 );
    SplitAndShow( Message, 5, mess1, mess2 );
    SplitAndShow( Message, 7, mess1, mess2 );
    SplitAndShow( Message, 11, mess1, mess2 );
    SplitAndShow( Message, 12, mess1, mess2 );

    //
    std::cout << "[end]" << std::endl;
    std::cin.ignore();
    return 0;
}

アバター
みけCAT
記事: 6311
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: 全角の文字列を指定文字数で2つの文字列に分割する。

#6

投稿記事 by みけCAT » 6年前

Shift_JIS用のサンプルは既に出ているので、UTF-8用のサンプルを置いておきます。

コード:

#include <stdio.h>

/* UTF8の次の文字をoutに格納し、そのバイト数を返す。
 * 不正バイト列のチェックは行わない。
 * (bufの先頭がナル文字でなければ)outにナル文字は格納しない。
 */
int get_next_utf8_char(char *out, const char *buf) {
	int bytes = 1;
	int i;
	if ((buf[0] & 0x80) == 0x00) {
		bytes = 1;
	} else {
		int now_mask = 0xE0;
		int now_puttern = 0xC0;
		for (i = 2; i <= 6; i++) {
			if ((buf[0] & now_mask) == now_puttern) {
				bytes = i;
				break;
			}
			now_mask = (now_mask >> 1) | 0x80;
			now_puttern = (now_puttern >> 1) | 0x80;
		}
	}
	for (i = 0; i < bytes; i++) out[i] = buf[i];
	return bytes;
}

void split_string(const char *input, char *mess1, char *mess2, int length) {
	int i;
	for (i = 0; *input != '\0' && i < length; i++) {
		int length = get_next_utf8_char(mess1, input);
		mess1 += length;
		input += length;
	}
	*mess1 = '\0';

	while (*input != '\0') {
		int length = get_next_utf8_char(mess2, input);
		mess2 += length;
		input += length;
	}
	*mess2 = '\0';
}

int main(void) {
	const int sample_length[] = {0, 5, 7, 11, 12, 30, -1};
	const char* samples[] = {
		"1234567891011121314",
		"Cat「にゃーん!!」",
		/* 1件のテストケースを省略 : Ideone.com参照 */
		NULL
	};
	char mess1[100], mess2[100];
	int i, j;

	for (i = 0; sample_length[i] >= 0; i++) {
		printf("length = %d\n", sample_length[i]);
		for (j = 0; samples[j] != NULL; j++) {
			split_string(samples[j], mess1, mess2, sample_length[i]);
			printf("(input, mess1, mess2) = (\"%s\", \"%s\", \"%s\")\n",
				samples[j], mess1, mess2);
		}
	}
	return 0;
}
https://ideone.com/9kxjXZ
オフトピック
3番目のテストケースを入れた状態で投稿しようとすると
SQL ERROR [ mysqli ]

Incorrect string value: '\xF0\xA6\xA5\x91\xF0\xA6...' for column 'post_text' at row 1 [1366]

SQLエラー が発生したため、ページ情報をデータベースから取得できませんでした。問題が解決しない場合は管理人にご連絡ください。
と言われてしまいました…。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

pg78445
記事: 10
登録日時: 7年前

Re: 全角の文字列を指定文字数で2つの文字列に分割する。

#7

投稿記事 by pg78445 » 6年前

環境は
Windows 7
microsoft visual studio express 2013 for windows desktop
です。SHIFT-JIS前提で決め打ちしてます。
http://charset.7jp.net/sjis.html

ideoneはutf-8みたいですね。
https://ideone.com/Eh8arn
http://ash.jp/code/unitbl21.htm

アバター
namari
記事: 111
登録日時: 7年前

Re: 全角の文字列を指定文字数で2つの文字列に分割する。

#8

投稿記事 by namari » 6年前

皆さん有難うございます。
文字コードを指定していないせいで色んな可能性を作ってしまった感がありますね。すみませんでした・・・。
「環境」
Windows 7
Shift-JIS
(これはusaoさんのコードでバグが出なかったことから恐らく自分はShift-JISで書いているんだろうなという予測だけで、正式に確認したわけではないです。
「マルチバイト文字セットを使用する」というプロジェクトの設定は関係あるのでしょうか?)
.cppですが主にC言語で表記。
VC++ 2013です。(FrameworkのバージョンがXPに対応していないので、いずれVC++のバージョンを下げる可能性があります)

char→wchar_tの変換が文字化けで上手くいかず、Shift-JISでしか動かず、
結果、usaoさんのコードが全く思った通りに動くので、使用させていただきました。
文法がほとんど分からないのですが、よくみたら関数部分はC言語ですね。
普段知ってること文法だけで好き勝手書いてるので、知識が狭くて恥ずかしいです。
IsSJIS_LeadByteが恐らくそのcharが一文字か中途半端な部分じゃないかを判定して、
インプットしたメッセージの最初のアドレスをiSrcに入れて、順々にpDst1とpDst2を入れていってるんですよね。

後から仕様変更するような真似をして申し訳ないんですが、
実はデザインの都合上、3つに分ける必要があることに気付いてしまいました。
それぞれに分割する文字数を設定して、3つに分ける関数です。

コード:

//恐らくこういう形になると思われる。
SJIS_str_split( const char *Src, size_t n1,size_t n2, char *pDst1, char *pDst2,char *pDst3 )

SplitAndShow( "12345678912345", 5, 7,mess1, mess2, mess3);
↓
mess1:"12345"
mess2:"6789123"
mess3:"45"
 
夜遅いので今日はこのメッセージを投稿して、そのまま寝てしまいますが、
勉強のため丸投げせず自分でも読み解いてアレンジしてみようと思います。

かずま

Re: 全角の文字列を指定文字数で2つの文字列に分割する。

#9

投稿記事 by かずま » 6年前

namari さんが書きました: char→wchar_tの変換が文字化けで上手くいかず、
setlocale(LC_CTYPE, "") を実行していないからありませんか?
namari さんが書きました: 実はデザインの都合上、3つに分ける必要があることに気付いてしまいました。
それぞれに分割する文字数を設定して、3つに分ける関数です。
指定した文字数に相当するバイト数を返す関数を用意すればよいのでは?

コード:

#include <stdio.h>
#include <string.h>

int isLeading(unsigned char c) { return (c ^ 0x20) - 0xa1u < 60; }

size_t blen(const char *s, size_t n)
{
    int i, k;
    for (i = k = 0; k < n && s[i]; i++, k++)
        if (isLeading(s[i])) i++;
    return i;
}

void split3(const char *s, int n1, int n2, char *s1, char *s2, char *s3)
{
    int k = blen(s, n1); memcpy(s1, s, k), s1[k] = 0;
    k = blen(s += k, n2), memcpy(s2, s, k), s2[k] = 0;
    strcpy(s3, s + k);
}

int main(void)
{
    char mess1[100], mess2[100], mess3[100];
    split3("12345678912345", 5, 7, mess1, mess2, mess3);
    puts(mess1);
    puts(mess2);
    puts(mess3);
    return 0;
}

かずま

Re: 全角の文字列を指定文字数で2つの文字列に分割する。

#10

投稿記事 by かずま » 6年前

先ほどは Shift-JIS 専用でしたが、今度は UTF-8版です。

コード:

#include <stdio.h>
#include <string.h>
 
size_t blen(const char *s, size_t n)
{
    int i, j, k, c;
    for (i = k = 0; k < n && (c = s[i]); k++, i++)
        if (c & 0xc0)
            for (j = 5; i++, c>>j & 1; j--) ;
    return i;
}
 
void split3(const char *s, int n1, int n2, char *s1, char *s2, char *s3)
{
    int k = blen(s, n1);  memcpy(s1, s, k), s1[k] = 0;
    k = blen(s += k, n2), memcpy(s2, s, k), s2[k] = 0;
    strcpy(s3, s + k);
}
 
int main(void)
{
    char mess1[100], mess2[100], mess3[100];
    split3("12345678912345", 5, 7, mess1, mess2, mess3);
    puts(mess1);
    puts(mess2);
    puts(mess3);
    return 0;
}
Shift-JIS でも、UTF-8 でも、EUC でも何でもいいのが次のプログラムです。

コード:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
 
size_t blen(const char *s, size_t n)
{
    int i, j, k, c;
    for (i = k = 0; k < n && s[i]; k++)
        i += mblen(s + i, MB_CUR_MAX);
    return i;
}
 
void split3(const char *s, int n1, int n2, char *s1, char *s2, char *s3)
{
    int k = blen(s, n1);  memcpy(s1, s, k), s1[k] = 0;
    k = blen(s += k, n2), memcpy(s2, s, k), s2[k] = 0;
    strcpy(s3, s + k);
}
 
int main(void)
{
    char mess1[100], mess2[100], mess3[100];
    setlocale(LC_CTYPE, "");
    split3("12345678912345", 5, 7, mess1, mess2, mess3);
    puts(mess1);
    puts(mess2);
    puts(mess3);
    return 0;
}

アバター
namari
記事: 111
登録日時: 7年前

Re: 全角の文字列を指定文字数で2つの文字列に分割する。

#11

投稿記事 by namari » 6年前

かずま さんが書きました:
namari さんが書きました: char→wchar_tの変換が文字化けで上手くいかず、
setlocale(LC_CTYPE, "") を実行していないからありませんか?
namari さんが書きました: 実はデザインの都合上、3つに分ける必要があることに気付いてしまいました。
それぞれに分割する文字数を設定して、3つに分ける関数です。
指定した文字数に相当するバイト数を返す関数を用意すればよいのでは?

コード:

//コード略
まず、setlocaleの挿入でうまくいきました。
mbstowcsの説明を検索すると大概ちゃんと置かれているのに完全にスルーしてしまいした。

3つに分けるコードはうまくいきました!
非常に明快に書いてくださったおかげで、すっと理解出来ました。
とは言いますが、普段テンプレのようなfor文しか使っていなかったせいで、
for文の意味を掴むのに少し時間がかかってしまいましたが。お恥ずかしい。

昨日の段階では余りわかってなかったんですが、isLeadingやget_next_utf8_charやIsSJIS_LeadByteは
その文字コードにおいて、入れたcharが確かに全角文字の1バイト目か見て、そうなら
1つ次までポインタを進めて、区切りが変な位置に来ないようにしていたのですね。
別にテキストに重きを置くゲームは作っていないですが、今なら改行回数を任意の数にもできそうです!
試しにsplit4も作ってみましたが正常に動きました!(たった1行加えただけですが・・・。この明快さには感動です。)

本当にありがとうございました。

アバター
namari
記事: 111
登録日時: 7年前

Re: 全角の文字列を指定文字数で2つの文字列に分割する。

#12

投稿記事 by namari » 6年前

すみません。トップページで解決表示になってませんね。かずまさんの返信での解決は解決扱いにならないのでしょうか。
解決です。

閉鎖

“C言語何でも質問掲示板” へ戻る