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

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

新規登録して質問してみよう
ただいま回答率
85.35%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

4回答

2378閲覧

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

KZK13

総合スコア43

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2020/08/01 11:49

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

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

words[n] = strtok(buf, "\t\n");

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

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


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

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

参考: MeCab ライブラリ

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

C

1#include <stdio.h> 2#include <mecab.h> 3 4 5int main(void) 6{ 7 char *str = "今日の天気は晴れです。"; 8 9 mecab_t *mecab = mecab_new2(""); 10 if (mecab == NULL) return 1; 11 12 const mecab_node_t *node = mecab_sparse_tonode(mecab, str); 13 if (node == NULL) return 1; 14 15 for (; node != NULL; node = node->next) { 16 if (node->stat == MECAB_NOR_NODE || node->stat == MECAB_UNK_NODE) { 17 fwrite(node->surface, sizeof(char), node->length, stdout); 18 printf("\n"); 19 } 20 } 21 return 0; 22}

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/02 02:41

編集2020/08/04 10:03
raccy

総合スコア21739

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KZK13

2020/08/02 07:00

え!? mecabから出力される文章中の品詞やタブ文字を省くためにstrtokが必要なのではないのでしょうか?
episteme

2020/08/02 08:30

「この回答は質問者が理解可能なレベルを超えています」でしたね...
raccy

2020/08/02 08:32 編集

> KZK13さん 回答の通り、必要ありません。例で示したコードではstrtokおよびそれに類するような関数は使っていませんが、コードをコンパイルして実行してみれば、言葉一つ一つを分割して出力できているのがわかると思います。 ただ、コードの内容について理解できるかについては保証しかねます。
KZK13

2020/08/02 14:14

ん?入力した文章を分解するだけならmecabのみでstrtokが必要ないのはわかっています。しかし、 品詞やタブ文字などのいらない部分を省くためにstrtokが必要なのではないのでしょうか?
kazuma-s

2020/08/02 16:04

「入力した文章を分解するだけならmecabのみでstrtokが必要ない」という意味は、 「mecabだけで単語に分解できて、そこにはタブも品詞もない」という意味です。 タブも品詞もないのにどうやってそれを strtok で省くんですか? https://teratail.com/questions/278952 の私の回答の追記の MeCab::createTagger("-Owakati"); を使うコードも、 今回の raccyさんのコードも自分で実行して結果を確認しましたか? タブも品詞もなく、単語だけを切り出しました。 タブや品詞を省く必要はありません、と言っているのです。
KZK13

2020/08/03 03:19 編集

>>今回の raccyさんのコードも自分で実行して結果を確認しましたか? すいません、エラーが出てしまい実行できていません。 えーと、今回のように関数strtokでもできるし、raccyさんのようにstrtokを使わずにmecabに元からある関数でも同じようにできるってことでしょうか? MeCab::createTagger("-Owakati"); を使うコードの前に頂いたコードではstrtokをを使って品詞やタブを省いていたのではないのですか? 関数strtokが必要ないと言われますが、kazuma-sさんから頂いたソースは関数strtokを利用して分解された後についてくるタブ
kazuma-s

2020/08/03 03:41 編集

> エラーが出てしまい実行できていません。 エラーが出た時は、エラーメッセージを見せてください。 raccyさんのコードでは、単語だけが取り出せるので、strtok は不要。 MeCab::createTagger("-Owakati"); を使うコードも、単語だけが取り出せるので、strtok は不要。 最初に、KZK13さんが質問した MeCab の使い方では、単語と品詞がタブと改行という区切り文字で 繫がったひとつの文字列しか得られないので、それを strtok でばらばらにする必要があった、 ということです。 strtok で分解した後にはタブも改行もありません。まだ strtok が理解できませんか? この質問で私の回答に strtok の使用例のコードを追記しましたが、それを実行してみましたか? そのコードを詳しく調べてみましたか? エラーが出るんだったら、エラーメッセージを見せてください。 理解できなかったら、どこが分からないのかを質問してください。
KZK13

2020/08/03 05:07 編集

>>最初に、KZK13さんが質問した MeCab の使い方では、単語と品詞がタブと改行という区切り文字で 繫がったひとつの文字列しか得られないので、それを strtok でばらばらにする必要があった、 ということです。 それは理解できています。必要な単語のみほしかったので strtok でばらばらにする必要があったのはわかっています。 しかし、少しわからないことがあります。改行やタブを省くためにstrtok(buf, "\t\n");の部分で省き、文章をばらばらにしますが、 それでも 例えば、 「すもも すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ」 の名詞,一般,*,*,*,*,すもも,スモモ,スモモは残っていると思うのですが、それはkazuma-sさんから頂いたコードのどこで処理をしているのでしょうか? こちらがコードです。 https://pastebin.com/WYfH9XT5 以下はエラーメッセージです。 1>C:\Users\Desktop\kaiwa\Source.cpp(7,52): error C2440: '初期化中': 'const char [23]' から 'char *' に変換できません。
episteme

2020/08/03 05:30

> ... それはkazuma-sさんから頂いたコードのどこで処理をしているのでしょうか? 37行目 >1>C:\Users\Desktop\kaiwa\Source.cpp(7,52): error C2440: '初期化中': 'const char [23]' から 'char *' に変換できません。 このエラーの対処方法がわからないのですか?
raccy

2020/08/03 09: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だけを続けるのであれば、ご勝手にどうぞ。
kazuma-s

2020/08/03 10:33 編集

