主頁 > 軟體設計 > (c語言)-- 深度剖析指標和陣列(上)

(c語言)-- 深度剖析指標和陣列(上)

2021-10-23 09:00:27 軟體設計

文章目錄

  • 一、前言
  • 二、指標
    • (一)何為指標?
    • (二)為什么要有指標?
    • (三)指標的記憶體布局
    • (四)指標解參考
    • (五)指標運算
      • 1、指標+-整數
      • 2、指標-指標
      • 3、指標的關系運算
    • (六)野指標
      • 1、野指標的成因
      • 2、如何規避野指標
  • 三、陣列
    • (一)陣列的概念
    • (二)一維陣列
      • 1、一維陣列的創建和初始化
      • 2、一維陣列的使用
      • 3、一維陣列的記憶體布局
      • 4、理解&a[0]和&a的區別
      • 5、陣列名a做為左值和右值的區別
    • (三)二維陣列
      • 1、二維陣列的創建和初始化
      • 2、二維陣列的使用
      • 3、二維陣列的記憶體布局
      • 4、理解&a[0]和&a的區別
      • 5、陣列名a做為左值和右值的區別
  • 四、指標與陣列的關系
    • (一)以指標的形式訪問和以陣列的形式訪問

一、前言

??本篇文章3萬5千字+,也是本人撰寫博客的第二篇,
??這篇文章主要針對指標和陣列的深入理解,對有簡單了解過指標和陣列的讀者閱讀起來體驗效果更好,雖不敢說全面闡述了指標和陣列,但是也是比較詳細的闡述了指標和陣列一些相關的底層性問題,這些問題可能是我們平時在學習c語言這塊內容時不容易關注到的,我都較詳細的敘述出來,希望各位讀者能夠耐心讀完,
??我也希望通過這篇文章,會讓讀者對指標和陣列產生更深一層的體驗,不僅僅是停留在簡單指標的定義和使用,陣列的定義和使用層次上,會讓讀者您對指標和陣列的記憶體布局,一些概念的深入理解的一個講解
??最后,還請您能夠多支持,多評論,互關互贊,謝謝!

二、指標

(一)何為指標?

帶著幾個問題來了解何為指標?

  • 有沒有指標變數這一概念?
  • 指標和指標變數是一個概念嗎?
  • 指標和指標變數又有何不同?我們口語中的"定義一個指標"究竟是什么意思?我們該如何理解這種說法?

??針對上述問題,我們先從究竟如何理解編址開始講起!
??首先,必須理解,計算機內是有很多的硬體單元,而硬體單元是要互相協同作業的,所謂的協同,至少相互之間要能夠進行資料通信傳遞,
??但是硬體與硬體之間是互相獨立的,那么如何通信呢?答案很簡單,用"線"連起來,
??在c語言中,我們程式的執行是先將代碼加載進記憶體中,然后通過CPU的讀取和寫入來實作程式的功能,在這程序中,CPU和記憶體之間是需要進行大量資料的互動,所以,兩者必須也用線連起來,
如下圖所示:
在這里插入圖片描述
??不過,我們今天關心一組線,叫做地址總線,
??CPU訪問記憶體中的某個位元組空間,必須知道這個位元組空間在記憶體的什么位置,而因為記憶體中位元組很多,所以需要給記憶體進行編址,(就如同宿舍很多,需要給宿舍編號一樣)
??計算機中的編址,并不是把每個位元組的地址記錄下來,而是通過硬體設計完成的,
??就像吉他,它并沒有花上任何空間來標識”do、re、mi、fa、so“這樣的資訊,但演奏者照樣能夠準確找到每一個琴弦的每一個位置,這是為何?因為制造商已經在樂器硬體層面上設計好了,并且所有的演奏者都知道,本質是一種約定出來的共識!
??硬體編址也是如此,我們可以簡單理解,32位機器有32根地址總線,每根線只有兩態,表示0,1【電脈沖有無】,那么一根線,就能表示2種含義,2根線就能表示4種含義,依次類推,32根地址線,就能表示2^32種含義,每一種含義都代表一個地址,
??地址資訊被下達給記憶體,在記憶體內部,就可以找到該地址對應的資料,將資料在通過資料總線傳入CPU內暫存器,實作資料的互動,
因此,記憶體當中的編址并不需要開辟空間為其存盤,而是通過硬體電路的方式對記憶體進行編制的,也就是說,我們記憶體的那些地址值,高地址到低地址的排序等這些都是硬體中規定好的,我們只需要遵守它的規定,正確使用規定就好,而我們所說的地址,又稱之為"指標",

注意:指標就是地址!
地址的本質是資料,且地址具有指向性的作用,
因此,指標又可以理解成“具有指向性的數字”,
在這里插入圖片描述
??在VS2017集成開發環境中,通過按下鍵盤F10進入程式除錯階段,打開記憶體,就可看到地址資料,從記憶體中可看到,地址(記憶體單元)從上往下依次遞增;地址的位元組數為4個位元組,
在liunx平臺下,地址(記憶體單元)從下往上依次遞減,

??指標的本質是資料,而資料可以被保存在變數空間里面,因此,保存指標(地址)資料的變數就稱之為指標變數

??嚴格意義上來講,指標和指標變數是兩個不同的概念,
??指標就是地址值,而指標變數是c語言規定變數形式的變數,即要在特定區域(堆疊)開辟空間,該變數可以用來保存地址資料,即保存指標,還可以被用來取地址,
??但是,我們經常在口語化表達的時候,又常將這兩個概念混合,并且在一些書籍中,指標和指標變數不進行區別的,這是為什么?
??要解答這個問題,我們先來看一段代碼:
在這里插入圖片描述
如何看待上面這段代碼中a的變數?兩者的a一樣嗎?

答案是不一樣,

??我們知道,定義一個變數,本質就是在堆疊中根據型別開辟空間,對于開辟的這個空間,我們現在從不同的兩個層面來理解它,
??第一,有了空間,就必須具有地址來標識這塊空間,以方便CPU進行尋址;
??第二,有了空間,就可以把資料保存進來,
??所以,我們先討論變數的空間和內容兩個概念,
??第一個a做為變數來存放資料10,此時a做為空間來使用,代表變數屬性;一般情況下,把使用空間的概念稱之為左值
??第二個a做為資料保存進變數b中,此時的a取變數的內容來使用,代表資料屬性;一般情況下,把使用內容的概念稱之為右值

結論:任何一個變數名,在不同的場合中,代表不同的含義!

  1. 作為變數屬性時,表示空間(可以存放資料的),一般格式表示為在賦值符號“=”的左邊,稱左值;
  2. 作為資料屬性時,表示內容,格式一般表示為在賦值符號“=”的右邊,稱右值,

在這里插入圖片描述
??再來看這段代碼,兩個p的含義一樣嗎?
??顯示,經過上面的探討,這里的p使我們意識到,指標變數p既可以充當左值,又可以充當右值;一旦充當右值,就取變數里面的內容來使用,表示地址,而地址就是指標,故容易混淆,

