天天看點

C和指針讀書筆記——部分簡介C和指針讀書筆記——部分簡介

C和指針讀書筆記——部分簡介

标簽(空格分隔): 讀書筆記

1 C語言的重要性

1.C語言效率高。

2.C語言具有可移植性。

1.1 重排字元

1.1.1 空白和注釋

注釋以符号/開始,以符号**/結束。注釋不能被嵌套。但是可以用:

#if 0
    statements
#endif
           

在#if和#endif之間的程式就可以有效地從程式中去除,不論之間有沒有其他注釋。

1.1.2 預處理指令

就是.h檔案,在main主函數中聲明

1.1.3 main函數

int main(void)
{
}
           

主要講了printf的用法。

1.1.4 read_column_number 函數

主要講scanf用法。

1.2 補充

1.3 編譯

1.4 總結

1.5 警告的總結

  • 在scanf函數的标量參數前未添加&字元。
  • 機械地把printf函數的格式代碼照搬于scanf函數。
  • 在應該使用&&操作符的地方誤使用了&操作符。
  • 誤用=操作符而不是==操作符來測試相等性。

1.6 程式設計提示的總結

  • 使用#inculde指令避免重複聲明。
  • 使用#define指令給常量值取名。
  • 在#include檔案中放置函數原型。
  • 在while或if表達式中蘊含複制操作。
  • 如何編寫一個空循環體。
  • 始終要進行堅持,確定數組不越界。

2 基本概念

2.1 環境

2.1.1 翻譯

2.1.2 執行

  • 程式必須載入到記憶體中。
  • 程式開始執行。
  • 開始執行程式代碼。
  • [x] 在絕大數機器裡,程式将使用一個運作時堆棧(stack),它用來存儲函數的局部變量和傳回位址。程式同時也可以使用靜态(static)記憶體,存儲于靜态記憶體中的變量在程式的整個執行過程中将一直保留他們的值。
  • 程式的終止。

2.2 詞法規則

2.2.1 字元

  • [x] 注意使用轉義序列
  • \? 在書寫連續多個問号時使用,防止他們被解釋為三個字母詞。
  • \” 用于表示一個字元串常量内部的雙引号。
  • \’ 用于表示字元常量’。
  • \\ 用于表示一個反斜杠,防止它被解釋為一個轉義序列符。
  • \a 警告字元。
  • \b 倒退鍵。
  • \f 進紙字元。
  • \n 換行符。
  • \r 回車符。
  • \t 水準制表符。
  • \v 垂直制表符。

2.2.2 注釋

2.2.3 自由形式的源代碼

  • 相鄰的标記之間必須出現一個至多個空白字元(或注釋),不然他們可能被解釋為單個标記。

3.3.4 辨別符

  • 辨別符就是變量、函數、類型等的名字,他們由大小寫字母、數字和下劃線組成,不能以數字開頭。
  • 辨別符不能使用關鍵字。

2.2.5 程式的形式

2.3 程式風格

讓人更加友善的讀寫代碼

3 資料

3.1 基本資料類型

  • 在C語言中,僅有4種基本資料類型——整型、浮點型、指針和聚合類型(如數組和結構等)。

3.1.1 整型家族

  • 包括字元、短整型、整型和長整型,都分為有符号(singed)和無符号(unsigned)兩種版本。
  • 長整型至少應該和整型一樣長,而整型至少應該和短整型一樣長。

一、整型字面值

二、枚舉類型

3.1.2 浮點類型

  • 浮點數家族包括float、double和long double類型。
  • 規定:long double至少和double一樣長,而double至少和float一樣長。
  • 所有浮點類型至少能夠容納從 10−37 到 1037 。

3.1.3 指針

一、指針常量

二、字元串常量

3.2 基本聲明

形式:

說明符(一個或多個) 聲明表達式清單

例如:

int a;
cha b;
           

3.2.1 初始化

例如:

3.2.2 聲明簡單數組

例如:

int values[];
           

3.2.3 聲明指針

例如:

int *a;
           

3.2.4 隐式聲明

