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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM