詳解C++數組和數組名問題(指針、解引用)

一、指針

 1.1 指針變量和普通變量的區別

指針:指針的實質就是個變量,它跟普通變量沒有任何本質區別。指針完整的應該叫指針變量,簡稱為指針。 是指向的意思。指針本身是一個對象,同時指針無需在定義的時候賦值

1.2 為什麼需要指針

指針的出現是為瞭實現間接訪問。在匯編中都有間接訪問,其實就是CPU的尋址方式中的間接上。

間接訪問(CPU的間接尋 址)是CPU設計時決定的,這個決定瞭匯編語言必須能夠實現問接尋又決定瞭匯編之上的C語言也必須實現簡介尋址。

1.3 指針使用三部曲

三部曲:定義指針變量、關聯指針變量、解引用

(1)當我們int *p定義一個指針變量p時,因為p是局部變量,所以也道循C語言局部變量的一般規律(定義局部變量並且未初始化,則值是隨機的),所以此時p變量中存儲的是一個隨機的數字。

(2)此時如果我們解引用p,則相當於我們訪問瞭這個隨機數字為地址的內存空間。那這個空間到底能不能訪問不知道(也許行也許不行),所以如果直接定義指針變量未綁定有效地址就去解引用幾平必死無疑。

(3)定義一個指針變量,不經綁定有效地址就去解引用,就好象拿一個上瞭鏜的槍隨意轉瞭幾圈然後開瞭槍。

(4)指針綁定的意義就在於讓指針指向一個可以訪問、應該訪問的地方(就好象拿著槍瞄準且標的過程一樣),指針的解引用是為瞭間接訪問目標變量(就好象開槍是為瞭打中目標一樣)

int val = 43;
int * p = &val;   // &在右值為取值符
cout << *p << endl;

//輸出
43

二、整形、浮點型數組

 前言

  • 在很多用到數組名字的地方,編譯器都會自動地將其替換為一個指向該數組首元素的指針。
  • 所以,在大多數表達式中,使用數組名其實是在使用一個指向該數組首元素的指針。

2.1 數組名其實是特殊的指針

int main()
{
	int a[] = { 0,1,2,3,4 };
	printf("%x\n", a);
	printf("%x\n", &a);
	printf("%x\n", &a[0]);
}

在這裡插入圖片描述

  • 從局部變量表可以看出,數組a和指針p的構成是很相似的。它們實際存的都是一個地址,都會指向一個對象(或多個對象的第一個對象)。所以說數組名其實是種特殊的指針。
  • 為什麼說是特殊呢?

一維數組

 int a[] = { 0,1,2,3,4 };
    int * p1 = a;
    int *p = &a[0];
    //指針p是 int * 的,而首元素是有地址的,所以取址,是允許的
    
	//int * p1 = &a;    //錯誤
	//理解:int (*p1)[5] = &a;  //正確
	/*
	但它倆自身又有不同:
	指針 p1 本身是一個對象,在內存中是為其分配瞭空間的;
	數組名 a 在內存空間中沒有分配到空間(這將導致&a操作的效果可能和預想的不大一樣)。
	可以理解為a指向一個含有5個整數的數組的指針,故 &a的類型為int(*)[5],不能用來初始化int */	

整理:

指針 類型
a int *
&a int (*) [5]

二維數組

int ia[3][4];
       int (*p)[4] = ia;      //ia 的類型就是 int(*)[4]
       int (*p)[3][4] = &ia;  //&ia的類型就是 int(*)[3][4]

整理:

指針

類型iaint (*) [4]&1aint (*) [3] [4]

2.2 理解復雜的數組的聲明

上述提到數組名是指向一個數組的指針,因此解釋一下一些復雜的數組聲明。加深理解

 int * ptr[10];             //ptr是含有10個  整形指針  的數組
   int & ref[10]  = /* ? */   //錯誤,不存在引用的數組
   int (*parray) [5] = &a;   //parray指向一個含有5個整數的數組
   /*
   同時也是上述數組名的解釋
   *parray意味著parray是一個指針;
   右邊是[5]表明是指向大小為10的數組;
   左邊int表明數組中元素為int.
   */
   
   int (&array)[5] = a;      //array引用一個含有5個整數的數組

   int * (&array) [10]  = ptrs;
   //array是數組的引用, 該數組是含有10個指針的數組

2.3 數組名a、數組名取地址&a、數組首元素地址&a[0]、指向數組首元素的指針*p

int main()
{
	int a[] = { 0,1,2,3,4 };
	printf("%x\n", a);
	printf("%x\n", &a);
	printf("%x\n", &a[0]);

	int *p = &a[0];
	
	decltype(a) t;
	decltype(&a) tt;

	cout << p << endl;
	printf("%x,%x\n", a + 1, p + 1);
	printf("%x\n", &a + 1);

	cout << sizeof(a) << " " << sizeof(&a) << endl;
}

輸出

在這裡插入圖片描述

  • a既然是種特殊的指針,那麼其打印時就會是存的地址。
  • &a的類型是int(*)[5](讀法從小括號裡往外,首先是指針,然後是大小為5的數組,然後數組元素類型是int),從局部變量中看到其類型也可寫成int[5] *:即指向大小為5的int數組的指針。由於數組名沒有內存分配空間
  • &a[0]就是取一個int對象的地址,這個int對象是數組首元素。綜上所述,就造成瞭a &a &a[0]三者打印出來的地址是一樣的。
  • p,指向數組首元素的指針。
  • a + 1,p + 1都是按照元素大小的字節數(4字節),增加4。
  • &a + 1,前面說瞭 &a的類型是指向大小為5的int數組的指針,大小為5的int數組所占字節數為20,所以&a + 1就應該增加20。
  • sizeof(a)為20,因為數組總的字節大小為20。
  • sizeof(&a)為4,因為&a是一種指針,指針在32位系統中占4字節。

