一些需要知道的基礎知識點:
在程式程式碼中是通過變數名對記憶體單元進行存取操作的,但是程式碼經過編譯後將變數名轉換為該變數在記憶體中的存放地址,對變數值的存取都是通過地址進行的。比如i+j的運算,如果i等於3,j等於4,程式是先根據變數名與地址的對應關係,找到變數i的地址,從第一個地址開始順序讀取四個位元組資料放到CPU暫存器中;再找到j的地址,依次讀取四個放到暫存器中,然後通過CPU的加法中斷計算出結果。
在低階的組合語言中都是直接通過地址來訪問記憶體單元的,而在高階語言中才使用變數名訪問記憶體單元,C語言作為高階語言卻提供了通過地址來訪問記憶體單元的方法,C++也繼承了這一特性。地址可以形象地稱為指標,意思是通過指標能找到記憶體單元,因為原本是通過地址找到記憶體單元的,所以一個變數的地址稱為該變數的指標。如果有一個變數專門存放另一個變數的地址,它就是指標變數。在C++語言中,專門用來存放記憶體單元地址的變數型別,稱為指標型別。
指標是一種資料型別,通常所說的指標就是指標變數,是專門用來存放地址的變數。而變數的指標說的就是變數在記憶體中的地址。變數地址在編寫程式碼時無法獲得,只有在程式執行時才可以得到。
指標跟常規的變數為賦值相同,沒有具體指向的指標不會導致編譯出錯,但是可能會導致難以預料的錯誤,所以一旦定義指標,一定要讓它有一個具體的指向,也就是說要有一個地址賦給它。
另外,定義指標變數的時候,跟其他常規變數為了區分,要加*號,但其實真正的指標變數是沒有*號的,在使用的時候要注意。
指標進行運算其實就是地址進行運算,指標的加減運算是跟指標的型別有關的,比如int型別的指標加1,地址值並不是加1而是加4,因為int型別佔四個位元組。
※指標還可以指向空型別void。空型別指標可以接受任何型別的資料,例如void *p = NULL
這裡有點問題,NULL代替空指標存在二義性,所以後來用nullptr來代替空指標。總之,只要記住nullptr代表空指標就可以了,而NULL在C++中都把它理解為0就可以了。
※const int *p = &i 表示這個指標是指向常量的指標,只能用來“讀”記憶體資料,無法通過*p的方式更改記憶體的內容,即更改變數的值,但是可以改變自身的地址值,就是指向別的記憶體地址;
※int* const p = &i 表示這個指標是一個常量指標,什麼意思呢,以為指標變數裡存放的是地址,定義為常量指標的話,說明這個指標變數存放的地址值是不可以改變的,但是呢,可以通過*p的方式改變記憶體的資料;
※const int* const p = &i 表示這個指標是指向常量的常量指標,有點拗口,跟上面的對比,反正就是隻能用來“讀”記憶體資料,也無法通過*p的方式更改記憶體的內容,也無法改變自身的地址值。
指標和一維陣列
一維陣列和二維陣列在記憶體中的存放結構都是線性的。陣列第一元素的地址就是整個陣列的儲存首地址,該地址存放在陣列名中,看吧,其實陣列名就相當於一個指標變數了,裡頭存放的是陣列的首地址。訪問陣列元素的方式有下標法和指標法。用陣列名[i]這個方式可以訪問陣列內容,其實也可以通過*(a+i)的方式,當然我們也可以再單獨定義一個指標變數,將陣列的首地址存放到這個指標變數中,當然了,可以用&a[0]也可以直接用a即陣列名,因為我們說過了陣列名本來就存放的是陣列的首地址。
*(p++)相當於a[i++],先對p進行*運算,再使p自增。
指標與二維陣列
a[0]是二維陣列第一個元素的地址,可以賦值給一個指標變數。
a代表二維陣列的地址,通過指標運算子可以獲取陣列中的元素。
a+n表示第n行的首地址;
&a[0][0]既可以看做陣列0行0列的首地址,還可以看做是二維陣列的首地址;
&a[0]是第0行的首地址;
a[0]+n是第0行第n個元素的地址;
*(*(a+n)+m)表示第n行第m列元素;
*(a[n]+m)表示第n行第m列元素。
陣列指標和指標陣列
這個還沒有理解好,尤其是陣列指標,指標陣列還好理解,就是儲存指標變數的陣列。
指標和字元陣列
字元陣列就是一個字串,通過字元指標可以指向一個字串,然後通過地址的加減實現讀取。
傳遞地址
之前接觸的函式都是實參傳遞進函式體後,生成的是實參的副本,函式體內改變副本的值並不會影響到實參,但是如果傳遞進去的是指標,即使是副本指標,指向的地址還是一樣的,因此可以改變指標指向地址的內容。
指向函式的指標
指標變數也可以指向一個函式。一個函式在編譯時被分配給了一個入口地址,這個地址可以理解為存放在函式名中,可以定義函式指標,指向這個函式,通過指標來呼叫函式。
比如:
int sum(int x, int y)
int *a(int, int);
a = sum;
呼叫的時候這樣:
int c,d;
(*a)(c,d);
還可以定義指標函式,返回值是指標,也就是地址了。
空指標呼叫函式
空型別指標指向任意型別函式或者將任意型別的函式指標賦值給空型別指標都是合法的。使用空指標呼叫自身所指向的函式仍然按照強制轉換的形式使用。
指標陣列
構造資料型別
結構體
C++ struct結構體變數其實跟陣列有點像,只不過陣列是相同型別元素的集合,而結構體變數可以是不同型別資料的集合。
定義結構體變數有兩種方式:1、在定義結構體的時候定義;2、定義完之後定義。
struct PersonInfo { int index; char name[30]; short age; }XiaoMing; 或者 struct PersonInfo { int index; char name[30]; short age; } PersonInfo XiaoMing;
引用方式:1.使用成員運算子.來引用;2.定義結構體指標變數之後,使用指向運算子->來引用
結構體還可以進行巢狀,結構體的大小一般情況下都是結構體內成員的大小之和。
使用typedef可以給一個複雜的資料型別定義一個別名,比如int(*)(int i)很複雜,就可以用一個別名來代替。
列舉型別enum的應用
列舉型別就是用大括號將不同識別符號放到一起,變數的值只能取自括號內的值,在定義時,編譯器預設將識別符號自動賦上整形常數。還可以自行修改整形常數的值,說白了就是一個識別符號對應一個常數,用於一些判斷或者什麼,用起來方便一些。賦值的時候,不能直接賦整型數,但是可以通過強制型別轉換來賦值。列舉型別的變數,實質其實是整型的數字,所以可以進行比較和運算。
結構體
結構體變數還可以做函式的引數,還可以使用結構體指標,使用結構體指標減少了時間和空間上的開銷,能夠提高程式的執行效率。
結構體還可以建立結構體陣列,跟建立結構體變數一樣,在定義結構體時建立或者定義完了之後用結構體名這種資料型別建立。可以定義結構體指標,指向陣列,使用指向符號->進行讀取,但是要注意,指標加1,指向的就是陣列裡的下一個結構體了。
共用體
共用體union資料型別其實和結構體很像,宣告共用體資料型別變數和宣告共用體變數一樣,都有三種方式,前兩種應該知道,第三種是省略了共用體資料型別變數名,在定義共用體之後接著宣告變數。
要注意共用體跟結構體最大的區別在於記憶體方面。共用體變數所佔的記憶體長度等於最長的成員的長度,一個共用體變數不能同時存放多個成員的值,某一時刻只能存放其實一個成員的值,這就是最後賦予他的值。
共用體的特點是:1.使用共用體變數的目的是希望用同一個記憶體段存放幾種不同型別的資料,但是某個瞬間只能存放一種,而不是同時存放幾種;2.能夠被訪問的是最後一個被賦值的變數,對新的賦值後原來的就失去作用了;3.共用體變數的地址和它的各成員的地址都是同一地址。4.不能對共用體變數名賦值,不能引用變數名來得到哦一個值,不能在定義共用體變數時對它初始化,不能作為函式引數。
自定義資料型別
跟前面的資料型別重新命名一樣的,使用typedef識別符號進行型別的重新命名,或者說是自定義資料型別,其實是為了寫起來方便或者用特定的單詞代表特殊的意思。
巨集定義
使用#define 可以對程式中經常出現的引數進行巨集定義,這樣在之後寫程式碼的時候就可以用巨集定義替換,一定程度上減輕了寫程式碼的複雜度,程式在編譯的時候自動進行替換,比如PI,一般識別符號習慣用大寫字母表示,以和變數名進行區分,要注意巨集定義不是C語言,不需要在後面加分號,還可以定義陣列和運算,定義運算的時候,括號建議都加上,以防止出現錯誤。如果字串長於一行,可以在該行末尾用反斜槓\來續行。一般來講,定義只有,作用域即有效範圍指的是定義命令之後到此原始檔結束,但也可通過#undef命令終止巨集定義的作用域。