C語言的設計哲學之一: 程序員知道本身在幹什麼-沒有安全帶!程序員
值的類型並非值的內在本質, 而是取決於它被使用的方式數組
1.#include <stdio.h>在預處理器處理的時候把stdio.h中的源碼讀到當前文件中, 而後交給編譯器安全
2.gets()函數會把輸入的值的換行符轉化爲NULL字節, 來結束字符串; 遇到EOF或者發生錯誤返回NULL指針, 因此返回NULL要用ferror或feof檢查是發生錯誤仍是遇到EOF網絡
3.指針屬性: 地址值和指針類型. 1)地址值表示指針所標識變量的首地址; 2)指針類型告訴編譯器該怎樣進行接下來的訪問數據結構
4.標量就是指char、int、double和枚舉型等數值類型, 以及指針; 相對地, 像數組、結構體和共用體這樣的將多個標量進行組合的類型, 咱們稱之爲聚合類型(aggregate)(字符串是char類型的數組, 也就不是標量了); 而數組帶下標選擇的是單一元素, 若此元素爲前者數值類型則是標量, 不然爲聚合類型; 的一個注意區分與變量和常量間的區別(二者說的不是一個概念);使用八進制'\101'而不是數字0101來對char類型進行賦值能夠很明顯讓人知道是字符串, 並且使用'\101'能夠嵌入到字符串中dom
5.scanf("%d", &num); 執行時從標準輸入讀取, 前導空白將被跳過, printf("*%010.5d\n*", 100)-->* 00100*;第一位10表示總長度, 3表示最小數字位數(標誌位:"+-0 #"), printf("*%10.2s*\n", "ABC")->"* AB*";printf使用%f輸出double, 而scanf使用%lf輸入double類型, 是由於printf時類型提高(短變長), float會被提高爲double型, 而scanf向float和double類型中存儲大不同, 因此要用lf;scanf使用全部格式碼(除了%c以外)時, 輸入值以前的空白(空格、製表符、換行符等)會被跳過, 值後面的空白表示該值的結束, 所以, 用%s格式碼輸入字符串時, 中間不能包含空白函數
6.pus()函數會在字符串結尾添加一個換行符, 與gets()相反測試
7.C語言有四種數據類型-整型、浮點型、指針和聚合類型(數組和結構等), 全部其餘的類型都是從這四種基本類型的某種組合派生而來!無布爾類型和字符串類型this
8.整型包括字符、短整型、整型和長整型, 並且都分爲有符號和無符號兩種版本spa
9.break和continue只是打斷最內層的循環, 不會影響到外層循環
10.goto語句, 必須定義goto到的語句, 而且在以後加上冒號":", next_do:...., 能夠用goto跳出多層循環, goto next_do;
11.getchar()函數返回一個整型值, 一個緣由是EOF須要的位數比字符型值提供的要多, 若是長於字符型, 會被截取\377爲EOF
12.變量屬性做用域、連接屬性、存儲類型;整型常量是能最小字節存儲就最小字節存儲, 若加長存儲能夠用L等;字符常量類型老是int
13.sizeof(int)返回int類型所佔字節, sizeof(arr)返回數組arr所佔總字節; 而sizeof(a = b + 1)判斷表達式長度並不須要對a求值, 因此沒有對a進行任何賦值
14.不論++仍是--都是對變量的值的一份拷貝, 前綴在賦值以前增長變量的值, 後綴在複製以後增長變量的值, 操做符的結果不是被他們修改的變量, 而是變量值的拷貝, 認識這點很是重要; 如++a = 10;是錯誤的, ++優先級較高, 返回值, 值固然不能用於左操做數
15.逗號操做符, 將多個表達式分隔開來, 這些表達式自左向右逐個進行求值, 整個逗號表達式返回的值就是最後一個表達式的值
16.*p中, p表明內存中某個特定位置的地址, *操做符使機器指向那個位置; 做爲左值的時候這個表達式指定要修改的位置, 做爲右值的時候它就提取當前存儲於這個位置的值.
17.操做符的優先級與結合性; 優先級決定了兩個相鄰的操做符哪一個先執行, 能夠依次相鄰比較找出最早執行的(好比 1 + 2 + 3 * 4); 結合性就是一串操做符是從左到右依次執行仍是從右到左依次執行; 優先級和結合性都與操做符有關, 而與變量或者值無關
18.沒有標明存儲位置, 不能做爲左值
19.C根本不會對數組長度作檢查, 即便索引超過數組長度也不會報錯, 不論取值仍是賦值!int a[5]; a[6] = 10; printf("%d", a[6]); 但這種會出現未知結果!!由於下個內存地址誰知道被誰使用呢!
20.標準容許指向數組元素的指針與指向數組最後一個元素後面的那個內存位置的指針進行比較, 但不容許與指向數組第一個元素以前的那個內存位置的指針進行比較!
21.指針減去一個整數後, 運算結果產生的指針所指向的位置在數組第一個元素以前, 那麼它也是非法的!加法則稍有不一樣, 若是結果指針指向數組最後一個元素後面的那個內存位置還是合法(但不應和不能對這個指針執行間接訪問操做), 不過再日後就不合法了!
22.未被提早聲明的函數會被編譯器認爲參數正確, 而且返回值爲整數
23.向函數傳參時傳遞的都是拷貝的值, 也就是值傳遞, 可是傳遞數組的時候是傳遞的數組指針, 也就是引用地址傳遞.因此函數定義時的數組參數並不須要加長度, 只需標明是數組結構便可(加上[]).
24.使用遞歸的時候堆棧裏會產生不少函數變量, 由於堆棧的特性, 因此會覆蓋原函數變量, 等本函數執行完後pop出最上變量;
25.數組名是一個指針常量, 只有在兩種場合下數組不用指針常量表示-數組名用做sizeof的參數-&數組名[1]
26.指針與數組名間能夠隨意轉換與使用, 好比int arr[10]; int *p = arr + 2; 能夠直接使用p[2];C的下標和指針的間接引用是同樣的!當不明白的時候就作下轉換!好比此時p[-1]能夠轉換爲*(p - 1), 即p[1]; 甚至能夠寫2[p], 由於其至關於*(2 + p); 可是p[11]即便超出下標範圍也不會被檢查!
27.數組下標引用實際執行的就是間接訪問!!下標引用實際上只是間接訪問表達式的一種假裝形式!!
28.完全理解用指針和多維數組的關係
29.int matrix[2][5], *p = matrix;是錯誤的, 由於matrix是一個指向整數類型的指針而不是指向一個數組(a[5])的指針
30.int (*p)[10]指向數組的指針 p是指向包含10個整型元素的數組的指針
31.二維數組作參數必須指定列數 void fun(int (*mat)[10])或者void fun(int mat[][10])
32.在多維數組的初始值列表中, 只有第一維的長度會被自動計算出來
33.strlen("abcd") - strlen("abcde") >= 0;這條語句永遠爲真, 由於strlen返回的類型爲size_t, 即unsigned int類型, 兩個unsigned int類型相減根據類型自動轉換翻譯的依舊是unsigned int類型
34.聚合數據類型是指可以同時存儲超過一個的單獨數據, C提供了兩種類型的聚合數據類型, 數組和結構, 數組能夠經過下標訪問時由於數組元素的長度相同, 但結構的成員長度可能不一樣, 結構變量屬於標量類型;
35.結構不能包含類型也是這個結構的成員, 可是他的成員能夠是一個指向這個結構的指針
36.注意結構變量的邊界對齊, sizeof(結構變量)返回的值中包含告終構中浪費的內存空間
37.一個聯合的全部成員都存儲於同一個內存位置, 經過訪問不一樣類型的聯合成員, 內存中相同的位組合能夠被解釋爲不一樣的東西.
union { float f; int i; } fi; fi.f = 3.1415926; printf("%d\n", fi.i);
38.
typedef struct{ char *name; short sex; short age; } stu, *stup;
*stup最好的理解方式爲相似int *p中把int替換爲(struct{...}), 聲明一個指向struct類型的指針;
39.malloc、calloc、realloc和free維護一個可用內存池, 當一個程序另外須要一些內存就調用alloc系列函數從內存池中提取一塊連續的內存, 並返回一個指向這塊內存的指針, 若是內存池爲空則返回NULL, 因此必須對alloc系列函數返回的值進行檢查肯定非NULL, 返回的void * 類型的指針能夠被轉換爲任何類型的指針, 能夠製做動態大小的數組
void *malloc(size_t size);
void *calloc(size_t num_elements, sizet element_size);返回內存指針前把內存中的數值初始化爲0
void realloc(void *ptr, size_t new_size);修改已經分配的內存塊(ptr)的大小, 若是比原來大就將新加的內存添加到原來內存以後, 小則刪減後面部分, 若是原先內存不能改變則新建立一塊內存, 因此realloc以後就不能使用原來的指針, 應該使用realloc返回的指針
void free(void *pointer);
40.經典的使用malloc分配內存方法
alloc.h
#include <stdlib.h> #define malloc /*注意此處爲空 不能直接調用malloc*/ #define MALLOC(num, type) (type *)alloc((num) * sizeof(type)) extern void *alloc(size_t size);
接口
#include <stdio.h> #include "alloc.h" #undef malloc void *alloc(size_t size) { void *new_mem; new_mem = malloc(size); if (new_mem == NULL) { exit(1); } return new_mem; }
實現
#include "alloc.h" void function() { int *new_memory; new_memory = MALLOC(25, int); }
41.內存釋放一部分是不容許的, 好比想用free(p + 5)釋放不被容許;動態分配的內存必須整塊一塊兒釋放, 可是realloc函數能夠縮小一塊動態分配的內存, 有效地釋放它尾部的部份內存
42.define時左邊常量不容許出現空格 不然會被認爲是後個語句, 注意後者替換時括號的使用 宏定義語句中能夠包含運算符"#"(將一個宏的參數不要計算而是把變量名轉換爲字符串字面量, 如#define PRINTF_INT(x) printf(#x "=%d\n", x))或者"##"(鏈接符),#x 會被替換爲字符串 "x"(注意帶引號),能夠這樣使用 printf("name" "John")
好比
#define STR(x) #x int main(int argc, char** argv) { printf("%s\n", STR(It's a long string)); // 輸出 It's a long str return 0; }
#define PHP_FUNCTION ZEND_FUNCTION #define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name)) #define ZEND_FN(name) zif_##name #define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS) #define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, \ zval *this_ptr, int return_value_used TSRMLS_DC PHP_FUNCTION(count); // 預處理器處理之後, PHP_FUCNTION(count);就展開爲以下代碼 void zif_count(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC)
43.define函數與普通函數的區別爲, 宏定義能夠用於任何類型, 好比#define MAX(a,b) ((a) > (b) ? (a) : (b)); #define ECHO(s) (ges(s), puts(s))、#define ECHO(s) {gets(s); puts(s);}#define指令只能是一行 若是是換行則須要在行末把換行符轉義(即加"\"), 通常的都會用do while來代替if語句, 由於";"不能亂加, 不然在if不加花括號的狀況下很容易打亂if結構
#define ALLOC_ZVAL(z) \ do { \ (z) = (zval*)emalloc(sizeof(zval_gc_info)); \ GC_ZVAL_INIT(z); \ } while (0)
44.預處理命令後都不須要";", 經常使用預處理命令#include(能夠文件嵌套, 注意區分兩種搜索順序, '/usr/include路徑或當前路徑'), #define, #undef, #if, #elif, #else, #endif, #ifdef, #ifndef,(#ifdef, #ifndef都是以#endif結尾) #error(編譯程序(預處理階段), 只要遇到#error就會生成一個編譯錯誤提示消息(#error後跟的錯誤信息, 不用雙引號), 並中止編譯), #line(從新設定行號和文件名, 即修改__FILE__、__LINE__, 之後的__FILE__、__LINE__也是從當前所置值開始計算 #line 24 "a.txt", 在把其餘語言解析爲C語言時經常使用), #pragma
45.預處理器定義的符號__FILE__, __LINE__, __DATE__, __TIME__, __STDC__(編譯器遵循ANSIC爲1, 不然爲0)
46.perror(存在於stdio.h)將錯誤輸出到stderr;exit(存在於stdlib.h)返回錯誤碼給操做系統(Linux下用$?查看)
perror(char const *s)用來將上一個函數發生錯誤的緣由輸出到標準設備(stderr) 參數s所指的字符串會先打印出, 後面再加上錯誤緣由字符串, 此錯誤緣由依照全局變量errno的值來決定要輸出的字符串, 在庫函數中有個errno變量, 每一個errno值對應着以字符串表示的錯誤類型, 當你調用"某些"函數出錯時, 該函數已經從新設置了errno的值, perror函數只是將你輸入的一些信息和如今的errno所對應的錯誤一塊兒輸出。
47.計算機擁有大量不一樣設備, 不少都與I/O操做有關, CD-ROM驅動器, 軟盤和硬盤驅動器, 網絡鏈接, 通訊端口和視頻適配器等都是這類設備, 每種設備具備不一樣的特性和操做協議, 操做系統負責這些不一樣設備的通訊細節, 並向程序員提供一個更爲簡單和統一的I/O接口.ANSI C進一步對I/O的概念進行了抽象, 就C程序而言, 全部的I/O操做只是簡單的從程序移進或者移出字節的事情, 所以, 好不驚奇的是, 這種字節流被稱爲流(stream), 程序員只要關心建立正確的輸出字節數據, 以及正確的屆時從輸入讀取的字節數據, 特定I/O設備的細節對程序員是隱藏
48.絕大多數流失徹底緩衝的(fully buffered), 這意味着讀取和寫入是從一塊被稱爲緩衝區(buffer)的內存區域來回複製數據, 從內存中複製數據是很是快的, 用於輸出流的緩衝區只有當它寫滿時纔會刷新(flush, 物理寫入)到設備或者文件中, 一次性把寫滿的緩衝區寫入和逐片把程序產生的輸出分別寫入相比效率更高, 相似, 輸入緩衝區當它爲空經過從設備或文件讀取下一塊較大的輸入, 從新填充緩衝區; 使用標準輸入或輸出時, 這種緩衝可能會引發混淆, 因此只有操做系統判定他們交互的設備沒有關聯時纔會進行徹底緩衝.一個常見的策略就是把標準輸入和輸出聯繫到一塊兒《 就是當請求輸入時同時刷新輸出緩衝區, 這樣, 在用戶必須進行輸入以前, 提示用戶進行輸入的信息和之前寫入到輸出緩衝區中的內容將出如今屏幕上。
47.可使用fflush(stdout/stdin)迫使緩衝區刷新, 無論是否已滿;
原型: int fflush(FILE *stream); stdin刷新標準輸入緩衝區, 把輸入緩衝區裏的東西丟棄【非標準】, stdout刷新標準輸出緩衝區, 把輸出緩衝區裏的東西打印到標準輸出設備上, printf後面加上fflush(stdout)可提升打印效率
返回: 返回值爲0表示成功, 返回值爲EOF表示錯誤
48.FILE是一個數據結構, 用來訪問一個流, 每一個流都有一個FILE與它關聯, 爲了在流上執行操做, 能夠調用一些合適的函數, 並向它們傳遞一個與這個流相關聯的FILE參數, 流經過fopen函數打開(能夠打開文件或設備), 爲了打開一個流你必需要指定須要訪問的文件或設備, 以及其訪問方式, fopen會驗證文件或設備是否存在, 並初始化返回FILE *結構, 系統必須爲每一個ANSI C程序提供至少三個流(stdin, stdout, stderror), 他們都是一個指向FILE結構的指針(參考47, 48好好理解)
49.爲每一個文件活動文件聲明一個指針變量, 其類型爲FILE *, 這個指針指向這個FILE結構, 當它處於活動狀態時由流使用
50.IO函數以三種基本的形式處理數據, 單字符/字符串/二進制數據, 對於每一種數據都有一組特定的函數對它們進行處理 -- ungetc(int ch, stdin); 把字符壓回標準輸入, 下次讀的時候相似棧先進後出
ungetc('a', stdin); ungetc('b', stdin); printf("%c\n", getchar()); printf("%c\n", getchar());
結果
b
a
51.標準流I/O不須要打開或者關閉
52.不論以何種方式打開(rwa), 數據只能從文件的尾部寫入!!
53."a+"表示該文件打開用於更新, 而且流既容許讀也容許寫. 可是若是你已經從該文件讀了一些數據, 那麼向它寫入數據以前, 你必須調用一個文件定位函數(fseek, fsetpos, rewind), 在你向文件寫入一些數據以後, 若是你又想從該文件讀取一些數據, 你首先必須調用fflush或者文件定位函數
54.perror的使用方法
#include <stdio.h> int main(int argc, char **argv) { FILE *input = fopen("./a.txt", "r"); if (input == NULL) { perror("./b.txt"); exit(EXIT_FAILURE); } return 0; }
55.fclose(FILE *f)在關閉以前刷新緩衝區, 若是執行成功返回0, 不然返回EOF
56.輸出緩衝區數據顯示在屏幕上的條件
1.遇到\n
2.函數結束了
3.輸出緩衝區滿了
4.fflush(stdout)
測試:
printf("你好");
for(;;);
以上程序不會打印
57.編譯器每次只能處理一個文件, 因此就能夠理解爲何只需聲明而不用見到定義!
58.鏈接器把編譯器生成的目標文件當作一組外部對象組成的, 每一個外部對象表明着機器內存的某個部分, 並經過一個外部名稱來識別, 所以程序中的每一個函數和每一個外部變量若沒有被聲明爲static, 就都是一個外部對象!
59.鏈接器載入目標模塊和庫文件, 處理外部對象命名衝突, 生成載入模塊(可執行文件)
60.
int a;若是出如今全部函數體以外, 則被稱爲外部對象a的定義!這說明a是一個外部變量, 同時爲a分配空間
extern int a;這個語句仍然說明a是一個外部對象, 可是因爲有extern關鍵字, 就顯式的說明了a的存儲空間是在程序的其餘地方分配的, 從鏈接器的角度來看就是一個隊外部變量a的引用, 而不是對a的定義
//下面的函數在外部變量random_seed中保存了整形參數n的一份拷貝
int random_seed;
void srand(int n) {
extern int random_seed;
random_seed = n;
}
每一個外部對象都必須在程序某個地方定義!因此, 一個程序中若包括了語句 extern int a; 那麼這個程序就必須在別的地方包括語句 int a; 這兩個語句能夠在同一個源文件中也能夠位於不一樣源文件中!若是同一個外部變量的定義不止一次, 大部分系統會拒絕接受該程序!兩個具備相同名稱的外部對象實際上表明的是同一對象!
61.若是是用#include一個頭文件, 在編譯階段則至關於一個文件, 因此不能出現定義重複, 即便extern、staic, 能夠在鏈接的時候使用, 好比如下狀況
a.c
#include <stdio.h> extern int a; //說明a是定義在其餘程序中的 int main(void) { printf("%d\n", a); return 0; }
b.c
int a = 10; //此處不能加extern 由於這纔是最初始定義
執行 gcc a.c b.c 成功!
62.時刻記住include是在編譯階段, extern等鏈接屬性是在鏈接階段使用的, 仔細考慮下變量的鏈接屬性
63