在Ubuntu下搭建C/C++程式設計環境
要安裝程式設計環境,在Terminal中執行sudo apt-get install build-essential
。
桌面系統的快捷鍵
如果不用桌面系統,可以關閉。
開啟Terminal的快捷鍵是Ctrl+Alt+T
,使用快捷鍵Ctrl+Super+Up
最大化,Alt+F4
關閉。
使用快捷鍵Alt+Tab
切換視窗。
長按Super鍵(預設為Windows鍵),可以看到關於桌面的所有快捷鍵。
Terminal的一些基本命令
一般最常用的是cd, ls, mkdir, rmdir, cp, rm, mv, clear, pwd, shutdown.
一般使用時只需記住常用命令,不清楚的時候用man查詢。如需檢視更多命令可以閱讀《The Linux Command Line》(《Linux命令列大全》)。
編輯
不去管vim與Emacs的世紀之爭,vim真的是很強大的編輯器,擺脫對圖形介面的依賴,效率會有很大提高。vim有無窮盡的外掛,善加選擇利用,有很多針對程式設計的快捷功能是vs也提供不了的。
vim的學習曲線雖然有些陡峭,但還是很值得的。
網上有很多學習Vim的資源,推薦幾本書:《Vim使用者手冊中文版7.3》《A Byte of Vim》《Practical Vim》,還有這個視訊教程(需要翻牆)。
使用vim,一般只需動用肌肉“記住”一些常用操作(如下表),其餘功能用的時候查詢即可。
vim入門
vim入門主要是熟練使用基本命令。最直接的學習資源是在Terminal裡輸入vimtutor
或者學習上文提到的書籍和視訊,對常見命令均有講解。
推薦直接從官方文件或是書籍入手,比在網上搜來搜去只鱗片爪有效率的多。四處搜尋別人的學習經驗,出發點是少走彎路,結果反而欲速則不達,還不如沉下心好好看完一本書。
有幾張不同角度的cheat sheet,方便查閱。作者見文末參考連結。
在vim中,獲得幫助的命令是
:help
。如果不知道自己想查什麼,可以執行
:help user-manual
檢視整個使用者手冊的目錄。如果大概知道想使用的功能而不知道具體命令,可以使用
:helpgrep
在整個文件中搜索相關內容。比如想了解如何檢視詞首,可以執行:helpgrep
beginning of a word
:cnext
和:cprev
在搜尋結果間跳轉。更多關於幫助的幫助,參看《A Byte of Vim》中Vim en:Help這一節。
vim自定義配置
基本操作熟練之後,多半都會開始安外掛折騰自定義配置。
自定義配置與外掛包括vimrc、global plugin、filetype plugin、syntax highlighting plugin、compiler plugin。
一般將簡單配置記錄在vimrc檔案裡,高階功能使用外掛來實現。
在Linux系統中,vimrc檔案地址是$HOME/.vimrc
。vim安裝時自帶的外掛在$VIMRUNTIME/plugin/
目錄下。自定義外掛放在$HOME/.vim/plugin/
的相應子目錄下,且需在vimrc檔案裡設定自動載入此外掛。
詳細的配置與外掛使用及編寫請參看《A Byte of Vim》中Vim en:Plugins這一節。
偷了個懶,在github上發現一個很棒的配置方案spf13-vim,就直接安上了。
spf13-vim是執行在vim層之上,為方便程式設計,對vimrc和plugin都做了很多特殊優化配置的一個工具。
spf13-vim。安裝很容易,根據readme上的指示進行即可。
安裝成功後,可以開啟~/.vimrc
看到spf13-vim的預置配置。建立~/.vimrc.local
和~/.gvimrc.local
進行自定義配置。
spf13-vim已經預置了很多外掛,關於這些外掛的啟動和使用可以看spf13-vim的介紹和這些外掛自己的github頁面。此處唯一提醒的一點是<leader>
鍵在spf13-vim這裡是逗號,
。
我目前也在摸索中,有心得會隨時更新。這是我的操作速查表。
在Quora上看到有些人並不推薦使用spf13等工具,認為會引誘你學習這個工具的配置方式,而不是真正學習vim的配置檔案,使用自定義的針對自己需求的配置檔案才最符合vim精神。不過對於初學者來說,我覺得spf13-vim還是很有價值的,畢竟時間這麼寶貴,折騰是很累人的。
編譯執行
gcc
GCC(GNU Compiler Collection)是一組編譯工具的總稱,支援多平臺、多語言原始檔到可執行檔案的編譯與生成。其中也包括gcc(C編譯器)和g++(C++編譯器)。
推薦書籍《An Introduction to GCC》。
在GCC內部尋找幫助,使用gcc --help
,如果想看gcc選項的完整列表使用gcc
-v --help 2>&1 | more
。
最簡單的應用示例:一個hello.cpp檔案。一下語句就是編譯與執行。
1 2 |
g++ hello.cpp hello ./hello |
gcc常用命令
基本語法格式如下。
對於C:gcc [options] [filenames]
對於C++:g++ [options] [filenames]
上述命令列按編譯選項(options)指定的操作對給定的檔案(filenames)進行編譯處理。
選項主要列表如下。
選項 | 選項描述 |
---|---|
-c | 只對檔案進行編譯和彙編,但不進行連線,生成目標檔案”.o” |
-S | 只對檔案進行編譯,但不彙編和連線 |
-E | 只對檔案進行預處理,但不編譯彙編和連線 |
-g | 在可執行程式中包含標準除錯資訊 |
-o file1 [file2] | 將檔案file1編譯成可執行檔案file2 |
-v | 打印出編譯器內部編譯各過程的命令列資訊和編譯器的版本 |
-I dir | 在標頭檔案的搜尋路徑列表中新增dir目錄 |
-L dir | 在庫檔案的搜尋路徑列表中新增dir目錄 |
-static | 強制連結靜態庫 |
-lNAME | 連線名為libNAME的庫檔案 |
-Wall -W | 開啟GCC最常用的警告,GCC的warning一般格式為file:line-number:message |
-pedantic | 要求嚴格符合ANSI標準 |
-Wconversion | 開啟隱式型別轉換警告 |
-Wshadow | 開啟同名變數函式警告 |
-Wcast-qual | 開啟對特性移除的cast的警告,如const |
-O(-O1) | 對編譯出的程式碼進行優化 |
-O2 | 進行比-O高一級的優化 |
-O3 | 產生更高級別的優化 |
-Os | 產生最小的可執行檔案 |
-pg | 開啟效能測試,記錄每個函式的呼叫次數與時長 |
-ftest-coverage | 記錄每一行被執行的次數 |
-fprofile-arcs | 記錄每個分支語句執行的頻率 |
注意選項的大小寫。只在最後發行版時再使用優化。即使在最後發行版也應該加上-g選項。
幾種情境
以c++為例。
編譯單個檔案為可執行檔案:
g++ -Wall -W hello.cpp -o hello
編譯多個檔案為可執行檔案:
g++ -Wall -W main.cpp hello_fun.cpp -o newhello
編譯單個檔案為可執行檔案,連線靜態庫static library:
系統預設庫檔案在目錄
/usr/lib
和/lib
,還會自動搜尋/usr/local/lib/
和/usr/lib/
。
相應的,系統預設標頭檔案在目錄,會自動搜尋/usr/local/include/
和/usr/include/
。
顯式指定庫目錄與檔案,g++ -Wall -W calc.cpp /usr/lib/libm.a -o calc
或更好的寫法g++ -Wall -W -static calc.cpp -lm -o calc
,連線系統自動搜尋庫目錄裡的庫檔案。注意g++優先使用shared library。如果找到同名.so就不會用同名.a。所以如果需要強制使用.a檔案的話,應使用-static
。
如果不在自動搜尋的庫目錄,兩種方法。一般使用命令列這一種。
- 使用命令列
-I
新增搜尋標頭檔案的目錄和-L
新增搜尋庫檔案的目錄,例如g++ -Wall -W -static -I/code/test/include -I/code/another/include -I. -L/code/test/lib -L/code/another/lib -L. calc.cpp -ltest
。.表示當前目錄。- 或使用環境變數
C_INCLUDE_PATH
(C)或CPLUS_INCLUDE_PATH
(C++)和LIBRARY_PATH
。用如下語句新增搜尋路徑,之後就可以使用g++ -Wall -W -static calc.cpp -ltest
了。
1 2 3 4 CPLUS_INCLUDE_PATH = /code/test/include:/code/another/include:.:$CPLUS_INCLUDE_PATH export CPLUS_INCLUDE_PATH LIBRARY_PATH = /code/test/lib:/code/another/lib:.:$LIBRARY_PATH export LIBRARY_PATH
編譯單個檔案為可執行檔案,連線共享庫shared library:
如果只是用了系統預設庫,
g++ -Wall -W calc.cpp -lm -o calc
除了前文系統自動搜尋庫檔案目錄之外,如果要新增其他共享庫目錄,必須在環境變數LD_LIBRARY_PATH
中新增路徑。
1 2 LD_LIBRARY_PATH = /code/test/lib:/code/another/lib:.:$LD_LIBRARY_PATH export LD_LIBRARY_PATH之後可以使用
g++ -Wall -W calc.cpp /usr/lib/libm.so -o calc
或g++ -Wall -W -I/code/test/include -I/code/another/include -I. -L/code/test/lib -L/code/another/lib -L. calc.cpp -ltest
。
使用ldd calc
可以檢視該可執行檔案依賴哪些.so。
編譯多個檔案為靜態庫檔案.a:
將多個.o檔案集合為一個靜態庫檔案.a。其中cr表示”create and replace”。
1 2 3 g++ -Wall -c hello_fn.cpp g++ -Wall -c bye_fn.cpp ar cr libhello.a hello_fn.o bye_fn.o可以檢視一個.a檔案裡包含哪些.o。使用
ar t libhello.a
。
編譯多個檔案為共享庫檔案.so:
g++ x.cpp y.cpp z.cpp -fPIC -shared -o libtest.so
。其中-fPIC表示編譯為位置獨立的程式碼。
預處理
可以用gcc選項定義巨集,-DNAME會定義一個名為NAME的巨集。如g++ -Wall -DTEST
dtest.cpp
,定義了名為TEST的巨集。定義的巨集會對程式碼產生影響。
也可以為巨集定義值,-DNAME=VALUE。如g++ -Wall -DNUM=100 dtestval.cpp
,g++
-Wall -DNUM="2+2" dtestval.cpp
,g++ -Wall -DMESSAGE="\"Hello,World!\"" dteststr.cpp
。在程式碼中把巨集用括號括起來是好習慣。
效能
開啟-pg選項生成可執行檔案後。首先正常執行一次可執行檔案./hello
,然後執行gprof
hello
檢視資料。
開啟-fprofile-arcs -ftest-coverage選項生成可執行檔案後。首先正常執行一次可執行檔案./hello
,然後執行gcov
hello.cpp
檢視資料。未被執行的語句會在.gcov檔案中標記上-,可以通過執行grep '-' *.gcov
查詢未被執行的語句。
makefile檔案
對於較大的工程,如果還像前文一樣寫命令列就太痛苦了。而使用makefile可以管理整個工程的編譯規則,之後用一個make命令就可自動編譯,相對方便很多。
推薦書籍《跟我一起寫Makefile》和官方文件。內容看起來還是很晦澀…(・-・*)還好現在有了自動化工具,用來自動生成的工具也可以用工具自動生成了。
最基礎的用法
makefile基本規則是:
target1 target2 target3: prerequisite1 prerequisite2
command1
command2
其中target是目標檔案。prerequisites是要生成target所需檔案或目標。command是make需要執行的命令。規則表示了一個檔案的依賴關係,即target依賴於prerequisites,生成規則為command。如果target不存在或prerequisites中至少一個檔案比target新的話,command定義的命令就會執行。這就是makefile中最核心的內容。
make工作的流程是:
當我們輸入make命令之後,
- 讀入所有的makefile檔案。
- 讀入被include包括的其他makefile檔案。
- 初始化檔案中的變數。
- 推導隱式規則,分析所有規則。
- 為所有的目標檔案建立依賴關係鏈。
- 根據依賴關係,決定哪些目標要重新生成。
- 執行生成命令。
makefile檔名應為Makefile
或makefile
。
小示例
我為自己刷題的一個小專案寫了一個將幾個目錄下所有的原始檔一起編譯的一個makefile。在這裡看工程的目錄結構。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
EXEC = MyLeetCode SRC_DIR = ../../catch ../../include ../../src SOURCES := $(foreach x, ${SRC_DIR},\ $(wildcard \ $(addprefix ${x}/*,.cpp))) OBJECTS = $(SOURCES:.cpp=.o) CXX = g++ CXXFLAGS = -I../../catch -I../../include -I../../src -std=c++11 $(EXEC): $(OBJECTS) $(CXX) -o [email protected] $^ .PHONY : clean clean: $(RM) $(OBJECTS) $(RM) $(EXEC) |
使用CMake自動生成makefile
當處理較大型的專案時,手動書寫makefile就比較痛苦,這時用來用來自動化自動化工具makefile的自動化工具就是CMake。不過天下哪有那麼便宜的事,它也是要寫自己的CMakeLists.txt的。
推薦書籍《CMake實踐》《Mastering CMake》和官網幫助。
簡介
CMake是一個跨平臺的自動化建構系統,它是用一個名為CMakeLists.txt的檔案來描述構建過程,可以產生標準的構建檔案,如Unix的makefile或Windows Visual Studio的projects/workspaces。
檔案CMakeLists.txt需要手工編寫,也可以通過編寫指令碼進行半自動的生成。
在Linux平臺下使用CMake生成makefile並編譯的流程如下:
- 安裝CMake。在Ubuntu上安裝cmake很簡單
$sudo apt-get install cmake
。如果想要其Qt圖形介面另需安裝sudo apt-get install cmake-qt-gui
。一般不需要,在Ubuntu系統上用ccmake就可以了。 - 編寫CMakeLists.txt。
- 執行CMake。用
cd
將當前目錄設為生成目標目錄,執行命令ccmake srcdir
(文字介面)或cmake -i
(互動命令列),如果想使用Qt圖形介面使用cmake-gui
。 - Makefile已經生成。使用make命令進行編譯。
- 如果想清理工程。使用
make clean
。
簡單語法
註釋:#
命令語法:COMMAND(引數1 引數2 ...)
字串列表:A;B;C
或A
B C
。分號或空格分隔的值。
變數(字串或字串列表):
set(Foo a b c)
設定變數Foo。command(${Foo})
等價於command(a b c)
。command("${Foo}")
等價於command("a b c")
。command("/${Foo}")
轉義,和a b c無關聯。
流控制結構:
IF()...ELSE()/ELSEIF()...ENDIF()
WHILE()...ENDWHILE()
FOREACH()...ENDFOREACH()
正則表示式:
常用命令總結
命令 | 意義 |
---|---|
INCLUDE_DIRECTORIES( “dir1” “dir2” … ) | 標頭檔案路徑,相當於編譯器引數 -Idir1 -Idir2 |
AUX_SOURCE_DIRECTORY( “sourcedir” variable) | 收集目錄中的檔名並賦值給變數 |
ADD_EXECUTABLE | 可執行程式目標 |
ADD_LIBRARY | 庫目標 |
ADD_CUSTOM_TARGET | 自定義目標 |
ADD_DEPENDENCIES( target1 t2 t3 ) | 目標target1依賴於t2 t3 |
ADD_DEFINITIONS( “-Wall -ansi”) | 本意是供設定 -D… /D… 等編譯預處理需要的巨集定義引數,對比 REMOVE_DEFINITIONS() |
TARGET_LINK_LIBRARIES( target-name lib1 lib2 …) | 設定單個目標需要連結的庫 |
LINK_LIBRARIES( lib1 lib2 …) | 設定所有目標需要連結的庫 |
SET_TARGET_PROPERTIES( … ) | 設定目標的屬性 OUTPUT_NAME, VERSION, …. |
MESSAGE(…) | 這個指令用於向終端輸出使用者定義的資訊 |
INSTALL( FILES “f1” “f2”DESTINATION . ) | DESTINATION 相對於 ${CMAKE_INSTALL_PREFIX} |
SET( VAR value [CACHE TYPE DOCSTRING [FORCE]]) | 定義與修改變數 |
LIST( APPEND/INSERT/LENGTH/GET/REMOVE_ITEM/REMOVE_AT/SORT …) | 列表操作 |
STRING( TOUPPER/TOLOWER/LENGTH/SUBSTRING/REPLACE/REGEX …) | 字串操作 |
SEPARATE_ARGUMENTS( VAR ) | 轉換空格分隔的字串到列表 |
FILE( WRITE/READ/APPEND/GLOB/GLOB_RECURSE/REMOVE/MAKE_DIRECTORY …) | 檔案操作 |
FIND_FILE | 注意 CMAKE_INCLUDE_PATH |
FIND_PATH | 注意 CMAKE_INCLUDE_PATH |
FIND_LIBRARY | 注意 CMAKE_LIBRARY_PATH |
FIND_PROGRAM | |
FIND_PACKAGE | 注意 CMAKE_MODULE_PATH |
EXEC_PROGRAM( bin [work_dir] ARGS <..> [OUTPUT_VARIABLE var] [RETURN_VALUE var] ) | 執行外部程式 |
OPTION( OPTION_VAR “description” [initial value] ) |
變數
工程路徑
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
<projectname>_SOURCE_DIR
表示工程頂層目錄。CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR
表示生成目標目錄。CMAKE_CURRENT_SOURCE_DIR
表示當前處理的CMakeLists.txt所在的目錄。CMAKE_CURRRENT_BINARY_DIR
表示當前處理的CMakeLists.txt的目標目錄。CMAKE_CURRENT_LIST_FILE
輸出呼叫這個變數的CMakeLists.txt的完整路徑。
Debug和Release模式的構建
在CMakeList.txt檔案中使用
SET(CMAKE_BUILD_TYPE Debug)
。
或命令列引數cmake DCMAKE_BUILD_TYPE=Release
。
編譯器引數
CMAKE_C_FLAGS
CMAKE_CXX_FLAGS
也可以通過指令ADD_DEFINITIONS()
新增。
包含路徑
CMAKE_INCLUDE_PATH
配合FIND_FILE()
以及FIND_PATH()
使用。如果標頭檔案沒有存放在常規路徑(/usr/include, /usr/local/include等),則可以通過這些變數就行彌補。如果不使用FIND_FILE
和FIND_PATH
的話,CMAKE_INCLUDE_PATH
沒有任何作用。CMAKE_LIBRARY_PATH
配合配合FIND_LIBRARY()
使用。否則沒有任何作用。CMAKE_MODULE_PATH
CMake為上百個軟體包提供了查詢器(finder):FindXXXX.cmake。當使用非CMake自帶的finder時,需要指定finder的路徑,這就是CMAKE_MODULE_PATH
,配合FIND_PACKAGE()
使用。
編寫CMakeLists.txt的示例
對於如下的目錄結構。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
+example | +--- CMakeList.txt +--+ src/ | | | +--- main.cpp | /--- CMakeList.txt | +--+ thirdparty/ | | | +--- hello.h | +--- hello.cpp | /--- CMakeList.txt | /--+ build/ |
將所有子資料夾中的原始檔包含進來,然後生成。
在頂資料夾中example的CMakeLists.txt中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
cmake_minimum_required(VERSION 2.6) project(HELLO) # include src include(src/CMakeLists.txt) foreach(FILE ${FILES}) set(subdir1Files ${subdir1Files} src/${FILE}) endforeach(FILE) # include thirdparty include(thirdparty/CMakeLists.txt) foreach(FILE ${FILES}) set(subdir2Files ${subdir2Files} thirdparty/${FILE}) endforeach(FILE) # add the source files to the executable add_executable(hello ${subdir1Files} ${subdir2Files}) |
在src目錄的CMakeLists.txt中:
1 2 |
# list the source files for this directory set (FILES main.cpp) |
在thirdparty目錄的CMakeLists.txt中:
1 2 |
# list the source files for this directory set(FILES hello.h hello.cpp) |
和上一種方式一樣,只不過是自動包含。
在頂資料夾中example的CMakeLists.txt中:
1 2 3 4 5 |
cmake_minimum_required(VERSION 2.6) project(HELLO) AUX_SOURCE_DIRECTORY(./thirdparty subdir1Files) AUX_SOURCE_DIRECTORY(./src subdir2Files) |