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

  • 相关阅读:
    Mysql:为什么用limit时,offset很大会影响性能
    [解决方案]未能找到路径“~in oslyncsc.exe”的一部分
    [经验分享]NuGet发布自己的Dll(类库包)
    [解决方案]使用百度富文本编辑器,编辑显示不了内容
    [解决方案]未能加载文件或程序集
    [经验分享]WebApi+SwaggerUI 完美展示接口
    [经验分享]Linux网络连接-VMware+CentOS 7
    [经验分享]WebAPI中返回类型JsonMessage的应用
    [解决方案]WebAPI+SwaggerUI部署服务器后,访问一直报错的问题
    [解决方案] 当 IDENTITY_INSERT 设置为 OFF 时
  • 原文地址:https://www.cnblogs.com/zjutzz/p/6818799.html
Copyright © 2011-2022 走看看