C++学习:六个月从基础到就业——C++基础语法回顾:数组与字符串

C++学习:六个月从基础到就业——C++基础语法回顾:数组与字符串

本文是我C++学习之旅系列的第五篇技术文章,主要回顾C++中的数组与字符串,包括传统C风格数组和字符串以及现代C++中的容器和字符串类。查看完整系列目录了解更多内容。

引言

数组和字符串是几乎所有编程语言中最基础的数据结构,它们用于存储和操作线性数据集合。在C++中,有两种主要的方式处理数组和字符串:一种是传统的C风格方法,另一种是现代C++提供的标准库容器和类。本文将详细介绍这两种方法,包括它们的语法、特性、使用场景以及最佳实践。

C风格数组

基本概念

C风格数组是一种固定大小的连续内存区域,用于存储相同类型的元素。它是C++从C语言继承的遗产之一。

数组声明与初始化
// 声明一个包含5个整数的数组
int numbers[5];

// 声明并初始化
int numbers[5] = {1, 2, 3, 4, 5};

// 省略数组大小(由初始化列表确定)
int numbers[] = {1, 2, 3, 4, 5};

// 部分初始化(未显式初始化的元素被设为0)
int numbers[5] = {1, 2, 3};  // 等价于 {1, 2, 3, 0, 0}

// 特定元素初始化(C++11)
int numbers[5] = {};  // 所有元素初始化为0
int numbers[5] = {0}; // 所有元素初始化为0

// 指定初始化器(C++20)
int numbers[5] = {[0]=1, [2]=3, [4]=5};  // 等价于 {1, 0, 3, 0, 5}
数组访问

通过索引访问数组元素,索引从0开始:

int numbers[5] = {10, 20, 30, 40, 50};

// 读取第一个元素(索引为0)
int first = numbers[0];  // first = 10

// 修改第三个元素(索引为2)
numbers[2] = 35;  // 数组变为 {10, 20, 35, 40, 50}

注意:C++不会自动进行数组边界检查。访问超出数组范围的元素会导致未定义行为:

int numbers[5] = {1, 2, 3, 4, 5};
int value = numbers[10];  // 未定义行为!可能导致程序崩溃或返回垃圾值
多维数组

C++支持多维数组,它们实际上是"数组的数组":

// 2x3的二维数组
int matrix[2][3] = {
    {1, 2, 3},  // 第一行
    {4, 5, 6}   // 第二行
};

// 访问元素
int element = matrix[1][2];  // 获取第二行第三列的元素,值为6

// 3D数组
int cube[2][3][4];  // 2页,每页3行,每行4列
数组大小

由于C风格数组的大小是固定的,可以在编译时确定其大小:

int numbers[5];
size_t size = sizeof(numbers) / sizeof(numbers[0]);  // size = 5

但是,当数组作为函数参数传递时,它会退化为指针,因此上述方法不再有效。

数组与指针

在C和C++中,数组名在多数情况下会退化为指向其第一个元素的指针:

int numbers[5] = {1, 2, 3, 4, 5};
int* ptr = numbers;  // ptr指向numbers[0]

// 通过指针访问数组元素
int first = *ptr;        // 等价于 numbers[0]
int second = *(ptr + 1); // 等价于 numbers[1]
int third = ptr[2];      // 等价于 numbers[2],指针也可以使用[]运算符

这种数组-指针关系导致一些重要的结果:

  1. 函数参数中的数组实际上是指针:
void processArray(int arr[], int size) {
    // arr是一个指针,而不是数组
    std::cout << sizeof(arr) << std::endl;  // 输出指针的大小,而非数组大小
}

void processArray(int* arr, int size) {  // 与上面的函数等价
    // ...
}
  1. 无法通过值传递数组(只能传递指针):
void function(int arr[5]) {  // 实际上是 void function(int* arr)
    // 无法知道数组的大小
}
  1. 因此必须显式传递数组大小:
void processArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        // 处理arr[i]
    }
}

C风格数组的限制

虽然C风格数组简单且内存效率高,但它们有许多限制:

  1. 固定大小:必须在编译时确定大小,不能动态调整。
  2. 无边界检查:访问越界不会被检测,可能导致严重的安全问题和bug。
  3. 功能有限:没有内置的大小信息、搜索、排序等功能。
  4. 传递困难:作为函数参数时退化为指针,丢失大小信息。
  5. 不能直接赋值:不支持直接用一个数组赋值给另一个数组。

