news 2026/6/1 13:38:13

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++零基础到工程实战(5.2.7):函数与vector数组和引用

目录

前言

一、本节学习内容概要

1.1 本节主要学习什么?

1.2 vector 和普通数组有什么区别?

1.3 vector 的基本内存理解

二、vector 的遍历:auto、auto& 和 const auto&

2.1 使用范围 for 遍历 vector

2.2 auto d:会复制元素

2.3 auto& d:引用原来的元素,可以修改 vector

2.4 const auto& d:只读访问,避免复制

三、vector 作为函数参数:传值会复制

3.1 vector 作为参数传值

3.2 传值时,函数内部是新的 vector

3.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 auto&

7.6 vector 传值会复制但是return vector和外面相同

7.8 move 可以减少 vector 内部数据复制

7.9 引用传参不会复制并不代表返回也不复制


前言

上一节我们学习了函数与普通数组、数组引用,知道了一个非常重要的结论:

普通数组作为函数参数时,会退化为指针。

所以普通数组传入函数后,函数内部无法直接通过sizeof得到数组真实大小,一般需要额外传入size

但是在 C++ 中,除了传统数组以外,更常用的是:

vector

vector可以简单理解为 C++ 提供的动态数组容器。它可以自动管理内存,支持动态扩容,也可以通过.size()获取元素个数

本节就继续围绕函数展开,重点讲解:

  1. vector 作为函数参数时会不会复制?
  2. vector 作为返回值时能不能返回?
  3. vector 引用传参有什么好处?
  4. for(auto d : vdatas) 和 for(auto& d : vdatas) 有什么区别?
  5. move(vdatas) 到底做了什么?
  6. 引用传参和 move 有什么区别?


一、本节学习内容概要

1.1 本节主要学习什么?

本节主要围绕vector和函数之间的关系展开。

主要内容包括:

  1. vector 的基本使用
  2. vector 的内存结构
  3. vector 作为函数参数
  4. vector 作为函数返回值
  5. vector 引用传参
  6. vector 和 move 移动语义

在 C++ 中,如果想使用vector,需要引入头文件:

#include <vector>

定义一个vector<int>数组:

vector<int> vdatas{ 11,22,33,4,5 };

这里的vdatas可以理解为一个动态数组对象,里面保存了 5 个int类型的数据。


1.2 vector 和普通数组有什么区别?

普通数组定义后,大小一般固定。

例如:

int datas[5] = { 11,22,33,4,5 };

这个数组大小是固定的,不能像vector那样方便地动态增加元素。

vector可以这样写:

vector<int> vdatas{ 11,22,33,4,5 };

也可以继续添加元素:

vdatas.push_back(100); vdatas.push_back(200);

并且可以通过:

vdatas.size()

直接获取元素个数。

这比普通数组方便很多。


1.3 vector 的基本内存理解

定义:

vector<int> vdatas{ 11,22,33,4,5 };

可以简单理解为:

  1. vdatas 是一个 vector 对象;
  2. 这个对象本身一般定义在栈区;
  3. 对象内部保存了指向堆区数据的指针;
  4. 真正的 11、22、33、4、5 一般存放在堆区连续空间中。

也就是说,vector对象本身并不直接把所有元素都塞在对象里面。

它内部更像保存了几个关键信息:

  1. 指向数据起始位置的指针
  2. 当前元素个数
  3. 当前容量大小

可以通过:

vdatas.data()

查看vector内部元素存储空间的首地址。

例如:

vector<int> vdatas{ 11,22,33,4,5 }; cout << "main :" << vdatas.data() << endl;

这里打印的是vector内部数组空间的首地址,也就是第一个元素所在的地址。


二、vector 的遍历:auto、auto& 和 const auto&

2.1 使用范围 for 遍历 vector

遍历vector最常见的方式是范围 for:

vector<int> 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 d

2.2 auto d:会复制元素

代码示例:

for (auto d : vdatas) { d = 100; }

