最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于C++ 11標準庫實現(xiàn)輕量級CSV文件讀寫工具庫

 更新時間:2026年07月29日 09:03:34   作者:李遲  
本文介紹一個基于 C++11 標準庫實現(xiàn)的輕量級 CSV 文件讀寫工具庫,該庫提供三個核心 API —— ReadCSV、WriteCSV、AppendCSV,覆蓋了 CSV 文件的讀取、覆寫與追加場景,需要的朋友可以參考下

1. 背景

CSV(Comma-Separated Values)是數(shù)據(jù)交換領域使用最為廣泛的純文本格式之一。無論是數(shù)據(jù)庫導出、日志采集、配置下發(fā)還是跨系統(tǒng)數(shù)據(jù)同步,CSV 都因其簡單、人類可讀、工具鏈成熟而占據(jù)重要地位。

在服務器端 C++ 開發(fā)中,經(jīng)常面臨這樣的需求:

  • 將程序運行狀態(tài)或計費結果導出為 CSV 供下游消費;
  • 讀取上游系統(tǒng)生成的 CSV 配置文件;
  • 在已有 CSV 文件尾部追加新紀錄,實現(xiàn)滾動日志式存儲。

然而,C++ 標準庫并未提供開箱即用的 CSV 解析與生成能力。常見的第三方方案(如將數(shù)據(jù)映射到 std::vector<std::vector<std::string>>)雖然功能完備,但在高頻調用或嵌入式環(huán)境中會引入不必要的內(nèi)存開銷和模板膨脹。因此,設計一個零依賴、輕量級、調用方自主管理數(shù)據(jù)格式的 CSV 讀寫庫具有實際工程價值。

2. 目的

本庫的設計目標明確且克制:

  1. 零第三方依賴 —— 僅使用 C++11 標準庫,可在任意 Linux 環(huán)境編譯運行;
  2. 調用方控制數(shù)據(jù)格式 —— 不強制將 CSV 解析為二維數(shù)組或結構體,數(shù)據(jù)內(nèi)容的構造與解析完全由調用方?jīng)Q定;
  3. 清晰的內(nèi)存語義 —— 讀操作由庫內(nèi)部分配內(nèi)存,調用方負責釋放,邊界清晰、無隱式拷貝;
  4. 簡單的錯誤模型 —— 通過整型返回值區(qū)分成功與各類失敗,不引入異常依賴;
  5. 覆蓋三種基本操作 —— 讀取、覆寫、追加,滿足日常工作 90% 的 CSV 場景。

3. 設計

3.1 API 概覽

// 讀?。汉瘮?shù)內(nèi)部分配 buffer,調用方 delete[] 釋放
int ReadCSV(const std::string& filename, char*& buffer, size_t& len);

// 覆寫:先寫 title(表頭),再寫 buffer(數(shù)據(jù)行)
int WriteCSV(const std::string& filename,
             const std::string& title,
             const std::string& buffer);

// 追加:在文件末尾追加數(shù)據(jù)
int AppendCSV(const std::string& filename, const std::string& buffer);

返回值約定:

  • 0:操作成功
  • -1:文件打開失敗
  • -2:內(nèi)存分配失敗 / 寫入失敗
  • -3:讀取失敗

3.2 內(nèi)存語義

這是設計中最關鍵的決策點。對于 ReadCSV

  • 分配在庫內(nèi):確保 buffer 大小與文件內(nèi)容精確匹配,調用方無需預分配或二次擴容;
  • 釋放在庫外:調用方在消費完數(shù)據(jù)后主動 delete[],生命周期由使用者掌控,不存在懸掛指針風險;
  • buffer 總是以 \0 結尾:雖按二進制方式讀取,但額外追加一個空字符,方便調用方直接作為 C 字符串使用。

3.3 寫入語義

WriteCSVAppendCSV 中的 buffer 參數(shù)由調用方自由構造——可以是一個完整的 CSV 多行字符串,也可以是一行記錄。庫不做任何格式校驗,保證了最大的靈活性:

// 調用方自行拼接 CSV 行
std::string data = "1,Alice,95\n2,Bob,87\n3,Charlie,92";
WriteCSV("out.csv", "id,name,score", data);

庫僅負責一項防御性處理:若 title 或 buffer 不以換行結尾,自動補一個 \n。這確保了 AppendCSV 追加時始終從新行開始,避免數(shù)據(jù)粘連。

4. 實踐

4.1 文件結構

文件結構如下,具體的完整代碼見附錄。

.
├── csv_utils.h      # 頭文件:API 聲明
├── csv_utils.cpp    # 實現(xiàn)文件
└── main.cpp         # 測試文件

4.2 ReadCSV 實現(xiàn)要點

int ReadCSV(const std::string& filename, char*& buffer, size_t& len)
{
    // ① 以二進制 + 末尾定位模式打開,直接獲取文件大小
    std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
    if (!ifs.is_open()) { buffer = nullptr; len = 0; return -1; }

    const std::streamsize fileSize = ifs.tellg();

    // ② 分配 buffer,多 1 字節(jié)用于 '\0'
    try {
        buffer = new char[static_cast<size_t>(fileSize) + 1];
    } catch (const std::bad_alloc&) { buffer = nullptr; len = 0; return -2; }

    // ③ 回到文件頭,一次性讀取全部內(nèi)容
    ifs.seekg(0, std::ios::beg);
    if (!ifs.read(buffer, fileSize)) { delete[] buffer; buffer = nullptr; len = 0; return -3; }

    len = static_cast<size_t>(fileSize);
    buffer[len] = '\0';
    return 0;
}

設計取舍分析

  • std::ios::ate 模式打開后在文件尾,一次 tellg() 即可獲得文件大小,避免 seekg 到末尾再回來的二次 IO 開銷;
  • 內(nèi)存分配失敗時捕獲 std::bad_alloc,確保不會因 new 拋出異常而破壞調用方的異常安全策略;
  • 讀取失敗時立即 delete[] buffer 并將輸出參數(shù)歸零,保證失敗后調用方拿到的是干凈狀態(tài)。