由于这些限制,现代C++开发通常推荐使用标准库容器而非原始数组。

C风格字符串

基本概念

C风格字符串是以空字符(‘\0’)结尾的字符数组。空字符标记字符串的结束。

字符串声明与初始化
// 字符数组
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

// 字符串字面量(自动添加结尾的空字符)
char greeting[] = "Hello";  // 等价于 {'H', 'e', 'l', 'l', 'o', '\0'}

// 注意数组大小需要包含结尾的空字符
char name[10] = "John";     // 数组大小为10,实际使用5(包括'\0')
字符串字面量

字符串字面量(用双引号括起来的字符序列)是常量,尝试修改它们会导致未定义行为:

const char* str = "Hello";  // 推荐:明确指出str指向的内容是常量
// str[0] = 'h';  // 错误:尝试修改字符串字面量

char mutable_str[] = "Hello";  // 数组内容可以修改
mutable_str[0] = 'h';  // 正确:修改了数组内容
字符串操作

C标准库(在C++中通过<cstring>头文件提供)包含许多用于处理C风格字符串的函数:

#include <cstring>

// 计算字符串长度(不包括结尾的空字符)
char str[] = "Hello";
size_t length = strlen(str);  // length = 5

// 字符串复制
char source[] = "Hello";
char destination[10];
strcpy(destination, source);  // destination现在包含"Hello"

