<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 第?2?章?常量、變量和表達式 **目錄** + [1\. 繼續Hello World](ch02s01.html) + [2\. 常量](ch02s02.html) + [3\. 變量](expr.variable.html) + [4\. 賦值](ch02s04.html) + [5\. 表達式](expr.expression.html) + [6\. 字符類型與字符編碼](ch02s06.html) ## 1.?繼續Hello World 在[第?4?節 “第一個程序”](intro.helloworld.html "4.?第一個程序")中,讀者應該已經嘗試對Hello world程序做各種改動看編譯運行結果,其中有些改動會導致編譯出錯,有些改動會影響程序的輸出,有些改動則沒有任何影響,下面我們總結一下。首先,注釋可以跨行,也可以穿插在程序之中,看下面的例子。 **例?2.1.?帶更多注釋的Hello World** ``` #include <stdio.h> /* * comment1 * main: generate some simple output */ int main(void) { printf(/* comment2 */"Hello, world.\n"); /* comment3 */ return 0; } ``` 第一個注釋跨了四行,頭尾兩行是注釋的界定符(Delimiter)/*和*/,中間兩行開頭的*號(Asterisk)并沒有特殊含義,只是為了看起來整齊,這不是語法規則而是大家都遵守的C代碼風格(Coding Style)之一,代碼風格將在[第?9?章 _編碼風格_](ch09.html#codingstyle)詳細介紹。 使用注釋需要注意兩點: 1. 注釋不能嵌套(Nest)使用,就是說一個注釋的文字中不能再出現/*和*/了,例如`/* text1 /* text2 */ text3 */`是錯誤的,編譯器只把`/* text1 /* text2 */`看成注釋,后面的 `text3 */`無法解析,因而會報錯。 2. 有的C代碼中有類似`// comment`的注釋,兩個/斜線(Slash)表示從這里直到該行末尾的所有字符都屬于注釋,這種注釋不能跨行,也不能穿插在一行代碼中間。這是從C++借鑒的語法,在C99中被標準化。 ### C語言標準 C語言的發展歷史大致上分為三個階段:Old Style C、C89和C99。Ken Thompson和Dennis Ritchie最初發明C語言時有很多語法和現在最常用的寫法并不一樣,但為了向后兼容性(Backward Compatibility),這些語法仍然在C89和C99中保留下來了,本書不詳細講Old Style C,但在必要的地方會加以說明。C89是最早的C語言規范,于1989年提出,1990年首先由ANSI(美國國家標準委員會,American National Standards Institute)推出,后來被接納為ISO國際標準(ISO/IEC 9899:1990),因而有時也稱為C90,最經典的C語言教材[[K&R]](bi01.html#bibli.kr "The C Programming Language")就是基于這個版本的,C89是目前最廣泛采用的C語言標準,大多數編譯器都完全支持C89。C99標準(ISO/IEC 9899:1999)是在1999年推出的,加入了許多新特性,但目前仍沒有得到廣泛支持,在C99推出之后相當長的一段時間里,連`gcc`也沒有完全實現C99的所有特性。C99標準詳見[[C99]](bi01.html#bibli.c99 "ISO/IEC 9899: Programming Languages - C")。本書講C的語法以C99為準,但示例代碼通常只使用C89語法,很少使用C99的新特性。 C標準的目的是為了精確定義C語言,而不是為了教別人怎么編程,C標準在表達上追求準確和無歧義,卻十分不容易看懂,[[Standard C]](bi01.html#bibli.standardc "Standard C: A Reference")和[[Standard C Library]](bi01.html#bibli.standardclib "The Standard C Library")是對C89及其修訂版本的闡釋(可惜作者沒有隨C99更新這兩本書),比C標準更容易看懂,另外,參考[[C99 Rationale]](bi01.html#bibli.rationale "Rationale for International Standard - Programming Languages - C")也有助于加深對C標準的理解。 像`"Hello, world.\n"`這種由雙引號(Double Quote)引起來的一串字符稱為字符串字面值(String Literal),或者簡稱字符串。注意,程序的運行結果并沒有雙引號,`printf`打印出來的只是里面的一串字符`Hello, world.`,因此雙引號是字符串字面值的界定符,夾在雙引號中間的一串字符才是它的內容。注意,打印出來的結果也沒有`\n`這兩個字符,這是為什么呢?在[第?2?節 “自然語言和形式語言”](intro.naturalformal.html "2.?自然語言和形式語言")中提到過,C語言規定了一些轉義序列(Escape Sequence),這里的`\n`并不表示它的字面意思,也就是說并不表示\和n這兩個字符本身,而是合起來表示一個換行符(Line Feed)。例如我們寫三條打印語句: ``` printf("Hello, world.\n"); printf("Goodbye, "); printf("cruel world!\n"); ``` 運行的結果是第一條語句單獨打到第一行,后兩條語句都打到第二行。為了節省篇幅突出重點,以后的例子通常省略`#include`和`int main(void) { ... }`這些Boilerplate,但讀者在練習時需要加上這些構成一個完整的程序才能編譯通過。C標準規定的轉義字符有以下幾種: **表?2.1.?C標準規定的轉義字符** | | | | --- | --- | | `\'` | 單引號'(Single Quote或Apostrophe) | | `\"` | 雙引號" | | `\?` | 問號?(Question Mark) | | `\\` | 反斜線\(Backslash) | | `\a` | 響鈴(Alert或Bell) | | `\b` | 退格(Backspace) | | `\f` | 分頁符(Form Feed) | | `\n` | 換行(Line Feed) | | `\r` | 回車(Carriage Return) | | `\t` | 水平制表符(Horizontal Tab) | | `\v` | 垂直制表符(Vertical Tab) | 如果在字符串字面值中要表示單引號和問號,既可以使用轉義序列`\'`和`\?`,也可以直接用字符'和?,而要表示\或"則必須使用轉義序列,因為\字符表示轉義而不表示它的字面含義,"表示字符串的界定符而不表示它的字面含義。可見轉義序列有兩個作用:一是把普通字符轉義成特殊字符,例如把字母n轉義成換行符;二是把特殊字符轉義成普通字符,例如\和"是特殊字符,轉義后取它的字面值。 C語言規定了幾個控制字符,不能用鍵盤直接輸入,因此采用\加字母的轉義序列表示。`\a`是響鈴字符,在字符終端下顯示這個字符的效果是PC喇叭發出嘀的一聲,在圖形界面終端下的效果取決于終端的實現。在終端下顯示`\b`和按下退格鍵的效果相同。`\f`是分頁符,主要用于控制打印機在打印源代碼時提前分頁,這樣可以避免一個函數跨兩頁打印。`\n`和`\r`分別表示Line Feed和Carriage Return,這兩個詞來自老式的英文打字機,Line Feed是跳到下一行(進紙,喂紙,有個喂的動作所以是feed),Carriage Return是回到本行開頭(Carriage是卷著紙的軸,隨著打字慢慢左移,打完一行就一下子移回最右邊),如果你看過歐美的老電影應該能想起來這是什么。用老式打字機打完一行之后需要這么兩個動作,`\r\n`,所以現在Windows上的文本文件用`\r\n`做行分隔符,許多應用層網絡協議(如HTTP)也用`\r\n`做行分隔符,而Linux和各種UNIX上的文本文件只用`\n`做行分隔符。在終端下顯示`\t`和按下Tab鍵的效果相同,用于在終端下定位表格的下一列,`\v`用于在終端下定位表格的下一行。`\v`比較少用,`\t`比較常用,以后將“水平制表符”簡稱為“制表符”或Tab。請讀者用`printf`語句試試這幾個控制字符的作用。 注意`"Goodbye, "`末尾的空格,字符串字面值中的空格也算一個字符,也會出現在輸出結果中,而程序中別處的空格和Tab多一個少一個往往是無關緊要的,不會對編譯的結果產生任何影響,例如不縮進不會影響程序的結果,`main`后面多幾個空格也沒影響,但是`int`和`main`之間至少要有一個空格分隔開: ``` int main (void) { printf("Hello, world.\n"); return 0; } ``` 不僅空格和Tab是無關緊要的,換行也是如此,我甚至可以把整個程序寫成一行,但是`include`必須單獨占一行: ``` #include<stdio.h> int main(void){printf("Hello, world.\n");return 0;} ``` 這樣也行,但肯定不是好的代碼風格,去掉縮進已經很影響可讀性了,寫成現在這個樣子可讀性更差。如果編譯器說第2行有錯誤,也很難判斷是哪個語句有錯誤。所以,_好的代碼風格要求縮進整齊,每個語句一行,適當留空行_。 ## 2.?常量 常量(Constant)是程序中最基本的元素,有字符(Character)常量、整數(Integer)常量、浮點數(Floating Point)常量和枚舉常量。枚舉常量將在[第?3?節 “數據類型標志”](ch07s03.html#struct.datatag)介紹。下面看一個例子: ``` printf("character: %c\ninteger: %d\nfloating point: %f\n", '}', 34, 3.14); ``` 字符常量要用單引號括起來,例如上面的`'}'`,注意單引號只能括一個字符而不能像雙引號那樣括一串字符,字符常量也可以是一個轉義序列,例如`'\n'`,這時雖然單引號括了兩個字符,但實際上只表示一個字符。和字符串字面值中使用轉義序列有一點區別,如果在字符常量中要表示雙引號"和問號?,既可以使用轉義序列`\"`和`\?`,也可以直接用字符"和?,而要表示'和\則必須使用轉義序列。<sup>[[2](#ftn.id2707915)]</sup> 計算機中整數和小數的內部表示方式不同(將在[第?14?章 _計算機中數的表示_](ch14.html#number)詳細介紹),因而在C語言中是兩種不同的類型(Type),例如上例的`34`和`3.14`,小數在計算機術語中稱為浮點數。這個語句的輸出結果和Hello world不太一樣,字符串`"character: %c\ninteger: %d\nfloating point: %f\n"`并不是按原樣打印輸出的,而是輸出成這樣: ``` character: } integer: 34 floating point: 3.14 ``` `printf`中的第一個字符串稱為格式化字符串(Format String),它規定了后面幾個常量以何種格式插入到這個字符串中,在格式化字符串中%號(Percent Sign)后面加上字母c、d、f分別表示字符型、整型和浮點型的轉換說明(Conversion Specification),轉換說明只在格式化字符串中占個位置,并不出現在最終的打印結果中,這種用法通常叫做占位符(Placeholder)。這也是一種字面意思與真實意思不同的情況,但是轉換說明和轉義序列又有區別:_轉義序列是編譯時處理的,而轉換說明是在運行時調用`printf`函數處理的_。源文件中的字符串字面值是`"character: %c\ninteger: %d\nfloating point: %f\n"`,`\n`占兩個字符,而編譯之后保存在可執行文件中的字符串是`character: %c換行integer: %d換行floating point: %f換行`,`\n`已經被替換成一個換行符,而`%c`不變,然后在運行時這個字符串被傳給`printf`,`printf`再把其中的`%c`、`%d`、`%f`解釋成轉換說明。 有時候不同類型的數據很容易弄混,例如`"5"`、`'5'`、`5`,如果你注意了它們的界定符就會很清楚,第一個是字符串字面值,第二個是字符,第三個是整數,看了本章后面幾節你就知道為什么一定要嚴格區分它們之間的差別了。 ### 習題 1、總結前面介紹的轉義序列的規律,想想在`printf`的格式化字符串中怎么表示一個%字符?寫個小程序試驗一下。 * * * <sup>[[2](#id2707915)]</sup> 讀者可能會奇怪,為什么需要規定一個轉義序列`\?`呢?因為C語言規定了一些三連符(Trigraph),在某些特殊的終端上缺少某些字符,需要用Trigraph輸入,例如用`??=`表示#字符。Trigraph極不常用,介紹這個只是為了讓讀者理解C語言規定轉義序列的作用,即特殊字符轉普通字符,普通字符轉特殊字符,?也是一種特殊字符。極不常用的C語法在本書中通常不會介紹。 ## 3.?變量 變量(Variable)是編程語言最重要的概念之一,變量是計算機存儲器中的一塊命名的空間,可以在里面存儲一個值(Value),存儲的值是可以隨時變的,比如這次存個字符`'a'`下次存個字符`'b'`,正因為變量的值可以隨時變所以才叫變量。 常量有不同的類型,因此變量也有不同的類型,變量的類型也決定了它所占的存儲空間的大小。例如以下四個語句定義了四個變量`fred`、`bob`、`jimmy`和`tom`,它們的類型分別是字符型、整型、浮點型: ``` char fred; int bob; float jimmy; double tom; ``` ### 聲明和定義 C語言中的聲明(Declaration)有變量聲明、函數聲明和類型聲明三種。如果一個變量或函數的聲明要求編譯器為它分配存儲空間,那么也可以稱為定義(Definition),因此定義是聲明的一種。在接下來幾章的示例代碼中變量聲明都是要分配存儲空間的,因而都是定義,等學到[第?2?節 “定義和聲明”](ch20s02.html#link.defdecl)我們會看到哪些變量聲明不分配存儲空間因而不是定義。在下一章我們會看到函數的定義和聲明也是這樣區分的,分配存儲空間的函數聲明可以稱為函數定義。從[第?7?章 _結構體_](ch07.html#struct)開始我們會看到類型聲明,聲明一個類型是不分配存儲空間的,但似乎叫“類型定義”聽起來也不錯,所以在本書中“類型定義”和“類型聲明”表示相同的含義。聲明和語句類似,也是以;號結尾的,但是在語法上聲明和語句是有區別的,語句只能出現在{}括號中,而聲明既可以出現在{}中也可以出現在所有{}之外。 浮點型有三種,`float`是單精度浮點型,`double`是雙精度浮點型,`long double`是精度更高的浮點型。它們之間的區別和轉換規則將在[第?15?章 _數據類型詳解_](ch15.html#type)詳細介紹,在隨后的幾章中我們只使用`double`類型,上一節介紹的常量3.14應該看作`double`類型的常量,`printf`的`%f`也應該看作`double`型的轉換說明。給變量起名不能太隨意,上面四個變量的名字就不夠好,我們猜不出這些變量是用來存什么的。而像下面這樣起名就很好: ``` char firstletter; char lastletter; int hour, minute; ``` 我們可以猜得到這些變量是用來存什么的,前兩個變量的取值范圍應該是`'A'`~`'Z'`或`'a'`~`'z'`,變量`hour`的取值范圍應該是0~23,變量`minute`的取值范圍應該是0~59,所以應該給變量起有意義的名字。從這個例子中我們也看到兩個相同類型的變量(`hour`和`minute`)可以一起聲明。 給變量起名有一定的限制,C語言規定必須以字母或下劃線_(Underscore)開頭,后面可以跟若干個字母、數字、下劃線,但不能有其它字符。例如這些是合法的變量名:`Abc`、`__abc__`、`_123`。但這些是不合法的變量名:`3abc`、`ab$`。其實這個規則不僅適用于變量名,也適用于所有可以由程序員起名的語法元素,例如以后要講的函數名、宏定義、結構體成員名等,在C語言中這些統稱為標識符(Identifier)。 另外要注意,表示類型的`char`、`int`、`float`、`double`等雖然符合上述規則,但也不能用作標識符。在C語言中有些單詞有特殊意義,不允許用作標識符,這些單詞稱為關鍵字(Keyword)或保留字(Reserved Word)。通常用于編程的文本編輯器都會高亮顯示(Highlight)這些關鍵字,所以只要小心一點通常不會誤用作標識符。C99規定的關鍵字有: auto??break??case??char??const??continue??default??do??double else??enum??extern??float??for??goto??if??inline??int??long register??restrict??return??short??signed??sizeof??static??struct??switch??typedef union??unsigned??void??volatile??while??_Bool??_Complex??_Imaginary 還有一點要注意,_一般來說應避免使用以下劃線開頭的標識符_,以下劃線開頭的標識符只要不和C語言關鍵字沖突的都是合法的,但是往往被編譯器用作一些功能擴展,C標準庫也定義了很多以下劃線開頭的標識符,所以除非你對編譯器和C標準庫特別清楚,一般應避免使用這種標識符,以免造成命名沖突。 請記住:_理解一個概念不是把定義背下來就行了,一定要理解它的外延和內涵,也就是什么情況屬于這個概念,什么情況不屬于這個概念,什么情況雖然屬于這個概念但一般推薦的做法(Best Practice)是要盡量避免這種情況,這才算是真正理解了。_ ## 4.?賦值 定義了變量之后,我們要把值存到它們所表示的存儲空間里,可以用賦值(Assignment)語句實現: ``` char firstletter; int hour, minute; firstletter = 'a'; /* give firstletter the value 'a' */ hour = 11; /* assign the value 11 to hour */ minute = 59; /* set minute to 59 */ ``` 注意變量一定要先聲明后使用,編譯器必須先看到變量聲明,才知道`firstletter`、`hour`和`minute`是變量名,各自代表一塊存儲空間。另外,變量聲明中的類型表明這個變量代表多大的一塊存儲空間,這樣編譯器才知道如何讀寫這塊存儲空間。還要注意,這里的等號不表示數學里的相等關系,和1+1=2的等號是不同的,這里的等號表示賦值。在數學上不會有`i=i+1`這種等式成立,而在C語言中表示把變量`i`的存儲空間中的值取出來,再加上1,得到的結果再存回`i`的存儲空間中。再比如,在數學上`a=7`和`7=a`是一樣的,而在C語言中后者是不合法的。總結一下:定義一個變量,就是分配一塊存儲空間并給它命名;給一個變量賦值,就是把一個值保存到這塊存儲空間中。變量的定義和賦值也可以一步完成,這稱為變量的初始化(Initialization),例如要達到上面代碼的效果也可以這樣寫: ``` char firstletter = 'a'; int hour = 11, minute = 59; ``` 在初始化語句中,等號右邊的值叫做Initializer,例如上面的`'a'`、11和59。注意,_初始化是一種特殊的聲明,而不是一種賦值語句_。就目前來看,先定義一個變量再給它賦值和定義這個變量的同時給它初始化所達到的效果是一樣的,C語言的很多語法規則既適用于賦值也適用于初始化,但在以后的學習中你也會了解到它們之間的不同,請在學習過程中注意總結賦值和初始化的相同和不同之處。 如果在紙上“跑”一個程序(每個初學編程的人都要練這項基本功),可以用一個框表示變量的存儲空間,在框的外邊標上變量名,在框里記上它的值,如下圖所示。 **圖?2.1.?在紙上表示變量** ![在紙上表示變量](https://box.kancloud.cn/2016-04-02_56ff80d058c79.png) 你可以用不同形狀的框表示不同類型的變量,這樣可以提醒你給變量賦的值必須符合它的類型。如果所賦的值和變量的類型不符會導致編譯器報警告或報錯(這是一種語義錯誤),例如: ``` int hour, minute; hour = "Hello."; /* WRONG ! */ minute = "59"; /* WRONG !! */ ``` 注意第3個語句,把`"59"`賦給`minute`看起來像是對的,但是類型不對,字符串不能賦給整型變量。 既然可以為變量的存儲空間賦值,就應該可以把值取出來用,現在我們取出這些變量的值用`printf`打印: ``` printf("Current time is %d:%d", hour, minute); ``` 變量名用在等號左邊表示賦值,而用在`printf`中表示把它的存儲空間中的值取出來替換在那里。不同類型的變量所占的存儲空間大小是不同的,數據表示方式也不同,變量的最小存儲單位是字節(Byte),在C語言中`char`型變量占一個字節,其它類型的變量占多少字節在不同平臺上有不同的規定,將在[第?15?章 _數據類型詳解_](ch15.html#type)詳細討論。 ## 5.?表達式 常量和變量都可以參與加減乘除運算,例如`1+1`、`hour-1`、`hour * 60 + minute`、`minute/60`等。這里的+ - * /稱為運算符(Operator),而參與運算的常量和變量稱為操作數(Operand),上面四個由運算符和操作數所組成的算式稱為表達式(Expression)。 和數學上規定的一樣,`hour * 60 + minute`這個表達式應該先算乘再算加,也就是說運算符是有優先級(Precedence)的,*和/是同一優先級,+和-是同一優先級,*和/的優先級高于+和-。對于同一優先級的運算從左到右計算,如果不希望按默認的優先級計算則要加()括號(Parenthesis)。例如`(3+4)*5/6`應先算3+4,再算*5,再算/6。 前面講過打印語句和賦值語句,現在我們定義:在任意表達式后面加個;號也是一種語句,稱為表達式語句。例如: ``` hour * 60 + minute; ``` 這是個合法的語句,但這個語句在程序中起不到任何作用,把hour的值和minute的值取出來加乘,得到的計算結果卻沒有保存,白算了一通。再比如: ``` int total_minute; total_minute = hour * 60 + minute; ``` 這個語句就很有意義,把計算結果保存在另一個變量`total_minute`里。事實上等號也是一種運算符,稱為賦值運算符,賦值語句就是一種表達式語句,等號的優先級比+和*都低,所以先算出等號右邊的結果然后才做賦值操作,整個表達式`total_minute = hour * 60 + minute`加個;號構成一個語句。 _任何表達式都有值和類型兩個基本屬性_。`hour * 60 + minute`的值是由三個`int`型的操作數計算出來的,所以這個表達式的類型也是`int`型。同理,表達式`total_minute = hour * 60 + minute`的類型也是`int`,它的值是多少呢?C語言規定等號運算符的計算結果就是等號左邊被賦予的那個值,所以這個表達式的值和`hour * 60 + minute`的值相同,也和`total_minute`的值相同。 等號運算符還有一個和+ - * /不同的特性,如果一個表達式中出現多個等號,不是從左到右計算而是從右到左計算,例如: ``` int total_minute, total; total = total_minute = hour * 60 + minute; ``` 計算順序是先算`hour * 60 + minute`得到一個結果,然后算右邊的等號,就是把`hour * 60 + minute`的結果賦給變量`total_minute`,這個結果同時也是整個表達式`total_minute = hour * 60 + minute`的值,再算左邊的等號,即把這個值再賦給變量`total`。同樣優先級的運算符是從左到右計算還是從右到左計算稱為運算符的結合性(Associativity)。+ - * /是左結合的,等號是右結合的。 現在我們總結一下到目前為止學過的語法規則: 表達式?→?標識符 表達式?→?常量 表達式?→?字符串字面值 表達式?→?(表達式) 表達式?→?表達式?+?表達式 表達式?→?表達式?-?表達式 表達式?→?表達式?*?表達式 表達式?→?表達式?/?表達式 表達式?→?表達式?=?表達式 語句?→?表達式; 語句?→?printf(表達式,?表達式,?表達式,?...); 變量聲明?→?類型?標識符?=?Initializer,?標識符?=?Initializer,?...; (=?Initializer的部分可以不寫) 注意,_本書所列的語法規則都是簡化過的,是不準確的,目的是為了便于初學者理解_,比如上面所列的語法規則并沒有描述運算符的優先級和結合性。完整的C語法規則請參考[[C99]](bi01.html#bibli.c99 "ISO/IEC 9899: Programming Languages - C")的Annex A。 表達式可以是單個的常量或變量,也可以是根據以上規則組合而成的更復雜的表達式。以前我們用`printf`打印常量或變量的值,現在可以用`printf`打印更復雜的表達式的值,例如: ``` printf("%d:%d is %d minutes after 00:00\n", hour, minute, hour * 60 + minute); ``` 編譯器在翻譯這條語句時,首先根據上述語法規則把這個語句解析成下圖所示的語法樹,然后再根據語法樹生成相應的指令。語法樹的末端的是一個個Token,每一步展開利用一條語法規則。 **圖?2.2.?語法樹** ![語法樹](https://box.kancloud.cn/2016-04-02_56ff80d067d7a.png) 根據這些語法規則進一步組合可以寫出更復雜的語句,比如在一條語句中完成計算、賦值和打印功能: ``` printf("%d:%d is %d minutes after 00:00\n", hour, minute, total_minute = hour * 60 + minute); ``` 理解組合(Composition)規則是理解語法規則的關鍵所在,正因為可以根據語法規則任意組合,我們才可以用簡單的常量、變量、表達式、語句搭建出任意復雜的程序,以后我們學習新的語法規則時會進一步體會到這一點。從上面的例子可以看出,表達式不宜過度組合,否則會給閱讀和調試帶來困難。 根據語法規則組合出來的表達式在語義上并不總是正確的,例如: ``` minute + 1 = hour; ``` 等號左邊的表達式要求表示一個存儲位置而不是一個值,這是等號運算符和+ - * /運算符的又一個顯著不同。有的表達式既可以表示一個存儲位置也可以表示一個值,而有的表達式只能表示值,不能表示存儲位置,例如`minute + 1`這個表達式就不能表示存儲位置,放在等號左邊是語義錯誤。表達式所表示的存儲位置稱為左值(lvalue)(允許放在等號左邊),而以前我們所說的表達式的值也稱為右值(rvalue)(只能放在等號右邊)。上面的話換一種說法就是:_有的表達式既可以做左值也可以做右值,而有的表達式只能做右值_。目前我們學過的表達式中只有變量可以做左值,可以做左值的表達式還有幾種,以后會講到。 我們看一個有意思的例子,如果定義三個變量`int a, b, c;`,表達式`a = b = c`是合法的,先求`b = c`的值,再把這個值賦給`a`,而表達式`(a = b) = c`是不合法的,先求`(a = b)`的值沒問題,但`(a = b)`這個表達式不能再做左值了,因此放在`= c`的等號左邊是錯的。 關于整數除法運算有一點特殊之處: ``` hour = 11; minute = 59; printf("%d and %d hours\n", hour, minute / 60); ``` 執行結果是`11 and 0 hours`,也就是說59/60得0,這是因為兩個`int`型操作數相除的表達式仍為`int`型,只能保存計算結果的整數部分,即使小數部分是0.98也要舍去。 向下取整的運算稱為Floor,用數學符號??表示;向上取整的運算稱為Ceiling,用數學符號??表示。例如: ?59/60?=0 ?59/60?=1 ?-59/60?=-1 ?-59/60?=0 在C語言中整數除法取的既不是Floor也不是Ceiling,無論操作數是正是負總是把小數部分截掉,在數軸上向零的方向取整(Truncate toward Zero),或者說當操作數為正的時候相當于Floor,當操作符為負的時候相當于Ceiling。回到先前的例子,要得到更精確的結果可以這樣: ``` printf("%d hours and %d percent of an hour\n", hour, minute * 100 / 60); printf("%d and %f hours\n", hour, minute / 60.0); ``` 在第二個`printf`中,表達式是`minute / 60.0`,60.0是`double`型的,/運算符要求左右兩邊的操作數類型一致,而現在并不一致。C語言規定了一套隱式類型轉換規則,在這里編譯器自動把左邊的`minute`也轉成`double`型來計算,整個表達式的值也是`double`型的,在格式化字符串中應該用`%f`轉換說明與之對應。本來編程語言作為一種形式語言要求有簡單而嚴格的規則,自動類型轉換規則不僅很復雜,而且使C語言的形式看起來也不那么嚴格了,C語言這么設計是為了書寫程序簡便而做的折衷,有些事情編譯器可以自動做好,程序員就不必每次都寫一堆繁瑣的轉換代碼。然而C語言的類型轉換規則非常難掌握,本書的前幾章會盡量避免類型轉換,到[第?3?節 “類型轉換”](ch15s03.html#type.conversion)再集中解決這個問題。 ### 習題 1、假設變量`x`和`n`是兩個正整數,我們知道`x/n`這個表達式的結果要取Floor,例如`x`是17,`n`是4,則結果是4。如果希望結果取Ceiling應該怎么寫表達式呢?例如`x`是17,`n`是4,則結果是5;`x`是16,`n`是4,則結果是4。 ## 6.?字符類型與字符編碼 字符常量或字符型變量也可以當作整數參與運算,例如: ``` printf("%c\n", 'a'+1); ``` 執行結果是`b`。 我們知道,符號在計算機內部也用數字表示,每個字符在計算機內部用一個整數表示,稱為字符編碼(Character Encoding),目前最常用的是ASCII碼(American Standard Code for Information Interchange,美國信息交換標準碼),詳見[圖?A.1 “ASCII碼表”](apas01.html#app-encoding.ascii)。表中每一欄的最后一列是字符,前三列分別是用十進制(Dec)、十六進制(Hx)和八進制(Oct)表示的字符編碼,各種進制之間的換算將在[第?2?節 “不同進制之間的換算”](ch14s02.html#number.convert)介紹。從十進制那一列可以看出ASCII碼的取值范圍是0~127。表中的很多字符是不可見字符(Non-printable Character)或空白字符(Whitespace)<sup>[[3](#ftn.id2709665)]</sup>,不能像字母a這樣把字符本身填在表中,而是用一個名字來描述該字符,例如CR(carriage return)、LF(NL line feed,newline)、DEL等等。作為練習,請讀者查一查[表?2.1 “C標準規定的轉義字符”](ch02s01.html#expr.escapeseq)中的字符在ASCII碼表中的什么位置。 回到剛才的例子,在ASCII碼中字符`a`是97,字符`b`是98。計算`'a'+1`這個表達式,應該按ASCII碼把`'a'`當作整數值97,然后加1,得到98,然后`printf`把98這個整數值當作ASCII碼來解釋,打印出相應的字符`b`。 之前我們說“整型”是指`int`型,而現在我們知道`char`型本質上就是整數,只不過取值范圍比`int`型小,所以_以后我們把`char`型和`int`型統稱為整數類型(Integer Type)或簡稱整型_,以后我們還要學習幾種類型也屬于整型,將在[第?1?節 “整型”](ch15s01.html#type.integertype)詳細介紹。 字符`'a'`~`'z'`、`'A'`~`'Z'`、`'0'`~`'9'`的ASCII碼都是連續的,因此表達式`'a'+25`和`'z'`的值相等,`'0'+9`和`'9'`的值也相等。注意`'0'`~`'9'`的ASCII碼是十六進制的30~39,和整數值0~9是不相等的。 字符也可以用ASCII碼轉義序列表示,這種轉義序列由\加上1~3個八進制數字組成,或者由`\x`或大寫`\X`加上1~2個十六進制數字組成,可以用在字符常量或字符串字面值中。例如`'\0'`表示NUL字符(Null Character),`'\11'`或`'\x9'`表示Tab字符,`"\11"`或`"\x9"`表示由Tab字符組成的字符串。注意`'0'`的ASCII碼是48,而`'\0'`的ASCII碼是0,兩者是不同的。 * * * <sup>[[3](#id2709665)]</sup> 空白字符在不同的上下文中有不同的含義,在C語言中空白字符定義為空格、水平Tab、垂直Tab、換行和分頁符,本書在使用“空白字符”這個詞時會明確說明在當前上下文中空白字符指的是哪些字符。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看