私は Visual Studio 2017 の VC++ を使っていますが、このコンパイラは C++ でコンパイルしてもエラーになりません。KZK13さんは Visual Studo 2019 の VC++ を使っているようで、それだと C++ 本来の仕様どおりエラーになるのでしょう。VS2017 の場合、&& や || の代わりに and や or を使うとエラーになるというのもありました。 KZK13さん、エラーが出たら、メッセージをよく読んで、const を補い、 const char *str = "今日の天気は晴れです。"; と修正するぐらいの実力をつけてください。 あるいは、char str[] = "今日の天気は晴れです。"; と修正することもできます。 違いがわからなければ、入門書に戻って学んでください。
KZK13

2020/08/03 13:13

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

0

C++

1#pragma comment(lib, "libmecab.lib") 2#include <mecab.h> 3#include <stdio.h> 4 5int main() { 6 char buffer[] = "タバコを吸います。"; 7 MeCab::Tagger* tagger = MeCab::createTagger(""); 8 const char* result = tagger->parse(buffer); 9 printf("[%s]\n", result); 10 return 0; 11}

実行結果

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

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

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

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

C

1#include <stdio.h> // puts, printf 2#include <string.h> // strcpy, strtok 3 4int main() 5{ 6 const char *str = "タバコ\t名詞,一般,*,*,*,*,タバコ,タバコ,タバコ\n" 7 "を\t助詞,格助詞,一般,*,*,*,を,ヲ,ヲ\n"; 8 char buf[1024], *p; 9 10 strcpy(buf, str); 11 puts("-- \"\n\" --"); 12 p = strtok(buf, "\n"); 13 while (p) { printf("[%s]\n", p); p = strtok(NULL, "\n"); } 14 15 strcpy(buf, str); 16 puts("-- \"\t\" --"); 17 p = strtok(buf, "\t"); 18 while (p) { printf("[%s]\n", p); p = strtok(NULL, "\t"); } 19 20 strcpy(buf, str); 21 puts("-- \",\" --"); 22 p = strtok(buf, ","); 23 while (p) { printf("[%s]\n", p); p = strtok(NULL, ","); } 24 25 strcpy(buf, str); 26 puts("-- \",\t\n\" --"); 27 p = strtok(buf, ",\t\n"); 28 while (p) { printf("[%s]\n", p); p = strtok(NULL, ",\t\n"); } 29}

なぜ、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/01 14:35

編集2020/08/03 11:29
kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KZK13

2020/08/01 15:08

>>分割したときの区切り文字が何か知りたいなあと思いませんでしたか? 知りたいと思い調べたのですが、わからず今回質問しました。
KZK13

2020/08/01 15:10

えと、何もない空白の空間を省くために'\t' を使用したのでしょうか?
fu7mu4

2020/08/02 00:57

"何もない空白の空間"にタブ文字がはいっています。 タブ文字を\tで表しています。
KZK13

2020/08/02 06:58

fu7mu4さん、わかりやすい説明どうもありがとうございます。
KZK13

2020/08/03 05:20 編集

>>strcpy を何度も実行しているのか分かりますか? 文章が「,\t\n"」のいずれかを含んでいる場合もあるため、一気に「,\t\n"」は出来ず、部分的に「,\t\n"」のいずれかを省くために何度もstrcpy を行っていると考えています。 >>strtok は元の文字列に '\0' をたくさん書き込みます。 えと、指定した「,\t\n"」などの文字を省いた後で、省いた部分に '\0'を書き込むのですか?
KZK13

2020/08/03 07:57

"タバコ\t名詞,一般,*,*,*,*,タバコ,タバコ,タバコ\n" "を\t助詞,格助詞,一般,*,*,*,を,ヲ,ヲ\n" を各strtokのバージョンごとにどのように区切られるか行ったプログラムですね。
KZK13

2020/08/03 13:16

今更ですいません、kazuma-sさんから頂いたhttps://pastebin.com/3uQiNc5mのプログラムにおいて、 while (n < 100 && words[n]) { strtok(NULL, "\t\n"); words[++n] = strtok(NULL, "\t\n"); } の部分が「名詞,一般,*,*,*,*,タバコ,タバコ,タバコ」などの必要ない部分を省いていると思うのですが、 どうやって省いているのか処理の動きがわかりません。どうか教えて頂けないでしょうか?
kazuma-s

2020/08/03 13:38

whileループの中の 2つの strtok のうち、 一つ目は返された "名詞,...,タバコ" のアドレスを word[++n] に入れていません。 二つ目は返された "を" のアドレスを word[++n] にいれています。 ループを何度も回っていると、最後には word[n] に NULL が入るのでループを終了します。 ループ終了後、配列 word[] に入ったの単語だけです。品詞は入れていませんから。 なぜ、このことがコードから読み取れないのかが、私には不思議でたまりません。
KZK13

2020/08/03 13:51 編集

すいません、先ほど、正しいかどうかはわかりませんが、自分なりにコードを解説してみました。 正しいでしょうか? 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が含まれていないためでしょうか?
kazuma-s

2020/08/03 14:48 編集

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[] には、単語も品詞も入ります。 なぜ分からないのかなあ?
KZK13

2020/08/05 15:53 編集

正直、凄くわかりにくいです。 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"); }の解説をお願いできないでしょうか?
guest

0

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

正規表現 メタ文字一覧

投稿2020/08/01 11:54

technocore

総合スコア7337

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Daregada

2020/08/01 11:57

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

2020/08/01 12:01

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

0

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

投稿2020/08/01 11:53

pepperleaf

総合スコア6385

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pepperleaf

2020/08/01 11:59

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

2020/08/04 08:23

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

2020/08/04 12:15

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問