4.3 WriteCSV 與 AppendCSV 的實現(xiàn)

int WriteCSV(const std::string& filename,
             const std::string& title,
             const std::string& buffer)
{
    // trunc 模式:覆蓋寫入
    std::ofstream ofs(filename, std::ios::out | std::ios::trunc);
    if (!ofs.is_open()) return -1;

    ofs << title;
    if (title.empty() || title.back() != '\n') ofs << '\n';
    if (!buffer.empty()) {
        ofs << buffer;
        if (buffer.back() != '\n') ofs << '\n';
    }
    return ofs.good() ? 0 : -2;
}

int AppendCSV(const std::string& filename, const std::string& buffer)
{
    // app 模式:追加寫入
    std::ofstream ofs(filename, std::ios::out | std::ios::app);
    if (!ofs.is_open()) return -1;

    if (!buffer.empty()) {
        ofs << buffer;
        if (buffer.back() != '\n') ofs << '\n';
    }
    return ofs.good() ? 0 : -2;
}

WriteCSVAppendCSV 的唯一區(qū)別在于文件打開模式 —— trunc vs app,其余邏輯保持一致,這遵循了 DRY 原則且便于維護。

4.4 使用示例

場景一:生成數(shù)據(jù)并讀取驗證

// 寫入
WriteCSV("report.csv",
         "交易ID,金額,狀態(tài)",
         "T001,1500,成功\nT002,2300,成功\nT003,800,失敗");

// 讀取
char* buf = nullptr;
size_t len = 0;
if (ReadCSV("report.csv", buf, len) == 0) {
    std::cout << std::string(buf, len) << std::endl;
    delete[] buf;   // ← 調用方負責釋放
}

場景二:滾動追加日志

WriteCSV("log.csv", "時間,級別,消息", "");

// 運行中持續(xù)追加
AppendCSV("log.csv", "2026-07-27 10:00,INFO,服務啟動");
AppendCSV("log.csv", "2026-07-27 10:05,WARN,內(nèi)存使用率超過80%");
AppendCSV("log.csv", "2026-07-27 10:30,INFO,任務執(zhí)行完成");

5. 測試

5.1 編譯

g++ -std=c++11 -Wall -Wextra -o csv_test csv_utils.cpp main.cpp
./csv_test

5.1 測試用例

測試編號測試場景驗證要點
測試 1WriteCSV + ReadCSV 往返寫入后讀取內(nèi)容完全一致
測試 2WriteCSV + AppendCSV 多次追加追加后記錄順序與完整性
測試 3讀取不存在的文件返回 -1,輸出參數(shù)為 nullptr / 0
測試 4空數(shù)據(jù)寫入僅標題行,無數(shù)據(jù)行的邊界情況
測試 5含逗號與引號的轉義字段帶引號的字段不破壞 CSV 結構

5.2 測試結果

========== CSV Utils 測試 ==========

=== 測試 1:WriteCSV + ReadCSV ===
文件長度: 51 bytes
文件內(nèi)容:
id,name,score
1,Alice,95
2,Bob,87
3,Charlie,92

? 測試 1 通過

=== 測試 2:WriteCSV + AppendCSV + ReadCSV ===
id,name,score
1,Alice,95
2,Bob,87
3,Charlie,92
4,Diana,88
5,Eve,100

? 測試 2 通過

=== 測試 3:讀取不存在的文件 ===
返回碼: -1(預期 -1)
? 測試 3 通過

=== 測試 4:空數(shù)據(jù)寫入 ===
文件內(nèi)容: [col1,col2,col3
]
? 測試 4 通過

=== 測試 5:含轉義字段的 CSV ===
id,name,description
1,"Smith, John","He said ""Hello"""
2,Jane Doe,Normal text

? 測試 5 通過

========== 全部測試通過 ==========

6. 小結

Golang在語言層支持很多常見的格式或協(xié)議的解析,而C++只能找第三方庫或自己造輪子。本文件實現(xiàn)的CSV讀取庫用于臨時頂替數(shù)據(jù)庫功能。實際場景并未使用到。

7. 附

頭文件csv_utils.h

#pragma once

#include <cstddef>
#include <string>

/**
 * @brief 讀取CSV文件全部內(nèi)容到內(nèi)存緩沖區(qū)
 * @param filename  文件路徑
 * @param buffer    輸出參數(shù),函數(shù)內(nèi)部分配內(nèi)存,調用方負責釋放(使用 delete[])
 * @param len       輸出參數(shù),讀取到的數(shù)據(jù)長度(不含結尾'\0')
 * @return  0 成功
 *         -1 文件打開失敗
 *         -2 內(nèi)存分配失敗
 *         -3 讀取失敗
 */
int ReadCSV(const std::string& filename, char*& buffer, size_t& len);

/**
 * @brief 寫入CSV文件(覆蓋模式),包含標題行和數(shù)據(jù)內(nèi)容
 * @param filename  文件路徑
 * @param title     標題行(CSV表頭字符串)
 * @param buffer    數(shù)據(jù)內(nèi)容(CSV格式行數(shù)據(jù),由調用方構造)
 * @return  0 成功
 *         -1 文件打開失敗
 *         -2 寫入失敗
 */
int WriteCSV(const std::string& filename,
             const std::string& title,
             const std::string& buffer);

/**
 * @brief 追加數(shù)據(jù)到已有CSV文件末尾
 * @param filename  文件路徑
 * @param buffer    要追加的數(shù)據(jù)內(nèi)容(由調用方構造)
 * @return  0 成功
 *         -1 文件打開失敗
 *         -2 寫入失敗
 */
int AppendCSV(const std::string& filename, const std::string& buffer);

