如何用輕小說的方式學(xué)C++(五) 斐波那契與他的小兔子們

剎那悄悄地瞥了一眼活動室內(nèi)。
黑燈瞎火,空無一人。
剎那松了一口氣,潛進(jìn)了活動室,正要摸開關(guān),發(fā)現(xiàn)燈突然就被兩位摯友給點亮了。
“剎那啊你發(fā)給我的那個程序什么意思?以為自己寫的復(fù)雜點我看不懂就會以為你是對的嗎?”
凜音跳了出來,一手叉腰一手直指剎那,仿佛名偵探推理完畢指著嫌疑人的那酷酷的動作。
“說過了要寫成特定的形式計算機才能讀得懂,你這亂寫肯定會出錯的!”
剎那羞愧地低下了頭。
因為回去以后把循環(huán)的語法忘得一干二凈,結(jié)果寫了很久都沒寫出來,最后不得不拜托隔壁班某位櫻姓同學(xué)的姐姐幫忙。傳說這位姐姐姓櫻名寧寧,也是半年前才開始自學(xué)C艸與游戲編程,現(xiàn)在卻已經(jīng)在Eagle Jump里面擔(dān)當(dāng)游戲開發(fā)。
大概過了十分鐘,這位姐姐把程序發(fā)了過來,剎那看也沒看就轉(zhuǎn)發(fā)給了凜音。
然后就有了剛才的一幕。
久遠(yuǎn)走上前看了一下程序,然后暴擊凜音:
“雖說肯定不是剎那寫的,但是這代碼毫無疑問是可以運行且正確的啊!”
“誒?!”
凜音一臉懵逼。這和說好的不一樣???
面對一臉懵逼的凜音,久遠(yuǎn)無奈地再重錘了凜音一拳:
“之前說了你想教剎那至少自己先把《C++ Primer》過一遍??!這些都是常規(guī)操作啊!”
“阿諾,很抱歉...我重寫就是了,不要責(zé)怪凜音醬了...”
剎那似乎是以為自己的無能牽扯了為師的凜音,這讓久遠(yuǎn)更是無奈了,只好對剎那說程序是對的不過下次要自己寫。
“那么,今天的主題是——斐波那契!”
“肥婆納妾?”
“是斐波那契!”
“我知道!是那個有一堆小兔子的吧!”
“知道就好講話了。那么今天的課題——求出斐波那契的前n項!”
“誒有n個數(shù)的話怎么存嘛?”
“這里就要請出——數(shù)組!”
“數(shù)組?”
“字面意思!數(shù)組!”
int a; //一個整形變量
int b[100]; //一個整形變量的數(shù)組
b[0]=100; //如此這般訪問。要注意,從0開始算,所以b[1]是第二個元素
“所以并沒有什么難的,那么讓我們開始吧!上次的循環(huán)沒忘吧?”
“...要是沒忘就交上次作業(yè)了。”
“呃...那么看這段吧?!?/p>
#include<iostream>
int main() {
????int a[1024],n;
????a[0]=1;
????a[1]=1;
????std::cin >> n;
????for(int j=2;j<n;++j) {
????????a[j]=a[j-1]+a[j-2];
?????}
????for(int j=0;j<n;++j) {
????????std::cout<<j<<" "<<a[j]<<std::endl;
????}
????return 0;
}
“嗯,看——不懂?!?/p>
凜音掀起了桌子。
“就是一個循環(huán)??!不停地把后兩項加到前面一項去??!”
(似懂非懂)
“另外,字符串也可以看成一個數(shù)組喲?!?/p>
(點頭)
“那么我們進(jìn)入到高精度加法吧。”
“...等等?高精度?”
“先說一句,int的取值范圍是-2147483648~2147483647內(nèi)”
“誒!第一次聽說?。∵€有這魔法一樣的數(shù)字是什么鬼!”
“[-2^31]~[2^31-1],合計2^32個數(shù),就是這么魔法。因為現(xiàn)在的電腦上int通常占用4個字節(jié),每個字節(jié)8個bit,也就是32個bit。”
“唔...暈了!”
“也沒要你記。現(xiàn)在你寫一個能夠進(jìn)行100位數(shù)以內(nèi)的加法的程序。”
“誒?!100位?!真的能算嗎?!我可以手算??!”
“前提是你手算的能有電腦快?!?/p>
剎那茫然地看著編輯器一閃一閃的光標(biāo)。
“補充一些關(guān)于std::string的東西吧?!?/p>
“寫上#include<string>以后,你就可以用這個東西了?!?/p>
一些用法:
std::string a;
std::cin>>a; //輸入一個字符串。
a.length(); //能夠得到字符串的長度,類型是size_t,一般是unsigned int的重定義,即無符號整數(shù),能表示0~4294967295之間的數(shù)。通常也不會有這么長的字符串就是。
a[100]; //字符串第101個元素的值。(要從0號開始算,所以是第101個)
ざわざわ~
ざわざわ~
“動手!能寫多少寫多少!”凜音咆哮。
#include<iostream>
#include<string>
int main() {
???? std::string a,b;
???? std::cin>>a>>b;
???? size_t aLen = a.length();
???? size_t bLen = b.length();
???? size_t maxLen = aLen > bLen ? aLen : bLen;
????.......
“寫不下去了!怎么做嘛...”
“提示,先倒序,這樣進(jìn)位可以往后面進(jìn)。”
???? ......
???? int ia[101],ib[101];
???? for(int i=0;i<aLen;++i) {
???????? ia[i] = a[aLen-i-1];
???? }
???? for(int i=0;i<bLen;++i) {
???????? ib[i] = b[bLen-i-1];
???? }
“?!@里要注意,字符的'0'的值并不是0喲。”
“什么意思?”
“有個東西叫ASCII碼,把常見的一些字符與具體的二進(jìn)制數(shù)值做了一個映射。比如字符0的ASCII是48號,A是65,a是97等。所以要改成這樣:”
???? ......
???? int ia[101],ib[101];
???? for(int i=0;i<aLen;++i) {
???????? ia[i] = a[aLen-i-1] - '0';
???? }
???? for(int i=0;i<bLen;++i) {
???????? ib[i] = b[bLen-i-1] - '0';
???? }
“沒必要去背那個表,寫成 '0' 機器自動就會認(rèn)出來并且當(dāng)成常量處理的?!?/p>
繼續(xù):
???? for(int i=0;i<maxLen;++i) {
???????? ia[i]=ia[i]+ib[i];
?????????if(ia[i]>9) {
???????????? ia[i]=ia[i]-10;
???????????? ++ia[i+1];
???????? } //進(jìn)位
???? }
“至此運算就全部做完了,不過還需要輸出。綜上所述:”
#include<iostream>
#include<string>
int main() {
???? std::string a,b;
???? std::cin>>a>>b;
???? size_t aLen = a.length();
???? size_t bLen = b.length();
???? size_t maxLen = aLen > bLen ? aLen : bLen;
???? int ia[101]={0},ib[101]={0}; //數(shù)組元素全部清零
???? for(int i=0;i<aLen;++i) {
???????? ia[i] = a[aLen-i-1] - '0';
???? }
???? for(int i=0;i<bLen;++i) {
???????? ib[i] = b[bLen-i-1] - '0';
???? } //倒序
???? for(int i=0;i<maxLen;++i) {
???????? ia[i]=ia[i]+ib[i];
???????? if(ia[i]>9) {
???????????? ia[i]=ia[i]-10;
???????????? ++ia[i+1];
???????? } //進(jìn)位
???? }
???? int i;
???? for(i=maxLen-1;i>0;--i) {
???????? if(ia[i]!=0) break; //break的意思是跳出這一層循環(huán)
???? }
???? for(;i>=0;--i) {
???????? std::cout<<ia[i];
???? }
???? return 0;
}
“作為今天的第一個作業(yè),思考一下輸出的時候為什么要這兩個循環(huán)吧~”
“誒?!”
“那么進(jìn)入今天的第二個部分——遞歸!”
“遞歸?好像數(shù)學(xué)課里面有聽到過,唔...”
“遞歸就是說一個函數(shù)自己調(diào)用自己~”
“誒那不會一直調(diào)用下去嗎?!”
“所以有個東西叫遞歸出口啊~”
“誒?”
“比如斐波那契數(shù)列求第n項可以這么寫:”
int fib(int n) {
???? if(n==1 || n==2) return 1;
???? else return fib(n-1)+fib(n-2);
}
“可以看到,fib這里自己調(diào)用了自己兩次,然后把值加起來返回。正可謂數(shù)學(xué)里面的A[n]=A[n-1]+A[n-2]”
(似懂非懂)
“那個...凜音醬啊,這里這么多n...不會互相覆蓋嗎?”
“你還記得我之前說的實際參數(shù)與形式參數(shù)嗎?”
“忘了?!?/p>
“就是說,這些東西里面互相不干擾,會進(jìn)行替換的。這里的參數(shù)n只是一個標(biāo)記,當(dāng)你運行fib(n-1)的時候,相當(dāng)于新開了一個副本,又有一個新的n,我們姑且記作n'吧,它的值是n-1。然后在n'的里面,又有fib(n'-1),記作n",以此類推。”
“好厲害??!那就是說會有很多個n嘍?”
“的確。”
“好我們來算一下fib(10000)吧!”
Segmentation fault (core dumped)
“誒?!出錯了?!”
“這也引出另一個話題。正是因為計算機要給這些n存位置,包括你新調(diào)用一次fib就會多一些數(shù)據(jù),當(dāng)這些數(shù)據(jù)太大了以后就會炸掉。所以——盡量避免遞歸。”
(雖然不懂但還是點點頭吧)
“但是遞歸的確是一個解決的思路。并且——遞歸與循環(huán)等價?!?/p>
“等價?”
“那么今天的第二個作業(yè)~禁止使用for do-for while以及goto,實現(xiàn)1到100的求和?!?/p>
“誒?!”

【久遠(yuǎn)的小課堂】

從命名上可以看出有多嚴(yán)重的心理問題
[注意: 不是所有的編譯器都支持寬字符標(biāo)識符的,寫代碼請還是好好用字母]
這一次我就對剎那不知從哪里搞來的代碼進(jìn)行剖析。
聽不懂也沒事,聽個響也行。
先從main過程入手,可以看到main里面僅僅是對另一個過程的調(diào)用。那么讓我們來看一下這個“有多嚴(yán)重的心理問題()”
這個過程里面只有一個循環(huán),for(auto 問題 : 心理問題() );
剎那你應(yīng)該還記得之前凜音說到的那個帶冒號的范圍for吧,這里就出現(xiàn)了。
冒號的意思表示,前面的這個變量取變后面這個范圍的所有的值,并且進(jìn)行一些操作。這里直接寫分號不進(jìn)行任何操作,所以僅僅是給“問題”賦上全部的值以后結(jié)束。
然后,自然而然地我們會看到心理問題,即上面的struct。
final的意思表示不允許被繼承,關(guān)于繼承我們會放到之后的類來講。
可以看到,心理問題里面有三個部分:類“草”的定義,begin方法及end方法。
通常而言,我們進(jìn)行for操作的,都是一個集合,比如數(shù)組啊容器啊之類的,但是也不是一定的,只要一個數(shù)據(jù)結(jié)構(gòu)滿足一定的條件,我們就可以for,即begin、end還有operator++。
begin返回一個泛迭代器,你可以理解為一個名片,名片上面印著第一個元素的姓名、地址等很多信息。end則是最后一個元素。operator++則代表你可以獲得下一個名片。
在這個例子里面,“草”就是這個名片的種類。你會不停地拿到像“草”一樣的名片。當(dāng)執(zhí)行operator++時,草里面的i會自增。顯然,循環(huán)是要停下來的,那么就是在operator!=為假(即now==end)時停止。if(i==100) goto end;可以看到end后面的return false;
即循環(huán)100次以后會結(jié)束循環(huán)。
這里有個template,以及后面的一些type_traits內(nèi)的模板,這是一個典型的錯誤用法,不過鑒于不會對這里造成什么影響我就不去說了。
“草”是個名片,但是你肯定要根據(jù)名片去找具體的元素。每找一次,就調(diào)用一次operator*,可以看到operator*會返回一個デデドン類型的元素。循環(huán)100次,則會生成100個デデドン。
在類與對象里,對象死亡后有個東西叫“析構(gòu)函數(shù)”,格式就是一個波浪號~加上類的名字デデドン。后面的noexcept(false)表示允許拋出異常。通常而言析構(gòu)函數(shù)是不允許拋出異常的,因為會造成異常語句之后的語句沒法正確執(zhí)行,這里顯然不知道為什么就給允許了。再后面看似有個while,實際如果順利的話只會執(zhí)行一次——
std::cout<<"まわれ";
執(zhí)行完畢后,會返回std::cout也是說過的。那么,把std::cout轉(zhuǎn)換為bool的時候,會根據(jù)這個流是否依然有效來轉(zhuǎn)換。如果順利輸出了,那么就是true,取反感嘆號后變成false,結(jié)束循環(huán);但是一旦沒順利輸出,則會一直死循環(huán)在這,直到引發(fā)異常。
生成100個對象,那么這100個對象死的時候也會輸出100個まわれ。
綜上,這個程序順利的話能正常運行完畢。