------------------------------------------------------------------------------------
全局變量:
// 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>