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

1次阅读
没有评论

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

image.webp

sizeof 陷阱:为什么我的数组大小总不对?

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

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

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

int main() {int nums[] = {1,2,3,4,5};
    std::cout << "Real size:" << sizeof(nums) / sizeof(nums[0]) << std::endl; // 正确输出 5
    printSize(nums); // 输出 2(32 位系统)或 1(64 位系统)}

这个经典陷阱揭示了 C ++ 数组传参的核心问题: 数组会退化为指针 。当数组作为函数参数时,编译器实际上传递的是指向数组首元素的指针,所有数组长度信息都丢失了。

传统传参方式三分天下

1. 指针模式(int* arr)

void process(int* arr, size_t len) {for(size_t i=0; i<len; ++i) 
        arr[i] *= 2;
}
  • 优点:最灵活,兼容 C 语言
  • 缺点:
  • 必须手动传递长度
  • 无法防止越界访问
  • 类型信息不完整

2. 定长数组模式(int arr[5])

void process(int arr[5]) {/* 实际仍会退化为指针 */}
  • 优点:语法直观
  • 缺点:
  • 长度只是文档提示,实际仍会退化为指针
  • 长度硬编码不灵活

3. 数组引用模式(int (&arr)[N])

template<size_t N>
void process(int (&arr)[N]) {static_assert(N > 0, "Array cannot be empty");
    // 现在可以安全使用 N
}
  • 优点:保留数组长度信息
  • 缺点:
  • 语法晦涩
  • 需要模板推导
  • 仅适用于栈数组

现代 C ++ 解决方案

std::array:编译期已知长度的首选

#include <array>

void process(std::array<int, 5>& arr) {for(auto& num : arr) 
        num *= 2;
}

// 使用示例
std::array<int, 5> nums{1,2,3,4,5};
process(nums);
  • 优势:
  • 完整保留长度信息
  • 支持迭代器
  • 值语义(可整体拷贝)
  • 限制:长度必须编译期确定

std::span:运行时长度的安全视图(C++20)

#include <span>

void process(std::span<int> arr) {if(arr.empty()) return;
    for(auto& num : arr) 
        num *= 2;
}

// 支持各种容器
std::vector<int> vec{1,2,3};
int raw[] = {4,5,6};
process(vec);
process(raw);
  • 优势:
  • 统一处理各种连续内存(原生数组 /vector/string 等)
  • 自动携带长度信息
  • 零开销抽象

防御性编程实战

编译期检查(static_assert)

template<typename T>
void safeProcess(T& container) {
    static_assert(
        std::is_same_v<T, std::array<int,5>> || 
        std::is_same_v<T, std::vector<int>>,
        "Only support specific containers"
    );
    // ...
}

运行时边界检查

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

性能与安全权衡

方式 长度信息 边界检查 拷贝成本 适用场景
原生数组 ❌ ❌ 低 性能敏感代码
std::array ✔️ 可选 中 固定长度容器
std::span ✔️ 可选 无 只读视图 / 接口参数
std::vector ✔️ ✔️ 高 动态数组

避坑指南

多线程安全

  • 只读访问:多个线程可同时读取同一数组
  • 写操作:必须同步(mutex/atomic)
  • 警惕 false sharing:对高频修改的相邻数据做 padding
struct ThreadSafeArray {
    std::array<int, 100> data;
    std::mutex mtx;

    void safeWrite(size_t idx, int val) {std::lock_guard<std::mutex> lock(mtx);
        data.at(idx) = val; // at() 自带边界检查}
};

迭代器失效

std::vector<int> vec{1,2,3};
auto it = vec.begin();
vec.push_back(4); // 可能导致迭代器失效
// 正确做法:操作后重新获取迭代器 

思考题:动态数组包装类

尝试设计一个 SafeArray 类,要求:
1. 支持运行时动态大小
2. 自动边界检查
3. 提供迭代器接口
4. 线程安全的随机访问

提示:可以组合使用 std::vector 和 std::mutex 实现。

单元测试示例

#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include "array_utils.h"

TEST_CASE("Array processing") {SECTION("std::array sum") {std::array<int,3> arr{1,2,3};
        REQUIRE(arraySum(arr) == 6);
    }

    SECTION("Boundary check") {std::vector<int> vec(10);
        REQUIRE_THROWS_AS(safeAccess(vec, 15), std::out_of_range);
    }
}
正文完
 0
评论(没有评论)