官术网_书友最值得收藏!

第一篇 預備知識篇

1
C魔法概覽

本章內容主要對C編程語言(以下簡稱C語言)進行大體介紹,包括它的歷史以及C語言標準的演化進程。然后介紹一下C語言編程思想,當前主流C語言編譯器以及GNU語法擴展。最后簡單介紹一下從用C語言編寫程序到編譯、構建一個可執行程序的大致過程。

計算機編程語言從對計算機硬件底層的抽象程度進行分類,可分為:機器語言、匯編語言以及高級語言。下面由底層到高層分別介紹這幾種類別的編程語言。

1.1 例說編程語言

1)機器語言是直接通過十六進制數表示當前處理器架構的機器指令碼。指令碼包含了當前指令的功能(比如算術邏輯運算、移位、分支、中斷、I/O等)、寄存器、立即數等多種元素。每種處理器架構所對應的機器碼的字節長度也各不相同,有些是固定長度的(比如ARM、MIPS等架構),有些是可變長度的(比如x86架構)。

2)匯編語言(Assembly Language)通過簡單的指令助記符(memonics)來表示對應機器指令的功能、寄存器編號、立即數(immediates)等元素。匯編語言是對機器指令的簡單抽象,通過匯編器(assembler)可以將匯編語句翻譯成對應的機器指令碼。

3)高級語言的表達形式更為抽象且貼近我們日常的語言表述。而且,高級語言比起匯編語言往往更具有表達力,且擁有更加豐富的語法特性,以便將程序進行結構化和模塊化。比如,高級語言具有自定義變量標識符、自定義數據結構、分支與循環、更形象自然的表達式等。高級語言一般通過編譯器(compiler)可直接將表達式翻譯為對應的機器指令碼;也可以將高級語言先翻譯為中間語言(類似于匯編,但可能比匯編適用范圍更廣、更利于跨平臺的字節碼),最后將中間語言翻譯為最終的機器指令碼。

當然,有些書中還介紹了第四代語言,它基于高級語言,比高級語言更抽象,只需要一些簡單的描述語句就能讓計算機做比較復雜的工作。比如SQL(結構化查詢語言,用于數據庫查詢)算是一種第四代語言。

下面,為了能讓大家對這三種層次的編程語言有一個感性的認識,這里將列舉ARMv8架構處理器下的機器語言、匯編語言,加上它們相應的C語言。讀者如果手頭有Xcode,并且有包含Apple A7或更高版本處理器的iOS設備的話,可以直接編譯運行,并能看到最終效果。

下面首先列出一個文件名為my_sub.s的匯編源文件,其中包含了機器語言和匯編語言。見代碼清單1-1:

代碼清單1-1 機器語言與匯編語言

.text
.align 4

#ifdef __arm64__

.globl _my_sub_machine
.globl _my_sub_assembly

// 用機器語言實現減法操作
_my_sub_machine:

    .long 0x4b010000

    .long 0xd65f03c0

// 用匯編語言實現減法操作
_my_sub_assembly:

    sub w0, w0, w1

    ret

#endif

在代碼清單1-1中,_my_sub_machine程序片段中的兩條.long語句即為機器指令。這兩條機器指令正好與_my_sub_assembly中的兩條匯編指令相對應。也就是說,“0x4b010000”這串32位的十六進制代碼意思就是“sub w0, w0, w1”,表示將寄存器w0與寄存器w1的值進行相減,然后將結果寫回w0寄存器中。而“0xd65f03c0”指令碼對應于“ret”(更確切地說是ret x30),表示返回當前過程(procedure)。在匯編語言中,一般會使用過程或者例程(routine)來表示一個可執行的程序片段。在C語言中一般都用函數(function)表示。我們在這里能夠明顯看到,匯編語言采用指令助記符的方式比寫機器指令碼要直觀得多,而且也不容易出錯。“sub”指令的功能從助記符上就能知道是“減法”功能;而w0、w1也明確指明了使用的寄存器是w0和w1。這些在“0x4b010000”這種機器指令碼上都無法直觀地表現出來。

代碼清單1-2列出C語言是如何表達一個減法操作的。

代碼清單1-2 減法操作對應的C語言

static int my_sub_c(int a, int b)
{
    return a - b;
}