3.3 typedef

  • 使用typedef比#define來建立新的類型名要好,因為後者無法處理指針類型。

3.4 常量

3.5 作用域

  • 編譯器可以确認4中不同類型的作用域——文本作用域、代碼作用域、函數作用域和原型作用域。
  • 辨別符聲明的位置決定它的作用域。

3.5.1 代碼作用域

  • 位于一對花括号之間的所有語句稱為一個代碼塊。任何在代碼塊的開始位置聲明的辨別符都具有代碼塊作用域。
  • 函數形參的作用域開始于形參的聲明處,位于函數體之外。

3.5.2 檔案作用域

  • 任何在所有代碼塊之外聲明的辨別符都具有檔案作用域,它表示這些辨別符從他們聲明之處直到它所在的源檔案結尾處都是可以通路的。

3.5.3 原型作用域

  • 原型作用域隻适用于在函數原型中聲明的參數名。

3.5.4 函數作用域

  • 一個函數中所有語句标簽必須唯一。

3.6 連結屬性

  • 連結屬性一共有三中——external(外部)、internal(内部)和none(無)。

3.7 存儲類型

  • 凡是在任何代碼塊之外聲明的變量總是存儲于靜态記憶體中,也就是不屬于堆棧的記憶體,這類變量稱為靜态(static)記憶體。
  • 在代碼塊内部聲明的變量的預設存儲類型是自動的(automatic),也就是說它存儲于堆棧中,稱為自動(auto)變量。
  • 函數的形式參數不能聲明為靜态,因為實參總是在堆棧中傳遞給函數,用于支援遞歸。

3.8 static關鍵字

3.9 作用域、存儲類型示例

3.12 程式設計提示的總結

  • 為了保持最佳的可移植性,把字元的值限制在有符号字元和無符号字元範圍的交集之内,或者不要在字元上緻謝算數運算。
  • 用他們在使用時最自然的形式來表示字面值。
  • 不要把整型值和枚舉值混在一起使用。
  • 不要依賴隐式聲明。
  • 在定義類型的新名字時,使用typedef而不是#define。
  • 用const聲明其值不會被修改的變量。
  • 使用名字常量而不是字面值常量。
  • 不要在嵌套的代碼塊之間使用相同的變量名。

4 語句

4.1 空語句

4.2 表達式語句

4.3 代碼塊

4.4 if語句

文法:

if(expression)
    statement
else
    statement
           

4.5 while語句

文法:

while(expression)
    statement
           

4.5.1 break和continue語句

  • break用于永久終止循環。
  • continue用于永久種植目前的那次循環。

4.5.3 while語句的執行過程

4.6 for語句

文法:

for(expression1;expression2;expression3)
    statement
           

4.7 do語句

文法:

do
    statement
while(expression);
           
  • 當你需要循環體至少執行一次時,選擇do。

4.8 switch語句

文法:

switch(expression)
    statement
           

4.8.1 switch中的break語句

  • 如果在switch語句執行中遇到了break語句,執行流就會立即調到語句清單的末尾。

4.8.2 default子句

  • 當switch表達式的值并不比對所有case标簽的值時,這個default子句後面的語句就會執行。是以,每個switch語句中隻能出現一條default子句。

4.8.3 switch語句的執行過程

4.9 goto語句

文法:

goto 語句标簽;
           
  • 一般使用goto來結束深層循環。

    如:

while(condition1){
    while(condition2){
        while(condition3){
            if(some disaster){
                goto quit;
            }
        }
    }
}
quit:;
           

5 操作符和表達式

5.1 操作符

5.1.1 算數操作符

5.1.2 移位操作符

5.1.3 位操作符

5.1.4 指派

指派操作符:=
複合指派符:+= -= *= /= %= <<= >>= &= ^= |=
           

5.1.5 單目操作符

5.1.6 關系操作符

5.1.7 邏輯運算符

5.1.8 條件操作符

5.1.9 逗号操作符

expression1,expression2,...,expressionN
           
  • 逗号操作符将兩個或多個表達式分割開來。這些表達式自左向右逐個進行求值,整個逗号表達式的值及時最後那個表達式的值。

