一、作为参数
1、值传递
#include <iostream>
using namespace std;
void swap(int a, int b) {
cout << __FUNCTION__ << "交换前a:" << a << " b:" << b << endl;
int tmp = a;
a = b;
b = tmp;
cout << __FUNCTION__ << "交换后a:" << a << " b:" << b << endl;
}
int main() {
int x = 0;
int y = 1;
cout << __FUNCTION__ << "交换前x:" << x << " y:" << y << endl;
swap(x, y);
cout << __FUNCTION__ << "交换后x:" << x << " y:" << y << endl;
return 0;
}
2、地址传递
#include <iostream>
using namespace std;
void swap(int* a, int* b) {
cout << __FUNCTION__ << "交换前a:" << *a << " b:" << *b << endl;
int tmp = *a;
*a = *b;
*b = tmp;
cout << __FUNCTION__ << "交换后a:" << *a << " b:" << *b << endl;
}
int main() {
int x = 0;
int y = 1;
cout << __FUNCTION__ << "交换前x:" << x << " y:" << y << endl;
swap(&x, &y);
cout << __FUNCTION__ << "交换后x:" << x << " y:" << y << endl;
return 0;
}
3、引用传递
#include <iostream>
using namespace std;
void swap(int& a, int& b) {
cout << __FUNCTION__ << "交换前a:" << a << " b:" << b << endl;
int tmp = a;
a = b;
b = tmp;
cout << __FUNCTION__ << "交换后a:" << a << " b:" << b << endl;
}
int main() {
int x = 0;
int y = 1;
cout << __FUNCTION__ << "交换前x:" << x << " y:" << y << endl;
swap(x, y);
cout << __FUNCTION__ << "交换后x:" << x << " y:" << y << endl;
return 0;
}
二、作为返回值
1、值作为返回值
int foo(){
int a = 2;
return a;
}
int main(){
int d = foo();
}
2、指针作为返回值
int* foo(){
int a = 2;
return &a;
}
int main(){
int* d = foo();
cout << *d;//悬空指针不能解引用
}
3、引用作为返回值
int& foo(){
int a = 2;
return a;
}
int main(){
int d = foo();
cout << d;//悬空引用不能访问
}
3、使用情形
总结一下前面情形。
作为函数参数 | 作为函数返回值 | |
---|---|---|
值 | 有复制操作 。不会改变实参 。 | 有复制操作,可以将栈空间内容拷贝一份,将出栈要销毁的内容保存下来 。 |
指针 | 有复制操作,但固定指针大小 。会改变实参 | 有复制操作,只是拷贝了地址,但内容出栈销毁,会导致悬空指针 |
引用 | 无复制操作 ,相当于直接操作实参。会改变实参 。 | 无复制操作,直接为栈空间内容取别名,但内容出栈销毁,会导致悬空引用 |
(1)值传递场景
考虑到值传递会进行复制操作,所以如果参数是基本类型
,其复制开销较小时可以考虑使用,但还是尽量使用引用
。值传递不会改变实参的值,当实参不希望被函数篡改
时可使用,但更常用的应该是使用const引用
,确保不能修改,修改时会报错。
int add(int a, int b){
a += b;
return a;
}
int sub(int a, int b){
a += 2;
return a;
}
int main(){
int a = 5;
int add = add(a, 3); // 我们只是想知道5+3、和5-3是多少,如果a进行地址传递或引用传递,经过add后,a的值会变为8,导致后面不能得到预期的5-3,而是8-3
int sub = sub(a, 3);
}
(2)地址传递、引用传递场景
地址传递复制开销为8字节,引用无复制,所以这两种方式在传递数组,字符串及自定义类型等复制开销大的类型参数
时效率很高。因为这两种方式都会改变函数实参,所以在希望在函数内部修改实参
的时候可以使用,它们也可以用来返回多个值
。另外,引用使用起来比指针简单,所以尽量用引用。
void minmax(int a, int b, int& min, int& max){
min = a>b?b:a;
max = a<b?b:a;
}
int main(){
int min = 0, max = 0;
minmax(5, 6, min,max);
cout << min << " " << max << endl;
}
(3)值作为返回值场景
值作为返回值最大的好处就是可以保存出栈要销毁的内容(使用移动语义
也可做到),又因其复制开销大,基本就用于返回基本类型。
(4)指针作为返回值场景
指针作为返回值最大的弊端是会导致悬空指针
,但是如果在函数中是利用new动态分配较大内存空间
时,那么指针指向的内容在堆区,出栈不会销毁,不会导致悬空指针,这时受益于指针复制开销小,使用指针作为返回值还是比较合适。但是需要显式管理内存来避免内存泄漏
。
(5)引用作为返回值场景
指针作为返回值最大的弊端是会导致悬空引用
,所以要求返回的对象生命周期长于函数
,因为无需复制对象,所以返回复制开销大的对象就非常适用了。