W2 2021/4/27(星期二)
連續放假三天,星期日去爬山,隔天乳酸堆積的下半身讓我有說不出的爽快痛感,還好休息一天之後,又恢復到能屈能伸的巴低。
「台式香腸便當」是我的最愛,也是陽陽的;只是要台式菜色,都不是圈圈的菜,但如果依照他的喜好帶便當,那就是一三帶肉派,二四帶雞塊薯條,星期五Hot dog,那可不行。
今天學校有越野競跑活動(Cross Country),圈圈要求我去參加,才使得便當文稍晚發放。他今天跑步從一開始吹哨起跑,倒數第一(屬於很沒有爆發力的小孩),跑到四分之一的時候倒數第八,看他沒有放棄的繼續在爬坡奔跑(上坡很多孩子都用走的了),我很感動,本來就不是運動咖,但運動家的精神倒是沒讓我失望,鬥志很堅強,一路追趕到終點,竟然跑進前十名,完全是龜兔賽跑裡的小烏龜以耐力取勝啊,媽我都落淚了,很慶幸自己今天去見證了他的努力。
繼續來說:
陽陽升學記(三)
「選校!選到起笑」
「選校」這是屬於父母的業務專區,小孩就是上學和去參加考試就好。
選私校?還是公校?要報考幾所才夠?要選哪幾所?怎麼選?要報考什麼班?
這個說起來真的是講不完,所以,我直接以陽陽的學業條件和我的經驗來分享,當然所有的決定還包含了經濟考量,所以,大家參考就好,不要當成指標和學習的對象。畢竟每一家的各項客觀條件和主觀考量因素都是不同的。
先講公校:
跨區就讀公校,一定是要參加考試,他們會透過考試篩選出成績相對優異的學生,除了考試,還要審核成績單,看在校成績和品性以及是否積極參與社團,如果通過考試的學生太多,名額有限 ,那成績單就是決勝的關鍵,通常都是以五年級的成績單為必須送件的參考。
我們決定學校的參考依據之一是透過一個叫 The Better Education
https://bettereducation.com.au/ 的網站,查詢學校的評鑑分數,這些分數來自於學生參加Naplan成績平均值和學校各項學術表現得出來的結果 。這個網站還能交叉比對你心儀的幾所學校輸入之後的排名。可以幫助你在喜歡的幾所學校中,那一所排為第一志願,那一所作為候補志願。當然這都是以學術成績來作為參考標準。
另外,需要考量的因素就是接送是否方便,將來我必須奔波於兩校之間接送孩子,跨區就讀,選校必須要在能力所及的範圍,或是老杯是否有條件可以就近支援,大眾運輸是否方便(如果你放心孩子自己搭公車火車的話)。
再來就是學校的校風如何?是否有種族歧視的問題?校園霸凌事件是否頻傳?負面新聞是否多於正面新聞?這些對我們應該是最重要的考量因素。
其實以上三點考量,在選擇私校與公校時都是一樣的考慮範圍。
最後,我們選GC中區北區最搶手的A高中,最近一次2019年評鑑成績還不錯落在94分(老杯方便接送),以及兩所我們家隔壁學區評鑑成績大約在中上評比同88分的公校稱B和C ,一共三所公校,兩所要參加考試,一所申請就好。每個學校的申請條件和考試時間都不同。
你以為選好就去報名,然後就等考試了嗎?私校可能如此,公校(搖頭)沒這麼簡單。
孩子六年級第一個學期開始,我們就開始關注學校的網站,等公佈Open day我們就得上網登記參加,時間到,就帶著孩子前往學校聽簡報。孩子絕對是心不干情不願的前往,對他們來說真的很無聊,但你得去了解,才能知道學校招生條件是什麼?也能在第一時間拿到申請表。
我們只參加公立學校的Open Day,三所都參加了,辛苦老杯。
拿到申請表以為填寫資料,附上成績單就繳費就可以等考試,又錯了。
我沒想到這裡的高中跨區申請會這樣麻煩,
A校:獨招,考試依照Naplan考題模式出題。
申請表中需要選填分組志願:
1.法語班:用法文學習數學,科學和電腦和程式語言等學科,當然必修法語。(這個我們直接放在第三志願,雖然老杯是在法國巴黎念拿的博士學位,也用法文通過博士論文答辯,但要他用法語教數學和科學,人生沒有必要這樣找自己的麻煩,直接跳過)
2. Stem班:我理解為理工班,在這理會比較著重數學,科學和電腦程式等理工方面的學科,未來大學也是朝這個面向去重點學習和選科,我們了解到女兒對於電腦程式語言是非常的弱項,而且排斥,雖然在校成績總能拿到A或B,但每次問他「不是討厭,怎麼還能拿A或B」他自己也不知道。(所以這個成為第二志願)
3.Waldorf 華德福教育體制班:全GC應該是唯一創辦這個班級的學校吧,這個教育體制源自於德國。是一種新的教育思想,創始人相信這是一套能夠照顧學生身心靈整體發展的教育模式。(有興趣的朋友可以自己去google一下 Waldorf Education)
學校的文案說明中表示:這是一種創造性的學術學習方式,對英文,數學,歷史和地理還有科學的學習,輔以藝術角度和公開演說訓練及發展獨立思考的能力。這個班級主要在學習獨立創造和思考,自己尋找答案,而不只是老師單向的傳授。我覺得如果孩子熱愛藝術和充滿寫作興趣、饒富文藝氣息可以選擇這個科系。
老杯說,當參加學校簡報時,這是陽陽唯一抬起頭來聆聽的部分,當然,陽陽自己決定了Waldorf為第一志願。我們也認為這個最適合他。
另外學校還有其他特長班可以報考,像是音樂班,體育班和戲劇班。這些特長班除了要參加學科考試,還得有術科面試。也是三個志願中可以選擇的項目。
學校B:獨招,試題以ICAS考題模式進行。
需要先送成績單,符合資格,才通知考試。
所以,你有可能送報名表,繳了報名費,然後不一定能參加考試。(哼!跩什麼跩,但還是默默地報名了ˊ)
申請表中的志願選項有:
1學術班:同上stem班,就不贅述。報考資格:數學和科學和英文,電腦程式語言四個科目中,至少要兩個A,兩個B
2商業領袖人才培訓班:數學和英文一定要拿A才擁有考試資格。課程內容包含數學會帶到商業應用,科學和電腦應用程式都會著重在商業領域,聽說還會學習分組成立公司和經營。學校還有作業是發五元給孩子們當創業基金,讓他們利用所學,用五元創造更大利潤,曾有個孩子用五元賺了800元。學校舉例說明如此。(他們才11、12歲耶,要開始學這些了?!)
3日語班:需要基礎日文能力。學術成績要求(我忘了)
老杯說,不想女兒太早沾染銅臭味,於是我們就報了第一和第三,兩樣都拿到考試資格,因為當時open day遲到,沒聽到日語班的條件,拿到考試資格時才發現根本不會日文,所以直接放棄沒有去考。
B校申請資格這麼硬,你要不要送件?
當然要送,哪來這麼多AAAA的學生,不管孩子成績單幾個ABC,就賭他收不到這麼多符合資格的人數,不得不把把資格放寬,所以不要被條件限制所影響而退縮。有送有機會,有考有希望👍。
第三所學校是送審成績單,但成績單也都要求主要科目要拿A,時間也壓在比較後期,我們想說如果報考的學校都名落孫山,再考慮送件,畢竟公校報考費用至少都是100元起跳,在加上兩所私立學校的報名費又是兩三百元,能先不花,就省下來。
公校申請完畢,私校申請和考試及放榜過程明天繼續,時光飛逝,我又要去接小孩放學了。
祝大家有美好的午後時光❤️
「指標c考題」的推薦目錄:
- 關於指標c考題 在 Facebook 的最讚貼文
- 關於指標c考題 在 閱讀文章- 看板C_and_CPP - 批踢踢實業坊 的評價
- 關於指標c考題 在 面試考題-C語言 的評價
- 關於指標c考題 在 [C program] 指標練習題(一) @ AAA :: 隨意窩Xuite日誌 的評價
- 關於指標c考題 在 軟體/韌體工程師《面試重點與觀念複習》: 轉自ptt: C/C++常見 ... 的評價
- 關於指標c考題 在 Re: [問題] 指標和雙重指標考題- 看板C_and_CPP - PTT數位 ... 的評價
- 關於指標c考題 在 C 程式語言第六章6-2指標運算子*&,指標參數,C語言和Java都 ... 的評價
- 關於指標c考題 在 #請益Array陣列題目求解 - 軟體工程師板 | Dcard 的評價
- 關於指標c考題 在 [心得] 瑞昱,群聯,擎發,威聯通,易享,玉山TMA 的評價
指標c考題 在 面試考題-C語言 的推薦與評價
指標. The content of array a? ... printf("%c", *p++); printf("%c", *(p++)); printf("%c", (*p)++); printf("%c", *++p); printf("%c", *(++p)); ... <看更多>
指標c考題 在 [C program] 指標練習題(一) @ AAA :: 隨意窩Xuite日誌 的推薦與評價
201506041517[C program] 指標練習題(一) ?程式練習題. 判斷下列程式碼輸出結果. No1. int main(). {. int a=17;. int *c;. *c=a;. printf("c=%d ",*c);. ... <看更多>
指標c考題 在 閱讀文章- 看板C_and_CPP - 批踢踢實業坊 的推薦與評價
C 語言新手十誡(The Ten Commandments for Newbie C Programmers)
by Khoguan Phuann
請注意:
(1) 本篇旨在提醒新手,避免初學常犯的錯誤(其實老手也常犯:-Q)。
但不能取代完整的學習,請自己好好研讀一兩本 C 語言的好書,
並多多實作練習。
(2) 強烈建議新手先看過此文再發問,你的問題極可能此文已經提出並
解答了。
(3) 以下所舉的錯誤例子如果在你的電腦上印出和正確例子相同的結果,
那只是不足為恃的一時僥倖。
(4) 不守十誡者,輕則執行結果的輸出數據錯誤,或是程式當掉,重則
引爆核彈、毀滅地球(如果你的 C 程式是用來控制核彈發射器的話)。
^L:Menu:#@E,:End:#1,:1:#2,:2:#3,:3:#4,:4:#5,:5:#6,:6:#7,:7:#8,:8:#9,:9:#a,:A:#b,:B:#c,:C:#d,:D:#A,:A:#B,:B:#C,:C:#D,:D:#
████████ 請選擇相對應的數字鍵,或按 End 結束播放 ████████
1. 不可以使用尚未給予適當初值的變數
2. 不能存取超過陣列既定範圍的空間
3. 不可以提取不知指向何方的指標
4. 不要試圖用 char* 去更改一個"字串常數"
5. 不能在函式中回傳一個指向區域性自動變數的指標
6. 不可以只做 malloc(), 而不做相應的 free()
7. 在數值運算、賦值或比較中不可以隨意混用不同型別的數值
8. 在一個運算式中,不能對一個基本型態的變數修改其值超過一次以上
9. 在 Macro 定義中, 務必為它的參數個別加上括號
A(10). 不可以在 stack 設置過大的變數
B(11). 使用浮點數精確度造成的誤差問題
C(12). 不要猜想二維陣列可以用 pointer to pointer 來傳遞
D(13). 函式內 new 出來的空間記得要讓主程式的指標接住
^L:1:#@H,:Menu:,回主選單#@E,:End:,結束播放#
01. 你不可以使用尚未給予適當初值的變數
錯誤例子:
int accumulate(int max) /* 從 1 累加到 max,傳回結果 */
{
int sum; /* 未給予初值的區域變數,其內容值是垃圾 */
int num;
for (num = 1; num <= max; num++) { sum += num; }
return sum;
}
正確例子:
int accumulate(int max)
{
int sum = 0; /* 正確的賦予適當的初值 */
int num;
for (num = 1; num <= max; num++) { sum += num; }
return sum;
}
^L:2:#@H,:Menu:,回主選單#@E,:End:,結束播放#
02. 你不可以存取超過陣列既定範圍的空間
錯誤例子:
int str[5];
int i;
for (i = 0 ; i <= 5 ; i++) str[i] = i;
正確例子:
int str[5];
int i;
for (i = 0; i < 5; i++) str[i] = i;
說明:宣告陣列時,所給的陣列元素個數值如果是 N, 那麼我們在後面
透過 [索引值] 存取其元素時,所能使用的索引值範圍是從 0 到 N-1
C/C++ 為了執行效率,並不會自動檢查陣列索引值是否超過陣列邊界,
我們要自己來確保不會越界。一旦越界,操作的不再是合法的空間,
將導致無法預期的後果。
^L:3:#@N,f+1,下一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
03. 你不可以提取(dereference)不知指向何方的指標(包含 null 指標)。
錯誤例子:
char *pc1; /* 未給予初值,不知指向何方 */
char *pc2 = 0; /* pc2 起始化為 null pointer */
*pc1 = 'a'; /* 將 'a' 寫到不知何方,錯誤 */
*pc2 = 'b'; /* 將 'b' 寫到「位址0」,錯誤 */
正確例子:
char c; /* c 的內容尚未起始化 */
char *pc1 = &c; /* pc1 指向字元變數 c */
*pc1 = 'a'; /* c 的內容變為 'a' */
/* 動態分配 10 個 char(其值未定),並將第一個char的位址賦值給 pc2 */
char *pc2 = (char *) malloc(10);
pc2[0] = 'b'; /* 動態配置來的第 0 個字元,內容變為 'b'
free(pc2);
說明:指標變數必需先指向某個可以合法操作的空間,才能進行操作。
^L#@N,f+1,下一頁#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
( 使用者記得要檢查 malloc 回傳是否為 NULL,
礙於篇幅本文假定使用上皆合法,也有正確歸還記憶體 )
錯誤例子:
char *name; /* name 尚未指向有效的空間 */
printf("Your name, please: ");
gets(name); /* 您確定要寫入的那塊空間合法嗎??? */
printf("Hello, %s\n", name);
正確例子:
/* 如果編譯期就能決定字串的最大空間,那就不要宣告成 char* 改用 char[] */
char name[21]; /* 可讀入字串最長 20 個字元,保留一格空間放 '\0' */
printf("Your name, please: ");
gets(name);
printf("Hello, %s\n", name);
^L#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
正確例子(2):
/* 若是在執行時期才能決定字串的最大空間,則需利用 malloc() 函式來動態
分配空間 */
size_t length;
char *name;
printf("請輸入字串的最大長度(含null字元): ");
scanf("%u", &length);
name = (char *)malloc(length);
printf("Your name, please: ");
scanf("%s", name);
printf("Hello, %s\n", name);
/* 最後記得 free() 掉 malloc() 所分配的空間 */
free(name);
^L:4:#@N,f+1,下一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
04. 你不可以試圖用 char* 去更改一個"字串常數"
錯誤例子:
char* pc = "john"; /* pc 現在指著一個字串常數 */
*pc = 'J'; /* 但是 pc 沒有權利去更改這個常數! */
正確例子:
char pc[] = "john"; /* pc 現在是個合法的陣列,裡面住著字串 john */
/* 也就是 pc[0]='j', pc[1]='o', pc[2]='h',
pc[3]='n', pc[4]='\0' */
*pc = 'J';
pc[2] = 'H';
說明:字串常數的內容是"唯讀"的。您有使用權,但是沒有更改的權利。
若您希望使用可以更改的字串,那您應該將其放在合法空間
^L#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
錯誤例子:
char *s1 = "Hello, ";
char *s2 = "world!";
/* strcat() 不會另行配置空間,只會將資料附加到 s1 所指唯讀字串的後面,
造成寫入到程式無權碰觸的記憶體空間 */
strcat(s1, s2);
正確例子(2):
/* s1 宣告成陣列,並保留足夠空間存放後續要附加的內容 */
char s1[20] = "Hello, ";
char *s2 = "world!";
/* 因為 strcat() 的返回值等於第一個參數值,所以 s3 就不需要了 */
strcat(s1, s2);
^L:5:#@N,f+1,下一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
05. 你不可以在函式中回傳一個指向區域性自動變數的指標。否則,會得到垃圾值
[感謝 gocpp 網友提供程式例子]
錯誤例子:
char *getstr(char *name)
{
char buf[30] = "hello, "; /*將字串常數"hello, "的內容複製到buf陣列*/
strcat(buf, name);
return buf;
}
說明:區域性自動變數,將會在離開該區域時(本例中就是從getstr函式返回時)
被消滅,因此呼叫端得到的指標所指的字串內容就失效了。
正確例子:
void getstr(char buf[], int buflen, char const *name)
{
char const s[] = "hello, ";
strcpy(buf, s);
strcat(buf, name);
}
^L#@N,f+1,下一頁#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
正確例子:
int* foo()
{
int* pInteger = (int*) malloc( 10*sizeof(int) );
return pInteger;
}
int main()
{
int* pFromfoo = foo();
}
說明:上例雖然回傳了函式中的指標,但由於指標內容所指的位址並非區域變數,
而是用動態的方式抓取而得,換句話說這塊空間是長在 heap 而非 stack,
又因 heap 空間並不會自動回收,因此這塊空間在離開函式後,依然有效
(但是這個例子可能會因為 programmer 的疏忽,忘記 free 而造成
memory leak)
^L#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
[針對字串操作,C++提供了更方便安全更直觀的 string class, 能用就盡量用]
正確例子:
#include <string> /* 並非 #include <cstring> */
using std::string;
string getstr(string const &name)
{
return string("hello, ") += name;
}
^L:6:#@H,:Menu:,回主選單#@E,:End:,結束播放#
06. 你不可以只做 malloc(), 而不做相應的 free(). 否則會造成記憶體漏失
但若不是用 malloc() 所得到的記憶體,則不可以 free()。已經 free()了
所指記憶體的指標,在它指向另一塊有效的動態分配得來的空間之前,不可
以再被 free(),也不可以提取(dereference)這個指標。
[C++] 你不可以只做 new, 而不做相應的 delete
注:new 與 delete 對應,new[] 與 delete[] 對應,不可混用
切記,做了幾次 new,就必須做幾次 delete
小技巧: 可在 delete 之後將指標指到 0,由於 delete 本身會先做檢查,
因此可以避免掉多次 delete 的錯誤
正確例子:
int *ptr = new int(99);
delete ptr;
ptr = NULL;
delete ptr; /* delete 只會處理指向非 NULL 的指標 */
^L:7:#@N,f+1,下一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
07. 你不可以在數值運算、賦值或比較中隨意混用不同型別的數值,而不謹慎考
慮數值型別轉換可能帶來的「意外驚喜」(錯愕)。必須隨時注意數值運算
的結果,其範圍是否會超出變數的型別
錯誤例子:
unsigned int sum = 2000000000 + 2000000000; /* 超出 int 存放範圍 */
unsigned int sum = (unsigned int) (2000000000 + 2000000000);
double f = 10 / 3;
正確例子:
/* 全部都用 unsigned int, 注意數字後面的 u, 大寫 U 也成 */
unsigned int sum = 2000000000u + 2000000000u;
/* 或是用顯式的轉型 */
unsigned int sum = (unsigned int) 2000000000 + 2000000000;
double f = 10.0 / 3.0;
^L#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
錯誤例子:
unsigned int a = 0;
int b[10];
for(int i = 9 ; i >= a ; i--) { b[i] = 0; }
說明:由於 int 與 unsigned 共同運算的時候,會提升 int 為 unsigned,
因此迴圈條件永遠滿足,與預期行為不符
錯誤例子: (感謝 sekya 網友提供)
unsigned char a = 0x80; /* no problem */
char b = 0x80; /* implementation-defined result */
if( b == 0x80 ) { /* 不一定恒真 */
printf( "b ok\n" );
}
說明:語言並未規定 char 天生為 unsigned 或 signed,因此將 0x80 放入
char 型態的變數,將會視各家編譯器不同作法而有不同結果
^L:8:#@N,f+1,下一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
08. 你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值
超過一次以上。否則,將導致未定義的行為(undefined behavior)
錯誤例子:
int i = 7;
int j = ++i + i++;
正確例子:
int i = 7;
int j = ++i;
j += i++;
你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值,
而且還在同一個式子的其他地方為了其他目的而存取該變數的值。(其他目的,
是指不是為了計算這個變數的新值的目的)。否則,將導致未定義的行為。
錯誤例子:
x = x++;
^L#@N,f+1,下一頁#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
錯誤例子:
int arr[5];
int i = 0;
arr[i] = i++;
正確例子:
int arr[5];
int i = 0;
arr[i] = i;
i++;
錯誤例子:
int i = 10;
cout << i << "==" << i++;
正確例子:
int i = 10;
cout << i << "==";
cout << i++;
^L#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
錯誤例子:
int Integer=10;
printf( "%d %d %d", Integer++, Integer++, Integer++ );
錯誤例子:
void foo(int a, int b) { ... }
int main() {
int i=0;
foo(i++, i++);
}
說明: C/C++ 並沒有強制規定參數會由哪個方向開始處理(不像Java是由左到右),
因此可能會造成與預期不符的情況
^L:9:#@N,f+1,下一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
09. 在 Macro 定義中, 務必為它的參數個別加上括號
錯誤例子:
#include <stdio.h>
#define SQUARE(x) (x * x)
int main()
{
printf("%d\n", SQUARE(10-5));
return 0;
}
正確例子:
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
int main()
{
printf("%d\n", SQUARE(10-5));
return 0;
}
^L#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
說明:如果是用 C++, 請多多利用 inline function 來取代上述的 macro,
以免除 macro 定義的種種危險性。如:
inline int square(int x) { return x * x; }
macro 定義出的「偽函式」至少缺乏下列數項函式本有的能力:
(1) 無法進行參數型別的檢查。
(2) 無法遞迴呼叫。
(3) 無法用 & 加在 macro name 之前,取得函式位址。
(4) 呼叫時往往不能使用具有 side effect 的引數。例如:
錯誤例子: (感謝 yaca 網友提供)
#define MACRO(x) (((x) * (x)) - ((x) * (x)))
int main()
{
int x = 3;
printf("%d\n", MACRO(++x));
return 0;
}
^L:A:#@H,:Menu:,回主選單#@E,:End:,結束播放#
10. 不可在 stack 設置過大的變數,否則會造成 stack overflow
(感謝 VictorTom 版友幫忙)
錯誤例子:
int array[10000000]; // 僅舉例說明
說明:由於編譯器會自行決定 stack 的上限,某些預設是數 KB 或數十 KB,
當變數所需的空間過大時,很容易造成 stack overflow,程式亦隨之
當掉,若真正需要如此大的空間,那麼建議配置在 heap 上,或是採用
static / globla variable,亦或是改變編譯器的設定
使用 heap 時,雖然整個 process 可用的空間是有限的,但採用動態抓取
的方式,new 無法配置時會丟出 std::bad_alloc 例外,malloc 無法配置
時會回傳 null,不會影響到正常使用下的程式功能
正確例子:
int *array = (int*) malloc( 10000000*sizeof(int) );
說明:由於此時 stack 上只需配置一個 int* 的空間,可避免 stack overflow
更多說明請參考精華區 z-10-13
^L:B:#@H,:Menu:,回主選單#@E,:End:,結束播放#
11. 使用浮點數千萬要注意精確度所造成的誤差問題
根據 IEEE 754 的規範,又電腦中是用有限的二進位儲存數字,因此常有可
能因為精確度而造成誤差,例如加減乘除,等號大小判斷,分配律等數學上
常用到的操作,很有可能因此而出錯(不成立)
更詳細的說明可以參考精華區 z-8-11
或參考冼鏡光老師所發表的一文 "使用浮點數最最基本的觀念"
https://blog.dcview.com/article.php?a=VmgBZFE5AzI%3D
^L:C:#@N,f+1,下一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
12. 不要猜想二維陣列可以用 pointer to pointer 來傳遞
(感謝 loveme00835 legnaleurc 版友的幫忙)
首先必須有個觀念,C 語言中陣列是無法直接拿來傳遞的!
不過這時候會有人跳出來反駁:
void pass1DArray( int array[] );
int a[10];
pass1DArray( a ); /* 可以合法編譯,而且執行結果正確!! */
事實上,編譯器會這麼看待
void pass1DArray( int *array );
int a[10];
pass1DArray( &a[0] );
我們可以順便看出來,array 變數本身可以 decay 成記憶體起頭的位置
因此我們可以 int *p = a; 這種方式,拿指標去接陣列。
^L#@N,f+1,下一頁#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
也因為上述的例子,許多人以為那二維陣列是不是也可以改成 int **
錯誤例子:
void pass2DArray( int **array );
int a[5][10];
pass2DArray( a );
/* 這時候編譯器就會報錯啦 */
/* expected ‘int **’ but argument is of type ‘int (*)[10]’*/
在一維陣列中,指標的移動操作,會剛好覆蓋到陣列的範圍
例如,宣告了一個 a[10],那我可以把 a 當成指標來操作 *a 至 *(a+9)
因此我們可以得到一個概念,在操作的時候,可以 decay 成指標來使用
也就是我可以把一個陣列當成一個指標來使用 (again, 陣列!=指標)
但是多維陣列中,無法如此使用,事實上這也很直觀,試圖拿一個
pointer to pointer to int 來操作一個 int 二維陣列,這是不合理的!
^L#@N,f+1,下一頁#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
儘管我們無法將二維陣列直接 decay 成兩個指標,但是我們可以換個角度想,
二維陣列可以看成 "外層大的一維陣列,每一維內層各又包含著一維陣列"
如果想通了這一點,我們可以仿造之前的規則,
把外層大的一維陣列 decay 成指標,該指標指向內層的一維陣列
void pass2DArray( int (*array) [10] ); // array 是個指標,指向 int [10]
int a[5][10];
pass2DArray( a );
這時候就很好理解了,函數 pass2DArray 內的 array[0] 會代表什麼呢?
答案是它代表著 a[0] 外層的那一維陣列,裡面包含著內層 [0]~[9]
也因此 array[0][2] 就會對應到 a[0][2],array[4][9] 對應到 a[4][9]
結論就是,只有最外層的那一維陣列可以 decay 成指標,其他維陣列都要
明確的指出陣列大小,這樣多維陣列的傳遞就不會有問題了
^L#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
也因為剛剛的例子,我們可以清楚的知道在傳遞陣列時,實際行為是在傳遞
指標,也因此如果我們想用 sizeof 來求得陣列元素個數,那是不可行的
錯誤例子:
void print1DArraySize( int* arr ) {
printf("%u", sizeof(arr)/sizeof(arr[0])); /* sizeof(arr) 只是 */
} /* 一個指標的大小 */
受此限制,我們必須手動傳入大小
void print1DArraySize( int* arr, size_t arrSize );
C++ 提供 reference 的機制,使得我們不需再這麼麻煩,
可以直接傳遞陣列的 reference 給函數,大小也可以直接求出
正確例子:
void print1DArraySize( int (&array)[10] ) { // 傳遞 reference
cout << sizeof(array) / sizeof(int); // 正確取得陣列元素個數
}
^L:D:#@N,f+1,下一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
13. 函式內 new 出來的空間記得要讓主程式的指標接住
對指標不熟悉的使用者會以為以下的程式碼是符合預期的
void newArray(int* local, int size) {
local = (int*) malloc( size * sizeof(int) );
}
int main() {
int* ptr;
newArray(ptr, 10);
}
接著就會找了很久的 bug,最後仍然搞不懂為什麼 ptr 沒有指向剛剛拿到的合法空間
^L#@N,f+1,下一頁#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
讓我們再回顧一次,並且用圖表示
______________
1. int* ptr; ptr -> |__未知的空間__|
______________
2. 呼叫函式 newArray ptr -> |__未知的空間__| <- local
______________
3. malloc 取得合法空間 ptr -> |__未知的空間__|
______________
|___合法空間___| <- local
______________
4. 離開函式 ptr -> |__未知的空間__|
用圖看應該一切就都明白了,我也不需冗言解釋
^L#@N,f+1,下一頁#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
也許有人會想問,指標不是傳址嗎?
精確來講,指標也是傳值,只不過該值是一個位址 (ex: 0xfefefefe)
local 接到了 ptr 指向的那個位置,接著函式內 local 要到了新的位置
但是 ptr 指向的位置還是沒變的,因此離開函式後就好像事什麼都沒發生
( 嚴格說起來還發生了 memory leak )
以下是一種解決辦法
int* createNewArray(int size) {
return (int*) malloc( size * sizeof(int) );
}
int main() {
int* ptr;
ptr = createNewArray(10);
}
^L#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
改成這樣亦可 ( 為何用 int** 就可以?想想他會傳什麼過去給local )
void createNewArray(int** local, int size) {
*local = (int*) malloc( size * sizeof(int) );
}
int main() {
int *ptr;
createNewArray(&ptr, 10);
}
如果是 C++,別忘了可以善用 Reference
void newArray(int*& local, int size) {
local = new int[size];
}
^L:End:E
後記:從「古時候」流傳下來一篇文章
"The Ten Commandments for C Programmers"(Annotated Edition)
by Henry Spencer
https://www.lysator.liu.se/c/ten-commandments.html
一方面它不是針對 C 的初學者,一方面它特意模仿中古英文
聖經的用語,寫得文謅謅。所以我現在另外寫了這篇,希望
能涵蓋最重要的觀念以及初學甚至老手最易犯的錯誤。
作者:潘科元(Khoguan Phuann) (c)2005. 感謝 ptt.cc BBS 的 C_and_CPP
看板眾多網友提供寶貴意見及程式實例。
nowar100 多次加以修改整理,擴充至 13 項,並且製作成動畫版。
如發現 Bug 請推文回報,謝謝您!
--
※ 發信站: 批踢踢實業坊(ptt.cc)
... <看更多>