// 安全的字符串复制(指定最大长度)
char dest[10];
strncpy(dest, source, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';  // 确保以空字符结尾

// 字符串连接
char str1[20] = "Hello, ";
char str2[] = "World!";
strcat(str1, str2);  // str1现在包含"Hello, World!"

// 字符串比较
int result = strcmp("apple", "banana");  // 返回负值(apple在banana之前)

注意:这些函数可能导致缓冲区溢出,如果目标缓冲区不够大:

char small_buffer[5];
strcpy(small_buffer, "Too long string");  // 缓冲区溢出!

C风格字符串的限制

C风格字符串有几个重要的限制:

  1. 手动内存管理:必须确保分配足够的内存并处理内存释放。
  2. 容易出错:缓冲区溢出、遗漏空字符等问题很常见。
  3. 功能有限:基本操作需要使用库函数,不支持现代字符串功能。
  4. 安全风险:不安全的函数如strcpystrcat可能导致安全漏洞。

现代C++数组替代品

std::array

std::array(在C++11中引入)是原始数组的包装器,提供了与STL容器类似的接口,同时保持了大小固定和内存连续的特性:

#include <array>

// 声明一个包含5个整数的std::array
std::array<int, 5> numbers = {1, 2, 3, 4, 5};

// 访问元素
int first = numbers[0];       // 使用下标运算符(无边界检查)
int second = numbers.at(1);   // 使用at()方法(有边界检查)

// 迭代
for (const auto& num : numbers) {
    std::cout << num << ' ';
}

// 获取大小
size_t size = numbers.size();  // size = 5

// 检查是否为空
bool is_empty = numbers.empty();  // is_empty = false

// 获取原始数组
int* raw_array = numbers.data();

std::array的优势:

  1. 知道自己的大小size()方法返回元素数量。
  2. 可以整体复制:支持赋值运算符。
  3. 边界检查at()方法在访问越界时抛出异常。
  4. 兼容STL算法:可以与标准库算法一起使用。
  5. 不会退化为指针:作为函数参数时保留大小信息。

std::array应该是新代码中使用固定大小数组的首选。

std::vector

std::vector是一个动态数组,可以在运行时调整大小:

#include <vector>

// 创建一个空的vector
std::vector<int> numbers;

// 添加元素
numbers.push_back(1);
numbers.push_back(2);
numbers.push_back(3);  // numbers现在包含 {1, 2, 3}

// 初始化方式
std::vector<int> primes = {2, 3, 5, 7, 11};  // 使用初始化列表
std::vector<int> zeros(5, 0);                // 5个0
std::vector<int> sequence(10);               // 10个默认初始化的int
std::vector<std::string> words(3, "hello");  // 3个"hello"字符串

// 访问元素
int first = numbers[0];      // 使用下标运算符(无边界检查)
int second = numbers.at(1);  // 使用at()方法(有边界检查)

// 修改元素
numbers[0] = 10;  // 修改第一个元素

// 获取大小
size_t size = numbers.size();  // size = 3

// 调整大小
numbers.resize(5);  // 添加2个默认构造的元素,numbers现在包含 {10, 2, 3, 0, 0}
numbers.resize(2);  // 移除最后3个元素,numbers现在包含 {10, 2}

// 迭代
for (const auto& num : numbers) {
    std::cout << num << ' ';
}

// 检查是否为空
bool is_empty = numbers.empty();  // is_empty = false

// 插入与删除
numbers.insert(numbers.begin() + 1, 42);  // 在第二个位置插入42,numbers变为 {10, 42, 2}
numbers.erase(numbers.begin());           // 移除第一个元素,numbers变为 {42, 2}

std::vector的优势:

  1. 动态大小:可以根据需要增长或缩小。
  2. 自动内存管理:处理内存分配和释放。
  3. 丰富的接口:提供多种访问、修改和操作元素的方法。
  4. 高效的添加操作push_back()通常是常数时间复杂度(摊销)。
  5. 兼容STL算法:可以与标准库算法无缝配合。

std::vector应该是新代码中使用可变大小数组的首选。

std::string

std::string是C++标准库中用于处理字符串的类,它提供了丰富的功能,使字符串操作更加安全和方便。

基本用法

#include <string>

// 创建字符串
std::string greeting = "Hello";
std::string empty_string;  // 创建空字符串

// 连接字符串
std::string name = "John";
std::string message = greeting + ", " + name + "!";  // message = "Hello, John!"

// 获取长度
size_t length = message.length();  // 也可以使用 message.size()

// 访问字符
char first = message[0];        // 无边界检查
char second = message.at(1);    // 有边界检查(越界时抛出异常)

// 子字符串
std::string sub = message.substr(7, 4);  // sub = "John"(从索引7开始,长度4)

// 查找
size_t pos = message.find("John");  // pos = 7
size_t not_found = message.find("Alice");  // not_found = std::string::npos

// 替换
message.replace(7, 4, "Alice");  // message = "Hello, Alice!"

// 插入
message.insert(7, "dear ");  // message = "Hello, dear Alice!"

// 删除
message.erase(7, 5);  // message = "Hello, Alice!"(删除"dear ")

// 比较
bool equals = (greeting == "Hello");  // true
bool comes_before = (greeting < "Zebra");  // true(字典序比较)

// 转换为C风格字符串
const char* c_str = message.c_str();  // 返回C风格字符串(以'\0'结尾)

字符串修改

std::string str = "Hello";

// 添加内容
str.push_back('!');  // str = "Hello!"
str.append(" World");  // str = "Hello! World"

// 清空
str.clear();  // str = ""

// 重新赋值
str = "New content";

// 调整大小
str.resize(5);  // str = "New c"
str.resize(10, '.');  // str = "New c....."

字符串转换

#include <string>

// 数值转字符串
int number = 42;
std::string str = std::to_string(number);  // str = "42"

// 字符串转数值
std::string number_str = "42";
int num = std::stoi(number_str);  // num = 42
double num_double = std::stod("3.14");  // num_double = 3.14

// 注意:转换函数在失败时会抛出异常
try {
    int invalid = std::stoi("not a number");
} catch (const std::invalid_argument& e) {
    std::cout << "转换失败:无效的参数" << std::endl;
} catch (const std::out_of_range& e) {
    std::cout << "转换失败:结果超出范围" << std::endl;
}

std::string vs C风格字符串

std::string对比C风格字符串的优势:

  1. 自动内存管理:不需要担心缓冲区大小和内存释放。
  2. 安全性:提供边界检查,防止缓冲区溢出。
  3. 丰富的功能:内置大量字符串操作函数。
  4. 方便的操作符:支持+==<等直观的运算符。
  5. 与STL兼容:可以与标准库算法和容器一起使用。

常见的字符串处理操作

字符串分割

C++标准库没有内置的分割函数,但可以实现一个:

#include <string>
#include <vector>
#include <sstream>

std::vector<std::string> split(const std::string& s, char delimiter) {
    std::vector<std::string> tokens;
    std::string token;
    std::istringstream token_stream(s);
    while (std::getline(token_stream, token, delimiter)) {
        tokens.push_back(token);
    }
    return tokens;
}

// 使用示例
std::string csv = "apple,banana,cherry";
std::vector<std::string> fruits = split(csv, ',');
// fruits = {"apple", "banana", "cherry"}
字符串修剪(去除首尾空白)
#include <string>
#include <algorithm>

std::string trim(const std::string& str) {
    auto start = std::find_if_not(str.begin(), str.end(), [](unsigned char c) {
        return std::isspace(c);
    });
    auto end = std::find_if_not(str.rbegin(), str.rend(), [](unsigned char c) {
        return std::isspace(c);
    }).base();
    
    return (start < end) ? std::string(start, end) : std::string();
}

// 使用示例
std::string padded = "  hello world  ";
std::string trimmed = trim(padded);  // trimmed = "hello world"
大小写转换
#include <string>
#include <algorithm>
#include <cctype>

std::string to_lower(std::string s) {
    std::transform(s.begin(), s.end(), s.begin(), 
                   [](unsigned char c) { return std::tolower(c); });
    return s;
}

std::string to_upper(std::string s) {
    std::transform(s.begin(), s.end(), s.begin(), 
                   [](unsigned char c) { return std::toupper(c); });
    return s;
}

// 使用示例
std::string mixed = "Hello World";
std::string lower = to_lower(mixed);  // lower = "hello world"
std::string upper = to_upper(mixed);  // upper = "HELLO WORLD"

字符串字面量

传统字符串字面量

C++中的传统字符串字面量是以双引号括起来的字符序列:

const char* str = "Hello, World!";

这种字面量是由字符组成的数组,以空字符结尾,类型为const char[N],但通常会退化为const char*

C++11的原始字符串字面量

C++11引入了原始字符串字面量,使用R"delimiter(content)delimiter"语法,可以包含未转义的特殊字符:

// 传统字符串需要转义反斜杠和引号
std::string path = "C:\\Program Files\\App\\config.ini";
std::string json = "{\"name\": \"John\", \"age\": 30}";

// 原始字符串不需要转义
std::string path_raw = R"(C:\Program Files\App\config.ini)";
std::string json_raw = R"({"name": "John", "age": 30})";

// 自定义分隔符处理包含 )" 的字符串
std::string complicated = R"***("This string contains ) and " characters")***";