結論:指標就是地址,指標變數是用來存放指標的,指標是唯一標識一塊地址空間的,

(二)為什么要有指標?

結論:為了提高CPU在記憶體中尋址的效率,

舉一個例子來理解上述結論,
??假設有一所學校,該學校宿舍樓為 5層,每層20間,每間住8人,其中有一棟宿舍樓年久失修,假設我是一個包工頭,負責了這棟宿舍樓的重新裝修作業,經過兩個月的動工,完成了裝修作業,但是,由于我的疏忽,沒有給這棟宿舍樓沒有裝上樓牌號,每間宿舍沒有裝上門牌號,
??到了第二天,我帶著張三和李四來到這棟宿舍樓下,這時我蒙上兩人的眼睛并分別帶到不同樓層的兩間宿舍,然后讓張三帶電話給李四,讓李四過來找他,這時候的李四因為這棟宿舍樓沒有樓牌號和門牌號,所以只能通過一間一間的找,費時又費力,
??接下來,我立即裝好樓牌號和門牌號,又進行同樣的實驗,這個時候的李四通過樓牌號和門牌號找到了張三,
??故事到此結束,提問:為什么現實生活中要給我們的樓層裝上樓牌號和門牌號,裝上的意義何在?
??主要是為了提高查找效率!!!!一間一間查找相當于以遍歷的方式去找,很費時間,效率又低,

??剛舉的例子中,提到了宿舍樓,每間宿舍以及每間宿舍的床位數,類比一下,宿舍樓相當于計算中的記憶體訪問記憶體的基本單元是位元組,而一個位元組,由8個位元位組成,所以一個位元組相當于一間宿舍,宿舍中的每一張床位相當于一個位元位,
我作為包工頭叫李四去宿舍樓找到張三的具體位置,需要這棟宿舍樓的樓牌號和門牌號;對應的,CPU要在記憶體中尋址時,如果沒有給記憶體單元進行標記,那么CPU要找到記憶體單元位置,效率是很低的,因此,為了提高效率,我們給記憶體單元進行了編號,這些編號被稱為記憶體單元的地址,地址就叫做指標,

注意幾個問題:
1、 什么是記憶體?
??記憶體就是電腦上特別重要的存盤器,計算機中所有程式的運行都是在記憶體上進行的,所以為了有效的使用記憶體,就把記憶體劃分成一個個小的記憶體單元,每個記憶體單元的大小是1個位元組,為了能夠有效的訪問到記憶體的每個單元,就給記憶體單元進行了編號,這些編號被稱為記憶體單元的地址,

2、 記憶體如何編址?
??經過仔細的計算和權衡我們發現一個位元組給一個對應的地址是比較合適的,
??對于32位的機器,我們簡單認為有32根地址線,那么假設每根地址線在尋址的是產生一個電信號正電/負電(1或者0),那么32根地址線產生的地址就會是:
在這里插入圖片描述
??這里就是2^32次個地址,
??每個地址標識一個位元組(記憶體單元),那么我們就可以給出(2^32 Byte <–> 2^32 / 1024 KB <–>2^32 / 1024 / 1024 MB <–> 2^32 / 1024 / 1024 / 1024 GB <–> 4GB)4G的空間進行編址,
??同樣的方法,64位機器,如果給出64根地址線,這里就有2^64次個地址,
??每個地址標識一個位元組(記憶體單元),那么我們就可以給出(2^64 Byte <–> 2^64 / 1024 KB <–>2^64 / 1024 / 1024 MB <–> 2^64 / 1024 / 1024 / 1024 GB <–> 8GB)8G的空間進行編址,
在這里插入圖片描述
??在32位的機器上,地址是32個0或者1組成二進制序列,那地址就得用4個位元組的空間來存盤,所以一個指標變數的大小就應該是4個位元組,
??那如果在64位機器上,如果有64個地址線,那一個指標變數的大小是8個位元組,才能存放一個地址,

3、記憶體的最小單元是多大?
??根據前面的分析,可知記憶體的最小單元為一個位元組,

結論:指標的大小在32位平臺是4個位元組,在64位平臺是8個位元組,
補充一個額外知識:我們一般在挑選電腦的時候,會看重電腦的一個性能指標,就是電腦的記憶體,一般有4G或者8G;記憶體越大,對電腦本身有什么好處?大多數人都只知道選越大記憶體越好,就是不知道為什么,我們電腦要運行各種軟體(程式),首先要先把程式加載進記憶體中才能運行,此時電腦的記憶體越大,你同一時間開啟的軟體數量就可以更多,從而不會覺得卡頓,

(三)指標的記憶體布局

如何畫出正確的指標指向圖?
在這里插入圖片描述

1、這里定義了幾個變數?在哪里定義的?

上述代碼,定義了兩個變數,分別是整型變數a和整形指標變數p;在堆疊上定義的,
我們知道,所謂的定義變數,其本質就是在記憶體中開辟空間,開辟大小為變數型別的位元組數;記憶體的分布的情況在前面有詳談過,就是將記憶體分割為2^32塊記憶體單元,每個記憶體單元占1個位元組;CPU訪問記憶體時需要依靠記憶體單元編址好后的地址進行訪問;整型變數和指標變數占4個位元組,也就是定義整型變數時,需要在記憶體中開辟4個位元組數的空間,定義整型指標變數時也是在記憶體中開辟4個位元組數的空間,

2、一個整形,有4個位元組,那么應該有4個地址!那么&a取了哪一個地址?那么如何全部訪問這4個位元組呢?
在這里插入圖片描述
除錯后可知,&a的地址值是變數a在記憶體中開辟4個位元組數的最低地址(起始地址),如何訪問這4個位元組,是通過找到該位元組的起始地址,然后根據型別連續訪問4個位元組,
結論:對變數取地址,不用考慮該變數的型別,該地址是變數在記憶體中開辟眾多位元組數的最低地址,

3、如何正確的畫出指標指向圖
在這里插入圖片描述
結論: 指標永遠指向變數在記憶體中開辟眾多位元組數的最低地址,

(四)指標解參考

#include <stdio.h>
#include <windows.h>

int main() {
	int a = 10;
	int *p = &a;
	int b = *p;
	*p = 20;
	system("pause");
	return 0;
}

上述兩種解參考,該怎么理解?

??解參考一句話概括就是把指標變數里面的內容所代表的地址所對應的變數a給拿出來,即*p最終訪問的就是變數a,而此時第一個*p在運算式的右側,取得是*p的右值,也就是變數a的右值,*p就等價于10;第二個*p解參考,訪問p變數,找到p變數里面對應的內容,即變數a的地址,該地址指向a,接著訪問變數a,訪問的是*p指向的目標所對應的空間,代表的是左值,

*p完整理解是,取出p中的地址,訪問該地址指向的記憶體單元,(空間或者內容)(其實通過指標變數訪問,本質是一種間接尋址的方式)
*結論:在同型別情況下,對指標解參考,代表指標所指向的目標,

我們再來深入理解一下:

