C语言入门(一)——程序的基本概念
程序的基本概念
程序和編程語言
自然語言和形式語言
程序的調試
第一個程序
程序的基本概念
程序和編程語言
1.程序
程序(Program)告訴計算機應如何完成一個計算任務,這里的計算可以是數學運算,比如解方 程,也可以是符號運算,比如查找和替換文檔中的某個單詞。從根本上說,計算機是由數字電路組 成的運算機器,只能對數字做運算,程序之所以能做符號運算,是因為符號在計算機內部也是用數字表示的。此外,程序還可以處理聲音和圖像,聲音和圖像在計算機內部必然也是用數字表示的,這些數字經過專門的硬件設備轉換成人可以聽到、看到的聲音和圖像。程序由一系列指令組成,?指令是指示計算機做某種運算的命令,通常包含以下幾類:
- 輸入(Input):從鍵盤,文件或者其他設備獲取數據.
- 輸出(Output):把數據顯示到屏幕,或者存入一個文件,或者發送到其他設備.
- 基本運算:執行最基本的數學運算和數據存取.
- 測試和分支:測試某個條件,然后根據不同的測試結果執行不同的后續指令.
- 循環:重復執行一系列操作
基本我們用過的任何一個程序,都是由這幾類指令組成的,程序是雖然很復雜,但是編寫程序能夠用到的指令只有這簡單的幾種,這中間的東西需要我們來填充,所以編寫程序就是一件很復雜的工作.
實質就是:把復雜的問題分解為子任務,把子任務再分解成更為簡單的任務,層層分解,直到最后簡單得可以用上面的指令來完成.
2.編程語言
編程語言(Programming Language)分為低級語言(Low-level Language)和高級語言(High-level Language)。機器語言(Machine Language)和匯編語言(Assembly Language)屬于低級語言,直接用計算機指令編寫程序。而C、C++、Java、Python等屬于高級語言,用語句(Statement)編寫程序,語句是計算機指令的抽象表示。舉個例子,同樣一個語句用C語言、匯編語言和機器語言分別表示如下:| 編程語言 | 表示形式 |
| C語言 | a=b+1 |
| 匯編語言 | mov? 0x804a01c,%eax add? $0x1,%eax mov %eax,0x804a18 |
| 機器語言 | a1 1c a0 04 08 83 c0 01 a3 18 a0 04 08 |
?計算機中只能對數字做運算處理,符號,聲音等在計算機內部都需要用數組來表示,指令也不例外,上述表中機器語言都是由十六進制數字組成的.最早一批的程序員就是使用機器語言進行編程的,但是是很麻煩的,需要查詢大量的表格來確定每個數字代表的意思,編寫出來的程序也不是很友好,于是再次基礎上有了匯編語言,就是把機器語言中一組一組的數字用助記符表示,直接用這些助記符寫出匯編程序,然后讓匯編器去查表把助記符替換成數字,也就把匯編語言翻譯成了機器語言.從上面的例子可以看出,匯編語言和機器語言的指令是一一對應的,匯編語言有三條指令,機器語言也有三條指令,匯編器就是做一個簡單的替換工作,例如在第一條指令中,把mov ?,%eax這種格式的指令替換成機器碼a1 ?,?表示一個地址,在匯編指令中是0x804a01c,轉換成機器碼之后是1c a0 04 08.(小端表示)
從上述例子我們還可以看出來,C語言的語句和低級語言的指令之間并不是簡單都得一一對應關系,一語句a=b+1;語句要翻譯成三條匯編或者機器指令,這個過程我們稱為編譯,由編譯器來完成,顯然編譯器的功能比匯編器要復雜的多.用C語言編寫的程序必須經過編譯轉成機器指令才能被計算機指令,編譯需要花一些時間,這是用高級語言編譯的一個缺點,然而更多的是優點.首先,用C語言編程更容易,寫出來的代碼更緊湊,可讀性更強,出了錯也更容易改正.其次,C語言是可移植的或者稱為平臺無關的.
首先來解釋一下平臺這個詞的意思,可以指的是計算機體系結構,也可以指的是操作系統,也可以指開發平臺(編譯器,連接器等).不同的計算機體系結構有不同的指令集,可以識別的機器指令格式是不同的,直接用某種體系結構的匯編或機器指令寫出來的程序只能在這種體系結構的計算機上運行,然后各種體系結構的計算機都有各自的C編譯器,可以把C程序編譯成各種不同體系結構的機器指令,這意味著用C語言寫的程序只需要稍加修改甚至不用修改就可以在不用的計算機上編譯運行.各種高級語言也都是具備C語言的這些優點,所以絕大部分程序都是用高級語言編寫的,只有和硬件關系密切的少數程序(例如驅動程序)才會用到低級語言.還要注意一點,即使在相同的體系結構和操作系統下,用不同的C編譯器編譯同一個程序得到的結果也有可能是不同的,C語言有些語言特性在C標準種并沒有明確規定,各編譯器有不同的實現方式,編譯出來的指令行為特性也會不同,應該盡量避免使用不可移植的語法特性.
總結一下編譯執行的過程,首先你用文本編輯器寫一個C程序,然后保存成一個文件,例如test.c.這稱為源代碼或源文件,然后運行編譯器對它進行編譯,編譯的過程并不執行程序,而是把源代碼全部翻譯成機器指令,再加上一些描述信息,生成一個新的文件,例如a.out,這成為可執行文件,可執行文件可以被操作系統加載運行,計算機執行該文件中由編譯器生成的指令.
?有些高級語言以解釋(Interpret)的方式執行,解釋執行過程和C語言的編譯執行過程很不一樣。
例如編寫一個Shell腳本script.sh,內容如下: #! /bin/sh VAR=1 VAR=$(($VAR+1)) echo $VAR?定義Shell變量VAR的初始值是1,然后自增1,然后打印VAR的值。用Shell程序/bin/sh解釋執行這個腳本,結果如下:
$ /bin/sh script.sh 2?這里的/bin/sh稱為解釋器(Interpreter),它把腳本中的每一行當作一條命令解釋執行,而不需要
先生成包含機器指令的可執行文件再執行。如果把腳本中的這三行當作三條命令直接敲到Shell提示符下,也能得到同樣的結果: $ VAR=1 $ VAR=$(($VAR+1)) $ echo $VAR 2編程語言仍在發展演化。以上介紹的機器語言稱為第一代語言(1GL,1st Generation Programming Language),匯編語言稱為第二代語言(2GL,2nd Generation Programming Language),C、C++、Java、Python等可以稱為第三代語言(3GL,3rdGenerationProgramming Language)。目前已經有了4GL(4th Generation Programming Language)和5GL(5th Generation Programming Language)的概念。3GL的編程語言雖然是用 語句編程而不直接用指令編程,但語句也分為輸入、輸出、基本運算、測試分支和循環等幾種,和 指令有直接的對應關系。而4GL以后的編程語言更多是描述要做什么(Declarative)而不描述具體 一步一步怎么做(Imperative),具體一步一步怎么做完全由編譯器或解釋器決定,例如SQL語言 (SQL,Structured Query Language,結構化查詢語言)就是這樣的例子。
問題一:解釋執行的語言相比編譯執行的語言有什么優缺點??1.主體不同:
編譯執行:由編譯程序將目標代碼一次性編譯成目標程序,再由機器運行目標程序
解釋執行:將源語言直接作為源程序輸入,解釋執行解釋一句后就提交計算機執行一句,并不形成目標程序。
2.優勢不同
編譯執行:相比解釋執行編譯執行效率高,占用資源小,適合復雜程序
解釋執行:開發速度快,出現嚴重BUG的幾率小。
3.缺點不同
編譯執行:兼容性差,例如在windows平臺上寫的編譯程序一般不可以在unix平臺上運行。
解釋執行:解析需要時間,不生成目標程序而是一句一句的執行的方式會造成計算機資源的浪費,即執行效率低。
各類語言分類:
- 編譯執行:GO語言、C語言、C++
- 解釋執行:python
- 半編譯半解釋型語言:java、C#
自然語言和形式語言
自然語言(Natural Language)就是人類講的語言,比如漢語、英語和法語。這類語言不是人為設 計(雖然有人試圖強加一些規則)而是自然進化的。形式語言(Formal Language)是為了特定應 用而人為設計的語言。例如數學家用的數字和運算符號、化學家用的分子式等。編程語言也是一種 形式語言,是專門設計用來表達計算過程的形式語言。 形式語言有嚴格的語法(Syntax)規則,例如,3+3=6是一個語法正確的數學等式,而3=+6$則不 是,H2O是一個正確的分子式,而2Zz則不是。語法規則是由符號(Token)和結構(Structure)的規則所組成的。Token的概念相當于自然語言中的單詞和標點、數學式中的數和運算符、化學分子式中的元素名和數字,例如3=+6$的問題之一在于$不是一個合法的數也不是一個 事先定義好的運算符,而2Zz的問題之一在于沒有一種元素的縮寫是Zz。結構是指Token的排列方式,3=+6$還有一個結構上的錯誤,雖然加號和等號都是合法的運算符,但是不能在等號之后緊跟加號,而2Zz的另一個問題在于分子式中必須把下標寫在化學元素名稱之后而不是前面。關于Token的規則稱為詞法(Lexical)規則,而關于結構的規則稱為語法(Grammar)規則. 當閱讀一個自然語言的句子或者一種形式語言的語句時,你不僅要搞清楚每個詞(Token)是什么 意思,而且必須搞清楚整個句子的結構是什么樣的(在自然語言中你只是沒有意識到,但確實這樣 做了,尤其是在讀外語時你肯定也意識到了)。這個分析句子結構的過程稱為解析(Parse)。例 如,當你聽到“The other shoe fell.”這個句子時,你理解the other shoe是主語而fell是謂語動詞,一 旦解析完成,你就搞懂了句子的意思,如果知道shoe是什么東西,fall意味著什么,這句話是在什 么上下文(Context)中說的,你還能理解這個句子主要暗示的內容,這些都屬于語(Semantic)的范疇。 雖然形式語言和自然語言有很多共同之處,包括Token、結構和語義,但是也有很多不一樣的地 方。 現在給出一些關于閱讀程序(包括其它形式語言)的建議。首先請記住形式語言遠比自然語言緊 湊,所以要多花點時間來讀。其次,結構很重要,從上到下從左到右讀往往不是一個好辦法,而應 該學會在大腦里解析:識別Token,分解結構。最后,請記住細節的影響,諸如拼寫錯誤和標點錯 誤這些在自然語言中可以忽略的小毛病會把形式語言搞得面目全非。程序的調試
編程是一件復雜的工作,因為是人做的事情,所以難免經常出錯。據說有這樣一個典故:早期的計 算機體積都很大,有一次一臺計算機不能正常工作,工程師們找了半天原因最后發現是一只臭蟲鉆 進計算機中造成的。從此以后,程序中的錯誤被叫做臭蟲(Bug),而找到這些Bug并加以糾正的 過程就叫做調試(Debug)。有時候調試是一件非常復雜的工作,要求程序員概念明確、邏輯清 晰、性格沉穩,還需要一點運氣。調試的技能我們在后續的學習中慢慢培養,但首先我們要區分清 楚程序中的Bug分為哪幾類。- 編譯時錯誤: 編譯器只能翻譯語法正確的程序,否則將導致編譯失敗,無法生成可執行文件。對于自然語 言來說,一點語法錯誤不是很嚴重的問題,因為我們仍然可以讀懂句子。而編譯器就沒那么 寬容了,只要有哪怕一個很小的語法錯誤,編譯器就會輸出一條錯誤提示信息然后罷工,你 就得不到你想要的結果。雖然大部分情況下編譯器給出的錯誤提示信息就是你出錯的代碼 行,但也有個別時候編譯器給出的錯誤提示信息幫助不大,甚至會誤導你。在開始學習編程 的前幾個星期,你可能會花大量的時間來糾正語法錯誤。等到有了一些經驗之后,還是會犯 這樣的錯誤,不過會少得多,而且你能更快地發現錯誤原因。等到經驗更豐富之后你就會覺 得,語法錯誤是最簡單最低級的錯誤,編譯器的錯誤提示也就那么幾種,即使錯誤提示是有 誤導的也能夠立刻找出真正的錯誤原因是什么。相比下面兩種錯誤,語法錯誤解決起來要容 易得多
- 運行時錯誤 編譯器檢查不出這類錯誤,仍然可以生成可執行文件,但在運行時會出錯而導致程序崩潰。 對于我們接下來的幾章將編寫的簡單程序來說,運行時錯誤很少見,到了后面的章節你會遇 到越來越多的運行時錯誤。讀者在以后的學習中要時刻注意區分編譯時和運行時(Run- time)這兩個概念,不僅在調試時需要區分這兩個概念,在學習C語言的很多語法時都需要區 分這兩個概念,有些事情在編譯時做,有些事情則在運行時做。
- 邏輯錯誤和語義錯誤 第三類錯誤是邏輯錯誤和語義錯誤。如果程序里有邏輯錯誤,編譯和運行都會很順利,看上 去也不產生任何錯誤信息,但是程序沒有干它該干的事情,而是干了別的事情。當然不管怎 么樣,計算機只會按你寫的程序去做,問題在于你寫的程序不是你真正想要的,這意味著程 序的意思(即語義)是錯的。找到邏輯錯誤在哪需要十分清醒的頭腦,要通過觀察程序的輸 出回過頭來判斷它到底在做什么。
第一個程序
通常一本教編程的書中第一個例子都是打印“Hello, World.”,用C語言寫這個程序可以這樣寫: #include <stdio.h> /* main: generate some simple output */ int main(void) {printf("Hello, world.\n");return 0; } 將這個程序保存成main.c,然后編譯執行 $ gcc main.c $ ./a.out Hello, world. gcc是Linux平臺的C編譯器,編譯后在當前目錄下生成可執行文件a.out,直接在命令行輸入這個可 執行文件的路徑就可以執行它。如果不想把文件名叫a.out,可以用gcc的-o參數自己指定文件名: 雖然這只是一個很小的程序,但我們目前暫時還不具備相關的知識來完全理解這個程序,比如程序 的第一行,還有程序主體的int main(void){...return 0;}結構,這些部分我們暫時不詳細解釋, 讀者現在只需要把它們看成是每個程序按慣例必須要寫的部分(Boilerplate)。但要注意main是一 個特殊的名字,C程序總是從main里面的第一條語句開始執行的,在這個程序中是指printf這條語 句. 第3行的/* ... */結構是一個注釋(Comment),其中可以寫一些描述性的話,解釋這段程序在 做什么。注釋只是寫給程序員看的,編譯器會忽略從/*到*/的所有字符,所以寫注釋沒有語法規 則,愛怎么寫就怎么寫,并且不管寫多少都不會被編譯進可執行文件中。 printf語句的作用是把消息打印到屏幕。注意語句的末尾以;號(Semicolon)結束,下一條語 句return 0;也是如此。 C語言用{}括號(Brace或Curly Brace)把語法結構分成組,在上面的程序中printf和return語句套 在main的{}括號中,表示它們屬于main的定義之中。我們看到這兩句相比main那一行都縮進 (Indent)了一些,在代碼中可以用若干個空格(Blank)和Tab字符來縮進,縮進不是必須的,但 這樣使我們更容易看出這兩行是屬于main的定義之中的,要寫出漂亮的程序必須有整齊的縮進. 正如前面所說,編譯器對于語法錯誤是毫不留情的,如果你的程序有一點拼寫錯誤,例如第一行寫 成了stdoi.h,在編譯時會得到錯誤提示: $ gcc main.c main.c:1:19: error: stdoi.h: No such file or directory 這個錯誤提示非常緊湊,初學者往往不容易看明白出了什么錯誤,即使知道這個錯誤提示說的是 第1行有錯誤,很多初學者對照著書看好幾遍也看不出自己這一行哪里有錯誤,因為他們對符號和 拼寫不敏感(尤其是英文較差的初學者),他們還不知道這些符號是什么意思又如何能記住正確的 拼寫?對于初學者來說,最想看到的錯誤提示其實是這樣的:“在main.c程序第1行的第19列,您試 圖包含一個叫做stdoi.h的文件,可惜我沒有找到這個文件,但我卻找到了一個叫做stdio.h的文 件,我猜這個才是您想要的,對嗎?”可惜沒有任何編譯器會友善到這個程度,大多數時候你所得 到的錯誤提示并不能直接指出誰是犯人,而只是一個線索,你需要根據這個線索做一些偵探和推 理。 有些時候編譯器的提示信息不是error而是warning,例如把上例中的printf("Hello, world.\n");改成printf(1);然后編譯運行: $ gcc main.c main.c: In function ‘main’: main.c:7: warning: passing argument 1 of ‘printf’ makes pointer from integer without a cast $ ./a.out Segmentation fault 這個警告信息是說類型不匹配,但勉強還能配得上。警告信息不是致命錯誤,編譯仍然可以繼續, 如果整個編譯過程只有警告信息而沒有錯誤信息,仍然可以生成可執行文件。但是,警告信息也是 不容忽視的。出警告信息說明你的程序寫得不夠規范,可能有Bug,雖然能編譯生成可執行文件, 但程序的運行結果往往是不正確的,例如上面的程序運行時出了一個段錯誤,這屬于運行時錯誤。 各種警告信息的嚴重程度不同,像上面這種警告幾乎一定表明程序中有Bug,而另外一些警告只表 明程序寫得不夠規范,一般還是能正確運行的,有些不重要的警告信息gcc默認是不提示的,但這 些警告信息也有可能表明程序中有Bug。一個好的習慣是打開gcc的-Wall選項,也就是讓gcc提示所 有的警告信息,不管是嚴重的還是不嚴重的,然后把這些問題從代碼中全部消滅。 比如把上例中的printf("Hello, world.\n");改成printf(0);然后編譯運行: $ gcc main.c $ ./a.out 編譯既不報錯也不報警告,一切正常,但是運行程序什么也不打印。如果打開-Wall選項編譯就會 報警告了 $ gcc -Wall main.c main.c: In function ‘main’: main.c:7: warning: null argument where non-null required (argument 1) 如果printf中的0是你不小心寫上去的(例如錯誤地使用了編輯器的查找替換功能),這個警告就 能幫助你發現錯誤。雖然本書的命令行為了突出重點通常省略-Wall選項,但是強烈建議你寫每一 個編譯命令時都加上-Wall選項。總結
以上是生活随笔為你收集整理的C语言入门(一)——程序的基本概念的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: tensorflow实现iris分类
- 下一篇: c语言冗余数据什么意思,冗余是什么意思_