c和指針筆記


參考資料:c和指針。

 
 

作用域

作用於分爲文件作用域、函數作用域、代碼塊作用域、原型作用域。

  • 文件作用域(file scope)
    代碼塊之外的聲明的標識符都具有文件作用域,從它們聲明之處到源文件結尾處都可以訪問。

  • 原型作用域(prototype scope)
    原型作用域只適用於在函數原型中聲明的參數名。在原型中參數名不是必須的,但是,如果出現了參數名,你可以隨意取個名字。原型作用域防止這些參數名和程序其他部分的名字衝突。
    int funcA(int A);

  • 函數作用域(function scope)
    它只適用於語句標籤,語句標籤用於goto。

  • 代碼塊作用域(block scope)
    位於一對花括號之間的所有語句稱爲一個代碼塊。任何從代碼塊開始位置聲明的標識符都具有代碼塊作用域。可嵌套。

 
 

鏈接屬性

鏈接屬性有3中,外部(external),內部(internal),none(無)。

無鏈接屬性的標識符總是被當作單獨的個體,也就是該標識符的多個聲明被當作獨立不同的實體。

屬於internal鏈接屬性的標識符在同一源文件內的所有聲明中都指同一個實體,但位於不同源文件的多個聲明則分屬不同的實體。

外部鏈接屬性的標識符無論聲明瞭多少次,位於幾個源文件中都表示同一個實體。
 
 

static和extern

關鍵字static合extern用於在聲明中修改標識符的鏈接屬性。
如果某個聲明在正常情況下具有external鏈接屬性,在他前面加上static關鍵字就可以把它變爲internal。函數也一樣。static只對缺省爲external的聲明纔有效。代碼塊爲加static效果是不一樣的。

extern關鍵字更爲複雜。一般而言,它爲一個標識符指定external屬性,這樣就可以在其他任何位置定義這個實體。
 
 

存儲類型

變量的儲存類型是指存儲變量值的內存類型。存儲類型決定了變量的生命週期。有三個地方可以存儲變量:普通內存,運行時堆棧,硬件寄存器。

變量的缺省存儲類型由聲明的位置決定。凡是在代碼塊之外聲明的變量總是存儲於靜態內存中,這類變量稱爲靜態變量。你無法爲他們指定其他的存儲類型。靜態變量是在程序運行之前就創建的,始終存在。

在代碼塊內聲明的變量的缺省存儲類型是自動的,也就是它存儲與堆棧中,叫自動變量。關鍵字爲auto,默認缺省爲auto,所以極少使用。

在代碼塊內部聲明的變量,如果給它加上關鍵字static,可以使它的存儲類型從自動變爲靜態,但作用域不變。

關鍵字register可以用於自動變量的聲明,提示它們存儲於機器的硬件寄存器中,這類變量稱爲寄存器變量。訪問效率比存儲於內存的高很多。

靜態變量的缺省值爲0,自動變量沒有缺省的初始值,即默認爲垃圾值。
 
 

static

當它用於函數或者代碼塊之外的變量聲明時,static用於修改標識符的鏈接屬性,從external改爲internal。但存儲類型和作用域不變。
當它用於代碼塊之內的變量聲明時,static用於修改變量的存儲類型,從自動變量改爲靜態變量。但鏈接屬性和作用域不變。

 
 

移位操作符

邏輯位移與算術位移
算術左移和邏輯左移是相同的,都是低位補0。
右移就要看符號位了,如果符號位是1,則移入的位均爲1,符號位爲0,移入的則均爲0。
例如10010110右移兩位,邏輯位移爲00100101,算術位移爲11100101。當操作數爲正時則一樣。
使用邏輯位移還是算術位移由編譯器決定。
 
 

指針

指針表達式

在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

*cp++++的優先級是要大於的,這裏看上去像是*大於++,實際上的步驟:
1++產生cp的一份拷貝 == 》 temp = cp;
2++操作符增加cp的值 == 》 cp = cp + 1;
3、在cp的拷貝上執行*間接訪問操作 ==*temp;