这段代码不会修改vdatas中原来的元素。

原因是:

d 是 vdatas 中元素的副本。

也就是说:

  1. 每次遍历时,只是把vdatas中的元素复制一份给d
  2. 修改d,只是在修改副本,不会影响原来的vector

例如:

vector<int> vdatas{ 11,22,33,4,5 }; for (auto d : vdatas) { d = 100; } for (auto d : vdatas) { cout << d << " "; }

输出仍然是:

11 22 33 4 5

2.3 auto& d:引用原来的元素,可以修改 vector

如果想修改vector中原来的元素,需要使用引用:

for (auto& d : vdatas) { d = 100; }

这里:

auto& d

表示dvdatas中元素的引用。

修改d,就是修改原数组中的元素。

例如:

vector<int> vdatas{ 11,22,33,4,5 }; for (auto& d : vdatas) { d = 100; } for (auto d : vdatas) { cout << d << " "; }

输出结果:

100 100 100 100 100

所以:

  1. auto d 是复制元素;
  2. auto& d 是引用元素。

2.4 const auto& d:只读访问,避免复制

如果只是想读取元素,不想修改元素,可以写成:

for (const auto& d : vdatas) { cout << d << " "; }

这里:

const auto& d

表示只读引用

它有两个好处:

  1. 不会复制元素;
  2. 不能修改元素。

对于int这种小类型来说,复制成本很低,auto dconst auto& d性能差别不大。

因为:

int 通常是 4 字节; 指针在 64 位程序中通常是 8 字节。

但是如果vector里面存的是大对象,例如:

vector<string> vector<MyClass>

这时使用:

const auto& d

就可以避免每次遍历都复制一个大对象。

所以常见习惯是:

  1. 只读遍历大对象:const auto&
  2. 需要修改元素:auto&
  3. 小类型只读:auto 或 const auto& 都可以

三、vector 作为函数参数:传值会复制

3.1 vector 作为参数传值

先看一个函数:

vector<int> TestVector(vector<int> 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<int> vdatas

这是传值

也就是说,调用函数时,会把外面的vector复制一份给函数内部的vdatas


3.2 传值时,函数内部是新的 vector

调用代码:

vector<int> 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 不是同一个对象内部的数据空间。

也就是说:

vector<int> TestVector(vector<int> vdatas)

这种写法会复制一份vector数据。


3.3 vector 传值复制的是什么?

这里要分清楚两件事:

vector 对象本身 vector 内部管理的堆区数组

当写:

TestVector(vdatas);

因为参数是传值:

vector<int> vdatas

所以:

  1. 会创建一个新的vector对象。
  2. 这个新的vector对象内部,也会有自己的堆区数组空间。
  3. 因此原来的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不一样。

下面这种写法是可以的:

vector<int> TestVector(vector<int> vdatas) { return vdatas; }

原因是:

这里返回的是 vector 对象本身,不是返回局部对象的地址。

函数返回时,C++ 会把这个vector对象作为返回值交给外面。

现代 C++ 编译器通常会进行优化,例如:

返回值优化 移动构造

所以返回vector并不一定会真的完整复制一份所有元素。


3.5 为什么返回后的地址可能和函数内部一样?

在运行结果中可能看到:

vdatas :00000196F05251F0 rdatas :00000196F05251F0

也就是说:

函数内部 vdatas.data() 和 外面 rdatas.data() 打印出来的地址一样。

这是因为返回时可能发生了:

移动构造

也可以理解为:

函数内部 vdatas 管理的堆区数组,被移动给了外面的 rdatas。

这时不会重新复制 5 个元素,而是把内部数组资源转交给返回值对象。

所以:

return vdatas;
  1. 虽然返回的是局部对象,但这是安全的。
  2. 因为返回的不是局部对象地址,而是把对象的资源交给了外部对象。

四、move 移动语义:减少 vector 数据复制

4.1 std::move 的基本使用

如果想避免第一次传参时复制vector,可以使用:

move

代码示例:

vector<int> 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));