代碼清單1-2所列出的C語言代碼與代碼清單1-1中的機器指令碼和匯編語言完全對應,意思一目了然——將參數變量a的值與參數變量b的值進行相減,然后將結果返回。從這里我們就能看到機器語言、匯編語言以及以C語言為代表的高級語言之間在表達力上的差距了。高級語言的目的就是為了給程序員提供更良好的編程工具,更簡潔、更富有表達力的語言,使得我們程序員能提升生產力,并且能構思出更多精彩炫酷的應用,而不是把太多的精力都投入在如何讓計算機執行的細節上。

代碼清單1-3能讓我們在主函數或其他函數中測試上述已經編寫好的函數。

代碼清單1-3 展示減法操作的結果

#ifdef __arm64__

extern int my_sub_machine(int a, int b);
extern int my_sub_assembly(int a, int b);

int result_machine = my_sub_machine(10, 2);
int result_assembly = my_sub_assembly(5, 3);
int result_c = my_sub_c(6, 2);

printf("Three results: %d, %d, %d\n", result_machine, result_assembly, result_c);

#endif

執行了上述代碼之后,我們最后能在控制臺看到輸出結果:“Three results: 8, 2, 4”??梢姡鲜鋈N不同的編程語言,計算功能是完全一致的,都是對兩個輸入參數做減法操作,然后返回差值。然而就可讀性、可理解性以及編程便利性而言,顯然C語言比起其他兩者要強得多。而可讀性最差的無疑就是機器指令碼了。

1. C語言的類別與產生

對于高級語言來說,從表達上又可分為命令式編程語言(imperative programming language)和陳述型編程語言(declarative programming language)。命令式語言主要包括過程式(procedural)、結構化(structured)以及面向對象(object-oriented)的編程語言;陳述型編程語言主要包括函數式(functional)以及邏輯型(logical)編程語言。而C語言則屬于結構化的命令式編程語言。不過現在很多命令式編程語言也包含了一些函數式編程語言的特征。在本書中,后面第18章中談到的Blocks語法就是一個很典型的函數式編程語言的語法。

C語言最初由Dennis Ritchie于1969年到1973年在AT&T貝爾實驗室里開發出來,主要用于重新實現Unix操作系統。此時,C語言又被稱為K&R C。其中,K表示Kernighan的首字母,而R則是Ritchie的首字母。K&R C語言與后來標準化的C語言有很大差異。比如,如果函數返回類型為int,則int可省:int my_function() { },也可以寫成my_function(){ }。編譯器不會有任何警告,更不會報錯。另外,還有現在看來比較奇葩的函數定義,像我們現在定義這么一個函數——void my_function(int a, char *p){ },如果是用K&R C語法定義的話要寫成:void my_function(a, p) int a; char *p; { }。K&R的C語法中,定義一個函數時,其形參列表先列出形參的標識符,然后在函數聲明的后面緊跟著對形參標識符的完整聲明,最后是函數體。這在現行標準中已經被逐步廢棄使用了。另外,當時的第一本C語言專業書《The C Programming Language》也并非一個正式的編程語言規范,但被用了許多年。

2. C90標準

由于C語言被各大公司所使用(包括當時處于鼎盛時期的IBM PC),因此到了1989年,C語言由美國國家標準協會(ANSI)進行了標準化,此時C語言又被稱為ANSI C。而僅過一年,ANSI C就被國際標準化組織ISO給采納了。此時,C語言在ISO中有了一個官方名稱——ISO/IEC 9899:1990。其中,9899是C語言在ISO標準中的代號,像C++在ISO標準中的代號是14882。而冒號后面的1990表示當前修訂好的版本是在1990年發布的。對于ISO/IEC 9899:1990的俗稱或簡稱,有些地方稱為C89,有些地方稱為C90,或者C89/90。不管怎么稱呼,它們都指代這個最初的C語言國際標準。這個版本的C語言標準作為K&R C的一個超集(即K&R C是此標準C的一個子集),把后來引入的許多非官方特性也一起整合了進去。其中包括了從C++借鑒的函數原型(Function Prototypes),指向void的指針,對國際字符集以及本地語言環境的支持。在此標準中,盡管已經將函數定義的方式改為現在我們常用的那種方式,不過K&R的語法形式仍然兼容。

3. C99標準

