C++如何在数组与指针中实现数组拷贝与赋值

来源:PHP编程网作者:冷风头衔:草根站长
导读:本期聚焦于小伙伴创作的《C++如何在数组与指针中实现数组拷贝与赋值》,敬请观看详情。直接把数组名赋给另一个数组为什么编译报错?这背后是C++把数组名在多数语境中退化为指向首元素的指针,但数组类型本身不可整体赋值。若用指针接收,只是拷贝了地址,两份指针指向同一段内存,修改会互相影响。正确的做法是使用循环逐元素复制,或调用std::copy、memcpy等标准手段。当涉及动态内存时,指针赋值更需警惕浅拷贝带来的重复释放问题。理解数组与指针在拷贝语义上的差异,才能写出安全且可维护的代码,避免隐蔽的运行时错误。

在C++里,数组和指针经常一起出现,但二者在拷贝与赋值上的行为差异很大。很多初学者以为数组可以直接像普通变量一样整体赋值,结果编译器直接报错;而用指针去接数组名,看似成功了,实际只是拿到了地址。下面我们详细拆开讲清楚其中的机制与写法。

C++如何在数组与指针中实现数组拷贝与赋值

数组名的本质与为什么不能整体赋值

在C++中,数组名在大多数表达式里会隐式转换为指向首元素的指针,这被称为“数组到指针的退化”。但数组作为一种复合类型,其类型是包含元素个数信息的,例如int a[5]的类型是int[5]。语言规则明确禁止数组类型的整体赋值和传值拷贝,因此下面这样的代码无法通过编译:

int a[5] = {1, 2, 3, 4, 5};
int b[5];
b = a; // 编译错误:数组不可赋值

上面的b = a之所以错,是因为b作为数组名,在这里并没有退化成指针,而是代表整个数组对象,而数组对象没有可用的赋值运算符。如果写成int* p = a;,这时a退化成int*指针,赋值合法,但只是把首元素地址给了p,并不是拷贝数据。

这种设计的底层原因和历史有关:C语言为了效率不希望数组在传参或赋值时不经意发生大量内存复制,于是规定数组名容易退化为指针;C++沿用了该模型并增加了类型安全检查。理解这一点,就能明白为什么很多“拷贝数组”的错误其实是指针误用。

使用循环进行数组拷贝

最直观也最安全的数组拷贝方式,是用循环逐元素复制。这样不依赖数组退化,也能完整控制目标数组的大小与边界。

#include <iostream>

int main() {
    int src[5] = {10, 20, 30, 40, 50};
    int dst[5];

    // 逐元素拷贝
    for (int i = 0; i < 5; ++i) {
        dst[i] = src[i];
    }

    for (int i = 0; i < 5; ++i) {
        std::cout << dst[i] << " ";
    }
    return 0;
}

这种写法优点是非常清晰,任何读者都能一眼看懂在做什么,而且适用于所有元素类型,包括没有默认构造或不可memcpy的类类型。缺点是当数组维度很大或者需要频繁拷贝时,手写循环显得啰嗦。

要注意目标数组必须有足够的空间。如果dst声明为更小的大小,循环越界会导致未定义行为。在实际工程中,常把数组大小用常量或模板参数管理,避免魔法数字。

利用标准库完成拷贝

C++标准库提供了更简洁的算法来完成数组间元素复制。常用的是std::copy,它位于<algorithm>头文件中,利用迭代器语义,对原生数组同样有效。

#include <iostream>
#include <algorithm>

int main() {
    int src[5] = {1, 3, 5, 7, 9};
    int dst[5];

    std::copy(src, src + 5, dst);

    for (int i = 0; i < 5; ++i) {
        std::cout << dst[i] << " ";
    }
    return 0;
}

std::copy本质上也是赋值每个元素,但代码更短,并且可以和容器无缝配合。对于平凡类型(trivially copyable)的数组,也可以用C标准库的memcpy做按字节拷贝,效率通常很高。

#include <cstring>

int main() {
    int src[5] = {2, 4, 6, 8, 10};
    int dst[5];
    std::memcpy(dst, src, sizeof(src));
    return 0;
}

使用memcpy时必须保证元素类型是平凡可拷贝的,例如内置类型或POD结构;若数组里放的是带有虚函数或自定义构造/析构的类对象,用memcpy会破坏对象语义。因此日常C++更推荐std::copy

指针赋值与浅拷贝陷阱

当使用指针指向数组时,赋值操作只是复制地址,这叫做浅拷贝。看下面例子:

#include <iostream>

int main() {
    int arr[3] = {1, 2, 3};
    int* p1 = arr;
    int* p2 = p1; // 仅拷贝指针,不拷贝数据

    p1[0] = 99;
    std::cout << p2[0] << std::endl; // 输出99,因为指向同一数组
    return 0;
}

这里p2 = p1让两个指针指向同一块内存,任一方修改都会影响另一方。如果在函数里返回局部数组的指针,或者多个指针管理同一段动态内存,还容易出现重复释放(double free)或悬空指针。

对于动态分配的数组,正确的“拷贝”应该是重新申请内存并复制内容,也就是深拷贝:

#include <cstring>

int main() {
    int* src = new int[4]{1, 2, 3, 4};
    int* dst = new int[4];
    std::memcpy(dst, src, 4 * sizeof(int));

    // 使用完后各自释放
    delete[] src;
    delete[] dst;
    return 0;
}

如果只写dst = src;然后delete[] src; delete[] dst;,程序会对同一地址释放两次,直接崩溃。因此指针赋值不等于数组拷贝,管理资源时必须区分清楚。

用std::array和std::vector规避原始数组问题

现代C++更建议使用std::arraystd::vector替代原生数组。它们支持整体赋值和拷贝,语义明确。

#include <iostream>
#include <array>

int main() {
    std::array<int, 3> a{1, 2, 3};
    std::array<int, 3> b = a; // 合法,深拷贝

    b[0] = 100;
    std::cout << a[0] << " " << b[0] << std::endl; // 1 100
    return 0;
}

std::array在栈上分配,大小和原生数组一致,但拥有值语义;std::vector则在堆上动态管理,拷贝时自动深拷贝。用它们就不需要手动处理指针和循环,也避免了数组退化带来的混淆。

当然,在和C接口交互或写底层库时,原生数组和指针仍不可避免。此时牢记:数组名赋值非法,指针赋值只传地址,数据复制请用循环或标准算法。把这些规则内化,数组与指针的拷贝赋值就不再容易出错了。

C++数组拷贝指针赋值修改时间:2026-08-08 10:21:38

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。