原始字符串字面量特别适合表示正则表达式、文件路径、JSON和XML等包含大量特殊字符的字符串。

C++14的字符串字面量后缀

C++14引入了用户定义的字面量,包括字符串字面量的后缀:

#include <string>
using namespace std::string_literals;

// 使用 s 后缀创建std::string
auto str1 = "Hello"s;  // 类型为std::string,而非const char*

// 使用标准字符串字面量
auto str2 = "Hello";   // 类型为const char*

C++17的字符串视图

C++17引入了std::string_view,一个非拥有的字符串引用,提高了字符串处理的效率:

#include <string_view>
using namespace std::string_view_literals;

// 使用 sv 后缀创建std::string_view
auto view = "Hello"sv;  // 类型为std::string_view

// string_view的基本使用
std::string_view sv = "Hello, World!";
std::cout << sv.substr(0, 5) << std::endl;  // 输出"Hello",无复制

std::string_view适合作为函数参数,避免不必要的字符串复制:

void process(std::string_view sv) {
    // 使用sv,但不修改它
}

// 可以接受多种字符串类型,无需复制
process("literal");             // const char*
process(std::string("string")); // std::string

多维数据结构

二维数组

C++中有多种方式表示二维数组:

使用C风格二维数组
// 静态分配的二维数组
int matrix[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

// 访问元素
int element = matrix[1][2];  // 获取第2行第3列的元素,值为7
使用std::array的二维数组
#include <array>

// 使用std::array定义二维数组
std::array<std::array<int, 4>, 3> matrix = {{
    {{1, 2, 3, 4}},
    {{5, 6, 7, 8}},
    {{9, 10, 11, 12}}
}};

// 访问元素
int element = matrix[1][2];  // 获取第2行第3列的元素,值为7
使用std::vector的二维数组
#include <vector>

// 创建3x4的二维vector
std::vector<std::vector<int>> matrix(3, std::vector<int>(4));

// 初始化
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        matrix[i][j] = i * 4 + j + 1;
    }
}

