1、三种参数传递方式:
1)值传递:
形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。默认是值传递。
2)指针传递:
形参也是实参的拷贝,只不过拷贝的是实参的地址。当在函数内对形参的指向操作时,就相当于对实参本身进行的操作。
- 传递参数时:通过取地址运算符(&)对实参取地址,或定义一个指针变量;
- 在函数中:必须要通过寻址运算符(*)对指针形参进行解引用,除非你有其他的用途(如:指针的指针,指向数组的指针...)
2)引用传递:
传地址(指针)已经可以解决所有传参问题了,但是为了更加简洁,出现了引用传递。引用传递有个约定:
- 形参的类型要指定成地址(引用类型);
- 在调用函数传参时,实参不需要指定地址,语法上还是按照值传递的方式,编译期会根据形参类型,自动将实参的地址传递给形参;(调用上和值传递一样)
- 在函数中,可以直接对形参进行操作;(使用上和值传递一样)
看一个例子:
#include <iostream>
using namespace std;
void swap_1(int a,int b);
void swap_2(int& a,int& b);
void swap_3(int* a,int* b);
main() {
int a=1,b=2;
cout<<"before swap a,b:"<<a<<","<<b<<endl;
swap_1(a,b);
cout<<"after swap a,b:"<<a<<","<<b<<endl;
swap_2(a,b);
cout<<"after swap a,b:"<<a<<","<<b<<endl;
swap_3(&a,&b);
cout<<"after swap a,b:"<<a<<","<<b<<endl;
}
//值传递
void swap_1(int a,int b) {
int tmp = a;
a = b;
b = tmp;
cout<<"swap_1,a:"<<a<<",b:"<<b<<endl;
}
//引用传递
void swap_2(int& a,int& b) {
int tmp = a;
a = b;
b = tmp;
cout<<"swap_2,a:"<<a<<",b:"<<b<<endl;
}
//指针传递
void swap_3(int* a,int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
cout<<"swap_3,a:"<<*a<<",b:"<<*b<<endl;
}
输出:后两种都可以交换变量;
2、按址传递
2.1)三种传参区别:
- 无论你传值还是传指针,函数都会生成一个临时变量(复制值或地址);但传引用时不会生成临时变量(效率更高);
- 传递指针,在函数体内只能修改指针的内容(通过*解引用),对指针本身的修改不起任何作用(在方法里做修改只是修改的指针的copy而不是指针本身,原来的指针还保留着原来);
- 传递指针,如果形参是const type *参数 ,在函数中是不能通过解引用来修改指针内容的,否则编译时会报错;(只读的)
- 可以通过传递指针的引用、指针的指针,来做到修改指针的内容,也可以修改指针本身;
1)示例:
#include <iostream>
using namespace std;
void change_1(int &a);
void change_2(int *a);
void change_3(int *a);
void change_4(const int *a);
main() {
int a=1;
cout<<"before change a:"<<a<<endl;
//change_1(a);
//change_2(&a);
//change_3(&a);
change_4(&a);
cout<<"after swap a:"<<a<<endl;
}
//引用传递
void change_1(int& a) {
a = a+100;
cout<<"change_1,a:"<<a<<endl;
}
//指针传递
void change_2(int *a) {
*a = *a + 600;
cout<<"change_2,a:"<<*a<<endl;
}
//指针传递
void change_3(int *a) {
cout<<"change_3,a:"<<a<<endl;
a = a+100;//直接对指针操作,不会有任何变化
cout<<"change_3,a:"<<a<<endl;
}
//const指针传递
void change_4(const int *a){
cout<<"change_4,a:"<<a<<endl;
a = a +100;//对指针本身操作不会报错
cout<<"change_4,a:"<<a<<endl;
*a = *a + 2000;//对指针内容做修改,编译时报错
}
注意:指针传递和引用传递本质上是一致的,他们都可以成为按址传递,后者是前者的一种简化;
2)为什么要用址传递?
- 数据可以双向传递;
- 提高效率,例如传递一个大的对象,或者数组,采用值传递会拷贝,消耗资源,采用址传递只需要传递首地址;
具体我们来看一下,按照传值传递对象、传址传递对象:
A、按值传递对象:函数的参数采用按值传递一个对象,会调用复制构造函数和析构函数
- 在传递的过程中会默认调用拷贝构造函数,创建一个临时某个对象的临时副本;
- 在函数返回时,传递该对象时创建的副本会被删除,会自动调用其析构函数释放内存;
注:函数调用采用值传递对象时,会建立一个该对象的拷贝(调用复制构造函数);函数返回一个对象时,也要建立这个被返回的对象的一个拷贝。
B、按址传递对象:函数参数采用按址传递一个对象,不会调用对象的复制构造函数和析构函数,减小内存开销。
- 按值传递对象虽然比较慢,但好处是实参不会被函数内部修改;按址传递对象,由于数据可以双向传递,有一定的风险;
- 使用const指针可以防止任何试图对该对象所进行的操作行为,并且保证返回一个不被修改的对象。
2.2)指针的引用、指针的指针进行参数传递:
#include <iostream>
using namespace std;
int m_value=11;
//指针的指针
void func(int **p) {
*p = &m_value;
//也可以根据你的需求分配内存
*p = new int;
**p = 5;
}
//指针的引用
void func2(int *&p) {
p = &m_value;
//也可以根据你的需求分配内存
p = new int;
*p = 6;
}
main(){
int n = 1;
int *p = &n;
cout<<*p<<endl;
//func(&p);
func2(p);
cout<<*p<<endl;
}