csvやファイルパスをカンマや'\'で分割する関数をユニコードでも
対応できるように改修していのですがやり方が分かりません。
関数のロジックはおおよそ以下の通りです。
(仕事先のセキュリティの関係でソースは持ち出せませんので概要ですが・・・)
split(str, item, len, words[item][len], delimiter)
char* str: 分割対象の文字列
int item:項目数(上限あり)
int len:項目の文字数(上限あり)
words[item][len]:文字列を分割、項目として格納する配列
char delimiter:区切り文字
int i=0, j=0;
while(*str != NULL){
// 文字列を分割する処理
if(str != delimiter){
words[i][j++] = *str
}
else if{
words[i][j] = NULL
i++; j=0;
}
・・・・・・・・・
str++;
}
// 後処理があります
VCの設定がマルチバイト対応だったので問題なかったのですが
ユニコードに設定を切り替えるとパスの分割でカタカナの「ソ」
を含む文字列が正しく分割されないという事象が発生しました。
関数内の改修だけで解決する方法があるでしょうか?
ある程度プロジェクトが動いているのであまり時間をかけないで
対応する必要があります。
OS: Windows7 SP1
環境: VC++ 2008 MFC
No.1
- 回答日時:
...別に問題なさそうですが。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <locale.h>
#include <tchar.h>
int split(const TCHAR* str, TCHAR* words[], TCHAR delimiter) {
TCHAR* p;
int row = 0;
TCHAR* buf = (TCHAR*)malloc(sizeof(TCHAR)*(_tcslen(str)+1));
_tcscpy(buf, str);
words[row] = buf;
for ( p = buf; *p != _T('\0'); ++p ) {
if ( *p == delimiter) {
*p = _T('\0');
words[++row] = p+1;
}
}
return ++row;
}
/* おためし */
int main() {
const TCHAR* input = _T("ソーシャル,ネットワーク,,ソリューション,");
TCHAR* words[5];
int n;
int i;
setlocale(LC_ALL, "japanese");
n = split(input, words, L',');
for ( i = 0; i < n; ++i ) {
_tprintf(_T("[%s]\n"), words[i]);
}
free(words[0]);
}
この回答への補足
回答ありがとうございます。
いくつか確認させてください。
1.split()の引数が全てTCHAR型です。
問題の関数は全て現在はchar型で処理しています。
内部的にTCHAR型にキャストして処理すればよいでしょうか?
あるいはsplit()の呼び出し部分も合わせて改修が必要ですか?
プログラム内部では文字列は原則std::string型で保持していて
split()コール時にchar型にキャストしています。
2.setlocale()がコールされていますが、必須でしょうか?
現状はファイルI/Oかコンソール出力時のみ、該当の箇所で直前に
設定して処理後に設定を解除しています。
3.main()関数の最後にfree()がコールされていますが、TCHAR型は
メモリ領域を確保いているのでしょうか?
よろしくお願いいたします。
No.2
- 回答日時:
そのファイルがShift_JISで書かれていているのでは?
それをchar*として読み込めば、「全角」文字は「2つの文字」になります。
「ソ」を2つに分けたとき、2番目が「\」になっています。
この2番目の\を「ソの一部」と判断できずに「\」だと判定してしまっているのが今の現象です。
※ 「シフトJISのだめ文字」として有名な現象です。検索すれば他にもあることがわかります。
プロジェクト全体を変えるなら、TCHARを使って全角文字を「1文字」として処理するようにするのがいいのでしょう
(#1さんにある方法です)
ここだけを変えるなら、「2バイト目でないdelimiter」だけを処理するようにすることでしょう。
例えば。
bool is2byte = false ; // 2バイト目かどうかのフラグ
while(*str != NULL){
// 文字列を分割する処理
if( is2byte || (str != delimiter) ){ //2バイト目ならdelimiterとの比較はしない
words[i][j++] = *str;
// 次の処理が2バイト目かどうか
if(is2byte){
is2byte=false;
}else{
is2byte=(*strが2バイト文字の1バイト目):
}
}
else {
words[i][j] = NULL
i++; j=0;
is2byte=false;
}
とか。
あと、細かいことかもしれませんが
> while(*str != NULL){
> words[i][j] = NULL
「何も指していないポインタ」であるNULLと、ヌル文字'\0'は別です。
VC++の実装でNULL='\0'=0にはなっていますが。
回答ありがとうございます。
>「ソ」を2つに分けたとき、2番目が「\」になっています。
>この2番目の\を「ソの一部」と判断できずに「\」だと判定してしまっているのが今の現象です。
知りませんでした。
いままで文字コードを意識してコーディングをしたことがなかったもので・・・
>あと、細かいことかもしれませんが
>> while(*str != NULL){
>> words[i][j] = NULL
>「何も指していないポインタ」であるNULLと、ヌル文字'\0'は別です。
>VC++の実装でNULL='\0'=0にはなっていますが。
こちらのほうは改修に合わせて修正します。
No.3
- 回答日時:
> 内部的にTCHAR型にキャストして処理すればよいでしょうか?
あなたのいうUNICODE対応とは何ですか?
呈示したコードは文字セットがchar, wchar_tのどちらでも動くことを
目的としたものです。
> setlocale()がコールされていますが、必須でしょうか?
それは僕がきめられることではありません。
> main()関数の最後にfree()がコールされていますが、TCHAR型は
> メモリ領域を確保いているのでしょうか?
コードを読んでください。spィt内で確保した領域を解放しています。
何度も回答いただきありがとうございます。
1.2.については不具合を指摘した人の環境を確認してみます。
3.についてはコードをよく読んでいませんでした。
#1で示していただいた内容と、他の方のアドバイスも含めて
プロジェクトのリーダに相談いたします。
お手数をおかけしました。
No.4
- 回答日時:
どうも
epistemeさんへの補足について
1.
そこなんですよね。
「ふつ~」に使いたいという前提では少なくとも
char*の内部表現とwchar_t*の内部表現は違いますので
こればっかりは全部マルチバイト専用のコードになってる場合
必要箇所全てにわたって書きなおす必要があります。
(現実的には「編集→検索と置換→クイック置換」などの機能を駆使して必要箇所をまとめて書き変えていく、といったことになるかと)
コストがかかっても変換する必要がある場合はWideCharToMultiByteやMultiByteToWideCharなどで変換する必要があります。
(MFCではCStringA、CStringW、CStringなどの便利な使い方があったと思いますが
http://www.usefullcode.net/2006/11/widechartomul …)
ただ
いずれにせよ、よっぽどでかいプロジェクトの場合、最初からこれが配慮されてないコードで書かれたものを正確に直し続けていく事は
結構大変な作業になることは覚悟しなければいけません。
ただし、特定の個所でのみ両対応にする、という場合はそれほど大変ではない場合もあります。
例えば
const char* text = "aaa";
MessageBox(NULL, text, "", 0 );
はデフォがマルチバイトの設定でしか通用しない書き方ですが
LPCTSTR text = _T("aaa");
MessageBox(NULL, text, _T(""), 0 );
とかに直していくのが面倒で、この付近に関してはマルチバイト文字しか使わなくてOKと分かってる場合は
const char* text = "aaa";
MessageBoxA(NULL, text, "", 0 );
こういう風に、呼び出し側の関数をマルチバイト専用に明示的に指定してやることで対処することも可能です。
逆に、ユニコード版しか用意されてない関数とかでは
LPCWSTR text = L"WINDOW";
HTHEME theme = OpenThemeData( hw, text );
などと、明示的にユニコード指定にしてやる必要があります。
2.
ユニコードを使う「可能性がある箇所」では必須です。
なのでTCHARを使う場合はやっておいてください。
とはいえ、アプリケーション中で一つのロケールしか必要ないのであれば
アプリケーションの最初に1回だけ呼んでおいて、そのまま変更しなければOKです。
3.
これは
epistemeさんが、baysidehotelさんの「関数のロジック」をもとに
splitを書いたときに
内部でmallocを使用したことに起因するものです。
もし私が一から作る場合は
確保と解放は近くに書かれてた方がぱっと見分かりやすいので、呼び出し側で確保するともいます。
コードを一部お借りすると
MFCが使えるという事でC++の規則込みで書いてしまいますが
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <locale.h>
#include <tchar.h>
typedef LPCTSTR const TcsLiteral;
int split( TCHAR* p, LPCTSTR words[], const TCHAR delimiter ){
int row = 0;
words[row] = p;
for ( ; *p; ++p ) {
if ( *p != delimiter ) continue;
*p = _T('\0');
words[++row] = p+1;
}
return ++row;
}
/* おためし */
int main() {
TcsLiteral input = _T("ソーシャル,ネットワーク,,ソリューション,");
LPCTSTR words[5]; //おためしなので決め打ち
TCHAR* const temp = _tcsdup( input ); //確保に当たる
setlocale( LC_ALL, "japanese" );
const int n = split( temp, words, _T(',') );
for ( int i = 0; i < n; ++i ) _tprintf( _T("[%s]\n"), words[i] );
free(temp); //対で解放
}
私ならこんな感じにするかもしれません。
実行結果
[ソーシャル]
[ネットワーク]
[]
[ソリューション]
[]
回答ありがとうございます。
1.2.について
→やはり根本的には相当数のコストをかけて該当箇所を修正するのがベストですね
ただ実施できるかは。。。
3.について
→epistemeさんのコードをよく見ていませんでした。
メモリの確保と開放がmain()内にあるので保守性を考慮するとこちらのほうが
いいのかもしれません。
No.5
- 回答日時:
もひとつ
>プログラム内部では文字列は原則std::string型で保持していて
依然大変な作業になる可能性はあるかもしれませんが、状況次第では
こちらのページの内容が役に立つかもしれません。
・UNICODEで分けないtstring型を宣言する
http://marupeke296.com/TIPS_No14_tstring.html
No.6
- 回答日時:
>char* str: 分割対象の文字列
「分割対象の文字列」の文字コードがUTF-8であるなら問題ないですが
(問題なといってもASCIIコードの文字で分割する場合においてですが)
Shift_JISだと処理内部でシングルバイト文字と
マルチバイト文字の1バイト目と2バイト目を区別する必要があります。
なぜ区別しないといけないかはShift_JISの文字コード表とにらめっこするなり
googleなどで検索するなりしてみてください。
また他の人もいってますが
>while(*str != NULL){
>words[i][j] = NULL
コンパイラはエラーや警告を出さないかもしれませんが
内容的にはバグと変わりありません。
NULLはポインタなので例え定義が0であろうと
charとして扱ってはいけません。
No.8
- 回答日時:
おお!全員に対してしっかりご返答なさっていらっしゃいますし
ご担当なさっているプロジェクトのこれからについて大事なことでしょうから
こちらとしてももうちょいしっかり情報を補足しておきます。
・ユニコードを使う「可能性がある箇所」では必須です。
と書きましたが、これの意図するところは
実際には
現状ではファイルI/Oやコンソール出力時などのみで、確かに十分、というのが実状かもしれません。
ただし「将来的なことまで見据えて考えると」
Microsoftの気まぐれ一つで状況が変わったりするかもしれませんし
「特定の、この関数」内部では未来永劫絶対setlocaleの必要はない!と断言するのは
さすがにMicrosoft幹部でも難しいと思うので
場合分けしてやって、将来変更がないか常におびえる状況になるよりも
先に全体にやっておいた方が遥かにリスクが少ない
ので「必須と思った方が遥かに楽であろう」という意味です。
・usertype.datについて
既にご存知でしたら良いですが
もしまだ未着手でしたらだいぶ可読性が上がるかもしれませんので
usertype.datを使うと、intやifなどの、元々色がつくようになっている
予約語など以外にも
VC++上で任意のキーワードに色がついた状態で表示出来ます。
こちらの環境では
Program Files\Microsoft Visual Studio 9.0\Common7\IDEの中に
usertype.datを置くと効果がありました。
既にある場合は、ここに色を変えたいキーワードを一行にひとつずつ書き加えます。
ない場合は、例としてはメモ帳などで以下のように書いたテキストファイルを作り
LPCTSTR
TcsLiteral
TCHAR
ファイル名と拡張子をusertype.datにして上記などの、適切なディレクトリに置き
VC++を再起動すれば効果が確認できるはずです。
・実際の実装法について
実際では決め打ちとかできないので
標準ではない便利なライブラリとかになるべく頼らず書くなら(C++03の仕様では)
これくらい書かないといけないかもしれません。
(なお、載せるのに分割状況書くのは面倒なので全部インライン指定になってますが、コンストラクタやデストラクタなどは一見短いように見えて、生成される機械語は長くなったりする可能性があるのでインライン化はこの場合お勧めできません。また、細かい仕様とかその辺は調整してください。)
typedef LPCTSTR const TcsLiteral;
class TcsPtrArray {
typedef struct LINEARDATA {
LINEARDATA* next;
TcsLiteral p;
LINEARDATA( TcsLiteral p_ ) : next(NULL), p(p_){}
} LD;
TCHAR* temp;
LD* words;
TcsPtrArray() : temp(NULL), words(NULL) {}
~TcsPtrArray(){
free( temp );
ReleaseAllWords();
}
void ReleaseAllWords(){
for ( LD* a; a = words; ){ words = words->next; delete a; }
words = NULL;
}
public:
static void CheckFunc_S( TcsLiteral s ){ _tprintf( _T("[%s]\n"), s ); }
void Split( const TCHAR delimiter ){
if (words) ReleaseAllWords();
TCHAR* c = temp;
words = new LD( c );
LD** pp = &words->next;
for ( ; *c; ++c ) {
if ( *c != delimiter ) continue;
*c = _T('\0');
*pp = new LD( c+1 );
pp = &(*pp)->next;
}
}
template < class F >
void ForEach( F& f ) const { for ( const LD* a = words; a ; a = a->next ) f(a->p); }
static bool Create( TcsPtrArray** pp, TcsLiteral s ){ //生成
try { *pp = new TcsPtrArray; }
catch ( std::bad_alloc ){ return false; }
(*pp)->temp = _tcsdup( s );
if ( !(*pp)->temp ) return false;
return true;
}
static void Release( TcsPtrArray** pp ){ //解放
delete *pp; *pp = NULL;
}
};
少々長くなりましたが、必要ヘッダとかをインクルードすればコピペでサクッと出来ると思います。
機能拡張等は用途に応じてご自由に行ってください。
ここまで準備しておけば使い方は簡単です。
int main() {
setlocale( LC_ALL, "japanese" );
TcsLiteral input = _T("ソーシャル,ネットワーク,,ソリューション,");
TcsPtrArray* tpa;
if ( !TcsPtrArray::Create( &tpa, input ) ) return 0;
tpa->Split( _T(',') );
tpa->ForEach( TcsPtrArray::CheckFunc_S );
TcsPtrArray::Release( &tpa ); //Createに対して対で解放
}
分からない箇所がありましたら聞いてください。
No.10ベストアンサー
- 回答日時:
それとCreate関数内で
if ( !(*pp)->temp ) return false;
となってる部分がありますが
if ( !(*pp)->temp ){
Release( pp );
return false;
}
にしとかないと(ほとんどないとは思いますが)メモリリークの危険が一応残っていますね。
ついでにstd::bad_allocに対しても全部例外安全にしようとすると
Splitのnewのとこもこだわらないといけませんが
std::bad_allocが飛ぶ状況ってほとんどまず考慮しないでいいかもしれないので
アプリケーション次第ではtry-catch使わない方向で割り切ってしまっても全然いいかもしれません。
(後で細切れで気づくと何度も投稿しないといけないのが不便ですw 編集出来たらいいのに >教えて!goo )
回答ありがとうございます。
本日、リーダさんに確認をとりました。
プロジェクトのどこかで工数を確保して改修することになりそうです。
いただいた回答全てを盛り込めるほどの工数は無理と思いますが・・・
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# プログラミングの授業の課題です 1 2023/01/17 22:15
- C言語・C++・C# c言語 プログラムのエラー 1 2023/02/11 20:31
- C言語・C++・C# sprintf()の使い方について 1 2022/08/17 16:16
- C言語・C++・C# 宣言する関数の形が決まっている状態で、 str1とstr2の文字列をこの順に引っ付けてstrに保存し 2 2022/05/30 18:21
- C言語・C++・C# str[j++]の意味 2 2022/08/30 16:20
- C言語・C++・C# c言語配列の結合についてです。 なぜうまくいかないのでしょうか。 #include <stdio.h 4 2022/05/30 22:42
- その他(プログラミング・Web制作) python質問 1 2023/08/14 11:54
- 大学・短大 C言語線形リストの問題です 3 2022/12/22 00:45
- その他(プログラミング・Web制作) pythonにおける単方向リストの実装について 4 2022/07/13 12:34
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
fgetsなどのときのstdinのバッ...
-
干支のプログラム
-
TCP/IP通信型大文字・小文字変...
-
c++ 文字列を入力して、一文字...
-
C言語のfor文です。 繰り返しの...
-
CStringをwchar_tに変換したい
-
Visual Studio strcpyについて
-
charからLPTSTRへの変換方法
-
charでの計算?
-
配列をnビットシフトする
-
int main()の・・・
-
double型の値をchar配列に変換...
-
switch文で文字を比較すること...
-
DPマッチング
-
int型からchar型への変換
-
文字列から空白を取り除きたい...
-
バイナリファイルをコピーする...
-
c言語でユーザ関数を利用して入...
-
C言語のポインターで詰まっている
-
C++ の FileCopy の設定が解り...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
fgetsなどのときのstdinのバッ...
-
charでの計算?
-
C言語のfor文です。 繰り返しの...
-
charからLPTSTRへの変換方法
-
文字列から空白を取り除きたい...
-
C言語の入力した文字を反転させ...
-
'const char *' 型は 'char *' ...
-
配列をnビットシフトする
-
str系関数を使わずに二つの文字...
-
int main()の・・・
-
atoi( ) の反対をやりたい
-
CStringをwchar_tに変換したい
-
c++ 文字列を入力して、一文字...
-
switch文で文字を比較すること...
-
干支のプログラム
-
3桁区切(コンマ)記号をつけ...
-
絶対パスからのファイル名の切...
-
間接操作のレベルとは
-
間接参照のレベルが異なっています
-
型変換
おすすめ情報