2.4 對數組名以及取值符&的理解

數組中每個元素都是對象,即占有特定類型的內存空間。(對象,占有一塊數組類型的內存空間。因為對象是指一塊能存儲數據並且具有某種類型的內存空間。)

數組名可以轉化為這個數組對象的首個元素的地址。

這裡我們不去討論一維數組,直接從二維說起。所謂二維數組也是數組,隻不過它的元素也是一個數組。

首先我們寫一個二維數組留作使用

#include<iostream>
using namespace std;
int a[][10]={
    {1,2,3,4,5,5,6,7,8,8},
    {10,12,32,42,51,15,16,71,121,18}
};

簡單說明一下數組:數組a 是包含2個元素的數組,每個元素是一個包含10個 int 的數組。
既然說到數組名是其首個對象的地址那麼來驗證一下,測試數組名,以及對數組名求地址:

void test01(){
    cout << (long long)a << endl;         // 140273290059808
    cout << (long long)(a+1) << endl;     // 140273290059848
    // 相差40個字節
   }

(用long long 型一眼就能看出是40個字節)

aa + 1 正好相差40個字節,說明:

(1)數組名a 是(首元素{1,2,3,4,5,5,6,7,8,8})這一整行對象的地址,即首元素地址;

(2)所以在a+1偏移瞭一個元素大小——40字節。

void test01(){
    cout << (long long)&a << endl;        // 140273290059808
    cout << (long long)(&a+1) << endl;    // 140273290059888
    // 相差80個字節
   }

&a&a + 1 正好相差80個字節,說明:

(1)取址符取得是整個對象的地址,&a 是對二維數組求址,針對的是整個對象;

(2) &a+1 偏移一位就變成瞭整個二維數組的尾地址,c++中的尾地址是對象所在地址的下一位。&a+1 正好比 a 多瞭 80 個字節。

在上面也提到數組名會自動轉換成一個特殊指針(兩個表格當中的總結),接下來將理解這個指針到底是什麼?

從指針解引用方面理解:

void test03(){
    cout << *a << endl;     // 0x7f051ce02020
    //為瞭驗證,我們偏移一下
    cout << *(a + 1) << endl; // 0x7f051ce02048
    // 正好相差40個字節
}

*a 把數組名解引用之後是首元素(因為數組名是指向首元素的特殊指針),而首元素也是一個有10個元素的數組,現在 *a 是代表這個對象,輸出它就是此數組的首元素——1 的地址.。

cout << *(*a) << endl; //1    **a 即可	

第二層解掉:*(*a) 自然就是第一個 int 型的元素。

cout << *a[0] << endl; //   1

因為指針指向數組對象時,可以用下標訪問下一個位置,又 a 是指針指向瞭數組,下一個偏移為 0,即 * a = * (a + 0)

// cout << (a[0])[0] << endl; 
 cout << a[0][0] << endl;//   1

基於上述, *a 也就是a[0],也會自動轉化為指向自己的首個對象(10個元素的第一個元素的位置)的指針。所以 a[0] 可以用下標訪問數組對象(10個元素)內其他元素:a[0][0] == 1

我們多搞幾個案例:

 // 要是訪問當前行的下一個元素呢?將這個首地址
    cout << *(*a + 1) << endl;// 2 即 *((*a) + 1) 
    // 請註意這裡的指針是 (*a),
    cout << (*a)[1] << endl; //    2
    // 同理(*a)相當於*(a + 0) 即a[0] 
    cout << a[0][1] << endl; //2
    
    // 如果訪問下一行呢?
    cout << **(a+1) << endl;
    cout << *a[1] << endl; 
    cout << a[1][0] << endl; // *(a[1] + 0)
    
    // 第二行第二個元素呢?
    cout << *(*(a+1) +1 ) << endl;
    cout << *(a[1] +1) << endl;
    cout << a[1][1] << endl;

查看數組名類型理解

cout << typeid(*a).name()<< endl;  // A10_i
cout << typeid(&a[0]).name()<< endl; // PA10_i

A10_i :是由10個 int 組成數組
PA10_i :是一個指針類型, 指向一個數組對象,這個數組對象有10個int型的對象。編譯器會識別為int(*)[10]

cout << typeid(a).name()<< endl; // A2_A10_i
cout << typeid(&a).name()<< endl; // PA2_A10_i	

A2_A10_i:由多維數組是數組的數組。A表示這個是數組類型2表示是兩個對象組成的數組,每個對象(A10_i)是由有10個對象的數組,這10個對象是int型的

PA2_A10_i:是一個指針類型, 指向一個數組對象這個數組對象有2個數組型的對象。編譯器會識別為int(*)[2][10]

三、字符數組數組名

c++對待字符數組名不會輸出其地址,會直接輸出字符

#include <iostream>
using namespace std;
 
int main()
{
	//int a[5]={1,2,34,4,5};   //如果不是char型,cout<<"a="<<a<<endl;
	// 輸出的為int數組首地址。不會輸出數組中的值。
	char a[5]="aaaa";         //cout重載瞭char[],可以輸出整個字符串數組
	cout<<"a="<<a<<endl;
	return 0;
}

詳細參考這篇博客

到此這篇關於詳解C++數組和數組名問題(指針、解引用)的文章就介紹到這瞭,更多相關C++數組和數組名內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: