几种传参方式简单对比
- 传值
1.1 参数形式:void fun(vector<int> v);
1.2 函数调用:fun(v);
1.3 函数内使用:cout << v[1];
1.4 是否可以改变函数外对象的值:否
1.5 是否会调用拷贝构造函数:是 - 传指针
2.1 参数形式:void fun(vector<int>* p);
2.2 函数调用:fun(&v);
2.3 函数内使用:cout << (*p)[1];
2.4 是否可以改变函数外对象的值:是
2.5 是否会调用拷贝构造函数:否 - const+传指针
3.1 参数形式:void fun(const vector<int>* p);
3.2 函数调用:fun(&v);
3.3 函数内使用:cout << (*p)[1];
3.4 是否可以改变函数外对象的值:否
3.5 是否会调用拷贝构造函数:否 - 传引用
4.1 参数形式:void fun(vector<int>& v);
4.2 函数调用:fun(v);
4.3 函数内使用:cout << v[1];
4.4 是否可以改变函数外对象的值:是
4.5 是否会调用拷贝构造函数:否 - const+传引用
5.1 参数形式:void fun(const vector<int>& v);
5.2 函数调用:fun(v);
5.3 函数内使用:cout << v[1];
5.4 是否可以改变函数外对象的值:否
5.5 是否会调用拷贝构造函数:否
结合代码对比
下面,我们来探讨,各种方式是否会修改到函数外部的v。
如果会,那我就把它定性为一种有风险的行为。如果不会,我就把它定性为一种安全的行为。
一、传值
#include <iostream>
#include<vector>void changeValue(std::vector<int> v) {//修改v里面的值v[0] = 2;std::cout << "修改值之后的第一个元素(函数内): " << v[0] << std::endl;
}
void changeReference(std::vector<int> v) {//修改v的指向std::vector<int> c;c.push_back(3);v = c;std::cout << "修改指向之后的第一个元素(函数内): " << v[0] << std::endl;
}int main()
{std::vector<int> v;v.push_back(1);std::cout << "原本的第一个元素: " << v[0] << std::endl;changeValue(v);std::cout << "修改值后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReference(v);std::cout << "修改指向之后的第一个元素(函数外): " << v[0] << std::endl;
}
可见,传值的话,如果在函数内修改了v的值或者指向,函数外的v也不会受到任何影响。因此是一种安全的行为。
二、传指针
#include <iostream>
#include<vector>void changeValue(std::vector<int>* p) {//修改v里面的值(*p)[0] = 2;std::cout << "修改值之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}
void changeReferenced(std::vector<int>* p) {//修改v的指向的指向std::vector<int> c;c.push_back(3);p = &c;std::cout << "修改指向之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}
void changeReferencedReference(std::vector<int>* p) {//修改v的指向std::vector<int> c;c.push_back(3);*p = c;std::cout << "修改指向的对象的指向之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}int main()
{std::vector<int> v;v.push_back(1);std::cout << "原本的第一个元素: " << v[0] << std::endl;changeValue(&v);std::cout << "修改值后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReferenced(&v);std::cout << "修改指向之后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReferencedReference(&v);std::cout << "修改指向的对象的指向之后的第一个元素(函数外): " << v[0] << std::endl;
}
- 由changeValue的结果可见,传指针的话,如果在函数内修改了p对应的元素的值,函数外的v会受到一样的影响;
- 由changeReferenced的结果可见,如果在函数内修改了p的指向,则函数外的v不会受到任何影响,因为此时的p已经不再关联外部的v了。
- 然而,由changeReferencedReference的结果可见,如果不是修改p的指向,而是修改了p指向的对象的指向,那么结果就会相反,此时函数外的v也会受到一样影响!
- 所有,传递指针,是有影响到外部的v的风险的,是一种有风险的行为。
三、const+传指针
#include <iostream>
#include<vector>void changeValue(const std::vector<int>* p) {//修改v里面的值(*p)[0] = 2;std::cout << "修改值之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}
void changeReferenced(const std::vector<int>* p) {//修改v的指向std::vector<int> c;c.push_back(3);p = &c;std::cout << "修改指向之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}
void changeReferencedReference(const std::vector<int>* p) {//修改v的指向的指向std::vector<int> c;c.push_back(3);*p = c;std::cout << "修改指向的对象的指向之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}int main()
{std::vector<int> v;v.push_back(1);std::cout << "原本的第一个元素: " << v[0] << std::endl;changeValue(&v);std::cout << "修改值后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReferenced(&v);std::cout << "修改指向之后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReferencedReference(&v);std::cout << "修改指向的对象的指向之后的第一个元素(函数外): " << v[0] << std::endl;
}
- 由图可知,我们无法修改p指向的容器的值,也无法修改p指向的容器的指向,因此这两种情况都不会导致外部的v被改变。
- 但是,我们此时可以修改p的指向,那么我们有没有可能改变函数外的v呢。由上一条实验三我们知道,修改了p的指向之后,并不会影响到外部的v,因为此时p已经脱离了和v的关联了。所以修改p的指向,是不会影响到外部的v的风险的。
- 综上,const+传指针的方式,是不会影响到外部的v的,是一种安全的行为。
四、传引用
#include <iostream>
#include<vector>void changeValue(std::vector<int>& v) {//修改v里面的值v[0] = 2;std::cout << "修改值之后的第一个元素(函数内): " << v[0] << std::endl;
}
void changeReference(std::vector<int>& v) {//修改v的指向std::vector<int> c;c.push_back(3);v = c;std::cout << "修改指向之后的第一个元素(函数内): " << v[0] << std::endl;
}int main()
{std::vector<int> v;v.push_back(1);std::cout << "原本的第一个元素: " << v[0] << std::endl;changeValue(v);std::cout << "修改值后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReference(v);std::cout << "修改指向之后的第一个元素(函数外): " << v[0] << std::endl;
}
可见,传引用的话,如果在函数内修改了v的值或者指向,函数外的v也会受到一样的影响。因此是一种有风险的行为。
五、const+传引用
#include <iostream>
#include<vector>void changeValue(const std::vector<int>& v) {//修改v里面的值v[0] = 2;std::cout << "修改值之后的第一个元素(函数内): " << v[0] << std::endl;
}
void changeReference(const std::vector<int>& v) {//修改v的指向std::vector<int> c;c.push_back(3);v = c;std::cout << "修改指向之后的第一个元素(函数内): " << v[0] << std::endl;
}int main()
{std::vector<int> v;v.push_back(1);std::cout << "原本的第一个元素: " << v[0] << std::endl;changeValue(v);std::cout << "修改值后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReference(v);std::cout << "修改指向之后的第一个元素(函数外): " << v[0] << std::endl;
}
如图,由于const,我们无法修改v的元素的值,也无法修改v的指向。所以我们不会改变到函数外的v。也是一种安全的行为。
总结
如果我们想要安全(不修改函数外的v的值),只有以下三种方式:
- 传值
- const+传指针
- const+传引用。
- 这几种方法中,如果从是否调用拷贝构造函数的角度考虑,应该选择 const+指针 ,或者const+引用因为他们是不调用拷贝构造函数的。
- 但是如果从书写的方便性来说,应该选择 传值 和 const+传引用 这两种方式,他们比const+传指针要更加便于书写,因为这两者只需要用v就可以代表传进来的vector对象,而const+传指针需要用(*p)的方式来代表,多了一个*,而且有时候还要加括号(),防止优先级有问题,比如要用[ ]取值的时候,必须写成类似(*p)[0],而不能是*p[0]这样的。
- 实际使用中,似乎更多人用 const+传引用 。
const+引用是否调用拷贝构造函数
文章 C++(笔记)容器(vector)作为函数参数如何传参 中却认为,const+传引用会调用拷贝构造函数。我认为这应该是错误的。因为大多数其他的文章都认为, const+传引用不会调用拷贝构造函数**。如 C++拷贝构造函数(复制构造函数)详解、C++ 中的 const & (常引用)参数 、C++ 基础之 “引用形参” 和 “利用const引用避免复制” & 等等。所以还是服从多数,const+引用不会调用拷贝构造函数。这也是它相比于直接传值的重要优势。
不调用拷贝构造函数的好处
具体可以学习C++拷贝构造函数(复制构造函数)详解这一篇文章。
我总结一下,好处可能有两个:
- 可以减少空间开销。
- 可以避免在类中定义了一个不好的拷贝构造函数,从而导致了形参和实参不一致。
补充:使用 const+引用/指针 比单独使用 引用/指针 作为参数的好处
- 更加安全。这一点毋庸置疑。加了const以后,不管是指针还是引用都不用担心更改到外部的对象的内容。
- 使得函数可以接受常量对象作为参数。除此之外,还有一个很重要的,就是,哪怕你在函数内仔细控制,保证不会修改引用/指针指向的外部对象,也建议加一个const。那是因为,这样就可以向函数传递常量对象,而直接使用 引用/指针 作为参数的话,是无法向函数传递常量对象的。(这里我还是参考了C++拷贝构造函数(复制构造函数)详解这篇文章,虽然文章里只是对拷贝构造函数的参数写法进行的建议,但是我认为道理其实是通用的)
如下,如果我使用 vector<int>& v作为参数(没有加const),那么我是无法传递一个常量vector对象进去的。
然而,如果我使用了 const vector<int>& v作为参数(加了const),我就可以传递一个常量vector对象。
#include <iostream>
#include<vector>void printValue(const std::vector<int>& v) {std::cout << v[0];
}int main()
{const std::vector<int> v = { 1, 2, 3 };printValue(v);
}
可以接受常量对象的话,整个函数的可用性会更加强。
参考链接
C++拷贝构造函数(复制构造函数)详解
C++中vector作为参数的三种传参方式(传值 && 传引用 && 传指针)
C++ 中的 const & (常引用)参数
C++ 基础之 “引用形参” 和 “利用const引用避免复制” &
C++(笔记)容器(vector)作为函数参数如何传参 (疑似有误)