實現(xiàn)文件csv_utils.cpp

#include "csv_utils.h"

#include <cstring>
#include <fstream>

// ──────────────────────────────────────────────
// ReadCSV – 讀取整個文件到調用方可釋放的緩沖區(qū)
// ──────────────────────────────────────────────
int ReadCSV(const std::string& filename, char*& buffer, size_t& len)
{
    // 1. 以二進制方式打開,并在末尾定位以獲取文件大小
    std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
    if (!ifs.is_open()) {
        buffer = nullptr;
        len    = 0;
        return -1;  // 文件打開失敗
    }

    const std::streamsize fileSize = ifs.tellg();
    if (fileSize < 0) {
        buffer = nullptr;
        len    = 0;
        return -1;
    }

    // 2. 分配緩沖區(qū)(多一個字節(jié)存放 '\0',方便調用方當作 C 字符串使用)
    try {
        buffer = new char[static_cast<size_t>(fileSize) + 1];
    } catch (const std::bad_alloc&) {
        buffer = nullptr;
        len    = 0;
        return -2;  // 內(nèi)存分配失敗
    }

    // 3. 回到文件頭并讀取全部內(nèi)容
    ifs.seekg(0, std::ios::beg);
    if (!ifs.read(buffer, fileSize)) {
        delete[] buffer;
        buffer = nullptr;
        len    = 0;
        return -3;  // 讀取失敗
    }

    len = static_cast<size_t>(fileSize);
    buffer[len] = '\0';  // 以 null 結尾,方便作為 C 字符串使用

    return 0;
}

// ──────────────────────────────────────────────
// WriteCSV – 覆蓋寫入標題行 + 數(shù)據(jù)內(nèi)容
// ──────────────────────────────────────────────
int WriteCSV(const std::string& filename,
             const std::string& title,
             const std::string& buffer)
{
    std::ofstream ofs(filename, std::ios::out | std::ios::trunc);
    if (!ofs.is_open()) {
        return -1;
    }

    // 寫入標題行,末尾確保換行
    ofs << title;
    if (title.empty() || title.back() != '\n') {
        ofs << '\n';
    }

    // 寫入數(shù)據(jù)緩沖區(qū)
    if (!buffer.empty()) {
        ofs << buffer;
        // buffer 不以換行結尾時補一個換行,保證后續(xù) AppendCSV 從新行開始
        if (buffer.back() != '\n') {
            ofs << '\n';
        }
    }

    if (!ofs.good()) {
        return -2;
    }

    return 0;
}

// ──────────────────────────────────────────────
// AppendCSV – 追加數(shù)據(jù)到文件末尾
// ──────────────────────────────────────────────
int AppendCSV(const std::string& filename, const std::string& buffer)
{
    std::ofstream ofs(filename, std::ios::out | std::ios::app);
    if (!ofs.is_open()) {
        return -1;
    }

    if (!buffer.empty()) {
        ofs << buffer;
        if (buffer.back() != '\n') {
            ofs << '\n';
        }
    }

    if (!ofs.good()) {
        return -2;
    }

    return 0;
}

測試文件main.cpp

#include "csv_utils.h"

#include <cassert>
#include <cstring>
#include <iostream>
#include <sstream>
#include <string>

// ──────────────────────────────────────────────
// 測試 1 :WriteCSV → ReadCSV,驗證寫入與讀取一致
// ──────────────────────────────────────────────
static int test_write_and_read()
{
    const std::string filename = "test_write.csv";

    // 構造 CSV 數(shù)據(jù)(調用方?jīng)Q定 buffer 內(nèi)容)
    const std::string title  = "id,name,score";
    std::ostringstream oss;
    oss << "1,Alice,95\n"
        << "2,Bob,87\n"
        << "3,Charlie,92";
    const std::string data = oss.str();

    // 寫入
    int ret = WriteCSV(filename, title, data);
    assert(ret == 0);

    // 讀取
    char*  buf = nullptr;
    size_t len = 0;
    ret = ReadCSV(filename, buf, len);
    assert(ret == 0);
    assert(buf != nullptr);
    assert(len > 0);

    std::cout << "=== 測試 1:WriteCSV + ReadCSV ===" << std::endl;
    std::cout << "文件長度: " << len << " bytes" << std::endl;
    std::cout << "文件內(nèi)容:" << std::endl;
    std::cout << buf << std::endl;

    // 調用方負責釋放
    delete[] buf;

    // 驗證內(nèi)容正確
    ret = ReadCSV(filename, buf, len);
    assert(ret == 0);
    std::string content(buf, len);
    assert(content.find("id,name,score") != std::string::npos);
    assert(content.find("1,Alice,95")   != std::string::npos);
    assert(content.find("2,Bob,87")     != std::string::npos);
    assert(content.find("3,Charlie,92") != std::string::npos);
    delete[] buf;

    std::cout << "? 測試 1 通過" << std::endl << std::endl;
    return 0;
}

// ──────────────────────────────────────────────
// 測試 2 :AppendCSV 追加內(nèi)容,驗證追加后文件完整
// ──────────────────────────────────────────────
static int test_append()
{
    const std::string filename = "test_append.csv";

    // 先寫入基礎內(nèi)容
    const std::string title = "id,name,score";
    const std::string data  = "1,Alice,95\n2,Bob,87";
    int ret = WriteCSV(filename, title, data);
    assert(ret == 0);

    // 追加內(nèi)容
    const std::string appendData = "3,Charlie,92\n4,Diana,88";
    ret = AppendCSV(filename, appendData);
    assert(ret == 0);

    // 再追加一條
    ret = AppendCSV(filename, "5,Eve,100");
    assert(ret == 0);

    // 讀取并驗證
    char*  buf = nullptr;
    size_t len = 0;
    ret = ReadCSV(filename, buf, len);
    assert(ret == 0);

    std::string content(buf, len);
    delete[] buf;

    std::cout << "=== 測試 2:WriteCSV + AppendCSV + ReadCSV ===" << std::endl;
    std::cout << content << std::endl;

    assert(content.find("id,name,score") != std::string::npos);
    assert(content.find("1,Alice,95")    != std::string::npos);
    assert(content.find("2,Bob,87")      != std::string::npos);
    assert(content.find("3,Charlie,92")  != std::string::npos);
    assert(content.find("4,Diana,88")    != std::string::npos);
    assert(content.find("5,Eve,100")     != std::string::npos);

    std::cout << "? 測試 2 通過" << std::endl << std::endl;
    return 0;
}

