0

这是一个文本文件的内容:

SQUARE 2
SQUARE
RECTANGLE 4 5

我试图弄清楚为什么我的 strtok() 循环不会结束第二个“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 个字符字符串数组。这保证了数组元素已分配内存,在您的情况下,它们是空指针,并且当 strtok 返回 NULL 时保持这种状态,然后您不能使用 NULL 指针。
  2. 对 strtok 的第二次调用是“strtok(0,DELIMITER)”,但应该是“strtok(NULL,DELIMITER)”。

我认为它们是唯一的差异,但使用 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] 已被标记化。如果您查看第二个“SQUARE”,您会发现对于该行,它会将 token[1] 设置为 NULL。然后将 NULL 传递给 atof() ,这是可以理解的错误。

您还错误地使用了 strtok() 。没有理由从它的结果中去 strcpy(),因为 strtok() 本身就是一个破坏性的操作。

所以这里有一个关于 strtok 的讲座。

首先,它是邪恶的,但非常方便,以至于你有时会使用它。Tokenizers 写起来可能会很痛苦。

strtok 背后的想法是创建一个简单的标记器。分词器写起来很麻烦,如果你不介意让它很容易炸毁你的计算机,它的界面实际上相当不错。例如,您可以使用非常少量的代码来解析命令行参数。

但是, strtok 对您使用它的字符串具有破坏性。它将用 0 替换它找到的标记,自动以 null 终止返回的值。这意味着您可以直接使用返回的字符串而无需复制它。像这样的字符串:

here are spaces0

改为

here0are0spaces0

其中 0 分隔字符串字符 (0) 的结尾。这是就地完成的,你会得到指向这里、是和空格的指针。

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,则终止循环(第二行)。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 个标记)。

如果给它这个字符串:

这里有一些=words0

这将是

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

如您所见,无需复制任何内容,因为该字符串在内存中被替换为:

这里0是0some0words0

其中 0 分隔字符串字符 (0) 的结尾。

我希望这回答了你的问题。

于 2012-09-02T01:37:42.447 回答
0

这是一些基于您的代码的工作 C++。

我已经修改了 I/O 处理;fin.getline()报告它是否有一条线,所以它应该用来控制循环;fin.eof()在我的估计中是一个危险信号(就像feof(fp)在 C 中一样)。

发生核心转储是因为您没有检查在 word 之后是否有长度标记SQUARE。修改后的代码检查它是否获得了正确数量的令牌,如果没有则抱怨。使用的代码strtok()已经统一到一个循环中;它包含一个诊断打印语句,显示刚刚找到的令牌(对于检查正在发生的事情很有价值)。

我删除了一堆未使用的变量;每个变量在计算块中定义和初始化。

关于使用 C 字符串和strtok()在 C++ 中可能存在无穷无尽的保留(如果所有代码都是使用 C 标准 I/O 函数等 C 语言编写的,那么打印会更加简洁printf())。您可以strtok()Strange strtok()error找到替代方案的讨论。您可以在阅读用户输入和检查字符串中找到另一个关于为什么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 回答
0

好的。当你的线

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] 是一个指向 NULL 字符串的有效指针,它将长度设置为 0。我建议您使用 -g 标志进行编译(例如 g++ -g test.cpp -o test)。然后调用“gdb test”并使用 break、run、continue 命令来单步执行代码。您可以使用 print 命令来显示变量的内容。

在 gdb 的第一次运行中,只需输入“运行”。这将失败,然后输入'bt',它将告诉你失败的行,我们称之为行号。

在第二次运行中输入“break linenumber”,然后输入“run”,执行将在失败的行上停止,但在执行之前。然后,您可以查看变量的内容,这将为您提供有关失败原因的重要线索。

于 2012-09-02T00:39:41.650 回答