0

テキストファイルの内容は次のとおりです。

SQUARE 2
SQUARE
RECTANGLE 4 5

strtok()ループが2番目の「SQUARE」の終わりをとらず、長さを0にする理由を理解しようとしています。strtokの背後にある概念も完全には理解していません。 strtokについての講義。コードは次のとおりです。

#include <cstring>
#include <cstdlib>
#include <iostream>
using std::cout;
using std::endl;
using std::cin;
using std::ios;

#include<iomanip>
using std::setprecision;

#include <fstream>
using std::ifstream;

const int MAX_CHARS_PER_LINE = 512;
const int MAX_TOKENS_PER_LINE = 20;
const char* const DELIMITER = " ";

int main()
{
// create a file-reading object
ifstream fin;
fin.open("geo.txt"); // open a file
if (!fin.good()) 
    return 1; // exit if file not found

//PI
float pi = 3.14159265359; 

//DIMENSIONS
float length, width, height, radius;

//AREAS, PERIMETERS, VOLUMES
float areaSquare, periSquare;
float areaRectangle, periRectangle;
float areaCube, volCube;
float areaPrism, volPrism;
float areaCircle, periCircle;
float areaCylinder, volCylinder;

// read each line of the file
while (!fin.eof())
{
    // read an entire line into memory
    char buf[MAX_CHARS_PER_LINE];
    fin.getline(buf, MAX_CHARS_PER_LINE);
    // parse the line into blank-delimited tokens
    int n = 0; // a for-loop index

    // array to store memory addresses of the tokens in buf
    const char* token[MAX_TOKENS_PER_LINE] = {0}; // initialize to 0

    // parse the line
    token[0] = strtok(buf, DELIMITER); // first token
    if (token[0]) // zero if line is blank
    {
        for (n = 1; n < MAX_TOKENS_PER_LINE; n++)
        {
            token[n] = strtok(0, DELIMITER); // subsequent tokens
            if (!token[n] || token[n]==0) break;
        }
    }

    if(strcmp("SQUARE", token[0]) == 0) //1
    {
        length = atof(token[1])?atof(token[1]):0;
        areaSquare = length * length;
        periSquare = 4 * length;

        cout.setf(ios::fixed|ios::showpoint);
        cout << setprecision(2);
        cout << token[0] << ' ' << "length="<< token[1] << ' ';
        cout << "Area=" << areaSquare << ' ';
        cout << "Perimeter=" << periSquare << '\n';
        cout.unsetf(ios::fixed|ios::showpoint);
        cout << setprecision(6);
    }

    else if(strcmp("RECTANGLE", token[0]) == 0) //2
    {
        length = atof(token[1])?atof(token[1]):0;
        width = atof(token[2])?atof(token[2]):0;

        areaRectangle = length * width;
        periRectangle = 2 * length + 2 * width;

        cout.setf(ios::fixed|ios::showpoint);
        cout << setprecision(2);
        cout << token[0] << ' ' << "length="<< token[1] << ' ';
        cout << "width=" << token[2] << ' ' ;
        cout << "Area=" << areaRectangle << ' ';
        cout << "Perimeter=" << periRectangle << '\n';
        cout.unsetf(ios::fixed|ios::showpoint);
        cout << setprecision(6);
        }
    else
    {
        cout << "End of program. Press ENTER to exit.";
        cin.ignore(1000,10);
        break;
}
    }
}
4

4 に答える 4

2

これが動作するバージョンです。

主な違いは、

  1. char*の配列を20文字の文字列の配列に変更しました。これにより、配列要素にメモリが割り当てられることが保証されます。この場合、それらはnullポインターであり、strtokがNULLを返したときにこのままであり、NULLポインターを使用することはできません。
  2. strtokへの2番目の呼び出しは「strtok(0、DELIMITER)」ですが、「strtok(NULL、DELIMITER)」である必要があります。

それらが唯一のdiffだと思いますが、diffユーティリティを使用して確認してください。

#include <cstring>
#include <cstdlib>
#include <iostream>
using std::cout;
using std::endl;
using std::cin;
using std::ios;

#include<iomanip>
using std::setprecision;

