質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.07%

関数strtokを使う上で使用した「\t」の意味が知りたい。

受付中

回答 4

投稿

  • 評価
  • クリップ 0
  • VIEW 850

KZK13

score -7

以前kazma-sさんから頂いたプログラムを読み直していて疑問があります。

言語分析のためにmekabを使い、入力した文章を品詞ごとに分ける際に、品詞の部分以外の言葉のみを使いたかったため
strtokが必要だとわかりました。改行の部分を省くために「\n」を使うのはわかりるのですが、もう一つの「\t」は何を省くために使ったのでしょうか?
mecabからの単語は

 words[n] = strtok(buf, "\t\n");
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+5

この回答は質問者が理解可能なレベルを超えていますが、他の人が見たときに参考にするために回答しています。


strtokが必要だとわかりました。

MeCabを使って形態素を取り出すのにstrtokは必要ありません。MeCab内で各ノードはすでに分割されており、それぞれのノードへアクセスするためのAPI(ライブラリの関数)があります。strtok等で再度結果を分割する必要はありません。もし、そのようなことが必要な場合は、正しいAPIを使用していない事になります。

参考: MeCab ライブラリ

以下は、Cで、各ノードを一行ずつ表示するサンプルです。

#include <stdio.h>
#include <mecab.h>


int main(void)
{
    char *str = "今日の天気は晴れです。";

    mecab_t *mecab = mecab_new2("");
    if (mecab == NULL) return 1;

    const mecab_node_t *node = mecab_sparse_tonode(mecab, str);
    if (node == NULL) return 1;

    for (; node != NULL; node = node->next) {
        if (node->stat == MECAB_NOR_NODE || node->stat == MECAB_UNK_NODE) {
            fwrite(node->surface, sizeof(char), node->length, stdout);
            printf("\n");
        }
    }
    return 0;
}

mecab_sparse_tonode()でMeCabに文字列を分析するように依頼し、最初のノードを受け取ります。node->surfaceがそのノードの文字列ですが、後に続くノードも後ろにくっついています。そのため、node->lengthで長さを取得して、その分までの文字列を使うようにする必要があります。node->nextは次のノードを示し、最後のノードはNULLを示すようになっています。

CのAPIはC++のAPIのラッパーですが、CのみでもMeCabは扱う事ができますので、C++の知識は不要です。


「以前kazma-sさんから頂いたプログラム」がどのようなコードだったかは知りませんが、たぶん、ノード単位で使うことを想定していなかったのだと思われます。ノード単位でアクセスしたい場合は、ノード毎に取得できるAPIを呼び出す必要があります。それらも含めてライブラリのドキュメントに書いてありますし、サンプルにも簡単な例が書いてあります。他人から貰ったコードをそのまま使うのではなく、あくまで参考とするのが普通です。ライブラリのドキュメントをよく読んだ上で、利用可能な部分は使う、利用できなさそうな部分は別の物に起きかけるなど、自分でできるようにならないと、プログラムが完成することはないでしょう。

なお、\tが何かというとことに答えないのは、この質問が、XY 問題になってしまっているからです。


【追記】

例示のCのコードについてエラーになると言われたので、なぜ、エラーになる場合があるのかについて解説します。なお、CやC++の基礎的な知識が前提となりますので、あらかじめご了承ください。

例示のCのコードをC++としてコンパイルした場合、コンパイラの種類やバージョン、および、オプションによっては、

  • 問題なくコンパイルできる。
  • 警告が表示されるがコンパイルできる。
  • エラーとなりコンパイルできない。

の3パターンがあらわれます。これは文字列リテラルに関する歴史的経緯とその後のC++の対応によるものです。

まず、C++はCを拡張した作られた言語ですが、CがそのままC++として必ず動くわけではありません。C++はCの完全上位互換ではなく、Cとは非互換な部分があります。今回はその非互換な部分の一つが現れた結果です。.