在隨后的幾年里,C語言的標準化委員會又不斷地對C語言進行改進,到了1999年,正式發布了ISO/IEC 9899:1999,簡稱為C99標準。C99標準引入了許多特性,包括內聯函數(inline functions)、可變長度的數組、靈活的數組成員(用于結構體)、復合字面量、指定成員的初始化器、對IEEE754浮點數的改進、支持不定參數個數的宏定義,在數據類型上還增加了long long int以及復數類型。毫不夸張地說,即便到目前為止,很少有C語言編譯器是完整支持C99的。像主流的GCC以及Clang編譯器都能支持高達90%以上,而微軟的Visual Studio 2015中的C編譯器只能支持到70%左右。

4. C11標準

2007年,C語言標準委員會又重新開始修訂C語言,到了2011年正式發布了ISO/IEC 9899:2011,簡稱為C11標準。C11標準新引入的特征盡管沒C99相對C90引入的那么多,但是這些也都十分有用,比如:字節對齊說明符、泛型機制(generic selection)、對多線程的支持、靜態斷言、原子操作以及對Unicode的支持。本書將主要針對C11標準為大家詳細講解C編程語言。關于C語言歷史與演化進程的詳細介紹可參考維基百科:https://en.wikipedia.org/wiki/C_%28programming_language%29。

筆者近兩年也是在不斷地了解C語言標準委員會的最新動態(可參見:http://www.open-std.org/jtc1/sc22/wg14/),其中看到有人提出想為C語言添加面向對象的特性,包括增加類、繼承、多態等已被C++語言所廣泛使用的語法特性,但是最終被委員會駁回了。因為這些復雜的語法特性并不符合C語言的設計理念以及設計哲學,況且C++已經有了這些特性,C語言無需再對它們進行支持。筆者將在第19章給大家談談C語言設計理念與發展方向。

1.2 用C語言編程的基本注意事項

C語言的發明其實基于Unix操作系統。當時在C語言未面世之前,Dennis Ritchie所在的AT&T貝爾實驗室用的Unix系統是完全用匯編語言寫的。匯編語言的優勢是直接面向處理器本身,能直接對底層硬件進行控制,充分發揮處理器的硬件能力。然而,它的缺陷也是顯而易見的。

1.匯編語言的不足

首先,不可移植性。每種處理器,其指令集都大相徑庭,比如ARM有ARM的指令集架構(ISA),Intel x86有x86的ISA,還有MIPS、Power(原來為PowerPC),Motorola 68000等;再加上各類微控制器單元(Micro-Controller Unit, MCU)、各類數字信號處理器(Digital Signal Processor, DSP),每種ISA都有其相應的匯編語言。那么多處理器如果對每一種都使用不同的匯編語言來實現同一個操作系統,那操作系統的開發人員真要崩潰了……而且即便實現出來,可能各個處理器上的實現也會有所不同,標準也很難被統一起來。

其次,匯編語言本身要比高級語言精密。因為匯編語言面對的都是寄存器、存儲器以及各類底層硬件,而不是一種抽象的數據模型,所以代碼編寫時需要非常謹慎,而且調試程序也十分麻煩,且非常容易出錯。所以,如果有一種既能面向底層硬件,又能對數據以及程序進行抽象的高級語言出現,那勢必既能不太影響程序執行效率,又能大大提升程序的可執行性、可讀性以及編寫的效率,這將是非常偉大的貢獻。C語言也就是在這種背景下誕生的。

如果說,匯編語言面向的是底層硬件、一種過程化的編程風格的話,那么C語言就是面向數據流和算法、一種結構化的編程風格。C語言是一種結構化的、靜態類型的編譯型編程語言。也就是說,用C語言編寫了源代碼之后,需要通過C語言編譯器進行編譯,構建為相應的處理器能直接執行的機器碼,然后處理器可以對生成出來的機器碼進行執行。所以在各個處理器上,處理器廠商或第三方只需要為當前處理器寫一個對應的C語言編譯器即可。然后任何符合C語言標準的程序都能在上面編譯后執行,除了需要支持某些機器特定的功能和特性外(后面會介紹)。

2. C語言編寫程序要注意什么

那么我們在用C語言寫程序的時候應該注意哪些方面呢?

1)可移植性:C語言被設計出來的一大初衷就是為了能將同一個源代碼放到各個不同的平臺上編譯運行。因此,如果我們的代碼要在多種不同架構的處理器上運行的話,我們就得注意C語言標準規定了哪些特性是編譯器必須遵守的,哪些特性是平臺或編譯器自己實現的。我們要盡量使用標準中已明文規定的編程規范,盡可能避免在不同平臺可能會產生不同行為的語法特性。當然,由于上面提到的處理器種類太過多樣,尤其在嵌入式開發領域,很多MCU用的還都是8位處理器,這種情況下C源代碼就很難被移植到32位或64位系統下了。本書后面將會指出大部分主流平臺對C語言標準中所提到的“實現定義”行為的區別。另外,也會提到一些技巧來應對不同的平臺特性。