进入函数时,参数:

vector<int> vdatas

通过移动构造创建。

此时原来vdatas内部管理的堆区数组资源,可能会被直接转移给函数参数。

这样就不用复制所有元素了。


4.3 使用 move 后,原来的 vector 会怎么样?

看代码:

vector<int> 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 前后的地址变化

代码示例:

vector<int> 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的优点:

减少大对象复制; 避免大量元素重新分配和拷贝; 适合资源转移场景。

例如一个很大的vector

vector<int> bigData(1000000);

如果传值复制,可能要复制 100 万个int

如果使用移动语义,就可以只转移内部资源,效率更高。

但是move也有代价:

原来的对象资源被转移后,不应该继续依赖原来的数据。

例如:

auto mdatas = TestVector(move(vdatas));

这之后,不建议继续使用vdatas原来的元素内容。

可以重新给它赋值:

vdatas = { 1,2,3 };

或者重新添加元素:

vdatas.push_back(100);

这样是可以的。


五、vector 引用传参与返回值

5.1 vector 引用传参不会复制

如果不想复制vector,最常见的方法是引用传参

例如:

vector<int> TestVectorRef(vector<int>& 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; }

参数是:

vector<int>& vdatas

这表示:

vdatas 是外部 vector 的引用。

函数内部并没有创建新的vector数据副本。


5.2 引用传参时,函数内部地址不变

调用代码:

vector<int> 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

这个地址变了。

  1. 原因是函数返回值类型是:vector<int>
  2. 而不是:vector<int>&

所以:

return vdatas;

虽然vdatas是引用参数,但是返回时返回的是一个新的vector对象。

也就是说:

引用传参不复制; 但是按值返回会复制或移动生成新的返回对象。

5.3 如果只读,推荐 const vector<int>&

如果函数内部只是读取vector,不修改它,推荐写成:

void PrintVector(const vector<int>& vdatas) { for (const auto& d : vdatas) { cout << d << " "; } cout << endl; }

这里:

const vector<int>& vdatas

有两个好处:

不会复制 vector; 函数内部不能修改原 vector。

这在实际工程中非常常见。

例如:

void PrintVector(const vector<int>& vdatas);

表示这个函数只是查看数据,不会修改数据。


5.4 如果想返回引用,返回值也要写引用

如果确实想返回原来的vector,可以写成:

vector<int>& TestVectorRefReturn(vector<int>& vdatas) { return vdatas; }

调用:

vector<int> vdatas{ 11,22,33,4,5 }; auto& r = TestVectorRefReturn(vdatas); cout << r.data() << endl;

这里:

auto& r

也要写引用。

如果写成:

auto r = TestVectorRefReturn(vdatas);

那么还是会复制一份。

所以要记住:

函数返回引用,接收时也要用引用,才能继续保持引用关系。

但是返回引用也要注意生命周期。

可以返回:

  1. 外部传进来的 vector 引用;
  2. 全局 vector 引用;
  3. static vector 引用。

不能返回:

函数内部普通局部 vector 的引用。

错误示例:

