1. 程式人生 > >C Primer Plus(三)

C Primer Plus(三)

重讀C Primer Plus ,查漏補缺

  重讀C Primer Plus,記錄遺漏的、未掌握的、不清楚的知識點

 

檔案輸入/輸出

  1、fgets函式在讀取檔案內容時會將換行符讀入,但gets不會,fputs函式在寫入檔案時不會追加一個換行符,但puts會,應該對應配合使用。

  2、不同作業系統下,以文字方式開啟檔案,幾乎沒有區別,但由於不同作業系統檔案結尾的的識別符號不同,以二進位制方式開啟時,可能會將結尾識別符號錯誤輸出。

  3、對於大型檔案,有兩個特殊的函式提供支援:

1 int fgetpos(FILE * restrict stream, fpos_t * restrict pos);
2 int fsetpos(FILE * stream, const fpos_t *pos);

  其中,fpos_t是通過其他型別定義的檔案定位型別,在使用上述函式時,fsetpos中的pos必須是通過fgetpos函式獲得的。當兩個函式執行成功時,會返回0。

  4、其他標準IO函式

 1 size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE* restrict fp);
 2 size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE* restrict fp);
 3 // 是否到達檔案結尾
 4 int feof(FILE* fp);
 5 // 是否發生讀寫錯誤
 6 int ferror(FILE* fp);
 7 // 將字元迴流進緩衝區
 8 int ungetc(int c, FILE* fp)
 9 // 立刻將緩衝區內容寫入檔案
10 int fflush(FILE* fp)
11 // 替換緩衝區
12 int setvbuf(FILE* restrict fp, char * restrict buf, int mode, size_t size)

  當然,上述的一些函式在目前的VS Studio中會被認為是不安全的函式,已經過時。

  

結構和其他資料格式

   5、C99標準下支援對結構體初始化時的任意欄位賦值:

1 struct book gift = {.value=25.99, .author="Harry Potter", .title="Yoo"};
2 // 此時 0.25 會被賦給定義結構體時author後的那個成員,即便那個成員已經被初始化過。
3 struct book gift = {.value=25.99, .author="Harry Potter", 0.25};

  6、對於結構體陣列,陣列名不是其首個元素的地址,需要引用首個元素再取地址。

  7、在結構中一般使用字元陣列,而不使用字元指標,結構中的字元指標無法很好的初始化地址,這樣會有使用上的風險,所以結構中的字元指標最好只指向那些字串常量或者是指向由malloc分配的記憶體。

  8、C99標準對結構也支援複合文字,同時複合文字的結構也可以作為函式引數,也可以取地址,也和普通變數有相同的生存週期,宣告方式如下:

1 (struct book) {"The Idiot", "Fyodor Dostoyevsky", 6.99}

  9、C99支援一種伸縮型陣列成員,這個成員必須是結構中最後一個成員,而且不是唯一一個成員,就像宣告普通陣列一樣,但括號內為空,這個成員不會在聲明後立即存在,實際上,C99希望使用malloc為這樣含有伸縮型成員的陣列分配空間。

1 struct flex{
2     int count;
3     double avreage;
4     double scores[]; // 伸縮型成員
5 }
6 struct flex * pf;
7 pf = malloc(sizeof(struct flex) + 5*sizeof(double))
8 pf->count = 5;
9 pf->scores[2] = 2.99;

  10、對於C中的列舉型別,某些屬性不能順延至C++,例如C允許對列舉做++運算,但C++不允許。

1 enum spectrum {red, yellow, green, blue};
2 spectrum color;
3 for(color = red; color != blue; color++);

  11、在C中,對於同一作用域下的標記和變數名可以使用同一個名字,因為對於標記(列舉、結構,聯合),他們使用的名字空間與普通變數不同,但C++中不可以,例:

1 struct complex{double x, double y};
2 int complex; // 在C中不會引起衝突,但C++中則不允許

  12、對於函式指標執行函式時,會出現兩種語法,ANSI C把他們視為等價的。

1 void ToUpper(char *);
2 void (*pf) (char*);
3 char str[] = "hello";
4 pf = ToUpper;
5 (*pf)(str); // 語法1
6 pf(str);    // 語法2

  