#include <fstream>
using std::ifstream;

const int MAX_CHARS_PER_LINE = 512;
const int MAX_TOKENS_PER_LINE = 20;
const char* const DELIMITER = " ";

int main()
{
// create a file-reading object
   char *tok;
   ifstream fin;
   fin.open("geo.txt"); // open a file
   if (!fin.good())
       return 1; // exit if file not found

       //PI
       float pi = 3.14159265359;

       //DIMENSIONS
       float length, width, height, radius;

       //AREAS, PERIMETERS, VOLUMES
       float areaSquare, periSquare;
       float areaRectangle, periRectangle;
       float areaCube, volCube;
       float areaPrism, volPrism;
       float areaCircle, periCircle;
       float areaCylinder, volCylinder;

       // read each line of the file
       while (!fin.eof())
       {
           // read an entire line into memory
           char buf[MAX_CHARS_PER_LINE];
           fin.getline(buf, MAX_CHARS_PER_LINE);
           // parse the line into blank-delimited tokens
           int n = 0; // a for-loop index

           // array to store memory addresses of the tokens in buf
//         const char* token[MAX_TOKENS_PER_LINE] = {0}; // initialize to 0
           char token[MAX_TOKENS_PER_LINE][20];
           for (n=0;n<MAX_TOKENS_PER_LINE;n++)
               {
               token[n][0] = NULL;
               }
           // parse the line
           tok = strtok(buf, DELIMITER); // first token
           if (tok == NULL)
               break;
           strcpy(token[0],tok);
           if (token[0]) // zero if line is blank
               {
               for (n = 1; n < MAX_TOKENS_PER_LINE; n++)
                   {
                   tok = strtok(NULL, DELIMITER); // subsequent tokens
                   if (tok == NULL)
                       break;
                   strcpy(token[n],tok);
//                 if (!token[n] || token[n]==0) break;
                   }
               }
           if(strcmp("SQUARE", token[0]) == 0) //1
                {
                length = atof(token[1])?atof(token[1]):0;
                areaSquare = length * length;
                periSquare = 4 * length;

                cout.setf(ios::fixed|ios::showpoint);
                cout << setprecision(2);
                cout << token[0] << ' ' << "length="<< token[1] << ' ';
                cout << "Area=" << areaSquare << ' ';
                cout << "Perimeter=" << periSquare << '\n';
                cout.unsetf(ios::fixed|ios::showpoint);
                cout << setprecision(6);
                }

            else if(strcmp("RECTANGLE", token[0]) == 0) //2
                {
                length = atof(token[1])?atof(token[1]):0;
                width = atof(token[2])?atof(token[2]):0;

                areaRectangle = length * width;
                periRectangle = 2 * length + 2 * width;

                cout.setf(ios::fixed|ios::showpoint);
                cout << setprecision(2);
                cout << token[0] << ' ' << "length="<< token[1] << ' ';
                cout << "width=" << token[2] << ' ' ;
                cout << "Area=" << areaRectangle << ' ';
                cout << "Perimeter=" << periRectangle << '\n';
                cout.unsetf(ios::fixed|ios::showpoint);
                cout << setprecision(6);
            }
        else
            {
            cout << "End of program. Press ENTER to exit.";
            cin.ignore(1000,10);
            break;
            }
        }
}
于 2012-09-01T02:04:50.807 に答える
2

セグメンテーション違反は次の原因で発生します。

length = atof(token[1])?atof(token[1]):0;

token[1]がトークン化されたと想定するのを間違えました。2番目の「SQUARE」を見ると、その行でtoken[1]がNULLに設定されていることがわかります。次に、当然のことながらエラーになるatof()にNULLを渡します。

また、strtok()を不適切に使用しています。strtok()自体は破壊的な操作であるため、結果からstrcpy()を実行する理由はありません。

それで、これがstrtokについての講義です。

第一に、それは邪悪ですが、とにかく時々それを使うほど便利です。トークナイザーは、書くのが面倒な場合があります。

