對理工科新生來說,C語言是隱藏不了的坎。因為進入大學之前很多人根本沒有接觸過編程相關的內容,所以對這門課的接受能力普遍較低,學習起來也非常困難。
這里就總結一些可能在課堂上老師不會詳細講解,但是對于理解 C 語言個人感覺比較重要的一些內容,供大家參考討論。首先我們來討論一些基礎的內容。由于學校教學時長是有限的,每節(jié)課的時間也比較短,因此在進入具體教學前的緒論環(huán)節(jié),并不會花費過多的筆墨。很多時候,甚至只會告訴同學如何安裝 IDE、如何新建文件、保存、編譯并運行,但不會告訴同學們?yōu)槭裁匆@么做,每一步背后到底都干了些什么。要搞清楚這些問題,首先需要知道一些編程語言的基本知識,我們從分類講起。
編程語言怎么分類?
編程語言的分類其實有很多種分類方法,首先可以將其分為高級語言與低級語言,而高級語言之中又有著非常多的種類,這些將在下面進行介紹。
高級語言與低級語言
首先按照高級低級來區(qū)分,可以分為機器語言(匯編語言)和其他。高級語言的分類較多,這邊先簡單聊一聊低級語言。
機器語言僅由 0 和 1 組成,是計算機硬件能夠直接理解的語言。不同的架構——如我們熟知的 ARM,x86,RISC-V 等架構——都有著不同的機器語言。機器語言能夠直接操作處理器,其操作碼在計算機內都有著對應的電路來完成。
匯編語言是在機器語言的基礎上誕生的一種語言,每一條語句都與機器語言中的操作碼一一對應,能夠直接翻譯到機器語言。其存在的意義就是能夠方便程序員理解。
上圖:匯編語言與機器語言對比
舉一個簡單的例子:如果想要讓兩個數(shù)進行加法運算,例如計算 2+3 ,那么使用機器語言可能就是 00000011 00000010 00000011 (這里為了方便加入了空格,實際的機器語言中不存在空格,只有 0 和 1 ,且該機器碼為個人杜撰,僅作為例子使用,并非某一架構實際所使用的機器碼),而用匯編語言寫起來則是 ADD 2, 3 這樣較為便于理解的方式。當然,匯編語言可不止這么簡單,不過由于本文并非主要介紹匯編語言,因此就不進行深入討論了。
盡管如此,匯編語言依然非常復雜,而且限制頗多,一旦需要寫一些復雜功能,或是算法運算,很容易寫完后連自己都讀不懂。我在學習微機原理時,寫過一個課程項目,要求是用匯編為 89C51 單片機寫一段搖一搖計數(shù)的代碼,剔除驅動 LCD 屏的代碼,總共不過四五百行,卻又寫了近三百行注釋以便理解。雖然其中有我對于匯編不夠熟練的緣故,但其繁復程度可見一斑。
由此可見,顯然不太可能用機器語言或是匯編語言來進行復雜代碼的編寫。這時高級語言就應運而生。下面就簡單介紹一下高級語言以及其分類。
編譯型、解釋型與混合型
接下來介紹通過語言的翻譯方式來進行的分類,以三種目前非常流行的語言為例,分別為 C 語言,Python 以及 Java。
由于所有寫的代碼最終都會變成機器語言才能執(zhí)行,因此不同的語言最終也會殊途同歸,翻譯回匯編和機器語言,只是不同類型的語言翻譯的方式不同而已。這邊首先介紹 C 語言為代表的編譯型語言。
編譯型語言,顧名思義,就是通過編譯將代碼翻譯到機器語言,再進行執(zhí)行,因此執(zhí)行前會首先將代碼進行編譯,這一步在老師教學的時候,會告訴同學們必須要先點擊編譯再點擊執(zhí)行,或點擊編譯并執(zhí)行,其原因就在這里。編譯會調用現(xiàn)成的編譯器對代碼進行分析,優(yōu)化,處理,其中的過程在這里由于篇幅原因也不過多贅述了,總之最后會將所寫的 .c 代碼編譯為以 .exe(Windows 下)或 .out (macOS 下)結尾可執(zhí)行文件。
C 語言編譯前
C 語言編譯后
這里可以控制編譯器生成匯編語言文件,可以看一下兩者的差距
上圖:C 語言與對應匯編語言
顯然 C 語言的版本更容易理解。
編譯型語言雖然在會在執(zhí)行前進行分析優(yōu)化,運行起來速度也非???,但對于大型程序來說,編譯耗時也會非常長。那么能否不進行編譯而直接運行呢?答案顯然是可以的,這就是解釋型語言,如 Python。
對于解釋型語言,將不會使用編譯器進行翻譯,最終生成機器語言的可執(zhí)行文件,再進行執(zhí)行。它會調用解釋器,逐行翻譯源文件,將每一行實時翻譯到機器碼并執(zhí)行。如此一來,就不需要進行編譯,執(zhí)行前的準備時間大大減少。但是由于解釋器并不會對代碼進行優(yōu)化,而且每次運行時都需要從頭解釋一遍,導致執(zhí)行效率不如編譯型語言。
編譯型語言還存在另一個問題。根據(jù)前文所提到的,每種架構都有獨特的機器語言,而編譯的過程就是將代碼翻譯為機器語言的過程,這就導致每次編譯生成的文件都只能在特定平臺上運行。那能不能做到一次編譯,就能在全平臺運行呢?顯然這也是可以的。這就是混合型語言,如 Java。
這類語言同樣需要編譯,但是編譯后生成的并非機器碼,而是字節(jié)碼。通常這類語言在運行時會再轉換成機器碼執(zhí)行,或直接由虛擬機解釋執(zhí)行。由于編譯到字節(jié)碼而非機器碼,因而編譯得到的執(zhí)行文件是全平臺通用的。
指定數(shù)據(jù)類型
許多同學在學習 C 語言的時候可能會疑惑,數(shù)據(jù)類型到底有什么用?要理解這個問題,我們先來看看數(shù)據(jù)是怎么存儲的。
數(shù)據(jù)類型的意義
在內存中,所有數(shù)據(jù)都會被以二進制進行存儲,即 01001001 等形式。這些數(shù)據(jù)僅僅只是 0 和 1 而已,所表達的意義都是人為規(guī)定的。
通常,第一位會被視為符號位,即 0 位正, 1 為負。然而,如果我希望第二位來表示符號位,也完全是符合規(guī)定的,只是所有涉及到運算的代碼都要重寫罷了。而數(shù)據(jù)類型就是用來規(guī)定每一位所代表的意義。
舉個例子,在 32 位系統(tǒng)中,對于 int 類型而言,第一位表示符號,后 31 位表示具體的值。而對于 float 類型而言,盡管第一位也表示符號,但剩下的 31 位與 int 類型所表示的意義就不同了。緊接著的 8 位是指數(shù)位,剩下的是尾數(shù),即使用科學計數(shù)法表示為 尾數(shù) * (2 ^ 指數(shù)) 。這里是 2 的原因是計算機中所有數(shù)據(jù)都是二進制存儲的,而非十進制。
上圖:float 類型存儲方式
這里用 0 10000010 00010000 00000000 0000000 來展示一下 float 類型的具體計算。理解這段需要會一些簡單的進制轉換,如果不會建議自學一下。通過二進制計算器,可以很容易得到它對應的十進制數(shù)是 1094975488 。
對于 float 類型來說,其指數(shù)為 10000010 ,即 129 ,再根據(jù)規(guī)定減去 127 ,最終得到其指數(shù)為 2 。對于尾數(shù)而言,由于一定由 1 開頭,因此最開始的這位 1 會被省略,因此其尾數(shù)實際為 10001000 00000000 00000000 ,即為 1.0001 * (2 ^ 2) ,換算成十進制為 4.25 。
如果希望對這兩個類型的數(shù)據(jù)進行簡單的加法運算,而不指定數(shù)據(jù)類型,匯編中會直接進行對位相加,即對應的每一個 0 或 1 相加,并加上前一位的進位。這樣計算會得到 10000010 00010000 00000000 00000000 ,顯然不是我們想要的 1094975492.25 。
如果不指定數(shù)據(jù)類型,計算就會得到錯誤的答案。由此可見,在內存中無意義的一串二進制數(shù),我們可以通過規(guī)定每一位的意義,來得到不一樣的結果。
為什么要指定這么多數(shù)據(jù)類型
很多同學可能也有這樣的疑惑,為什么光一個整數(shù)就有 short , int , lang 三種,浮點數(shù)也有 float , double 兩種,甚至還有與 int 類型對應的 char 類型呢?只需要 int 和 double 不就夠用了么?
由于現(xiàn)在的計算機內存普遍充裕,不太會遇到內存空間不足的問題,因此可以直接選用高精度的數(shù)據(jù)類型進行存儲與計算。然而,在多年以前,或是在嵌入式領域,這類存儲空間非常緊張的條件下,不同數(shù)據(jù)類型的差距就顯現(xiàn)出來了。
由于在這些條件下,每一個 bit 都顯得彌足珍貴,因此程序員會想方設法地優(yōu)化存儲空間的使用,能夠用低精度的就不會用高精度。
而浮點數(shù)根據(jù)上文對存儲方式的解釋可以看出,精度越高,其所能表示的大小越小,因此在表示較大的,對精度要求高的數(shù)據(jù)時,就必須使用高精度的數(shù)了,反之則可以用低精度的節(jié)省空間。由于 float 所能表示的精度實在是非常低,因此建議在學校編寫 C 程序時,如無特殊要求,一律使用 double 類型。
而 char 類型則較為特殊,可以與整數(shù)類型進行相互轉換。在單片機等環(huán)境中,由于存儲空間有限,因此更傾向于使用 char 這一只消耗一字節(jié)的數(shù)據(jù)類型,而不是 int 等更大的。另外, char 一般用來表示字符,因此如果要表示例如 'A' 這種字符型的數(shù)據(jù)時,一般用 char 類型。 char 類型在后文有關字符串的部分還會提到。
然而, short 類型不一定就比 int 類型消耗的空間少, long 也不一定就比 int 表示的精度高,一切由編譯器決定(只需要遵守 2 <= short <= int <= long 就是符合規(guī)定的)。因此如果真的有需要,可以用 char 來降低消耗,而不是使用 short 。
上圖:數(shù)據(jù)類型在不同操作系統(tǒng)下的大小
由此可見,雖然常用的數(shù)據(jù)類型就這么幾個,但是其他的類型也都有其存在的意義,可以不用,但不能沒有。數(shù)據(jù)類型一旦確定,該變量在內存中所分配的大小,以及每一位所代表的意義,也就隨之確定下來了。
數(shù)組與指針
明白數(shù)據(jù)類型,接下來就可以定義數(shù)組了。一個數(shù)組是由一定數(shù)量的,相同數(shù)據(jù)類型的變量組成的一種數(shù)據(jù)結構,也就是說,一個數(shù)組可以由一定數(shù)量的其他數(shù)組組成,而這些數(shù)組也可以由數(shù)組組成,形成套娃。
在 C 語言中,數(shù)組在定義時必須顯式指定其長度與數(shù)據(jù)類型,而在一些其他語言——如 Java、 Python 中——可以不斷擴展數(shù)組的長度,但 C 語言中卻不能這樣做。這又是為什么呢?這需要從如何在內存中生成一個數(shù)組說起。
數(shù)組的生成
我們在 C 語言中創(chuàng)建數(shù)組時,會指定數(shù)組的數(shù)據(jù)類型和長度,而編譯器可以根據(jù) 數(shù)據(jù)類型 * sizeof(數(shù)據(jù)類型) 推算出這一數(shù)組具體需要占據(jù)多大的內存空間,進而在程序運行到這一步,需要創(chuàng)建數(shù)組時,為其在內存中申請符合要求的,連續(xù)的一段空間進行數(shù)組的生成。但為什么要連續(xù)的空間,而不能斷斷續(xù)續(xù)呢?
數(shù)組在訪問時,會首先找到其內存地址。數(shù)組在創(chuàng)建時的變量名,實際也是一個指向數(shù)組第一項的一個指針(后面會講到)。隨后,根據(jù)具體訪問哪一項,如第 n 項,就會將這一地址加上 n * sizeof(數(shù)據(jù)類型) ,就能直接找到這一項的內存地址。因此數(shù)組在生成時需要申請連續(xù)的內存地址,否則就無法做到這么高效的訪問速度。
C 語言中的數(shù)組與其他語言的數(shù)組
那么問題來了:為什么別的語言能做到擴展數(shù)組長度,通過變量來初始化數(shù)組,而不是通過常數(shù)來指定呢?
事實上,在最底層的實現(xiàn)中,它們也是會指定一個具體的值來生成數(shù)組,其原理與 C 語言完全相同。但是在需要更長的數(shù)組時,會申請一段更長的連續(xù)內存空間來存放新數(shù)組,并將原來的舊數(shù)組完全復制一份過去。當然,各種語言會存在一定的優(yōu)化,申請的空間會比所需的空間略大一些,防止重復不斷的復制降低運行效率。
上圖:數(shù)組動態(tài)擴容
由于 C 語言的數(shù)組是最原始的數(shù)組,語言本身不會自行進行申請新地址,復制舊數(shù)組等操作,因此需要在初始化時就指定好長度。
指針的作用
另外一個初學時難以理解的概念就是指針了。 先來看下指針到底是什么。指針是一個存放內存地址的變量,也就是說可以直接訪問并操作內存。
上圖:指針示意圖
圖中 a 表示一個整數(shù)類型的變量,值為 100,在內存中存放在 0x0010 這一地址中。因此可以定義一個指針 x 指向這一地址。可以理解為 x 中存放了 0x0010 這一地址,訪問這一指針就相當于訪問這一地址。這就引出了一個問題:既然指針存放的是地址,訪問的也是地址,那么為什么還要定義一個類型呢?
原因很簡單,因為要取出該地址具體存放的值。前面說過,數(shù)據(jù)類型決定了該數(shù)據(jù)所占的大小,以及每一位具體所代表的內容。因此,要取出該地址具體存放的值,必須要知道其數(shù)據(jù)類型才行。這就是為什么 C 語言中定義指針時要指定數(shù)據(jù)類型,指明該內存地址存放數(shù)據(jù)的具體數(shù)據(jù)類型。
指針與數(shù)組的關系
指針與數(shù)組的關系也非常緊密。定義數(shù)組時取的名稱就是指向數(shù)組第一個元素的指針,也就是說,要訪問數(shù)組 a 中的 a[0] ,可以直接訪問 *a 。以此類推,可以通過訪問 *(a+1), *(a+2)來訪問 a[1], a[2] 。這是因為在定義數(shù)組時已經指定了數(shù)據(jù)類型,因此這里的 +1 就不是簡單的加法,而是在指針存儲的地址的基礎上,加上 sizeof(a[0]) (這里的 sizeof 用來獲取某一變量在存放時使用的內存大?。纳蠄D可以看出,每個 int 類型如果占了 4 個字節(jié),那么每次 +1 都會將內存地址 +4 再訪問。
需要注意的是,通過這種方式訪問數(shù)組會有數(shù)組越界的問題。也就是說,如果定義了一個長為 n 的數(shù)組,但是通過 *(a+n) 來訪問第 n+1 位,C 語言并不會有任何的錯誤提示,只會返回一個存儲在該內存地址的,根據(jù)定義的數(shù)據(jù)類型來計算得出的值。很多情況下無法分辨到底是否越界,因此使用這種方式訪問需要小心謹慎。
另外,雖然數(shù)組名是一個指針,但是是一個常量,因此不能給其賦值。
字符與字符串
之前提到, char 類型多用于表示字符。字符串是由字符組成的,其底層是一串由 char 類型的變量組成的數(shù)組,因此可以通過 char* 或是 char[] 來生成字符串。賦值時,可以通過數(shù)組一個一個字符賦值,也可以通過雙引號直接賦值。
在一些其他編程語言中,會專門有一個數(shù)據(jù)類型 String 來表示字符串,但在 C 語言中并沒有。因此對字符串的處理就等價于對字符數(shù)組的處理。
在處理字符串時需要注意,數(shù)組長度是包含最后的 \0 的,而 strlen 函數(shù)則不會。另外,如果通過數(shù)組的方式一個個添加字符,且在最后沒有加上 \0 ,那么則由于數(shù)組越界進而使得字符串中的數(shù)據(jù)出現(xiàn)錯誤。為了防止出現(xiàn)這一錯誤,最好直接通過雙引號進行賦值。另外,不論字符數(shù)組有多長,第一次出現(xiàn) \0 就代表著字符串的結束。
由于 char 實際就是一個數(shù)字,因此在解決如 大小寫轉換 之類的問題時,可以通過 +- 32 來解決。這里的 32 來自于 ASCII 碼表,每一個數(shù)字都對應著一個字符。 碼表 可以在網上輕松找到。如果不記得具體的大小,可以通過格式化輸出 %d 直接查看對應的數(shù)字,如果記不得大小寫間差了 32,可以用 'a' - 'A' 來臨時湊合使用一下。
一些要注意的語法格式
老師可能不會著重提語法格式,但是實際上良好的格式能夠顯著提升代碼的可讀性,方便理解與找錯。
main 函數(shù)
首先,根據(jù) C99 標準, main 函數(shù)應當定義為 int main(void) {...} 或是 int main(int argc, char *argv[]) {...} 。前一種在現(xiàn)在學習的階段更為常用,其中的 void 一般是可以省略的。但是,最后的 return 0; 是可以被省略的,如果不寫將會默認返回 0。有些老師或者書上可能會寫成 void main() {...} ,或是說一定要顯示地寫出 return 0; 。這些都是錯誤的。具體標準可以參考 標準文檔 。
main 函數(shù)定義
上圖:main 函數(shù)返回值
縮進
縮進與換行的使用也是很重要的。 { 與 } 應當獨占一行,其中所包裹的內容應當進行一次縮進。另外,盡管 if 語句或是 for 語句等,如果大括號內只包含一條語句,很多老師會去掉大括號,并寫在一行內。這并不是一個好習慣,應當照樣換行,加上大括號與縮進,方便閱讀與之后的修改。
可以使用在線格式化,或是 astyle 等格式化軟件來進行代碼格式化。
其他一些小 tips
除了以上的這些老師可能會一筆帶過的內容外,還有一些我在編程中所學到的一些小 tips:
Warning(敲代碼是不可忽略的警告)
希望所有看到這篇文章的、需要學習 C 語言的同學們能夠順利學好這門課,取得一個好成績。
對于準備成為一名優(yōu)秀程序員的朋友,如果你想更好的提升你的編程核心能力(內功),讓自己成為一個具有真材實料的厲害的程序員,不妨從現(xiàn)在開始!C/C++,永不過時的編程語言~
編程學習書籍分享:
編程學習視頻分享:
整理分享(多年學習的源碼、項目實戰(zhàn)視頻、項目筆記,基礎入門教程)
歡迎轉行和學習編程的伙伴,利用更多的資料學習成長比自己琢磨更快哦!
對于C/C++感興趣可以關注小編在后臺私信我:【編程交流】一起來學習哦!可以領取一些C/C++的項目學習視頻資料哦!已經設置好了關鍵詞自動回復,自動領取就好了!
1.《計算機專業(yè):老師也許會跳過的 C 語言基礎知識,趕緊補上一課》援引自互聯(lián)網,旨在傳遞更多網絡信息知識,僅代表作者本人觀點,與本網站無關,侵刪請聯(lián)系頁腳下方聯(lián)系方式。
2.《計算機專業(yè):老師也許會跳過的 C 語言基礎知識,趕緊補上一課》僅供讀者參考,本網站未對該內容進行證實,對其原創(chuàng)性、真實性、完整性、及時性不作任何保證。
3.文章轉載時請保留本站內容來源地址,http://f99ss.com/gl/3087525.html