// 或者直接初始化
std::vector<std::vector<int>> matrix = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

// 访问元素
int element = matrix[1][2];  // 获取第2行第3列的元素,值为7

// 动态增加行
matrix.push_back({13, 14, 15, 16});  // 添加第4行
使用一维数组模拟二维数组

有时,使用一维数组模拟二维数组可以提高性能(内存连续性更好):

#include <vector>

int rows = 3, cols = 4;
std::vector<int> matrix(rows * cols);

// 设置元素
void set(std::vector<int>& m, int row, int col, int value, int cols) {
    m[row * cols + col] = value;
}

// 获取元素
int get(const std::vector<int>& m, int row, int col, int cols) {
    return m[row * cols + col];
}

// 使用示例
set(matrix, 1, 2, 7, cols);  // 设置第2行第3列为7
int element = get(matrix, 1, 2, cols);  // 获取第2行第3列的元素,值为7

字符串数组

字符串数组是包含多个字符串的数组:

使用C风格字符串数组
// C风格字符串数组
const char* fruits[] = {"apple", "banana", "cherry"};

// 访问元素
const char* first_fruit = fruits[0];  // "apple"
使用std::string数组
#include <string>

// 使用std::string的数组
std::string fruits[] = {"apple", "banana", "cherry"};

// 使用std::vector<std::string>
std::vector<std::string> fruits_vec = {"apple", "banana", "cherry"};
fruits_vec.push_back("date");  // 添加新元素

// 访问与修改
std::string first = fruits_vec[0];  // "apple"
fruits_vec[0] = "avocado";         // 修改第一个元素

实际应用案例

案例1:文本处理

以下是一个简单的文本处理程序,用于统计文件中每个单词的出现频率:

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <algorithm>
#include <cctype>

// 转换为小写并移除标点
std::string normalize(const std::string& word) {
    std::string result;
    for (char c : word) {
        if (std::isalpha(c)) {
            result.push_back(std::tolower(c));
        }
    }
    return result;
}

// 分割字符串
std::vector<std::string> split(const std::string& text, char delimiter = ' ') {
    std::vector<std::string> tokens;
    std::istringstream stream(text);
    std::string token;
    
    while (std::getline(stream, token, delimiter)) {
        if (!token.empty()) {
            tokens.push_back(token);
        }
    }
    
    return tokens;
}

int main() {
    std::ifstream file("sample.txt");
    if (!file) {
        std::cerr << "无法打开文件" << std::endl;
        return 1;
    }
    
    std::map<std::string, int> word_count;
    std::string line;
    
    // 逐行读取文件
    while (std::getline(file, line)) {
        auto words = split(line);
        for (const auto& word : words) {
            std::string normalized = normalize(word);
            if (!normalized.empty()) {
                word_count[normalized]++;
            }
        }
    }
    
    // 转换为vector以便排序
    std::vector<std::pair<std::string, int>> word_freq(
        word_count.begin(), word_count.end()
    );
    
    // 按频率排序
    std::sort(word_freq.begin(), word_freq.end(),
              [](const auto& a, const auto& b) {
                  return a.second > b.second; // 频率降序
              });
    
    // 打印前10个高频词
    std::cout << "最常见的10个单词:" << std::endl;
    for (size_t i = 0; i < std::min(word_freq.size(), size_t(10)); ++i) {
        std::cout << word_freq[i].first << ": " 
                  << word_freq[i].second << std::endl;
    }
    
    return 0;
}

案例2:矩阵操作

以下是一个简单的矩阵类,演示如何使用二维数组表示和操作矩阵:

#include <iostream>
#include <vector>
#include <stdexcept>

class Matrix {
private:
    std::vector<std::vector<double>> data;
    size_t rows;
    size_t cols;
    
public:
    // 构造函数
    Matrix(size_t r, size_t c, double initial_value = 0.0)
        : rows(r), cols(c), data(r, std::vector<double>(c, initial_value)) {}
    
    // 访问元素
    double& at(size_t r, size_t c) {
        if (r >= rows || c >= cols) {
            throw std::out_of_range("Matrix indices out of range");
        }
        return data[r][c];
    }
    
    const double& at(size_t r, size_t c) const {
        if (r >= rows || c >= cols) {
            throw std::out_of_range("Matrix indices out of range");
        }
        return data[r][c];
    }
    
