Создание простого CSV-файла
Благодаря возможностям файлового потока стандартной библиотеки C++, создать CSV-файл — тот, в котором значения разделены запятыми — просто. Начнем с примера:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
int main() {
std::ofstream file("example.csv");
if (!file.is_open()) {
std::cerr << "Failed to open file!" << std::endl;
return 1;
}
std::vector<std::vector<std::string>> data = {
{"Name", "Age", "City"},
{"John", "30", "New York"},
{"Alice", "25", "London"},
{"Bob", "35", "Paris"}
};
for (const auto& row : data) {
for (size_t i = 0; i < row.size(); ++i) {
file << row[i];
if (i != row.size() - 1) file << ",";
}
file << "\n";
}
file.close();
std::cout << "CSV file created successfully." << std::endl;
return 0;
}
Этим кодом создается простой CSV-файл со строкой заголовка и примерными данными, которые перебираются вложенными циклами и записываются в файл.
Обработка специальных символов
Сложными CSV-файлы становятся из-за специальных символов, особенно запятых в данных. Создадим функцию для корректного экранирования и закавычивания полей:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
std::string escapeCSV(const std::string& field) {
if (field.find(',') != std::string::npos ||
field.find('"') != std::string::npos ||
field.find('\n') != std::string::npos) {
std::ostringstream result;
result << '"';
for (char c : field) {
if (c == '"') result << '"';
result << c;
}
result << '"';
return result.str();
}
return field;
}
void writeCSV(const std::string& filename, const std::vector<std::vector<std::string>>& data) {
std::ofstream file(filename);
if (!file.is_open()) {
std::cerr << "Failed to open file: " << filename << std::endl;
return;
}
for (const auto& row : data) {
for (size_t i = 0; i < row.size(); ++i) {
file << escapeCSV(row[i]);
if (i != row.size() - 1) file << ",";
}
file << "\n";
}
file.close();
std::cout << "CSV file created successfully: " << filename << std::endl;
}
int main() {
std::vector<std::vector<std::string>> data = {
{"Name", "Description", "Price"},
{"Widget", "A \"fancy\" widget", "19.99"},
{"Gadget", "A simple, useful gadget", "9.99"},
{"Doodad", "A doodad with, commas", "5.99"}
};
writeCSV("products.csv", data);
return 0;
}
В этой усовершенствованной версии корректно обрабатываются поля с запятыми, кавычками или символами новой строки — за счет их закавычивания и экранирования внутренних кавычек.
Использование библиотеки CSV
Для CSV-операций посложнее используется библиотека вроде csv-parser. Вот пример:
#include <iostream>
#include <vector>
#include <string>
#include "csv.hpp"
int main() {
std::vector<std::vector<std::string>> data = {
{"Name", "Age", "City"},
{"John", "30", "New York"},
{"Alice", "25", "London"},
{"Bob", "35", "Paris"}
};
csv::CSVWriter writer("output.csv");
for (const auto& row : data) {
writer << row;
}
std::cout << "CSV file created successfully." << std::endl;
return 0;
}
Библиотекой процесс упрощается и пограничные случаи обрабатываются автоматически.
Реальный сценарий: генератор отчетов о продажах
Создадим пример посложнее, в котором генерируется CSV-файл отчетов о продажах:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <iomanip>
#include <ctime>
struct SaleRecord {
std::string date;
std::string product;
int quantity;
double unitPrice;
double getTotalPrice() const { return quantity * unitPrice; }
};
std::string getCurrentDate() {
std::time_t now = std::time(nullptr);
std::tm* localTime = std::localtime(&now);
char buffer[11];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d", localTime);
return std::string(buffer);
}
void generateSalesReport(const std::vector<SaleRecord>& sales, const std::string& filename) {
std::ofstream file(filename);
if (!file.is_open()) {
std::cerr << "Failed to open file: " << filename << std::endl;
return;
}
file << "Date,Product,Quantity,Unit Price,Total Price\n";
double grandTotal = 0.0;
for (const auto& sale : sales) {
file << sale.date << ","
<< sale.product << ","
<< sale.quantity << ","
<< std::fixed << std::setprecision(2) << sale.unitPrice << ","
<< sale.getTotalPrice() << "\n";
grandTotal += sale.getTotalPrice();
}
file << "\nGrand Total,,," << grandTotal << "\n";
file << "Report Generated," << getCurrentDate() << "\n";
file.close();
std::cout << "Sales report generated: " << filename << std::endl;
}
int main() {
std::vector<SaleRecord> sales = {
{"2023-05-01", "Widget A", 10, 19.99},
{"2023-05-02", "Gadget B", 5, 29.99},
{"2023-05-03", "Widget A", 8, 19.99},
{"2023-05-04", "Gizmo C", 3, 49.99}
};
generateSalesReport(sales, "sales_report.csv");
return 0;
}
В этом примере показано, как создать более структурированный CSV-файл с вычисляемыми полями и сводным разделом.
Производительность
При работе с большими наборами данных важна производительность. Вот рекомендации по повышению производительности записи в CSV:
- Для строковых операций используйте
stringstream
: производительность повышается применениемstd::stringstream
вместо конкатенации строк. - Записи в буфер: записывайте в буфер памяти, а затем сбрасывайте в файл кусками побольше.
- Используйте резерв для векторов: зная приблизительный размер данных, заранее выделяйте память при помощи
reserve()
.
А так реализуются эти оптимизации:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
void writeCSVOptimized(const std::string& filename, const std::vector<std::vector<std::string>>& data) {
std::ofstream file(filename, std::ios::binary); // Двоичный режим ради производительности
if (!file.is_open()) {
std::cerr << "Failed to open file: " << filename << std::endl;
return;
}
std::stringstream buffer;
buffer.reserve(1024 * 1024); // Резервируется буфер на 1 Мб
for (const auto& row : data) {
for (size_t i = 0; i < row.size(); ++i) {
buffer << row[i];
if (i != row.size() - 1) buffer << ",";
}
buffer << "\n";
if (buffer.tellp() > 1024 * 1024) { // Если буфером превышен 1 Мб
file << buffer.rdbuf();
buffer.str("");
buffer.clear();
}
}
// Записываются любые остальные данные
file << buffer.rdbuf();
file.close();
std::cout << "CSV file created successfully: " << filename << std::endl;
}
int main() {
std::vector<std::vector<std::string>> data;
data.reserve(1000000); // Резервируется место для 1 млн строк
// Генерируются примерные данные
for (int i = 0; i < 1000000; ++i) {
data.push_back({std::to_string(i), "Data" + std::to_string(i)});
}
writeCSVOptimized("large_file.csv", data);
return 0;
}
В этой оптимизированной версии эффективно обрабатываются гораздо бо́льшие наборы данных.
Обработка различных типов данных
Для работы с различными типами данных создается параметризованная функция:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
template<typename T>
std::string toString(const T& value) {
std::ostringstream oss;
oss << value;
return oss.str();
}
template<typename... Args>
void writeCSVRow(std::ofstream& file, const Args&... args) {
std::vector<std::string> row = {toString(args)...};
for (size_t i = 0; i < row.size(); ++i) {
file << row[i];
if (i != row.size() - 1) file << ",";
}
file << "\n";
}
int main() {
std::ofstream file("mixed_data.csv");
if (!file.is_open()) {
std::cerr << "Failed to open file!" << std::endl;
return 1;
}
writeCSVRow(file, "Name", "Age", "Height", "Is Student");
writeCSVRow(file, "John Doe", 30, 1.75, true);
writeCSVRow(file, "Jane Smith", 25, 1.68, false);
file.close();
std::cout << "CSV file created successfully." << std::endl;
return 0;
}
При таком подходе легко записывать строки со смешанными типами данных.
Типичные ошибки и как их избежать
- Незакрытый файл. Всегда закрывайте файл после записи. Для автоматического закрытия файлов используйте смарт-указатели или принципы RAII, что расшифровывается как «получение ресурса есть инициализация».
- Некорректная обработка специальных символов. Убедитесь, что поля с запятыми, кавычками или символами новой строки корректно экранированы или закавычены.
- Предполагается, что все данные строковые. При работе с числовыми данными учитывайте настройки локали, особенно для десятичных точек.
- Не проверяется, открылся ли файл. До записи данных в файл всегда проверяйте, открыт ли он.
- Игнорирование проблем с кодировкой символов. Не забывайте о ней, особенно при работе с международными данными. Используйте библиотеки, которыми поддерживается кодировка UTF-8.
Заключение
Способы создания CSV-файлов на C++ варьируются от простых манипуляций со строками до использования специализированных библиотек. Выбор определяется сложностью данных и требованиями по производительности.
Для простых задач достаточно базовых операций файлового ввода-вывода. Для задач посложнее — обработка специальных символов, больших наборов данных или различных типов данных — применяются подходы позамысловатее.
Читайте также:
- C++: подробное руководство по std::accumulate
- Как объединить несколько CSV файлов через 8 строчек кода
- Спецификатор constexpr в C++: зачем он нужен и как работает
Читайте нас в Telegram, VK и Дзен
Перевод статьи ryan: How to Create CSV File Using C++