int a = 10;
int *p = &a;

??*是一個運算子,那么*p本質上可以理解成運算式;當我們進行解參考(*p)的時候,使用的是p的左值還是右值?
在這里插入圖片描述
??將整型數字0強制型別轉換成double指標,此時0當作指標看待,對其解參考表示訪問0x00000000地址,數字10.0寫入0x00000000地址發生訪問沖突,
在這里插入圖片描述
??結合上述代碼可知,最后編譯結束后看到的都是“寫入位置0x00000000時發生訪問沖突”,第一種是直接訪問0x00000000地址,將10.0寫入0x00000000地址,最終因地址解參考指向的目標找不到,直接報錯;第二種指標變數里面保存的是0x00000000;解參考時也是將10.0寫0x00000000地址發生訪問沖突,也就是兩種情況是等價的,即對指標變數p解參考,使用的是指標變數p里面的內容,即右值

注意:int *p = NULL和*p = NULL的區別

//指標變數p和解參考p(*p)兩者的含義有何不同?
int main(){
    int a = 10;
	int *p = &a;
	*p = NULL;
	system("pause");
	return 0;
}

我們先來分析這條代碼:

	int *p = NULL;

??我們通過VS2017集成開發環境的除錯功能可以看到p的值為0x00000000,這行代碼的意思是:定義一個指標變數p,其指向的記憶體里面保存的是int型別的資料;在定義變數p的同時把p的值設定為0x00000000,而不是把*p的值設定為0x00000000,這個程序叫初始化,是在編譯的時候進行的,
??明白了什么是初始化之后,再看下面的代碼:

	int a = 10;
	int *p = &a;
	*p = NULL;

??同樣,我們在VS2017集成開發環境上除錯這三行代碼,
??第一行:定義一個變數a并初始化,值為10;
??第二行:定義一個指標變數p,其指向的記憶體里面保存的是int型別的資料;在定義變數p的同時把p的值設定為a的地址;
??第三行:給*p賦值為NULL,即給p指向的記憶體賦值為NULL;而p本身的值,即保存變數a的地址并沒有改變,

#define NULL (void*)0

注意:NULL是一個宏,被宏定義為0

//最后,我們看看這份有意思的代碼
#include<stdio.h>
#include <windows.h>
int main()
{
int *p = NULL;
p = (int*)&p;//這里的強制型別轉換主要是確定左右兩邊的型別一致,
             //位元組一樣,不代表型別一致;
             //強制型別轉換的本質是改變資料的看待方式,其結構內容是完全一致的,
*p = 10;
p = 20;
system("pause");
return 0;
}

??如何理解上述代碼?上述代碼的本質是讓我們深刻的不斷強化指標的指向和指標本身兩個概念,

??第一行:定義一個指標變數p,其指向的記憶體保存的是int型別的資料,在定義變數p的同時把p的值設定為NULL(0x00000000);
在這里插入圖片描述
??第二行:p是一個變數,既然是變數,就得在記憶體中開辟空間,只要有空間,那么p就一定有地址,其地址值就是&p,其型別為二級指標int**,通過強制型別轉換成一級指標int*,保存進指標變數p中,其不再指向NULL,而是指向&p;換句話說,就是定義一個指標,自己指向自己;
在這里插入圖片描述
??第三行:對p解參考,包含兩層意思,第一種理解,解參考的操作本質是訪問p的右值,相當于p的內容,而p的內容放的就是p的地址,換言之,就是把10直接放到變數p里面;第二種理解,對指標解參考,代表指標所指向的目標,當前的p指向的就是變數p(即自身),所以解參考后就是變數p;
在這里插入圖片描述
??第四行:p是一個單獨的變數,此時它有左值和右值,這里充當左值,相當于把20放到變數p的空間里面,此時表示指標變數p指向0x00000014地址對應的記憶體空間,
在這里插入圖片描述

(五)指標運算

1、指標±整數

講解指標±整數運算前,我們先來談談指標變數型別?
我們知道,只要是變數,就會有對應的型別;

int main() {
	char a = 0;          //字符型
	short b = 0;         //短整型 
	int c = 0;           //整型
	long d = 0;          //長整型
	float e = 0.0;       //單精度浮點型
	double f = 0.0;      //雙精度浮點型
	system("pause");
	return 0;
}

??那么指標變數也是變數,也得有對應得型別,怎樣定義指標變數相對應的型別?
??指標變數的型別主要取決于該指標變數里面保存的指標對其解參考指向的目標是什么型別,該指標變數就是那種型別,

int main() {
	int a = 0;
	int *p = &a;
	system("pause");
	return 0;
}

??定義整型變數a,取出變數a的地址,將其存放進指標變數p中,由于該指標變數p存放的地址解參考后是一個整型變數,故指標變數p的型別為整型,叫整型指標變數

int main() {
	char *pc = NULL;
	short *ps = NULL;
	int *pi = NULL;
	long *pl = NULL;
	float *pf = NULL;
	double *pd = NULL;
	system("pause");
	return 0;
}

??這里可以看到,指標變數的定義方式是: type + * , 其實: char* 型別的指標變數是為了存放 char 型別變數的地址, short* 型別的指標變數是為了存放 short 型別變數的地址, int* 型別的指標變數是為了存放int 型別變數的地址,
??那指標型別的意義是什么?
在這里插入圖片描述
??上述代碼中,我們取出整型變數a的地址存放進整型指標變數pa中,列印整型變數a的地址和整形指標變數pa的內容,可看到兩者的內容是一致的,都是整型變數a的地址,為0x00D3F95C,這時將整型指標變數(做為右值,取其內容)+1操作并列印,列印的內容為0x00D3F960,變成了在原來的內容基礎上加4;
??我們取出整型變數a的地址,為整型指標,將其強制轉換成字符指標,并存放進字符指標變數pc中,此時列印字符指標變數pc的內容為0x00D3F95C,這時將字符指標變數(做為右值,取其內容)+1操作并列印,列印的內容為0x00D3F95D,變成了在原來的內容基礎上加1,
??我們發現,整型指標變數pa和字符指標變數pc保存的內容都是變數a取地址后的整型指標0x00D3F95C,然后分別對兩個指標變數(做為右值,取其內容)+1操作,得到了不一樣的結果?原來,我們對指標變數進行+1操作時,其本質就是將指標變數保存的指標往高地址進行移動,移動的距離(步長)取決于該指標變數是什么型別,如果該指標變數是整型指標變數,+1操作時,往高地址移動整型個位元組數,即移動4個位元組,
同理,我們對指標變數進行-1操作時,其本質就是將指標變數保存的指標往低地址進行移動,移動的距離(步長)取決于該指標變數是什么型別,如果該指標變數是整型指標變數,-1操作時,往低地址移動整型個位元組數,即移動4個位元組,

總結:指標變數的型別決定了指標變數(做為右值,取其內容)±n(整數)時,向低地址或者向高地址走n步有多大(距離),

