20191205張瀟——學習筆記1
一、知識點總結:
第一章:引言
前言:
第一章是本書的引言的部分,講述了Unix的歷史,介紹了Linux的開發及其各種發行版,還解釋了Linux的啟動過程,描述了Unix/Linux檔案系統組織、檔案型別和常用的Unix/Linux命令,最後介紹了使用者管理和維護Linux系統需執行的一些系統管理任務。
學到了什麼?
- 開篇介紹系統程式設計的作用:是電腦科學和計算機工程教育不可或缺的一部分。
- 這本書的目標:(1)強化學生的程式設計背景知識、(2)可以應用動態資料結構(包括C結構、指標、連結串列和鏈樹)、(3)理解程序概念和程序管理、4)嘗試使用使用者級執行緒,實現執行緒同步工具,並通過使用者級執行緒練習併發程式設計、(
- 通過閱覽本書的獨特之處,感覺到這本書很適用於我們學生群體,既考驗了學生的動手實踐能力,又考驗了小組團隊合作能力。
- Unix和Linux歷史:Unix系統衍生出了兩個系統(BSD伯克利軟體釋出和System V),BSD是加州大學伯克利分校搞出來的,而System V是AT&T的;Linux系統的組成:(1)Linux核心功能:程序排程:Linux屬於搶佔式多工作業系統。計算機只有一個
- 對於虛擬機器上的Linux,我在大一上時候採用的是VirtualBox,但是後來隨著用的次數越來越多,我發現它並不是特別好用,時常會出現卡頓的情況,所以這半年打算下載VMware來啟動Linux。
- Unix/Linux檔案系統組織:檔案型別有目錄檔案、非目錄檔案(常規檔案和特殊檔案(字元特殊檔案與塊特殊檔案))、符號連結檔案。
- Ubuntu Linux系統管理:(1)使用者賬戶:當用戶使用登入名和密碼登入後,登入程序將通過獲取使用者的
第二章:程式設計背景
前言:
第二章主要講述了系統程式設計所需的背景資訊:介紹了基於GUI的文字編輯器(這個在大二下學java時有所涉及);展示瞭如何在命令和GUI模式下使用EMACS編輯器來編輯、編譯和執行C語言程式;並且向我們闡述了程式開發的步驟;詳細闡釋了函式呼叫慣例和執行時堆疊的使用;展示了C語言程式與彙編程式碼的連結;學會運用GUNmake工具編寫makefile;提及瞭如何使用GDB除錯工具除錯C語言程式,並防止出現除錯過程中出現的常見錯誤;複習了C語言中的結構和指標。以及資料結構中的二叉樹模擬Unix/Linux檔案系統樹中的操作等。
學到了什麼?
- Linux中的文字編輯器:第二章介紹了三個文字編輯器,有vim、gedit和EMACS。對於vim而言有三種不同的操作模式:命令模式、插入模式和末行模式;gedit是GNOME桌面環境預設的文字編輯器;EMACS(GUN EMACS 2015)可以在很多不同的平臺上執行。
- 程式開發:步驟:(1).建立原始檔:使用文字編輯器建立一個或多個程式原始檔;(2).用gcc把原始檔轉換成二進位制可執行檔案;(3).完成gcc三大步驟:1.將C原始檔轉換為彙編程式碼檔案;2.把彙編程式碼轉換成目的碼;3.執行連結器:將.o檔案的所有程式碼段組合成單一程式碼段,再將所有資料段組合成單一資料段,最後將所有BSS段組合成單一bss段,用.o檔案中的重定位資訊調整組合程式碼段中的指標以及組合資料段、bss段中的偏移量,便於用符號表來解析各個.o檔案之間的交叉引用。
- C語言程式變數:全域性變數、區域性變數、靜態變數、自動變數和暫存器變數
- 建立二進位制可執行檔案的方式有:靜態連結和動態連結。其中動態連結的優點是:可減小每個a.out檔案的大小;許多執行程式可在記憶體中共享相同的庫函式;修改庫函式不需要重新編譯原始檔。
- 大部分C編譯器和連結器可生成多種不同格式的可執行檔案:(1)二進位制可執行平面檔案;(2)a.out可執行檔案;(a.out檔案的內容包括檔案頭、程式碼段、資料段和符號表)(3)ELF可執行檔案。
- Makefile:一個make檔案由一系列目標項(建立或更新的檔案,也可能是make程式要引用的指令或標籤)、依賴項和規則(使用依賴項列表構建目標項所需的命令)組成。
- 錯誤總結:C語言程式中常見的錯誤:1.未初始化的指標或含有錯誤值的指標;2.陣列下標越界;3.字串指標和char陣列使用不當;4.assert巨集
- C語言結構體的屬性:(1)定義C語言結構體時,該結構體的每個欄位都必須具有一個編譯器已知的型別,但自引用的指標除外;(2)每個C語言結構體資料物件都分配了一個連續記憶體塊;(3)一個結構體的大小可以由sizeof(struct type)確定;(4)假設“NODE x,y;”為兩個相同型別的結構體;(5)C語言聯合體與結構體類似。
- 連結串列操作:構建、遍歷、搜尋、插入、刪除、重新排序;
- 其中第二章的陣列、指標、連結串列和樹的知識是以前學的內容,這裡我就不再過多贅述。
最有收穫的內容:
看完第一章和第二章內容之後,我對makefile的知識仍然有所不清楚,對於書中講解的內容很感興趣,所以我便去查閱了很多資料來進一步瞭解這方面知識。以下是我的總結:
makefile
1.1Makefile介紹
Makefile的核心是“自動化程式設計”,簡簡單單的make命令就可以使很龐大的系統按照我們自定義的依賴規則和執行命令的順序進行編譯。其中Makefile最重要的語法是
target:prerequisites
Command
其中有兩條規則:
- 要得到target,需要執行命令command;
- Target依賴prerequisites,當prerequisites中至少有一個檔案比target檔案更新的時候,command才被執行。
我看到網路上很多人說在Unix下的軟體編譯,你就得自己寫Makefile了,會不會寫Makefile,從一個側面說明了一個人是否具備完成大型工程的能力。確實,Makefile關係到了整個工程的編譯規則,一個工程中的原始檔不計數,其按型別、功能、模組分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些檔案需要先編譯,哪些檔案需要後編譯,哪些檔案需要重新編譯,甚至於進行更復雜的功能操作,因為makefile就像一個Shell指令碼一樣,其中也可以執行作業系統的命令。makefile帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編譯,極大地提高了軟體開發的效率。make是一個命令工具,是一個解釋makefile中指令的命令工具,一般來說,大多數的IDE都有這個命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可見,makefile都成為了一種在工程方面的編譯方法。
1.2Makefile工作
在預設的方式下,當我們輸入make命令時:
- make會在當前目錄下找名字叫“Makefile”或“makefile”的檔案。
- 如果找到,它會找檔案中的第一個目標檔案(target),在上面的例子中,他會找到“edit”這個檔案,並把這個檔案作為最終的目標檔案。
- 如果edit檔案不存在,或是edit所依賴的後面的 .o 檔案的檔案修改時間要比edit這個檔案新,那麼,他就會執行後面所定義的命令來生成edit這個檔案。
- 如果edit所依賴的.o檔案也存在,那麼make會在當前檔案中找目標為.o檔案的依賴性,如果找到則再根據那一個規則生成.o檔案。(這有點像一個堆疊的過程)
- 當然,你的C檔案和H檔案是存在的啦,於是make會生成 .o 檔案,然後再用 .o 檔案宣告make的終極任務,也就是執行檔案edit了。
說明:make會一層一層地去找檔案的依賴關係,直到編譯出第一個目標檔案,在找尋的過程中,如果出現錯誤,那麼make就會直接退出,並報錯;make只管檔案的依賴性,即,如果在我找了依賴關係之後,冒號後面的檔案還是不在,那麼make就不工作了。
1.3Makefile使用變數
在 Makefile中的定義的變數,就像是C/C++語言中的巨集一樣,它代表了一個文字字串,在Makefile中執行的時候其會自動原模原樣地展開在所使用的地方。其與C/C++所不同的是,你可以在Makefile中改變其值。在Makefile中,變數可以使用在“目標”,“依賴目標”,“命令”或是 Makefile的其他部分中。變數的命名字可以包含字元、數字,下劃線(可以是數字開頭),但不應該含有“:”、“#”、“=”或是空字元(空格、回車等)。變數是大小寫敏感的,“foo”、“Foo”和“FOO”是三個不同的變數名。傳統的Makefile的變數名是全大寫的命名方式,但我推薦使用大小寫搭配的變數名,如:MakeFlags。這樣可以避免和系統的變數衝突,而發生意外的事情,有一些變數是很奇怪字串,如“$<”、“$@”等,這些則是自動化變數。
下面附上我在部落格園上找到的一張圖片,巨集觀地介紹了Makefile各個方面:
上課老師講的內容:
快捷鍵:
Ctrl+alt+T——開啟終端;
Ctrl+shift+T——開啟另一個終端;
alt+1、2、3——切換介面(一般是介面1用來編寫程式碼、介面2用來編譯、介面三用來除錯);
gcc+檔名——編譯;
!142——顯示第142行程式碼;
history——顯示歷史命令;
預處理:gcc -E hello.c -o hello.i,gcc -s hello.i -o hello.s;
file:檢視檔案格式;
二進位制檔案讀寫:od -tc-tx4 hello.c;
預處理:gcc -E xx.c-o xx.i;編譯:gcc -S xx.c-o xx.s;彙編:gcc -C xx.c-o xx.o;
程式碼包括:虛擬碼、產品程式碼、測試程式碼;
-L:指定庫路徑,-l:指定使用哪個庫;
gcc src/hello.c -Iinclude;
…………
二、問題與解決思路
我有如下兩個問題:
(1)為什麼每個C語言程式都必須有一個main()函式?
(2)是否可以使用中序遍歷或後序遍歷來儲存和重構二叉樹?
我的解決思路如下:
第一問:
- 通過看書
我知道gcc的編譯過程分為三步:
第一步將 *.c 檔案分別通過編譯器解析成組合語言*.s。
第二步將 *.s 檔案分別通過彙編器生產目標檔案*.o 。
第三步將 c.o檔案通過連結器合成一個 .out的可執行檔案。
當執行.out或者.exe 可執行檔案時,程式入口通常是main函式。
- 查閱資料
在 linux 裡面,根據TIS 的ELF規範,一個可執行程式,也就是一個可執行的物件檔案,它有一個程式進入點,這個進入點不是我們寫的 main 函式,而是位於 c 庫中的 _start 彙編程式碼中。在這個彙編程式碼中,它會去呼叫我們寫的 main C函式,而這個呼叫是寫死的,所以我們沒辦法不用 main 函式。 實際上,假如你的C程式沒用用 main 函式,那用 gcc 連結的時候,將會報錯。
- 自我總結
一個程式中,必須要有一個入口函式來告訴連結器指明程式碼從哪裡開始執行。大部分連結器的預設函式入口都是main函式。所以當然也可以告訴連結器,入口函式是不是main函式,而是其他函式。自然而然,這個函式名字只要你願意,改什麼都可以,只需要在連結的時候告訴連結器就可以。所以C程式包括其他高階語言的程式main()函式並不都是必需的,只不過是連結器的預設指明的程式入口是main而已。
第二問:
- 通過看書
重構二叉樹目前主要是採取遞迴的方式,只能通過前序,中序或者後續,中序進行重構,而前序和後序是不能夠重構的,因為在得知根節點後只有中序遍歷才能確定左子樹和右子樹的數目。
二叉樹的幾種遍歷
前序遍歷:根結點 —> 左子樹 —> 右子樹
中序遍歷:左子樹—> 根結點 —> 右子樹
後序遍歷:左子樹 —> 右子樹 —> 根結點
層次遍歷:只需按層次遍歷即可
- 查閱資料
這裡我主要以先序和後序遍歷重構二叉樹介紹:
解題思路是:
(1)首先先序中的第一個元素一定是二叉樹的根節點。根據這個根節點找到在中序中的位置,那麼這一位置左邊的元素全部是二叉樹的左孩子,這一節點的所有右邊節點全部是二叉樹的右孩子。
(2)其次構造右子樹和左子樹。構造思路是:從先序和中序中找到左子樹和右子樹的先序和中序序列,然後遞迴構造,直至所有節點使用完。即得到重構的二叉樹。
3.自我總結
通過思考我總結了以下關於如何用中序或者後序遍歷來重構二叉樹的解題步驟:
1、後序遍歷的最後一個節點即為根節點;
2、根據根節點,在中序遍歷中找出左子樹和右子樹,並統計左子樹和右子樹的個數;
3、遞迴構建左子樹和右子樹。
三、實踐內容
我實踐的內容是設計一個優先佇列,並且為這個優先佇列設計一個數據結構,該題借鑑於課本課後題,我適當地修改了一下:
說明
一、優先佇列的定義:
優先佇列是0個或多個元素的集合,每個元素都有一個優先權或值,對優先佇列執行的操作有查詢、插入一個新元素、刪除。
二、我採用了C語言來設計此程式
實現本優先佇列的初始化,查詢,插入,刪除操作,並且控制其查詢,插入,刪除操作的演算法時間複雜度為O(logn)。所以採用堆正好能實現該時間複雜度。相關程式碼以及圖片在附件裡:
- 最大優先佇列操作:先插入三個元素1,2,3,;權值分別為3,5,8;再刪除最大權值元素是3,最後通過查詢最大權值元素為2,權值為5,如下圖:
- 最小優先佇列操作:先插入兩個元素3,2;權值分別為3,5。再刪除最小權值元素為3,最後查詢最小權值元素為2,如下圖:
實踐二:
另外有我自己在VirtualBox裡實踐的一些程式碼如下圖:
程式碼:
#include <stdio.h>
int func(int n){
int sum=0,i;
for(i=0; i<n; i++)
sum+=i;
}
return sum;
int main(){
int i;
long result =0;
for(i=1; i<=100;i++){
result +=i;
printf("result[1-100] =%ld \n", result );
printf("result[1-250] =%d \n", func(250) );
}
編譯過程:
執行結果: