Appearance
指標 (Pointers)
指標是 C/C++ 中較獨特的概念,也是讓它成為難以取代的語言的主要原因,當然也是最難的概念之一。
指標的概念
可以想像我們在程式中宣告的一切變數、函數,都會儲存在記憶體中,所以每一個變數都有他對應的一塊記憶體,這些區塊都有他對應的「地址」,也就是記憶體位置。
這些「地址」在 C 中可以被變數儲存起來,這個變數的類別叫做「指標(pointers)」,因為它存著一個「地址」,指標就可以找到在那個位址的變數,找到這個變數的過程稱為「指向(point)」,找到的變數會有各種型別與大小,但本質上是一段記憶體區塊,一段 0 與 1,因此可以用各種方式解讀,用 int 解讀就叫 int pointer;用 char 解讀就叫 char pointer。
使用指標
int *ptr: 宣告指向 int 的指標ptr = &num: 給指標一個地址ptr: 記憶體位置*ptr: 記憶體位置中儲存的數值
c
int *ptr; // 宣告一個指標,用 int 方式解讀記憶體
int num = 123; // 4 bytes
float fnum = 0.123; // 4 bytes
ptr = # // 取得 num 的記憶體位址,讓 ptr 指向他
printf("%p: %d\n", ptr, *ptr); // 印出 ptr 指向的位置與 ptr 解讀出的內容
ptr = (int *) &fnum; // 取得 fnum 的記憶體位址,並強制編譯器以 int 型別理解這塊位置
printf("%p: %d\n", ptr, *ptr); // 印出 位置: 1039918957不同的 pointer 之間可以互相轉型 (type casting),只要指向的內容不會超出記憶體範圍,像是 char pointer 指向 int 或 int pointer 指向 float,在很多時候能夠更靈活的操作。
型別轉換
這是前面為了簡單沒有細說的部分,大致上來說就是用不同的方式去解讀同一個數值,float int char 都是不同的解讀方式。
c
int a = 90.25; // float -> int 捨棄小數點
float b = (float) a / 2; // int -> float 通常沒事
char c = a; // int -> char 可能會因為 int 數值比 char 大導致 overflow不同的 pointer 間也可以互相轉換,但是如果直接讓 int pointer 指向 float 位置,編譯器會覺得你是不是做錯了,必須讓編譯器明確的知道需要轉換位置的類別。
更多型別與指標
指標的指標
有時候會需要指標的指標來達成類似二維陣列的效果,就是指向指標的指標,具體用途會在指標(下)。
c
int **ptr;所有用中括號宣告的陣列宣告本質上都是指標,不是指標的指標
常數指標
傳遞變數有時候是很大的性能開銷,使用 pointer 可以有效解決問題,但又不想讓函數能夠隨意更改位置或裡面的值,這時候就會使用 常數指標。
陣列名稱可以看做
type const*
c
// 不能改變裡面的值
void func1(const int *ptr);
// 不能改變 pointer 指向的位址
void func2(int const *ptr);
// 都不能改變
void func3(const int const *ptr);理解方式
為了解讀這個複雜的情況,我使用一套優秀的解讀方案
const跟後面的型別- 公式:
type (const) *ptrptr 是指向 type 的 (常數)指標,type可以是 const int、int *
字串指標
字串是一個比較複雜的部份,之前有說過宣告時可以使用 arr[] = "ciallo" 來複製一段字串到陣列中,但如果只是單純要儲存一個字串的話,其實也可以使用 pointer。
指標指向常數:*ptr = "ciallo"
這樣寫時,其實並沒有創造一個陣列,只是在指向一段已經存在於記憶體中的「唯讀」資料。
- 唯讀性:這段字串存在於唯讀資料區(Read-only Data),你不能寫
ptr[0] = 'b';,這會導致程式崩潰。 - 字串池 (Interning):編譯器很聰明,如果程式中出現兩次一樣的
"ciallo",它只會存一份。
陣列作法:arr[] = "ciallo"
這是在 Stack 上開闢一塊空間,然後把 "ciallo" 複製(寫)進來。
可修改性:因為這份資料是專屬的副本,可以隨意修改它:
arr[0] = 'b';。獨立性:即使內容一樣,它的地址也絕對跟字串常數不同。
c
char *ciallo = "ciallo";
char *hi = "ciallo";
if (hi == ciallo) {
printf("same\n");
}
hi = "ciallo (∠·ω )⌒★";
if (hi != ciallo) {
printf("not the same after change\n");
}
char arr_ciallo[] = "ciallo";
if (arr_ciallo != ciallo) {
printf("unique memory for arr string\n");
}進階: string literal 記憶體位置
如果是 ptr = "ciallo" 那麼字串本身是存在 不可變 data 段;如果是用 arr[] = "ciallo" 的實作方式則取決於 C 語言實作本身,可能是從 data 段移過來,也可能是直接在 stack 做一個。