strtokの背後にある考え方は、簡単なトークナイザーを作成することでした。トークナイザーは書くのが面倒で、コンピューターを簡単に爆破できるようにすることを気にしないのであれば、そのインターフェースは実際にはかなりまともです。たとえば、ごく少量のコードを使用して、コマンドライン引数を解析できます。

ただし、strtokは、使用する文字列を破壊します。検出したトークンを0に置き換え、戻り値を自動的にnullで終了します。つまり、返された文字列をコピーせずに直接使用できます。このような文字列:

here are spaces0

に変更されます

here0are0spaces0

ここで、0はストリング文字(0)の終わりを区切ります。これは適切に行われ、ここ、are、およびスペースへのポインターを取得します。

strtokは静的変数を使用します。つまり、呼び出し間で状態情報を保持します。最初の呼び出しで、トークン化しようとしている文字列へのポインタを渡します。それ以降は、NULLポインターを渡して、前に中断したところから続行することを通知します。次のトークンを返し、文字列の終わりを見つけるとNULLを返します。

strtokループは非常に簡単に記述できます。このコードは、文字列を適切にトークン化します。次のサンプルコードは醜いですが、私は疲れていると非難します。

char *input_string;        // Just so we have it
const int MAX_TOKENS = 10; // Arbitrary number
char *tokens[MAX_TOKENS];  // This will do all the storage we need.
tokens[0] = strtok(input_string, " -=\""); // Setup call.
int number_of_tokens = 1;  // We've already filled tokens[0], so we have 1 token. We want to start filling from 1.

do {
    if (tokens[number_of_tokens] = strtok(NULL," -=\"")) number_of_tokens++;
    else break;
} while(number_of_tokens < MAX_TOKENS);

ループの最初の行はCプログラマーにとって一般的な方法ですが、読みやすさのために醜いです。これが何をするかです:

a)tokens[number_of_tokens]をstrtokの戻り値に設定します。b)それがNULLの場合、ループを終了します(2行目)。addendnum:インラインテストがあります。if(a = 1)を実行すると、trueが返され、aが1に設定されます。if(a = 0)の場合、aを0に設定すると、falseが返されます。この行は、strtok( )はNULLを返します、まあ、それは誤りです。c)それがNULLでない場合、tokens [number_of_tokens]には、文字列内で見つかった次のトークンへのポインタが含まれるようになりました。d)トークンが見つかったため、トークンの数(number_of_tokens)が増加します。5)保持するポインタの配列へのインデックスとして、トークンの数をカウントする変数を再利用します。6)strtokがNULLを返す条件、またはwhile()条件(この場合、10個を超えるトークンがある)のいずれかを満たすまで、無限にループします。

この文字列が与えられた場合:

ここにsome=words0があります

それはそのようになります

*tokens[0]="here"
*tokens[1]="are"
*tokens[2]="some"
*tokens[3]="words"
*tokens[4] = NULL
number_of_tokens = 4

ご覧のとおり、その文字列はメモリ内で次のように置き換えられるため、何もコピーする必要はありません。

here0are0some0words0

ここで、0はストリング文字(0)の終わりを区切ります。

これがあなたの質問に答えることを願っています。

于 2012-09-02T01:37:42.447 に答える
0

Ok。あなたのラインが

const char* token[MAX_TOKENS_PER_LINE] = {0};

ポインタの配列を作成しますが、それらのどれも何も指していません。最初の要素は0(NULLアドレス)に設定され、残りは初期化されません。行2(1つの要素を持つ)を実行して処理すると、token[0]は'SQUARE'を指しますが、token [1]には値0x00(NULL)が与えられます。これは無効なメモリ位置です。次に、token[1]を次の行で処理します

length = atof(token[1])?atof(token[1]):0;

token [1]はNULLポインターであるため、これによりセグメンテーション違反が発生します。私のバージョンでは、token [1]は長さを0に設定するNULL文字列への有効なポインターです。-gフラグを使用してコンパイルすることをお勧めします(例:g ++ -g test.cpp -o test)。次に、「gdb test」を呼び出し、break、run、continueコマンドを使用してコードをステップ実行します。printコマンドを使用して、変数の内容を表示できます。