2)可維護性:可維護性在實際工程項目的研發中非常重要。它體現在最初工程架構的設計、對各個功能模塊的劃分、相應的開發人員安排,還有后期的測試。一般來說,現在一個工程如果是從無到有進行開發的話會采用螺旋式開發模型。也就是說,一個項目啟動后,可以先做一個功能簡單但能正常工作的產品原型。然后在此基礎上不斷地為它增加更多功能,或對之前的功能進行修改。在此期間,我們如何對整個工程進行模塊化劃分,從而能安排不同開發人員針對不同功能模塊進行開發就變得尤為重要。另外,在工程開發過程中,如果有人員流動,那么如何將即將離職的開發人員手中的工作交付給新人也關系到整個項目的進展。因此,一個良好的C語言代碼應該具有可讀性、良好的文檔化注釋風格,以及較詳細的設計文檔。對于一個較大的工程項目來說,開發人員不僅僅需要把自己的代碼寫好,而且要寫得能讓別人看懂,并且要做好詳細的設計文檔,這樣才能把項目風險降低。

3)可延展性:大家或許已經知道,像微軟的Windows操作系統由數千名工程師合作研發;Linux操作系統對外開源,參與其中的研發人員也有數百上千人。如果我們在一個開發團隊中負責一個需要由多人合作開發的工程項目,那么我們寫的功能模塊需要與其他人寫的功能模塊進行對接。所以,我們在開發一個較大工程項目時,需要協調好各自對外的模塊接口(Application Program Interface, API)。由于C語言沒有全局名字空間(namespace)這個概念,所以命名一個對外接口也是非常重要的,否則可能會與其他功能模塊的接口名發生沖突。本書后面會對C語言函數命名以及符號連接做進一步介紹。

4)性能:性能是提升程序使用者效率和生產力的體現。一個應用程序的性能越高,那么計算一個任務所花費的時間越短,也越節省計算機的耗電。而對于如何提升性能,一方面需要程序員對處理器架構、硬件特性有一定了解;另一方面需要程序員擁有比較豐富的算法知識,能針對實際需求靈活采用高效的算法。而像C語言這種十分接近硬件底層的高級編程語言,能極大限度地發揮處理器的特長,從而達到高效的運行性能。

1.3 主流C語言編譯器介紹

對于當前主流桌面操作系統而言,可使用Visual C++、GCC以及LLVM Clang這三大編譯器。其中,Visual C++(簡稱MSVC)只能用于Windows操作系統;其余兩個,除了可用于Windows操作系統之外,主要用于Unix/Linux操作系統。像現在很多版本的Linux都默認使用GCC作為C語言編譯器。而像FreeBSD、macOS等系統默認使用LLVM Clang編譯器。由于當前LLVM項目主要在Apple的主推下發展的,所以在macOS中,Clang編譯器又被稱為Apple LLVM編譯器。MSVC編譯器主要用于Windows操作系統平臺下的應用程序開發,它不開源。用戶可以使用Visual Studio Community版本來免費使用它,但是如果要把通過Visual Studio Community工具生成出來的應用進行商用,那么就得好好閱讀一下微軟的許可證和說明書了。而使用GCC與Clang編譯器構建出來的應用一般沒有任何限制,程序員可以將應用程序隨意發布和進行商用。不過由于MSVC編譯器對C99標準的支持就十分有限,加之它壓根不支持任何C11標準,所以本書的代碼例子不會針對MSVC進行描述。所幸的是,Visual Studio Community 2017加入了對Clang編譯器的支持,官方稱之為——Clang with Microsoft CodeGen,當前版本基于的是Clang 3.8。也就是說,應用于Visual Studio集成開發環境中的Clang編譯器前端可支持Clang編譯器的所有語法特性,而后端生成的代碼則與MSVC效果一樣,包括像long整數類型在64位編譯模式下長度仍然為4個字節,所以各位使用的時候也需要注意。為了方便描述,本書后面涉及Visual Studio集成開發環境下的Clang編譯器簡稱為VS-Clang編譯器。