    // 矩阵加法
    Matrix operator+(const Matrix& other) const {
        if (rows != other.rows || cols != other.cols) {
            throw std::invalid_argument("Matrix dimensions must match for addition");
        }
        
        Matrix result(rows, cols);
        for (size_t i = 0; i < rows; ++i) {
            for (size_t j = 0; j < cols; ++j) {
                result.data[i][j] = data[i][j] + other.data[i][j];
            }
        }
        
        return result;
    }
    
    // 矩阵乘法
    Matrix operator*(const Matrix& other) const {
        if (cols != other.rows) {
            throw std::invalid_argument("Matrix dimensions must match for multiplication");
        }
        
        Matrix result(rows, other.cols, 0.0);
        for (size_t i = 0; i < rows; ++i) {
            for (size_t j = 0; j < other.cols; ++j) {
                for (size_t k = 0; k < cols; ++k) {
                    result.data[i][j] += data[i][k] * other.data[k][j];
                }
            }
        }
        
        return result;
    }
    
    // 打印矩阵
    void print() const {
        for (size_t i = 0; i < rows; ++i) {
            for (size_t j = 0; j < cols; ++j) {
                std::cout << data[i][j] << " ";
            }
            std::cout << std::endl;
        }
    }
};

int main() {
    // 创建两个矩阵
    Matrix A(2, 3);
    Matrix B(3, 2);
    
    // 初始化矩阵A
    A.at(0, 0) = 1; A.at(0, 1) = 2; A.at(0, 2) = 3;
    A.at(1, 0) = 4; A.at(1, 1) = 5; A.at(1, 2) = 6;
    
    // 初始化矩阵B
    B.at(0, 0) = 7; B.at(0, 1) = 8;
    B.at(1, 0) = 9; B.at(1, 1) = 10;
    B.at(2, 0) = 11; B.at(2, 1) = 12;
    
    std::cout << "矩阵A:" << std::endl;
    A.print();
    
    std::cout << "矩阵B:" << std::endl;
    B.print();
    
    // 计算矩阵乘积
    Matrix C = A * B;
    std::cout << "矩阵A * B:" << std::endl;
    C.print();
    
    return 0;
}

案例3:CSV解析器

以下是一个简单的CSV文件解析器,演示如何处理字符串和数组:

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>

class CSVParser {
private:
    char delimiter;
    bool has_header;
    std::vector<std::string> headers;
    std::vector<std::vector<std::string>> data;
    
    // 分割CSV行
    std::vector<std::string> splitLine(const std::string& line) {
        std::vector<std::string> fields;
        std::string field;
        bool in_quotes = false;
        std::ostringstream current_field;
        
        for (char c : line) {
            if (c == '"') {
                in_quotes = !in_quotes;
            } else if (c == delimiter && !in_quotes) {
                fields.push_back(current_field.str());
                current_field.str("");
            } else {
                current_field << c;
            }
        }
        
        // 添加最后一个字段
        fields.push_back(current_field.str());
        
        return fields;
    }
    
public:
    CSVParser(char delim = ',', bool header = true) 
        : delimiter(delim), has_header(header) {}
    
    // 从文件加载数据
    bool loadFromFile(const std::string& filename) {
        std::ifstream file(filename);
        if (!file.is_open()) {
            return false;
        }
        
        std::string line;
        bool first_line = true;
        
        while (std::getline(file, line)) {
            auto fields = splitLine(line);
            
            if (first_line && has_header) {
                headers = fields;
                first_line = false;
            } else {
                data.push_back(fields);
                first_line = false;
            }
        }
        
        return true;
    }
    
    // 获取表头
    const std::vector<std::string>& getHeaders() const {
        return headers;
    }
    
    // 获取所有数据
    const std::vector<std::vector<std::string>>& getData() const {
        return data;
    }
    
    // 获取特定行
    const std::vector<std::string>& getRow(size_t index) const {
        if (index >= data.size()) {
            throw std::out_of_range("Row index out of range");
        }
        return data[index];
    }
    
    // 获取特定列
    std::vector<std::string> getColumn(size_t index) const {
        if (data.empty() || index >= data[0].size()) {
            throw std::out_of_range("Column index out of range");
        }
        
        std::vector<std::string> column;
        for (const auto& row : data) {
            column.push_back(row[index]);
        }
        return column;
    }
    
