static關鍵字
- static修飾局部變量
生命週期:靜態變量在程序運行之前創建,在程序的整個運行期間始終存在,直到程序結束。
注意: 靜態變量,只改變了它的存儲類型(即生命週期),並沒有改變它的作用域,變量b還是只能在test函數內部使用。
靜態本地變量, 具有全局的生存期, 只初始化一次, 離開函數後仍然存在, 具有函數內的局部作用域.
靜態本地變量實際上是特殊的全局變量, 都位於相同的內存區域, 即使在聲明時未賦初值, 也會默認初始化爲0
- static修飾全局變量
相當於私有的全局變量, 作用域只限於該源文件.
默認情況下, 一個全局變量是可以供多個源文件共享的, 也就是說多個源文件中同名的全局變量都代表着同一個變量.
static與函數
- 外部函數 : 如果在當前文件中定義的函數允許其他文件訪問, 調用, 就成爲外部函數, C語言規定, 不允許有同名的外部函數.
- 內部函數 : 如果在當前文件中定義的函數不允許其他文件訪問, 調用, 只能在內部使用, 就稱爲內部函數. C語言規定不同的源文件可以有同名的內部函數, 並且互不干擾.
所以如果要定義外部函數完整的定義是要加上extern關鍵字. 不過因爲默認情況下, 所有的函數就是外部函數, 所以一般簡化.
static修飾函數
1.定義內部函數
static void test() {
printf("dsf");
}
2.聲明內部函數
1 #include <stdio.h>
2
3 static void test();
4
5 int main(int argc, const char * argv[])
6 {
7 test();
8 return 0;
9 }
10
11 static void test() {
12 printf("調用了test函數");
13 }
在第11行定義了一個test函數,這是一個內部函數,接着在第3行對test函數進行提前聲明,然後就可以在第7行可以調用test()函數了
總結 :
- static在定義函數時, 在函數的最左邊加上static可以把該函數聲明爲內部函數, 這樣該函數就只能在其定義所在的文件中使用. 如果在不同的文件中有同名的內部函數, 則互不干擾.
- static也可以用來聲明一個內部函數.
宏
1>宏與typedef的區別:
(1)宏只是簡單的替換, 而typedef可以看成是徹底的"封裝"
例:#define X int*
X a, b;
只有a是指針
typedef int* X
X a, b;
a, b都是指針
(2)可以用其他類型說明符對宏定義的類型進行拓展, 而typedef不可以
例:#define X int
unsigned X a; 可以
而
typedef X int; unsigned X a;不可以
2>#用來將宏參數轉換爲字符串, 也就是在宏參數的開頭和末尾添加引號, 例如:
#define A(x) #x
printf("%s", A(hello, world));
// 將會被展開爲
printf("%s", "hello, world");
3>##稱爲連接符, 用來將宏參數和其他的串連接起來. 例如:
#define A(a, b) a##e##b
#define B(a, b) a##b##00
printf("%f\n", A(1, 2));
printf("%d\n", B(11, 22));
// 將會被展開爲
printf("%f\n", 1e2);
printf("%d\n", 112200);
4>如果一個宏的值中有其他宏的名字, 而宏定義中無#或##, 則會先進行宏參數的展開, 再展開當前宏.例如:
#define YEAR 2018
#define LEVELONE(x) "XiyouLinux "#x"\n"
#define LEVELTWO(x) LEVELONE(x)
#define MULTIPLY(x,y) x*y
int main(int argc, char *argv[])
{
int x = MULTIPLY(1 + 2, 3);
printf("%d\n", x);
printf(LEVELONE(YEAR));
printf(LEVELTWO(YEAR));
}
%zu 輸出 size_t型, size_t 在庫中被typedef爲unsigned int 無符號整型
浮點數的存儲
十進制8.25的二進制是1000.01
->1.000012^3(3指數, 00001尾數)
浮點數中1.x2^y也就是說1是固定的,2也是不變的,所以在內存中不用存儲,在內存中只存有x和y也就是尾數和指數,當然還有符號位.
浮點數在內存中無法精確存儲, float的有效位數只有7位(包括整數部分和小數部分), 超過7位之後均爲無效數字, double的有效位數有16位, 超過16位之後均爲無效數字.
判斷浮點數是否相同:由於精度問題, 不可將浮點變量用 == 或 != 與數字比較, 應該設法轉化爲>= 或 <= 之類的形式
例:要判斷float類型的變量i與0的比較
if ((x >= -0.00001) && (x <= 0.00001)) {
printf(“相等\n”);
}
也就是
if (fabs(i-0) < 1e-6) {
printf(“相等\n”);
}
printf, scanf的返回值
printf返回輸出的字符的個數
scanf返回正確讀取到的字符的個數
如果輸入數據與指定格式不符,則會產生輸入錯誤。遇到輸入錯誤,scanf函數會立即終止,返回已經成功讀取的數據的個數。
所以,通過scanf函數的返回值和指定輸入數據的個數(由格式符決定)的比較,可以判斷數據輸入是否成功。
大小端
個人pc一般都是小端序, 數據在內存中存放時低字節對應低地址, 高字節對應高地址.
讀取時從高地址往低地址讀取.
當定義一個變量時,系統就會爲這個變量分配一定的存儲空間。
1 int main()
2 {
3 char a = 'A';
4
5 int b = 10;
6
7 return 0;
8 }
1> 在64bit環境下,系統爲變量a、b分別分配1個字節、4個字節的存儲單元。也就是說:
-
變量b中的10是用4個字節來存儲的,4個字節共32位,因此變量b在內存中的存儲形式應該是0000 0000 0000 0000 0000 0000 0000 1010。
-
變量a中的’A’是用1個字節來存儲的,1個字節共8位,變量a在內存中的存儲形式是0100 0001
2> 上述變量a、b在內存中的存儲情況大致如下表所示:
-
從圖中可以看出,變量b佔用了內存地址從ffc1~ffc4的4個字節,變量a佔用了內存地址爲ffc5的1個字節。每個字節都有自己的地址,其實變量也有地址。變量存儲單元的第一個字節的地址就是該變量的地址。變量a的地址是ffc5,變量b的地址是ffc1。
-
內存尋址是從大到小的, 也就是說做什麼事都會先從內存地址較大的字節開始,因此係統會優先分配地址值較大的字節給變量。由於是先定義變量a、後定義變量b,因此你會看到變量a的地址ffc5比變量b的地址ffc1大。
-
注意看表格中變量b存儲的內容,變量b的二進制形式是:0000 0000 0000 0000 0000 0000 0000 1010。由於內存尋址是從大到小的,所以是從內存地址最大的字節開始存儲數據,存放順序是ffc4 -> ffc3 -> ffc2 -> ffc1,所以把前面的0000 0000都放在ffc2~ffc4中,最後面的八位0000 1010放在ffc1中。
gcc 的編譯過程
.c ->.i -> .s -> .o -> a.out
- 預處理:刪除註釋, 宏展開和宏替換, 處理條件編譯, 文件包含等, 生成.i文件
- 編譯:生成彙編文件.s, 生成相應的彙編代碼, 會檢查語法等
- 彙編:彙編代碼對應生成機器碼, 叫做目標文件.o
- 鏈接:所有目標文件和庫在一起鏈接, 在這個過程可以找到函數的定義從而修正假的函數地址, 得到可執行文件.
負數的二進制形式
1 int main()
2 {
3 int b = -10;
4 return 0;
5 }
在第3行定義了一個整型變量,它的值是-10。-10在內存中怎樣存儲的呢?其實任何數值在內存中都是以補碼的形式存儲的。
- 正數的補碼與原碼相同。比如9的原碼和補碼都是1001
- 負數的補碼等於它正數的原碼取反後再+1。
那麼-10的補碼計算過程如下:
1> 先算出10的二進制形式:0000 0000 0000 0000 0000 0000 0000 1010
2> 對10的二進制進行取反:1111 1111 1111 1111 1111 1111 1111 0101
3> 對取反後的結果+1:1111 1111 1111 1111 1111 1111 1111 0110
因此,整數-10在內存中的二進制形式是:1111 1111 1111 1111 1111 1111 1111 0110