"abc"のような文字列リテラルの型は、Cでのchar[N]です。Nはそのリテラルの終端null文字を含む大きさになります。しかし、文字列リテラルを構成する各文字を変更することは未定義な動作です。そこで、C++では、文字列リテラルの型はconst char[N]const修飾子によって保護するようになりました。(Cが作られた当初は、const修飾子そのものがなかったため、保護するという作りにしませんでした。その後の互換性から未だにchar[N]のままです。)

賢明な読者の方々であれば、constあり型の値をconstなし型の値に代入することはキャストをしないとできないことはご存じかと思います。つまり、C++ではconst char[N]char *に代入することはできないと言うことです。しかし、Cとの互換性のためにC++03までは特別に文字列リテラルからchar *へ代入することは許容されていました。C++11になって、そのような例外を認めず、明示的にキャストしない限り認めないとなったのです。

このことが複雑な事情を産みました。C++03までしか対応していない古いコンパイラであれば問題ありませんが、C++11に対応したコンパイラの場合、仕様を厳密に適用するのか、警告で済ますのか、仕様を無視するのかの選択が生まれ、コンパイルオプションによって選択できるようにしました。そのため、コンパイラの種類、バージョン、および、コンパイルオプションによって、コンパイルができたりできなかったり、警告やエラーが出る出ないも変わってくると言うことです。

なお、char s[] = "abc";と書く場合はC++11以上でも問題ありません。なぜなら、この場合は文字列リテラル部分は配列の初期化子として扱われ、char s[] = {'a', 'b', 'c', '\0'};相当の動作になるからです。これも、C/C++をご存じであれば、知っていて当たり前のことですね。

このようにCとC++は似ているようで、結構な違いあります。自分が書いているコードがCなのか、C++なのか、また、Cとしてコンパイルしているのか、C++としてコンパイルしているのかを正しく区別できないと、C/C++を扱うのは難しいです。特に、Visual StudioのC++コンパイラは、拡張子によってCなのかC++なのか扱いが変わるという動作がデフォルトになっており、さらに混乱することになるでしょう。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/03 18:46

    > すいません、エラーが出てしまい実行できていません。
    >
    > 以下はエラーメッセージです。
    > 1>C:\Users\Desktop\kaiwa\Source.cpp(7,52): error C2440: '初期化中': 'const char [23]' から 'char *' に変換できません。

    質問のタグに「C」とだけあるため、私は「Cで、各ノードを一行ずつ表示するサンプル」を書きました。しかし、あなたは何故か、拡張子を.cppにしています。Visual Studioでは拡張子が.cppの場合はC++としてコンパイルするようになっているため、Cのソースコードファイルの拡張子に.cppを付けることは適切ではありません(たとえ、他のコンパイラを使っていたとしても)。本当にCとしてコンパイルしているのかを確認してください。

    なお、上のこと理解できていなければ、CとC++を区別することができないと言うことです。その状態でもCだけを続けるのであれば、ご勝手にどうぞ。

    キャンセル

  • 2020/08/03 19:29 編集

    私は Visual Studio 2017 の VC++ を使っていますが、このコンパイラは C++ でコンパイルしてもエラーになりません。KZK13さんは Visual Studo 2019 の VC++ を使っているようで、それだと C++ 本来の仕様どおりエラーになるのでしょう。VS2017 の場合、&& や || の代わりに and や or を使うとエラーになるというのもありました。

    KZK13さん、エラーが出たら、メッセージをよく読んで、const を補い、
    const char *str = "今日の天気は晴れです。"; と修正するぐらいの実力をつけてください。
    あるいは、char str[] = "今日の天気は晴れです。"; と修正することもできます。
    違いがわからなければ、入門書に戻って学んでください。

    キャンセル

  • 2020/08/03 22:13

    >>KZK13さん、エラーが出たら、メッセージをよく読んで、const を補い、
    const char *str = "今日の天気は晴れです。"; と修正するぐらいの実力をつけてください。
    あるいは、char str[] = "今日の天気は晴れです。"; と修正することもできます。

    不出来ですいません。

    キャンセル