而在嵌入式系統方面,可用的C語言編譯器就非常豐富了。比如用于Keil公司51系列單片機的Keil C51編譯器;當前大紅大紫的Arduino板搭載的開發套件,可用針對AVR微控制器的AVR GCC編譯器;ARM自己出的ADS(ARM Development Suite)、RVDS(RealView Development Suite)和當前最新的DS-5 Studio; DSP設計商TI(Texas Instruments)的CCS(Code Composer Studio);DSP設計商ADI(Analog Devices, Inc.)的Visual DSP++編譯器,等等。通常,用于嵌入式系統開發的編譯工具鏈都沒有免費版本,而且一般需要通過國內代理進行購買。所以,這對于個人開發者或者嵌入式系統愛好者而言是一道不低的門檻。不過Arduino的開發套件是可免費下載使用的,并且用它做開發板連接調試也十分簡單。Arduino所采用的C編譯器是基于GCC的。還有像樹莓派(Raspberry Pi)這種迷你電腦可以直接使用GCC和Clang編譯器。此外,還有像nVidia公司推出的Jetson TK系列開發板也可直接使用GCC和Clang編譯器。樹莓派與Jetson TK都默認安裝了Linux操作系統。在嵌入式領域,一般比較低端的單片機,比如8位的MCU所對應的C編譯器可能只支持C90標準,有些甚至連C90標準的很多特性都不支持。因為它們一方面內存小,ROM的容量也?。涣硪环矫?,本身處理器機能就十分有限,有些甚至無法支持函數指針,因為處理器本身不包含通過寄存器做間接過程調用的指令。而像32位處理器或DSP,一般都至少能支持C99標準,它們本身的性能也十分強大。而像ARM出的RVDS編譯器甚至可用GNU語法擴展。

圖1-1展示了上述C語言編譯器的分類。

圖1-1 C語言編譯器的分類

1.4 關于GNU規范的語法擴展

GNU是一款能用于構建類Unix操作系統的計算機軟件合集,由自由軟件之父Richard Stallman開創,于1983年9月27日對外發布。GNU完全由自由軟件(free software)構成。GNU語法擴展源自于GCC編譯器,在1987年發布1.0版本,稱為GNU C Compiler。隨后,GCC編譯器前端 源代碼編譯流程請見1.5節圖1-2。支持了C++、Objective-C/C++、Fortran、Ada、Java以及最近躍升的Go等編程語言,因此現在GCC被稱為GNU Compiler Collection。由于在20世紀90年代,GNU C編譯器就對C90標準做了相當多的語法擴展,包括復合字面量、匿名結構體和數組、可指定的初始化器等,這些語法擴展被廣泛使用,尤其是大量用于Linux內核代碼中,因此C99標準將這些語法特性全都列入標準之中。

正因為GCC本身是開源自由軟件,因此很多商用編譯器也基于GCC進行擴展。像ARM的RVCT(RealView Compiler Toolkit)本身就支持GNU擴展。還有不少開發平臺本身就直接使用GCC編譯工具。由于有不少大公司頂級開發人員的參與,因此GCC編譯器的目標代碼優化能力相當高,而且還支持許多不同的處理器。所以,GCC當前被廣泛使用并博得開發者的好評。像Linux操作系統基本默認使用GCC作為默認編譯器,包括Android的NDK開發工具一開始也是如此。

然而,由于GCC基于比較嚴格的GPL許可證,許多大型商業開發商對它望而卻步。該許可證允許使用者免費使用軟件,但是要求不能隨意對它進行篡改并重新發布。如果開發者對它進行篡改,然后發布自己修改之后的軟件,那么必須要把自己修改的那部分也開源出來。因此,在2003年誕生了一個LLVM開源項目,基于更為寬松的BSD許可證,其編譯器稱為Clang。BSD許可證允許開發者隨意對軟件進行修改并重新發布,甚至可以將修改過的版本作為自主版權,因而這個許可證深受大公司的歡迎?,F在Apple對LLVM項目的投入非常大。macOS上的開發工具Xocde從4.0版本起就開始使用Clang編譯工具鏈,隨后Apple將自己改寫的Clang編譯器稱為Apple LLVM。當前最新的Xcode 8所使用的Apple LLVM版本為8.x。而當前Android NDK也支持了Clang編譯器工具鏈。Clang編譯器并非基于GCC,它是從頭開始寫的。但是它的目標是盡量與GCC編譯器兼容,所以Clang編譯器包含大部分GNU語法擴展,除此之外還含有它自己特有的C語言擴展。當然也有一些特性是GCC含有而Clang不具備的,不過這些特性一般很少使用。