// ──────────────────────────────────────────────
// 測試 3 :讀取不存在的文件,驗證錯誤碼
// ──────────────────────────────────────────────
static int test_read_nonexistent()
{
    const std::string filename = "nonexistent.csv";

    char*  buf = nullptr;
    size_t len = 0;
    int ret = ReadCSV(filename, buf, len);

    assert(ret == -1);
    assert(buf == nullptr);
    assert(len == 0);

    std::cout << "=== 測試 3:讀取不存在的文件 ===" << std::endl;
    std::cout << "返回碼: " << ret << "(預期 -1)" << std::endl;
    std::cout << "? 測試 3 通過" << std::endl << std::endl;
    return 0;
}

// ──────────────────────────────────────────────
// 測試 4 :空數(shù)據(jù)寫入與讀取
// ──────────────────────────────────────────────
static int test_empty_data()
{
    const std::string filename = "test_empty.csv";

    // 僅寫入標題,數(shù)據(jù)為空
    int ret = WriteCSV(filename, "col1,col2,col3", "");
    assert(ret == 0);

    char*  buf = nullptr;
    size_t len = 0;
    ret = ReadCSV(filename, buf, len);
    assert(ret == 0);
    assert(len > 0);

    std::string content(buf, len);
    delete[] buf;

    std::cout << "=== 測試 4:空數(shù)據(jù)寫入 ===" << std::endl;
    std::cout << "文件內(nèi)容: [" << content << "]" << std::endl;
    assert(content.find("col1,col2,col3") != std::string::npos);
    std::cout << "? 測試 4 通過" << std::endl << std::endl;
    return 0;
}

// ──────────────────────────────────────────────
// 測試 5 :含逗號和引號的 CSV 字段(轉義場景)
// ──────────────────────────────────────────────
static int test_escaped_fields()
{
    const std::string filename = "test_escape.csv";

    // 包含引號和逗號的字段
    const std::string title = "id,name,description";
    const std::string data  = "1,\"Smith, John\",\"He said \"\"Hello\"\"\"\n"
                               "2,Jane Doe,Normal text";

    int ret = WriteCSV(filename, title, data);
    assert(ret == 0);

    char*  buf = nullptr;
    size_t len = 0;
    ret = ReadCSV(filename, buf, len);
    assert(ret == 0);

    std::string content(buf, len);
    delete[] buf;

    std::cout << "=== 測試 5:含轉義字段的 CSV ===" << std::endl;
    std::cout << content << std::endl;

    assert(content.find("id,name,description") != std::string::npos);
    assert(content.find("\"Smith, John\"")     != std::string::npos);
    assert(content.find("\"\"Hello\"\"")       != std::string::npos);

    std::cout << "? 測試 5 通過" << std::endl << std::endl;
    return 0;
}

// ──────────────────────────────────────────────
// main
// ──────────────────────────────────────────────
int main()
{
    std::cout << "========== CSV Utils 測試 ==========" << std::endl << std::endl;

    test_write_and_read();
    test_append();
    test_read_nonexistent();
    test_empty_data();
    test_escaped_fields();

    std::cout << "========== 全部測試通過 ==========" << std::endl;
    return 0;
}

title: 一種輕量級C++ CSV文件讀寫庫的實現(xiàn)方案
urlname: a-lightweight-C+±CSV-file-library-implementation
tags:

  • 工程庫代碼
    categories:
  • 我的程序代碼
    date: 2026-07-28 00:00:00
    photos:
  • gallery/tech/c2.jpg

本文介紹一個基于 C++11 標準庫實現(xiàn)的輕量級 CSV 文件讀寫工具庫。該庫提供三個核心 API —— ReadCSVWriteCSV、AppendCSV,覆蓋了 CSV 文件的讀取、覆寫與追加場景。

1. 背景

CSV(Comma-Separated Values)是數(shù)據(jù)交換領域使用最為廣泛的純文本格式之一。無論是數(shù)據(jù)庫導出、日志采集、配置下發(fā)還是跨系統(tǒng)數(shù)據(jù)同步,CSV 都因其簡單、人類可讀、工具鏈成熟而占據(jù)重要地位。

在服務器端 C++ 開發(fā)中,經(jīng)常面臨這樣的需求:

  • 將程序運行狀態(tài)或計費結果導出為 CSV 供下游消費;
  • 讀取上游系統(tǒng)生成的 CSV 配置文件;
  • 在已有 CSV 文件尾部追加新紀錄,實現(xiàn)滾動日志式存儲。

然而,C++ 標準庫并未提供開箱即用的 CSV 解析與生成能力。常見的第三方方案(如將數(shù)據(jù)映射到 std::vector<std::vector<std::string>>)雖然功能完備,但在高頻調用或嵌入式環(huán)境中會引入不必要的內(nèi)存開銷和模板膨脹。因此,設計一個零依賴、輕量級、調用方自主管理數(shù)據(jù)格式的 CSV 讀寫庫具有實際工程價值。

2. 目的

