1. 程式人生 > 程式設計 >深入瞭解c++陣列與指標

深入瞭解c++陣列與指標

1.陣列

陣列大小(元素個數)一般在編譯時決定,也有少部分編譯器可以執行時動態決定陣列大小,比如icpc(Intel C++編譯器)。

1.1陣列名的意義

陣列名的本質是一個文字常量,代表陣列第一個元素的地址和陣列的首地址。陣列名本身不是一個變數,不可以定址,且不允許為陣列名賦值。假設定義陣列:

int A[10];

那麼再定義一個引用:

int* &r=A;

這是錯誤的寫法,因為變數A是一個文字常量,不可定址。如果要建立陣列A的引用,應該這樣定義:

int* const &r=A;

此時,現在資料區開闢一個無名臨時變數,將陣列A代表的地址常量拷貝到該變數中,再將常引用r與此變數進行繫結。此外,定義一個數組A,則A、&A[0]、A+0是等價的。

在sizeof()運算中,陣列名代表的是全體陣列元素,而不是某個單個元素。例如,定義int A[5],生成Win32的程式,sizeof(A)就等於5*sizeof(int)=5*4=20。示例程式如下。

#include <iostream>
using namespace std;

int main()
{
int A[4]={1,2,3,4};
int B[4]={5,6,7,8};
int (&rA)[4]=A; //建立陣列A的引用

cout<<"A:"<<A<<endl;
cout<<"&A:"<<&A<<endl;
cout<<"A+1:"<<A+1<<endl;
cout<<"&A+1:"<<&A+1<<endl;
cout<<"B:"<<B<<endl;
cout<<"rA:"<<rA<<endl;
cout<<"&rA:"<<&rA<<endl;
}

執行結果:

A:0013F76C
&A:0013F76C
A+1:0013F770
&A+1:0013F77C
B:0013F754
rA:0013F76C
&rA:0013F76C

閱讀以上程式,注意如下幾點。

(1)A與&A的結果在數值上是一樣的,但是A與&A的資料型別卻不同。A的型別是int[4],&A的型別則是int(*)[4]。它們在概念上是不一樣的,這就直接導致A+1與&A+1的結果完全不一樣。

(2)為變數建立引用的語法格式是type& ref,因為陣列A的型別是int[4],因此為A建立引用的是int (&rA)[4]=A;

1.2陣列的初始化

定義陣列的時候,為陣列元素賦初值,叫作陣列的初始化。可以為一維陣列指定初值,也可以為多維陣列指定初值。例如。

Int A[5]={}; //定義長度為5的陣列,所有陣列元素的值都為0
int A[]={1,3}; //定義長度為3的陣列,陣列元素分別為1,3
int A[5]={1,2}; //定義長度為5的陣列,A[0],A[1]分別為1,2,其他值均為0
int A[][2]={{1,2},{3,4},{5,6}}; //定義一個型別為int[3][2]的二維陣列
int A[][2]={{1},{1},{1}}; //定義一個型別為int[3][2]的二維陣列,A[0][0]、A[1][0]、A[2][0]三個元素的值為1,其他元素的值均為0

//以下是幾種錯誤的初始化方法
int A[3]={1,4}; //初始化項的個數超過陣列的長度 
int A[3]={1,3}; //不允許中間跳過某項

2.指標

2.1指標的定義

指標是用來存放地址值的變數,相應的資料型別成為指標型別。在32位平臺上,任何指標型別所佔用的空間都是都是4位元組。比如sizeof(int*)、sizeof(double*)、sizeof(float*)等的值都為4。

2.2定義指標的形式

定義指標的形式是:type* p,其中type是指標所指向物件的資料型別,而*則是指標的標誌,p是指標變數的名字。由於C++中允許定義複合資料型別,因此指向複合資料型別物件的指標的定義方式可能較為複雜。理解指標,關鍵是理解指標的型別和指標所指向資料的型別。例如:

int (*p)[5]; //指標p的型別是int(*)[5],指標所指向的資料型別是int[5]
int* p[5]; //p是有5個分量的指標陣列,每個分量的型別都是int*(指向int的指標)
int** p; //指標p的型別是int**,p指向的型別是int*,p是指向指標的指標

2.3指標的初始化

定義指標變數之後,指標變數的值一般是隨機值,這樣的值不是合法訪問的地址。指標變數值的合法化途徑通常有兩個,
一是顯示置空,二是讓指標指向一個已經存在的變數,三是為指標動態申請記憶體空間。如下:

//顯示置空
int *p=NULL;

//將指標指向某個變數
int i;
int *p=&i;

//動態申請記憶體空間
int* p=new int[10];

2.4指標可以參與的運算

由於指標是一個變數,所以指標可以參與一些運算。假設定義指標int* p,指標p能夠參與的運算有:
(1)解引用運算,即獲取指標所指的記憶體地址處的資料,表示式為*p,如果指標指向的是一個結構或者類的物件,那麼訪問物件成員有兩種方式:(*p).mem或p->mem。

(2)取地址運算,即獲取指標變數的地址,表示式為&p,其資料型別為int**;

(3)指標與整數相加減。表示式p+i(或者p-i),實際上是讓指標遞增或遞減地移動i個int型變數的距離。

(4)兩個指標相減,如p-q,其結果是兩個指標所儲存的地址之間的int型資料的個數。

2.5注意指標的有效性

使用指標的關鍵就是讓指標變數指向一個它可以合法訪問的記憶體地址,如果不知道它指向何處,請置為空指標NULL或者((void*)0)。

