strtok阅读长度

时间:2012-09-01 00:21:59

标签: c++ strtok

这里是文本文件的内容:

SQUARE 2
SQUARE
RECTANGLE 4 5

我试图找出为什么我的strtok()循环不会结束2ND" 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 个答案:

答案 0 :(得分:2)

这是一个有效的版本。

主要区别在于,

  1. 已将char *数组更改为20个char字符串的数组。这保证了数组元素分配了内存,在你的情况下它们是空指针并且当strtok返回NULL时保持这种方式,你就不能使用NULL指针。
  2. 对strtok的第二次调用是“strtok(0,DELIMITER)” 但应该是“strtok(NULL,DELIMITER)”。
  3. 我认为它们是唯一的差异,但使用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;
                }
            }
    }
    

答案 1 :(得分:2)

您的分段错误是由此引起的:

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

你犯了假设令牌[1]被标记化的错误。如果你看看你的第二个“SQUARE”,你会发现对于那一行,它会将token [1]设置为NULL。然后将NULL传递给atof(),这可以理解为错误。

你也正在使用strtok()。没有理由从结果中strcpy(),因为strtok()本身是一个破坏性的操作。

所以这是关于strtok的讲座。

首先,它是邪恶的,但有时候你还是有用的。标记符可能是一个痛苦的写入。

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)它将标记[number_of_tokens]设置为strtok的返回值。 b)如果为NULL,则终止循环(第二行)。  addendnum:有一个内联测试。您可以执行if(a = 1)并且它将返回true并将a设置为1.您可以执行if(a = 0),当将a设置为0时,它将返回false。如果strtok(此行)利用了这一事实)返回NULL,嗯,这是假的。 c)如果不是NULL,则令牌[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

正如您所看到的,不需要复制任何内容,因为该字符串在内存中被替换为:

here0are0some0words0

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

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

答案 2 :(得分:0)

确定。当你的行

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

创建一个指针数组,但它们都没有指向任何东西。第一个元素设置为0(这是一个NULL地址),其余元素未初始化。当您运行并处理第2行(其中包含1个元素)时,令牌[0]指向“SQUARE”但令牌[1]的值为0x00(NULL)。这是一个无效的内存位置。然后使用行

处理令牌[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的第一次运行中输入'run'。这将失败,然后输入'bt',它将告诉你失败的行,让我们称之为亚麻。

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

答案 3 :(得分:0)

以下是一些与您的代码密切相关的工作C ++。

我修改了I / O处理; fin.getline()报告是否有线,因此应该用它来控制循环;我估计fin.eof()是一个红旗警告(C中为feof(fp)。)

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

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

在C ++中使用C字符串和strtok()有很多可能的保留(如果所有代码都使用C标准I / O函数(如{{1}编写),则打印会更加简洁})。您可以在Strange strtok() error找到printf()替代方案的讨论。您可以在Reading user input and checking the string找到关于strtok()在库函数中发生灾难的原因的另一个讨论。

问题中3行数据的工作代码

strtok()