本庫的設計目標明確且克制:

  1. 零第三方依賴 —— 僅使用 C++11 標準庫,可在任意 Linux 環(huán)境編譯運行;
  2. 調用方控制數(shù)據(jù)格式 —— 不強制將 CSV 解析為二維數(shù)組或結構體,數(shù)據(jù)內(nèi)容的構造與解析完全由調用方?jīng)Q定;
  3. 清晰的內(nèi)存語義 —— 讀操作由庫內(nèi)部分配內(nèi)存,調用方負責釋放,邊界清晰、無隱式拷貝;
  4. 簡單的錯誤模型 —— 通過整型返回值區(qū)分成功與各類失敗,不引入異常依賴;
  5. 覆蓋三種基本操作 —— 讀取、覆寫、追加,滿足日常工作 90% 的 CSV 場景。

3. 設計

3.1 API 概覽

// 讀取:函數(shù)內(nèi)部分配 buffer,調用方 delete[] 釋放
int ReadCSV(const std::string& filename, char*& buffer, size_t& len);

// 覆寫:先寫 title(表頭),再寫 buffer(數(shù)據(jù)行)
int WriteCSV(const std::string& filename,
             const std::string& title,
             const std::string& buffer);

// 追加:在文件末尾追加數(shù)據(jù)
int AppendCSV(const std::string& filename, const std::string& buffer);

返回值約定:

  • 0:操作成功
  • -1:文件打開失敗
  • -2:內(nèi)存分配失敗 / 寫入失敗
  • -3:讀取失敗

3.2 內(nèi)存語義

這是設計中最關鍵的決策點。對于 ReadCSV

  • 分配在庫內(nèi):確保 buffer 大小與文件內(nèi)容精確匹配,調用方無需預分配或二次擴容;
  • 釋放在庫外:調用方在消費完數(shù)據(jù)后主動 delete[],生命周期由使用者掌控,不存在懸掛指針風險;
  • buffer 總是以 \0 結尾:雖按二進制方式讀取,但額外追加一個空字符,方便調用方直接作為 C 字符串使用。

3.3 寫入語義

WriteCSVAppendCSV 中的 buffer 參數(shù)由調用方自由構造——可以是一個完整的 CSV 多行字符串,也可以是一行記錄。庫不做任何格式校驗,保證了最大的靈活性:

// 調用方自行拼接 CSV 行
std::string data = "1,Alice,95\n2,Bob,87\n3,Charlie,92";
WriteCSV("out.csv", "id,name,score", data);

庫僅負責一項防御性處理:若 title 或 buffer 不以換行結尾,自動補一個 \n。這確保了 AppendCSV 追加時始終從新行開始,避免數(shù)據(jù)粘連。

4. 實踐

4.1 文件結構

文件結構如下,具體的完整代碼見附錄。

.
├── csv_utils.h      # 頭文件:API 聲明
├── csv_utils.cpp    # 實現(xiàn)文件
└── main.cpp         # 測試文件

4.2 ReadCSV 實現(xiàn)要點

int ReadCSV(const std::string& filename, char*& buffer, size_t& len)
{
    // ① 以二進制 + 末尾定位模式打開,直接獲取文件大小
    std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
    if (!ifs.is_open()) { buffer = nullptr; len = 0; return -1; }

    const std::streamsize fileSize = ifs.tellg();

    // ② 分配 buffer,多 1 字節(jié)用于 '\0'
    try {
        buffer = new char[static_cast<size_t>(fileSize) + 1];
    } catch (const std::bad_alloc&) { buffer = nullptr; len = 0; return -2; }

    // ③ 回到文件頭,一次性讀取全部內(nèi)容
    ifs.seekg(0, std::ios::beg);
    if (!ifs.read(buffer, fileSize)) { delete[] buffer; buffer = nullptr; len = 0; return -3; }

    len = static_cast<size_t>(fileSize);
    buffer[len] = '\0';
    return 0;
}

設計取舍分析

  • std::ios::ate 模式打開后在文件尾,一次 tellg() 即可獲得文件大小,避免 seekg 到末尾再回來的二次 IO 開銷;
  • 內(nèi)存分配失敗時捕獲 std::bad_alloc,確保不會因 new 拋出異常而破壞調用方的異常安全策略;
  • 讀取失敗時立即 delete[] buffer 并將輸出參數(shù)歸零,保證失敗后調用方拿到的是干凈狀態(tài)。

4.3 WriteCSV 與 AppendCSV 的實現(xiàn)

int WriteCSV(const std::string& filename,
             const std::string& title,
             const std::string& buffer)
{
    // trunc 模式:覆蓋寫入
    std::ofstream ofs(filename, std::ios::out | std::ios::trunc);
    if (!ofs.is_open()) return -1;

    ofs << title;
    if (title.empty() || title.back() != '\n') ofs << '\n';
    if (!buffer.empty()) {
        ofs << buffer;
        if (buffer.back() != '\n') ofs << '\n';
    }
    return ofs.good() ? 0 : -2;
}

int AppendCSV(const std::string& filename, const std::string& buffer)
{
    // app 模式:追加寫入
    std::ofstream ofs(filename, std::ios::out | std::ios::app);
    if (!ofs.is_open()) return -1;

    if (!buffer.empty()) {
        ofs << buffer;
        if (buffer.back() != '\n') ofs << '\n';
    }
    return ofs.good() ? 0 : -2;
}

WriteCSVAppendCSV 的唯一區(qū)別在于文件打開模式 —— trunc vs app,其余邏輯保持一致,這遵循了 DRY 原則且便于維護。

4.4 使用示例

場景一:生成數(shù)據(jù)并讀取驗證

// 寫入
WriteCSV("report.csv",
         "交易ID,金額,狀態(tài)",
         "T001,1500,成功\nT002,2300,成功\nT003,800,失敗");

// 讀取
char* buf = nullptr;
size_t len = 0;
if (ReadCSV("report.csv", buf, len) == 0) {
    std::cout << std::string(buf, len) << std::endl;
    delete[] buf;   // ← 調用方負責釋放
}

場景二:滾動追加日志

