日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

androidstudio调用系统相机为什么resultcode一直返回0_函数递归调用?看这文就够了...

發(fā)布時間:2024/9/27 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 androidstudio调用系统相机为什么resultcode一直返回0_函数递归调用?看这文就够了... 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

作者 | Cooper Song

責(zé)編 | Elle

出品 | 程序人生(ID:coder_life)

我猜,大多數(shù)程序員第一次接觸函數(shù)的遞歸調(diào)用都是在算斐波那契數(shù)列某項(xiàng)值的時候,這是函數(shù)遞歸調(diào)用最常見的應(yīng)用之一。規(guī)定第一項(xiàng)和第二項(xiàng)為1,后面的項(xiàng),每一項(xiàng)都是其前面兩項(xiàng)的和。

用公式表示就是f(n)=f(n-2)+f(n-1)。

而進(jìn)一步轉(zhuǎn)化,就是f(n)=[f(n-2-2)+f(n-2-1)]+[f(n-1-2)+f(n-1-1)]。

很明顯,這是一個遞歸的過程。

遞歸的優(yōu)點(diǎn)是算法簡單、容易理解,代碼行數(shù)少。

但遞歸也有缺點(diǎn),咱們將上面的f(n)再化簡一下就變成了f(n)=[f(n-4)+f(n-3)]+[f(n-3)+f(n-2)],可以看出,f(n-3)被計算了兩次,而f(n-4)+f(n-3)就是再計算f(n-2),又與最后一項(xiàng)f(n-2)是一樣的,f(n-2)也被重復(fù)計算了。因此,遞歸的一大缺點(diǎn)就是存在大量的重復(fù)計算,運(yùn)行起來浪費(fèi)時間也浪費(fèi)空間。

遞歸的另一個缺點(diǎn)是遞歸的層數(shù)不能太多(不能遞歸太深)。那遞歸得太深了會怎樣呢?答案是會爆棧。那么什么是爆棧呢?又是怎樣引發(fā)爆棧的呢?下面就要從最底層的角度講一講函數(shù)調(diào)用及函數(shù)遞歸調(diào)用的原理,相信讀完了就會找到答案。

這就要先從程序的鏈接和裝入說起了。

程序的鏈接(Link)

一個程序是由多個模塊構(gòu)成的,以C語言為例,有頭文件,只有引用了這個頭文件你才能使用scanf和printf;還有頭文件,只有引用了這個頭文件你才能直接調(diào)用strlen函數(shù)得到字符串的大小。所謂程序的鏈接,就是將整個程序的所有目標(biāo)模塊(比如程序員自己寫的頭文件和函數(shù))以及其他所需要的庫函數(shù)裝配成一個完整的裝入模塊。

原來每個模塊都有每個模塊的邏輯地址,經(jīng)過鏈接后,形成了統(tǒng)一的從0開始的邏輯地址,如下圖所示。

如何理解模塊?看上圖大概就有了概念,一個函數(shù)就是一個模塊。

程序的裝入(Load)

學(xué)過計算機(jī)組成原理的同學(xué)都知道,在計算機(jī)中有個部件叫程序計數(shù)器(Program Counter,簡稱PC),它存放的是程序要執(zhí)行的下一條指令的地址,CPU要到內(nèi)存當(dāng)中去取指令,取到CPU中進(jìn)行譯碼分析然后執(zhí)行。

程序原本存儲在磁盤上,因此只經(jīng)過鏈接還不能運(yùn)行,還需要裝入主存(內(nèi)存),CPU通過PC提供的線索到內(nèi)存中去取指令,如此循環(huán)往復(fù),程序才得以運(yùn)行下去。雖然程序的第一條指令的邏輯地址是0,但它裝入內(nèi)存時在內(nèi)存中的地址可不是0,因?yàn)閮?nèi)存中的低地址是留給系統(tǒng)使用的,也就是系統(tǒng)區(qū),比系統(tǒng)區(qū)的地址高的空間才是留給用戶使用的,也就是用戶區(qū)。雖然裝入內(nèi)存后其地址不再是從0開始,但其相對地址是不變的,將上面鏈接好的裝入模塊裝入內(nèi)存,內(nèi)存空間示意圖如下。

函數(shù)的調(diào)用

所謂函數(shù)的調(diào)用,就是程序原本在主模塊中順序執(zhí)行,遇到調(diào)用指令暫時到別的模塊執(zhí)行,在別的模塊執(zhí)行完后再返回主模塊的下一條指令繼續(xù)執(zhí)行,如下圖所示。

