這是一門需要了解底層的語言——《例C》(三)
C語言被認為難,原因就在於要想編寫C程序就必須了解一些「底層」的概念。C語言本身也以高效、靠近底層聞名天下。
本章任務
- 了解數據的含義
- 聲明自定義整型變數並賦值,然後通過printf函數輸出。
關於「量」
什麼是量?量在計算機科學的定義是由「內存空間」(Memory location)和「標識符」(Identifier),以及它們的綁定關係所構成。內存空間存儲數據,被綁定的標識符中,一方面(名字標識符)代表內存空間,以及所表示的值(value),另一方面(類型與屬性標識符)代表內存空間的位狀態能夠表示的值(由類型定義),也代表該類型所能允許的操作。
與量相關的兩個概念是「常量」(Constant)和「變數」(Variable)。常量是位狀態不允許變更的量,變數是位狀態允許變更的量,一般通過「賦值」(Assignment)變更量的位狀態。
數據
如今的電子計算機,其硬體電路分為兩種狀態:高電平、低電平;於是建立了基於二進位的計算機體系結構,也就意味著所有數據都是用二進位保存的(含程序指令)。
數據在內存中的保存形式是0和1的二進位位。比如0是1位,0011是4位……和平時說的「三位數」、「四位數」的「位」的意思一樣,只不過計算機的「位」是二進位。另外8個「位」(bit)代表1個「位元組」(Byte)。
以整數為例,數在計算機內存中的存儲形式是二進位,比如38的二進位版是100110,那麼它在8位的內存空間里就是這個樣子:
00100110
而位數的大小直接決定了能表示的整數範圍,例如4位的內存空間所能表示的非負整數如下:
Status: 內存空間狀態Integer: 表示的非負十進位整數+------+-------+------+-------+------+-------+------+-------+|Status|Integer|Status|Integer|Status|Integer|Status|Integer|+------+-------+------+-------+------+-------+------+-------+| 0000 | 0 | 0001 | 1 | 0010 | 2 | 0011 | 3 |+------+-------+------+-------+------+-------+------+-------+| 0100 | 4 | 0101 | 5 | 0110 | 6 | 0111 | 7 |+------+-------+------+-------+------+-------+------+-------+| 1000 | 8 | 1001 | 9 | 1010 | 10 | 1011 | 11 |+------+-------+------+-------+------+-------+------+-------+| 1100 | 12 | 1101 | 13 | 1110 | 14 | 1111 | 15 |+------+-------+------+-------+------+-------+------+-------+
從另一個角度來說,4位可以表示16種「狀態」。
立Flag:在這套體系結構未被革命的情況下,C語言將永遠不會過時。
標識符
標識符的作用是能讓程序員分辨出自定義數據,數據的類型或是數據的屬性。C語言標準規定了一套數據類型標識符和數據屬性標識符,它們被稱為「關鍵字」(keyword),自定義數據(名稱)的標識符不能和關鍵字重複。C語言的全部關鍵字可參考這裡。
標識符有一套通用的規則:標識符由大小寫英文字母、數字和下劃線排列組合而成(其他語言可能允許其他字元),且第一個字元不能是數字。那為什麼不能是數字呢?因為
主要是工程折衷。
程序語言的分析分詞法和語法兩部分。詞法分析主要用的是正規文法,也就是三型文法。這類文法主要採用正則表達式分析。正則文法分析器的特點是它是不回溯的,所以實現很簡單。
如果一個變數以數字開頭,那麼分析器就必須在遇到第一個或第二個英文字元的時候回溯來確定是否是數字、變數名還是詞法錯誤,這時候就變成了二型文法。二型文法分析器的好處是支持回溯和遞歸語法(所以語法分析是靠它的),但是缺點是狀態機相比正則文法狀態大大增加,而且代碼寫起來更困難。
考慮到詞法分析部分只是用來斷字,我們實在是沒有為了支持變數名以數字開頭這麼一個小功能而讓整個詞法分析部分用二型文法寫。
故,最後大家都默認了變數要避免用數字開頭。
說人話就是要是支持首字元用數字的話,編譯器/解釋器寫起來很麻煩,收益和付出不成正比;於是大家都不支持標識符的首字元使用數字了。
數據類型與數據屬性
「類型」(Type)包含類型標識符、數據格式和類型允許的操作。
比如整數類型(Integer,有時被簡稱為整型)的數據類型標識符(之一)是int,數據格式是二進位表示的十進位數(非負部分,負數的格式有所不同),允許的操作有加減乘除,還有取模(餘數)。
「屬性」(Property)包含屬性標識符和屬性,比如常量屬性標識符是const,屬性是只允許被綁定的量被賦值一次。
示例代碼
本次示例代碼在integer文件夾。
#include <stdio.h>#include <stdint.h> // 使用C99固定大小整數#include <inttypes.h> // 為了輸出C99固定大小整數#ifdef _WIN32#include <stdlib.h>#define PAUSE system("pause");#else#define PAUSE#endifint main() { // 聲明變數並賦值,和類型標識符、以及名字(標識符)進行綁定 int saber = 10; long archer = 20; // 聲明常量並賦值,與常量屬性標識符進行了綁定 const int caster = 666; printf("print data
"); printf("saber: %i
", saber); printf("archer: %li
", archer); printf("caster: %i
", caster); // 對變數重新賦值(更新位狀態) saber = 0; printf("saber updated to: %
", saber); // 如果用不了C99,就只能把量的聲明和賦值分開 /* ANSI C style */ short lancer; lancer = 20; int8_t dora = 73; uint16_t vivian = 162; printf("
print fixed-size integers
"); // 通過查詢文檔可得如何輸出固定大小整數類型 // 涉及了一點宏 printf("%" PRIi8 " %" PRIu16 "
", dora, vivian); PAUSE return 0;}
量的聲明與賦值
什麼是量的「聲明」(Declaration)?聲明的意思是把一塊大小為(程序員通過類型標識符所指代的)類型大小的內存空間的「所有權」(Ownership)據為己有;把自定義(名字)標識符、數據類型和數據屬性標識符和那塊內存空間「綁定」(Binding)。
對於量的「賦值」(Assignment),簡單來說就是把一個量的數據(位狀態)更新為提供的值(所代表的位狀態)。
C的整數類型
C語言有一些內置的整數類型,之前我們說main函數返回的是整數,那麼int就是整數類型的關鍵字之一。然而int的大小(所佔位數),是不固定的,C語言標準只規定了最小的大小。(前面有unsigned表示無符號整數類型,也就是只能表示非負整數)
Data type: 數據類型Size: 最小的大小(位)+--------------------+--------------------+------+| Data type | Data type | Size |+--------------------+--------------------+------+| char | unsigned char | 8 |+--------------------+--------------------+------+| short | unsigned short | 16 |+--------------------+--------------------+------+| int | unsigned int | 16 |+--------------------+--------------------+------+| long | unsigned long | 32 |+--------------------+--------------------+------+| long long | unsigned long long | 64 |+--------------------+--------------------+------+
long long和unsigned long long屬於C99。
char 本來就是整數類型,不過一般不用它表示整數,而且在不同的平台還不一樣。
雖然一般不用太在意,但不確定總是沒安全感。因此在C99標準中有固定大小的整數提供(需要導入頭文件stdint.h),像是int8_t表示8位大小的有符號整數,可以表示-128到127,uint16_t表示16位大小的無符號整數,可以表示0到65535; stdint.h提供了8、16、32、64位,無符號、有符號等整數類型。
- int8_t:8位,可表示-127到127
- uint8_t:8位,可表示0到255
- int16_t:16位,可表示-32767到32767
- uint16_t:16位,可表示0到65535
- int32_t:32位,可表示-2147483647到214783647
- uint32_t: 32位,可表示0到4294967295
- int64_t:64位,可表示-9223372036854775807到9223372036854775807
- uint64_t:64位,可表示0到18446744073709551615
格式化輸出
向控制台(或終端)輸出一般使用printf函數(要使用的話不要忘記導入頭文件)。要輸出自定義數據,除了需要前者的標識符外,還需要printf函數家族特有的格式控制標識符。就是示常式序中,字元串內以半形百分號開始,後面跟字母或數字的東西。
在示常式序中,列舉了幾種數據類型的輸出方法,但沒有列全。格式控制標識符用起來很複雜,很多時候不一定能記得起。這種時候就需要查詢文檔了。
庫函數與文檔查詢
在使用C語言實現功能時,要用到很多的函數。之前提到函數具有輸入(參數)和返回值(輸出)。實際上
printf("hello, world
");
這一行代碼完成的是「函數調用」(Function call)。調用一個函數時,需要參數的話由程序員提供參數,然後如果程序需要返回值的話就將返回值賦值給一個變數,不需要的話就不管返回值。
函數是多種多樣的,有的函數可以解方程,有的函數可以新建文件,也有的函數可以快速佔滿內存(讓電腦死機)……函數的不同(多樣性)在形式上的體現有:
- 參數的數量和類型不同
- 返回值的類型不同
- 函數標識符不同
函數不僅功能豐富,而且數量極多。要是編程需要把函數如何調用都記住的話,和文科又有什麼區別呢?況且想把浩如煙海的函數調用方式都記住也是不現實的。
所以我們可以通過文檔來查詢各種庫函數。比如要讓程序把一張彩色圖片轉換成黑白圖片需要調用哪個庫函數?需要傳入那些參數?有返回值嗎?有的話返回值代表什麼呢?實際上這樣的庫函數是存在的,這個功能屬於圖像處理的範疇,而這方面有一個庫叫做OpenCV。
在本章任務中,需要輸出一些整數變數的值到控制台。不知道如何輸出long long?可以查詢文檔啊。實際上每個庫的編寫者都有義務在文檔中寫明提供的庫函數具有的功能,以及應該如何被調用。
printf庫函數家族文檔
devdocs.io集成了很多語言的官方文檔,用起來很方便。
除了不知道某個庫函數如何調用外,還有一種情況是我們想讓程序完成某個功能,但是連庫函數的名字(標識符)都不知道怎麼辦?
Stack Overflow是一個程序員的問答網站,程序出現問題或者不知道如何編寫時可以在上面搜索、提問。如果你能用Google的話,直接搜索就好了,一般情況下搜索結果都出自Stack Overflow.
從上面的幾個網站可以看出,編程的相關資料,絕大多數都是英語版。所以英語能力對程序員十分重要。
英語不好會影響編程嗎?
我同學經常把main寫成mian……
推薦閱讀: