zoukankan      html  css  js  c++  java
  • C++传值、传引用

    C++传值、传引用

    C++的函数参数传递方式,可以是传值方式,也可以是传引用方式。传值的本质是:形参是实参的一份复制。传引用的本质是:形参和实参是同一个东西。

    传值和传引用,对大多数常见类型都是适用的(就我所知)。指针、数组,它们都是数据类型的一种,没啥特殊的,因此指针作为函数参数传递时,也区分为传值和传引用两种方式。

    e.g.

    void fun_1(int a);    //int类型,传值(复制产生新变量)
    void fun_2(int& a);   //int类型,传引用(形参和实参是同一个东西)
    void fun_3(int* arr); //指针类型,传值(复制产生新变量)
    void func_4(int*& arr); //指针类型,传引用(形参和实参是同一个东西)
    

    如果希望通过将参数传递到函数中,进而改变变量的值(比如变量是T a,T表示类型),则可以有这2种方式选择:

    1. 传a的引用:void myfun(T& a)
    2. 传a的地址的值:void myfun(T* a)

    传值方式

    这是最简单的方式。形参意思是被调用函数的参数/变量,实参意思是主调函数中放到括号中的参数/变量。传值方式下,形参是实参的拷贝:重新建立了变量,变量取值和实参一样。

    写一段测试代码,并配合gdb查看:

    test.cc

    #include <iostream>
    using namespace std;
    
    void swap(int a, int b){
        int temp;
        temp = a;
        a = b;
        b = temp;
        cout << a << " " << b << endl;
    }
    
    int main(){
        int x = 1;
        int y = 2;
        swap(x, y);
        cout << x << " " << y << endl;
    
        return 0;
    }
    
    ➜  hello-cpp git:(master) ✗ g++ -g test.cc
    ➜  hello-cpp git:(master) ✗ gdb a.out 
    (gdb) b main
    Breakpoint 1 at 0x4008fa: file test.cc, line 13.
    (gdb) r
    Starting program: /home/chris/work/hello-cpp/a.out 
    
    Breakpoint 1, main () at test.cc:13
    13          int x = 1;
    (gdb) s
    14          int y = 2;
    (gdb) p &x
    $1 = (int *) 0x7fffffffdc58
    (gdb) p &y
    $2 = (int *) 0x7fffffffdc5c
    (gdb) s
    15          swap(x, y);
    (gdb) s
    swap (a=1, b=2) at test.cc:6
    6           temp = a;
    (gdb) p &a
    $3 = (int *) 0x7fffffffdc2c
    (gdb) p &b
    $4 = (int *) 0x7fffffffdc28
    (gdb) 
    

    可以看到,实参x和y的值为1和2,形参a和b的值都是1和2;而x与a的地址、y与b的地址,并不相同,表明形参a和b是新建里的变量,也即实参是从形参复制了一份。这就是所谓的传值

    传指针?其实还是传值!

    test2.cc

    #include <iostream>
    using namespace std;
    
    void test(int *p){
        int a = 1;
        p = &a;
        cout << p << " " << *p << endl;
    }
    
    int main(void){
        int *p = NULL;
        test(p);
        if(p==NULL){
            cout << "指针p为NULL" << endl;
        }
        return 0;
    }
    

    这次依然用gdb调试(不用gdb也可以,直接看运行结果):

    ➜  hello-cpp git:(master) ✗ g++ -g test2.cc 
    ➜  hello-cpp git:(master) ✗ gdb a.out 
    (gdb) b main
    Breakpoint 1 at 0x4009e0: file test2.cc, line 11.
    (gdb) r
    Starting program: /home/chris/work/hello-cpp/a.out 
    
    Breakpoint 1, main () at test2.cc:11
    11          int *p = NULL;
    (gdb) s
    12          test(p);
    (gdb) p p
    $1 = (int *) 0x0
    (gdb) p &p
    $2 = (int **) 0x7fffffffdc58
    (gdb) s
    test (p=0x0) at test2.cc:4
    4       void test(int *p){
    (gdb) s
    5           int a = 1;
    (gdb) p p
    $3 = (int *) 0x0
    (gdb) p &p
    $4 = (int **) 0x7fffffffdc18
    (gdb) 
    

    可以看到,main()函数内和test()函数内,变量p的值都是0,也就是都是空指针;但是它们的地址是不同的。也就是说,形参p只是从形参p那里复制了一份值(空指针的取值),形参是新创建的变量。

    直接运行程序的结果也表明了这一点:

    ➜  hello-cpp git:(master) ✗ ./a.out 
    0x7fff2a329e24 1
    指针p为NULL
    

    传引用

    传值是C和C++都能用的方式。传引用则是C++比C所不同的地方。传引用,传递的是实参本身,而不是实参的一个拷贝,形参的修改就是实参的修改。相比于传值,传引用的好处是省去了复制,节约了空间和时间。假如不希望修改变量的值,那么请选择传值而不是传引用。

    test3.cc

    #include <iostream>
    using namespace std;
    
    void test(int &a){
        cout << &a << " " << a << endl;
    }
    
    int main(void){
        int a = 1;
        cout << &a << " " << a << endl;
        test(a);
        return 0;
    }
    

    再次开gdb调试(依然是多此一举的gdb...直接运行a.out看结果就可以):

    ➜  hello-cpp git:(master) ✗ g++ -g test3.cc
    ➜  hello-cpp git:(master) ✗ gdb a.out 
    (gdb) b main
    Breakpoint 1 at 0x4009af: file test3.cc, line 8.
    (gdb) r
    Starting program: /home/chris/work/hello-cpp/a.out 
    
    Breakpoint 1, main () at test3.cc:8
    8       int main(void){
    (gdb) s
    9           int a = 1;
    (gdb) s
    10          cout << &a << " " << a << endl;
    (gdb) s
    0x7fffffffdc44 1
    11          test(a);
    (gdb) s
    test (a=@0x7fffffffdc44: 1) at test3.cc:5
    5           cout << &a << " " << a << endl;
    (gdb) s
    0x7fffffffdc44 1
    6       }
    (gdb) 
    
    

    直接运行./a.out的结果:

    ➜  hello-cpp git:(master) ✗ ./a.out
    0x7ffec97399e4 1
    0x7ffec97399e4 1
    

    显然,形参a和实参a完全一样:值相同,地址也相同。说明形参不是实参的拷贝,而是就是实参本身。

    简单实践-实现swap()函数

    swap()函数用来交换两个数字。根据前面一节的分析和测试,可以知道,既可以用传值的方式(也即传指针)来实现,也可以用传引用的方式来实现。

    代码如下:

    myswap.cc

    #include <iostream>
    using namespace std;
    
    void myswap_pass_by_reference(int& a, int &b){
        int t = a;
        a = b;
        b = t;
    }
    
    void myswap_pass_by_pointer_value(int* a, int* b){
        int t = *a;
        *a = *b;
        *b = t;
    }
    
    int main(){
        int a=1, b=2;
        cout << "originally" << endl;
        cout << "a=" << a << ", b=" << b << endl;
    
        myswap_pass_by_reference(a, b);
        cout << "after myswap_pass_by_reference" << endl;
        cout << "a=" << a << ", b=" << b << endl;
    
        myswap_pass_by_pointer_value(&a, &b);
        cout << "after myswap_pass_by_pointer_value" << endl;
        cout << "a=" << a << ", b=" << b << endl;
    
        return 0;
    
    }
    

    程序执行结果:

    originally
    a=1, b=2
    after myswap_pass_by_reference
    a=2, b=1
    after myswap_pass_by_pointer_value
    a=1, b=2
    

    真的理解了吗?

    其实出问题最多的还是指针相关的东西。指针作为值传递是怎样用的?指针作为引用传递又是怎样用的?

    首先要明确,“引用”类型变量的声明方式:变量类型 & 变量名
    “指针”类型的声明方式:基类型* 变量名
    所以,“指针的引用类型”应当这样声明:基类型*& 变量名

    这样看下来,不要把指针类型看得那么神奇,而是把它看成一种数据类型,那么事情就简单了:指针类型,也是有传值、传引用两种函数传参方式的。

    指针的传值

    void myfun(int* a, int n)
    

    指针的传引用

    void myfun(int*& arr, int n)
    

    update

    考虑这样一个问题:写一个函数,遍历输出一个一维数组的各个元素。

    第一种方法,数组退化为指针,传值。同时还需要另一个参数来指定数组长度:

    void traverse_1d_array(int* arr, int n){
    	...
    }
    

    缺点是需要指定n的大小。以及,传值会产生复制,如果大量执行这个函数会影响性能。

    另一种方式,传入参数是数组的引用。想到的写法,需要事先知道数组长度:

    void traverse_1d_array(int (&arr)[10]){
    	...
    }
    

    缺点是需要在函数声明的时候就确定好数组的长度。这很受限。

    还有一种方法。使用模板函数,来接受任意长度的数组:

    template <size_t size>
    void fun(int (&arr)[size]){
    	...
    }
    

    这种使用模板声明数组长度的方式很方便,当调用函数时,编译器从数组实参计算出数组长度。也就是说,不用手工指定数组长度,让编译器自己去判断。这很方便啊。用这种方式,随手写一个2维数组的遍历输出函数:

    template<size_t m, size_t n>
    void traverse_array_2d(int (&arr)[m][n]){
    	for(int i=0; i<m; i++){
    		for(int j=0; j<n; j++){
    			cout << arr[i][j] << ",";
    		}
    		cout << endl;
    	}
    }
    

    总结一下

    普通类型,以int a为例:

    void myfun(int a)    //传值,产生复制
    void myfun(int& a)	 //传引用,不产生复制
    void myfun(int* a)   //传地址,产生复制,本质上是一种传值,这个值是地址
    

    指针类型,以int* a为例:

    void myfun(int* a)   //传值,产生复制
    void myfun(int*& a)  //传引用,不产生复制
    void myfun(int** a)   //传地址,产生复制,本质上是一种传值,这个值是指针的地址
    

    数组类型,以int a[10]为例:

    void myfun(int a[], int n) //传值,产生复制
    void myfun(int* a, int n) //传值,产生复制,传递的数组首地址
    void myfun(int (&arr)[10]) //传引用,不产生复制。需要硬编码数组长度
    template<size_t size> void myfun(int (&arr)[size]) //传引用,不产生复制。不需要硬编码数组长度
    

    reference

    http://www.cnblogs.com/dolphin0520/archive/2011/04/03/2004869.html

    http://www.cnblogs.com/yjkai/archive/2011/04/17/2018647.html

    http://bbs.csdn.net/topics/390362450

  • 相关阅读:
    【leetcode】1295. Find Numbers with Even Number of Digits
    【leetcode】427. Construct Quad Tree
    【leetcode】1240. Tiling a Rectangle with the Fewest Squares
    【leetcode】1292. Maximum Side Length of a Square with Sum Less than or Equal to Threshold
    【leetcode】1291. Sequential Digits
    【leetcode】1290. Convert Binary Number in a Linked List to Integer
    【leetcode】1269. Number of Ways to Stay in the Same Place After Some Steps
    【leetcode】1289. Minimum Falling Path Sum II
    【leetcode】1288. Remove Covered Intervals
    【leetcode】1287. Element Appearing More Than 25% In Sorted Array
  • 原文地址:https://www.cnblogs.com/zjutzz/p/6818799.html
Copyright © 2011-2022 走看看