0

「\t」... タブコードです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/01 20:59

    あと、\0 (null 8進の0) , \r (0x0D), ...とかエスケープ文字で調べると分かるとかと思います。

    キャンセル

  • 2020/08/04 17:23

    どうもありがとうございます。
    ちなみに、タブコード\tって「・」のことですか?

    キャンセル

  • 2020/08/04 21:15

    ほとんど、別の回答で済んでいると思っていましたが、、
    タブコードは、キーボードの左側にある [Tab]キーで入力されるコードです。文字の区切りとかで使われます。水平タブと言った方が正確かも知れませんが、分かる人は少ないでしょうか。(VT: 垂直タブもあります)

    キャンセル

0

#pragma comment(lib, "libmecab.lib")
#include <mecab.h>
#include <stdio.h>

int main() {
    char buffer[] = "タバコを吸います。";
    MeCab::Tagger* tagger = MeCab::createTagger("");
    const char* result = tagger->parse(buffer);
    printf("[%s]\n", result);
    return 0;
}


実行結果

[タバコ    名詞,一般,*,*,*,*,タバコ,タバコ,タバコ
を        助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
吸い      動詞,自立,*,*,五段・ワ行促音便,連用形,吸う,スイ,スイ
ます      助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。        記号,句点,*,*,*,*,。,。,。
EOS
]


「タバコ」と「名詞,...」の間の区切り文字は '\t' です。
「...,タバコ,タバコ」と「を」の間の区切り文字は '\n' です。
「を」と「助詞,...」の間の区切り文字は '\t' です。
「...,ヲ,ヲ」と「吸い」の間の区切り文字は '\n' です。

MeCab でのパース結果を見て、単語だけ取り出そうとするとき、
分割したときの区切り文字が何か知りたいなあと思いませんでしたか?

追記
質問者が頑張れば理解可能な strtok の使用例。

#include <stdio.h>   // puts, printf
#include <string.h>  // strcpy, strtok

int main()
{
    const char *str = "タバコ\t名詞,一般,*,*,*,*,タバコ,タバコ,タバコ\n"
                      "を\t助詞,格助詞,一般,*,*,*,を,ヲ,ヲ\n";
    char buf[1024], *p;

    strcpy(buf, str);
    puts("-- \"\\n\" --");
    p = strtok(buf, "\n");
    while (p) { printf("[%s]\n", p); p = strtok(NULL, "\n"); }

    strcpy(buf, str);
    puts("-- \"\t\" --");
    p = strtok(buf, "\t");
    while (p) { printf("[%s]\n", p); p = strtok(NULL, "\t"); }

    strcpy(buf, str);
    puts("-- \",\" --");
    p = strtok(buf, ",");
    while (p) { printf("[%s]\n", p); p = strtok(NULL, ","); }

    strcpy(buf, str);
    puts("-- \",\\t\\n\" --");
    p = strtok(buf, ",\t\n");
    while (p) { printf("[%s]\n", p); p = strtok(NULL, ",\t\n"); }
}


なぜ、strcpy を何度も実行しているのか分かりますか?
strtok は元の文字列に '\0' をたくさん書き込みます。
そのことは理解できていますか?

追記2
strtok について説明します。

char str[] = "abc:123,def:45,ghi:678."; という文字列があったとします。

char *p = strtok(str, ",."); を実行すると、
strtok は、str の先頭から ',' または '.' を探しに行き、
str[7] に ',' が入っているのを見つけて、そこを '\0' に書き換えます。
"abc:123" という文字列を作ったことになります。
そして、'a' の入った str[0] のアドレスを返します。
その時 strtok は 'd' の入った str[8] のアドレスを内部に憶えておきます。

次に、p = strtok(NULL, ".,"); を実行すると、
strtok は文字列をもらえず、NULL をもらったので、先ほど憶えておいた
str[8] のアドレスから ',' または '.' を探しに行き、
"def:45" という文字列を作って、その先頭の文字のアドレスを返します。

