C++copy_n复制前N个元素
copy_n:C++里那个“只管前N个”的老实人
你有没有遇到过这种场景:手头有个 vector<int>,里面塞了上百个数据,但你只需要把前10个拷贝到另一块内存里——既不想写循环,又懒得调用 std::copy 配合 begin() 和 begin() + 10 这种带计算的迭代器?这时候,std::copy_n 就像一个提前约好、准时敲门的快递员:它不问源容器多长,也不管目标够不够大,只按你说的数字,老老实实搬走前N个。
copy_n 是 C++11 引入的算法,定义在 <algorithm> 头文件中。它的签名很干净:
template<class InputIt, class Size, class OutputIt>
OutputIt copy_n(InputIt first, Size count, OutputIt result);
注意两点:count 是有符号整数类型(通常是 int 或 ptrdiff_t),且必须 ≥ 0;first 只需满足输入迭代器要求,不要求是随机访问——这点常被忽略。 比如,你甚至能对 std::list 的 begin() 调用 copy_n,只要确保 count 不越界。
但现实总爱悄悄埋雷。最常见的坑是:count 为 0 时,copy_n 安全返回;可一旦 count > distance(first, last),行为未定义——它不会检查,也不会抛异常,直接越界读。 这不是缺陷,而是设计哲学:C++ 算法默认信任调用者。就像借锤子给你,不负责提醒你别砸自己脚。
所以,安全使用的第一步,永远是主动兜底。比如从 vector 拷贝前 N 个:
vector<int> src = {1, 2, 3};
vector<int> dst(5); // 目标空间必须足够!
size_t n = 10;
copy_n(src.begin(), min(n, src.size()), dst.begin());
这里 min(n, src.size()) 不是可选项,是必选项。有人会说:“我确定 N 永远不大于源大小。” —— 但代码跑在生产环境,上游数据可能突变,日志里突然冒出 std::bad_alloc 或静默数据错乱,排查起来比加一行 min 费十倍力气。
另一个易被轻视的细节:目标空间必须预先分配好,copy_n 不负责扩容。 它只做“赋值”,不调用 push_back。若目标是空 vector,直接 copy_n(..., vec.begin()) 会导致写入野地址。正确做法是:
- 用
resize()预留空间(适合已知最终大小); - 或用
back_inserter配合std::copy(但这就绕开copy_n了); - 或干脆用
assign():dst.assign(src.begin(), next(src.begin(), min(n, src.size())))——这其实是更自然的替代方案,尤其当目标容器需要完全重置时。
那 copy_n 究竟该用在哪儿?它真正的价值场景,是边界明确、长度可控、且追求零开销抽象的场合。比如解析协议帧:收到一包 128 字节的原始数据,需提取前 4 字节作为命令码,接下来 8 字节为序列号——这时 copy_n(recv_buf, 4, cmd_buf) 比 std::copy(recv_buf, recv_buf + 4, cmd_buf) 更直白,也避免了 +4 对非随机访问迭代器的非法操作。
再比如,配合 std::array 使用时,copy_n 的简洁性就凸显了:
array<char, 32> filename;
// 从 C 字符串截取前 31 字节,留一位给 '\0'
copy_n(c_str, min(strlen(c_str), size_t{31}), filename.begin());
filename[31] = '\0'; // 手动补结束符
没有 +n 计算,没有 distance,没有临时 vector,就是纯粹的“拿、放、停”。
当然,它也有软肋:不支持移动语义。 copy_n 内部调用的是 *result++ = *first++;,即拷贝赋值。如果你要移动前 N 个 unique_ptr,得用 std::move_iterator 包装:
vector<unique_ptr<int>> src = {/* ... */};
vector<unique_ptr<int>> dst(10);
copy_n(make_move_iterator(src.begin()), 10, dst.begin());
这不算缺陷,只是提醒你:工具箱里的每把刀,都得看清刃口朝哪边。
最后说句实在话:copy_n 不是万能钥匙,也远不如 std::copy 常用。但它存在的意义,是让“取前 N 个”这件事,在代码里拥有一个无歧义、无冗余、不依赖容器特性的标准表达。当你再次面对“只要开头几个”的需求时,不必再纠结循环变量名、不必手动算迭代器偏移、更不必为了一行逻辑引入额外头文件——copy_n 就在那里,不多不少,不声不响,搬完就走。
下次写到类似逻辑,试试它。不是为了炫技,而是让意图,在代码里站得笔直。


还没有评论,来说两句吧...