C語言中函數參數傳遞的三種方式
(1)值傳遞,就是把你的變量的值傳遞給函數的形式參數,實際就是用變量的值來新生成一個形式參數,因而在函數里對形參的改變不會影響到函數外的變量的值。
(2)地址傳遞,就是把變量的地址賦給函數里形式參數的指針,使指針指向真實的變量的地址,因為對指針所指地址的內容的改變能反映到函數外,能改變函數外的變量的值。
(3)引用傳遞,實際是通過指針來實現的,能達到使用的效果如傳址,可是使用方式如傳值。
說幾點建議:如果傳值的話,會生成新的對象,花費時間和空間,而在退出函數的時候,又會銷毀該對象,花費時間和空間。
因而如果int,char等固有類型,而是你自己定義的類或結構等,都建議傳指針或引用,因為他們不會創建新的對象。
例1:下面這段代碼的輸出結果為:
#include<stdio.h> void change(int*a, int&b, int c) { c=*a; b=30; *a=20; } int main ( ) { int a=10, b=20, c=30; change(&a,b,c); printf(“%d,%d,%d,”,a,b,c); return 0; }
結果:20 30 30
解析:
1,指針傳參 -> 將變量的地址直接傳入函數,函數中可以對其值進行修改。
2,引用傳參 -> 將變量的引用傳入函數,效果和指針相同,同樣函數中可以對其值進行修改。
3,值傳參 -> 在傳參過程中,首先將c的值復制給函數c變量,然后在函數中修改的即是函數的c變量,然后函數返回時,系統自動釋放變量c。而對main函數的c沒有影響。
例2:
#include<stdio.h> void myswap(int x, int y) { int t; t=x; x=y; y=t; } int main() { int a, b; printf("請輸入待交換的兩個整數:"); scanf("%d %d", &a, &b); myswap(a,b); //作為對比,直接交換兩個整數,顯然不行 printf("調用交換函數后的結果是:%d 和 %d\n", a, b); return 0; } #include<stdio.h> void myswap(int *p1, int *p2) { int t; t=*p1; *p1=*p2; *p2=t; } int main() { int a, b; printf("請輸入待交換的兩個整數:"); scanf("%d %d", &a, &b); myswap(&a,&b); //交換兩個整數的地址 printf("調用交換函數后的結果是:%d 和 %d\n", a, b); return 0; } #include<stdio.h> void myswap(int &x, int &y) { int t; t=x; x=y; y=t; } int main() { int a, b; printf("請輸入待交換的兩個整數:"); scanf("%d %d", &a, &b); myswap(a,b); //直接以變量a和b作為實參交換 printf("調用交換函數后的結果是:%d 和 %d\n", a, b); return 0; }
第一個的運行結果:輸入2 3,輸出2 3
第二個的運行結果:輸入2 3,輸出3 2
第三個的運行結果:輸入2 3,輸出3 2
解析:
在第一個程序中,傳值不成功的原因是指在形參上改變了數值,沒有在實參上改變數值。
在第二個程序中,傳地址成功的原因利用指針改變了原來的地址,所以實參就交換了。
在第三個程序中,引用是直接改變兩個實參變量a,b的值,所以就交換了。
////////////////////////////////////////////////////////////////////////////
下文會通過例子詳細說明關於值傳遞,指針傳遞,引用傳遞
1)值傳遞:
形參是實參的拷貝,改變形參的值並不會影響外部實參的值。從被調用函數的角度來說,值傳遞是單向的(實參->形參),參數的值只能傳入,
不能傳出。當函數內部需要修改參數,並且不希望這個改變影響調用者時,采用值傳遞。
2)指針傳遞:
形參為指向實參地址的指針,當對形參的指向操作時,就相當於對實參本身進行的操作
3)引用傳遞:
形參相當於是實參的“別名”,對形參的操作其實就是對實參的操作,在引用傳遞過程中,被調函數的形式參數雖然也作為局部變量在棧中開辟了內存空間,但是這時存放的是由主調函數放進來的實參變量的地址。被調函數對形參的任何操作都被處理成間接尋址,即通過棧中存放的地址訪問主調函數中的實參變量。正因為如此,被調函數對形參做的任何操作都影響了主調函數中的實參變量。
下面的代碼對此作出了細致解釋(從實參,形參在內存中存放地址的角度 說明了問題的本質,容易理解 )
#include<iostream> using namespace std; //值傳遞 void change1(int n){ cout<<"值傳遞--函數操作地址"<<&n<<endl; //顯示的是拷貝的地址而不是源地址 n++; } //引用傳遞 void change2(int & n){ cout<<"引用傳遞--函數操作地址"<<&n<<endl; n++; } //指針傳遞 void change3(int *n){ cout<<"指針傳遞--函數操作地址 "<<n<<endl; *n=*n+1; } int main(){ int n=10; cout<<"實參的地址"<<&n<<endl; change1(n); cout<<"after change1() n="<<n<<endl; change2(n); cout<<"after change2() n="<<n<<endl; change3(&n); cout<<"after change3() n="<<n<<endl; return true; }
運行結果如下,(不同的機器可能會有所差別)
可以看出,實參的地址為0x22ff44
采用值傳遞的時候,函數操作的地址是0x22ff20並不是實參本身,所以對它進行操作並不能改變實參的值
再看引用傳遞,操作地址就是實參地址 ,只是相當於實參的一個別名,對它的操作就是對實參的操作
接下來是指針傳遞,也可發現操作地址是實參地址
那么,引用傳遞和指針傳遞有什么區別嗎?
引用的規則:
引用被創建的同時必須被初始化(指針則可以在任何時候被初始化)。
不能有NULL引用,引用必須與合法的存儲單元關聯(指針則可以是NULL)。
一旦引用被初始化,就不能改變引用的關系(指針則可以隨時改變所指的對象)。
指針傳遞的實質:
指針傳遞參數本質上是值傳遞的方式,它所傳遞的是一個地址值。值傳遞過程中,被調函數的形式參數作為被調函數的局部變量處理,即在棧中開辟了內存空間以存放由主調函數放進來的實參的值,從而成為了實參的一個副本。值傳遞的特點是被調函數對形式參數的
任何操作都是作為局部變量進行,不會影響主調函數的實參變量的值。(這里是在說實參指針本身的地址值不會變)如果理解不了大可跳過這段
指針傳遞和引用傳遞一般適用於:
函數內部修改參數並且希望改動影響調用者。對比指針/引用傳遞可以將改變由形參“傳給”實參(實際上就是直接在實參的內存上修改,不像值傳遞將實參的值拷貝到另外的內存地址中才修改)。
另外一種用法是:當一個函數實際需要返回多個值,而只能顯式返回一個值時,可以將另外需要返回的變量以指針/引用傳遞給函數,這樣在函數內部修改並且返回后,調用者可以拿到被修改過后的變量,也相當於一個隱式的返回值傳遞吧。
以下是我覺得關於指針和引用寫得很不錯的文章,大家可參照看一下,原文出處地址:http://xinklabi.iteye.com/blog/653643
從概念上講。指針從本質上講就是存放變量地址的一個變量,在邏輯上是獨立的,它可以被改變,包括其所指向的地址的改變和其指向的地址中所存放的數據的改變。
而引用是一個別名,它在邏輯上不是獨立的,它的存在具有依附性,所以引用必須在一開始就被初始化,而且其引用的對象在其整個生命周期中是不能被改變的(自始至終只能依附於同一個變量)。
在C++中,指針和引用經常用於函數的參數傳遞,然而,指針傳遞參數和引用傳遞參數是有本質上的不同的:
指針傳遞參數本質上是值傳遞的方式,它所傳遞的是一個地址值。值傳遞過程中,被調函數的形式參數作為被調函數的局部變量處理,即在棧中開辟了內存空間以存放由主調函數放進來的實參的值,從而成為了實參的一個副本。值傳遞的特點是被調函數對形式參數的任何操作都是作為局部變量進行,不會影響主調函數的實參變量的值。(這里是在說實參指針本身的地址值不會變)
而在引用傳遞過程中,被調函數的形式參數雖然也作為局部變量在棧中開辟了內存空間,但是這時存放的是由主調函數放進來的實參變量的地址。被調函數對形參的任何操作都被處理成間接尋址,即通過棧中存放的地址訪問主調函數中的實參變量。正因為如此,被調函數對形參做的任何操作都影響了主調函數中的實參變量。
引用傳遞和指針傳遞是不同的,雖然它們都是在被調函數棧空間上的一個局部變量,但是任何對於引用參數的處理都會通過一個間接尋址的方式操作到主調函數中的相關變量。而對於指針傳遞的參數,如果改變被調函數中的指針地址,它將影響不到主調函數的相關變量。如果想通過指針參數傳遞來改變主調函數中的相關變量,那就得使用指向指針的指針,或者指針引用。
為了進一步加深大家對指針和引用的區別,下面我從編譯的角度來闡述它們之間的區別:
程序在編譯時分別將指針和引用添加到符號表上,符號表上記錄的是變量名及變量所對應地址。指針變量在符號表上對應的地址值為指針變量的地址值,而引用在符號表上對應的地址值為引用對象的地址值。符號表生成后就不會再改,因此指針可以改變其指向的對象(指針變量中的值可以改),而引用對象則不能修改。
最后,總結一下指針和引用的相同點和不同點:
1)相同點:
都是地址的概念;
指針指向一塊內存,它的內容是所指內存的地址;而引用則是某塊內存的別名。
2)不同點:
指針是一個實體,而引用僅是個別名;
引用只能在定義時被初始化一次,之后不可變;指針可變;引用“從一而終”,指針可以“見異思遷”;
引用沒有const,指針有const,const的指針不可變;(具體指沒有int& const a這種形式,而const int& a是有 的, 前者指引用本身即別名不可以改變,這是當然的,所以不需要這種形式,后者指引用所指的值不可以改變)
引用不能為空,指針可以為空;
“sizeof 引用”得到的是所指向的變量(對象)的大小,而“sizeof 指針”得到的是指針本身的大小;
指針和引用的自增(++)運算意義不一樣;
引用是類型安全的,而指針不是 (引用比指針多了類型檢查)
一、引用的概念
引用引入了對象的一個同義詞。定義引用的表示方法與定義指針相似,只是用&代替了*。
例如: Point pt1(10,10);
Point &pt2=pt1; 定義了pt2為pt1的引用。通過這樣的定義,pt1和pt2表示同一對象。
需要特別強調的是引用並不產生對象的副本,僅僅是對象的同義詞。因此,當下面的語句執行后:
pt1.offset(2,2);
pt1和pt2都具有(12,12)的值。
引用必須在定義時馬上被初始化,因為它必須是某個東西的同義詞。你不能先定義一個引用后才
初始化它。例如下面語句是非法的:
Point &pt3;
pt3=pt1;
那么既然引用只是某個東西的同義詞,它有什么用途呢?
下面討論引用的兩個主要用途:作為函數參數以及從函數中返回左值。
二、引用參數
1、傳遞可變參數
傳統的c中,函數在調用時參數是通過值來傳遞的,這就是說函數的參數不具備返回值的能力。
所以在傳統的c中,如果需要函數的參數具有返回值的能力,往往是通過指針來實現的。比如,實現
兩整數變量值交換的c程序如下:
一、引用的概念
引用引入了對象的一個同義詞。定義引用的表示方法與定義指針相似,只是用&代替了*。
例如: Point pt1(10,10);
Point &pt2=pt1; 定義了pt2為pt1的引用。通過這樣的定義,pt1和pt2表示同一對象。
需要特別強調的是引用並不產生對象的副本,僅僅是對象的同義詞。因此,當下面的語句執行后:
pt1.offset(2,2);
pt1和pt2都具有(12,12)的值。
引用必須在定義時馬上被初始化,因為它必須是某個東西的同義詞。你不能先定義一個引用后才
初始化它。例如下面語句是非法的:
Point &pt3;
pt3=pt1;
那么既然引用只是某個東西的同義詞,它有什么用途呢?
下面討論引用的兩個主要用途:作為函數參數以及從函數中返回左值。
二、引用參數
1、傳遞可變參數
傳統的c中,函數在調用時參數是通過值來傳遞的,這就是說函數的參數不具備返回值的能力。
所以在傳統的c中,如果需要函數的參數具有返回值的能力,往往是通過指針來實現的。比如,實現
兩整數變量值交換的c程序如下:
void swapint(int *a,int *b) { int temp; temp=*a; *a=*b; *b=temp; }
使用引用機制后,以上程序的c++版本為:
void swapint(int &a,int &b) { int temp; temp=a; a=b; b=temp; }
調用該函數的c++方法為:swapint(x,y); c++自動把x,y的地址作為參數傳遞給swapint函數。
2、給函數傳遞大型對象
當大型對象被傳遞給函數時,使用引用參數可使參數傳遞效率得到提高,因為引用並不產生對象的
副本,也就是參數傳遞時,對象無須復制。下面的例子定義了一個有限整數集合的類:
const maxCard=100; Class Set { int elems[maxCard]; // 集和中的元素,maxCard 表示集合中元素個數的最大值。 int card; // 集合中元素的個數。 public: Set () {card=0;} //構造函數 friend Set operator * (Set ,Set ) ; //重載運算符號*,用於計算集合的交集 用對象作為傳值參數 // friend Set operator * (Set & ,Set & ) 重載運算符號*,用於計算集合的交集 用對象的引用作為傳值參數 ... }
先考慮集合交集的實現
Set operator *( Set Set1,Set Set2) { Set res; for(int i=0;i<Set1.card;++i) for(int j=0;j>Set2.card;++j) if(Set1.elems[i]==Set2.elems[j]) { res.elems[res.card++]=Set1.elems[i]; break; } return res; }
由於重載運算符不能對指針單獨操作,我們必須把運算數聲明為 Set 類型而不是 Set * 。
每次使用*做交集運算時,整個集合都被復制,這樣效率很低。我們可以用引用來避免這種情況。
Set operator *( Set &Set1,Set &Set2) { Set res; for(int i=0;i<Set1.card;++i) for(int j=0;j>Set2.card;++j) if(Set1.elems[i]==Set2.elems[j]) { res.elems[res.card++]=Set1.elems[i]; break; } return res; }
三、引用返回值
如果一個函數返回了引用,那么該函數的調用也可以被賦值。這里有一函數,它擁有兩個引用參數並返回一個雙精度數的引用:
double &max(double &d1,double &d2) { return d1>d2?d1:d2; }
由於max()函數返回一個對雙精度數的引用,那么我們就可以用max() 來對其中較大的雙精度數加1:
max(x,y)+=1.0;
一.main函數參數解析
1.main函數的原型
int main(int argc,char *argv[],char *envp[]) { program-statements }
2.各個參數
1.第一個參數:argc
argc是一個整型變量,表示命令行參數的個數(含第一個參數)。
2.第二個參數:argv
argv是一個字符指針的數組,每一個元素是一個字符指針,指向一個字符串。這些字符串就是命令行中的每一個參數(字符串)。
argv數組中的第一個元素指向的是可執行程序的名稱。最后一個元素為空指針(NULL),空指針表示數組結束。
3.第三個參數:envp
envp是一個字符指針的數組,數組的每一個元素是指向一個環境變量(字符串)的字符指針。
envp的最后一個元素為空指針(NULL)。
二.通過命令行參數實現簡易計算器
1.題目要求
使用main函數的參數,實現一個整數計算器,程序可以接受三個參數,第一個參數“-a”選項執行加法,“-s”選項執行減法,“-m”選項執行乘法,“-d”選項執行除法,后面兩個參數為操作數。
例如:命令行參數輸入:test.exe -a 1 2
執行1+2輸出3
2.具體代碼
#include <stdio.h> #include <stdlib.h> #include <ctype.h> int Add(int x, int y)//加法 { return x + y; } int Sub(int x, int y)//減法 { return x - y; } int Mul(int x, int y)//乘法 { return x * y; } int Div(int x, int y)//除法 { return x / y; } int main(int argc,char* argv[],char* envp[]) { int ret = 0; if (argc != 4)//判斷參數是否有誤 { printf("參數有誤:argc=%d\n", argc); return 0; } switch (*(argv[1] + 1))//判斷參數部分是要執行什么計算 { case 'a': //輸入的命令行參數為字符 //所以這里需要將兩個參數變為對應的整型(atoi函數) ret = Add(atoi(argv[2]), atoi(argv[3])); break; case 's': ret = Sub(atoi(argv[2]), atoi(argv[3])); break; case 'm': ret = Mul(atoi(argv[2]), atoi(argv[3])); break; case 'd': ret = Div(atoi(argv[2]), atoi(argv[3])); break; } printf("%d\n", ret); system("pause"); return 0; }
C語言中訪問結構體成員時用‘.’和‘->’的區別
舉個例子,定義了一個叫Student,別名為stu的結構類型,我們聲明了一個結構體變量叫stu1,聲明了一個結構體指針為stuP。
typedef struct Student { char name[N]; int id; int score; struct Student *next; } stu; stu stu1; stu* stuP;
那么我們訪問他們的結構體成員時要這樣
stu1.name="Xiao Ming"; stu1.id=2015211; stuP->name="Xiao Hua"; stuP->id=2015311;
也就是說,結構體變量的成員用‘.’,結構體指針的成員用‘->’。
關於結構體成員的引用:
箭頭(->):左邊必須為指針;
點號(.) :左邊必須為實體。
那么如果一個結構體指針引用一個成員,這個成員又是一個結構體(並且是一個實體),那么如果要引用這個成員的成員要怎么辦呢?
即:箭頭左邊必須是指針,實體一定要用點號引用。for example C->student.age