@toc 編程
在計算機科學中,指針(Pointer)是編程語言中的一個對象,利用地址,它的值直接指向 points to)存在電腦存儲器中另外一個地方的值。因爲經過地址能找到所需的變量單元,能夠 說,地址指向該變量單元。所以,將地址形象化的稱爲「指針」。意思是經過它能找到以它爲地址的內存單元數組
那咱們就能夠這樣理解:markdown
內存:編程語言
指針ide
指針是個變量,存放內存單元的地址(編號)。函數
那對應到代碼:學習
#include <stdio.h> int main() { int a = 10;//在內存中開闢一塊空間 int *p = &a;//這裏咱們對變量a,取出它的地址,可使用&操做符。 //將a的地址存放在p變量中,p就是一個之指針變量。 return 0; }
總結∶指針就是變量,用來存放地址的變量。(存放在指針中的值都被當成地址處理)。3d
思考如下的問題∶
(1)一個小的單元究竟是多大 ? (1個字節)指針
(2)如何編址 ?調試
通過仔細的計算和權衡咱們發現一個字節給一個對應的地址是比較合適的。
對於32位的機器,假設有32根地址線,那麼假設每根地址線在尋址的是產生一個電信號正電 / 負電(1或者0)
那麼32根地址線產生的地址就會是∶
00000000 00000000 00000000 00000000
0000000 00000000 00000000 00000001
…….
11111111 1111111111111111 11111111這裏就有2的32次方個地址。 每一個地址標識一個字節,那咱們就能夠給(2 ^ 32Byte == 2 ^ 32 / 1024KB == 2 ^
32 / 1024 / 1024MB == 2 ^ 32 / 1024 / 1024 / 1024GB == 4GB)4G的空閒進行編址。
一樣的方法,那64位機器,若是給64根地址線,那能編址多大空間,本身計算。
這裏咱們就明白 :
(1)在32位的機器上,地址是32個0或者1組成二進制序列,那地址就得用4個字節的空間來存儲,因此一個指針變量的大小就應該是4個字節。
(2)那若是在64位機器上,若是有64個地址線,那一個指針變量的大小是8個字節,才能存放一個地址。
總結 :
(1)指針是用來存放地址的,地址是惟一標示一塊地址空間的。
(2)指針的大小在32位平臺是4個字節,在64位平臺是8個字節。
咱們先看這樣一段代碼的執行效果:
在32位 (x32)系統下
:
能夠看到無論什麼類型的指針,大小都是4,這時候咱們內心可能產生疑問,爲何大小都是4,還要區分不一樣類型的指針,那這種區分是否是沒有意義的?不一樣類型的指針是否是能夠互用?爲了驗證這些問題,請看如下代碼及運行結果。
咱們能夠先屏蔽其它代碼內容,保留前三行內容,再加上 * p = 0; 即調用指針來更改指針指向地址中的內容。經過F10調試,打開窗口-- - 內存監視器(相似變量監視窗口),能夠看到a的內容確實改變了
如今咱們*將char pc來重複上面的操做,看可否實現相應變動指針指向地址內容的功能。
經過F10調試,打開窗口-- - 內存監視器(相似變量監視窗口),咱們發現這裏僅前兩個變成了00,後面幾位沒有變化。**
經過這個咱們能夠發現指針的類型仍是意義的,其意義在於解引用操做時,對字節的操做數量不一樣。
好比int 類型的指針,能夠改動4個字節的內容,而char 類型的指針只能更改一個字節的內容。
指針的意義1:指針的解引用
總結:指針類型決定了指針進行解引用操做的時候,可以訪問空間的大小
舉例:
int p; p可以訪問4個字節
char p; p可以訪問1個字節
double p; p可以訪問8個字節
指針的意義2:指針 + - 整數
看下面這個例子:pa + 1 實際地址 + 4, pc + 1 實際地址 + 1
總結:指針的類型決定了指針向前或者向後走一步有多大(距離、指針的步長)。
舉例:
int p; p + 1 – > 日後跳4字節
char p; p + 1 – >日後跳1字節
double* p; p + 1 – > 日後跳8字節
思考:那麼指針這兩個意義的價值是什麼呢?
把arr[10]數組全部元素依次加1 :↓↓↓↓
若是咱們將int p = arr; 改爲char pc = arr;會發生什麼呢?
是否是隻會將10個字節改爲1,而咱們數組int arr[10]有40個字節,10個字節的大小至關於2個半int類型的大小,咱們能夠打開內存窗口調試,看一下是否是這樣的狀況。
只改了前十個字節
野指針概念:野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)
野指針成因:
1.指針未初始化
#include<stdio.h> int main() { int a;//局部變量不初始化,默認是隨機值 int* p;//局部的指針變量不初始化,默認也會給隨機值 *p = 20;//這時候對指針進行解引用操做,實際上並不知道更改的內存是在哪裏 return 0; }
2.指針越界訪問
#include<stdio.h> int main() { int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i < 12; i++) { //當指針指向的範圍超出數組arr的範圍時,就越界訪問了,此時p就是野指針 *(p++)=1; } return 0; }
3.指針指向的空間釋放
#include<stdio.h> int* test() { int a = 10; return &a; } //1.a 建立的空間在函數結束的時候返回給系統了,不屬於當前程序的內容 int main() { int* p = test(); //2.這時候經過*p 去訪問一個返還系統的空間,該空間有可能已經存放其它內容了 //3.因此p是一個野指針 *p = 20; return 0; }
那麼咱們如何可以避免野指針呢?
1.指針初始化
2.當心指針越界
3.指針指向空間釋放,即置爲NULL
int*pa=NULL; //空指針
4.指針使用以前檢查有效性
指針運算有三種,分別爲:
①指針 + -整數
②指針 - 指針
③指針的關係運算
下面對這三種運算進行詳細的講解:
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10}; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; for (i = 0; i < sz; i++) { printf("%d ", *p); //p = p + 1; //指針 + 整數 p++; } return 0; }
這裏咱們使用的時指針 + 1,那麼指針 + 2或指針 + 3是什麼樣子的呢?請看下面的舉例;
指針除了能夠 + 整數,也能夠 - 整數,好比:
int*p=arr[sz-1]-->訪問下標
練習:
解析:
咱們知道指針變量是用於存放地址的,那麼指針 - 指針 就是 地址 - 地址,那指針 - 指針的結果又是什麼呢?請看下面的例子:
打印 & arr[9] - &arr[0] 結果爲9,表明從arr[0]到arr[9], 其中間有9個元素。
總結:指針 - 指針獲得的結果是中間元素的個數
固然若是咱們將 & arr[9] - &arr[0] 的順序調換如下,變成 & arr[0] - &arr[9],獲得的又是什麼呢?
結果是: - 9
因此若是咱們要獲得元素的個數,應該用大地址 - 小地址。
看下面這個 錯誤作法:
指針 + - 要使他們都指向一個空間
練習:模擬strlen的功能函數 (第三種方法)-->見' 遞歸 '
int my_strlen(char* str) { char* start = str; char* end = str; while (*end != '\0') { end++; } return end - start; } int main() { char arr[] = "hello,world"; int len = my_strlen(arr); printf("%d", len); return 0; }
關係運算,簡單來講就是比較大小,有 > 、 >= 、 == 、 != 、 < 、 <=
看下面例子:
#define N_VALUES 5 float values[N_VALUES]; //表明右5個元素 float *vp; //指針+-整數;指針的關係運算 for (vp = &values[N_VALUES]; vp > &values[0];) { *--vp = 0; }
將上面代碼改造一下↓↓↓↓↓
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--) { *vp = 0; }
實際在絕大部分的編譯器上是能夠順利完成任務的,然而咱們仍是應該避免這樣第二種寫,由於標準並不保證它可行。
標準規定∶
容許指向數組元素的指針與指向數組最後一個元素後面的那個內存位置的指針比較,可是不容許與指向第一個元素以前的那個內存位置的指針進行比較。
這句話看到這裏,仍是不太明白,因此就畫個圖來演示一下:
(畫圖演示很重要)
數組名是什麼?
在以前的學習中,咱們可能已經知道了數組名就是首元素的地址,這裏咱們驗證一下這個說法是否正確,請看下面這個例子:
能夠看到 arr 和& arr[0] 結果是同樣,說明 「數組名就是首元素的地址」 是正確的!
可是,數組名就一直是首元素地址嗎?
數組名在絕大多數狀況下是首元素地址,但有兩個例外:
一、& arr --- &數組名 - 數組名不是首元素的地址,數組名錶示整個數組 &
數組名 , 取出的是整個數組的地址。
整個數組的地址和首元素地址有什麼區別呢?
二、sizeof(arr)----sizeof(數組名)-- - 數組名錶示的整個數組–sizeof(數組名)計算的是整個數組的大小。
咱們回到剛剛說的問題:整個數組的地址和首元素地址有什麼區別呢?
在打印的時候咱們發現打印出來的值是相等的。
解析:
舉例:咱們讓它們都進行 + 1, 首元素地址加1就到了第二個元素的地址,而數組地址 + 1則是直接跳過該數組
上面咱們說了 數組名就是首元素的地址,那麼咱們經過使用指針來訪問數組就成爲了可能:
#include<stdio.h> int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { printf("&arr[%d] = %p <===> p+%d = %p\n", i, &arr[i], i, p + i); } return 0; }
p + i其實計算的是數組arr下標爲i的地址。那麼咱們就能夠直接經過指針來訪問數組,好比:
指針變量也是變量,是變量就有存儲空間,就有地址,那麼指針變量的地址存放在哪裏那?
這就是二級指針,指向內容是存放地址的指針。
一般咱們所說的指向數組的指針是一級指針。
#include<stdio.h> int main() { int a = 10; int* pa = &a;//pa 一級指針 int** ppa = &pa;//ppa 二級指針 ......後面還有 三級指針、四級指針....n級指針 return 0; }
這裏咱們進行畫圖演示:
好了,如今咱們對二級指針應該理解清楚了,可是二級指針怎麼應用呢?
若是咱們對 ppa 進行解引用操做 * ppa就 能找到 pa 也就是a的地址,再次進行解引用操做** ppa,就能找到a了!
以下圖:
在咱們學習指針和數組的概念時,會接觸到兩個這樣的概念
1.指針數組-- - 數組-- - 存放指針的數組
2.數組指針-- - 指針
那麼咱們怎麼來理解1.指針數組呢?咱們來看下面的這個問題。
#include<stdio.h> int main() { int a = 10; int b = 20; int c = 30; int* pa = &a; int* pc = &c; //這裏只有三個變量,咱們建立了三個指針分別存放它們的地址 //假若有10個變量,咱們要一口氣建立10個指針來存放它們的地址嗎? return 0; }
這個時候咱們就會思考,這10個指針變量能不能利用數組的方式來建立呢,就跟10個int 類型的變量同樣,經過建立一個 int arr[10]就能實現。
整型數組-- - 存放整型
字符數組-- - 存放字符
指針數組-- - 存放指針
按照這個思路,咱們建立一個指針數組 int* arr[3] = { &a,&b,&c };
咱們能夠經過指針數組訪問其內容並打印: