首页 > 解决方案 > 在特定分隔符后连接标记?

问题描述

我已经设置了一个动态二维数组,用户将输入一个数字来设置 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;
}

标签: c++

解决方案


我已经设置了一个动态二维数组,...如果我要添加 4 和 5,strtok 或与令牌相关的东西会起作用吗?

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

虽然使用基本类型和动态分配并没有错new,但您会错过 C++ 容器类型提供的自动内存管理的所有好处,例如vectorand string(以及错过它们包含的所有不错的成员函数)它会为您的字符串声明存储要容易得多,因为std::vector<std::string>这将使您能够填充每个字符串并简单.push_back(string)地将其添加到您vector的字符串中。

假设这是一项教育任务,并且您确实希望使用它char**来允许分配给定数量的指针(由用户输入的数字确定),然后您想要分配存储空间来保存输入的每个字符串,您可以使用方法类似于您尝试过的方法。但是,如果您为每个字符串动态分配存储空间,那么分配固定数量的字符(例如cppStrings[i] = new char[100];)几乎没有意义。如果是这种情况,您不妨只声明一个char.

相反,您想读取用户输入的每个字符串 (c_string),然后确定输入的字符串的长度。(例如使用strlen()),然后为length + 1字符分配存储空间(为终止每个 c_string的nul 终止+1字符提供存储空间)。然后只需将读取的字符串复制到分配的新内存块并依次将该内存块的起始地址分配给下一个可用指针即可。

理解,在以这种方式处理分配和存储时,您实际上是在用 C 编写代码,除了使用iostreamfor input/output 而new/delete不是malloc/free. 基本上是 30 年前如何使用 C++。如前所述,这并没有错,只是失去了过去 30 年 C++ 进步的好处。具有讽刺意味的是,除非您从缓冲区创建 astringstream并使用分隔符进行标记,否则getline您将使用 C 函数strtok将字符串拆分为逗号。既然你已经包含<cstring>了,你不妨利用strtok.

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

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

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

你对令牌做什么取决于你。你提到串联。如果这是您的目标,您可以简单地使用最大长度的第二个固定缓冲区来连接所有 c_strings strcat(不是您必须在调用之前strcpy将第一个字符串或将第二个缓冲区设为空字符串strcat,因为它需要一个以nul 结尾的缓冲区连接到)。

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

#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

示例使用/输出

$ ./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

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有两个责任:(1)始终保留指向内存块起始地址的指针,(2)它可以在没有时被释放更需要。

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的范围,尝试读取或基于未初始化的值进行条件跳转,最后确认释放所有分配的内存。

对于 Linuxvalgrind是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/newptr2array2
==4344== Memcheck, a memory error detector
==4344== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==4344== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==4344== Command: ./bin/newptr2array2
==4344==
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
==4344==
==4344== HEAP SUMMARY:
==4344==     in use at exit: 0 bytes in 0 blocks
==4344==   total heap usage: 4 allocs, 4 frees, 72,762 bytes allocated
==4344==
==4344== All heap blocks were freed -- no leaks are possible
==4344==
==4344== For counts of detected and suppressed errors, rerun with: -v
==4344== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

(这是自动内存管理vector和其他容器变得容易得多的地方。当对它的最终引用超出范围时,它们会处理释放内存,从而将您从该责任中解脱出来。)

回顾一下,了解如何正确使用具有很好的教育价值,new/delete因为过去几十年中仍有大量代码库使用这种方法。展望未来,您将需要使用 C++ 提供的容器类。如果您还有任何问题,请发表评论,我很乐意为您提供进一步的帮助。


推荐阅读