Я пытаюсь сохранить определенные значения из файла CSV, который содержит следующие данные в массиве динамических символов. Я могу прочитать файл CSV и предоставил код для этого в описании ниже. Я был бы признателен, если бы кто-нибудь мог сообщить мне, какой подход я буду использовать для хранения определенных данных из строки в CSV в массиве динамических символов. Спасибо! Я также написал функцию подстроки, которая возвращает определенную строку, помещая параметры начального индекса, конечного индекса и источника.

Файл CSV: - место, имя, широта, долгота, 1/22 / 20,1 / 23 / 20,1 / 24/20. Я хочу сохранить даты после длинных (без запятых) в массиве динамических символов (я не могу использовать векторы) спасибо!

char* substring(char* source, int startIndex, int endIndex)
{
    int size = endIndex - startIndex + 1;
    char* s = new char[size+1];
    strncpy(s, source + startIndex, size); 
    s[size]  = '\0'; //make it null-terminated
    return s;
} 

char** readCSV(const char* csvFileName, int& csvLineCount)
{
    ifstream fin(csvFileName);
    if (!fin)
    {
        return nullptr;
    }
    csvLineCount = 0;
    char line[1024];
    while(fin.getline(line, 1024))
    {
        csvLineCount++;
    };
    char **lines = new char*[csvLineCount];
    fin.clear();
    fin.seekg(0, ios::beg);
    for (int i=0; i<csvLineCount; i++)
    {
        fin.getline(line, 1024);   
        lines[i] = new char[strlen(line)+1];
        strcpy(lines[i], line);
    };
    fin.close();
    return lines;
}
2
Pressing_Keys_24_7 22 Апр 2020 в 06:45

2 ответа

Лучший ответ

В соответствии с вашим комментарием, у вас все еще возникают проблемы с динамическим выделением строк, считываемых из файла csv (и с оговоркой, программы сегодня должны избегать старого указателя на char в пользу вектора строк) - одна из причин заключается в том, что Вы подходите к распределению указателей неэффективно. Вместо одного прохода через ваш входной файл, вы делаете 2 прохода через ваш файл, один для чтения количества строк (для выделения указателей), а затем снова для чтения и выделения памяти для каждой строки. Хотя это один из подходов - это очень неэффективный подход, поскольку файловый ввод-вывод является одной из наименее эффективных задач, которые вы можете выполнить (и вы делаете это дважды)

Вместо этого просто выделите некоторое начальное количество указателей (1, 2 или 8 - хорошая отправная точка). Затем вы отслеживаете количество доступных выделенных указателей (скажем, с помощью size_t avail = 2; и количества использованных указателей (скажем, size_t used = 0;)). Затем, читая строки, вы проверяете if (used == available), чтобы узнать, когда пришло время перераспределить больше указателей. Вы можете просто перераспределить в 2 раза текущее количество указателей, используя временный указатель char**. Затем вы копируете существующие указатели в tmp, delete[] lines; и затем назначаете новый блок памяти, содержащий указатели на lines.

Еще одно изменение - открыть поток файлов std::ifstream в main(), проверить, что он открыт для чтения, а затем передать ссылку на открытый поток в качестве параметра функции вместо передачи имени файла ( если вы не можете успешно открыть поток в вызывающей программе - нет необходимости вызывать функцию для подсчета строк)

Чтобы прочитать строки из вашего потока, обрабатывающие распределения, по ходу дела, вы можете сделать что-то вроде следующего:

#include <iostream>
#include <fstream>
#include <cstring>

#define MAXC 1024

char **readcsv (std::ifstream& fin, size_t& csvLineCount)
{
    size_t avail = 2, used = 0;                     /* allocated/used counters */
    char line[MAXC], **lines = new char*[avail];    /* line and lines */

    while (fin.getline (line, MAXC)) {              /* loop reading each line */
        size_t len;                                 /* for line length */
        if (used == avail) {                        /* all pointers used? */
            char **tmp = new char *[2 * avail];     /* allocate twice as many */
            memcpy (tmp, lines, used * sizeof *lines);  /* copy lines to new tmp */
            delete[] lines;                         /* free existing pionters */
            lines = tmp;                            /* set lines to new block */
            avail *= 2;                             /* update ptrs available */
        }
        lines[used] = new char[(len = strlen(line)) + 1];   /* alloc for lines[used] */
        memcpy (lines[used++], line, len + 1);      /* copy line to lines[used] */
    }
    csvLineCount = used;                            /* update csvLineCount to used */

    return lines;       /* return lines */
}

Добавление короткого main(), который принимает имя файла для чтения в качестве первого аргумента программы и открывает поток в main() перед передачей ссылки на открытый поток в вашу функцию чтения, будет выглядеть так:

int main (int argc, char **argv) {

    if (argc < 2) { /* validate 1 argument given for filename */
        std::cerr << "error: insufficient input.\n"
                     "usage: " << argv[0] << " filename.\n";
        return 1;
    }

    char **lines = nullptr;         /* pointer-to-pointer to char */
    size_t nlines = 0;              /* line counter */
    std::ifstream f (argv[1]);      /* file stream */

    if (!f.is_open()) {     /* validate file open for reading */
        std::cerr << "error: file open failed '" << argv[1] << "'.\n";
        return 1;
    }

    if (!(lines = readcsv (f, nlines))) {   /* call line read function/validate */
        std::cerr << "error: readcsv() failed.\n";
        return 1;
    }

    for (size_t i = 0; i < nlines; i++) {   /* loop outputting lines, freeing memory */
        std::cout << lines[i] << '\n';
        delete[] lines[i];                  /* free lines */
    }
    delete[] lines;                         /* free pointers */
}

Пример входного файла

$ cat dat/latlon.csv
place1,name1,Lat1,Long1,1/22/20,1/23/20,1/24/20
place2,name2,Lat2,Long2,1/22/20,1/23/20,1/24/20
place3,name3,Lat3,Long3,1/22/20,1/23/20,1/24/20
place4,name4,Lat4,Long4,1/22/20,1/23/20,1/24/20

Пример использования / вывода

Все строки успешно сохранены в выделенной памяти:

$ ./bin/read_alloc_csv_lines dat/latlon.csv
place1,name1,Lat1,Long1,1/22/20,1/23/20,1/24/20
place2,name2,Lat2,Long2,1/22/20,1/23/20,1/24/20
place3,name3,Lat3,Long3,1/22/20,1/23/20,1/24/20
place4,name4,Lat4,Long4,1/22/20,1/23/20,1/24/20

Использование памяти / проверка ошибок

В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанности в отношении любого выделенного блока памяти: (1) всегда сохраняет указатель на начальный адрес для блока так что, (2) он может быть освобожден , когда он больше не нужен.

Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памяти или записывать за пределы / за пределы выделенного блока, пытаетесь прочитать или основать условный переход на неинициализированном значении и, наконец, подтвердить что вы освобождаете всю выделенную память.

Для Linux valgrind это нормальный выбор. Есть похожие проверки памяти для каждой платформы. Все они просты в использовании, просто запустите вашу программу через него.

$ valgrind ./bin/read_alloc_csv_lines dat/latlon.csv
==8108== Memcheck, a memory error detector
==8108== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8108== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==8108== Command: ./bin/read_alloc_csv_lines dat/latlon.csv
==8108==
place1,name1,Lat1,Long1,1/22/20,1/23/20,1/24/20
place2,name2,Lat2,Long2,1/22/20,1/23/20,1/24/20
place3,name3,Lat3,Long3,1/22/20,1/23/20,1/24/20
place4,name4,Lat4,Long4,1/22/20,1/23/20,1/24/20
==8108==
==8108== HEAP SUMMARY:
==8108==     in use at exit: 0 bytes in 0 blocks
==8108==   total heap usage: 10 allocs, 10 frees, 82,712 bytes allocated
==8108==
==8108== All heap blocks were freed -- no leaks are possible
==8108==
==8108== For counts of detected and suppressed errors, rerun with: -v
==8108== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Всегда проверяйте, что вы освободили всю выделенную память и что ошибок памяти нет.

Я оставил вам обработку подстроки, поскольку ваш комментарий касался проблем с чтением строк в выделенной памяти. Если у вас возникнут проблемы с этим позже, просто дайте мне знать. Также дайте мне знать, если у вас есть дополнительные вопросы о том, как все было сделано выше.

1
David C. Rankin 22 Апр 2020 в 19:33

Я бы немного изменил ваш код, чтобы итерировать поля строки заголовка, а не итерировать строки файла:

char* substring(const char *start, const char *end)
{
    int size = end - start + 1;
    char* s = new char[size + 1];
    strncpy(s, start, size);
    s[size] = '\0'; //make it null-terminated
    return s;
}

char** readHeaderDates(const char* csvFileName, int& csvDateCount)
{
    ifstream fin(csvFileName);
    if (!fin)
    {
        return nullptr;
    }
    csvDateCount = 0;
    char line[1024];
    if (! fin.getline(line, 1024))   // read header line
    {
        return nullptr;
    };
    fin.close();
    // count commas in line:
    for (const char *ix = line;; ix = strchr(ix, ',')) {
        if (NULL == ix) break;
        csvDateCount += 1;
        ix += 1;
    }
    csvDateCount -= 3;
    if (csvDateCount <= 0) {
        return nullptr;
    }
    char **dates = new char*[csvDateCount];
    const char *ix = line;
    for (int i = 0; i < 4; i++) {
        ix = strchr(ix, ',') + 1;
    }
    for (int i = 0; i<csvDateCount; i++)
    {
        const char *start = ix;
        const char *end = strchr(ix, ',');
        if (nullptr == end) end = start + strlen(start);
        dates[i] = substring(start, end);
    }
    return dates;
}

ВНИМАНИЕ: непроверенный код ...

1
Serge Ballesta 22 Апр 2020 в 14:31