我們現在可以看到GNU語法擴展適用性十分廣泛。如果讀者當前在做Linux/Unix或Windows上的C語言編程開發,或者是在開發macOS/iOS應用,又或者是在開發Android應用,那么完全可以毫無顧忌地使用GNU語法擴展。本書最后幾個章節會分別介紹GCC編譯器特定的語法擴展以及Clang編譯器特定的語法擴展。由于Clang編譯器已經包含了大部分GNU語法擴展,因此在介紹GCC語法擴展的時候,如果當前特性Clang不支持,則會指明。

1.5 用C語言構建一個可執行程序的流程

從用C語言寫源代碼,然后經過編譯器、連接器到最終可執行程序的流程圖大致如圖1-2所示。

圖1-2 C語言源代碼編譯流程圖

從圖1-2中我們可以清晰地看到C語言編譯器的大致流程。首先,我們先用C語言把源代碼寫好,然后交給C語言編譯器。C語言編譯器內部分為前端和后端。前端負責將C語言代碼進行詞法和語法上的解析,然后可以生成中間代碼。中間代碼這部分不是必須的,但是它能夠為程序的跨平臺移植帶來諸多好處。比如,同樣的一份C語言源代碼在一臺計算機上編譯完之后,生成一套中間代碼。然后針對不同的目標平臺(比如要將這一套代碼分別編譯成ARM處理器的二進制機器碼、MIPS處理器的二進制機器碼以及x86處理器的二進制機器碼),只需要編寫相應目標平臺的編譯器后端即可。所以,這么做就可以把編譯器的前端與后端剝離開來(這在軟件工程上又可稱為解耦合),不同處理器廠商可以針對自家的處理器特性,對中間代碼生成到目標二進制代碼的過程再度進行優化。接下來,由C語言編譯器后端生成源文件相應的目標文件。目標文件在Windows系統上往往是.obj文件;而在Unix/Linux系統上往往是.o文件。C語言的源文件在所有平臺上都統一用.c文件表示。最后,對于各個獨立的目標文件,通過連接器將它們合并成一個最終可執行文件。連接器與C語言編譯器是完全獨立的。所以,只要最終目標代碼的ABI(應用程序二進制接口)一致,我們可以把各個編譯器生成的目標代碼都放在一起,最后連接生成一個可執行文件。比如,有些源代碼可用GCC編譯,有些使用Clang編譯,還有些匯編語言源文件可直接通過匯編器生成目標代碼,最后將所有這些生成出來的目標代碼連接為可執行文件。最終用戶可以在當前的操作系統上加載可執行文件進行執行。操作系統利用加載器將可執行文件中相關的機器碼存放到內存中來執行應用程序。

1.6 本章小結

本章簡要地介紹了計算編程語言的分類,描述了C語言的歷史及演化,以及C語言的編程思想。此外還介紹了GNU的來龍去脈以及C語言編譯器將C語言代碼翻譯成最終機器碼的大致流程。

C語言作為一門更接近硬件底層的高級編程語言具有良好的抽象力、表達力和靈活性。此外,它具有非常高效的運行時性能。當前的C語言編譯器最終翻譯成的機器指令碼與我們手工寫匯編語言所得到的性能在大部分情況下相差無幾。C語言基本能達成我們對性能的要求,而在某些對性能要求十分嚴苛的熱點(hotspot)上,我們可以對這些功能模塊手工編寫匯編代碼。C語言與匯編語言的ABI是完全兼容的,而且大部分C語言編譯器還支持直接內聯匯編語言。因此,C語言從1970年直到現在都是系統級編程的首要編程語言。

主站蜘蛛池模板: 从江县| 崇明县| 嘉兴市| 上思县| 瓮安县| 行唐县| 余江县| 德阳市| 丹巴县| 从化市| 进贤县| 屏南县| 伊宁县| 班戈县| 浦县| 平舆县| 法库县| 利津县| 嘉义市| 会同县| 连云港市| 张家川| 兖州市| 炎陵县| 尤溪县| 枣庄市| 大邑县| 静乐县| 隆昌县| 郸城县| 安塞县| 包头市| 阿荣旗| 阳曲县| 辽阳市| 舒兰市| 清苑县| 吴江市| 兰溪市| 沂水县| 巴塘县|