5.1.10下标引用、函數調用和結構成員

5.2 布爾值

規則:零是假,任何非零值皆為真。

5.3 左值和右值

5.4 表達式求值

5.4.1 隐式類型轉換

5.4.2 算數轉換

  • 如果某個操作符的各個操作數屬于不同的類型,那麼除非其中一個操作數轉換為另一個操作數的類型,否則操作就無法進行。

5.4.3 操作符的屬性

5.4.4 優先級和求值的順序

6 指針

6.1 記憶體和位址

  • 記憶體中的每個位置由一個獨一無二的位址辨別。
  • 記憶體中的每個位置都包含一個值。

6.2 值和類型

  • 不能簡單地通過檢查一個值得位來判斷它的類型。為了判斷值的類型(以及它的值),你必須觀察程式中這個值的使用方式。

6.3 指針變量的内容

6.4 間接通路操作符

通過一個指針通路它所指向的位址的過程稱為間接通路(indirection)或解引用指針(dereferencing the pointer)。

6.5 末初始化和非法的指針

6.6 NULL指針

6.7 指針、間接通路和左值

  • 指針變量可以作為左值,并不是因為他們是指針,而是因為他們是變量。

6.8 指針、間接通路和變量

6.9 指針常量

6.10 指針的指針

6.11 指針表達式

6.13 指針的運算

  • 一個指針加1,它會根據類型進行調整,指向下一個類型。

6.13.1 算術運算

第一種形式

指針 ± 整數

  • 标準定義這種形式隻能用于指向數組中某個元素的指針。

第二種形式

指針 - 指針

  • 隻有當兩個指針都指向同一個數組中的元素時,才允許從一個指針減去另一個指針。

6.13.2 關系運算

  • 前提是他們都指向同一個數組中的元素。

7 函數

7.1 函數定義

函數的定義就死函數體的實作

文法:

傳回值類型 函數名(形式參數){
    代碼塊
}
           

7.2 函數聲明

7.2.1 原型

傳回值類型 函數名(形式參數);
           
  • 現在函數原型具有檔案作用域,是以原型的一份拷貝可以作用于整個源檔案,較之在該函數每次調用前單獨書寫一份函數原型藥容易得多。
  • 現在函數原型隻書寫一次,這樣就不會出現多份原型的拷貝之間的不比對現象。
  • 如果函數的定義進行了修改,我們隻需要修改原型,并重新編譯所有包含了該原型的源檔案即可。
  • 如果函數的原型同時也被#include指令包含到定義函數的檔案中,編譯器就可以确認函數原型與函數定義的比對。

7.2.2 函數的預設認定

  • 所有的函數都應該具有原型,尤其是那些傳回值不是整型的函數,記住,值的類型并不是值的内在本質,而是取決于它被使用的方式。

7.3 函數的參數

  • 傳遞給函數的标量參數是傳值調用的。
  • 傳遞給函數的數組參數在行為上就像是通過傳址調用的那樣。

7.4 ADT和黑盒

7.5 遞歸

遞歸函數就是直接或者間接調用自身的函數。

7.5.1 追蹤遞歸函數

7.5.2 遞歸與疊代

階乘

遞歸舉例:

long factorial(int n ){
    if(n <= ){
        return ;
    }else{
        return n * factorial(n-);
    }
}
           

疊代舉例:

long factorial(int n ){
    int result = ;
    while(n > ){
        result *= n;
        n -= ;
    }
    return result;
}
           

7.6 可變參數清單

7.6.1 stdarg宏

可變參數清單是通過宏來實作的,這些宏定義于stdarg.h頭檔案,它是标準庫的一部分。

7.6.2 可變參數的限制

  • 注意,可變參數必須從頭到尾按照順序逐個通路,如果你在通路了幾個可變參數後想半途終止,這是可以的。但是,如果你想一開始就通路參數清單中間的參數,那是不行的。

8 數組

8.1 一維數組

8.1.1 數組名

8.1.2 下标引用

8.1.3 指針與下标

