------------------------------------------------------------------------------------
全局變量:
// main.c // Created by weichen on 15/7/14. // Copyright (c) 2015年 weichen. All rights reserved. #include <stdio.h> int gAll; // int g2 = gAll; 編譯不通過;如果是 const int gAll = 10;int g2 = gAll;是可以的,但是不推薦這麼寫 void f(int a); void t(void); int main(int argc, const char * argv[]) { /* 1. 全局變量 定義在函數外面的變量是全局變量 全局變量具有全局的生存期和作用域 它們與任何函數無關 在任何函數內部都可以使用它們 2. 全局變量初始化 沒有做初始化的全局變量會得到0值,編譯器會加上;本地變量是內存裡有什麼就得到什麼 指針沒有初始化會得到NULL值 只能用編譯時刻已知的值來初始化全局變量 它們的初始化發生在main函數之前 3. 靜態本地變量 在本地變量定義時加上static修飾符就成為靜態本地變量 當函數離開的時候,靜態本地變量會繼續存在並保持其值 靜態本地變量的初始化只會在第一次進入這個函數時做,以後進入函數時會保持上次離開時的值 靜態本地變量實際上是特殊的全局變量 它們位於相同的內存區域;&gAll和&all相差正好是一個int的大小,而實際的本地變量放在另外的地方 靜態變量具有全局的生存期,函數內的局部作用域 static在這裡的意思是局部作用域(本地可訪問) 4. 返回指針的函數 返回本地變量的地址是危險的;因為離開函數,本地變量就不存在了 返回全局變量或靜態本地變量的地址是安全的 返回在函數內malloc的內存是安全的,但是容易造成問題 最好的做法是返回傳入的指針 5. Tips 不要使用全局變量來在函數間傳遞參數和結果 盡量避免使用全局變量 豐田汽車的案子 使用全局變量和靜態本地變量的函數是線程不安全的 */ printf("in %s is %d\n", __func__, gAll); // in main is 0 f(gAll); printf("again in %s is %d\n", __func__, gAll); // again in main is 2 t(); // all is 6 t(); // all is 7 , 沒有被重新初始化為5,使用上次得到的變量值 t(); // all is 8 return 0; } // 全局變量 void f(int a) { printf("in %s is %d\n", __func__, a); // in f is 0 gAll += 2; printf("again in %s is %d\n", __func__, gAll); // again in f is 2 int gAll = 1; // 重新聲明一個與全局變量同名的本地變量,此時全局變量gAll被隱藏 printf("last in %s is %d\n", __func__, gAll); // last in f is 1 } // 靜態本地變量 void t(void) { static int all = 5; int k = 0; all += 1; printf("all is %d\n", all); printf("&gAll = %p\n", &gAll); // &gAll = 0x10000101c printf("&all = %p\n", &all); // &all = 0x100001018 printf("&k = %p\n", &k); // &k = 0x7fff5fbff80c } /* int* g(void) { int x = 10; return &x; // 返回本地變量的地址,編譯要麼不通過要麼提示warning } */
編譯預處理:
// main.c // Created by weichen on 15/7/15. // Copyright (c) 2015年 weichen. All rights reserved. #include <stdio.h> // const double PI = 3.14159; // C99可以使用的方式 #define PI 3.14159 // C99之前使用的方式,#define是編譯預處理指令 #define FORMAT "%f\n" #define PI2 2*PI // pi * 2 #define PRT printf("%f ", PI);\ printf("%f\n", PI2) #define cube(x) ( (x) * (x) * (x) ) #define RADTODEG1(x) (x * 57.3) #define RADTODEG2(x) (x) * 57.3 int main(int argc, const char * argv[]) { /* 編譯預處理指令 #開頭的是編譯預處理指令 它們不是C語言的成分,但是C語言程序離不開它們 #define用來定義一個宏 如何看到編譯這個過程(保存編譯過程中的臨時文件,加--save-temps選項): 終端下 gcc main.c -o 1.out --save-temps 【 main.c -> main.i(預處理後的文件) -> main.s(匯編代碼)-> main.o(目標代碼文件) 1.out(可執行文件) 】 tail main.i 看到後面幾行中PI被替換成值 宏 #define <名字> <值> 注意結尾不能加分號,因為不是C的語句 名字必須是一個單詞,值可以是各種東西 在C語言的編譯器開始編譯之前,編譯預處理程序(cpp)會把程序中的名字換成值 完全的文本替換 gcc --save-temps 如果一個宏的值中有其他的宏的名字,也是會被再次替換 如果一個宏的值超過一行,最後一行之前的行末需要加\ 宏的值後面出現的注釋不會被當做宏的值得一部分,C語言的注釋有效 沒有值的宏 #define _DEBUG 這類宏是用於條件編譯的,後面有其他的編譯預處理指令來檢查這個宏是否已經被定義過了(如果定義了執行這一部分代碼,沒有定義執行另一部分代碼) 預定義的宏 __LINE__ // 當前代碼行號 __FILE__ // 源代碼包含路徑的文件名 __DATE__ // 編譯時日期 __TIME__ // 編譯時時間 __STDC__ // 當要求程序嚴格遵循ANSIC標准時,標識符__STDC__就會被賦值為1 帶參數的宏 #define cube(x) ( (x) * (x) * (x) ) 宏可以帶參數 可以帶多個參數 #define MIN(a, b) ((a) > (b) ? (b) : (a)) 也可以組合(嵌套)使用其它宏 在大型程序的代碼中使用非常普遍(在代替函數時運行效率比函數高,但是代碼大小比函數大) 可以非常復雜,如“產生”函數 在#和##這兩個運算符的幫助下 存在中西方文化差異(國外使用宏的項目更多) 部分宏會被inline函數替代(加上inline就代表這個函數是聲明而不是定義,使用inline時,相當於把當前調用替換成函數裡的代碼,增加了代碼但是減少了函數調用的額外開銷,是以空間換時間的做法;什麼時候用inline:函數2-3行很小或在循環裡頻繁調用的函數,什麼時候不用inline:超過20行的函數或遞歸的函數) 錯誤定義的宏 #define RADTODEG1(x) (x * 57) #define RADTODEG2(x) (x) * 57 帶參數的宏的原則 一切都要括號 整個值要括號 參數出現的每個地方都要括號 #define RADTODEG(x) ((x) * 57.3) 其它編譯預處理指令 條件編譯 error ... */ printf("%f\n", 2*PI); // 6.283180 printf(FORMAT, PI2); // 6.283180 PRT; // 3.141590 6.283180 printf("%s:%d\n", __FILE__, __LINE__); // Users/weichen/.../main.c:66 #通常用於調試 printf("%s:%s\n", __DATE__, __TIME__); // Jul 15 2015:01:47:36 #區分程序版本 if (__STDC__ == 1) { printf("ANSIC\n"); } else { printf("Not ANSIC\n"); } int i = 2; printf("%d\n", cube(5)); // 125 printf("%d\n", cube(i)); // 8 printf("%d\n", cube(i+2)); // 64 printf("%f\n", RADTODEG1(5+2)); // 119.600000 printf("%f\n", 180/RADTODEG2(1)); // 10314.000000 #我們希望的是180/57.3, 而實際卻不是 return 0; }
大程序結構:
// main.c // Created by weichen on 15/7/15. // Copyright (c) 2015年 weichen. All rights reserved. #include <stdio.h> int main(int argc, const char * argv[]) { /* 大程序結構 main()裡地代碼太長了適合分成幾個函數 一個源代碼.c文件太長了適合分成幾個文件 兩個獨立的源代碼文件不能編譯形成可執行的程序 項目 在DevC++中新建一個項目,然後把幾個源代碼文件加入進去 對於項目,DevC++的編譯會把一個項目中所有的源代碼文件都編譯後,鏈接起來 有得IDE有分開的編譯和構建兩個按鈕,前者是對單個源代碼文件編譯,後者是對整個項目做鏈接 編譯單元 一個.c文件是一個編譯單元 編譯器每次編譯只處理一個編譯單元,編譯形成.o文件,由鏈接器鏈接它們 頭文件 如果main裡面調用的函數沒有進行聲明,C語言會將函數的所有參數和返回值默認當做int對待,這種情況下不能保證函數被正確使用 把函數原型放到一個頭文件(以.h結尾)中,在需要調用這個函數的源代碼(.c文件)中#include這個頭文件,就能讓編譯器在編譯的時候知道函數的原型 #include是一個編譯預處理指令,和宏一樣,在編譯之前就處理了 它把那個文件的全部文本內容原封不動的插入到它所在的地方 所以也不是一定要在.c文件的最前面#include #include有兩種形式來指出要插入的文件 " "要求編譯器首先在當前目錄(.c文件所在的目錄)尋找這個文件,如果沒有,到編譯器指定的目錄去找 < >讓編譯器只在指定的系統目錄去找(/usr/include) 編譯器自己知道自己的標准庫的頭文件在哪裡 環境變量和編譯器命令行參數也可以指定尋找頭文件的目錄 在使用和定義這個函數的地方都應該#include這個頭文件 一般的做法是除了main之外的任何.c都有對應的同名的.h, 把所有對外公開的函數的原型和全局變量的聲明都放進去 #include的誤區 #include不是用來引入庫的 stdio.h裡只有printf的原型,printf的代碼在另外的地方,某個.lib(windows)或.a(Unix)中 現在的C語言編譯器默認會引入所有的標准庫 #include <stdio.h>只是為了讓編譯器知道printf函數的原型,保證你調用時給出的參數值是正確地類型 不對外公開的函數 在函數前面加上static就使得它成為只能在所在的編譯單元中被使用的函數(函數不希望別人用,僅當前文件中能使用) 在全局變量前面加上static就使得它成為只能在所在的編譯單元中被使用的全局變量(僅當前文件中能使用的全局變量) 變量定義和聲明的區別 int i; 是變量的定義 extern int i; 是變量的聲明,不能初始化,放在頭文件中;用來告訴編譯器,其它用到的變量i是存在的 聲明是不產生代碼的東西 函數原型 變量聲明 結構聲明 宏聲明 枚舉聲明 類型聲明 inline聲明 定義是產生代碼的東西 函數 全局變量 只有聲明可以被放在頭文件中(是規則不是法律) 否則會造成一個項目中多個編譯單元裡有重名的實體 某些編譯器允許幾個編譯單元中存在同名的函數,或者用weak修飾符來強調這種存在 同一個編譯單元裡,同名的結構不能被重復聲明 如果你的頭文件裡有結構的聲明,很難這個頭文件不會在一個編譯單元裡被#include多次 所以需要"標准頭文件結構",運用條件編譯和宏,保證這個頭文件在一個編譯單元裡只會被#include一次 #ifndef _MAX_H_ #define _MAX_H_ ..... #endif #pragma once也能起到相同的作用,但不是所有的編譯器都支持 */return 0; }
Link:http://www.cnblogs.com/farwish/p/4647044.html
@黑眼詩人 <www.farwish.com>