在某些情況下,指標的值開始是合法的,以後隨著某些操作的進行,它變成了非法的值。考察如下程式。

#include <iostream>
using namespace std;

int* pPointer;
void SomeFunction()
{
int nNumber=25;
pPointer=&nNumber; //將指標pPointer指向nNumber
}

void UseStack()
{
int arr[100]={};
}

int main()
{
SomeFunction();
UseStack();
cout<<"value of *pPointer:"<<*pPointer<<endl;
}

輸出結果是0,並非想象中的25。原因是函式SomeFunction()執行結束之後,區域性變數nNumber已經被清空,其佔有的空間在離開函式後歸還給系統,之後又分配給函式UseStack()中的區域性變數arr。因此指標pNumber的解引用後的值變成了0。所以,要想正確的使用指標,必須保證指標所指向單元的有效性。

3.陣列與指標的關係

陣列名代表陣列的首地址,而陣列A的某個元素A[i]可以解釋成*(A+i),所以陣列名本身可以理解為一個指標(地址),一個指標常量。所以,在很多情況下,陣列與指標的用法是相同的,但是陣列與指標本質上存在一些重要的區別。

(1)陣列空間是靜態分配的,編譯時決定大小。而指標在定義時,可以沒有合法訪問的地址空間,也就是野指標。

(2)陣列名代表一個指標常量,企圖改變陣列名所代表的地址的操作都是非法的。例如如下程式碼:

int arr[5]={0,1,4};
arr++; //編譯錯誤

(3)函式形參中的陣列被解釋為指標。考察如下程式:

void show0(int A[])
{
A++;
cout<<A[0]<<endl;
}

void show1(int A[5])
{
A++;
cout<<A[0]<<endl;
}

int main()
{
int d[5]={1,4,5};
show0(d);
show1(d);
}

以上程式編譯通過並輸出2和2。程式中形引數組A可以進行自增運算,改變了自身的值,這個說明了形引數組A被當作指標看待。之所以這樣處理,原因有兩個,一是C++語言不對陣列的下標作越界檢查,因此可以忽略形引數組的長度;二是陣列作整體進行傳遞時,會有較大的執行時開銷,為了提高程式執行效率,將陣列退化成了指標。

(4)如果函式的形參是陣列的引用,那麼陣列的長度將被作為型別的一部分。實際上,對陣列建立引用,就是對陣列的首地址建立一個常引用。由於引用是C++引入的新機制,所以在處理引用時使用了一些與傳統C語言不同的規範。在傳統的C語言中,對陣列的下標是不做越界檢查,因此在函式的引數說明中,int[5]和int[6]都被理解為int[](也就是int*),C++語言也沿用了這種處理方式。但是,int(&)[5]與int(&)[6]被認為是不同的資料型別,在實參與形參的匹配過程作嚴格檢查。考察如下程式。

#include <iostream>
using namespace std;

void show(int(&A)[5])
{
cout<<"type is int(&)[5]"<<endl;
}

void show(int(&A)[6])
{
cout<<"type is int(&)[5]"<<endl;
}

int main()
{
int d[5]={1,5};
show(d);
}

程式結果:

type is int(&)[5]

(5)在概念上,指標同一維陣列相對應。多維陣列是儲存在連續的儲存空間,而將多維陣列當做一維資料看待時,可以有不同的分解方式。考察如下程式。

#include <iostream>
using namespace std;

void show1(int A[],int n)
{
for(int i=0;i<n;++i)
cout<<A[i]<<" ";
}

void show2(int A[][5],int n)
{
for(int i=0;i<n;++i)
show1(A[i],5);
}

void show3(int A[][4][5],int n)
{
for(int i=0;i<n;++i)
show2(A[i],4);
}

int main()
{
int d[3][4][5];
int i,j,k,m=0;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
for(int k=0;k<5;++k)
d[i][j][k]=m++;

show1((int*)d,3*4*5);
cout<<endl;
show2((int(*)[5])d,3*4);
cout<<endl;
show3(d,3);
}

程式執行結果可以看出,以下三條輸出語句的資料結果是相同的。

show1((int *)d,3*4*5);
show2((int(*)[5])d,3*4);
show3(d,3);

它們的輸出結果完全一樣,即從0到59。這說明把3維陣列d當作一維陣列看待,至少可以有以下3中不同的分解方式:

a.資料型別為int,元素個數為3*4*5=60;
b.資料型別為int[5],元素個數為3*4=12;
c.資料型別為int[4][5],元素個數為3。

所以,可以將多維陣列看做“陣列的陣列”。在將多為陣列轉換為指標的時候,一定要注意多為陣列的分解方式,以便進行正確的型別轉換。

(6)字元陣列與字元指標的區別。
字元陣列字元指標在形式上很接近,但在記憶體空間的分配和使用上還是有重大的差別。如前所述,陣列名並不是一個執行實體,它本身不能被定址。而指標是一個變數(執行時實體),可以被定址,它所指向的空間是否合法要在執行時決定。錯誤地使用指標將導致對記憶體空間的非法訪問。考察如下程式。

#include <iostream>
using namespace std;
int main()
{
char s[]="abc"; //s是字元陣列,空間分配在棧上。對字元陣列元素的修改是合法的
char *p="abc"; 
s[0]='x';
cout<<s<<endl;
//p[0]='x'; //此句編譯出錯,指標指向常量區的字串,對字串常量的修改是非法的
cout<<p<<endl;
}

程式輸出結果:

xbc
abc

以上就是深入瞭解c++陣列與指標的詳細內容,更多關於c++陣列與指標的資料請關注我們其它相關文章!