1. 程式人生 > >【C++入門筆記】指標

【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