Bootstrap

C++函数的参数传递

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;
}

 

;