vector<int>& Test() { vector<int> temp{ 1,2,3 }; return temp; // 错误,temp 离开函数后会销毁 }

这个问题和前面讲的“不能返回栈区变量的引用”本质是一样的。


六、完整代码示例

#include <iostream> #include <vector> #include <utility> using namespace std; // 函数与 vector 数组和引用 vector<int> TestVector(vector<int> 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++ 中通常会移动或优化 } vector<int> TestVectorRef(vector<int>& 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; // 返回值是 vector<int>,所以会产生新的返回对象 } void PrintVector(const vector<int>& vdatas) { for (const auto& d : vdatas) { cout << d << " "; } cout << endl; } int main() { vector<int> 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; vector<int> 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)vector<int> vdatasvector<int>不同通常和函数内部相同
move 传值TestVector(move(vdatas))vector<int> vdatasvector<int>通常相同通常和函数内部相同

引用传参,

按值返回

TestVectorRef(vdatas)vector<int>& vdatasvector<int>相同通常不同

引用传参,

返回引用

TestVectorRefReturn(vdatas)vector<int>& vdatasvector<int>&相同相同
  1. 普通传值:进函数会复制,所以 main 和函数内部地址不同;
  2. move 传值:资源被移动进去,所以 main 原地址、函数内部地址、返回地址通常相同;
  3. 引用传参:进函数不复制,所以 main 和函数内部地址相同;
  4. 引用传参但按值返回:return 会生成新对象,所以返回地址不同;
  5. 引用传参并返回引用:从头到尾都是同一个对象,所以三个地址都相同。
  1. 看参数有没有 &,决定进函数时是否复制;
  2. 看返回值有没有 &,决定 return 出来后是否还是同一个对象。

7.1 使用 vector 需要引入头文件

#include <vector>

如果使用move,建议引入:

#include <utility>

7.2 vector 是动态数组容器

定义:

vector<int> vdatas{ 11,22,33,4,5 };

可以简单理解为:

  1. vdatas 对象本身通常在栈区;
  2. 真正的元素数据通常在堆区;
  3. 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 auto&

for (const auto& d : vdatas)

这种写法既不会复制元素,也不会修改元素。

对于大对象更推荐这样写。


7.6 vector 传值会复制但是return vector和外面相同

vector<int> TestVector(vector<int> 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 引用传参不会复制并不代表返回也不复制

vector<int> TestVectorRef(vector<int>& vdatas)

这里参数是引用,函数内部操作的就是外面的vector

所以函数内部vdatas.data()和外部vdatas.data()一样。


如果函数返回值是:

vector<int>

那么:

return vdatas;

仍然会生成一个新的返回对象。

如果想返回引用,要写成:

vector<int>& Test(vector<int& vdatas)

正确写法是:

vector<int>& Test(vector<int>& vdatas) { return vdatas; }

并且接收时也要写:

auto& r = Test(vdatas);
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/1 13:37:11

KeyboardChatterBlocker:拯救机械键盘的智能防连击神器

KeyboardChatterBlocker&#xff1a;拯救机械键盘的智能防连击神器 【免费下载链接】KeyboardChatterBlocker A handy quick tool for blocking mechanical keyboard chatter. 项目地址: https://gitcode.com/gh_mirrors/ke/KeyboardChatterBlocker 还在为机械键盘的连击…

作者头像 李华
网站建设 2026/6/1 13:37:10

自然语言查询(NLQ)如何革新数据交互:从SQL到业务对话的演进

1. 项目概述&#xff1a;当自然语言成为数据查询的“母语” 作为一名和数据打了十几年交道的从业者&#xff0c;我经历过从命令行到图形界面&#xff0c;再到各种复杂查询语言的演变。最近几年&#xff0c;一个趋势越来越明显&#xff1a;用自然语言&#xff08;比如英语、中文…

作者头像 李华
网站建设 2026/6/1 13:36:11

怎么轻松提取Godot游戏资源:5分钟快速上手指南

怎么轻松提取Godot游戏资源&#xff1a;5分钟快速上手指南 【免费下载链接】godot-unpacker godot .pck unpacker 项目地址: https://gitcode.com/gh_mirrors/go/godot-unpacker 想要获取Godot引擎开发的游戏中的精美素材吗&#xff1f;godot-unpacker这款实用工具让你无…

作者头像 李华
网站建设 2026/6/1 13:33:08

90+格式全兼容:ImageGlass重新定义Windows图片浏览体验

90格式全兼容&#xff1a;ImageGlass重新定义Windows图片浏览体验 【免费下载链接】ImageGlass &#x1f3de; A fast, open-source, modern image viewer for 90 formats – including WEBP, GIF, SVG, AVIF, JXL, HEIC and more – built for smooth browsing across Windows…

作者头像 李华