摘要:在位機器上,如果有個地址線,那一個指針變量的大小是個字節,才能存放一個地址。就是一個指針變量,也有自己的類型,指針變量的類型我們可以發現指針的定義方式是類型星號。也就是說存儲什么變量類型就用什么指針變量類型。
前言:
本篇為你介紹什么是指針以及指針的基本運用,讓你更了解什么是指針,指針的運算,指針與數組,二級指針等
指針,是C語言中的一個重要概念及其特點,也是掌握C語言比較困難的部分。
指針也就是內存地址,指針變量是用來存放內存地址的變量,不同類型的指針變量所占用的存儲單元長度是相同的,而存放數據的變量因數據的類型不同,所占用的存儲空間長度也不同。有了指針以后,不僅可以對數據本身,也可以對存儲數據的變量地址進行操作。
在計算機科學中,指針(Pointer)是編程語言中的一個對象,利用地址,它的值直接指向(points to)存在電腦存儲器中另一個地方的值。由于通過地址能找到所需的變量單元,可以說,地址指向該變量單元。因此,將地址形象化的稱為“指針”。意思是通過它能找到以它為地址的內存單元。
我們可以這樣理解:
這里是編譯器里面的一個存儲數據的內存,然后我們把他分為若干個字節,每一個字節叫做一個內存單元。每一個字節進行一個編號,這個編號是唯一跟這個單元匹配的,用這個編號就可以找到這個內存單元。
也就是說:地址指向了一個確定的空間,所以地址形象的被稱為指針。
那既然存儲的內存單元是有編號的,我們來看看下面的代碼:
int main(){ int a = 10; //在內存中開辟一塊空間存放a的值 //a的地址:0x00CFFC6C int * pa = &a; //pa是用來存放地址的,所以pa是指針變量。 return 0;}
上面的代碼中,我們先創建變量a,也就是在內存中開辟一塊空間存放a的值,而&
符則可以取出該變量的地址,然后把a的地址存到pa里面去,說明pa就是一個指針變量。
所以總的來說:
指針就是變量,用來存放地址的變量。(存放在指針中的值都被當成地址處理)。
那么這里存在兩個問題:
上面我們說到,指針是存放地址的變量,那一個地址多大呢,又有多少地址呢,編址能擁有多大的空間呢,這就是我們接下來要研究的問題。
我們知道,機器有32位的,也有64位的。那32位的機器有32根地址線,然后通電后的高電頻低電頻由電信號轉化為數字信號,這個數字信號就是1或者0,而32根地址線產生32個1/0。
那一個內存單元多大才合適呢,這里我們有bit,byte,kb,mb,gb,tb
等供您選擇,我們先試試最小的bit合不合適,2的32次方bit化為gb就是0.5gb,一共大小才0.5gb,不合適吧。經過仔細的計算和權衡我們發現一個字節(byte)給一個對應的地址是比較合適的!
每一個地址一個字節,那么就有4GB的空間來進行編制,對于計算機來說是足夠的了。同樣的方法,64位機器,如果給64根地址線,那能編址多大空間,有興趣的小伙伴可以自己計算一下。
所以總結起來就是:
1.在32位的機器上,地址是32個0或者1組成二進制序列,那地址就得用4個字節的空間來存儲,所以一個指針變量的大小就應該是4個字節。
2.在64位機器上,如果有64個地址線,那一個指針變量的大小是8個字節,才能存放一個地址。
3.指針的大小在32位平臺是4個字節,在64位平臺是8個字節。
首先我們知道變量有不同的類型,整形,浮點型等,那指針也有不同的類型嗎,答案是:有的。
我們來看一下這個代碼:
int main(){ int a = 10; p = &a; //這樣的代碼可不可行? return 0;}
答案是不可行的,因為p根本沒有定義。我們現在是想用p來存儲a的地址,所以p也要帶上他自己的類型,也就是int * p = &a;
。
p就是一個指針變量,也有自己的類型,指針變量的類型:
char *pc = NULL;int *pi = NULL;short *ps = NULL;long *pl = NULL;float *pf = NULL;double *pd = NULL
我們可以發現:指針的定義方式是: type + *
(類型+星號)。 其實: char*
類型的指針是為了存放 char 類型變量的地址。 short*
類型的指針是為了存放 short 類型變量的地址。也就是說存儲什么變量類型就用什么指針變量類型。
那指針的類型的意義又是什么呢?
我們來看下面一段代碼:
int main(){ int n = 10; char* pc = (char*)&n; int* pi = &n; printf("%p/n", &n); printf("%p/n", pc); printf("%p/n", pc + 1); printf("%p/n", pi); printf("%p/n", pi + 1); return 0;}
這一段代碼的意思是:創建一個int變量,用char *
類型去存儲他的地址,和用int *
去存他的地址,當打印地址時,觀察他們的區別。我們來看看結果:
我們可以觀察到,n的地址是00CFFD0C(當然每一次運行的時候都有可能不一樣),然后我們創建的char * pc
指針變量存儲n的地址,這里因為類型不同在&n前面加了強制類型轉換,而int * pi
同樣也是存儲n的地址。然后我們發現,pc跟pi存的地址打印出來的時候都是一樣的,說明存儲這個過程是可以進行的,但當pc和pi加1后,就產生了差異。
這里就涉及到指針 ± 整數的意義了,指針在 ± 整數的時候,實際上就是往下一個地址去,那作為指針(地址)我們知道是有大小的,比如存一個int類型的變量的地址要4個字節,所以我們在指針±時,也是要一個這樣的大小。比如上面的代碼中,char類型的pc+1
,地址在變動了一個字節,而int類型的pi+1
,就跳過了四個字節。
總的來說就是:
指針的類型決定了指針向前或者向后走一步有多大(距離)。
解引用過程中也是同樣關乎到大小的問題,我們用代碼來說明:
#include int main(){ int n = 0x11223344; char* pc = (char*)&n; int* pi = &n; *pc = 0; *pi = 0; //我們來觀察解引用的時候會有什么區別。 return 0;}
我們來逐步觀察這個代碼的變化:
① 這里的第一步就是創建n變量,第二步創建char * pc
存儲n的地址,第三步創建int * pi
存儲n的地址。這些都不重要,下面兩條代碼才是主要的區別,看他們值的變化。
② 在這里,第三步轉換第四步的時候,實際上就是執行了*pc = 0
的代碼,然后* pc
變為0,但是pc所存的地址也就是n的地址
里面的內容卻只變了高位的44,而不是將地址內的內容改為0。
③ 這里第四到第五步中,執行的是*pi =0
的代碼,然后* pi變成0了,而且pi所存的地址也就是n的地址
里面的內容也變成了0。
說明了什么?
說明了在指針解引用操作的時候,你所存儲地址的指針類型是多大,你能操作的地址就是多少,比如char *
類型的指針,在解引用的時候操作的就是一個字節的內容,所以上面* pc
改變內容的時候,只有一個字節的內容發生了變化,而* pi
改變的時候可以將全部都改變,因為這個指針也是int 的類型。
所以總的來說:
指針的類型決定了,對指針解引用的時候有多大的權限(能操作幾個字節)。 比如:
char*
的指針解引用就只能訪問一個字節,而int*
的指針的解引用就能訪問四個字節。
這就是指針類型的意義。
概念: 野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)
野指針的成因是什么?其實野指針就是沒有指向具體位置的指針。而成因主要有以下三種:
#include int main(){ int *p; //局部變量指針未初始化,默認為隨機值 *p = 10; return 0;}
我們創建指針變量,通常會指向某一個確定的變量的地址,但是直接創建指針變量卻不初始化,那這個指針就如同無家可歸的孩子,只能隨機找一個地方呆了。然后給這個指針解引用賦值,也是賦值到了不知道何處。這就是其中一種野指針。
int main(){ int arr[10] = {0}; int *p = arr; int i = 0; for(i=0; i<=11; i++) { //當指針指向的范圍超出數組arr的范圍時,p就是野指針 *(p++) = i; } return 0;}
這里就是超出數組范圍后,雖然指針指向一個地址本身是沒有錯的,但不能去改變里面的內容,這樣子就是錯誤的了。當超出數組范圍后,指針就是越界訪問了,就不是一個正常的指針了。這就是第二種野指針。
int* fun(){ int a = 10; return &a;}int main(){ int* p = fun(); printf("%d/n", *p); return 0;}
在這里我們創建一個p的指針變量,然后調用fun函數返回a的地址,但是注意,這里fun函數在返回地址后就會銷毀,也就是說這個函數調用完之后推出就不見了,當我們的p指針變量去找他的時候,那里已經不是a確定的地址了,這是十分危險的。
但是我們運行后得出來*p的值仍然是10,但這并不是a的10,而是這個內存中這一個地址里面的數據沒有變,p指針找過去得到的而已,所以這也不是一個確定具體的地址。當這個地址被覆蓋的時候,得到就也就不會還是10了。這就是第三種野指針。
1. 指針初始化
2. 小心指針越界
3. 指針指向空間釋放即使置NULL
4. 指針使用之前檢查有效性
PS:如果不懂得函數創建銷毀的,可以看一下【C語言】函數棧幀的創建與銷毀這里面具體講到了代碼每一步運行是怎樣的,基于棧幀的運行時,函數是怎么創建銷毀的。
①.指針±整數
②.指針-指針
③.指針的關系運算
等等
看到第二點的時候就會有人覺得,指針有±整數,為什么第二點就只有指針-指針,而沒有指針+指針呢?這是因為指針+指針,就是地址加地址,有什么意義呢,這如同日期-日期知道天數差距,日期+日期卻沒什么意義,所以我們不討論指針+指針。
我們來看這一段代碼:
#define N_VALUES 5int main(){ float values[N_VALUES]; float *vp; //指針+-整數;指針的關系運算 for (vp = &values[0]; vp < &values[N_VALUES];) { *vp++ = 0; } return 0;}
這里的#define N_VALUES 5
是一個宏定義,就是不會改變的一個數值。然后我們創建一個values數組,代入了N_VALUES
所以這個數組就是5個元素。然后for循環其實就是把0放進數組里面,因為是后置++,先放進去再++,所以就是能填滿數組。
這里就有指針±整數了,指針±整數,實際上就是以該指針類型大小向后面的內存中劃出一個指針類型大小,然后指向這一個地址。圖示可能更容易理解:
這里雖然到N_VALUES
,但是并沒有訪問,所以這里并不是一個野指針。
對于指針-指針呢,我們看這個代碼;
int main(){ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("%d/n", &arr[9] - &arr[0]); printf("%d/n", &arr[0] - &arr[9]); return 0;}
這里我們創建一個數組,然后地址-地址,也就是指針-指針,得到的是什么呢?我們來看一下結果:
得到的是他們之間的內存空間嗎,其實并不是。結果是9和-9,這是什么意思呢,其實指針-指針的含義是這樣的:
指針-指針,在滿足兩個指針指向同一塊區域的前提下,得到的數字的絕對值是指針和指針之間元素的個數。
所以我們得到的是arr[0]到arr[9]之間的元素個數,也就是0-8這9個元素。
我們知道關系運算有等于、大于、小于、大于等于、小于等于和不等于六種。對于指針來說,等于和不等于就是判斷兩個指針的值是否相同或不同,即兩個指針是否指向了相同或不同的地方。而大于和小于是判斷指針的值哪個大哪個小。值較小的在存儲器中的位置比較靠前,值較大的在存儲器中的位置比較靠后。
我們用一個代碼來說明:
#define N_VALUES 5float values[N_VALUES];float* vp;int main(){ for (vp = &values[N_VALUES]; vp > &values[0];) { *--vp = 0; }}
這里就是拿指針變量vp和數組比較,然后按照大到小依次把數組里面的值變為0,指針的比較就是指針的關系運算。
但比較的也有他的前提:
標準規定:
允許指向數組元素的指針與指向數組最后一個元素后面的那個內存位置的指針比較,但是不允許與指向第一個元素之前的那個內存位置的指針進行比較。
這就是指針的運算。
數組名是什么?老規矩,上代碼:
#include int main(){ int arr[10] = {1,2,3,4,5,6,7,8,9,0}; printf("%p/n", arr); printf("%p/n", &arr[0]); return 0;}
我們來觀察一下,數組名和數組首元素地址有什么聯系,結果:
我們會發現,數組名和數組首元素的地址是一樣的,這可以讓我們得出一個猜測的結論:數組名表示的是數組首元素的地址。
這個結論是不是正確的呢,我們來測試一下讓數組名代替首元素地址看看得出的是否可以正常運行。
int main(){ int arr[] = { 1,2,3,4,5,6,7,8,9,0 }; int* p = arr; //指針存放的是數組首元素的地址 int sz = sizeof(arr) / sizeof(arr[0]); for (int i = 0; i < sz; i++) { printf("&arr[%d] = %p <====> p+%d = %p/n", i, &arr[i], i, p + i); //打印觀察arr能不能作為首元素地址 } return 0;}
答案是可行的,當我們將arr表示的地址存儲起來后,讓指針變量p±整數時,變化的就是該數組的元素輸出,那我們就可以直接通過指針來訪問數組,所以arr所表示的地址確實為數組首元素的地址。
但是也有例外,
sizeof(數組名) - 這里的數組名不是首元素的地址,是表示整個數組的,這里計算的是整個數組的大小,單位還是字節
&數組名 - 這里的數組名不是首元素的地址,是表示整個數組的,拿到的是整個數組的地址。
看一段代碼:
int main(){ int arr[10] = { 0 }; printf("%p/n", arr);//數組名是首元素的地址 printf("%p/n", arr + 1); printf("%d/n", sizeof(arr));//第一個元素的地址 printf("%p/n", &arr);//取出整個數組的地址,但打印的是首元素地址 printf("%p/n", &arr + 1); return 0;}
我們先看打印結果:
然后我們來分析一下:
① 數組名打印首元素地址,表示首元素,然后數組名+1,得到的是跳過同類型大小的地址,也就是數組下一個元素的地址,說明數組名確實表示數組首元素的地址。
② sizeof(數組名)所打印的是整個數組的大小,這里10個元素表示40字節。
③ 取地址+數組名打印的也是首元素地址,但他并不是表示數組首元素地址,在我們加一后,跳過的并不是一個元素的大小,而是整個數組的大小,說明取地址+數組名代表的是整個數組的地址。
c語言中有指針,那有沒有二級指針呢,三級呢,答案也是:有的。
首先我們知道,指針是用來存放變量的地址的,那指針是不是變量,指針變量,當然是變量,所以是變量就有地址,那指針變量的地址存放在哪里? 這就是 二級指針,那二級指針是不是變量,是變量,地址存儲用什么,三級指針。(俄羅斯套娃又來了) 。
對于上面的二級指針的運算有:
*p2
通過對p2
中的地址進行解引用,這樣找到的是p1
,*p2
其實訪問的就是p1
.
我們看這一個代碼:
int main(){ int a = 10; int* p1 = &a; int** p2 = &p1; int b = 20; *p2 = &b; printf("%p/n", &a); printf("%p/n", &b); printf("%p/n", p1); return 0;}
運行的結果為:
我們在一開始的時候是將a的地址存到了p1
上去,然后再以*p2=&b
去訪問p1的地址把b的地址放進去了。所以 *p2
其實訪問的就是 p1
。
**p2
先通過*p2
找到p1
,然后對p1
進行解引用操作:*p1
,那找到的是 a.
我們看這一個代碼:
int main(){ int a = 10; int* p1 = &a; int** p2 = &p1; **p2 = 30; printf("%d/n", a); //答案是30還是10? return 0;}
運行結果:30
這里就是說明其實三級指針也是可以二次解引用,訪問a的地址,然后去操作a地址上的內容,這里**p2
也可以想象成*(*p2)
,*p2
就是訪問p1
,所以化為*p1
,而*p1
訪問的就是a的地址。 所以最終結果是30.
Q:指針數組是指針還是數組?
答案:是數組。是存放指針的數組。
數組我們已經知道有整形數組,字符數組等如:
那指針數組是怎樣的?我們知道變量和指針都有不同類型,而同一種大小類型的變量和指針之間差一個 *
號,所以指針數組也和數組相似:
int main(){ int arr1[3]; char arr2[5]; int * arr3[3]; char* arr4[5]; return 0;}
那
int * arr3[3];
表示什么呢?
其實,這表示的意思就是arr3
是一個數組,有五個元素,每個元素是一個整形指針。就如同上面的數組一樣,一個數組,里面都是這個類型。
這幾種數組或指針數組的意思和定義:
int main(){ int arr[10]; //整型數組 - 存放整型的數組就是整型數組 char ch[5]; //字符數組 - 存放字符的數組就是字符數組 //指針數組 - 存放指針的數組就是指針數組 //int* 整型指針的數組 //char* 字符指針的數組 int* parr[5]; //整型指針的數組 char* pc[6]; //字符指針的數組 return 0;}
好啦,本篇的內容就到這里,小白制作不易,有錯的地方還請xdm指正,互相關注,共同進步。
還有一件事:
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/119674.html
摘要:還不清楚原碼反碼補碼的可以到語言從入門到入土操作符篇中的移位操作符處學習一下。比如原碼反碼補碼原碼顯示值補碼數據存放內存中其實存放的是補碼補碼的表示與存儲在計算機系統中,數值一律用補碼來表示和存儲。 ...
摘要:代碼修正后修改后,我們可以排列無限個數字這樣,一個冒泡排序就完成了。,數組名表示整個數組。 首先感謝一位博主: 原來45 他寫的博客內容十分詳細,為我創造博客提供了莫大的幫助,也為我解決了很多困難。 先貼出2篇他的文章 C語言從入門到入土(入門篇)(數組p1)_原來45的博客-CSDN博客 ...
摘要:基于許可的開源平臺,創始人是的項目架構師,它特色是提供了插件,開發人員可以通過插件直接繪畫出業務流程圖。二工作流引擎對象,這是工作的核心。五總結工作流的概念就先介紹這么多了,更多的去官網查看,下一節將用一個入門的實例來對工作流進行講解。 文章源碼托管:https://github.com/OUYANGSIHA...歡迎 star !!! 一、activiti介紹 Activiti5是由...
摘要:二環境準備編譯器選擇這里我們使用進行工作流開發,雖然對于工作流的友好度不是很好,因為會有一些小的,但是,對于的開發還是非常的好的。新建后出現下面的編輯頁面到現在,編輯插件就準備好了。 文章源碼托管:https://github.com/OUYANGSIHA...歡迎 star !!! 一、前言 在上一節中我們對activiti進行了基本的介紹activiti進行了基本的介紹,同時介紹了...
摘要:文章源碼托管歡迎一前言在上一節中,通過一個入門程序,把的環境準備好了,這一節,將整合,并且部署一個最簡單的流程圖。測試結果四總結這一節通過整合,繪制簡單的文件,然后成功部署了文件。 文章源碼托管:https://github.com/OUYANGSIHA...歡迎 star !!! 一、前言 在上一節中,通過一個入門程序,把activiti的環境準備好了,這一節,將整合spring,并...
閱讀 3166·2021-11-04 16:09
閱讀 3142·2021-09-23 11:49
閱讀 3657·2021-09-09 09:33
閱讀 3646·2021-08-18 10:22
閱讀 2052·2019-08-30 15:55
閱讀 3641·2019-08-30 15:53
閱讀 2666·2019-08-28 18:08
閱讀 905·2019-08-26 18:18