在特定分隔符之后连接令牌?

时间:2018-11-18 03:08:03

标签: c++

我已经建立了一个动态2d数组,用户将输入一个数字来设置n-1行的数量,然后输入一个字符串,其中示例字符串输入为:


“ shipment1,20180208,4”和“ shipment2,20180319,5”等。 (格式始终是这种方式)


逗号后没有空格分隔符,所以我想知道是否要添加4和5,strtok或其他与令牌相关的工作?首先将它们分成3个标记(用逗号分隔),然后使用atoi进行连接?
我才刚刚起步,也没有对所说的话题有太多了解,如果有人有一个大致的想法,将不胜感激,谢谢!

#include <iostream>
#include <cstring>
using namespace std;

int main()
{
 int n = 0;
 cin >> n; //int to set size

//allocate 2d array with varying lengths
char** cppStrings = new char*[n];

for(int i = 0; i < n; i++)
{
    cppStrings[i] = new char[100];
}

//input all the strings into the array
for(int i = 0; i < n; i++)
{
    cin.getline(cppStrings[i], 100);
}

//outputs the strings just to see
for(int i = 0; i < n; i++)
{
    cout << cppStrings[i] << endl;
}


//deallocates the array
for(int i = 0; i < n; i++)
{
    delete [] cppStrings[i];
}
delete [] cppStrings;
}

2 个答案:

答案 0 :(得分:0)

根据您的评论,我正在更新我的答案。最好使用std :: strings,std :: vector而不是使用char *数组来简化代码。但是,主要逻辑是使用逗号分割字符串(如上所述)并正确解析数据。这是代码:

#include<iostream>
#include<string>
#include <vector>
#include <map>

using namespace std;
std::vector<std::string> split(const std::string& s, char seperator)
{
    std::vector<std::string> output;

    std::string::size_type prev_pos = 0, pos = 0;

    while ((pos = s.find(seperator, pos)) != std::string::npos)
    {
        std::string substring(s.substr(prev_pos, pos - prev_pos));

        output.push_back(substring);

        prev_pos = ++pos;
    }

    output.push_back(s.substr(prev_pos, pos - prev_pos)); // Last word

    return output;
}



int main()
{
    map<int, string> monthMap{ pair<int, string>(1, "January"),
        pair<int, string>(2, "February"),
        pair<int, string>(3, "March"),
        pair<int, string>(4, "April"),
        pair<int, string>(5, "May"),
        pair<int, string>(6, "June"),
        pair<int, string>(7, "July"),
        pair<int, string>(8, "August"),
        pair<int, string>(9, "September"),
        pair<int, string>(10, "October"),
        pair<int, string>(11, "November"),
        pair<int, string>(12, "December"),
    };

    map<int, int> countOfShipmentsPerMonth = { pair<int, int>(1, 0),
        pair<int, int>(2, 0),
        pair<int, int>(3, 0),
        pair<int, int>(4, 0),
        pair<int, int>(5, 0),
        pair<int, int>(6, 0),
        pair<int, int>(7, 0),
        pair<int, int>(8, 0),
        pair<int, int>(9, 0),
        pair<int, int>(10, 0),
        pair<int, int>(11, 0),
        pair<int, int>(12, 0) };

    int n = 0;
    cin >> n; //int to set size
    std::cin.ignore(1000, '\n');

    vector<string> cppStrings;
    cppStrings.resize(n);

    //input all the strings into the array
    for (int i = 0; i < n; i++)
    {
        getline(cin, cppStrings[i]);
        cin.clear();
    }

    for each (string var in cppStrings)
    {
        //split string
        vector<string> v = split(var, ',');

        //v[0] = shipment1
        //v[1] = date of shipment
        //v[2] = number of shipments

        //if date is always of format yyyymmdd then we can simply get the month for getting 5th and 6th char of date string
        string month = v[1].substr(4, 2);
        int nMonth = stoi(month);       

        //Now find the already stored value of shipment in that month and add new value to it
        std::map<int, int>::iterator it = countOfShipmentsPerMonth.find(nMonth);
        if (it != countOfShipmentsPerMonth.end())
        {
            it->second = it->second + stoi(v[2]);
        }
    }


    //print results
    for each (pair<int, int> var in countOfShipmentsPerMonth)
    {
        if (var.second != 0)
        {
            cout << monthMap.at(var.first) << " : " << var.second << endl;
        }
    }

    return 0;
}

注意:拆分功能来自于stackoverflow的this答案。

答案 1 :(得分:0)

  

我已经建立了一个动态2D数组,...如果我要添加4和5,   strtok或某些与令牌相关的工作?

简短回答“是”。在查看答案之前,我们先来看一下您在做什么。

虽然使用new使用基本类型和动态分配没有任何问题,但是您缺少了C ++容器类型(例如vector和{{1})提供的自动内存管理的所有优点。 }(并且错过了它们所包含的所有漂亮的成员函数)将字符串的存储声明为string会容易得多,这将使您能够填充每个字符串并只需填充{{1} },将其添加到您的字符串std::vector<std::string>中。