8.1.4 指針的效率

指針有時比下标更有效率,前提是他們被正确的使用

  • 當你根據某個固定數目的增量在一個數組中移動時,使用指針變量将比使用下标産生更高的代碼。當這個增量是1并且機器具有位址自動增量模型時,這點表現得更為突出。
  • 聲明為寄存器變量的指針通常比位于靜态記憶體和堆棧中的指針效率更高(具體提高的幅度取決于你所使用的機器)。
  • 如果你可以通過測試一些已經初始化并經過調整的内容來判斷循環是否應該終止,那麼你就不需要使用一個單獨的計數器。
  • 那些必須在運作時求值的表達式較之諸如&array[SIZE]或array+SIZE這樣的常量表達式往往代價更高。

8.1.5 數組和指針

8.1.6 作為函數參數的數組名

8.1.7 聲明數組參數

int strlen(char *string);
int strlen(char string[]);
           

這兩個函數原型是相等的。

8.1.8 初始化

9.1.9 不完整的初始化

編譯器隻知道初始值不夠,但它無法知道缺少的是哪些值,是以,隻允許省略最後幾個初始值。

8.1.10 自動計算數組長度

上面自動計算出數組長度是3。

8.1.11 字元數組的初始化

char a[] = "Hello";
           

8.2 多元數組

int a[5][3];
           

8.2.1 存儲順序

8.2.2 數組名

8.2.3 下标

8.2.4 指向數組的指針

8.2.5 作為函數參數的多元數組

8.2.6 初始化

int a[2][3] = {{1,2,3},{4,5,6}};
           

8.3 指針數組

9 字元串、字元和位元組

9.1 字元串基礎

9.2 字元串長度

  • 注意strlen傳回一個類型為size_t的值,這個類型是在頭檔案stddef.h中定義的,它是一個無符号整型類型。

9.3 不受限制的字元串函數

9.3.1 複制字元串

用于複制字元串的函數是strcpy,它的原型如下所示:

char *strcpy(char *dst, char const *src);
           

這個函數把參數src字元串複制到dst參數。

  • 注意:程式員必須保證目标字元數組的空間足以容納需要複制的字元串。

9.3.2 連結字元串

要想把一個字元串添加(連結)到另一個字元串的後面,你就可以使用strcat函數,它的原型如下:

char *strcat(char *dst, char const *src);
           

9.3.3 函數的傳回值

9.3.4 字元串比較

庫函數strcmp用于比較兩個字元串,它的原型如下:

int strcmp(char const *s1, char const *a2);
           

9.4 長度受限的字元串函數

9.5 字元串查找基礎

9.5.1 查找一個字元

使用strchr和strrchr函數,他們的原型如下:

char *strchr(char const *str, int ch);
char *strrchr(char const *str, int ch);
           

9.5.2 查找任何幾個字元

用strpbrk函數,原型如下:

char *strpbrk(char const *str, char const *group);
           

9.5.3 查找一個子串

用函數strstr函數,原型如下:

char *strstr(char const *s1, char const *s2);
           

9.6 進階字元串查找

9.6.1 查找一個字元串字首

用函數strspn和strcspn,原型如下:

size_t strspn(char const *str, char const *group);
size_t strcspn(char const *str, char const *group);
           

9.6.2 查找标記

使用函數strtok,原型如下:

char *strtok(char *str, char const *sep);
           

9.7 錯誤資訊

9.8 字元操作

9.8.1 字元分類

9.8.2 字元轉換

toupper函數傳回其參數的對應大寫形式,tolower函數傳回其參數的對應小寫形式,原型如下:

int tolower(int ch);
int toupper(int ch);
           

9.9 記憶體操作

10 結構和聯合

10.1 結構基礎知識

10.1.1 結構聲明

在聲明結構時,必須列出它包含的所有成員。

struct tag {member-list}variable-list;
           

聲明結構時可以使用一種良好技巧,用typedef建立一種新的類型,例如:

typedef struct{
    int a;
    char b;
    float c;
}Simple;
           

之後就直接可以使用Simple進行定義變量。