在這裏插入圖片描述

++*cp,++* 操作符的結合性都是從右到左,所以先執行的是間接訪問.
所以這裏並不會對cp進行加1,cp指向的地址仍然不變。
*cp = *cp + 1; //'a' + 1 = 'b';

.在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

指針運算

在這裏插入圖片描述
+1加的是一個指針類型的大小。

指針的算術運算

C的指針只有兩種形式:

  • 指針 ± 整數
  • 指針 - 指針

標準允許指向數組元素的指針與指向數組最後一個元素後面的那個內存位置的指針進行比較,但不允許對數組第一個元素前的那個內存位置的指針進行比較。(實際上大多數編譯器不管)

 
 

函數

真函數

真函數(即返回一個值的函數),不應該在其內部調用一個過程類型的函數(無返回值)

函數的參數

  • 傳遞給函數的標量參數是傳值調用的。
  • 傳遞給函數的數組參數在行爲上就像它們是通過傳址調用的那樣。

可變參列表

可變參數列表是通過紅來實現的,這些宏定義於stdarg.h頭文件。這個頭文件聲明瞭一個類型va_list和三個宏:va_start、va_arg和va_end。

  • va_start需要一個參數,所以函數至少需要一個命名參數。
  • 這些宏無法判斷實際存在的參數個數。
  • 這些宏無法判斷每個參數的類型。

 
 

數組

數組名

數組名的值是一個指針常量,第一個數組元素的地址。不要根據這個就得出數組和指針是相同的結論。數組是具有確定數量的元素,而指針只是一個標量值。只有當數組名在表達式中使用時,編譯器纔會爲它產生一個指針常量。

只有兩種狀態下,數組名並不用常量來表示:當數組名作爲sizeof操作符或單目操作符&的操作數時。

下標

這兩個表達式是等同的:

  • array[sub]
  • *(array + (sub)) //理解數組和指針轉化的基礎

2[array]的寫法是合法的,換成對等的間接表達式是:
*(2 + (array)) ==> *(array + 2)
雖然合法,但是絕不該這樣寫,影響可讀性。

下標與指針

指針表達式和下標表達式怎麼選?
下標具有可讀性優勢,但可能會影響運行時的效率。
假定兩種方法都是正確的,下標絕對不會比指針更有效率,但是指針有時會比下標更有效率。
代碼的可讀性有時比較重要!

多維數組

matrix[1][5] ⇒ * (*(matrix + 1) + 5) ⇒ *(matrix[1] + 5)

  • 作爲參數的多維數組,必須聲明維的長度,因爲算第二維需要用到,計算下標時。
    eg : int matrix[3][10];
    可以這樣寫:
    int func(int (*mat)[10]);
    int func(int mat[][10]);
    不能寫成:
    int func(int **mat);
    只有一維數組纔可以寫成指針的形式。而這個例子的mat則是聲明爲一個指向整形指針的指針,他和指向整形數組的指針並不是一回事。

  • 數組長度的自動計算
    只有第一維才能根據初始化列表缺省地提供,剩餘的幾個維必須顯式的寫出。如:

    int matrix[][5] = {
    	{0, 2, 4},
    	{0, 1, 2}
    };
    

指針數組

指針數組的聲明,eg:
int *api[10];
由於下標的優先級大於間接訪問,所以api是某種類型的數組(包含10個元素),在取得一個數組的元素後,對它進行間接訪問,這個表達式不再有其他操作符,所以他的結果是一個整形值。元素類型是指向整形的指針。由於元素是指針,所以這裏可以用int **cp = api;

指針數組使用場景,eg:

const char *keyword1[] = {
	"do",
	"for",
	"if",
	NULL
};

const char keyword2[][10] = {
	"do",
	"for",
	"if"
};

第一個聲明瞭指針數組。
在這裏插入圖片描述
指針數組需要額外的空間保存指針。

第二個則是聲明瞭矩陣。
在這裏插入圖片描述

結構和聯合

高級指針

預處理器

.
.
.
.
.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章