//理解以下程式
int main()
{
#define N_VALUES 5		//定義宏,宏的本質是在編譯期間進行文本替換;
	float values[N_VALUES] = {0.0};		//定義單精度浮點型陣列,元素個數為5;
	float *pl = NULL;		//定義單精度浮點型指標變數;
	//指標+-整數;指標的關系運算
	for (pl = &values[0]; pl < &values[N_VALUES];)
	{
		*pl++ = 0;
	}
	system("pause");
	return  0;
}

上述代碼是通過指標的形式對其陣列進行初始化;
在這里插入圖片描述

2、指標-指標

結論:存在兩個指標指向同一陣列,兩指標進行相減操作,結果為兩指標包含的陣列元素的個數,
在這里插入圖片描述
在這里插入圖片描述
注意:低指標 - 高指標,結果是一個負值;高地址 - 低地址,結果為一個正值,

int main()
{
	//庫函式strlen()求解陣列的長度,
	char arr[] = "this is a array";
	int len = strlen(arr);
	printf("%d\n",len);//15
	system("pause");
	return  0;
}

自定義求解陣列長度的函式,

#include <stdio.h>
#include <windows.h>

int my_strlen(char *s)
{
	char *p = s;
	while (*p != '\0')
		p++;
	return p - s;
}
int main()
{
	char arr[] = "this is a array";
	int len = my_strlen(arr);
	printf("%d\n",len);
	system("pause");
	return  0;
}

3、指標的關系運算

標準規定:
??允許指向陣列元素的指標與指向陣列最后一個元素后面的那個記憶體位置的指標比較,但是不允許與指向第一個元素之前的那個記憶體位置的指標進行比較,

(六)野指標

1、野指標的成因

??所謂的野指標,就是指標指向的位置是不可知的,(隨機的、不正確的、沒有明確限制的)

野指標的成因有以下三種:

//指標未初始化
#include <stdio.h>
#include <windows.h>

int main(){
	int *p;		//區域變數指標未初始化,默認為隨機值
	//int *p = NULL;
	*p = 20;
	system("pause");
	return 0;
}

在這里插入圖片描述

//指標越界訪問
#include <stdio.h>
#include <windows.h>

int main(){
	int arr[10] = {0};
	int *p = arr;
	int i = 0;
	for(; i <= 10; i++){
		*p++ = i;	//當指標指向的范圍超出陣列arr的范圍時,p就是野指標
	}
	system("pause");
	return 0;
}
#include <stdio.h>
#include <windows.h>

int main()
{
	int *ptr = NULL;
	ptr = (int*)malloc(10 * sizeof(int));	//動態開辟40個位元組的記憶體空間;
	if (NULL != ptr)	//判斷ptr指標是否為空;
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(ptr + i) = 0;
		}
	}
	free(ptr);	//釋放ptr所指向的動態記憶體
	//ptr = NULL;	//將指標置為空指標;
	return 0;
}

2、如何規避野指標

?野指標的出現,會使我們我的程式發生錯誤,規避野指標成為我們不可忽略的問題,規避野指標的方式有以下幾點:
1、指標初始化;
?定義指標變數時,如果沒有給其附上對應的地址,應該先給指標變數附上空指標NULL,
2、小心指標越界;
3、指標指向的空間釋放后立即置為NULL;
?動態開辟記憶體空間,使用完畢釋放空間后,要將指標置為空指標NULL,否則,這個指標就變成了野指標,
4、指標使用前檢查有效性,
?使用函式時,如果傳入的引數有指標時,應該要在函式的陳述句塊開頭檢查指標的有效性,如果指標為一個空指標,應當立即退出函式,

三、陣列

(一)陣列的概念

陣列:相同型別的元素的集合,
在這里插入圖片描述

代碼格式:type array_name[const] = {date,date,date,… };

  • type 是指陣列的元素型別;
  • const 是一個常量運算式,用來指定陣列的大小;這個const可以是字面值,也可以是宏定義的常量;
  • date:是陣列保存的元素,

含義:陣列名與陣列下標參考運算子[ ]結合,我們表示為陣列,即“陣列名[ ]”,在陣列名前面加型別,型別取決陣列中保存的元素的型別,陣列下標參考運算子[ ]中添加常數運算式,表示陣列包含元素的個數,

??我們口頭話如何描述一個陣列,例如:int array[10] = {0};
我們可以這樣說,array是一個陣列,該陣列有10個元素,每個元素的型別是int,初始值都為0,

(二)一維陣列

1、一維陣列的創建和初始化

一維陣列的創建
??根據陣列元素個數的表示方式不同,陣列的創建,有以下兩個格式:

//格式一,陣列常量運算式直接賦字面值:
char array1[10];//創建一個陣列,里面包含10個char型別的元素;
short array2[10]; //創建一個陣列,里面包含10個short型別的元素;
int array3[10]; //創建一個陣列,里面包含10個int型別的元素;
long array4[10]; //創建一個陣列,里面包含10個long型別的元素;
float array[10]; //創建一個陣列,里面包含10個float型別的元素;
double array[10]; //創建一個陣列,里面包含10個double型別的元素;
//格式二,陣列常量運算式用宏定義:
#define CONST 10
char array1[ CONST];//創建一個陣列,里面包含10個char型別的元素;
short array2[ CONST]; //創建一個陣列,里面包含10個short型別的元素;
int array3[ CONST]; //創建一個陣列,里面包含10個int型別的元素;
long array4[ CONST]; //創建一個陣列,里面包含10個long型別的元素;
float array[ CONST]; //創建一個陣列,里面包含10個float型別的元素;
double array[ CONST]; //創建一個陣列,里面包含10個double型別的元素;

??上述兩種方式都是創建一個包含10個元素的陣列;但是,用宏定義作為常量運算式來創建陣列較格式一靈活且實用;特別是在大型專案中,創建陣列用格式二在后期維護起來特別方便,

注意:

  1. 陣列創建,[ ]中要給一個常量才可以,不能使用變數
  2. 陣列可創建包含任意型別的元素,以上創建的陣列的元素型別都是基本資料型別,陣列還可以創建元素型別為指標,結構體,陣列…等,

一維陣列的初始化

一維陣列的初始化:在創建陣列的同時給陣列的內容一些合理初始值(初始化),

//我們以上面創建陣列的一種格式來對其陣列進行初始化,代碼如下:
short array2[10] = {0,1,2,3,4,5,6,7,8,9}; //創建一個陣列,里面包含10個short型別的元素;
int array3[10] = {0,1,2,3,4,5,6,7,8,9}; //創建一個陣列,里面包含10個int型別的元素;
long array4[10] = {0,1,2,3,4,5,6,7,8,9}; //創建一個陣列,里面包含10個long型別的元素;
float array[10] = {0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0}; //創建一個陣列,里面包含10個float型別的元素;
double array[10] = {0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0}; ; //創建一個陣列,里面包含10個double型別的元素;

??給陣列進行初始化,就是根據陣列的型別和常量運算式的大小,在賦號"="右邊,在花括號{ }里面附上相對應的值,值與值之間用逗號隔開,
??這里有幾個小細節需要注意:

