--渚_花(討論) 2023年10月5日 (四) 22:08 (CST)
#include <stdio.h>
#include <stdlib.h>
int main(){
printf("Hello world!\n");
getchar();
return 0;
}
上面是C語言的hello world代碼。如果你是初學者的話,作為學習第一門程式語言的儀式,先嘗試一下執行以下上面的hello world代碼吧。具體網上有大量教程。
初學者學習一門任何一門新語言的第一個程序,都是在命令行上列印hello world,這是一個傳統。因此也有下面這個編程笑話
| “ | 某程式設計師對書法十分感興趣,退休後決定在這方面有所建樹。於是花重金購買了上等的文房四寶。一日,飯後突生雅興,一番磨墨擬紙,並點上了上好的檀香,頗有王羲之風範,又具顏真卿氣勢,定神片刻,潑墨揮毫,鄭重地寫下一行字:hello world | ” |
| ——https://zhuanlan.zhihu.com/p/603433946 | ||
推薦初學者使用VSCode或者Codeblocks電腦的這兩個軟體。另外,如果是使用VSCode的話。推薦使用Code Runner插件,這個插件可以省去大量的配置工作
如果報錯了怎麼辦?因為上面的代碼太基礎了,所以會有很多初學者遇到和你一樣的錯誤。解決方法是,把錯誤信息複製下來,百度
| 暈字怎麼辦 | ||
|---|---|---|
|
本教程將嘗試以用一些與其他教程不同的方式,比如將常量運算和變量運算分離等,解決大量新人難以理解的問題,比如很多新人到了指針之後就完全學不懂的問題
本教程的示例代碼較少,幾乎沒有習題和作業
為什麼學習程式語言都要以hello world入門呢?這是因為每門程式語言的hello world程序,都「麻雀雖小五臟俱全」,裡面攜帶著這門程式語言大量的語法信息
這一章開始會出現大量的專有名詞。其中很多第一次出現的專有名詞後面會詳細解釋,但有一些名詞並不會給出解釋,需要讀者自己通過語境進行理解。給出大量專有名詞一是為了給讀者一種「C語言是一個好大的開放世界」的探索的感覺,二是為了方便讀者遇到問題時進行表述,期望解決「我遇到了某個問題不知道怎麼描述」的現象。這裡很多內容只需要熟悉,而不需要完全弄懂
為什麼是第零章?這是因為程式設計師計數都是從0開始計數的。比如C語言的數組,C++的vector,Python的列表中最開始的元素的下標是0而不是1,儘管lua是個例外
| “ |
きみ2に |
” |
| ——Patricia,花開物語印象曲 | ||
#include <stdio.h> // 每一行//後的內容是註釋,註釋不會對代碼執行結果產生影響
#include <stdlib.h> // 上面一行引用了一個名字叫做`stdio.h'的頭文件,這一行引用了一個名字叫做`stdlib.h'的頭文件
int main(){ // 每一行兩個斜線後面的內容表示【註釋】,註釋並不會被識別為代碼內容
printf("Hello world!\n");
getchar(); // 在一些設備上如果不加上這一行代碼,可能會出現類似於閃退的情況
return 0;
}
我們一行一行開始講。前兩行是功能為引用頭文件的編譯預處理指令,其中#include表示引用,<>內是被引用的頭文件名字。前兩行分別引用了名字叫做stdio.h和stdlib.h的頭文件
#include語句所在行
<>把頭文件名字框起來。如果引用程式設計師自己寫的頭文件,則使用""stdio當做是studio。實際上,std是standard的縮寫,i表示input,o表示output。該頭文件的功能是給出標準輸入輸出的函數聲明(這裡涉及到連結)。stdlib中的lib是library,這裡並不會是圖書館,而是庫的意思然後是3-7行。這裡實現了一個名字叫main()的函數。int的意思是整形(一種數據類型,簡稱類型),寫在函數名字前面表示這個函數的返回值的類型是int類型
int是integer的縮寫,這個數據類型通常用來表示和存儲整數int類型,更常用的稱呼是int類型main()函數是一個特殊的函數,被稱為入口函數。之所以main()函數被叫做入口函數,是因為編譯後的可執行文件會從調用main()函數開始
int main(void),這個具體在函數聲明時會講到,不推薦這樣寫int main(int argc, char **argv)(第二個形式參數(或簡稱形參)char **argv也可以寫成char argv[],這兩種寫法都是正確的,這裡涉及到命令行參數的問題,後面的內容會詳細講到main(),這種寫法省略了前面的int。早期的C語言是可以省略int的,但現在不推薦這樣寫{寫到main()函數的下面,這種寫法是正確的。這種寫法涉及到代碼風格問題,下面會詳細講到main()函數後面由左大括號和右大括號框柱的內容叫代碼塊,代碼塊中有多條語句,每一條語句後面都要加一個分號。如果調用了main() 函數,代碼塊中的語句會從上到下從左到右逐條執行
可以看到,main( )代碼塊內部有縮進,這個會在後面代碼風格部分講到
#include <stdio.h>
#include <stdlib.h>
int main(){
printf("Hello world!\n");
getchar();
return 0;
}
現在簡單介紹函數調用。第四行調用了一個叫做printf()的函數,該函數可用於列印字符串
puts()實現列印字符串功能。它與printf()函數有區別,但在此代碼中可以互相替換printf()中的f是format的縮寫,意為按照指定格式列印
函數(function)調用的語法是function_name(argument1, argument2, ...),函數名字後面需要跟著一個括號,括號中按順序填充函數參數(argument)。如果函數沒有參數,可以將括號內部置空,但不允許省略括號
printf("Hello world!\n");
getchar();
上面兩行分別調用了兩個函數,第一個是printf()。這裡它只有一個參數,為字符串常量"Hello world\n"。第二個函數getchar()沒有參數
getchar()換成system("pause")。這是為了讓程序阻塞在這裡,以防止程序窗口一執行完printf()語句就自動關閉,就像閃退一樣,這樣就會看不到列印效果。後面的代碼示例會省略getchar()
getchar()函數的本來用途是,從'標準輸入流'stdin中讀取一個字符並返回。它會等待用戶通過鍵盤鍵入字符,待用戶輸入回車後,再從緩衝區中讀取字符system()函數的本來用途是,調用系統命令或shell命令,其唯一的一個字符串常量參數是將要被調用的命令
pause系統命令會等待用戶在鍵盤鍵入任意鍵
Press Any Key to Continue這時軟體用戶只需要在鍵盤上鍵入任意鍵即可繼續。然後很多用戶打電話詢問公司Any Key在鍵盤的哪個位置。原來很多用戶認為這裡需要按下一個鍵盤上一個名字叫做Any Key的鍵,而用戶在鍵盤上找不到這個鍵。所以後來很多提示語都變成了Press Enter to Continue,而實際上用戶鍵入任何鍵都可以代碼的倒數第二行return 0;的意思是,main()函數的返回值是0,或者說,main()函數返回0
main()函數的返回值具有特殊的意義,它表示程序的返回值。通常如果一個程序的返回值為0,表示程序正常運行,用其他值(比如-1)表示程序異常終止
echo (?$)來查看最後一個運行的程序的返回值關於注釋:C語言的注釋有兩種,分別是//這種註釋可以註釋掉//後面的內容和/* 這種註釋可以跨行 */兩種。注釋內容不會被識別為代碼內容,也不會被執行,可用於便於未來的程式設計師包括未來的自己閱讀代碼
//,以將該行代碼內容變為注釋,這樣被注釋掉的代碼不會被執行,以對比代碼執行結果較未被注釋掉前的區別,以達到調試的效果#include <stdio.h>
#include <stdlib.h>
int main(){ // 我是一條註釋
printf("1\n"); /* printf()函數
printf("2\n"); * 可以通過這種方式
printf("3\n"); * 打印數字
printf("4\n"); */
printf("5\n");
printf("6\n");
return 0;
}
下面是上面代碼的執行結果,請注意數字2 3 4不會被列印。至於為什麼不會被列印,留作習題
1 5 6
不同的代碼規範與代碼風格略有區別,但是有一點是確定的,如果要參與別人的代碼項目,必須按照對方的代碼風格貢獻代碼,除非對方的代碼風格本就特別混亂。很多大廠會指定程式設計師的代碼風格,以
這一節的所有示例代碼的目的都是為了講解代碼風格,所以不要把注意點放在這些代碼的實際功能和執行效果上。這裡的部分示例代碼是C++代碼。下面部分內容可能涉及到沒學到的內容,暫時不要求理解
事實上,編譯器在編譯的時候,會忽略掉換行空格與tab等空白字符。所以你寫成這樣也可以,儘管這樣會很不易讀
#include <stdio.h>
#include <stdlib.h>
int main(){printf("Hello world!\n"); getchar(); return 0;}
教程最開始的hello world示例代碼是右派寫法,即把左大括號{放到每一行右面。對應的,下面是左派寫法,即把左大括號放到第二行的開頭。一些代碼規範會要求在一些情況使用左派,一些情況使用右派
#include <stdio.h>
#include <stdlib.h>
int main()
{ // 左派寫法
printf("Hello world!\n");
getchar();
return 0;
}
需要注意的是,右大括號,和左派寫法的左大括號,需要單獨佔一行。不推薦下面的寫法
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{ if (argc == 1) // 左大括號要單獨佔一行
{ // 推薦這樣的左大括號用法,單獨佔一行
printf("There is no argument\n"); // 這一行並沒有和上一行的左大括號寫在同一行
return 0;
} printf("The number of arguments is %d\n", argc-1);
// 不推薦上一行這樣寫,推薦將printf()函數另起一行
return 0;
}
一行最好不要寫太長,如果寫太長的話,推薦分行寫。如果if ()語句或for ()語句一行需要寫很長的話,我個人推薦下面的寫法
// 提前聲明,我不是不會auto
for ( // 在寫 `if ()' 語句和 `for()' 語句時,最好在`if'和`for' 之後敲一個空格,以與函數調用區分開
vector<int>::iterator iter = v.begin(); // 括號內的內容推薦多一個縮進
iter != v.end();
iter++){ // 請注意這裡和下面,右小括號和左大括號放在一起時的寫法。這裡的寫法比較不容易區分iter++是否是大括號內的內容
count++;
int val = *iter;
if (
val >= lower_value && // 表達式太長需要換行時,運算符通常寫在當前行末尾,而不是第二行開頭
val < upper_value && // 為了好看,這裡的`upper_value'前面敲了兩個空格
is_prime(val)
){ // 請注意這裡和上面右小括號和左大括號放在一起時的寫法
sum += val;
}
else sum -= val;
}
關於空格的使用:通常在每個二元運算符左右各加一個空格,而一元運算符後面不加空格。通常在每個逗號和分號後面加一個空格
有時為了排版好看,故意用多個空格或tab,比如下面代碼
void Bit_vector::write_buffer_to_file(FILE* output){
fwrite(&capacity, sizeof(int), 1, output);
fwrite(&end_bit_index_of_byte, sizeof(int), 1, output);
fwrite(&end_byte_index, sizeof(int), 1, output);
fwrite(buffer, sizeof(char),end_byte_index+1, output);
}
上一章寫起來特別抽象,可能對於沒有編程基礎的人很難理解。問題不大,直接進入第一章。本教程有意將常量運算與變量運算分開,以便於講解更深入的內容
先介紹一個簡單的printf()列印功能,這裡用於列印int類型的數據。printf()函數的詳情功能可以查看這個頁面,下面也會詳細介紹
"%d\n"中的%d是佔位符
printf()函數會掃描第一個參數的字符串掃描並列印,如果掃描到佔位符,則用printf()後面的參數代替佔位符printf()函數第一個參數有兩個佔位符。列印時,第一個佔位符被替換成了第二個參數9,第二個佔位符被替換成了第三個參數30%d用於列印整形,%c用於列印char類型等,詳情查看這個頁面"%d\n"中的'\n'表示換行,即把光標移動到下一行的開始位置
2023930192359#include <stdio.h>
#include <stdlib.h>
int main(){
printf("%d\n", 2023);
printf("%d\n%d\n", 9, 30);
printf("%d\n", 9+10);
printf("%d\n", 3+10*2); // (*)的優先級大於(+)
printf("%d\n", 10-3-2); // (-)是左結合的
printf("%d\n", 10-(3-2)); // (())可以改變運算的優先級
return 0;
}
上面代碼的執行結果是下面
2023 9 30 19 23 5 9
下面先介紹運算符的運算順序、優先級與結合性。關於運算符具體的功能,將在後面的內容分節介紹
上面代碼中,printf("%d\n", 3+10*2);的結果是列印23。注意這裡的運算順序,並不是先運算(3+10)*2,而是先運算3+(10*2)。我們可能認為這理所當然,而在C語言中,這涉及到一個優先級的問題。在這裡,*的優先級是高於+的
優先級和結合性可以查看, 這個網站。括號可以改變運算的優先級
然後說什麼是結合性。在運算10-3-2的時候,我們理所當然認為應該先運算(10-3)-2,而不是10-(3-2)。因為,-是左結合的,或者叫從左到右運算。一些運算符是右結合的,比如(=)。雖然還沒到學變量的時候,但還是給出下面的代碼
#include <stdio.h>
#include <stdlib.h>
int main(){
int a;
a = 3+2;// (=)是右結合的,所以會先計算(=)右邊的內容,等到結果5計算完畢後,再進行(=)運算
printf("%d\n", a);
return 0;
}
優先級相同的運算符的結合性一定相同。運算時,先運算優先級高的,然後運算優先級低的。同等優先級的,按照結合性進行從左到右或者從右到左運算。括號可以改變優先級
上面我們涉及到的都是int類型的常量,現在介紹一個新的類型char和它的編碼表示。一個char類型的大小是一個字節(byte),一個字節是8位二進制數字。下面一段介紹什麼是二進制數字
我們經常使用的是十進制數字,十進制數字是由0123456789這些數字之中的一個或多個組成的串(但不可以有前導零)。二進制數字是一串用0或1組成的串(可以有前導零)
0b,八進制數字前綴為0,十六進制數字常用前綴為0x0b00001010這樣的8位二進制數字是合法的。十進制數字通常不可以有前導零,所以1023是合法的十進制數字,而01023不是合法的十進制數字二進制數字和十進制數字可以互相換算,詳情可以查看此文。下面是二進制數字0b01001010換算成十進制數字的例子,其中,每一個二進制位的位置對應著一個權重。運算時,將每一位的值乘以這一位對應的權重,然後求和,就可以得到十進制數字
128 64 32 16 8 4 2 1 每一位的權重
* * * * * * * * 乘以
+--+--+--+--+--+--+--+--+
ob| 0| 1| 0| 0| 1| 0| 1| 0|每一位的值
+--+--+--+--+--+--+--+--+
0+64+ 0+ 0+ 8+ 0+ 2+ 0 得到的數字
= 74 加在一起就是對應的十進制數字
計算機科學常見數字表示還有十六進制,它由0123456789ABCDEF16個字符組成,其中字母可以小寫。下面給出了每一個字符對應的十進制數字對應表,以及一個十六進制數字換算示例
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | F| E| D| C| B| A| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
65536 4096 256 16 1
* * * * *
+-----+-----+-----+-----+-----+
| 0xa| 6| 0| f| 3|
| 10| 6| 0| 15| 3|
+-----+-----+-----+-----+-----+
655360+24576+ 0+ 240+ 3=680179
上文提到,char類型是一個字節大小,而一個字節是8個二進制位。因為每個二進制位都有兩種可能(分別是0或1),所以char類型的數字一共有256種,可以用來表示-128~127或0~255之間的數字,也可以用來表示字符,畢竟char是character的縮寫
char類型的值可以用來表示ASCII碼字符,ASCII碼字符與char類型的值的對應關係可以查看這個網頁。單個ASCII字符的字面量,例如單個'a',的類型也是char類型
0xf中看出它的值是int類型的十進制數字15。而對於變量,無法從它的名字看出它的值
int類型的字面量沒有後綴,所以沒有後綴的,不帶小數點的數字字面量都是int類型TODO:可以使用兩個十六進制數字來表示一個字節
TODO:大端與小端
TODO:sizeof()關鍵字
位運算一共有6種,分別是按位非~、按位與&、按位或|、按位異或、左移、右移
我們從最簡單的運算:邏輯非運算講起。我們知道,計算機通過0或1的編碼來存儲數據。非運算很簡單,即在運算後,把操作數每一個二進制位中的0變成1,把1變成0。邏輯非的真值表以及一個運算示例如下
類型轉換分為兩種,一種是自動類型轉換,一種是手動類型轉換
C語言中手動類型轉換的語法是第一行。C++中支持更複雜的類型轉換,可以使用第二條語法來增強可讀性
(type) value type(value)
手動類型轉換常用於解決除法運算結果是整數的問題
#include <stdio.h>
#include <stdlib.h>
int main(){
printf("%lf\n", (double)10 / 3); // 先將10轉換為double類型,然後10/3就變成了(double)除以(int)。根據上述自動類型轉換內容,除號的兩個操作數均變為(double),所以運算結果是3.33333
printf("%lf\n", 10/(double)3); //
printf("%lf\n", (double)(10 / 3));
return 0;
}
上面代碼的執行結果是下面。為什麼第三行輸出是3.000000,因為它是先進行的(int 10)/(int 10),得到(int 3),然後由(int 3)轉為double類型
3.333333 3.333333 3.000000
#include <stdio.h>
#include <stdlib.h>
// 習慣將不同作用的代碼用空行分開。比如上面的代碼是引用頭文件,下面的代碼是函數聲明
int f(); // 一些教程會在函數被實現之前,在開頭給出其聲明
// 這裡的空行也是如此,用於分開函數聲明和函數實現
int f(void){ // 此函數先給出的聲明,然後給出的實現
return 10;
}
// 習慣為了好看,在函數實現後加一個空行
char g(){ // 此函數聲明時同時實現
return '+; //43
}
int main(){
printf("%d\n", f());
printf("%c\n", g());
printf("%d\n", f()+g());
printf("%c\n", getchar());
return 0;
}
上面代碼的執行結果是下面
10 63 53
然後程序會阻塞到這裡。此時輸入一個字符,假設是a,然後回車,然後程序就會將這個字符列印出來,然後退出
上面代碼聲明了三個函數,這三個函數的返回類型第一個和第三個是int類型,第二個是char類型。這兩個函數都沒有參數,沒有參數的函數可以在其參數列表置空,也可以寫入void
編譯器將C語言代碼編譯成可執行文件,可執行文件內部是可以直接由CPU執行的機器碼。機器碼可讀性很差,所以通常將一些機器碼簡記為彙編。彙編有兩種風格,分別是Intel和AT&T,這兩種風格有很大差別。此處使用AT&T彙編風格。linux系統中可以使用objdump命令查看一個可執行文件的反彙編代碼
48 83 ec 08 sub $0x8, %rsp # 彙編代碼的註釋使用的是井號,而不是兩個斜線
上面的示例中,第一行機器碼(用十六進制數字表示)對應的彙編代碼為第二行
運算時,CPU會將數據存儲到寄存器中,一些CPU內有16個寄存器。CPU的運算單元可以根據寄存器的值以及機器代碼來取出其中兩個寄存器的值,進行運算,並將運算結果存入其中一個寄存器中。在調用函數時,寄存器用於傳遞函數參數
#include <stdio.h>
int main(){
printf("%d\n", 3+10*2):
return 0;
}
上面的C語言代碼可以編譯後的可執行文件的反彙編是下面的彙編代碼,可以通過看注釋來體驗一下最底層CPU層面代碼是怎樣運行的。需要注意的是,幾乎所有編譯器都會進行優化,所以你進行反編譯的時候,極有可能不會得到和下面相同的反彙編代碼(所以下面的反彙編是我根據現有代碼改編的) TODO:添加代碼高亮,將注釋對其
<main>: mov $0xa, %edx # 將寄存器%edx賦值為立即數$3.彙編中,立即數前面需要加$符號,0x表示該立即數十六進制數字表示。$0xa對應十進制數字是`10' mov $0x2, %eax # 將寄存器%eax賦值為立即數2。此時,%edx寄存器的值為10,%eax寄存器的值為2 imul %eax, %edx # 讓寄存器%eax的值乘以%edx的值,結果保存在%edx寄存器中 mov $0x3, %eax # 將寄存器%eax的值賦值為立即數3。此時,%edx寄存器的值為20,%eax寄存器的值為3 add %edx, %eax # 讓寄存器%edx的值乘以%eax的值,結果保存在%eax寄存器中 mov %eax %esi # 將寄存器%eax的值賦值給%esi,這樣%esi的值就是剛剛的運算結果。%esi用於傳遞printf()函數的第二個參數 lea 0xe83(%rip), %rax # % 這一行和下兩行可以忽略 mov %rax, %rdi # 這一行和上一行用於將字符串常量"%d\n"作為第一個參數傳入 mov $0x0 %eax call <printf@plt> # 調用printf()函數。下面幾行可以忽略 mov $0x0, %eax # 對應return 0。%eax用於傳遞函數的返回值 leave ret # 函數調用結束,返回
前兩章都是關於常量運算的,這裡開始變量運算
TODO:變量的名字不可以是關鍵字 TODO:變量的關鍵字標記
const關鍵字可以標記一個變量為常變量,後面會詳細介紹TODO:靜態變量。雖然這裡還沒有講到變量的自加運算和指針,但還是給出一個靜態變量的使用示例 TODO:CSAPP中將program stack譯作程序棧
TODO:需要注意自加與自減運算
TODO:的返回值是0或1
TODO:可以沒有else
TODO:if-else if-else 是一種特殊的 if-else 使用方式
TODO:是否是計數
TODO:math函數庫
TODO:調換下順序
TODO:如果goto來goto去可能會太亂,不推薦使用
TODO:用計算10000!%10001為例引入群論,順便介紹王義和的離散數學引論 TODO:stack overflow
我們都知道,C語言是編譯型程式語言,也就是說,C語言代碼被編譯器編譯為可執行文件,然後才可以執行。本質上,被執行的並不是C語言代碼,而是被可執行文件
TODO:介紹malloc free函數,內存洩漏和堆
TODO:一個先有雞還是先有蛋的問題
TODO:C語言和C++的struct不同 TODO:介紹sizeof()操作,因為結構體內部分配內存情況未知
編程範式大概可分為兩種:面向過程和麵向對象(object-oriented programming,簡稱OOP或OO看上去很唬人——我在用面向對象編程哎——其實不是什麼新鮮東西)
| 面向結果編程(認真你就輸了) |
|---|
|
還有一種編程範式是面向結果編程,通常用於糊弄編程作業。比如下面的Python代碼用於糊弄列印前10個質數的作業
print(2) # Python 用井號開始註釋,語句後不需要加分號 print(3) # Python 的 print()函數可以自動換行 print(5) '''Python 也可以用這種方式作為註釋''' print(7) '''這種註釋的本質是一個沒有被賦值給變量的字符串''' print(9) '''這種註釋可以跨行''' print(11) # 你發沒發現,你現在可以看懂一些Python代碼了 print(13) print(17) print(19) print(23) print(29) |
TODO:需要在同一個文件夾或目錄下 TODO:其中file是一個FILE*類型的文件句柄。這其中,文件句柄並不是文件本身,但是程式設計師可通過文件句柄來讀取文件
#include <stdio.h>
#include <stdlib.h>
int main(){
const char* file_name = "file.txt"
FILE* file = fopen("file.txt", "r+");
if (file == NULL){
printf("Unable to open %s\n");
return -1;// 在程序出現錯誤時,通常不return 0
}
for(char c; c = fgetc(); c!=EOF){
putchar(c);
}
printf("\n");
return 0;
}
這一章就要實踐起來了,打開Code::Blocks或者VSCode,以及安裝一個linux虛擬機(推薦用VMVare和Ubuntu入門),因為很多指令在虛擬機環境下更容易操作
| “ | 紙上得來終覺淺,絕知此事要躬行 | ” |
| ——陸遊《冬夜讀書示子聿》 | ||
TODO:macro名字的由來
很多C語言教程會在這部分使用實現一個2048遊戲、十步萬度遊戲、掃雷遊戲或者迷宮遊戲等作為最後一章的實踐內容
TODO: 二叉樹的定義和操作,這裡選擇不帶有頭結點的二叉樹,每次執行返回頭結點的指針
TODO:printf()函數debug
你已經攻略了C語言娘了,現在,嘗試一下下面的內容吧
C語言是學習其他程式語言的跳板。如果能學明白C語言,那麼其他程式語言自然不會有太多吃力。因為C語言已經有50多年的歷史,在它之後的語言都或多或少參考了一些C語言
C語言被大量應用在嵌入式。可以嘗試買一個51單片機玩玩,大概80軟妹幣以內,然後學習模電和數電,走上嵌入式工程師的道路。找時間我也可以出一個單片機教程
除了嵌入式以外,C語言的應用場景較少。可以嘗試學一下C++。在高效性上C++的執行效率僅次於C語言。以後有時間我會考慮做一個C++教程
可以直接學CSAPP(Computer System: A Programmers' Perspective,深入理解計算機系統),這個是計算機科學最經典的教材之一,據說一些國外的大學要學好幾遍這本教材[來源請求]
可以攻略考取大學的計算機專業
可以嘗試參加一些代碼項目,哪怕只能貢獻翻譯也挺好的。可以幫我把這個爛攤子收拾一下U:渚 花/Lua參考手冊(翻譯)
可以看一些開原始碼。如果看不懂的話推薦先學習數據結構與常見算法,和常見設計模式
可以嘗試學一下軟體工程的內容。學完之後就可以嘗試為一些開源軟體貢獻代碼了
可以嘗試攻略一下洛谷娘
可以嘗試攻略一些常見的程式語言娘,比如Java娘、Python娘等。Python的設計和其他很多主流程式語言不同,被稱為Pythonic
| Python哲學 |
|---|
|
在Python命令行輸入 >>> import this |
可以嘗試攻略一下世界上最好的程式語言娘PHP娘
此教程主要參考菜鳥教程、蘇小紅《C語言程序設計 第四版》(也推薦這本)
待添加的內容
atoi()這種可讀性很差的庫函數名是歷史遺留問題。包括函數聲明和實現分離,也是歷史遺留問題,雖然很多教程還是會這樣教,但我認為現在已經完全無必要
一個static關鍵字的例子
#include <stdio.h>
#include <stdlib.h>
// 如果pcount不是NULL,則在其指向的內存地址中寫入該函數執行次數
// 否則僅返回加法返回值
int add(int a, int b, int* pcount){
static int count = 0; // 靜態變量只會在函數第一次被調用時被賦值
count++;
if (pcount){ // 等價於 if (pcount != NULL)
*pcount = count;
}
return a+b;
}
int main(){
int count = 0;
add(1, 2, NULL);
printf("%d\n", add(1, 2, &count));
printf("%d\n", count);
printf("%d\n", add(3, 4, &count));
printf("%d\n", count);
return 0;
}
下面是上面代碼的執行結果
3 2 7 3