WriteCSV("log.csv", "時間,級別,消息", "");

// 運行中持續(xù)追加
AppendCSV("log.csv", "2026-07-27 10:00,INFO,服務啟動");
AppendCSV("log.csv", "2026-07-27 10:05,WARN,內(nèi)存使用率超過80%");
AppendCSV("log.csv", "2026-07-27 10:30,INFO,任務執(zhí)行完成");

5. 測試

5.1 編譯

g++ -std=c++11 -Wall -Wextra -o csv_test csv_utils.cpp main.cpp
./csv_test

5.1 測試用例

測試編號測試場景驗證要點
測試 1WriteCSV + ReadCSV 往返寫入后讀取內(nèi)容完全一致
測試 2WriteCSV + AppendCSV 多次追加追加后記錄順序與完整性
測試 3讀取不存在的文件返回 -1,輸出參數(shù)為 nullptr / 0
測試 4空數(shù)據(jù)寫入僅標題行,無數(shù)據(jù)行的邊界情況
測試 5含逗號與引號的轉義字段帶引號的字段不破壞 CSV 結構

5.2 測試結果

========== CSV Utils 測試 ==========

=== 測試 1:WriteCSV + ReadCSV ===
文件長度: 51 bytes
文件內(nèi)容:
id,name,score
1,Alice,95
2,Bob,87
3,Charlie,92

? 測試 1 通過

=== 測試 2:WriteCSV + AppendCSV + ReadCSV ===
id,name,score
1,Alice,95
2,Bob,87
3,Charlie,92
4,Diana,88
5,Eve,100

? 測試 2 通過

=== 測試 3:讀取不存在的文件 ===
返回碼: -1(預期 -1)
? 測試 3 通過

=== 測試 4:空數(shù)據(jù)寫入 ===
文件內(nèi)容: [col1,col2,col3
]
? 測試 4 通過

=== 測試 5:含轉義字段的 CSV ===
id,name,description
1,"Smith, John","He said ""Hello"""
2,Jane Doe,Normal text

? 測試 5 通過

========== 全部測試通過 ==========

6. 小結

Golang在語言層支持很多常見的格式或協(xié)議的解析,而C++只能找第三方庫或自己造輪子。本文件實現(xiàn)的CSV讀取庫用于臨時頂替數(shù)據(jù)庫功能。實際場景并未使用到。

7. 附

頭文件csv_utils.h

#pragma once

#include <cstddef>
#include <string>

/**
 * @brief 讀取CSV文件全部內(nèi)容到內(nèi)存緩沖區(qū)
 * @param filename  文件路徑
 * @param buffer    輸出參數(shù),函數(shù)內(nèi)部分配內(nèi)存,調用方負責釋放(使用 delete[])
 * @param len       輸出參數(shù),讀取到的數(shù)據(jù)長度(不含結尾'\0')
 * @return  0 成功
 *         -1 文件打開失敗
 *         -2 內(nèi)存分配失敗
 *         -3 讀取失敗
 */
int ReadCSV(const std::string& filename, char*& buffer, size_t& len);

/**
 * @brief 寫入CSV文件(覆蓋模式),包含標題行和數(shù)據(jù)內(nèi)容
 * @param filename  文件路徑
 * @param title     標題行(CSV表頭字符串)
 * @param buffer    數(shù)據(jù)內(nèi)容(CSV格式行數(shù)據(jù),由調用方構造)
 * @return  0 成功
 *         -1 文件打開失敗
 *         -2 寫入失敗
 */
int WriteCSV(const std::string& filename,
             const std::string& title,
             const std::string& buffer);

/**
 * @brief 追加數(shù)據(jù)到已有CSV文件末尾
 * @param filename  文件路徑
 * @param buffer    要追加的數(shù)據(jù)內(nèi)容(由調用方構造)
 * @return  0 成功
 *         -1 文件打開失敗
 *         -2 寫入失敗
 */
int AppendCSV(const std::string& filename, const std::string& buffer);

實現(xiàn)文件csv_utils.cpp

#include "csv_utils.h"

#include <cstring>
#include <fstream>

// ──────────────────────────────────────────────
// ReadCSV – 讀取整個文件到調用方可釋放的緩沖區(qū)
// ──────────────────────────────────────────────
int ReadCSV(const std::string& filename, char*& buffer, size_t& len)
{
    // 1. 以二進制方式打開,并在末尾定位以獲取文件大小
    std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
    if (!ifs.is_open()) {
        buffer = nullptr;
        len    = 0;
        return -1;  // 文件打開失敗
    }

    const std::streamsize fileSize = ifs.tellg();
    if (fileSize < 0) {
        buffer = nullptr;
        len    = 0;
        return -1;
    }

    // 2. 分配緩沖區(qū)(多一個字節(jié)存放 '\0',方便調用方當作 C 字符串使用)
    try {
        buffer = new char[static_cast<size_t>(fileSize) + 1];
    } catch (const std::bad_alloc&) {
        buffer = nullptr;
        len    = 0;
        return -2;  // 內(nèi)存分配失敗
    }

    // 3. 回到文件頭并讀取全部內(nèi)容
    ifs.seekg(0, std::ios::beg);
    if (!ifs.read(buffer, fileSize)) {
        delete[] buffer;
        buffer = nullptr;
        len    = 0;
        return -3;  // 讀取失敗
    }

    len = static_cast<size_t>(fileSize);
    buffer[len] = '\0';  // 以 null 結尾,方便作為 C 字符串使用

    return 0;
}