??第一,在函式中創建陣列的同時必須進行初始化,不能只創建而不初始化,因為在函式中,任何一個變數的定義不進行初始化,系統會默認附上一個隨機值,并且在VS運行環境編譯下發出警告!
在這里插入圖片描述

??第二,陣列創建并初始化時,可以不完全初始化,在花括號里面附上一個0即可;但是此種情況要求創建含有不少于1個元素的陣列時,常量運算式不能省略;最終創建出一個包含CONST個元素,并且每個元素的初始值為0的陣列,代碼如下:

#include <stdio.h>
#include <windows.h>
define CONST 10		//宏定義一維陣列的元素個數;
int main(){
	char array[CONST] = {0}; //創建陣列時不完全初始化;
	system("pause");
	return 0;
}

在這里插入圖片描述

??第三,陣列創建時,如果省略常量運算式,那么就得進行完全初始化,陣列的元素個數根據初始化的內容來確定,代碼如下:

#include <stdio.h>
#include <windows.h>

int main(){
	int array[] = {0,1,2,3,4,5,6,7,8,9};//創建了包含10個元素的陣列;
	system("pause");
	return 0;
}

在這里插入圖片描述
注意:但是對于下面的代碼要區分,記憶體中如何分配,

#include <stdio.h>
#include <windows.h>
//對陣列進行以下兩種形式的初始化,陣列分別包含了多少個元素?
int main(){
	char arr1[] = "abc";
	char arr2[3] = {'a','b','c'};
	system("psuse");
	return 0;
}

??對于陣列arr2,我們知道,arr2沒有省略常量運算式,故陣列在記憶體中開辟空間的數量(元素的個數)由常量運算式決定,因此該陣列包含了3個元素;
在這里插入圖片描述
??對于陣列arr1,由于陣列省略常量運算式,故陣列在記憶體中開辟空間的數量(元素的個數)由初始化的內容決定,初始化的內容是一個字串,而字串是保存在字串常量池中,只由作業系統保護,真正意義上不可被修改的,以’\0’作為結束標志,在記憶體空間中需要開辟空間來保存’\0’字符,因此,該陣列包含4個元素,
在這里插入圖片描述

2、一維陣列的使用

??我們創建好了一個陣列,接下來就是如何去使用它;陣列里面包含多個元素,使用它就是如何獲取到陣列里面的每個元素的值,
對于陣列的使用我們參考一個運算子:[ ],陣列下標參考運算子,它其實就是陣列訪問的運算子,代碼如下:

#include <stdio.h>
#include <windows.h>

int main()
{
	int arr[10] = {0};	//陣列的不完全初始化 
	int sz = sizeof(arr)/sizeof(arr[0]);	//計算陣列的元素個數;
	//對陣列內容賦值,陣列是使用下標來訪問的,下標從0開始,所以:
	int i = 0;//做下標;
	for(; i < sz; i++)
	{
		arr[i] = i;
	}
	//遍歷陣列的內容;
	for(; i < sz; ++i)
	{
		printf("%d ", arr[i]);
	}
	system("pause");
	return 0;
}

注意:

  1. 陣列中元素的訪問格式:陣列名[ 下標 ],其中,陣列的下標從0開始+1遞增;
  2. 陣列中每個元素都有屬于自己對應的下標,首元素對應的陣列下標為0,然后依次遞增,直到最后一個元素,
  3. 通過for回圈的方式依次列印出陣列中每個元素的值,我們稱這種方式為陣列的遍歷,

總結:

  1. 陣列是使用下標來訪問的,下標是從0開始,
  2. 陣列的大小可以通過計算得到,

3、一維陣列的記憶體布局

//我們先來看以下代碼:
#include <stdio.h>
#include <windows.h>

int main(){
	int a = 10;
	int b = 20;
	int c = 30;
	printf("%p\n", &a);
	printf("%p\n", &b);
	printf("%p\n", &c);
	system("pause");
	return 0;
}

在這里插入圖片描述
??從運行結果,我們發現,先定義的變數,地址是比較大的,后續依次減小,這是為什么呢?
??因為a,b,c都在main函式中定義,也就是在堆疊上開辟的臨時變數,而a先定義意味著,a先開辟空間,那么a就先入堆疊,所以a的地址最高;接著b開辟空間,那么b就后入堆疊,所以b的地址較a的地址小;接著c開辟空間,那么c就入堆疊,地址也就相對a和b都小,
在這里插入圖片描述

//接下來,我們看以下代碼
#include<stdio.h>
#include <windows.h>

#define N 10
int main()
{
int a[N] = { 0 };
for (int i = 0; i < N; i++){
printf("&a[%d]: %p\n",i, &a[i]);
}
system("pause");
return 0;
}

在這里插入圖片描述
??上述代碼中,創建的陣列由10個元素構成,其中,arr是在main函式中定義的陣列,所以arr中的10個元素也是在堆疊上開辟空間的,我們發現,陣列的地址排布是:&a[0] < &a[1] < &a[2] < … < &a[9];如果同上述代碼相比,那么肯定是a[0]先被開辟空間,即a[0]先入堆疊,那么肯定&a[0]地址最大啊,可是事實上并非如此!我們從運行結果看到,往后元素依次入堆疊,對應的地址依次增大,與我們前面的結論正好相反,這是為什么?
??因為陣列排布具有線性、連續、遞增!,在開辟空間的時候,不應該將陣列認為成一個個獨立的元素,應該整體給陣列開辟開辟空間,整體被釋放,
??陣列是整體申請空間的,然后將陣列當中地址最低的空間,作為a[0]元素,依次類推!
在這里插入圖片描述

注意:任何一個c語言當中的變數取地址時,取出來的地址值一定是此變數所開辟的眾多位元組當中的最小地址,

4、理解&a[0]和&a的區別

#include<stdio.h>
#include <windows.h>

int main()
{
	char *c = NULL;
	short *s = NULL;
	int *i = NULL;
	double *d = NULL;

	printf("%d\n", c);
	printf("%d\n\n", c + 1);

	printf("%d\n", s);
	printf("%d\n\n", s + 1);

	printf("%d\n", i);
	printf("%d\n\n", i + 1);

	printf("%d\n", d);
	printf("%d\n\n", d + 1);

	system("pause");
	return 0;
}

在這里插入圖片描述
結論:對指標+1,本質加上其所指向型別的大小,

#include<stdio.h>
#include <windows.h>

#define N 10

int main()
{
	int a[N] = { 0 };
	printf("%p\n", &a[0]);	//首元素地址
	printf("%p\n", &a[0] + 1);	//第二個元素的地址
	printf("%p\n", &a);	//陣列的地址;
	printf("%p\n", &a + 1);	//下一個陣列的地址;
	system("pause");
	return 0;
}