位操作

  13、為什麼一個位元組可以表示的有符號整數的範圍是-128~+127?

  看這裡:https://www.cnblogs.com/Dylan7/p/12649972.html

  14、計算機中小數是如何表示的?(一部分表示指數,一部分表示小數,有精確度問題)

  15、對位進行操作的第二種方法就是位欄位(從沒用過,細節可以用到時再研究),位欄位好比一個結構體,但其中的成員,代表的是某幾位上的值,好處是避免了通過複雜的位運算去控制某些位上的值,宣告例如:

1 struct box
2 {
3     unsigned int opaque       :1 // 整體結構的對齊補齊依據無符號整型
4     unsigned int fill_color   :3 // 數字代表需要幾位來表示這個欄位
5     unsigned int              :4 // 可以跳過一些位
6     unsigned int show_border  :1 // 但一個欄位不能橫跨兩個無符號整型的邊界
7 }
8 struct box b;
9 b.fill_color = 7; // 不可以超過欄位所佔用的位可表示的上限

  

C前處理器和C庫

  16、程式翻譯的第一步,在預處理前,編譯器會對程式碼做一些翻譯,將程式碼中出現的字元對映到源字符集(用來處理多位元組字元和使C外觀更加國際化的三元字元擴充套件),接著查詢反斜槓後緊跟換行符的例項,將其轉換為一行,然後將文字劃分為預處理的語言符號序列以及空白字元及註釋序列(將用一個空格代替一個註釋),最後進入預處理階段,尋找每一個預處理指令。

  17、 幾個巨集定義

1 #define F(x) #x      // #將語言符號字串化
2 #define F(x) F##x    // ##將兩個語言符號組成一個語言符號
3 #define F(x,...)  printf("x", __VA_ARGS__)  // ...和__VA_ARGS__,可變引數(必須為最後一個引數)

  18、#if 指令後面跟常量整數表示式,可以與 #elif 配合使用,例如:

1 #if 1 == SYS
2     ...
3 #elif 2 == SYS
4     ...
5 #endif    

  同時,還有以下新的實現方式,defined 是一個預處理運算子,如果引數使用#define定義過,defined返回1,否則返回0。

1 #if defined(INMPC)
2     ...
3 #elif defined(VAX)
4     ...
5 #endif   

  19、#line 用於重置__LINE__,__FILE__巨集所報告的行數

    #error 指令使前處理器可以發出一條錯誤資訊

1 #line 10000
2 #line 10 cool.c"
3 #if __STD_VERSION__ != 199901L
4     #error Not C99
5 #endif

  20、C99 提供了_Pragma前處理器運算子,可以將字串轉換成常規的編譯指示

1 _Pragma("c99 on") 等價於
2 #pragma c99 on

  21、行內函數不會在偵錯程式中顯示,例如使用gdb除錯時,有些行內函數無法被手動執行,同時行內函數具有內部連結屬性,所以在多檔案程式中,使用其他檔案的行內函數時,要單獨宣告一次,並且在嘗試獲 取行內函數的地址時,編譯器都會產生非行內函數,也就是說可能產生外部定義的函式。

  23、在main()函式結束時,會隱式地呼叫exit()函式,同時,可以通過atexit()函式,向exit()註冊在程式允許結束時執行的函式,ANSI C保證可以設定至少32個函式,按照先設定後執行的順序執行,atexit()函式接受一個返回值為void,引數也為void的函式指標作為唯一引數。

  24、memcpy()與memmove()兩個函式的區別在於宣告上,以及memcpy()會假定兩個記憶體區沒有重疊。

1 void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
2 void *memmove(void *s1, void *s2, size_t n);

  25、可變引數的相關內容包含在stdarg.h標頭檔案中,使用起來比較複雜,包括初始化可變引數列表,遍歷列表,清理列表,拷貝列表等一系列操作,需要時再研究。

 

高階資料表示

  26、 這章沒什麼新奇內容,但它告訴我們,用C可以實現很多複雜的資料結構。

 

 

  2020年4月16日,星期五,晚23點09分,首次完整讀完這本書,共勉。

  學如逆水行舟,不進則退;心似平原放馬,易縱難收。

&n