// ──────────────────────────────────────────────
// WriteCSV – 覆蓋寫入標題行 + 數(shù)據(jù)內(nèi)容
// ──────────────────────────────────────────────
int WriteCSV(const std::string& filename,
             const std::string& title,
             const std::string& buffer)
{
    std::ofstream ofs(filename, std::ios::out | std::ios::trunc);
    if (!ofs.is_open()) {
        return -1;
    }

    // 寫入標題行,末尾確保換行
    ofs << title;
    if (title.empty() || title.back() != '\n') {
        ofs << '\n';
    }

    // 寫入數(shù)據(jù)緩沖區(qū)
    if (!buffer.empty()) {
        ofs << buffer;
        // buffer 不以換行結尾時補一個換行,保證后續(xù) AppendCSV 從新行開始
        if (buffer.back() != '\n') {
            ofs << '\n';
        }
    }

    if (!ofs.good()) {
        return -2;
    }

    return 0;
}

// ──────────────────────────────────────────────
// AppendCSV – 追加數(shù)據(jù)到文件末尾
// ──────────────────────────────────────────────
int AppendCSV(const std::string& filename, const std::string& buffer)
{
    std::ofstream ofs(filename, std::ios::out | std::ios::app);
    if (!ofs.is_open()) {
        return -1;
    }

    if (!buffer.empty()) {
        ofs << buffer;
        if (buffer.back() != '\n') {
            ofs << '\n';
        }
    }

    if (!ofs.good()) {
        return -2;
    }

    return 0;
}

測試文件main.cpp

#include "csv_utils.h"

#include <cassert>
#include <cstring>
#include <iostream>
#include <sstream>
#include <string>

// ──────────────────────────────────────────────
// 測試 1 :WriteCSV → ReadCSV,驗證寫入與讀取一致
// ──────────────────────────────────────────────
static int test_write_and_read()
{
    const std::string filename = "test_write.csv";

    // 構造 CSV 數(shù)據(jù)(調用方?jīng)Q定 buffer 內(nèi)容)
    const std::string title  = "id,name,score";
    std::ostringstream oss;
    oss << "1,Alice,95\n"
        << "2,Bob,87\n"
        << "3,Charlie,92";
    const std::string data = oss.str();

    // 寫入
    int ret = WriteCSV(filename, title, data);
    assert(ret == 0);

    // 讀取
    char*  buf = nullptr;
    size_t len = 0;
    ret = ReadCSV(filename, buf, len);
    assert(ret == 0);
    assert(buf != nullptr);
    assert(len > 0);

    std::cout << "=== 測試 1:WriteCSV + ReadCSV ===" << std::endl;
    std::cout << "文件長度: " << len << " bytes" << std::endl;
    std::cout << "文件內(nèi)容:" << std::endl;
    std::cout << buf << std::endl;

    // 調用方負責釋放
    delete[] buf;

    // 驗證內(nèi)容正確
    ret = ReadCSV(filename, buf, len);
    assert(ret == 0);
    std::string content(buf, len);
    assert(content.find("id,name,score") != std::string::npos);
    assert(content.find("1,Alice,95")   != std::string::npos);
    assert(content.find("2,Bob,87")     != std::string::npos);
    assert(content.find("3,Charlie,92") != std::string::npos);
    delete[] buf;

    std::cout << "? 測試 1 通過" << std::endl << std::endl;
    return 0;
}

// ──────────────────────────────────────────────
// 測試 2 :AppendCSV 追加內(nèi)容,驗證追加后文件完整
// ──────────────────────────────────────────────
static int test_append()
{
    const std::string filename = "test_append.csv";

    // 先寫入基礎內(nèi)容
    const std::string title = "id,name,score";
    const std::string data  = "1,Alice,95\n2,Bob,87";
    int ret = WriteCSV(filename, title, data);
    assert(ret == 0);

    // 追加內(nèi)容
    const std::string appendData = "3,Charlie,92\n4,Diana,88";
    ret = AppendCSV(filename, appendData);
    assert(ret == 0);

    // 再追加一條
    ret = AppendCSV(filename, "5,Eve,100");
    assert(ret == 0);

    // 讀取并驗證
    char*  buf = nullptr;
    size_t len = 0;
    ret = ReadCSV(filename, buf, len);
    assert(ret == 0);

    std::string content(buf, len);
    delete[] buf;

    std::cout << "=== 測試 2:WriteCSV + AppendCSV + ReadCSV ===" << std::endl;
    std::cout << content << std::endl;

    assert(content.find("id,name,score") != std::string::npos);
    assert(content.find("1,Alice,95")    != std::string::npos);
    assert(content.find("2,Bob,87")      != std::string::npos);
    assert(content.find("3,Charlie,92")  != std::string::npos);
    assert(content.find("4,Diana,88")    != std::string::npos);
    assert(content.find("5,Eve,100")     != std::string::npos);

    std::cout << "? 測試 2 通過" << std::endl << std::endl;
    return 0;
}

// ──────────────────────────────────────────────
// 測試 3 :讀取不存在的文件,驗證錯誤碼
// ──────────────────────────────────────────────
static int test_read_nonexistent()
{
    const std::string filename = "nonexistent.csv";

    char*  buf = nullptr;
    size_t len = 0;
    int ret = ReadCSV(filename, buf, len);

    assert(ret == -1);
    assert(buf == nullptr);
    assert(len == 0);

    std::cout << "=== 測試 3:讀取不存在的文件 ===" << std::endl;
    std::cout << "返回碼: " << ret << "(預期 -1)" << std::endl;
    std::cout << "? 測試 3 通過" << std::endl << std::endl;
    return 0;
}

// ──────────────────────────────────────────────
// 測試 4 :空數(shù)據(jù)寫入與讀取
// ──────────────────────────────────────────────
static int test_empty_data()
{
    const std::string filename = "test_empty.csv";

    // 僅寫入標題,數(shù)據(jù)為空
    int ret = WriteCSV(filename, "col1,col2,col3", "");
    assert(ret == 0);

    char*  buf = nullptr;
    size_t len = 0;
    ret = ReadCSV(filename, buf, len);
    assert(ret == 0);
    assert(len > 0);

    std::string content(buf, len);
    delete[] buf;

    std::cout << "=== 測試 4:空數(shù)據(jù)寫入 ===" << std::endl;
    std::cout << "文件內(nèi)容: [" << content << "]" << std::endl;
    assert(content.find("col1,col2,col3") != std::string::npos);
    std::cout << "? 測試 4 通過" << std::endl << std::endl;
    return 0;
}