在這里插入圖片描述
??&a[0]:陣列名a分別和運算子&和運算子[]結合,我們知道,運算子[]的運算優先級比運算子&高,所以陣列名a先和運算子[]結合,此時表示首元素,在與運算子&結合表示首元素地址,就是陣列內第一個元素的地址;&a:陣列名與運算子&結合表示陣列的地址,就是陣列整體的地址,兩者在數字層面上的值是一樣的,主要差別是體現在型別上,我們知道兩者的資料都是地址,而地址就是指標,對此進行指標+1,即&a[0]+1和&a+1時;我們發現指標移動的步長不一樣,對&a[0]+1,指標移動的步長為該元素保存的資料的型別大小;對&a+1,指標移動的步長為該陣列中元素保存的資料型別大小乘上元素個數,
注意:為什么首元素地址和陣列的地址的值一樣?因為首元素的地址和陣列的地址在在記憶體開辟總多位元組中對應的最低位元組是重疊的!所以,地址資料值相等,
??因為型別不同,所以指標+1所對應的步長不一樣,

結論:陣列名使用的時候代表整個陣列只有兩種情況:

  1. &a:陣列的地址;
  2. sizeof(a):單獨使用使用陣列名,sizeof內部沒有出現任何的運算式,

5、陣列名a做為左值和右值的區別

??在前面中,我們從變數的開辟空間來理解左值和右值,現在我們簡單的來理解左值和右值,
??出現在賦值符“=”右邊的就是右值,出現在賦值符“=”左邊的就是左值,比如x=y;
??左值:編譯器認為x的含義是x所代表的地址,就是說我們平時所說的變數名,在編譯器看來,是一個地址,編譯器在一個特定的區域保存這個地址,我們完全不必考慮這個地址保存在哪里,
??右值:編譯器認為y的含義是y所代表的地址里面的內容,這個內容是什么,只有到運行才知道,

//陣列名充當右值時;
//陣列名可以做右值,代表陣列首元素的地址
//陣列名做右值,本質等價于&arr[0]
//demo1
#include<stdio.h>
#include <windows.h>
#define N 10
int main()
{
	char a[N] = { 0 };
	printf("%p\n",&a[0]);
	char *p = a;
	printf("%p\n", p);
	system("pause");
	return 0;
}

在這里插入圖片描述
??當陣列名a做為右值時其意義與&a[0]是一樣的,代表的是陣列的首元素地址,而不是陣列的首地址,證明方案是除錯代碼時時編譯器沒有任何報錯,就證明“char p = a”這條陳述句中左側的型別和右側的型別是兼容的,也就是此時的陣列名a并非其他型別,而是char型別,但是要注意,這僅僅是代表,并沒有一個地方來存盤這個地址,也就是說編譯器并沒有為陣列a分配一塊記憶體來存盤其地址,

結論:陣列名可以充當右值,代表首元素地址!

//陣列名a不可以做左值!
//能夠充當左值的,必須是有空間且可被修改的,a不可以整體使用,只能按照元素為單位進行使用,
#include<stdio.h>
#include <windows.h>
#define N 10
int main()
{
	int a[N] = { 0 };
	a = { 1, 2, 3, 4, 5 };//只能夠進行整體初始化,不能整體賦值,不能作為左值;
	system("pause");
	return 0;
}

在這里插入圖片描述
??a不能做為左值!編譯器認為陣列名做為左值代表的意思是a的首元素的首地址,而一個能夠充當左值的東西,一定必須有對應的空間,那按理說陣列名在記憶體中開辟一塊記憶體是一個總體,可是c語言語法不讓陣列進行一個整體的賦值操作,所以陣列名有對應的空間,
但是這個地址指向的一塊記憶體是一個總體,我們只能訪問陣列的某個元素,而無法把陣列當一個總體進行訪問,所以我們可以把a[i]當左值,而無法把a當左值,

結論:陣列名不可以充當左值!

(三)二維陣列

1、二維陣列的創建和初始化

二維陣列的創建

代碼格式:type array_name[const1][const2];

  • type 是指陣列的元素的型別;
  • const1:常量運算式1,用來指定二維陣列的大小,const1可以是字面值,也可以是宏定義的常量;
  • const2:常量運算式2,用來表示二維陣列的每個元素(即每個一維陣列)的大小,這個const1可以是字面值,也可以是宏定義的常量;
  • date:是陣列保存的元素,

??我們前面討論過,陣列里面可以存放任何資料,除了函式,所謂二維陣列,就是陣列里面存放的元素內容為一維陣列,方格本,我相信大家都見過,我們平時就可以把二維陣列假象成一個方格本,比如:

char array[3][4];

假象中的二維陣列的布局如下:
在這里插入圖片描述
??因此,創建二維陣列時,我們一般將常量運算式1稱之為二維資料的行,常量運算式2稱之為陣列的列;

??我們口頭話如何描述一個二維陣列,例如:char array[3][4];
我們可以這樣說,array是一個二維陣列,該二維陣列有3個元素,每個元素的型別是char [4](陣列),該陣列包含4個元素,每個元素的型別是char,

二維陣列的創建,同樣有兩種格式:

//格式一,常量運算式1和常量運算式2直接附字面值:
int array[5][4];

//格式二:常量運算式1和常量運算式2用宏定義:
#define ROW 5	//ROW表示二維陣列的行數;
#define COL 4		//COL表示二維陣列的列數;
int array[ROW][COL];

??上述兩種格式都是創建一個二維陣列,里面有5個元素,每個元素的型別是陣列,該陣列里面有4個元素;同樣推薦格式二來創建二維陣列;

二維陣列的初始化

//宏定義陣列的行數和列數
#define ROW 5
#define COL 4
//格式一,不完全初始化,即二維陣列的每個元素都是0;
int array[ROW][COL] = {0};

//格式二:不完全初始化,不夠初始化的元素值用0進行初始化;
int array[ROW][COL] = {1,2,3,4};
//等價于:
int array[ROW][COL] = {{1,2,3,4},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}};

//格式三:不完全初始化,不夠初始化的元素值用0進行初始化;
int array[ROW][COL] = {{1,2},{3,4},{5,6}};
//等價于:
int array[ROW][COL] = {{1,2,0,0},{3,4,0,0},{5,6,0,0},{0,0,0,0},{0,0,0,0}};

//格式四:完全初始化
int array[ROW][COL] = {{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4}};

在這里插入圖片描述

注意:
??1.二維陣列創建并初始化時,可以省略常量運算式1,不可省略常量運算式2;
在這里插入圖片描述
在這里插入圖片描述
結論:創建n維陣列,就會有n個常量運算式,其中,只有第一個常量運算式可以省略,其余常量運算式不可省略,
原因我將放到(c語言)-- 深度剖析指標和陣列(下)進行解釋,

2、二維陣列的使用

二維陣列的使用也是通過下標的方式,看代碼:

#include <stdio.h>
#include <windows.h>
//宏定義二維陣列的行和列
#define ROW 5	//行數
#define COL 4		//列數
int main(){
	int array[ROW][COL] = {0};		//創建二維陣列并初始化
	int i = 0;
	//元素重新賦值;
	for(;i < ROW; i++){
		int j = 0;
		for(; j < COL; j++){
			array[i][j] = i * 4 + j;
		}
	}
	//遍歷陣列;
	for(;i < ROW; i++){
		int j = 0;
		for(; j < COL; j++){
			printf("%d ",array[i][j]);
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

3、二維陣列的記憶體布局

??我們前面在表示二維陣列的布局時,都是采用矩陣形式,這也是大部分書中所畫的形式;但是,現在我們要深入討論二維陣列的記憶體布局,之前的只能稱之為示意圖,并非真的記憶體布局,可以想象一些問題,如果按照書中矩陣樣子畫二維陣列的話,那么三維陣列,四維陣列又該如何畫呢?
??我們看如下代碼,深入理解二維陣列在記憶體中的正確布局:

#include <stdio.h>
#include <windows.h>
int main()
{
	char a[3][4] = { 0 };	//創建二維陣列;
	for (int i = 0; i < 3; i++){	//遍歷二維陣列;
		for (int j = 0; j < 4; j++){
			printf("&a[%d][%d] : %p\n", i, j, &a[i][j]);
		}
	}
	system("pause");
	return 0;	
}

運行結果:
在這里插入圖片描述
結論:二維陣列在記憶體地址空間排布上,也是線性連續且遞增的,

??如何正確畫出二維陣列的記憶體布局圖:

//創建一個二維陣列并初始化
char a[3][4] = {0};

在這里插入圖片描述
??擴展一下,如果將二維陣列做為某一個陣列的元素,那么這個陣列就是一個三維陣列!
在這里插入圖片描述
??我們看到,將兩個二維陣列做為元素拼接在一起,就成為一個三維陣列char x[2][3][4],其中,三維陣列有兩個元素,每個元素的型別位為char[3][4],
??同樣的,如果將三維陣列做為元素拼接在一起,就成為一個四維陣列,

技巧:如何看待多維陣列中元素的型別?
陣列中元素的型別,就是去掉陣列名和第一個方括號[ ]及其里面的常量運算式,剩余的就是陣列中元素型別;陣列中第一個方括號代表的是該陣列包含的元素的個數,里面的常量運算式可以省略不寫,如果不寫,陣列的元素個數由初始化決定,
例如:
char x[2][3][4] = {0};
??是一個三維陣列,由?于第一個方括號中常量運算式為2,所以該陣列有2個元素,每個元素的型別就是去掉x[2],剩余的char [3][4]就是元素的型別;
char a[3][4] = {0};
??是一個二維陣列,由于第一個方括號中常量運算式為3,所以該陣列有3個元素,每個元素的型別就是去掉a[3],剩余的char [4]就是元素的型別;
char arr[4] = {0};
??是一個一維陣列,由于第一個方括號中常量運算式為4,所以該陣列有4個元素,每個元素的型別就是去掉arr[4],剩余的char就是元素的型別;

結論:所有的陣列,都可以看成”一維陣列“,

??我們通過編譯上述代碼知道,二維陣列列印出來的地址都是連續的,所以我們可以得到,所有維度的陣列,在空間排布都是線性、連續且遞增!
解釋:
??我們知道陣列具有相同資料元素型別的集合,其特征是,陣列中可以保存任意型別,而不單單只是保存基本資料型別,既然可以保存任意型別,那么陣列中可以保存陣列嗎?答案是可以!在理解上,我們甚至可以理解所有的陣列都可以當成"一維陣列"!
??就二維陣列來說,我們認為二維陣列,可以被看做“一維陣列”,只不過內部“元素”也是一維陣列,那么內部一維陣列在記憶體中布局是“線性連續且遞增”的,多個該一維陣列構成另一個“一維陣列”,那么整體便也是線性連續且遞增的
??這也就解釋了,上述地址為何是連續的,
??在強調一遍,我們認為:二維陣列可以被看做內部元素是一維陣列的一維陣列,

??由于二維陣列是線性連續且遞增的,故我們在遍歷二維陣列時還可以用指標的形式進行遍歷,

#include <stdio.h>
#include <windows.h>
int main()
{
	char a[3][4] = { 0 };
	char *p = (char*)a;		//這里的a代表首元素(是一個陣列!!!char [4])的地址,
	for (int i = 0; i < 3 * 4; i++) {
		//printf("%p\n", &p[i]);
		printf("下標[i]對應的地址:%p\n",i, p + i);
	}
	//用來對比
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 4; j++) {
			printf("&a[%d][%d] : %p\n", i, j, &a[i][j]);
		}
	}
	system("pause");
	return 0;
}

在這里插入圖片描述

注意:上述定義二維陣列a時,如果a充當右值,取其內容,表示首元素地址,這里的首元素地址型別是char [4],是一個陣列,

4、理解&a[0]和&a的區別

??我們在剖析一維陣列的時候,對于這兩種表示,我們已經給出結論,同樣的,對于二維陣列,甚至多維陣列,其結論都是一樣的,但是對于二維陣列,我們應該怎樣去理解透它,我們看如下代碼:

#include <stdio.h>
#include <windows.h>

int main() {
	char a[3][4] = { 0 };
	char (*p1)[3][4] = &a;
	char (*p2)[4] = &a[0];
	char (*p3)[4] = a;
	char *p4 = &a[0][0];

	printf("二維陣列的地址:\t\t%p\n", p1);
	printf("二維陣列的首元素地址:\t\t%p\n", p2);
	printf("二維陣列的首元素地址:\t\t%p\n", p3);
	printf("二維陣列的第一個元素地址:\t%p\n", p4);
	printf("===========================================\n");
	printf("二維陣列的地址+1:\t\t%p\n", p1 + 1);
	printf("二維陣列的首元素地址+1:\t%p\n", p2 + 1);
	printf("二維陣列首元素地址+1:\t\t%p\n", p3 + 1);
	printf("二維陣列第一個元素地址+1:\t%p\n", p4 + 1);

	system("pause");
	return 0;
}

運行結果:
在這里插入圖片描述
??對于任何一個多維陣列來說,陣列名使用的時候代表整個陣列只有兩種情況,一種是對陣列取地址,另一種是用sizeof關鍵字內部單獨使用陣列名求陣列的型別,其余情況,陣列名一律代表首元素地址,這個首元素,我們現在就要特別關注了,在一維陣列中,由于一維陣列內部包含的元素都是基本資料型別,所以首元素其實就是陣列的第一個元素;但是到了多維,我們以二維陣列為例,二維陣列的首元素是一個一維陣列,而二維陣列的第一個元素是二維陣列的首元素中的首元素,這是兩個不同的概念,這點要區分好,這點區分好了,我們來理解上述代碼就很容易了,
??&a:對陣列名取地址,代表二維陣列的地址,+1操作,表示指標加上其所指向的型別大小
??a:單獨使用陣列名,代表首元素地址,二維陣列中首元素是一個一維陣列,+1指向二維陣列的下一個元素;
??&a[0]:首先,a[0]表示二維陣列的首元素,該元素是一個一維陣列,對其取地址,代表是的就是首元素地址,與a本質一樣;
&a[0][0]:首先,a[0][0]表示二維陣列的第一個元素,對其取地址,代表二維陣列的第一個元素地址,+1操作,表示指標加上其所指向的型別的大小,
??根據代碼運行結果可知,四種表示的地址是一樣,這是因為指標永遠指向開辟眾多位元組的最低位元組處;
??+1操作后,各自表示的地址出現變化,這是因為對指標進行+1操作,不是單單看字面意思進行簡單的+1操作,而是加上指向的型別的大小,由于指標指向的型別各不相同,所以+1操作后,呈現出來的地址也就不一樣,
在這里插入圖片描述

5、陣列名a做為左值和右值的區別

??二維陣列的陣列名a做右值時,取值內容,代表首元素地址,代碼如下:

//二維陣列陣列名充當右值時;
//二維陣列陣列名可以做右值,代表陣列首元素的地址
//二維陣列陣列名做右值,本質等價于&a[0]
#include <stdio.h>
#include <windows.h>

int main() {
	char a[3][4] = { 0 };
	char (*p)[4] = a;
	printf("二維陣列的首元素地址&a[0]:%p\n",&a[0]);
	printf("陣列名做為右值時(內容)時:%p\n",p);
	system("pause");
	return 0;
}

運行結果:
在這里插入圖片描述
結論:二維陣列陣列名可以充當右值,代表首元素地址!

//二維陣列陣列名不可以做左值!
//能夠充當左值的,必須是有空間且可被修改的,a不可以整體使用,只能按照元素為單位進行使用,
#include <stdio.h>
#include <windows.h>

int main() {
	char a[3][4] = { 0 };
	a = { {1,2,3,4},{1,2,3,4},{1,2,3,4} };
	system("pause");
	return 0;
}

運行結果:
在這里插入圖片描述
結論:二維陣列陣列名不可以充當左值!

四、指標與陣列的關系

結論:指標和陣列沒有關系!

(一)以指標的形式訪問和以陣列的形式訪問

??我們給出的結論是指標和陣列沒有關系,但是有時候在操作陣列元素的時候,發現既可以用陣列方式操作,也可以用指標方式操作,而且兩者操作的形式特別相似,那為什么兩者會沒有關系呢?(先給一段代碼大家感受一下)

const char *str = "china";
char arr[] = "china;"

對于這兩行代碼,我提出幾個問題,大家自行思考一下;

  1. 代碼中兩個字串保存的位置一樣的嗎?保存在哪里?
  2. 為什么要加關鍵字const?

??我們先從感性來區別一下,這就好比我跟你,我們的性格,一些生活習慣甚至個人行為方式都有很強的相似性,但是你能說我們兩個是相同的嘛?答案是不能,同樣是,指標和陣列雖然在一些方式上具有很強的相似性,但是兩者沒有半毛錢關系,現在有些人很懵,你說兩者沒關系,但又有相似性,那么這個相似性體現在哪里,沒有關系又體現在哪里?
??學過的同學應該知道,如果要遍歷以上兩個字串,用陣列的方式和用指標的方式進行遍歷,兩者的寫法是一摸一樣的,這就是兩者的相似性,但是,雖然遍歷元素的方式相似,但是兩者的底層含義卻是各有不同,這個就證明兩者沒有關系,接下來我們就來談談兩者的相似性以及證明兩者沒有關系,看如下代碼:

#include <stdio.h>
#include <string.h>
#include <windows.h>

int main()
{
	const char *str = "china";	//str指標變數在堆疊上保存,“china”在字符常量區,真正意義上屬于作業系統級別的保護,不可修改,const修改的只是編譯器級別的保護;
	char arr[] = "china";	//整個陣列都在堆疊上保存,可以被修改,相當于你定義的,空間屬于用戶,有資格進行修改,這部分可以區域測驗一下
	
	//1. 以指標的形式訪問指標和以下標的形式訪問指標
	printf("以指標的形式訪問指標和以下標的形式訪問指標\n");
	int len = strlen(str);
	for (int i = 0; i < len; i++) {
		printf("%c\t", *(str + i));
		printf("%c \n", str[i]);
	}
	printf("\n");

	//2. 以指標的形式訪問陣列和以下標的形式訪問陣列
	printf("以指標的形式訪問陣列和以下標的形式訪問陣列\n");
	len = strlen(arr);
	for (int i = 0; i < len; i++) {
		printf("%c\t", *(arr + i));
		printf("%c \n", arr[i]);
	}
	printf("\n");

	system("pause");
	return 0;
}

運行結果:
在這里插入圖片描述
??我們發現,指標與陣列,在訪問多個連續元素的時候,可以指標解參考方案,也可以[ ]方案!
理解鏈:
??我們的計算機并沒有為陣列名(陣列的起始地址)單獨開辟一段記憶體,去保存陣列名,它的陣列名就代表著它所對應的整個陣列的起始地址,也就是陣列名直接等價于陣列的首元素地址,如果要訪問’c’元素時,只要找到陣列名,加上下標0,然后解參考就找到元素’c’;如果要訪問’a’元素時,只要找到陣列名,加上下標4,然后解參考就能找到元素’a’;陣列名大部分情況下,代表首元素地址,換句話說,陣列名代表的首元素地址其實就是個字面常量,因此,我們在訪問某個元素時,是直接根據這個地址找到起始位置,然后具體想訪問哪個元素,就加上對應的索引解參考即可,這是陣列進行尋址的一種方案,
在這里插入圖片描述
??指標變數str是在堆疊上開辟的一個變數,用指標訪問元素’a’時,同樣可以用指標str加上對應的索引,然后解參考即可;雖然跟陣列訪問方式一樣,但是,str是一個變數,而陣列名最終是個字面常量,常量可以直接尋址,而str要進行尋址時,需要先找到str,然后把里面的內容(即字串的起始地址)拿出來,在根據這個內容去加上對應的索引,找到之后解參考就得到元素’a’,
在這里插入圖片描述
??換言之,雖然兩者對應的寫法都是一樣的,但是深入一點了解,兩者的尋址方案完全不同!!兩者僅僅是在c語言的語法范疇內訪問元素的方案相似,但是,在底層找資料的時候,兩者的找法完全不同,既然找法不一樣,就直接證明了兩者不是同樣的關系!

結論:指標和陣列指向(表示)一塊空間的時候,訪問方式是可以互通的,具有相似性,但是具有相似性,不代表具有相關性,

最后,留個問題,
??為什么c語言在設計的時候非得把指標和陣列在訪問元素的時候,訪問方式設計的這么像,且都是通用?
??關于這塊內容以及深度理解指標陣列和陣列指標、陣列傳參和指標傳參、函式指標等內容,我將放到(c語言)-- 深度剖析指標和陣列(下)的博客中,到時歡迎大家互訪,謝謝大家!
在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/332141.html

標籤:其他

上一篇:你需要知道的 20 個 Python 技巧

下一篇:yum到底是干什么的?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more