假设这是一项教育性的任务,并且您确实确实想使用.push_back(string)来分配给定数量的指针(由用户输入的数量确定),然后您想要分配要保存的存储空间输入的每个字符串都可以使用与尝试的方法类似的方法。但是,如果您要为每个字符串动态分配存储空间,则分配固定数量的字符(例如vector)几乎没有意义。如果是这种情况,您也可以声明一个{ 1}}。

相反,您想读取用户输入的每个字符串(c_string),然后确定输入的字符串的长度。 (例如,使用char**),然后为cppStrings[i] = new char[100];个字符分配存储空间(char为终止每个c_string的 nul-termination 字符提供存储空间)。然后,只需将读取的字符串复制到分配的新内存块中,然后将该内存块的起始地址依次分配给您的下一个可用指针即可。

了解,以这种方式进行分配和存储时,您正在有效地用C语言编写代码,但使用strlen()进行输入/输出,并使用length + 1而不是{{1} }。基本上是30年前使用C ++的方式。如前所述,这没错,只是失去了过去30年来C ++进步的好处。具有讽刺意味的是,除非您从缓冲区创建+1并使用带有分隔符的iostream进行标记化,否则您将使用C函数new/delete在逗号上分割字符串。由于您已包含malloc/free,因此您也可以使用stringstream

方法与您尝试的相距不远。如果确定100个字符的数组足以满足您的预期输入,则声明一个100个字符的固定缓冲区,作为您从用户那里获取输入的临时读取缓冲区。这使您可以在分配输入并将其复制到动态分配的存储之前,获取输入,确定缓冲区中输入的长度。使用这种方法,您可以调整分配的内存大小以完全适合用户输入。

std::strtok在标记化过程中修改原始字符串,因此,如果需要保留原始字符串,请复制要标记化的字符串。此外,由于读取输入具有固定的缓冲区,因此您可以简单地使用该缓冲区进行标记。

请注意,实际上没有必要将从用户读取的原始字符串存储在动态分配的存储中,您只需将输入读入固定缓冲区并从那里进行标记化。但是,由于您已经开始动态地输入内容,因此我们将在示例中使用该方法,并简单地复制回我们的固定缓冲区以进行令牌化。

您对令牌的处理完全取决于您。您提到串联。如果这是您的目标,则可以简单地使用最大长度的第二个固定缓冲区将所有c_string与getline连接(而不是必须strtok第一个字符串或将第二个缓冲区设为空字符串,然后再调用<cstring>,因为它需要连接 nul终止的缓冲区。)

将所有部分放在一起,您可以执行以下操作:

strtok

注意:完成操作后,请不要忘记通过调用strcat来释放通过strcpy分配的内存)

使用/输出示例

strcat

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于任何分配的内存块,您都有2个职责:(1)始终保留指向起始地址的指针因此,(2)当不再需要它时可以释放

当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在已分配的块的边界之外/之外进行写入,不要试图以未初始化的值读取或基于条件跳转,最后,以确认您释放了已分配的所有内存。

对于Linux,#include <iostream> #include <iomanip> #include <cstring> #define MAXS 100 /* if you need constants, #define one (or more) */ #define DELIM ",\n" using namespace std; int main (void) { int n = 0, nstr = 0; /* counter for c_strings read/allocated */ char **strings = nullptr, /* pointer to pointer to char */ buf[MAXS]; /* temporary buffer to hold c_string */ cout << "enter max number of strings: "; if (!(cin >> n)) { /* validate every user input */ cerr << "error: invalid entry\n"; return 1; } cin.getline (buf, MAXS); /* read/discard trailing '\n' */ strings = new char*[n]; /* allocate n pointers to char */ /* protect pointers limit / validate read of line */ while (nstr < n && cin.getline (buf, MAXS)) { size_t len = strlen (buf); /* get length of string */ strings[nstr] = new char[len+1]; /* allocate +1 for '\0' */ strcpy (strings[nstr++], buf); /* copy buf, increment nstr */ } for (int i = 0; i < nstr; i++) { /* ouput strings / tokenize */ char *p = buf; /* ptr to buf, tokenize copy * (strtok modifies string) */ strcpy (buf, strings[i]); /* copy string[i] to buf p */ cout << "\nstring[" << setw(2) << i << "]: " << p << '\n'; /* example tokenizing string in p with strtok */ for (p = strtok (p, DELIM); p; p = strtok (NULL, DELIM)) cout << " " << p << '\n'; } for (int i = 0; i < nstr; i++) /* free all allocated memory */ delete[] strings[i]; /* free allocated c_strings */ delete[] strings; /* free pointers */ } 是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。

new

始终确认已释放已分配的所有内存,并且没有内存错误。

(这是delete和其他容器的自动存储器管理的简化之处。当最终引用超出范围时,它们将处理释放存储器的工作,从而使您摆脱了责任。)< / p>

仔细研究一下,了解如何正确使用$ ./bin/newptr2array2 enter max number of strings: 2 shipment1,20180208,4 shipment2,20180319,5 string[ 0]: shipment1,20180208,4 shipment1 20180208 4 string[ 1]: shipment2,20180319,5 shipment2 20180319 5 具有良好的教育价值,因为在过去的几十年中仍然存在大量使用该方法的代码库。展望未来,您将要使用C ++提供的容器类。如果您还有其他问题,请发表评论,我们很乐意为您提供进一步的帮助。