C++函数调用数组参数:从基础原理到高效实践

1次阅读
没有评论

共计 2118 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

从 sizeof 陷阱看原生数组的缺陷

刚学 C ++ 时,我写过这样的代码:

C++ 函数调用数组参数:从基础原理到高效实践

void printSize(int arr[]) {std::cout << "Array size:" << sizeof(arr) / sizeof(arr[0]); // 陷阱!}

int main() {int nums[] = {1,2,3,4,5};
    printSize(nums); // 输出 8(指针大小)/4 = 2(错误)}
  • 这里 sizeof(arr) 返回的是指针大小(8 字节),而非数组总字节数
  • 数组作为参数时会退化为指针,丢失长度信息

传统传参方式对比

1. int[] 语法糖

void process(int arr[], size_t len); // 等价于 int* arr
  • 优点:写法简洁
  • 缺点:
  • 仍需额外传递长度参数
  • 无法防止越界访问

2. 裸指针 int*

void process(int* begin, int* end); // 迭代器风格
  • 优点:
  • 明确传递范围
  • 兼容 C 接口
  • 缺点:
  • 需手动检查空指针
  • 无法直观看出数组长度

3. 数组引用 int(&)[]

template<size_t N>
void process(int (&arr)[N]) { // 保留数组长度
    static_assert(N > 0, "Empty array");
}
  • 优点:
  • 自动推导数组长度
  • 类型安全
  • 缺点:
  • 语法复杂
  • 仅适用于静态数组

现代 C ++ 解决方案

std::array(编译期固定大小)

#include <array>
#include <stdexcept>

void safeProcess(const std::array<int, 5>& arr) {if (arr.empty()) throw std::invalid_argument("Empty array");
    // 编译器保证不会越界
}

std::span(C++20 运行时动态大小)

#include <span>

void processChunk(std::span<int> data) {if (data.size() < 3) // 运行时检查
        throw std::range_error("Need at least 3 elements");

    for (int& val : data) { // 支持范围 for
        val *= 2;
    }
}

防御性编程技巧

编译期检查

template<typename T, size_t N>
void validateArray(const T (&arr)[N]) {static_assert(N <= 100, "Array too large");
    static_assert(std::is_arithmetic_v<T>, "Numeric type required");
}

运行时边界检查

void safeAccess(std::span<int> arr, size_t idx) {if (idx >= arr.size()) {
        throw std::out_of_range("Index" 
            + std::to_string(idx) + "out of range");
    }
    arr[idx] = 42;
}

性能与内存考量

方案 栈内存 拷贝开销 长度安全
原生数组
std::array
std::span
  • 小数组(<16 元素):优先用std::array
  • 大数组或动态数据:用 std::span 避免拷贝

避坑指南

多线程安全

std::mutex mtx;
void threadSafeProcess(std::span<int> data) {std::lock_guard<std::mutex> lock(mtx);
    // 修改共享数组...
}

迭代器失效

void avoidInvalidation() {std::vector<int> vec{1,2,3};
    auto it = vec.begin();
    vec.push_back(4); // 可能使 it 失效
    // 正确做法:操作前保存 end()迭代器}

思考与延伸

试着实现一个 SafeDynamicArray 类,要求:
1. 封装原生指针和大小
2. 支持范围检查
3. 提供迭代器接口
4. 禁止拷贝但允许移动

样例框架:

class SafeDynamicArray {
public:
    explicit SafeDynamicArray(size_t size);
    ~SafeDynamicArray();

    // 禁用拷贝
    SafeDynamicArray(const SafeDynamicArray&) = delete;
    SafeDynamicArray& operator=(const SafeDynamicArray&) = delete;

    // 允许移动
    SafeDynamicArray(SafeDynamicArray&& other) noexcept;
    SafeDynamicArray& operator=(SafeDynamicArray&& other) noexcept;

    int& operator[](size_t idx) {if (idx >= size_) throw std::out_of_range("...");
        return data_[idx];
    }

private:
    int* data_;
    size_t size_;
};

通过理解这些核心机制,希望你能更自信地处理 C ++ 中的数组参数传递问题。记住:现代 C ++ 的类型安全特性是你的好朋友!

正文完
 0
评论(没有评论)