// ──────────────────────────────────────────────
// 測試 5 :含逗號和引號的 CSV 字段(轉義場景)
// ──────────────────────────────────────────────
static int test_escaped_fields()
{
    const std::string filename = "test_escape.csv";

    // 包含引號和逗號的字段
    const std::string title = "id,name,description";
    const std::string data  = "1,\"Smith, John\",\"He said \"\"Hello\"\"\"\n"
                               "2,Jane Doe,Normal text";

    int ret = WriteCSV(filename, title, data);
    assert(ret == 0);

    char*  buf = nullptr;
    size_t len = 0;
    ret = ReadCSV(filename, buf, len);
    assert(ret == 0);

    std::string content(buf, len);
    delete[] buf;

    std::cout << "=== 測試 5:含轉義字段的 CSV ===" << std::endl;
    std::cout << content << std::endl;

    assert(content.find("id,name,description") != std::string::npos);
    assert(content.find("\"Smith, John\"")     != std::string::npos);
    assert(content.find("\"\"Hello\"\"")       != std::string::npos);

    std::cout << "? 測試 5 通過" << std::endl << std::endl;
    return 0;
}

// ──────────────────────────────────────────────
// main
// ──────────────────────────────────────────────
int main()
{
    std::cout << "========== CSV Utils 測試 ==========" << std::endl << std::endl;

    test_write_and_read();
    test_append();
    test_read_nonexistent();
    test_empty_data();
    test_escaped_fields();

    std::cout << "========== 全部測試通過 ==========" << std::endl;
    return 0;
}

以上就是基于C++ 11標準庫實現(xiàn)輕量級CSV文件讀寫工具庫的詳細內(nèi)容,更多關于C++輕量級CSV文件讀寫庫的資料請關注腳本之家其它相關文章!

相關文章

  • C++中對象的賦值與復制操作詳細解析

    C++中對象的賦值與復制操作詳細解析

    對象之間的賦值也是通過賦值運算符“=”進行的。本來賦值運算符“=”只能用來對單個的變量賦值,現(xiàn)在被擴展為兩個同類對象之間的賦值,這是通過對賦值運算符的重載實現(xiàn)的
    2013-10-10
  • C++智能指針shared_ptr分析

    C++智能指針shared_ptr分析

    這篇文章主要介紹了C++智能指針shared_ptr分析的相關資料,需要的朋友可以參考下
    2017-03-03
  • sizeof()的簡單介紹

    sizeof()的簡單介紹

    sizeof操作符以字節(jié)形式給出了其操作數(shù)的存儲大小
    2013-04-04
  • C語言實現(xiàn)維吉尼亞密碼的示例代碼

    C語言實現(xiàn)維吉尼亞密碼的示例代碼

    維吉尼亞密碼(又譯維熱納爾密碼)是使用一系列凱撒密碼組成密碼字母表的加密算法,屬于多表密碼的一種簡單形式。本文將用C語言實現(xiàn)維吉尼亞密碼,需要的可以參考一下
    2022-11-11
  • C語言實現(xiàn)文件版通訊錄的代碼分享

    C語言實現(xiàn)文件版通訊錄的代碼分享

    這篇文章主要為大家詳細介紹了如何利用C語言實現(xiàn)一個文件版通訊錄,主要運用了結構體,一維數(shù)組,函數(shù),分支與循環(huán)語句等等知識,需要的可以參考一下
    2023-01-01
  • c語言統(tǒng)計素數(shù)之和的實例

    c語言統(tǒng)計素數(shù)之和的實例

    這篇文章主要介紹了c語言統(tǒng)計素數(shù)之和的實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • 基于C語言編寫一個簡單的Web服務器

    基于C語言編寫一個簡單的Web服務器

    C語言可以干大事,這篇文章主要為大家詳細介紹了如何基于C語言可以完成一個簡易的Web服務器,希望這篇文章會幫你你對C語言有更深入的理解
    2024-03-03
  • dev?c++的使用和調試方式

    dev?c++的使用和調試方式

    這篇文章主要介紹了dev?c++?的使用和調試方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • C語言初識動態(tài)內(nèi)存管理malloc calloc realloc free函數(shù)

    C語言初識動態(tài)內(nèi)存管理malloc calloc realloc free函數(shù)

    動態(tài)內(nèi)存是相對靜態(tài)內(nèi)存而言的。所謂動態(tài)和靜態(tài)就是指內(nèi)存的分配方式。動態(tài)內(nèi)存是指在堆上分配的內(nèi)存,而靜態(tài)內(nèi)存是指在棧上分配的內(nèi)存
    2022-03-03
  • 深入了解C語言中的const和指針

    深入了解C語言中的const和指針

    這篇文章將具體為大家介紹一下C語言中const和指針的使用,文中的示例代碼講解詳細,對我們學習const和指針有一定幫助,需要的可以參考一下
    2022-02-02

最新評論

拉萨市| 上虞市| 图片| 长泰县| 准格尔旗| 阳曲县| 克拉玛依市| 梅州市| 垫江县| 商南县| 元氏县| 新干县| 湖南省| 彝良县| 黑龙江省| 格尔木市| 霍邱县| 靖安县| 安阳县| 无棣县| 金塔县| 陕西省| 邻水| 彰武县| 通化市| 绍兴县| 伊春市| 通榆县| 景东| 平乡县| 宝鸡市| 双牌县| 江山市| 滦平县| 绥滨县| 米泉市| 鄯善县| 镇安县| 清丰县| 治多县| 嘉祥县|