10.1.2 結構成員

10.1.3 結構成員的直接通路

10.1.4 結構成員的間接通路

10.1.5 結構的自引用

10.1.6 不完整的聲明

10.1.7 結構的初始化

舉例:

struct INIT_EX{
    int a;
    short b[];
    simple c;
    }x = {
        ,
        {,,,,},
        {,'x',}
    };
           

10.2 結構、指針和成員

10.2.1 通路指針

10.2.2 通路結構

10.2.3 通路結構成員

10.3 結構的存儲配置設定

  • 隻有當存儲成員需要滿足正确的邊界對齊要求時,成員之間才可能出現用于填充的額外記憶體空間。

10.4 作為函數參數的結構

10.5 位段

10.6 聯合

聯合的所有成員引用的是記憶體中的相同位置。

10.6.1 變體記錄

10.6.2 聯合的初始化

如:

union{
    int a;
    float b;
    char c[];
} x = {};
           

把x.a初始化為5。

10.9 程式設計提示的總結

  • 把結構标簽聲明和結構的typedef聲明放在頭檔案中,當源檔案需要這些聲明時可以通過#include指令把他們包含進來。
  • 結構成員的最佳排列形式并不一定就是考慮邊界對齊而浪費記憶體空間最少的那種排列形式。
  • 把位段成員顯式地聲明為signed或unsigned int類型。
  • 位段是不可移植的。
  • 位段使源代碼中位的操作表達得更為清楚。

11 動态記憶體配置設定

11.1 為什麼使用動态記憶體配置設定

11.2 malloc和free

原型如下:

void *malloc(size_t size);
void free(void *pointer);
           

11.3 calloc和realloc

原型如下:

void *calloc(size_t num_elements, size_t element_size);
void realloc(void *ptr, size_t new_size);
           
  • calloc也用于配置設定記憶體。malloc和calloc之間的主要差別是後者在傳回指向記憶體的指針之前把它初始化為0。
  • realloc函數用于修改一個原先已經配置設定的記憶體塊的大小。

11.4 使用動态配置設定的記憶體

一般使用:

int *pi;
pi = malloc( * sizeof(int));
           

11.5 常見的動态記憶體錯誤

11.9 程式設計提示總結

  • 動态記憶體配置設定有助于消除程式内部存在的限制。
  • 使用sizeof計算資料類型的長度,提高程式的可移植性。

12 使用結構和指針

12.1 連結清單

  • 連結清單(linked list)就一些包含資料的獨立資料結構(通常稱為節點)的集合。

12.2 單連結清單

12.2.1 在單連結清單中插入

12.2.2 其他連結清單操作

12.3 雙連結清單

13 進階指針話題

13.1 進一步探讨指向指針的指針

13.2 進階聲明

13.3 函數指針

13.3.1 回調函數

13.3.2 轉移表

13.4 指令行參數

13.4.1 傳遞指令行參數

13.4.2 處理指令行參數

13.5 字元串常量

14 預處理器

14.1 預定義符号

14.2 #define

define name stuff
           

有了這條指令以後,每當有符号name出現在這條指令後面時,預處理器就會把它替換成stuff。

14.2.1 宏

14.2.2 #define替換

14.2.3 宏與函數

例如在兩個表達式中尋找其中較大(或較小)的一個:

14.2.4 帶副作用的宏參數

14.2.5 命名約定

14.2.6 #undef

這條預處理指令用于移除一個宏定義。

14.2.7 命名行定義

14.3 條件編譯

14.3.1 是否被定義

14.3.2 嵌套指令

14.4 檔案包含

14.4.1 函數庫檔案包含

14.4.2 本地檔案包含

14.4.3 嵌套檔案包含

14.7 警告的總結

  • 不要在一個紅定義的末尾加上分号,使其成為一條完整的語句。
  • 在宏定義中使用參數,但忘記在他們周圍加上括号。
  • 忘記在整個宏定義的兩倍加上括号。

15 輸入/輸出函數

16 标準庫函數

17 經典抽象資料類型

18 運作時環境

繼續閱讀