    // 获取特定列(按列名)
    std::vector<std::string> getColumn(const std::string& column_name) const {
        if (!has_header) {
            throw std::runtime_error("No header information available");
        }
        
        auto it = std::find(headers.begin(), headers.end(), column_name);
        if (it == headers.end()) {
            throw std::runtime_error("Column name not found");
        }
        
        return getColumn(std::distance(headers.begin(), it));
    }
    
    // 打印数据
    void print() const {
        if (has_header) {
            for (size_t i = 0; i < headers.size(); ++i) {
                std::cout << headers[i];
                if (i < headers.size() - 1) std::cout << "\t";
            }
            std::cout << std::endl;
            
            // 打印分隔线
            for (size_t i = 0; i < headers.size(); ++i) {
                std::cout << std::string(headers[i].length(), '-');
                if (i < headers.size() - 1) std::cout << "\t";
            }
            std::cout << std::endl;
        }
        
        for (const auto& row : data) {
            for (size_t i = 0; i < row.size(); ++i) {
                std::cout << row[i];
                if (i < row.size() - 1) std::cout << "\t";
            }
            std::cout << std::endl;
        }
    }
};

int main() {
    CSVParser parser;
    if (parser.loadFromFile("data.csv")) {
        std::cout << "CSV数据:" << std::endl;
        parser.print();
        
        try {
            // 获取特定列数据
            auto ages = parser.getColumn("Age");
            std::cout << "\n年龄数据:" << std::endl;
            for (const auto& age : ages) {
                std::cout << age << " ";
            }
            std::cout << std::endl;
        } catch (const std::exception& e) {
            std::cerr << "错误:" << e.what() << std::endl;
        }
    } else {
        std::cerr << "无法打开CSV文件" << std::endl;
    }
    
    return 0;
}

最佳实践与建议

  1. 优先使用标准库容器

    • 使用std::vector代替动态分配的C风格数组
    • 使用std::array代替固定大小的C风格数组
    • 使用std::string代替C风格字符串
  2. 避免C风格数组的缺陷

    • 避免使用new[]/delete[]手动管理数组内存
    • 避免数组作为参数时的指针退化问题
  3. 字符串处理

    • 对于只读字符串引用,考虑使用std::string_view(C++17)
    • 使用std::string的成员函数代替C风格字符串函数
    • 使用s字面量后缀创建std::stringusing namespace std::string_literals;
  4. 安全性

    • 总是使用边界检查的方法(如at())而不是未检查的下标运算符,除非对性能要求极高
    • 避免C风格字符串函数(如strcpystrcat)可能导致的缓冲区溢出
  5. 性能考虑

    • 使用引用避免不必要的复制,尤其是对大型字符串和容器
    • 考虑使用reserve()预分配std::vectorstd::string的容量
    • 对于固定大小的多维数组,使用一维数组+索引计算可能提供更好的缓存局部性
  6. 现代C++特性

    • 使用范围for循环遍历容器和数组
    • 使用auto简化代码,特别是处理复杂的STL类型时
    • 利用移动语义和右值引用减少不必要的复制
    • 使用原始字符串字面量(R"()")处理包含转义字符的字符串

总结

数组和字符串是C++编程的基础构建块,掌握它们的用法对于编写高效、安全的代码至关重要。本文回顾了C++中处理数组和字符串的各种方法,从传统的C风格方法到现代C++提供的标准库容器和类。

虽然C风格数组和字符串因其简单性和性能优势仍然在某些情况下有用,但现代C++开发应该优先考虑std::vectorstd::arraystd::string等安全且功能丰富的替代品。这些现代工具提供了自动内存管理、边界检查和丰富的操作接口,可以帮助我们编写更加安全、可维护的代码。

在下一篇文章中,我们将探讨C++中的指针与引用,这是C++区别于许多其他编程语言的重要特性。

参考资料

  1. Bjarne Stroustrup. The C++ Programming Language (4th Edition)
  2. Scott Meyers. Effective Modern C++
  3. cppreference.com - Arrays
  4. cppreference.com - std::string
  5. cppreference.com - std::vector
  6. cppreference.com - std::array
  7. C++ Core Guidelines - Arrays and Pointers

这是我C++学习之旅系列的第五篇技术文章。查看完整系列目录了解更多内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

superior tigre

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值