【C++入門筆記】指標
前言
這是一篇跟著FishC大佬的C++指標部分學習筆記,以前一直對指標折騰的雲裡霧裡,希望這篇能對大家有所幫助。如有紕漏,望各位大佬指正。
指標(一)
想了解指標,必須先了解地址。
首先需要了解:程式是在硬碟上以檔案的形式存在的,但它們的執行卻是發生在計算機的記憶體中。
演示一下變數在記憶體中的存放情況。
- int a = -12;
- char b = M;
- float c =3.14;
這裡需要來討論一下對齊,為什麼上圖中c要從8開始儲存呢??
其實,在C++裡,變數型別是根據它們的自然邊界進行對齊的!不過這個我們只需要知道即可,因為編輯器會自動幫我們處理這類問題。另外,對齊問題會因為系統平臺不同而不同。
接下來談一下定址,對於變數可以通過兩種方法來對它進行索引。
一種是通過變數名
一種是通過地址。
這裡引入一個新的操作符,叫做:取址操作符:&,它的作用是獲得變數的地址。
我們習慣這麼使用:
- int var = 123;
- std::cout <<"Address is:"<<&var;
使用指標
上邊我們對地址這個概念是理解和使用指標的基礎。地址是計算機記憶體中的某個位置,而指標是專門用來存放地址的特殊型別變數。一般情況下,我們用下面的形式來宣告指標變數:
type *pointerName;
例如:
- int *p;
- int pp = 123;
- p = &pp;
在建立指標時,空格放在哪裡都是沒關係的,下邊的語法都是可以接受的。
- int *p1;
- int * p1;
- int* p1;
指標變數前邊的型別是用來說明指標指向的資料的型別,請務必匹配來使用。
另外,允許void型別的指標變數:void*p
指標(二)
溫故而知新:
- 建立變數時,系統會分配一些記憶體塊用來儲存它們的值;
- 每個記憶體塊都擁有一個獨一無二的地址;
- 變數的地址可以用&variablename語法來取得;(注:&我們稱為“取地址”操作符)
- 可以把地址賦值給一種稱為指標的特殊變數;
- 指標的型別必須與由它儲存其地址的變數的型別一致。
接下來舉一個栗子:
- int a = 456;
- char b = ‘C’;
- int *aPointer = &a;
- char *bPointer = &b;
這會讓程式保留四個記憶體塊,兩個為變數保留,兩個為指標保留。變數a和變數b中存放的是變數的值(456和‘C’的ASCII碼值);兩個指標變數存放著的是指標的值,這些值是其他變數的地址。
為什麼aPointer指標變數中儲存的是0而不是0-3呢,因為既然知道儲存的a為整形變數,那麼只需要儲存它的首地址再加上sizeof(a)即可
當我們知道了某個變數在記憶體中的地址(通過指標),就可以利用指標訪問位於改地址的資料。
這需要對指標進行“解引用(Deference)”處理:即在指標名的前面加上型號(*)。
例如:std::count << *aPointer;
這裡我們理解一下:把整數變數a的地址存在在aPointer指標之後,*aPointer和變數a將代表同一個值
因此:*aPointer = 123;將會導致下圖所示結果:
一定要牢記:指標所儲存的是記憶體中的一個地址。它並不儲存指向的資料的值的本身。因此,務必確保指標對應一個已經存在的變數或者一塊已經分配的記憶體。
重點部分:關於星號的用途
第一種是用於建立指標:
int *myPointer = &myInt;
第二種是對指標進行解引用:
*myPointer = 3998;
Tips:
C++允許多個指標指向同一個地址,就是多個指標有相同的值,任意一個進行了修改,其從放的值都會發生修改。
C++支援無型別(void)指標,就是沒有被宣告為某種特定型別的指標,例如
void *vPointer;
注意:對一個無型別指標進行解引用前,必須先把它轉換成一種適當的資料型別。
指標和陣列(三)
在上述兩講中關於地址和指標的栗子中,我們使用的都是標量型別:整數、實數和字元。
所以當我們遇到一個標量型別的變數時,我們可以建立一個與其型別相同的指標來存放它的地址。如果我們遇到的是陣列(是一組數,而不是一個數)該怎麼辦呢?
我們知道,計算機把陣列是以一組連續的記憶體塊儲存的
例如:int myArray[3] = {1,2,3};
儲存形式如下圖所示:
這就說明了陣列擁有很多個地址,每個地址對應著一個元素。陣列的名字其實也是一個指標(指向陣列的基地址,也就是第一個元素的地址)
比如上述那個栗子,一下兩句話做的事同一件事情:
- int *ptr1 = &myArray[0];
- int *ptr2 = myArray;
我們可以輕易地將陣列的基地址用指標變數儲存起來,那如果我們要通過指標訪問其他陣列元素,如何辦到呢?
試試:ptr1++;
以上運算並不是將地址值簡單的做+1處理,而是安裝指向的陣列的資料型別來遞增的,也就是說+sizeof(int)
思考:
如有:
int Array[5] = {1,2,3,4,5};
int *ptr = Array;
則
*ptr + 1;
*(ptr + 1);
二者有什麼區別呢??
#include <iostream>
int main()
{
int Array[5] = {1,2,3,4,5};
int *ptr = Array;
std::cout << *ptr + 1 << '\n';
std::cout << *(ptr + 1) << '\n';
return 0;
}
程式執行之後得到的的結果為2,難道二者沒有區別嗎?
答案肯定是有區別的。
*ptr+1是指標變數ptr(陣列)指向的首地址(第一個)元素的值加1。
*(ptr+1)是指標變數ptr(陣列)指向的首地址後的(第二個)元素的值;
注:有括號先算括號裡面的,也就是*(ptr+1)中先將指標指向ptr+1,再求值;而*ptr+1,先求*ptr,再加1。所以二者值相等僅僅是巧合而已~
小結:
指標運算的重要性在高階和抽象的程式設計工作中體現的非常明顯,就目前而言,只需要記住陣列的名字同時也是指向其第一個元素(基地址)的指標。
陣列可以是任意一種資料型別,這意味著我們完全可以建立一個以指標為元素的陣列。
未完待續……
參考資料
FishC的《C++快速入門》大家可以在B站上搜到
https://www.bilibili.com/video/av7595819?from=search&seid=12323634976003104863