為什么可以執(zhí)行著執(zhí)行著就跳到別的模塊執(zhí)行了?又為什么在別的模塊執(zhí)行完了又回到原來的模塊執(zhí)行了呢?之所以能跳到別的模塊執(zhí)行,是因?yàn)楹瘮?shù)調(diào)用指令就指明了目標(biāo)模塊的首地址,將目標(biāo)模塊的首地址傳送給了程序計數(shù)器PC,就中斷了程序的順序執(zhí)行,然后進(jìn)入目標(biāo)模塊執(zhí)行。之所以執(zhí)行完子模塊還能回到主模塊中執(zhí)行,是因?yàn)閮?nèi)存中有一個專門實(shí)現(xiàn)函數(shù)調(diào)用的棧區(qū),在執(zhí)行調(diào)用指令的時候,就將主模塊調(diào)用指令之后的指令的地址入了棧,當(dāng)子模塊執(zhí)行到返回指令的時候,再出棧,將棧頂元素(也就是主模塊中要執(zhí)行的下一條指令的地址)傳給PC,程序的執(zhí)行就又回到了主模塊。

假設(shè)模塊A中的指令是:

add ax,bx ;本條指令的地址為10000

call B ;調(diào)用模塊B本條指令的地址為10001

mov dx,ax ;本條指令的地址為10002

假設(shè)模塊B中的指令是:

sub cx,dx ;本條指令的地址為15000

mov bx,cx ;本條指令的地址為15001

ret ;本條指令的地址為15002

模塊A為主模塊,模塊B為目標(biāo)模塊,在執(zhí)行call B指令的時候,函數(shù)調(diào)用棧區(qū)示意圖如下(左邊為調(diào)用前,右邊為調(diào)用后),SP為棧頂指針。

執(zhí)行完call B,就開始在模塊B中執(zhí)行,一直執(zhí)行到ret返回指令,此時函數(shù)調(diào)用棧區(qū)示意圖如下(左邊為返回后,右邊為返回前)。

執(zhí)行完ret返回指令,將棧頂元素出棧送給程序計數(shù)器PC以供CPU繼續(xù)執(zhí)行主模塊A中的剩余指令。

實(shí)際上,函數(shù)調(diào)用時入棧保護(hù)的不僅僅有主模塊中調(diào)用指令之后的指令的地址,還有一些變量或者說數(shù)據(jù),每個函數(shù)都有每個函數(shù)的局部變量,在主函數(shù)中調(diào)用子函數(shù),主函數(shù)中的局部變量必須入棧保護(hù),否則就會丟失。比如下面這個例子:

int add(int x,int y)

{

int a=x+1;

int b=y+1;

int c=a+b;

return c;

}

int main

{

int a=1,b=2;

int c=add(a,b);

printf(“%d+%d=%d ”,a,b,c);

return 0;

}

主函數(shù)和add函數(shù)里都有變量a和b,執(zhí)行完add函數(shù)再返回到主函數(shù)中a的值必須還為1,b的值必須還為2,因此可以在調(diào)用add函數(shù)前先將主函數(shù)的所有變量(a和b)入棧保護(hù),待執(zhí)行完返回主函數(shù)時再出棧送給變量a和變量b。

遞歸函數(shù)的調(diào)用

遞歸函數(shù)的調(diào)用本質(zhì)上也是函數(shù)的調(diào)用,只不過是自己在調(diào)用自己罷了。

以求斐波那契數(shù)列的項(xiàng)為例:

int fibonacci(int n)

{

if(n==1||n==2) //假設(shè)本條指令的地址為10000

return 1; //假設(shè)本條指令的地址為10001

int a=fibonacci(n-2); //假設(shè)本條指令的地址為10002

int b=fibonacci(n-1); //假設(shè)本條指令的地址為10003

int c=a+b; //假設(shè)本條指令的地址為10004

return c; //假設(shè)本條指令的地址為10005

}

如果進(jìn)入函數(shù)的n是1或者是2,那么就直接返回1;

否則,就繼續(xù)遞歸下去。

假設(shè)主函數(shù)調(diào)用斐波那契函數(shù)的指令的地址為15000,其下一條指令的地址為15001。

假設(shè)我們要求斐波那契數(shù)列的第5項(xiàng),公式為

f(5)=f(3)+f(4)

=[f(1)+f(2)]+[f(2)+f(3)]

