C/C++ 中指標和陣列表示法的區別

Ilja Kostenko 2023年10月12日
  1. C++ 陣列
  2. C++ 指標
C/C++ 中指標和陣列表示法的區別

指標和陣列無疑是 C++ 最重要和最複雜的方面之一。它們支援連結串列和動態記憶體分配,並且允許函式更改其引數的內容。

C++ 陣列

陣列是一組由索引訪問的相同型別的元素 - 陣列中元素的序號。例如:

int ival;

它將 ival 定義為 int 型別變數和指令。

int ia[10];

它設定了一個由十個 int 物件組成的陣列。這些物件或陣列元素中的每一個都可以使用獲取索引的操作來訪問。

ival = ia[2];

它將陣列 ia 中索引為 2 的元素的值分配給變數 ival。類似地

ia[7] = ival;

它將 ival 值分配給索引為 7 的元素。

陣列定義由型別說明符、陣列名稱和大小組成。大小指定陣列元素的數量(至少 1 個)並用方括號括起來。陣列的大小需要在編譯階段就已經知道,因此,它必須是一個常量表示式,儘管它不一定由文字設定。

元素的編號從 0 開始,因此對於 10 個元素的陣列,正確的索引範圍不是 1 到 10,而是 0 到 9。下面是一個對陣列的所有元素進行排序的示例。

int main() {
  const int array_size = 10;
  int ia[array_size];
  for (int ix = 0; ix < array_size; ++ix) ia[ix] = ix;
}

定義陣列時,你可以通過在大括號中列出其元素的值來顯式初始化它,用逗號分隔。

const int array_size = 3;
int ia[array_size] = {0, 1, 2};

如果我們明確指定一個值列表,我們可能不會指定陣列的大小:編譯器本身會計算元素的數量。

C++ 指標

指標是包含另一個物件的地址並允許間接操作該物件的物件。指標通常用於處理動態建立的物件,構建相關的資料結構,例如連結串列和分層樹,以及將大物件(陣列和類物件)作為引數傳遞給函式。

每個指標都與某種型別的資料相關聯。它們的內部表示不依賴於內部型別:指標型別的物件佔用的記憶體大小和取值範圍都相同。不同之處在於編譯器如何感知可定址物件。指向不同型別的指標可能具有相同的值,但對應型別的記憶體區域可能不同。

這裡有些例子:

int *ip1, *ip2;
complex<double> *cp;
string *pstring;
vector<int> *pvec;
double *dp;

指標由名稱前的星號表示。在通過列表定義變數時,應在每個指標之前放置一個星號(見上文:ip1 和 ip2)。在下面的例子中,lp 是一個指向 long 型別物件的指標,而 lp2 是一個 long 型別的物件。

long *lp, lp2;

在以下情況下,fp 被解釋為一個浮點物件,而 fp2 是一個指向它的指標:

float fp, *fp2;

給定一個 int 型別的變數:

int ival = 1024;

以下是定義和使用指向 int pi 和 pi2 的指標的示例。

[//] pi is initialized with the null address
int *pi = 0;
[//] pi2 is initialized with the address ival
int *pi2 = &ival;
[//] correct: pi and pi2 contain the ival address
pi = pi2;
[//] pi2 contains the null address
pi2 = 0;

不能為指標分配不是地址的值。

[//] error: pi cannot take the value int
pi = ival

同樣,如果定義了以下變數,則不能將值分配給一種型別的指標,該指標是另一種型別的物件的地址。

double dval;
double *ps = &dval;

那麼下面給出的兩個賦值表示式都會導致編譯錯誤。

[//] compilation errors
[//] invalid assignment of data types: int* <== double*
pi = pd
pi = &dval;

不是變數 pi 不能包含物件 dval 的地址 - 不同型別的物件的地址具有相同的長度。故意禁止這種地址混合操作,因為編譯器對物件的解釋取決於指標的型別。

當然,在某些情況下,我們對地址本身的值感興趣,而不是它指向的物件(假設我們想將此地址與其他地址進行比較)。為了解決這種情況,我們可以引入一個特殊的無效指標,它可以指向任何資料型別,下面的表示式是正確的:

[//] correct: void* can contain
[//] addresses of any type
void *pv = pi;
pv = pd;

void* 指向的物件的型別是未知的,我們無法操作該物件。我們對這樣一個指標所能做的就是將它的值分配給另一個指標或將它與某個地址值進行比較。

要使用其地址訪問物件,你需要應用取消引用操作或間接定址,由星號 (*) 指示。例如,

int ival = 1024;
, ival2 = 2048;
int *pi = &ival;

我們可以通過對指標 pi 應用解引用操作來讀取和儲存 ival 的值。

[//] indirect assignment of the ival variable to the ival2 value
*pi = ival2;
[//] value indirect use of variable value and pH value value
*pi = abs(*pi); // ival = abs(ival);
*pi = *pi + 1; // ival = ival + 1;

當我們將取地址 (&) 的操作應用於 int 型別的物件時,我們會得到 int* 型別的結果

int *pi = &ival;

如果將相同的操作應用於 int*型別的物件(指向 int C 型別的指標),我們得到一個指向 int 型別指標的指標,即型別 int**int** 是包含 int 型別物件地址的物件的地址。

取消引用 ppi,我們得到一個包含 ival 地址的 int*物件。要獲得 ival 物件本身,必須將取消引用操作應用於 PPI 兩次。

int **ppi = &pi;
int *pi2 = *ppi;
cout << "ival value\n"
     << "explicit value: " << ival << "\n"
     << "indirect addressing: " << *pi << "\n"
     << "double indirect addressing: " << **ppi << "\n"
     << end;

指標可用於算術表示式。請注意以下示例,其中兩個表示式執行完全不同的操作。

int i, j, k;
int *pi = &i;
[//] i = i + 2
*pi = *pi + 2;
[//] increasing the address contained in pi by 2
pi = pi + 2;

你可以向指標新增一個整數值,也可以從中減去。向指標加 1 會增加分配給相應型別物件的記憶體區域大小的值。如果 char 型別佔用 1 個位元組,int – 4double - 8,則將 2 加到指向字元、整數和 double 的指標上,它們的值將分別增加 2、8 和 16。

這怎麼解釋?如果相同型別的物件一個接一個地位於記憶體中,那麼將指標加 1 將使其指向下一個物件。因此,在處理 >array 時最常使用帶指標的算術運算;在任何其他情況下,它們幾乎沒有道理。

以下是使用迭代器迭代陣列元素時使用地址算術的典型示例:

int ia[10];
int *iter = &ia[0];
int *iter_end = &ia[10];
while (iter != iter_end) {
  do_the event_ with_(*iter);

相關文章 - C++ Pointer

相關文章 - C++ Array