gdbでの最初の実行では、「ru​​n」と入力するだけです。これは失敗します。次に、失敗した行を示す「bt」と入力します。これをlinenumberと呼びます。

2回目の実行では、「break linenumber」、次に「run」と入力すると、実行が実行される前に、失敗した行で実行が停止します。次に、変数の内容を確認して、失敗する理由の大きな手がかりを得ることができます。

于 2012-09-02T00:39:41.650 に答える
0

これは、コードに密接に基づいた動作中のC++です。

I/O処理を改訂しました。fin.getline()行を取得したかどうかを報告するため、ループを制御するために使用する必要があります。fin.eof()私の見積もりでは(feof(fp)Cのように)危険信号の警告です。

単語の後に長さトークンがあることを確認しないため、コアダンプが発生しますSQUARE。改訂されたコードは、正確に正しい数のトークンを取得したことを確認し、そうでない場合は文句を言います。を使用するコードstrtok()は、単一のループに統合されています。見つかったトークンを示す診断印刷ステートメントが含まれています(何が起こっているかを確認するのに役立ちます)。

未使用の変数の山を削除しました。各変数は、計算ブロックで定義および初期化されます。

C文字列とC++の使用については、無限の可能性がありstrtok()ます(すべてのコードがのようなC標準I / O関数を使用してCで記述されている場合、印刷ははるかに簡潔になりますprintf())。ストレンジエラーstrtok()で代替案の議論を見つけることができます。ユーザー入力の読み取りと文字列のチェックで、ライブラリ関数に災害が発生する理由に関する別の説明を見つけることができます。strtok()strtok()

質問の3行のデータの作業コード

#include <cstring>
#include <cstdlib>
#include <iostream>
using std::cout;
using std::endl;
using std::cin;
using std::ios;
using std::cerr;

#include<iomanip>
using std::setprecision;

#include <fstream>
using std::ifstream;

const int MAX_CHARS_PER_LINE = 512;
const int MAX_TOKENS_PER_LINE = 20;
const char* const DELIMITER = " ";

int main()
{
    // create a file-reading object
    const char *fname = "geo.txt";
    ifstream fin;
    fin.open(fname); // open a file
    if (!fin.good()) 
    {
        cerr << "Failed to open file " << fname << endl;;
        return 1; // exit if file not found
    }

    // read each line of the file
    char buf[MAX_CHARS_PER_LINE];
    while (fin.getline(buf, sizeof(buf)))
    {
        int n = 0;
        const char *token[MAX_TOKENS_PER_LINE] = {0};
        char *position = buf;

        while ((token[n] = strtok(position, DELIMITER)) != 0)
        {
            cout << "Token " << n << ": " << token[n] << endl;
            n++;
            position = 0;
        }

        if (strcmp("SQUARE", token[0]) == 0 && n == 2)
        {
            float length = atof(token[1])?atof(token[1]):0;
            float areaSquare = length * length;
            float periSquare = 4 * length;
            cout.setf(ios::fixed|ios::showpoint);
            cout << setprecision(2);
            cout << token[0] << ' ' << "length="<< token[1] << ' ';
            cout << "Area=" << areaSquare << ' ';
            cout << "Perimeter=" << periSquare << '\n';
            cout.unsetf(ios::fixed|ios::showpoint);
            cout << setprecision(6);
        }
        else if (strcmp("RECTANGLE", token[0]) == 0 && n == 3)
        {
            float length = atof(token[1])?atof(token[1]):0;
            float width = atof(token[2])?atof(token[2]):0;
            float areaRectangle = length * width;
            float periRectangle = 2 * length + 2 * width;
            cout.setf(ios::fixed|ios::showpoint);
            cout << setprecision(2);
            cout << token[0] << ' ' << "length="<< token[1] << ' ';
            cout << "width=" << token[2] << ' ' ;
            cout << "Area=" << areaRectangle << ' ';
            cout << "Perimeter=" << periRectangle << '\n';
            cout.unsetf(ios::fixed|ios::showpoint);
            cout << setprecision(6);
        }
        else
        {
            cout << "Unrecognized data: " << buf << endl;
        }
    }
}
于 2012-09-02T03:23:56.700 に答える