=[f(1)+f(2)]+[f(2)+[f(1)+f(2)]]

函數(shù)調(diào)用棧的示意圖如下。

第一步,從主函數(shù)中進(jìn)入斐波那契函數(shù),傳入的n為5。

第二步,斐波那契函數(shù)中執(zhí)行到int a=fibonacci(n-2),將下一條指令的地址壓入棧,也就是將10003入棧,此時的n=5,將n=5壓入數(shù)據(jù)棧,傳入的n=3。

第三步,斐波那契函數(shù)中執(zhí)行到int a=fibonacci(n-2),將下一條指令的地址壓入棧,也就是將10003入棧,此時的n=3,將n=3壓入數(shù)據(jù)棧,傳入的n=1。

第四步,此時n=1,可以直接返回1給上層的斐波那契函數(shù)的a,返回的同時出棧10003給程序計數(shù)器PC,出棧n=3給上一層斐波那契函數(shù)的n,回到上層的斐波那契函數(shù)。

第五步,執(zhí)行程序計數(shù)器PC指向的指令(內(nèi)存地址為10003的指令),也就是執(zhí)行int b=fibonacci(n-1),是一個函數(shù)調(diào)用,將下一條指令的地址壓入棧,也就是將10004入棧,此時n=3,將n=3壓入數(shù)據(jù)棧,此時a=1,將a=1壓入數(shù)據(jù)棧,傳入的n=2。

第六步,此時n=2,可以直接返回1給上層斐波那契函數(shù)的b,返回的同時出棧10004給程序計數(shù)器PC,出棧n=3給上一層斐波那契函數(shù)的n,出棧a=1給上一層斐波那契函數(shù)的a,回到了上層的斐波那契函數(shù)。

第七步,執(zhí)行程序計數(shù)器PC指向的指令(內(nèi)存地址為10004的指令),也就是執(zhí)行int c=a+b,然后順序執(zhí)行一直到返回,返回2給上一層斐波那契函數(shù)的a,返回的同時出棧10003給程序計數(shù)器PC,出棧n=5給上一層的斐波那契函數(shù)的n,回到上層的斐波那契函數(shù)。

f(5)=f(3)+f(4)

=[f(1)+f(2)]+[f(2)+f(3)]

=[f(1)+f(2)]+[f(2)+[f(1)+f(2)]]

此時紅色部分已通過遞歸計算完成。

第八步,執(zhí)行程序計數(shù)器PC指向的指令(內(nèi)存地址為10003的指令),也就是執(zhí)行int b=fibonacci(n-1),是一個函數(shù)調(diào)用,將下一條指令的地址壓入棧,也就是將10004入棧,此時n=5,將n=5壓入數(shù)據(jù)棧,此時a=2,將a=2壓入數(shù)據(jù)棧,傳入的n=4。

第九步,斐波那契函數(shù)中執(zhí)行到int a=fibonacci(n-2),將下一條指令的地址壓入棧,也就是將10003入棧,此時的n=4,將n=4壓入數(shù)據(jù)棧,傳入的n=2。

第十步,此時n=2,可以直接返回1給上層的斐波那契函數(shù)的a,返回的同時出棧10003給程序計數(shù)器PC,出棧n=4給上一層斐波那契函數(shù)的n,回到上層的斐波那契函數(shù)。

第十一步,執(zhí)行程序計數(shù)器PC指向的指令(內(nèi)存地址為10003的指令),也就是執(zhí)行int b=fibonacci(n-1),是一個函數(shù)調(diào)用,將下一條指令的地址壓入棧,也就是將10004入棧,此時n=4,將n=4壓入數(shù)據(jù)棧,此時a=1,將a=1壓入數(shù)據(jù)棧,傳入的n=3。

第十一步,斐波那契函數(shù)中執(zhí)行到int a=fibonacci(n-2),將下一條指令的地址壓入棧,也就是將10003入棧,此時的n=3,將n=3壓入數(shù)據(jù)棧,傳入的n=1。

第十二步,此時n=1,可以直接返回1給上層的斐波那契函數(shù)的a,返回的同時出棧10003給程序計數(shù)器PC,出棧n=3給上一層斐波那契函數(shù)的n,回到上層的斐波那契函數(shù)。

第十三步,執(zhí)行程序計數(shù)器PC指向的指令(內(nèi)存地址為10003的指令),也就是執(zhí)行int b=fibonacci(n-1),是一個函數(shù)調(diào)用,將下一條指令的地址壓入棧,此時n=3,將n=3壓入數(shù)據(jù)棧,此時a=1,將a=1壓入數(shù)據(jù)棧,傳入的n=2。

