国产视频自拍一区-99视频精品全部免费免费观-三级视频网站在线观看-轻轻碰在线视频免费视频 国产999在线观看_国产情吕AⅤ毛片AⅤ毛片_国产欧美一区二区精品性色_国产女人在线视频

開發(fā)軟件技術(shù)C程序設(shè)計語言

來自:米么信息科技
時間:2016-07-10 15:38:54
分享:
米么信息 米么信息 米么信息
開發(fā)軟件技術(shù)C程序設(shè)計語言,一個c語言程序, 無論大小, 都是由 函數(shù) 和 變量 組成的. 函數(shù)中含有一些 語句 , 以指定要執(zhí)行的計算操作; 變量則用于存儲計算過程中使用的值.

Chapter 1 導(dǎo)言

  • 一個c語言程序, 無論大小, 都是由 函數(shù) 和 變量 組成的. 函數(shù)中含有一些 語句 , 以指定要執(zhí)行的計算操作; 變量則用于存儲計算過程中使用的值.

  • 每個程序都從 main 函數(shù)開始, 這意味著每個程序都必須在某個位置包含一個main函數(shù)

  • 函數(shù)之間進(jìn)行數(shù)據(jù)交換的一種方式是調(diào)用函數(shù)向被調(diào)用函數(shù)提供一個值(成為 參數(shù))列表, 函數(shù)名后的一對圓括號()將參數(shù)列表括起來. 函數(shù)的語句用一對花括號{}括起來.

  • /* ... */ - (多行)注釋, 程序中允許出現(xiàn)空格, 制表符, 換行符的地方, 都允許使用注釋

  • 在c語言中, 所有變量都必須先聲明后使用

  • 縮進(jìn), 是為了凸顯程序的邏輯結(jié)構(gòu); 每行只寫一條語句, 并在運算符兩邊各加一個空格字符, 使運算的結(jié)合關(guān)系更清楚明了

  • c語言的整數(shù)除法操作將進(jìn)行 舍位 
    (python中, // 才是整除)

  • 格式化字符串, % 用于占位, %之后是描述, 表示之后會用何種類型的數(shù)據(jù)補位, 還可以添加具體的格式, 比如數(shù)字寬. 因此叫 格式化字符串

  • c語言本身并沒有定義IO功能, printf 是標(biāo)準(zhǔn)庫函數(shù)中的一個函數(shù)

  • 如果某個算術(shù)運算符的所有操作數(shù)均為整型, 則執(zhí)行整型運算; 如果至少有一個操作數(shù)是浮點數(shù), 則運算開始之前, 整型數(shù)將被轉(zhuǎn)換為浮點數(shù)

  • 即使浮點常量可以用整數(shù)的形式表示, 在書寫時最好為其顯式地加上小數(shù)點

  • 舉例說明: %6.1f - 表示待打印的浮點數(shù)至少占6個字符寬, 且小數(shù)點后有1位數(shù)字

  • %d - d是 decimal 的縮寫, 表示十進(jìn)制整型數(shù); %ld - 表示long; %o - 表示八進(jìn)制數(shù); %x - 表示十六進(jìn)制數(shù); %c - 表示字符; %s - 表示字符串; %% - 百分號本身

  • 在允許使用某種類型變量值的任何場合, 都可以使用該類型的更復(fù)雜的表達(dá)式

  • for語句比較適合初始化和增長步長都是單條語句并且邏輯相關(guān)的情形, 它將循環(huán)控制語句集中放在一起, 比while更緊湊

  • #define 符號常量 替換文本 - 將符號常量定義為一個特定的字符串, 之后所有程序中出現(xiàn)的符號常量都用替換文本替換. 指明行末沒有分號

  • 無論文本從何處輸入, 輸出到何處, 其輸入/輸出都是按字符流的方式處理. 文本流 是由多行字符構(gòu)成的字符序列, 而每行字符則由0個或多個字符組成, 行末是一個換行符

  • 字符在鍵盤, 屏幕或其他任何地方無論以什么形式表現(xiàn), 在機器內(nèi)部都以 位模式 存儲

  • EOF 定義在 

    中, 是一個整型數(shù)(-1)
  • 賦值操作是一個表達(dá)式, 并且具有一個值, 即賦值后左邊變量保存的值. 因此, 賦值可以作為更大的表達(dá)式的一部分出現(xiàn)

  • c語言的語法規(guī)則要求for語句必須有一個循環(huán)體, 可用單獨的分號表示 空語句

  • 用單引號表示字符, 可以表示一個整型值, 如 'A' 表示65; 用雙引號表示字符串, "A" 是一個僅包含一個字符的字符串變量

  • 通常將函數(shù)定義中圓括號內(nèi)列表中出現(xiàn)的變量成為 形式參數(shù) , 而將函數(shù)調(diào)用中與形式參數(shù)對應(yīng)的值稱為 實際參數(shù)

  • 函數(shù)調(diào)用, 將 控制權(quán) 交給被調(diào)用的函數(shù); 返回時, 將控制權(quán)返回給調(diào)用者, 返回值或不帶返回值

  • main函數(shù)本身也是函數(shù), 也可以向其調(diào)用者返回一個值, 該調(diào)用者實際上就是 程序的執(zhí)行環(huán)境 . 返回值為0, 表示程序正常終止, 返回值非0, 表示出現(xiàn)異常情況或出錯結(jié)束條件.

  • 在c語言中, 所有函數(shù)參數(shù)都”通過值”傳遞, 也就是說, 傳遞給被調(diào)用函數(shù)的參數(shù)值存放在臨時變量中, 而不是存放在原來的變量中. 這意味著, 在c語言中, 被調(diào)用函數(shù)不能直接修改主調(diào)函數(shù)中變量的值, 而只能修改其私有的臨時副本的值

  • 傳遞值的一個優(yōu)點是: 在被調(diào)用函數(shù)中, 參數(shù)可以看作便于初始化的局部變量(在函數(shù)內(nèi)部可任意修改, 而不影響原來的變量), 從而使得額外使用的變量更少, 使程序更緊湊簡潔.

  • void 關(guān)鍵字, 顯式地說明函數(shù)不返回任何值

  • c語言中, 形如”hello/n”的字符串常量, 以字符數(shù)組的形式存儲時, 數(shù)組的各元素分別存儲字符串的各個字符, 并以’/0’標(biāo)志字符串的結(jié)束

  • 初始化變量時, 不為其賦值, 其中存放的是無效值

  • 函數(shù)中的每個局部變量只在函數(shù)被調(diào)用時存在, 在函數(shù)執(zhí)行完畢退出時銷毀.

  • 外部變量 可以在全局范圍內(nèi)訪問. 函數(shù)間可通過外部變量交換數(shù)據(jù), 而不必使用參數(shù)表. 外部變量在程序執(zhí)行期間一直存在.

  • 外部變量必須定義在所有函數(shù)之外, 且只能定義一次, 定義后編譯程序時將為它分配存儲單元. 在每個需要訪問外部變量的函數(shù)中, 必須聲明相應(yīng)的外部變量, 聲明時用extern 顯式聲明, 也可以通過上下文隱式聲明.

  • 通常的做法是, 所有外部變量的定義都放在源文件的開始處, 這樣可以省略extern聲明. 如果程序包含多個源文件, 需要使用別的源文件中的外部變量時, 要用extern聲明來建立該變量與其定義之間的聯(lián)系

  • 通常將變量和函數(shù)的extern聲明放在一個單獨的文件中, 這個文件就稱為 頭文件 , 并在每個源文件的開頭使用#include語句將所要用的文件包含進(jìn)來

  • 在ANSI C中, 如果要聲明空參數(shù)列表, 必須使用關(guān)鍵字void進(jìn)行顯式聲明. 空圓括號對是舊版c語言的做法

  • 定義 表示創(chuàng)建變量或分配存儲單元, 而 聲明 指的是說明變量的性質(zhì), 但不分配存儲單元

  • 過分依賴外部變量會導(dǎo)致一定的風(fēng)險, 因為它會使程序中的數(shù)據(jù)關(guān)系模糊

Chapter 2 類型, 運算符與表達(dá)式

  • 在傳統(tǒng)c語言用法中, 變量名使用小寫字母, 符號常量名全部使用大寫字母

  • 不理解的一段話: 對于內(nèi)部名而言, 至少前面31個字符是有效的. 函數(shù)名與外部變量名包含的字符數(shù)目可能小于31, 因為匯編程序和加載程序可能會使用這些外部名, 而語言本身無法控制加載和匯編程序. 對于外部名, ANSI標(biāo)準(zhǔn)僅保證前6個字符的唯一性, 且不區(qū)分大小寫.

  • 選擇的變量名要能夠盡量從字面上表達(dá)變量的用途. 局部變量一般使用較短的變量名(尤其是循環(huán)控制變量), 外部變量使用較長的名字

  • c語言僅提供了下列幾種基本數(shù)據(jù)類型:

    1. char 字符型, 占用一個字節(jié)(8位), 可以存放本地字符集的一個字符

    2. int 整型, 通常反映了所用機器中整數(shù)的最自然長度

    3. float

    4. double

  • 類型限定符 signed 與 unsigned 用于限定char類型或任何整型. unsigned類型的數(shù)總是非負(fù)值, 并遵守算術(shù)模2^n定律. 舉例說明, char對象占8位, 那么unsigned char類型變量的取值范圍是0~255, 而signed char類型變量的取值范圍是-128~127

  • 類似 1234 的整數(shù)常量屬性int類型, long類型的常量以字母l或L結(jié)尾. 如果一個整數(shù)太大以致無法用int表示時, 將被當(dāng)作long類型處理. 無符號常量以字母u或U結(jié)尾. ul或UL表明unsigned long類型

  • 沒有后綴的浮點數(shù)常量為double類型, 用后綴f或F表示float類型, 后綴l或L表示long double

  • 用前綴0表示八進(jìn)制整數(shù), 0x或0X表示十六進(jìn)制整數(shù). 八進(jìn)制與十六進(jìn)制同樣可以使用u或l后綴

  • 一個 字符常量 是一個整數(shù), 書寫時將一個字符括在單引號中表示, 如’0’, 字符在機器字符集中的數(shù)值就是字符常量的值, 如’0’的值就是48

  • 使用字符常量的方便之處在于無需關(guān)心字符對應(yīng)的具體值, 且增加了程序可讀性. 字符常量一般用來與其他字符進(jìn)行比較, 比如將一個字符與’0’和’9’進(jìn)行比較, 以判斷其是否是數(shù)字字符

  • 某些字符可通過 轉(zhuǎn)義字符序列 表示為字符和字符串常量.

  • 用 /ooo 表示任意字節(jié)大小(0~255)的位模式, 其中ooo代表3個八進(jìn)制數(shù)字; 或用 /xhh 表示, hh是一個或多個十六進(jìn)制數(shù)字. 舉例說明, #define BELL '/007' , #define BELL '/x7'

  • ANSI C的全部轉(zhuǎn)義字符:

  • /a - 響鈴符

  • /b - 回退符

  • /f - 換頁符

  • /n - 換行符

  • /r - 回車符

  • /t - 橫向制表符

  • /v - 縱向制表符

  • // - 反斜桿

  • /? - 問號

  • /' - 單引號

  • /" - 雙引號

  • /ooo - 八進(jìn)制數(shù)

  • /xhh - 十六進(jìn)制數(shù)

  • 字符常量 '/0' 表示值為0的字符, 即空字符(null). 通常用 '/0' 的形式代表0, 以強調(diào)某些表達(dá)式的字符屬性, 但其數(shù)字值為0

  • 常量表達(dá)式 是僅僅只包含常量的表達(dá)式, 在編譯時求值, 而不是在運行時求值, 可以出現(xiàn)在常量可以出現(xiàn)的任何位置

  • 字符串常量 也叫 字符串字面值 , 是用雙引號括起來的0或多個字符組成的字符序列.

  • 從技術(shù)角度看, 字符串常量就是字符數(shù)組, 字符串的內(nèi)部表示使用一個空字符’/0’作為串的結(jié)尾, 因此, 存儲字符串的物理存儲單元數(shù)比括在雙引號內(nèi)的字符數(shù)多一個. 這種表示方法也說明, c語言對字符串長度沒有限制 , 但程序必須掃描完整個字符串后才能確定字符串的長度.

  • 字符常量與僅包含一個字符的字符串是有區(qū)別的, ‘x’與”x”是不同的: 前者是一個整數(shù), 其值是字母x在機器字符集中對應(yīng)的數(shù)值; 后者是一個包含一個字符以及一個結(jié)束符’/0’的字符數(shù)組

  • 枚舉是一個常量整型值的列表, 例如 enum boolean {NO = 0, YES};

  • 沒有顯式說明的情況下, enum類型第一個枚舉名的值為0, 第二個為1, 依此類推. 如果只指定了部分枚舉名的值, 那么未指定值的枚舉名的值將依著最后一個指定值向后遞增

  • 枚舉為建立常量值與名字值之間的關(guān)聯(lián)提供了一種便利的方式. 相對于#define語句, 優(yōu)勢在于常量值可以自動生成

  • 如果變量不是局部變量, 則只能進(jìn)行一次初始化操作, 從概念上講, 應(yīng)該在程序開始執(zhí)行之前進(jìn)行, 并且初始化表達(dá)式必須為常量表達(dá)式

  • 每次進(jìn)入函數(shù)或程序塊時, 顯式初始化的局部變量都將被初始化一次, 其初始化表達(dá)式可以是任何表達(dá)式

  • 默認(rèn)情況下, 全局變量與靜態(tài)變量都將被初始化為0; 未經(jīng)顯式初始化的局部變量的值為無效值

  • 任何變量的聲明都可以使用 const 限定符限定, 其指定變量的值不能被修改. 都與數(shù)組而言, const限定符指定數(shù)組的所有元素的值都不能被修改. const限定符也可配合參數(shù)使用, 表明函數(shù)不能修改參數(shù)的值

  • 在有負(fù)操作數(shù)的情況下, 整數(shù)除法截取的方向以及取模運算結(jié)果符號取決于具體機器的實現(xiàn)

  • if (!valid) vs. `if(valid == 0), 很難判斷哪種形式更好, 前者讀起來更直觀(如果不是有效的話), 但對于以下更復(fù)雜的結(jié)構(gòu)可能會難于理解

  • 一般來說, 自動轉(zhuǎn)換是指將”比較窄的”操作數(shù)轉(zhuǎn)換為”比較寬的”操作數(shù), 并不丟失信息的轉(zhuǎn)換

  • char類型就是較小的整型, 無論是否進(jìn)行符號擴展, char類型的變量都被轉(zhuǎn)換為整型變量

  • 由于c語言沒有指定char類型的變量是無符號數(shù)變量, 還是帶符號數(shù)變量, 當(dāng)將一個char類型的值轉(zhuǎn)換為int類型的值時, 其結(jié)果會由于機器的不同而不同

  • c語言的定義保證了機器的標(biāo)準(zhǔn)打印字符集中的字符不會是負(fù)值, 因此, 在表達(dá)式中這些字符總是正值. 但存儲在字符變量中的位模式在某些機器中可能是負(fù)的, 而在另一些機器中可能是正的. 為了保證程序的可移植性, 如果要在char類型的變量中存儲非字符數(shù)據(jù), 最好指定signed或unsigned限定符

  • c語言中, 很多情況下會進(jìn)行隱式的算術(shù)類型轉(zhuǎn)換, 將”較低”的類型轉(zhuǎn)換為”較高”的類型, 運算結(jié)果為”較高”的類型

  • 表達(dá)式中float類型的操作數(shù)不會自動轉(zhuǎn)換為double類型. 一般, 數(shù)學(xué)函數(shù)使用雙精度類型的變量. 使用float類型主要是為了在使用較大的數(shù)組時節(jié)省存儲空間, 有時也是為了節(jié)省機器執(zhí)行時間

  • 賦值時也要進(jìn)行類型轉(zhuǎn)換, 賦值運算符右邊的值需要轉(zhuǎn)換為左邊變量的類型, 左邊變量的類型即賦值表達(dá)式結(jié)果的類型

  • 當(dāng)較長的類型轉(zhuǎn)換為較短的類型, 超出的高位部分將被丟棄: int轉(zhuǎn)char, 丟失信息; float轉(zhuǎn)int, 小數(shù)部分被截斷; double轉(zhuǎn)float, 四舍五入還是截斷取決于具體實現(xiàn)

  • 函數(shù)調(diào)用的參數(shù)是表達(dá)式 , 因此在將參數(shù)傳遞給函數(shù)時也能進(jìn)行類型轉(zhuǎn)換. 在沒有函數(shù)原型的情況下, char與short類型都被轉(zhuǎn)換為int型, float被轉(zhuǎn)換為double. 因此, 即使調(diào)用函數(shù)的參數(shù)為char或float類型, 也將函數(shù)參數(shù)聲明為int或double類型

  • (類型名) 表達(dá)式 - 強制類型轉(zhuǎn)換 , 準(zhǔn)確含義: 表達(dá)式首先被賦給類型名指定的類型的某個變量, 然后再用該變量替換原表達(dá)式

  • 直到看到源碼, 我才明白為什么偽隨機叫偽隨機, 為什么需要初始化種子來提高偽隨機的隨機性:

unsigned long int next = 1;int rand(void){    next = next * 1103515245 + 12345;    return (unsigned int)(next / 65536) % 32768;
}

void srand(unsigned int seed){    next = seed;
}
  • ++n , 先自增, 再使用變量n的值; n++ , 先使用變量n的值, 再將n的值加1

  • 位操作符只能用于整型操作數(shù), 包括 & , | , ^ (異或), << , >> , ~ (按位取反)

  • 按位與運算經(jīng)常用于屏蔽某些二進(jìn)制位

  • 按位或運算常用于將某些二進(jìn)制位置1

  • 對無符號數(shù)進(jìn)行右移, 左邊空出的部分用0填補; 對帶符號數(shù)進(jìn)行右移, 某些機器將對左邊空出的部分用符號位填補( 算術(shù)移位 ), 另一些機器則對左邊空出的部分用0填補( 邏輯移位 )

  • 表達(dá)式 x & ~077 比表達(dá)式 x & 0177700 要好, 因為前者與機器字長無關(guān), 而后者假定x是16位的數(shù)值. 前者可移植性更強, 并且沒有增加額外的開銷, ~077是常量表達(dá)式, 在編譯時求值

  • expr1 op= expr2 等價于 expr1 = (expr1) op (expr2) . 前者 expr1 只計算一次, 后者 expr2 兩邊的圓括號必不可少

  • 賦值運算符的其他優(yōu)點: 表示方式與人們的思維習(xí)慣更接近, 使代碼更易讀, 還有助于編譯器產(chǎn)生高效代碼

  • 賦值表達(dá)式的類型就是它左邊操作數(shù)的類型, 其值是賦值操作完成后的值

  • 在使用條件表達(dá)式 expr1 ? expr2 : expr3 , 建議使用圓括號包裹expr1, 使表達(dá)式的條件部分更易讀

  • c語言沒有指定同一運算符中多個操作數(shù)的計算順序(即a+b等價于b+a), 也沒有指定函數(shù)各參數(shù)的求值順序. 因此 printf("%d %d/n", ++n, power(2, n)); 在不同的編譯器中可能產(chǎn)生不同的結(jié)果, 取決于++n與power函數(shù)的執(zhí)行順序

  • 函數(shù)調(diào)用, 嵌套賦值語句, 自增與自減運算符都可能產(chǎn)生”副作用”, 在對表達(dá)式求值的同時, 修改了某些變量的值. 表達(dá)式何時會產(chǎn)生副作用將由編譯器決定, 最佳的求值順序同機器結(jié)構(gòu)有關(guān)

  • ANSI C標(biāo)準(zhǔn)明確規(guī)定了所有對參數(shù)的副作用都必須在函數(shù)調(diào)用之前生效

  • 在任何一種編程語言中, 如果代碼的執(zhí)行結(jié)果與求值順序相關(guān), 則都不是好的程序設(shè)計風(fēng)格.

Chapter 3 控制流

  • 在c語言中, 分號是語句結(jié)束符; 用花括號將一組聲明和語句括在一起構(gòu)成了 復(fù)合語句 (或稱 程序塊 ), 復(fù)合語句在語法上等價于單條語句

  • 程序的縮進(jìn)結(jié)構(gòu)明確地表明了設(shè)計意圖, 但編譯器無法獲知. 因此在有if語句嵌套的情況下使用花括號, 使代碼可讀性更強

/*二分搜索*/int binsearch(int x, int v[], int n){    int low, high, mid;

    low = 0;
    high = n - 1;    while(low <= high){
        mid = (low + high) / 2;        if (x < v[mid])
            high = mid - 1;        else if (x > v[mid])
            low = mid + 1;        else
            return mid;
    }    return -1;
}
  • switch語句是一種多路判定語句, 它測試表達(dá)式是否與一些常量整數(shù)值中的某一個匹配, 并執(zhí)行相應(yīng)的分支操作. 若沒有哪個分支能匹配表達(dá)式, 執(zhí)行標(biāo)記為default的分支, default分支是可選的

  • switch語句中, case的作用只是一個標(biāo)號 . 某個分支中的代碼執(zhí)行完后, 程序?qū)⑦M(jìn)入下一個分支繼續(xù)執(zhí)行, 付費在程序中顯式地跳轉(zhuǎn), 常用的方法是使用break語句或return語句

  • 除了一個計算需要多個標(biāo)號的情況下, 應(yīng)盡量減少從一個分支直接進(jìn)入下一個分支執(zhí)行的用法, 在不得不使用的情況下應(yīng)該加上適當(dāng)?shù)淖⑨?/p>

  • 作為一種良好的程序設(shè)計風(fēng)格, 在switch語句最后一個分支(default)后也加上一個break語句. 這樣做在邏輯上沒有必要, 但當(dāng)需要向該switch語句后添加其他分支時, 這種防范措施會降低犯錯的可能性

  • 如果省略for語句的測試條件, 則認(rèn)為其值永遠(yuǎn)是真值. for(;;){ ... } 是一個無限循環(huán)

  • while vs. for

  • 如果沒有初始化或重新初始化的操作, 使用while循環(huán)語句更自然

  • 如果語句中需要執(zhí)行簡單的初始化和變量遞增, 使用for語句更適合, 它將循環(huán)控制語句集中放在循環(huán)的開頭, 結(jié)構(gòu)更緊湊, 更清晰

  • 將循環(huán)控制部分集中在一起, 對于多重嵌套循環(huán), 優(yōu)勢更明顯

  • Shell(希爾)排序: 先比較距離遠(yuǎn)的元素, 從而快速減少大量的無序情況, 減輕后續(xù)的工作. 被比較的元素之間的距離逐步減少, 直到減少為1, 排序就變成了相鄰元素的互換

/*Shell*//*
最外層的for語句控制兩個被比較元素之間的距離, 從n/2開始, 逐步進(jìn)行對折, 直到距離為0中間層的for循環(huán)語句用于在元素間移動位置
最內(nèi)層的for語句用于比較各對相距gap個位置的元素, 當(dāng)兩個元素逆序時將它們互換
由于gap的值最終要遞減到1, 因此所有元素最終都會位于正確的排序位置上*/void shellsort(int v[], int n){    int gap, i , j, temp;    for (gap = n / 2; gap > 0; gap /= 2){        for (i = gap; i < n; i++){            for (j = i - gap; j >= 0 && v[j] > v[j+gap]; j -= gap){
                temp = v[j];
                v[j] = v[j + gap];
                v[j + gap] = temp;
            }
        }
    }
}
  • 逗號運算符是c語言優(yōu)先級最低的運算符, 被逗號分隔的一對表達(dá)式將按照從左到右的順序進(jìn)行求值, 各表達(dá)式右邊的操作數(shù)的類型和值即為其結(jié)果的類型和值

  • 逗號運算符最適用于關(guān)系緊密的結(jié)構(gòu).

  • do { ... } while (表達(dá)式) - do-while 循環(huán)體至少會被執(zhí)行一次

  • break - 從switch語句或循環(huán)中提前退出

  • continue - 執(zhí)行下一次循環(huán). 在while或do-while語句中, 意味著立即執(zhí)行測試部分; 在for循環(huán)中, 意味著將控制轉(zhuǎn)移到遞增循環(huán)變量環(huán)節(jié)

Chapter 4 函數(shù)與程序結(jié)構(gòu)

  • 一個設(shè)計得當(dāng)?shù)暮瘮?shù)可以將程序中不需要了解的具體操作細(xì)節(jié)隱藏起來, 從而使整個程序結(jié)構(gòu)更加清晰, 并降低修改程序的難度

  • c語言在設(shè)計中考慮了函數(shù)的高效性和易用性兩個因素, c語言一般由許多小的函數(shù)組成, 而不是由少量較大的函數(shù)組成

  • 分別處理幾個小的部分, 比處理一個大的整體更容易. 這樣可以將不相關(guān)的細(xì)節(jié)隱藏在函數(shù)中, 從而減少不必要的相互影響的機會, 并且函數(shù)也可以在其他函數(shù)中使用

  • 函數(shù)定義中的各構(gòu)成部分都可以省略, 最簡單的函數(shù): dummy() {}

  • 如果函數(shù)定義中省略了返回值類型, 默認(rèn)為int類型

  • 程序可以看成變量定義和函數(shù)定義的集合. 函數(shù)之間通信可以通過參數(shù), 函數(shù)返回值以及外部變量進(jìn)行

  • return語句的后面可以跟任何表達(dá)式, 在必要時, 表達(dá)式將被轉(zhuǎn)換為函數(shù)的返回值類型. 表達(dá)式兩邊通常加一對圓括號

  • 如果某個函數(shù)從一個地方返回時有返回值, 而從另一個地方返回時沒有返回值, 該函數(shù) 并不非法 , 但可能是一種問題的征兆

  • 在任何情況下, 如果函數(shù)沒有成功返回一個值, 則它的”值”肯定是無用的

  • cc命令用 .c 和 .o 兩種擴展名區(qū)分源文件和目標(biāo)文件

  • 如果沒有函數(shù)原型, 則函數(shù)將在第一次出現(xiàn)的表達(dá)式中被隱式聲明, 該函數(shù)的返回值被假定為int類型, 但上下文并不對其參數(shù)做任何假設(shè).

  • 如果函數(shù)聲明中不包含參數(shù), 編譯程序不會對函數(shù)的參數(shù)作任何假設(shè), 并會關(guān)閉所有的參數(shù)檢查. 如果函數(shù)帶有參數(shù), 要聲明它們; 如果不帶參數(shù), 則使用void進(jìn)行聲明

  • 由于c語言不允許在一個函數(shù)中定義其他函數(shù), 因此函數(shù)本身是”外部的”. 默認(rèn)情況下, 外部變量與函數(shù)具有下列性質(zhì): 通過同一個名字對外部變量的所有引用實際上都是引用同一個對象

  • 外部變量可以在全局范圍內(nèi)訪問, 這為函數(shù)之間的數(shù)據(jù)交換提供了一種可以代替函數(shù)參數(shù)與返回值的方式.

  • 如果函數(shù)之間需要共享大量的變量, 使用外部變量要比使用一個長的參數(shù)表更方便, 有效 , 但這種方式可能對程序結(jié)構(gòu)產(chǎn)生不良影響, 而且可能會導(dǎo)致程序中各個函數(shù)之間具有太多的數(shù)據(jù)關(guān)系

  • 如果兩個函數(shù)必須共享某些數(shù)據(jù), 而它們互不調(diào)用對方, 這種情況下, 最簡單的方式是將這些共享數(shù)據(jù)定義為外部變量, 而不是作為函數(shù)參數(shù)傳遞

  • 在函數(shù)中重復(fù)出現(xiàn)的代碼段, 最好設(shè)計成獨立的函數(shù)

  • 逆波蘭表達(dá)式, push 和 pop 函數(shù)必須共享棧和棧頂指針, 因此定義在函數(shù)外部, 作為外部變量. 由于main函數(shù)本身沒有引用?;驐m斨羔? 因此, 對于main函數(shù)就是將它們隱藏起來了

#include <ctype.h>int getch(void);void ungetch(int);/* 獲取下一個運算符或數(shù)值操作數(shù) */int getop(char s[]){    int i, c;    while((s[0] = c = getch()) == ' ' || c == '/t')
        ;
    s[1] = '/0';    if (!isdigit(c) && c != '.')        return c;  /* 不是數(shù) */
    i = 0;    if (isdigit(c))        while (isdigit(s[++i] = c = getch()))
            ;  /* 整數(shù)部分 */
    if (c == '.')        while (isdigit(s[++i] = c = getch()))
            ;  /* 小數(shù)部分 */
    s[i] = '/0';    if(c != EOF)
        ungetch(c);    return NUMBER;
}#define BUFSIZE 100char buf[BUFSIZE];  /* 用于ungetch函數(shù)的緩沖區(qū) */int bufp = 0;       /* buf中下一個空閑位置 */int getch(void){    /* 取一個字符, 可能是ungetch寫回的字符 */
    return (bufp > 0) ? buf[--bufp] : getchar();
}void ungetch(int c){ /* 寫回字符 */
    if (bufp >= BUFSIZE)
        printf("ungetch: too many characters/n");    else
        buf[bufp++] = c;
}
  • 程序中經(jīng)常會出現(xiàn)這樣的情況: 程序不能確定它已經(jīng)讀入的輸入是否已經(jīng)足夠, 除非超前多讀入一些輸入. 讀入一些字符以合成一個數(shù)字的情況便是一例(上述): 在看到第一個非數(shù)字字符之前, 已經(jīng)讀入的數(shù)的完整性是不能確定的. 由于程序要超前讀入一個字符, 這樣就導(dǎo)致最后有一個字符不屬于當(dāng)前所要讀入的數(shù)

  • 上述, ungetch函數(shù)將要壓回的字符放到一個共享緩沖區(qū)中, 當(dāng)該緩沖區(qū)不空時, getch函數(shù)從緩沖區(qū)中讀取字符; 當(dāng)緩沖區(qū)為空時, getch函數(shù)調(diào)用getchar函數(shù)直接從輸入讀取字符

  • 如果要在外部變量的定義之前使用該變量, 或者外部變量的定義與變量的使用不在同一個源文件中, 則必須在相應(yīng)的變量 聲明 中強制性地使用關(guān)鍵字 extern

  • 外部變量的 聲明 和 定義 嚴(yán)格區(qū)分是重要的: 變量聲明 用于說明變量的屬性(主要是類型), 變量定義 除此之外還將引起存儲器的分配.

/* 定義外部變量, 并為止分配存儲單元, 并可以作為該源文件其余部分的聲明 */int sp;double val[MAXVAL];/* 為源文件的其余部分聲明了外部變量, 但這2個聲明并沒有建立變量或為它們分配存儲單元 */extern int sp;extern double val[];
  • 在一個源程序的所有源文件中, 一個外部變量只能在某個文件中定義1次, 而其他文件可以通過extern聲明來訪問它 
    (定義外部變量的源文件中也可以包含對該外部變量的extern聲明)

  • 外部變量的初始化只能出現(xiàn)在其定義中

  • 盡可能將共享部分集中在一起, 這樣就只需一個副本, 改進(jìn)程序時也容易保證程序的正確性

  • 一方面期望每個文件只能訪問它完成任務(wù)所需的信息; 另一方面是現(xiàn)實中維護較多的頭文件比較困難. 折中的做法是: 對于某些中等規(guī)模的程序, 最好只用一個頭文件存放程序中各部分共享的對象; 較大的程序需要使用更多的頭文件, 需要精心組織

  • 用 static 聲明限定外部變量和函數(shù), 將其后聲明的對象的作用域限定為被編譯源文件的剩余部分(作用域限定在本源文件內(nèi))

  • static類型的內(nèi)部變量是某種特定函數(shù)的局部變量, 只能在該函數(shù)中使用, 但不管其所在的函數(shù)是否被調(diào)用, 它一直存在. 換言之, static類型的內(nèi)部變量是一種只能在某個特定函數(shù)中使用但一直占據(jù)存儲空間(一直存在)的變量

  • register 聲明告訴編譯器, 它所聲明的變量在程序中使用頻率較高. 其思想是, 將register變量放在機器的寄存器中, 是程序更小, 執(zhí)行速度更快

  • register聲明只適用于局部變量以及函數(shù)的形式參數(shù)

  • 每個函數(shù)中只有很少的變量可以保存在寄存器中, 且只允許某些類型的變量.

  • 過量的寄存器聲明并沒有害處, 因為編譯器可以忽略過量的或不支持的寄存器變量聲明. 無論寄存器變量實際上是否存放在寄存器中, 它的地址都不能訪問

  • c語言不允許在函數(shù)中定義函數(shù), 但在函數(shù)中可以通過 程序塊 結(jié)構(gòu)的形式定義變量. 一對花括號包裹的, 即為一個復(fù)合語句程序塊, 在程序塊內(nèi)聲明的變量可以隱藏程序塊之外的同名變量, 它們之間沒有任何聯(lián)系

  • 每次進(jìn)入程序塊, 在程序塊中聲明以及初始化的自動變量都將被初始化, 但靜態(tài)變量(static關(guān)鍵字修飾的)只在第一次進(jìn)入程序塊時被初始化一次

  • 在一個好的程序設(shè)計風(fēng)格中, 應(yīng)該避免出現(xiàn)變量名隱藏外部作用域中相同名字的情況, 否則, 很可能引起混亂和錯誤

  • 在不顯式初始化的情況下, 外部變量和靜態(tài)變量都將被初始化為0; 局部變量和寄存器變量的初值沒有定義, 為無效值

  • 對于外部變量與靜態(tài)變量, 初始化表達(dá)式必須是常數(shù)表達(dá)式, 且只初始化一次; 對于局部變量和寄存器變量, 在每次進(jìn)入函數(shù)或程序塊時都將被初始化

  • 局部變量的初始化等效于簡寫的賦值語句. 但考慮到變量聲明中的初始化表達(dá)式容易被忽略, 且距離使用的位置較遠(yuǎn), 一般使用顯式的賦值語句

  • 數(shù)組的初始化可以在聲明的后面緊跟一個初始化 表達(dá)式列表 , 初始化表達(dá)式列表用花括號括起來, 各初始化表達(dá)式之間用逗號分隔

  • 當(dāng)省略數(shù)組長度時, 編譯器將把花括號中初始化表達(dá)式的個數(shù)作為數(shù)組的長度; 若初始化表達(dá)式的個數(shù)比數(shù)組元素少, 在對外部變量, 靜態(tài)變量和局部變量, 沒有初始化表達(dá)式的元素用0進(jìn)行初始化

  • 字符數(shù)組的初始化可用一個字符串代替花括號形式的初始化表達(dá)式序列. char a = "kissg"; 等價于 char a = {'k', 'i', 's', 's', 'g', '/0'}

  • 數(shù)字是以反序生成的: 低位數(shù)字先于高位數(shù)字生成, 但它們必須以與此相反的次序打印

  • 函數(shù)遞歸調(diào)用自身時, 每次調(diào)用的都會得到一個與之前的局部變量幾乎不同的新的局部變量集合

/* 快速排序(遞增)
   對一個給定的數(shù)組, 從中選擇一個元素, 以該元素為界將其余元素劃分為兩個子集,
   一個子集中的所有元素都小于該元素, 另一個子集中的所有元素都大于或等于該元素.*/void qsort(int v[], int left, int right){    int i, last;    void swap(int v[], int i, int j);    if (left >= right)  /* 數(shù)組包含的元素少于2個, 不執(zhí)行任何操作 */
        return;
    swap(v, left, (left + right)/2);  /* 將劃分子集的元素 */
    last = left;                      /* 移動到v[0] */
    for (i = left+1; i <= right; i++){/* 劃分子集 */
        if (v[i] < v[left])
            swap(v, ++last, i);
    }
    swap(v, left, last);              /* 恢復(fù)劃分子集的元素 */
    qsort(v, left, last-1);
    qsort(v, last+1, right);
}void swap(int v[], int i, int j){    int temp;

    temp = v[i];
    v[i] = v[j];
    v[j] = temp;
}
  • 遞歸并不節(jié)省存儲的開銷, 遞歸調(diào)用過程中必須在某個地方維護一個存儲處理值的棧; 遞歸的執(zhí)行速度也不快, 但遞歸代碼比較緊湊, 并且相比于非遞歸代碼更易編寫與理解

  • C預(yù)處理:

  • 文件包含:

  • #include "filename" 或 #include <filename> 行都將被替換為filename指定的文件內(nèi)容. 引號格式, 將在源文件所在位置查找目標(biāo)文件; 若在源文件所在位置沒有找到指定文件, 或使用<>格式, 將根據(jù)相應(yīng)的規(guī)則查找該文件. 被包含的文件本身也可以包含#include指令

  • 在大的程序中, #include指令是將所有聲明捆綁在一起的較好的方法, 它保證了所有的源文件都具有相同的定義與變量聲明, 可以避免出現(xiàn)一些不必要的錯誤

  • 宏替換

  • #define name subtext - 后續(xù)所有出現(xiàn)name記號的地方都被替換為subtext

  • 較長的宏定義可以分成若干行, 需要在行末加上反斜桿.

  • #define指令定義的name的作用域從其定義點開始, 到被編譯的源文件的末尾處結(jié)束

  • 替換只對記號進(jìn)行, 對括在引號中的字符串不起作用

  • subtext是任意的, #define forever for(;;) 定義了一個名為forever的無限循環(huán)

  • 宏定義可以使用參數(shù), 這樣可以對不同的宏調(diào)用使用不同的替換文本 
    #define max(A, B) ((A) > (B) ? (A) : (B)) - 使用時看起來像函數(shù)調(diào)用, 但其直接將替換文本插入到代碼中(跟宏匯編很像)

  • 通過 #undef 指令取消名字的宏定義, 這樣可以保證后續(xù)的調(diào)用是函數(shù)調(diào)用, 而不是宏調(diào)用

  • 形式參數(shù)不能用帶引號的字符串替換? 如果在替換文本中, 參數(shù)名以 # 作為前綴, 則結(jié)果將被擴展為由實際參數(shù)替換該參數(shù)的帶引號的字符串 
    #define dpirnt(expr) printf(#expr “= %g/n”, expr) 
    dprint(x/y) -> printf(“x/y” “= %g /n”, x/y)

  • 預(yù)處理器的 ## 運算符為宏擴展提供了一種連接實際參數(shù)的手段. 如果subtext中的參數(shù)與##相鄰, 則該參數(shù)將被實際參數(shù)替換, ##與前后的空白字符將被刪除, 并對替換后的結(jié)果重新掃描: 
    #define paste(front, back) front ## back 
    paste(name, 1) - 建立記號為name1 (我不懂)

  • 條件包含

    • 使用條件語句對預(yù)處理本身進(jìn)行控制, 條件語句的值在預(yù)處理執(zhí)行的過程中進(jìn)行計算. 為編譯過程中根據(jù)計算所得的條件值選擇性地包含不同代碼提供了一種手段

    • #if 語句對其中的常量整型表達(dá)式進(jìn)行求值, 若該表達(dá)式的值不等于0, 則包含其后的各行, 直到遇到 #endif , #elif 或 #else 語句

    • c語句專門定義了 #ifdef 和 ifndef 語句, 用于測試某個名字是否已經(jīng)定義

Chapter 5 指針與數(shù)組

  • ANSI C使用類型 void* 作為通用指針類型, 它可以存放指向任何類型的指針, 但不能間接引用自身

  • 指針是一種保存變量地址的變量, 使用指針通??梢陨筛咝У? 更緊湊的代碼

  • 一元運算符 & 可用于取一個對象的地址, 如 p = &c . &只能應(yīng)用于內(nèi)存中的對象, 即變量與數(shù)組元素, 不能作用于表達(dá)式, 常量或寄存器變量

  • 一元運算法 * 是間接尋址或間接引用運算符. 當(dāng)它作用于指針時, 將訪問指針?biāo)赶虻牡膶ο?/p>

  • int *ip; - ip是指向int類型的指針. 該聲明語句表明 表達(dá)式*ip 的結(jié)果是int類型 
    ip 與 *ip 的區(qū)別: ip 是指針, 是一個變量, 存放的是內(nèi)存地址; *ip 是一個表達(dá)式, 其結(jié)果是存放在指針指向地址的值 
    通俗地講: 在指針ip所指向的內(nèi)存地址, 給我一個int類型的變量

  • 指針只能指向某種特定類型的對象, 即每個指針都必須指向某種特定的數(shù)據(jù)類型

  • c語言以傳值的方式將參數(shù)值傳遞給被調(diào)用函數(shù), 因此, 被調(diào)用函數(shù)不能直接修改主調(diào)函數(shù)中變量的值; 若要實現(xiàn)對變量的修改, 可通過傳地址的方式實現(xiàn), 通過指針間接訪問指向它們指向的操作數(shù)

  • 指針參數(shù)使得被調(diào)函數(shù)能夠訪問和修改主調(diào)函數(shù)中對象的值, 就好像給一個房間的地址, 讓被調(diào)函數(shù)自己去房間里做修改, 改成什么樣就是什么樣

  • scanf函數(shù)的大致實現(xiàn)原理: 將標(biāo)識是否到達(dá)文件結(jié)尾的狀態(tài)作為函數(shù)的返回值, 同時使用一個指針參數(shù)實現(xiàn)修改, 并傳回給主調(diào)函數(shù) 
    (利用指針作修改, 利用函數(shù)返回來返回狀態(tài)量)

  • 通過數(shù)組下標(biāo)所能完成的任何操作都可以通過指針來實現(xiàn). 一般來說, 用指針編寫的程序比用數(shù)組下標(biāo)編寫的程序執(zhí)行速度更快, 但理解起來會稍微困難一點

  • 無論數(shù)組中的元素是何種類型或數(shù)組長度是多少, “指針+1”的操作都成立, 它意味著, pa+1指向pa所指向的對象的下一個對象. 對pa+i, 類似. 
    在計算pa+i時, i將根據(jù)pa指向的對象的長度按比例縮放, 而pa指向的對象的長度取決于pa的聲明

  • 數(shù)組名所代表的就是該數(shù)組第一個元素的地址.

  • 在計算數(shù)值元素a[i]的值時, c語言實際上先將其轉(zhuǎn)換為 *(a+i) 的形式, 再進(jìn)行求值. 在程序中, 這兩種形式是等價的. 同理, &a[i]與a+i的含義也是相同的.換言之, 一個通過數(shù)組和下標(biāo)實現(xiàn)的表達(dá)式可等價地通過指針和偏移量實現(xiàn)

  • 數(shù)組名與指針仍有不同, 指針是一個變量, 但數(shù)組名不是. 因此, pa=a, pa++是合法的, a=pa, a++是非法的(a為數(shù)組名, pa是指針)

  • 當(dāng)把數(shù)組名傳遞給一個函數(shù)時, 實際上傳遞的是該數(shù)組第一個元素的地址, 在被調(diào)用函數(shù)中, 該參數(shù)是一個局部變量, 因此, 數(shù)組名參數(shù)必須是指針, 即一個存儲地址值的變量

  • 在函數(shù)定義中, 形參 char s[] 和 char *s 是等價的, 但通常更習(xí)慣于使用后者, 它比前者更直觀地表明了該參數(shù)是一個指針

  • 如果確信相應(yīng)的元素存在, 可通過下標(biāo)訪問數(shù)組第一個元素之前的元素, 但引用數(shù)組邊界之外的對象是非法的

  • c語言中的地址算術(shù)運算方法是一致且有規(guī)律的, 將指針, 數(shù)組和地址的算術(shù)運算集成在一起是c語言的一大特點

  • c語言保證0永遠(yuǎn)不是有效的數(shù)據(jù)地址

  • 指針和整數(shù)之間不能相互轉(zhuǎn)換, 但0是唯一的例外: 常量0可以賦值給指針, 指針也可以和常量0進(jìn)行比較. 程序中經(jīng)常用符號常量 NULL 代替常量0, 這樣便于更清晰地說明常量0是指針的一個特殊值

  • 在 某些 情況下, 對指針可以進(jìn)行比較運算. 例如, 如果指針p和q指向同一個數(shù)組的成員, 那么它們之間就可以進(jìn)行類似于==, !=, <的關(guān)系比較運算. 任何指針與0進(jìn)行相等或不等的比較運算都是有意義的. 指向不同數(shù)組的元素的指針之間的算術(shù)或比較運算沒有定義, 通常是沒有意義的

  • 指針的減法運算也是有意義的 : 如果p和q指向相同數(shù)組中的元素, 且p<q, 那么q-p+1就是位于p和q指向的元素之間的元素數(shù)目(長度)

  • 指針的算術(shù)運算具有一致性: 如果處理的數(shù)據(jù)類型是比字符類型占據(jù)更多存儲空間的浮點類型, 并且p是一個指向浮點類型的指針, 那么在執(zhí)行p++之后, p指向下一個浮點數(shù)的地址. 所有指針運算都會自動考慮它所指向的對象的長度

  • 有效的指針運算包括:

  • 相同類型指針之間的賦值運算

  • 指針同整數(shù)之間的加法或減法運算

  • 指向相同數(shù)組中元素的兩個指針間的減法或比較運算

  • 將指針賦值為0或指針與0之間的比較運算

  • 字符串常量 是一個字符數(shù)組, “I am kissg”在字符串內(nèi)部表示中, 字符串?dāng)?shù)組以空字符’/0’結(jié)尾, 程序可以通過檢查空字符找到字符串?dāng)?shù)組的結(jié)尾. 字符串常量占據(jù)的存儲單元數(shù)也因此比雙引號內(nèi)的字符數(shù)大1

  • 字符串常量作為函數(shù)參數(shù), 實際上是通過字符指針訪問該字符串的. 換言之, 字符串常量可通過一個指向其第一個元素的指針訪問

  • c語言沒有提供將整個字符串作為一個整體進(jìn)行處理的操作符

char amessage[] = "I am kissg";char *pmessage = "I am kissg";/*           _      ______________
  pmessage: | | --> |I am kissg/0|
            ______________
  amessage: |I am kissg/0|
*/
  • 上述聲明中, amessage是一個僅僅足以存放初始化字符串以及空字符’/0’的一維數(shù)組, 數(shù)組中的單個字符可以進(jìn)行修改, 但amessage始終指向同一個存儲位置. pmessage是一個指針, 其初始值指向一個字符串常量, 之后它可以被修改為以指向其他地址, 但如果試圖修改字符串的內(nèi)容, 結(jié)果無定義.

/* 代碼進(jìn)化史 */void strcpy(char *s, char *t){    int i = 0;    while ((s[i] = t[i]) != '/0')
        i++;
}void strcpy(char *s, char *t){    while ((*s = *t) != '/0')
        t++;
        s++;
}void strcpy(char *s, char *t){    while ((*s++ = *t++) != '/0')
        ;
}void strcpy(char *s, char *t){    while (*s++ = *t++)
        ;
}
  • 進(jìn)棧與出棧的標(biāo)準(zhǔn)寫法:

  • *p++ = val;

  • val = *--p;

  • char *lineptr[MAXLINES] - 表示lineptr是一個具有MAXLINES個元素的一維數(shù)組, 其中數(shù)組的每個元素都是一個指向字符類型對象的指針. 即, lineptr[i]是一個字符指針, *lineptr[i]是該指針指向的第i個文本行的首字符

  • 在c語言中, 二維數(shù)組實際上是一種特殊的一維數(shù)組, 它的每個元素也是一個一維數(shù)組

  • 如果將二維數(shù)組作為參數(shù)傳遞給函數(shù), 那么在函數(shù)的參數(shù)聲明中, 必須指明數(shù)組的列數(shù) . 數(shù)組的行數(shù)沒有太大的關(guān)系, 因為函數(shù)調(diào)用時傳遞的是指針, 它指向由行向量構(gòu)成的一維數(shù)組

/* 二維數(shù)組 vs. 指針數(shù)組 */int a[10][20];int *b[10];
  • 從語法角度講, a[3][4]和b[3][4]都是對一個int對象的合法引用. 但a是一個真正的二維數(shù)組, 它分配了200個int類型長度的存儲空間, 并且通過常規(guī)的矩陣下標(biāo)計算公式20*row+col計算得到a[row][col]的位置; 但對b來說, 該定義僅僅分配了10個指針, 并且沒有初始化, 它們的初始化必須以顯式的方式進(jìn)行, 比如靜態(tài)初始化或通過代碼初始化, 假定b的每個元素指向一個具有20個元素的數(shù)組, 那么編譯器就要為它分配200個int類型長度的存儲空間以及10個指針的存儲空間

  • 指針數(shù)組的一個重要優(yōu)點在于, 數(shù)組的每一行長度可以不同 . 指針數(shù)組最頻繁的用戶是存放具有不同長度的字符串, 如 char *name[] = {"kissg", "Engine", "Treasure"};

  • 在支持c語言的環(huán)境中, 可以在程序開始執(zhí)行時將命令行參數(shù)傳遞給程序. 調(diào)用主函數(shù)main時, 它帶有2個參數(shù), 第一個參數(shù), 通常稱為argc, 用于參數(shù)計數(shù), 第二個參數(shù), 稱為argv, 用于參數(shù)向量, 是一個指向字符串?dāng)?shù)組的指針, 其中每個字符串對應(yīng)一個參數(shù)

  • 按照c語言的約定, argv[0]的值是啟動該程序的程序名, 因此argc至少為1. ANSI標(biāo)準(zhǔn)要求argv[argc]的值必須為一個空指針.

  • 標(biāo)準(zhǔn)庫函數(shù) strstr(s, t) 返回一個指針, 該指針指向字符串t在字符串s中第一次出現(xiàn)的位置; 如字符串t沒有在s中出現(xiàn), 函數(shù)返回NULL

  • Unix系統(tǒng)中的C語言程序有一個公共的約定: 以負(fù)號開頭的參數(shù)表示可選標(biāo)志或參數(shù). 可選參數(shù)應(yīng)該允許以任意次序出現(xiàn), 同時程序的其余部分應(yīng)該與命令行中參數(shù)的數(shù)目無關(guān). 此外, 如果可選參數(shù)能夠組合使用, 將為使用者帶來更大的方便

  • 在c語言中, 函數(shù)本身不是指針 , 但可以定義指向函數(shù)的指針. 這種類型的指針可以被賦值, 存放在數(shù)組中, 傳遞給函數(shù)以及作為函數(shù)的返回值等等

  • 一個例子: void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *));

  • 任何類型的指針都可以轉(zhuǎn)換為void *類型, 并且在將它轉(zhuǎn)換回原來的類型時不會丟失

  • int (*comp)(void *, void*) - 表明comp是一個指向函數(shù)的指針, *代表一個函數(shù), 該函數(shù)具有2個void *類型的參數(shù), 其返回值類型是int

  • int /*comp(void *, void *) - 表明comp是一個函數(shù), 該函數(shù)返回一個指向int類型的指針

/*
char **argv
    argv: pointer to pointer to char, 指針的指針int (*daytab)[13]
    daytab: pointer to array[13] of int, 數(shù)組指針int *daytab[13]
    daytab: array[13] of pointer to int, 指針數(shù)組
void *comp()
    comp: function returning pointer to void, 返回值為void *型的函數(shù)
void (*comp)()
    comp: pointer to function returning void, 函數(shù)指針
char (*(*x())[])()    x: function returning pointer to array[] of pointer to function returning char, 這...
char (*(*x[3])())[5]    x: array[3] of pointer to function returning pointer to array[5] of char, zhe ...*/
  • 如上所述, c語言的聲明不能從左至右閱讀, 而且使用了太多的圓括號. 前綴運算符*優(yōu)先級低于(), 因此, 加不加(), 相差巨大.

Chapter 6 結(jié)構(gòu)

  • 結(jié)構(gòu)是一個或多個變量的集合, 這些變量可能為不同的類型, 為了處理的方便而將這些變量組織在一個名字下. 由于結(jié)構(gòu)將一組相關(guān)的變量看作一個單元而不是各自獨立的實體, 結(jié)構(gòu)有助于組織復(fù)雜的數(shù)據(jù)

  • 結(jié)構(gòu)可以拷貝, 賦值, 傳遞給函數(shù), 作為函數(shù)的返回值

  • 關(guān)鍵字 struct 引入結(jié)構(gòu)聲明, 結(jié)構(gòu)聲明由包含在花括號內(nèi)的一系列聲明組成. 關(guān)鍵字 struct 后的名字是 可選的 , 稱為 結(jié)構(gòu)標(biāo)記 , 用于為結(jié)構(gòu)命名, 在定義之后, 結(jié)構(gòu)標(biāo)記就代表花括號內(nèi)的聲明

  • struct聲明定義了一種數(shù)據(jù)結(jié)構(gòu), 在標(biāo)志結(jié)構(gòu)成員表結(jié)束的右花括號之后可以跟一個變量表, 這與其他基本類型的變量聲明相同

  • 如果結(jié)構(gòu)聲明之后不帶變量表, 則不需要為其分配存儲空間, 它僅僅描述了一個結(jié)構(gòu)的模板或輪廓.

  • 通過點標(biāo)記法(a.b)來引用某個特定結(jié)構(gòu)的成員

  • 結(jié)構(gòu)可以嵌套

  • 結(jié)構(gòu)的合法操作只有:

  • 作為一個整體復(fù)制與賦值, 包括向函數(shù)傳遞參數(shù)以及從函數(shù)返回

  • 通過&運算符取地址

  • 訪問其成員

  • 結(jié)構(gòu)之間不能進(jìn)行比較

  • 傳遞結(jié)構(gòu)的方法:

    1. 分別傳遞結(jié)構(gòu)成員

    2. 傳遞整個結(jié)構(gòu)

    3. 傳遞指向結(jié)構(gòu)的指針…

  • 參數(shù)名和結(jié)構(gòu)成員同名不會引起沖突. 事實上, 使用重名可以強調(diào)兩者之間的關(guān)系

  • 如果傳遞給函數(shù)的結(jié)構(gòu)很大, 使用指針方式的效率通常比復(fù)制整個結(jié)構(gòu)的效率高

  • 結(jié)構(gòu)成員運算符 . 的優(yōu)先級比 * 的優(yōu)先級高, 因此*pp.x等價*(pp.x)

  • 在所有運算符中, 結(jié)構(gòu)運算符 . 和 -> , 用于函數(shù)調(diào)用的 () 以及用于下標(biāo)的 [] 優(yōu)先級最高, 它們同操作數(shù)之間的結(jié)構(gòu)也最緊密

  • ++p->len 將增加len的值, 而不是p的值, 因為->運算的優(yōu)先級更高

  • 一元運算符 `sizeof, 可用于計算任意對象的長度, 用整型值表示對象或類型占用的存儲空間字節(jié)數(shù), 其中對象可以是變量, 數(shù)組或結(jié)構(gòu); 類型可以是基本類型(int, double等), 也可以是結(jié)構(gòu)類型或指針類型

#define NKEYS (sizeof keytab / sizeof(struct key))#define NKEYS (sizeof keytab / sizeof keytab[0])
  • 以上, 使用第二種方法, 即使類型改變了, 也不需要改動程序

  • 條件編譯語句#if不能使用sizeof, 因為預(yù)處理器不對類型名進(jìn)行分析

  • 但預(yù)處理器并不計算#define語句中的表達(dá)式, 因此在#define中使用sizeof是合法的

  • 兩個指針之間的加法運算是非法的, 但指針的減法運算是合法的, 因此:

mid = (low + high) / 2mid = low + (high - low() / 2
  • c語言的定義保證數(shù)組末尾之后的第一個元素的指針運算是可以正常執(zhí)行的(即&tab[n])

  • 結(jié)構(gòu)的長度不等于各成員長度之和, 因為不同的對象有不同的 對齊 要求, 故, 結(jié)構(gòu)中可能會出現(xiàn)未命名的空穴

  • 一個包含其自身實例的結(jié)構(gòu)是非法的, 但是聲明 struct tnode /*left; 是合法的, 它將left聲明為指向tnode的指針, 而不是tnode實例本身

  • 散列查找 - 將輸入的名字轉(zhuǎn)換為一個小的非負(fù)整數(shù), 該整數(shù)隨后將作為一個指針數(shù)組的下標(biāo). 數(shù)組的每個元素指向某個鏈表的表頭, 鏈表中的各個塊用于描述具有該散列值的名字. 若沒有名字散列到該值, 則數(shù)組元素的值為NULL

  • c語言提供了一個稱為 typedef 的功能, 用來建立新的數(shù)據(jù)類型名.

  • 從任何意義上講, typedef聲明沒有創(chuàng)建一個新的類型, 只是為某個已存在的類型增加一個新的名稱而已. typedef聲明也沒有增加任何新的語義

  • typedef int (/*PEI)(char /*, char /*) 該語句定義了類型PEI是”一個指向函數(shù)的指針, 該函數(shù)具有兩個char *類型的參數(shù), 返回值類型為int”

  • 除了表達(dá)方式更簡潔外名, 使用typedef還有另外兩個重要原因. 首先, 它可以使程序參數(shù)化, 以提高程序的可移植性, 如果typedef聲明的數(shù)據(jù)類型與機器有關(guān), 那么, 當(dāng)程序移植到其他機器上時, 只需改變typedef類型定義即可. 一個經(jīng)常用到的情況是, 對于各種不同大小的整型值來說, 都使用typedef定義的類型名, 然后分別為各個不同的宿主機選擇一組合適的short, int和long類型大小. 其次, typedef為程序提供更好的說明性

  • 聯(lián)合(union) 是可以在(不同時刻)保存不同類型和長度的對象的變量, 編譯器負(fù)責(zé)跟蹤對象的長度和對齊要求. 聯(lián)合提供了一種方法, 以在單塊存儲區(qū)中管理不同類型的數(shù)據(jù), 而不需要在程序中嵌入任何同機器相關(guān)的信息

  • 聯(lián)合的目的是: 一個變量可以合法地保存多種數(shù)據(jù)類型中任何一種類型的對象

union u_tag{    int ival;    float fval;    char *sval;
} u;
  • 變量u必須足夠大, 以保存這3種類型中最大的一種. 這些類型中的任何一種類型的對象都可賦給u, 且可使用在隨后的表達(dá)式中, 但必須保證是一致的: 讀取的類型必須是最近一次存入的類型

  • 聯(lián)合可以用在結(jié)構(gòu)和數(shù)組中, 反之亦然. 訪問結(jié)構(gòu)中的聯(lián)合(或反之)的某一成員的表達(dá)法和嵌套結(jié)構(gòu)相同

  • 實際上, 聯(lián)合就是一個結(jié)構(gòu), 它的所有成員相對于基地址的偏移量都是0, 此結(jié)構(gòu)空間要大到足夠容納最”寬”的成員, 且, 對齊方式要適合于聯(lián)合中的所有類型的成員. 對聯(lián)合允許的操作與對結(jié)構(gòu)允許的操作相同: 作為一個整體單元進(jìn)行賦值與復(fù)制, 取地址及訪問其中一個成員

  • 聯(lián)合只能用其第一個成員類型的值進(jìn)行初始化

  • 位字段 是”字”中相鄰位的集合. “字”是單個的存儲單元:

struct {    unsigned int is_keyword : 1;    unsigned int is_extern : 1;    unsigned int is_static : 1;
}
  • 冒號后的數(shù)字表示字段的寬度(用二進(jìn)制位數(shù)表示), 單個字段的引用方式與其他結(jié)構(gòu)成員相同

  • 字段可以不命名, 無名字段(只有一個冒號和寬度)起填充作用, 特殊寬度0可以用來強制在下一個字邊界上對齊

  • 為了移植方便, 需要顯式聲明類型是signed類型還是unsigned類型.

  • 字段不是數(shù)組, 并且沒有地址, 因此不能對其使用&運算符

Chapter 7 輸入與輸出

  • ANSI標(biāo)準(zhǔn)精確地定義了庫函數(shù), 所以, 在任何可以使用c語言的系統(tǒng)中都有庫函數(shù)的兼容形式. 如果程序的系統(tǒng)交互部分僅僅使用了標(biāo)準(zhǔn)庫提供的功能, 則可以不經(jīng)修改地從一個系統(tǒng)移植到另一個系統(tǒng)中

  • 文本流由一系列行組成, 每一行的結(jié)尾是一個換行符(/n)

  • 在許多環(huán)境中, 用符號 < 來實現(xiàn) 輸入重定向 , 它將鍵盤輸入替換為文件輸入

  • 當(dāng)文件名用 <> 括起來時, 預(yù)處理器將在由具體實現(xiàn)定義的有關(guān)位置中查找指定的文件

  • getchar 函數(shù)從標(biāo)準(zhǔn)輸入中一次讀取一個字符

  • putchar(c) 函數(shù)將字符c送至標(biāo)準(zhǔn)輸出, 在默認(rèn)情況下, 標(biāo)準(zhǔn)輸出為屏幕顯示

  • 頭文件 

    中的getchar和putchar以及  中的tolower一般都是宏, 這樣就避免了對每個字符進(jìn)行函數(shù)調(diào)用的開銷
  • 輸出函數(shù)printf將內(nèi)部數(shù)值轉(zhuǎn)換為字符的形式, 在輸出格式的控制下, 將其參數(shù)進(jìn)行轉(zhuǎn)換和格式化, 并在標(biāo)準(zhǔn)輸出設(shè)備上打印出來, 返回打印的字?jǐn)?shù)

  • 格式字符串包含2種類型的對象: 普通字符和轉(zhuǎn)換說明. 在輸出時, 普通字符將原樣復(fù)制到輸出流中, 而轉(zhuǎn)換說明并不直接輸出到輸出流中, 而是用于控制printf中參數(shù)的轉(zhuǎn)換和打印. 每個轉(zhuǎn)換說明都由一個百分號字符開始, 以一個轉(zhuǎn)換字符結(jié)束

  • 在轉(zhuǎn)換說明中, 寬度和精度可以用星號表示, 這時, 寬度和精度的值通過轉(zhuǎn)換下一個參數(shù)來計算

  • sprintf的執(zhí)行和轉(zhuǎn)換和printf相同, 但它將輸出保存到一個字符串中, 而不是輸出到標(biāo)準(zhǔn)輸出中

  • scanf 從標(biāo)準(zhǔn)輸入中讀取字符序列, 按照format中的格式說明對字符序列進(jìn)行解釋, 并將結(jié)果保存到其余的參數(shù)中, 其它所有參數(shù)都必須是指針, 用于指定經(jīng)格式轉(zhuǎn)換后的相應(yīng)輸入保存的位置

  • sscanf 從一個字符串中讀取字符序列

  • scanf與sscanf的所有參數(shù)必須是指針

  • 轉(zhuǎn)換說明控制下一個輸入字段的轉(zhuǎn)換. 一般而言, 轉(zhuǎn)換結(jié)果存放在相應(yīng)的參數(shù)指向的變量中. 但若轉(zhuǎn)換說明中有賦值禁止字符*, 則跳過該輸入字段, 不進(jìn)行賦值. 輸入字段定義為一個不包含空白符的字符串, 其邊界定義為到下一個空白符或達(dá)到指定的字段寬度

  • 字符字面值也可以出現(xiàn)在scanf的格式串中, 它們必須與輸入中相同的字符匹配, 如要讀入形如mm/dd/yy的數(shù)據(jù), 可用 scanf("%d/%d/%d", &month, &day, &year)

  • scanf函數(shù)可以與其他輸入函數(shù)混合使用, 無論調(diào)用哪個輸入函數(shù), 下一個輸入函數(shù)的調(diào)用將從scanf沒有讀取的第一個字符處開始讀取數(shù)據(jù)

  • 標(biāo)準(zhǔn)輸入和輸出是操作系統(tǒng)自動提供給程序訪問的

  • 文件訪問: 在讀寫一個文件之前, 必須通過庫函數(shù) fopen 打開該文件, 該函數(shù)用外部文件名與操作系統(tǒng)進(jìn)行某些必要的連接和通信, 并返回一個可用于文件讀寫操作的指針, 稱為 文件指針 , 它指向一個包含文件的結(jié)構(gòu), 包括: 緩沖區(qū)的位置, 緩沖區(qū)中當(dāng)前字符的位置, 文件的讀寫狀態(tài), 是否出錯, 是否已經(jīng)到達(dá)文件結(jié)尾等等. 在 

    中, 用結(jié)構(gòu)`FILE`表示
  • int getc(FILE *fp) - 從文件中返回一個字符, 需要知道文件指針, 以確定對哪個文件執(zhí)行操作

  • int putc(int c, FILE *fp) - 將字符c寫入到fp指向的文件中. putc與getc是宏而不是函數(shù)

  • 啟動c語言程序時, 操作系統(tǒng)環(huán)境負(fù)責(zé)打開3個文件, 并將這3個文件的指針提供給該程序. 這3個文件分別是 標(biāo)準(zhǔn)輸入 , 標(biāo)準(zhǔn)輸出 , 標(biāo)準(zhǔn)錯誤 , 相應(yīng)的文件指針分別是stdin, stdout, stderr, 均在 

    中聲明. 在大多數(shù)環(huán)境中, stdin指向鍵盤, stdout和stderr指向顯示器. stdin和stdout可以被重定向到文件或管道(<, >, |)
  • fclose 執(zhí)行與fopen相反的操作, 斷開由fopen函數(shù)建立的文件指針和外部名之間的連接, 并釋放文件指針以供其他文件使用. 此外, fclose會將緩沖區(qū)中由putc正在收集的輸出寫到文件中, 當(dāng)程序正常終止時, 程序會自動為每個打開的文件調(diào)用fclose函數(shù)

  • 即使對標(biāo)準(zhǔn)輸出stdout進(jìn)行了重定向, 寫到stderr中的輸出通常也會顯示在屏幕上

  • 使用標(biāo)準(zhǔn)庫函數(shù) exit , 當(dāng)函數(shù)被調(diào)用時, 它將終止調(diào)用程序的運行. exit為每個已打開的輸出調(diào)用fclose函數(shù), 以將緩沖區(qū)的所有輸出寫到相應(yīng)的文件中

  • 在主程序main中, 語句 return expr 等價于 exit(expr) . 但使用exit有一個優(yōu)點, 它可以從其他函數(shù)中調(diào)用

  • char *fgets(char *line, int maxline, FILE *fp) - 函數(shù)從fp指向的文件中讀取下一個輸入行(包括換行符), 將其存放在字符數(shù)組line中, 最多可讀取maxline - 1個字符. 讀取的行將以 /0 結(jié)尾保存到數(shù)組中

  • int fputs(char *line, FILE *fp) - 將一個字符串(不需要包含換符)寫入到文件中

  • 庫函數(shù)gets和puts的功能與fgets和fputs類似, 但它們對stdin和stdout進(jìn)行操作. gets函數(shù)在讀取字符串時將刪除結(jié)尾的換行符, 而puts函數(shù)在寫入字符串時將在結(jié)尾添加一個換行符

  • int ungetc(int c, FILE *fp) - 函數(shù)將字符c寫回到文件中, 并返回c. 每個文件只能接收一個寫回字符, ungetc可以與任何一個輸入函數(shù)一起使用

  • system(char *s) 執(zhí)行包含在字符串s中的命令

  • 函數(shù)malloc和calloc用于動態(tài)地分配存儲塊. 用 free(p) 函數(shù)釋放p指向的存儲空間, p是此前通過malloc或calloc函數(shù)得到的指針. 存儲空間的釋放順序沒有限制, 但是, 如果釋放一個不是通過調(diào)用malloc或calloc函數(shù)得到的指針?biāo)赶虻拇鎯臻g, 將是一個嚴(yán)重錯誤 .

  • 使用已經(jīng)釋放的存儲空間是錯誤的, 正確的處理方法好似, 在釋放項目之前先將一切必要的信息保存起來:

for (p = head; p != NULL; p = p->next)
    free(p);                           /* 錯誤 */for (p = head; p != NULL; p = q){    q = p->next;
    free(p);
}
  • 函數(shù)rand()生成介于0和RAND_MAX之間的偽隨機整數(shù)序列

  • #define frand() ((double) rand() / (RAND_MAX + 1.0)) - 生成[0, 1)的隨機浮點數(shù)

  • 函數(shù)srand(unsigned)`設(shè)置rand函數(shù)的種子數(shù)

Chapter 8 UNIX系統(tǒng)接口

  • unix系統(tǒng)通過一系列 系統(tǒng)調(diào)用 提供服務(wù), 這些系統(tǒng)調(diào)用實際上是操作系統(tǒng)內(nèi)的函數(shù), 可被用戶程序調(diào)用. 借助系統(tǒng)調(diào)用, 可獲得最高的效率, 或者訪問標(biāo)準(zhǔn)庫沒有的某些功能

  • 在unix系統(tǒng)中, 所有外圍設(shè)備都被視為文件系統(tǒng)中的文件, 因此, 所有的輸入輸出都要通過讀文件或?qū)懳募瓿? 即通過一個單一接口就可以處理外圍設(shè)備與程序之間的所有通信

  • 通常情況下, 在讀寫文件之前, 必須先將這個意圖通知操作系統(tǒng), 該過層稱為 打開文件 . 如果一切正常, OS將返回一個小的非負(fù)整數(shù), 稱為 文件描述符 , 任何時候?qū)ξ募腎/O都是通過文件描述符標(biāo)識文件, 而不是通過文件名標(biāo)識文件. 系統(tǒng)負(fù)責(zé)維護已打開的文件的所有信息, 用戶程序只能通過文件描述符引用文件

  • 用shell運行一個程序時, 將打開3個文件, 對應(yīng)的文件描述符為0, 1, 2, 依次表示stdin, stdout, stderr. 如果程序從文件0中讀, 對1和2進(jìn)行寫, 就可以進(jìn)行I/O而不必關(guān)心打開文件的問題

  • 通過 <或>重定向程序的I/O時, shell將文件描述符的0和1的默認(rèn)賦值改變?yōu)橹付ǖ奈募? 在任何情況下, 文件賦值的改變都不是由程序完成的, 而是由shell完成的. 只要程序使用文件0作為輸入, 1和2作為輸出, 就不需要知道輸入從何而來, 又輸出到哪去

  • I/O通過 read 和 write 系統(tǒng)調(diào)用實現(xiàn). 在c語言程序中, 通過函數(shù)read和write訪問這2個系統(tǒng)調(diào)用. 在一次調(diào)用中, 讀出或?qū)懭氲臄?shù)據(jù)的字節(jié)數(shù)可以為任意大小, 最常用的值為1, 或是類似于4096這樣的與外圍設(shè)備的物理塊大小相應(yīng)的值

  • 除了默認(rèn)的標(biāo)準(zhǔn)輸入, 標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤文件外, 其他文件都必須在讀寫之前顯式地打開. 系統(tǒng)調(diào)用open和creat實現(xiàn)該功能

  • open與fopen相似, 但前者返回一個文件描述符, 僅僅是一個int類型的數(shù)值, 后者返回一個文件指針

  • 在unix文件系統(tǒng)中, 每個文件對應(yīng)一個9比特的權(quán)限信息, 分別控制文件的所有者, 所有者組和其他成員對文件的讀, 寫和執(zhí)行訪問.

  • 一個程序同時打開的文件數(shù)是有限制的(通常為20). 相應(yīng)的, 如果一個程序需要同時處理多個文件, 必須重用文件描述符. 函數(shù)close(int fd)用來斷開文件描述符與已打開文件之間的連接, 并釋放此文件的文件描述符, 以供其他文件使用. close函數(shù)與標(biāo)準(zhǔn)庫中的fclose相對應(yīng), 但不需要清洗(flush)緩沖區(qū). 若程序通過exit函數(shù)退出或從主程序中返回, 所有打開的程序?qū)⒈魂P(guān)閉

  • 函數(shù)unlink(char *name)將文件name從文件系統(tǒng)中刪除, 它對應(yīng)的標(biāo)準(zhǔn)庫函數(shù)為remove

  • 系統(tǒng)調(diào)用iseek可以在文件中任意移動位置而不實際讀寫任何數(shù)據(jù). 
    long lseek(int fd, long offset, int origin) - 將文件描述符為fd的文件的當(dāng)前位置設(shè)置為offset, offset是相對于origin指定的位置而言的. 隨后進(jìn)行的讀寫操作將從此位置開始. origin的值可以為0, 1, 2, 分別用于指定offset從文件開始, 從當(dāng)前位置或從文件結(jié)束處開始

  • 使用lssek系統(tǒng)調(diào)用, 可以將文件視為一個大的數(shù)組, 其代價是訪問速度會慢一些. lseek返回的long類型的值表示文件的新位置

  • 標(biāo)準(zhǔn)庫中的文件不是通過文件描述符描述的, 而是使用文件指針描述的. 文件指針是一個指向包含文件各種信息的結(jié)構(gòu)的指針, 該結(jié)構(gòu)包括: 一個指向緩沖區(qū)的指針, 通過它可以一次讀入文件中的一大塊內(nèi)容; 一個記錄緩沖區(qū)中剩余的字符數(shù)的計數(shù)器; 一個指向緩沖區(qū)下一個字符的指針; 文件描述符; 描述讀寫模式的標(biāo)識; 描述錯誤狀態(tài)的標(biāo)識等

  • 描述文件的數(shù)據(jù)結(jié)構(gòu)包含在頭文件 

    中, 任何需要使用標(biāo)識輸入/輸出庫中函數(shù)的程序都必須在源文件中包含這個頭文件
  • 長語句可用反斜桿分成多行表示

  • 通常還需要對文件系統(tǒng)執(zhí)行另一種操作, 以獲得文件的有關(guān)信息, 而不是讀取文件的具體內(nèi)容. 目錄列表程序便是一個例子(ls)

  • 在unix中, 目錄就是文件, 它包含一個文件名列表和一些指示文件位置的信息. “位置”是一個指向其他表的索引. 文件的inode是存放除文件名意外的所有文件信息的地方. 目錄項通常僅包含兩個條目: 文件名和inode

  • 為使程序的可移植性更像, 有時候需要分離不可移植部分分別處理

  • 許多程序并不是”系統(tǒng)程序”, 它們僅僅使用由OS維護的信息. 對于這樣的程序, 很重要的一點是, 信息的表示僅出現(xiàn)在標(biāo)準(zhǔn)頭文件中, 使用它們的程序只需要在文件中包含這些頭文件即可, 而不需要包含相應(yīng)的聲明. 其次, 有可能為系統(tǒng)相關(guān)的對象創(chuàng)建一個與系統(tǒng)無關(guān)的接口, 標(biāo)準(zhǔn)庫中的函數(shù)就是一個很好的例子

  • 通過一種與系統(tǒng)無關(guān)的方式編寫與系統(tǒng)有關(guān)的代碼

  • malloc并不是從一個編譯時就確定的固定大小的數(shù)組中分配存儲空間, 而是在需要時向OS申請空間. malloc管理的空間不一定是連續(xù)的, 空閑存儲空間以空閑塊鏈表的方式組織, 每個塊包含一個長度, 一個指向下一塊的指針, 一個指向自身的存儲空間的指針. 這些塊按照存儲地址升序組織, 最后一塊指向第一塊

  • 當(dāng)有申請時, malloc將掃描空閑塊鏈表, 直到找到一個足夠大的塊為止, 稱為”首次適應(yīng)(first fit)”; 尋找滿足條件的最小塊, 稱為”最佳適應(yīng)(best fit)”

  • 釋放的過程也是首先搜索空閑塊鏈表, 以找到可以插入或被釋放的合適位置. 空閑塊鏈表以地址的遞增順序鏈接在一起, 很容易判斷相鄰的塊是否空閑

  • 空閑塊包含一個指向鏈表中下一個塊的指針, 一個塊大小的記錄, 一個指向空閑空間本身的指針. 位于塊開始處的控制信息稱為”頭部”, 為了簡化塊的對齊, 所有塊的大小必須以頭部大小的整數(shù)倍, 且頭部已正確對齊

  • 在malloc函數(shù)中, 請求的長度(以字符為單位)將被舍入, 以保證它是頭部大小的整數(shù)倍, 實際分配的塊將多包含一個單元, 用于頭部本身. 實際分配的塊的大小將被記錄在頭部的size字段中, malloc函數(shù)返回的指針指向空閑空間, 而非塊的頭部, 用戶可對獲得的存儲空間進(jìn)行任何操作. 但, 如果在分配的存儲空間之外寫入數(shù)據(jù), 可能會破壞塊鏈表

  • 向系統(tǒng)請求存儲空間是一個開銷很大的操作

  • 類型的強制轉(zhuǎn)換使得指針的轉(zhuǎn)換是顯式進(jìn)行的, 這樣甚至可以處理設(shè)計不夠好的系統(tǒng)接口問題


米么信息 米么信息 米么信息
分享文章至