C++零基础到工程实战(5.2.7):函数与vector数组和引用

发布时间:2026/6/1 13:38:15

C++零基础到工程实战(5.2.7):函数与vector数组和引用 目录前言一、本节学习内容概要1.1 本节主要学习什么1.2 vector 和普通数组有什么区别1.3 vector 的基本内存理解二、vector 的遍历auto、auto 和 const auto2.1 使用范围 for 遍历 vector2.2 auto d会复制元素2.3 auto d引用原来的元素可以修改 vector2.4 const auto d只读访问避免复制三、vector 作为函数参数传值会复制3.1 vector 作为参数传值3.2 传值时函数内部是新的 vector3.3 vector 传值复制的是什么3.4 return vdatas 为什么可以3.5 为什么返回后的地址可能和函数内部一样四、move 移动语义减少 vector 数据复制4.1 std::move 的基本使用4.2 move 本身并不搬数据4.3 使用 move 后原来的 vector 会怎么样4.4 move 前后的地址变化4.5 move 的优点和代价五、vector 引用传参与返回值5.1 vector 引用传参不会复制5.2 引用传参时函数内部地址不变5.3 如果只读推荐 const vector 5.4 如果想返回引用返回值也要写引用六、完整代码示例七、总结7.1 使用 vector 需要引入头文件7.2 vector 是动态数组容器7.3 for(auto d : vdatas) 会复制元素7.4 for(auto d : vdatas) 可以修改原元素7.5 只读遍历推荐 const auto7.6 vector 传值会复制但是return vector和外面相同7.8 move 可以减少 vector 内部数据复制7.9 引用传参不会复制并不代表返回也不复制前言上一节我们学习了函数与普通数组、数组引用知道了一个非常重要的结论普通数组作为函数参数时会退化为指针。所以普通数组传入函数后函数内部无法直接通过sizeof得到数组真实大小一般需要额外传入size。但是在 C 中除了传统数组以外更常用的是vectorvector可以简单理解为 C 提供的动态数组容器。它可以自动管理内存支持动态扩容也可以通过.size()获取元素个数。本节就继续围绕函数展开重点讲解vector 作为函数参数时会不会复制vector 作为返回值时能不能返回vector 引用传参有什么好处for(auto d : vdatas) 和 for(auto d : vdatas) 有什么区别move(vdatas) 到底做了什么引用传参和 move 有什么区别一、本节学习内容概要1.1 本节主要学习什么本节主要围绕vector和函数之间的关系展开。主要内容包括vector 的基本使用vector 的内存结构vector 作为函数参数vector 作为函数返回值vector 引用传参vector 和 move 移动语义在 C 中如果想使用vector需要引入头文件#include vector定义一个vectorint数组vectorint vdatas{ 11,22,33,4,5 };这里的vdatas可以理解为一个动态数组对象里面保存了 5 个int类型的数据。1.2 vector 和普通数组有什么区别普通数组定义后大小一般固定。例如int datas[5] { 11,22,33,4,5 };这个数组大小是固定的不能像vector那样方便地动态增加元素。而vector可以这样写vectorint vdatas{ 11,22,33,4,5 };也可以继续添加元素vdatas.push_back(100); vdatas.push_back(200);并且可以通过vdatas.size()直接获取元素个数。这比普通数组方便很多。1.3 vector 的基本内存理解定义vectorint vdatas{ 11,22,33,4,5 };可以简单理解为vdatas 是一个 vector 对象这个对象本身一般定义在栈区对象内部保存了指向堆区数据的指针真正的 11、22、33、4、5 一般存放在堆区连续空间中。也就是说vector对象本身并不直接把所有元素都塞在对象里面。它内部更像保存了几个关键信息指向数据起始位置的指针当前元素个数当前容量大小可以通过vdatas.data()查看vector内部元素存储空间的首地址。例如vectorint vdatas{ 11,22,33,4,5 }; cout main : vdatas.data() endl;这里打印的是vector内部数组空间的首地址也就是第一个元素所在的地址。二、vector 的遍历auto、auto 和 const auto2.1 使用范围 for 遍历 vector遍历vector最常见的方式是范围 forvectorint vdatas{ 11,22,33,4,5 }; for (auto d : vdatas) { cout d ; }输出结果11 22 33 4 5这里auto d表示每次从vdatas中取出一个元素然后复制一份给d。因为这里元素类型是int所以auto d实际就是int d2.2 auto d会复制元素代码示例for (auto d : vdatas) { d 100; }这段代码不会修改vdatas中原来的元素。原因是d 是 vdatas 中元素的副本。也就是说每次遍历时只是把vdatas中的元素复制一份给d。修改d只是在修改副本不会影响原来的vector。例如vectorint vdatas{ 11,22,33,4,5 }; for (auto d : vdatas) { d 100; } for (auto d : vdatas) { cout d ; }输出仍然是11 22 33 4 52.3 auto d引用原来的元素可以修改 vector如果想修改vector中原来的元素需要使用引用for (auto d : vdatas) { d 100; }这里auto d表示d是vdatas中元素的引用。修改d就是修改原数组中的元素。例如vectorint vdatas{ 11,22,33,4,5 }; for (auto d : vdatas) { d 100; } for (auto d : vdatas) { cout d ; }输出结果100 100 100 100 100所以auto d 是复制元素auto d 是引用元素。2.4 const auto d只读访问避免复制如果只是想读取元素不想修改元素可以写成for (const auto d : vdatas) { cout d ; }这里const auto d表示只读引用。它有两个好处不会复制元素不能修改元素。对于int这种小类型来说复制成本很低auto d和const auto d性能差别不大。因为int 通常是 4 字节 指针在 64 位程序中通常是 8 字节。但是如果vector里面存的是大对象例如vectorstring vectorMyClass这时使用const auto d就可以避免每次遍历都复制一个大对象。所以常见习惯是只读遍历大对象const auto需要修改元素auto小类型只读auto 或 const auto 都可以三、vector 作为函数参数传值会复制3.1 vector 作为参数传值先看一个函数vectorint TestVector(vectorint vdatas) { cout begin TestVector endl; cout vdatas : vdatas.data() endl; cout vdatas.size() endl; for (auto d : vdatas) cout d ; cout endl; cout end TestVector endl; return vdatas; }函数参数是vectorint vdatas这是传值。也就是说调用函数时会把外面的vector复制一份给函数内部的vdatas。3.2 传值时函数内部是新的 vector调用代码vectorint vdatas{ 11,22,33,4,5 }; cout main : vdatas.data() endl; auto rdatas TestVector(vdatas); cout rdatas : rdatas.data() endl;可能输出类似main :00000196F0525150 begin TestVector vdatas :00000196F05251F0 5 11 22 33 4 5 end TestVector rdatas :00000196F05251F0注意看main : 00000196F0525150 vdatas : 00000196F05251F0这两个地址不同。说明main 中的 vdatas 和函数中的 vdatas 不是同一个对象内部的数据空间。也就是说vectorint TestVector(vectorint vdatas)这种写法会复制一份vector数据。3.3 vector 传值复制的是什么这里要分清楚两件事vector 对象本身 vector 内部管理的堆区数组当写TestVector(vdatas);因为参数是传值vectorint vdatas所以会创建一个新的vector对象。这个新的vector对象内部也会有自己的堆区数组空间。因此原来的vdatas.data()和函数内部的vdatas.data()地址不同。可以理解为main 中有一个 vector 函数参数中又复制出来一个 vector 两个 vector 各自管理自己的堆区数组。所以普通传值会有复制开销对于只有 5 个int的小数组来说这个开销不大。但如果vector中有很多数据比如几十万、几百万个元素那么复制成本就会明显增加。3.4 return vdatas 为什么可以函数中写return vdatas;这里返回的是函数内部的局部vector对象。前面学习普通数组时我们说过不能返回函数内部局部数组的地址。例如int* Test() { int arr[10]; return arr; // 错误 }但是vector不一样。下面这种写法是可以的vectorint TestVector(vectorint vdatas) { return vdatas; }原因是这里返回的是 vector 对象本身不是返回局部对象的地址。函数返回时C 会把这个vector对象作为返回值交给外面。现代 C 编译器通常会进行优化例如返回值优化 移动构造所以返回vector并不一定会真的完整复制一份所有元素。3.5 为什么返回后的地址可能和函数内部一样在运行结果中可能看到vdatas :00000196F05251F0 rdatas :00000196F05251F0也就是说函数内部 vdatas.data() 和 外面 rdatas.data() 打印出来的地址一样。这是因为返回时可能发生了移动构造也可以理解为函数内部 vdatas 管理的堆区数组被移动给了外面的 rdatas。这时不会重新复制 5 个元素而是把内部数组资源转交给返回值对象。所以return vdatas;虽然返回的是局部对象但这是安全的。因为返回的不是局部对象地址而是把对象的资源交给了外部对象。四、move 移动语义减少 vector 数据复制4.1 std::move 的基本使用如果想避免第一次传参时复制vector可以使用move代码示例vectorint vdatas{ 11,22,33,4,5 }; auto mdatas TestVector(move(vdatas));这里move(vdatas)表示把vdatas转成右值让函数参数可以通过移动构造接收它的资源。注意使用move需要引入#include utility不过很多时候其他头文件可能间接包含了它但规范写法建议加上4.2 move 本身并不搬数据很多初学者会误以为move(vdatas)这一句本身就把数据搬走了。其实不是。std::move本身主要做的是强制类型转换。它把一个左值转换成右值引用让后续代码可以调用移动构造或者移动赋值。真正发生资源转移的是vector 的移动构造函数例如auto mdatas TestVector(move(vdatas));进入函数时参数vectorint vdatas会通过移动构造创建。此时原来vdatas内部管理的堆区数组资源可能会被直接转移给函数参数。这样就不用复制所有元素了。4.3 使用 move 后原来的 vector 会怎么样看代码vectorint vdatas{ 11,22,33,4,5 }; auto mdatas TestVector(move(vdatas)); cout vdatas : vdatas.size() endl;很多编译器中可能输出vdatas :0这说明原来的vdatas数据资源已经被移动走了。但是要注意更严谨地说被 move 之后的 vector 仍然是一个有效对象 但是它里面的内容处于未指定状态。也就是说移动后的vdatas仍然可以析构 重新赋值 调用 size() 调用 clear() 继续 push_back()。但是不要再依赖它原来的内容。常见编译器下vdatas.size()很可能是 0。4.4 move 前后的地址变化代码示例vectorint vdatas{ 11,22,33,4,5 }; cout main : vdatas.data() endl; auto mdatas TestVector(move(vdatas)); cout mdatas : mdatas.data() endl; cout vdatas : vdatas.size() endl;可能输出main :00000196F0525150 begin TestVector vdatas :00000196F0525150 5 11 22 33 4 5 end TestVector mdatas :00000196F0525150 vdatas :0这里可以看到main 中原始 vdatas.data() 函数内部 vdatas.data() 返回后的 mdatas.data()可能是同一个地址。这说明堆区数组资源从原 vdatas 移动到了函数参数 函数参数返回时又移动到了 mdatas。整个过程中元素数据本身没有被重新复制。4.5 move 的优点和代价move的优点减少大对象复制 避免大量元素重新分配和拷贝 适合资源转移场景。例如一个很大的vectorvectorint bigData(1000000);如果传值复制可能要复制 100 万个int。如果使用移动语义就可以只转移内部资源效率更高。但是move也有代价原来的对象资源被转移后不应该继续依赖原来的数据。例如auto mdatas TestVector(move(vdatas));这之后不建议继续使用vdatas原来的元素内容。可以重新给它赋值vdatas { 1,2,3 };或者重新添加元素vdatas.push_back(100);这样是可以的。五、vector 引用传参与返回值5.1 vector 引用传参不会复制如果不想复制vector最常见的方法是引用传参。例如vectorint TestVectorRef(vectorint vdatas) { cout begin TestVectorRef endl; cout vdatas : vdatas.data() endl; cout vdatas.size() endl; for (auto d : vdatas) cout d ; cout endl; cout end TestVectorRef endl; return vdatas; }参数是vectorint vdatas这表示vdatas 是外部 vector 的引用。函数内部并没有创建新的vector数据副本。5.2 引用传参时函数内部地址不变调用代码vectorint vdatas{ 11,22,33,4,5 }; cout main : vdatas.data() endl; auto mdatas TestVectorRef(vdatas); cout mdatas : mdatas.data() endl; cout vdatas : vdatas.size() endl;可能输出------------ref------------ main :000002CD7B035A60 begin TestVectorRef vdatas :000002CD7B035A60 5 11 22 33 4 5 end TestVectorRef mdatas :000002CD7B035B00 vdatas :5注意main :000002CD7B035A60 vdatas :000002CD7B035A60这两个地址一样。说明引用传参时函数内部操作的就是外面的那个vector。但是mdatas :000002CD7B035B00这个地址变了。原因是函数返回值类型是vectorint而不是vectorint所以return vdatas;虽然vdatas是引用参数但是返回时返回的是一个新的vector对象。也就是说引用传参不复制 但是按值返回会复制或移动生成新的返回对象。5.3 如果只读推荐 const vectorint如果函数内部只是读取vector不修改它推荐写成void PrintVector(const vectorint vdatas) { for (const auto d : vdatas) { cout d ; } cout endl; }这里const vectorint vdatas有两个好处不会复制 vector 函数内部不能修改原 vector。这在实际工程中非常常见。例如void PrintVector(const vectorint vdatas);表示这个函数只是查看数据不会修改数据。5.4 如果想返回引用返回值也要写引用如果确实想返回原来的vector可以写成vectorint TestVectorRefReturn(vectorint vdatas) { return vdatas; }调用vectorint vdatas{ 11,22,33,4,5 }; auto r TestVectorRefReturn(vdatas); cout r.data() endl;这里auto r也要写引用。如果写成auto r TestVectorRefReturn(vdatas);那么还是会复制一份。所以要记住函数返回引用接收时也要用引用才能继续保持引用关系。但是返回引用也要注意生命周期。可以返回外部传进来的 vector 引用全局 vector 引用static vector 引用。不能返回函数内部普通局部 vector 的引用。错误示例vectorint Test() { vectorint temp{ 1,2,3 }; return temp; // 错误temp 离开函数后会销毁 }这个问题和前面讲的“不能返回栈区变量的引用”本质是一样的。六、完整代码示例#include iostream #include vector #include utility using namespace std; // 函数与 vector 数组和引用 vectorint TestVector(vectorint vdatas) { cout begin TestVector endl; cout vdatas : vdatas.data() endl; cout vdatas.size() endl; for (auto d : vdatas) cout d ; cout endl; cout end TestVector endl; return vdatas; // 返回 vector 对象现代 C 中通常会移动或优化 } vectorint TestVectorRef(vectorint vdatas) { cout begin TestVectorRef endl; cout vdatas : vdatas.data() endl; cout vdatas.size() endl; for (auto d : vdatas) cout d ; cout endl; cout end TestVectorRef endl; return vdatas; // 返回值是 vectorint所以会产生新的返回对象 } void PrintVector(const vectorint vdatas) { for (const auto d : vdatas) { cout d ; } cout endl; } int main() { vectorint vdatas{ 11,22,33,4,5 }; cout main : vdatas.data() endl; auto rdatas TestVector(vdatas); cout rdatas : rdatas.data() endl; auto mdatas TestVector(move(vdatas)); cout mdatas : mdatas.data() endl; cout vdatas size : vdatas.size() endl; { cout ------------ref------------ endl; vectorint vdatas{ 11,22,33,4,5 }; cout main : vdatas.data() endl; auto rdatas TestVectorRef(vdatas); cout rdatas : rdatas.data() endl; cout vdatas size : vdatas.size() endl; } return 0; }七、总结本节主要学习了函数与vector、引用、移动语义之间的关系。情况调用方式函数参数函数返回值main 和函数内部地址return 后地址普通传值TestVector(vdatas)vectorint vdatasvectorint不同通常和函数内部相同move 传值TestVector(move(vdatas))vectorint vdatasvectorint通常相同通常和函数内部相同引用传参按值返回TestVectorRef(vdatas)vectorint vdatasvectorint相同通常不同引用传参返回引用TestVectorRefReturn(vdatas)vectorint vdatasvectorint相同相同普通传值进函数会复制所以 main 和函数内部地址不同move 传值资源被移动进去所以 main 原地址、函数内部地址、返回地址通常相同引用传参进函数不复制所以 main 和函数内部地址相同引用传参但按值返回return 会生成新对象所以返回地址不同引用传参并返回引用从头到尾都是同一个对象所以三个地址都相同。看参数有没有 决定进函数时是否复制看返回值有没有 决定 return 出来后是否还是同一个对象。7.1 使用 vector 需要引入头文件#include vector如果使用move建议引入#include utility7.2 vector 是动态数组容器定义vectorint vdatas{ 11,22,33,4,5 };可以简单理解为vdatas 对象本身通常在栈区真正的元素数据通常在堆区vdatas 内部保存了指向堆区数据的指针。可以通过vdatas.data()查看内部数组空间首地址。7.3 for(auto d : vdatas) 会复制元素for (auto d : vdatas)这里d是元素副本。修改d不会影响原来的vector。7.4 for(auto d : vdatas) 可以修改原元素for (auto d : vdatas)这里d是元素引用。修改d就是修改vdatas中的元素。7.5 只读遍历推荐 const autofor (const auto d : vdatas)这种写法既不会复制元素也不会修改元素。对于大对象更推荐这样写。7.6 vector 传值会复制但是return vector和外面相同vectorint TestVector(vectorint vdatas)这种写法会把外面的vector复制一份到函数内部。所以函数内部vdatas.data()和外面的main 中 vdatas.data()通常不同。return vdatas;虽然vdatas是函数内部变量但是返回的是对象本身不是返回局部变量地址。现代 C 中通常会通过返回值优化或移动语义减少复制。7.8 move 可以减少 vector 内部数据复制auto mdatas TestVector(move(vdatas));move本身只是类型转换真正转移资源的是vector的移动构造。移动后原来的vdatas仍然是有效对象但是内容不要再依赖。7.9 引用传参不会复制并不代表返回也不复制vectorint TestVectorRef(vectorint vdatas)这里参数是引用函数内部操作的就是外面的vector。所以函数内部vdatas.data()和外部vdatas.data()一样。如果函数返回值是vectorint那么return vdatas;仍然会生成一个新的返回对象。如果想返回引用要写成vectorint Test(vectorint vdatas)正确写法是vectorint Test(vectorint vdatas) { return vdatas; }并且接收时也要写auto r Test(vdatas);

相关新闻