第十四步,此時n=2,可以直接返回1給上層的斐波那契函數(shù)的b,返回的同時出棧10004給程序計數(shù)器PC,出棧n=3給上一層斐波那契函數(shù)的n,出棧a=1給上一層斐波那契函數(shù)的a,回到上層的斐波那契函數(shù)。

第十五步,執(zhí)行程序計數(shù)器PC指向的指令(內(nèi)存地址為10004的指令),也就是執(zhí)行int c=a+b,然后順序執(zhí)行一直到返回,返回2給上層斐波那契函數(shù)的b,返回的同時出棧10004給程序計數(shù)器PC,出棧n=4給上一層的斐波那契函數(shù)的n,回到上層的斐波那契函數(shù)。

第十六步,執(zhí)行程序計數(shù)器PC指向的指令(內(nèi)存地址為10004的指令),也就是執(zhí)行int c=a+b,然后順序執(zhí)行一直到返回,返回3給上層斐波那契函數(shù)的b,返回的同時出棧10004給程序計數(shù)器PC,出棧n=5給上一層的斐波那契函數(shù)的n,出棧a=2給上一層的斐波那契函數(shù)的a,回到上層的斐波那契函數(shù)。

f(5)=f(3)+f(4)

=[f(1)+f(2)]+[f(2)+f(3)]

=[f(1)+f(2)]+[f(2)+[f(1)+f(2)]]

此時紅色部分已通過遞歸計算完成。

第十七步,執(zhí)行程序計數(shù)器PC指向的指令(內(nèi)存地址為10004的指令),也就是執(zhí)行int c=a+b,然后順序執(zhí)行一直到返回,返回5給上層斐波那契函數(shù)的接收者,返回的同時出棧15001給程序計數(shù)器PC,出棧主函數(shù)中的數(shù)據(jù)(未體現(xiàn)在圖中),回到主函數(shù)。

此時斐波那契第五項(xiàng)計算完成。

后記

到了揭曉為什么會爆棧的時刻了,內(nèi)存中實(shí)現(xiàn)函數(shù)調(diào)用的棧區(qū)的大小是有限的,如果遞歸層數(shù)太深,入棧的內(nèi)容越來越多,甚至出現(xiàn)只入棧不出棧的情況(還沒有符合返回條件執(zhí)行到返回指令棧就滿了),如此進(jìn)行下去,棧滿、棧溢出、爆棧只是時間問題,因此在實(shí)際項(xiàng)目應(yīng)用中,如果不能估算出遞歸的深度,函數(shù)遞歸就要慎用了。

本文雖以斐波那契數(shù)列為例介紹函數(shù)遞歸調(diào)用的底層原理,但在真正的面試中如果面試官問到了斐波那契數(shù)列相關(guān)的問題,還是不要給面試官回答一個遞歸的解法,原因之一就是當(dāng)n非常大的時候容易爆棧,原因之二就是文章開頭說的會產(chǎn)生大量的重復(fù)計算。在這里我給大家再提一種解法,就是動態(tài)規(guī)劃(DP)解法。不要一看到動態(tài)規(guī)劃就害怕,斐波那契數(shù)列的動態(tài)規(guī)劃解法還是很好理解的。先開一個大一些的數(shù)組f。

int fibonacci(int n)

{

f[1]=1,f[2]=1;

for(int i=3;i<=n;i++)

{

f[i]=f[i-2]+f[i-1];

}

return f[n];

}

這樣無非是把遞歸變成了循環(huán),但優(yōu)點(diǎn)是不會出現(xiàn)重復(fù)計算。

簡單的遞歸實(shí)現(xiàn)求斐波那契數(shù)列項(xiàng)的算法底層之復(fù)雜是我沒有想象到的,直到一張圖一張圖親手畫出來我才大吃一驚,在這里我要感謝底層硬件工程師的辛勤付出,沒有他們?yōu)槲覀儾季€鋪路,我們是無法使用高級語言輕松編程的。

本文的介紹本著一切從簡、方便理解的原則,可能有些地方與實(shí)際情況有出入,但是基本思想是一樣的。如有不當(dāng)之處,還請大家批評指正。

總結(jié)

以上是生活随笔為你收集整理的androidstudio调用系统相机为什么resultcode一直返回0_函数递归调用?看这文就够了...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。