これを繰り返すと、str は "abc:123\0def:45\0ghi:678\0" となります。

このように、元の文字列 str は書き換えられてしまうので、
今度は別の区切り文字、例えば ':' で分割しようと思ってももうできません。

だから、char buf[1024]; strcpy(buf, str); で str の文字列を buf に
コピーして、それを分割しているのです。

また、元の文字列が、const char *str = "abc:123,def:45,ghi:678."; だった場合、
str は配列ではなくポインタですから、文字列の実体は書き換えできない
文字列リテラル "abc:123,def:45,ghi:678." であり、ポインタがそれを指します。
strtok(str, ",.") とすることはできません。
この場合も、書き換え可能な char配列に strcpy でコピーしてから、
strtok を使うことになります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/03 22:44 編集

    すいません、先ほど、正しいかどうかはわかりませんが、自分なりにコードを解説してみました。
    正しいでしょうか?

    while (n < 100 && words[n]) {
    strtok(NULL, "\t\n");
    words[++n] = strtok(NULL, "\t\n");
    }
    の部分で品詞を消していると思うのですが、
    多分、 strtok(NULL, "\t\n");の部分は\t\nがなかった場合、空(NULL)の返り値を渡すため、
    「タバコ」の後に続く「名詞,一般,*,*,*,*,タバコ,タバコ,タバコ」の部分は空になり、「タバコ」のみがwords[++n] に渡され、それを繰り返すことで、 words[++n] = strtok(NULL, "\t\n");の部分によりwords[++n] に何も入らなくなるので、品詞を含まない単語のみがwords[++n] に入り、「タバコ」「を」「吸っている」などと表せるのかなと考えています。

    もし違った場合の質問なのですが、
    なぜ一つ目は返された "名詞,...,タバコ" のアドレスを word[++n] に入らないのでしょうか?
    strtok(NULL, "\t\n");の部分で "名詞,...,タバコ" の中に\t\nが含まれていないためでしょうか?

    キャンセル

  • 2020/08/03 23:30 編集

    strtok(NULL, "\t\n"); // 品詞のアドレスを word[++n] に入れていない。
    word[++n] = strtok(NULL, "\t\n"); // 単語のアドレスを word[++n] に入れている。

    word[++n] = がなければ、word[++n] に入らないのは当たり前ではありませんか?
    最終的に、word[] には、単語しか含まれない。品詞は含まれない。

    もし仮に次のように書いたとすれば、
    word[++n] = strtok(NULL, "\t\n"); // 品詞のアドレスを word[++n] に入れている。
    word[++n] = strtok(NULL, "\t\n"); // 単語のアドレスを word[++n] に入れている。

    word[] には、単語も品詞も入ります。
    なぜ分からないのかなあ?

    キャンセル

  • 2020/08/04 03:09 編集

    正直、凄くわかりにくいです。
    while (n < 100 && words[n]) {
    strtok(NULL, "\t\n");
    words[++n] = strtok(NULL, "\t\n");
    }
    の strtok(NULL, "\t\n");は「名詞,一般,*,*,*,*,タバコ,タバコ,タバコ」の部分を省くためにあり、
    次の words[++n] = strtok(NULL, "\t\n");で「吸ってる」の部分を wordsに入れるために、「吸ってる」の品詞や改行を表す部分を省くためにあるのでしょうか?
    もし違う場合は申し訳ないのですが、具体例を用いて
    while (n < 100 && words[n]) {
    strtok(NULL, "\t\n");
    words[++n] = strtok(NULL, "\t\n");
    }の解説をお願いできないでしょうか?

    キャンセル

-3

\t は正規表現のタブ文字に相当します。

正規表現 メタ文字一覧

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/01 20:57

    C言語のstrtokで指定する文字列に正規表現は関係ないよね。

    キャンセル

  • 2020/08/01 21:01

    あー、C言語ですか。
    見落としていました。
    たしかに関係ないですね。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.07%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る