
目錄
前言
一、 什么是指標?
引例
計算機是怎么對記憶體單元編號的呢?
記憶體空間的地址如何得到
想存地址怎么辦?
?
本質目的不是為了存地址
二、指標和指標型別
為什么有不同型別的指標
1.指標的解參考
2.指標+-整數
三、野指標
造成野指標的原因
1.未主動初始化指標
2.指標越界訪問
3.指標指向的空間釋放
規避野指標
四、指標運算
1.指標+-整數
2.指標-指標
3.指標的關系運算
五、指標與陣列
六、二級指標
七、指標陣列
前言
指標這一部分可能很多人在學習的時候都覺得很難,但在這里我想說的是:不要自己嚇自己,想一想,你當初剛上大學的時候可能覺得高數非常難,最后學完整本書的時候回過頭再看還覺得很難嗎? 肯定已經覺得沒有剛開始學那么難了,那么其實指標也是這樣的,只要把里面的東西都搞清楚,你就不覺得難了,




一、 什么是指標?
在計算機科學中,指標(Pointer)是編程語言中的一個物件,利用地址,它的值直接指向
(points to)存在電腦存盤器中另一個地方的值,由于通過地址能找到所需的記憶體單元,可以
說,地址指向該變數單元,因此,將地址形象化的稱為“指標”,意思是通過它能找到以它為地址
的記憶體單元,
其實計算機在設計的時候很多東西都是參考的生活中的東西
引例
eg.我國的國土面積:960萬平方公里,這么大的國土面積是怎么合理使用的呢?聰明的國家領匯入就想到,將這一大塊區域進行劃分,先劃分為各個省,每個省再劃分為各個市區,市區又劃分為縣城,縣劃分為鎮,鎮又劃分為村,每個村的每戶人家都有門牌號,可以很方便的找到,這樣就對這樣一大塊區域進行了很好的管理,
那么其實電腦的設計也是這樣的,在學C語言的這個階段,我們常說的計算機的記憶體劃分為3部分,堆疊區,堆區和靜態區,

計算機在最終也是將記憶體劃分為了很小的空間--記憶體單元,
我們平時要在學校找一個同學,也是先確定他的宿舍編號然后才去找的,不可能盲目的去找,而這里宿舍編號就是地址,
那在計算機記憶體中,我們想要找到某個記憶體單元,也是要知道它的編號呀,所以這里就對每個記憶體單元都進行了編號處理,將編號就稱為記憶體單元的地址,
那么問題又來了
計算機是怎么對記憶體單元編號的呢?
1.計算機中的32位或者62位
32位的機器,它是有32根地址線(32根物理的電線),通電之后會將電信號轉換成數字信號(正電--1,負電--0),32根地址線通電之后會產生以下的二進制序列數字信號:
00000000000000000000000000000000(32位)
00000000000000000000000000000001
00000000000000000000000000000010
………………………………………………
11111111111111111111111111111111
這個時候呢將二進制序列與記憶體單元一一對應,那么與記憶體單元相對應的二進制序列就是它的編號--地址,
2.每一個記憶體單元到底多大?
這個我們也是可以來計算一下
比如一個記憶體單元是1bit,那么32位機器的所產生的序列共計2^32個,即2^32bit,轉化為MB就是512MB,要想想我們買的計算機都是起步2G/4G,所以記憶體單元以bit為單位肯定是不行的,在C語言中,我們創建一個變數所申請的空間最小都是char--1byte,所以其實記憶體單元單位其實是位元組,
請看如下代碼:
int main()
{
int a = 10; //創建變數a,向記憶體申請4個位元組的空間,將10存盤進去
return 0;
}

記憶體空間的地址如何得到
我們創建了變數a,如何得到它的地址呢? 我們只需要在a的前面加上 & (取地址運算子
)符號,便可以得到它的空間地址
int main()
{
int a = 10; //創建變數a,向記憶體申請4個位元組的空間,將10存盤進去
printf("%p\n", &a); //%p--列印地址(16進制)
return 0;
}

我們可以把列印出來的地址00EFF6E8(16進制)轉化成二進制來看看1110 1111 1111 0110 1110 1000
想存地址怎么辦?
我們平時可以創建字符變數來存字符,創建整型變數來存整型,那如果想將地址存起來呢?那我們就得創建一個指標變數----用來存放地址
int main()
{
int a = 10; //創建變數a,向記憶體申請4個位元組的空間,將10存盤進去
int* p = &a; //&a取出a的地址,創建指標變數p,接收a的地址
return 0;
}
* 說明p是指標變數,前面的int說明p指向的是整型變數
指標變數p里面存放的就是a的地址,我們可以通過p里面所存盤的值(地址)來找到變數a的記憶體空間,所以我們就說p指向了a,所以將p形象的稱為指標(注意變數名字是p,不是*p)


那么我們以后想要存盤地址的時候,就可以用指標
int main()
{
float b = 2.5;
float* p = &b;
return 0;
}
本質目的不是為了存地址
我們創建指標存放變數地址的本質目的并不是為了存放變數的地址,而是為了將來能夠通過地址來找到這個變數的記憶體空間,使用它,
我們上面已經存放了a的地址在p里面,那么我們怎么找到a呢
int main()
{
int a = 10;
int* p = &a;
*p = 20; //這里*---解參考運算子
return 0;
}

這里的 * --解參考運算子(間接訪問運算子),p里面存的是a的地址,那么*p就是通過地址來找到a
二、指標和指標型別
通過上面的代碼我們會發現,一個char型別的變數的地址放在char*的指標里面,整型變數的地址存放在整型指標里面,為什么要這樣呢?
為什么有不同型別的指標
1.指標的解參考
如下代碼:
int main()
{
int a = 10;
char ch = 'f';
int* pa = &a;
char* pc = &ch; //兩個指標都是4個位元組
}
這里要存放a的地址和ch的地址,都只需要4個位元組(32位平臺)的指標就可以,那為什么還要用不同型別的指標呢?
雖然它們都是4個位元組,但是也是有區別的,我們通過以下代碼的對比來看看
代碼1:
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
}
在記憶體里面看
a的記憶體里面的內容(執行*pa = 0;這個陳述句前后對比)(先不要管為什么記憶體里面是倒著存放的)


代碼2:
int main()
{
int a = 0x11223344;
char* ca = &a;
*ca = 0;
}
a的記憶體里面的內容(執行*ca = 0;這個陳述句前后對比)


我們通過代碼1和代碼2以及他們的記憶體情況可以看出在代碼1int*型別的指標在解參考的時候將a里面4個位元組的內容全部改成了0(可訪問4個位元組),但是代碼2中char*型別的指標在解參考的時候只改了1個位元組的內容(只能訪問一個位元組),這就是指標不同型別的差異
2.指標+-整數
通過以下代碼來體驗一下:
#include <stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
char* ca = &a;
printf("%p\n", pa);
printf("%p\n", pa + 1);
printf("\n");
printf("%p\n", ca);
printf("%p\n", ca + 1);
return 0;
}

通過這段代碼我們可以發現int*型別的指標進行+1操作地址加了4,char*型別的指標進行+1操作地址加了1
我們也可以通過下面的例子再來看看(通過指標來列印整型陣列的內容)
代碼1(通過整型指標來訪問整型陣列)
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* parr = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(parr + i));
}
return 0;

分析:
整型指標進行+1操作,意味著一次跳過一個整型(如下圖)

代碼2(通過字符指標來訪問整型陣列)
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
char* parr = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(parr + i));
}
return 0;
}

字符型別的指標+1每次跳過一個char(1個位元組)

總結:
指標型別的意義:
- 指標的解參考:指標的型別決定了,對指標解參考的時候有多大的權限(能操作幾個位元組),能一次性訪問幾個位元組
- 指標+-整數:指標的型別決定了指標向前或向后走一步有多大(它是什么型別的指標,+1就跳過該型別大小的位元組,eg.char*+1跳過一個位元組,short*+1跳過2個位元組,int*+1跳過4個位元組,float*+1跳過4個位元組,double*+1跳過8個位元組)
三、野指標
概念: 野指標就是指標指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)
造成野指標的原因
1.未主動初始化指標
請看如下代碼:
int main()
{
int* p;
*p = 8;
}
分析:
這段代碼里面的p就是野指標,因為p是一個區域變數,指標變數,未主動初始化,那么它里面就是隨機值(隨機地址),就是說這個地址是不清楚的,然后后面又對它進行解參考,將8存放到這塊不明確的地址里面,這就是又問題的
運行結果:

2.指標越界訪問
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pa = arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
*pa = 1;
pa++;
}
return 0;
}
分析:
這里的陣列長度為10,但明顯可以看到回圈執行了11次,pa最后所指向的已經不是陣列里面的記憶體單元,越界訪問了
可以來看看記憶體
這是在陣列初始化之后陣列中的內容

在執行完11次回圈之后

此時會發現指標pa最后一次回圈時越界,并且將陣列arr后面的一個記憶體單元里面的內容改成了1,但是要知道這塊記憶體并不屬于該程式(這就是非法的)
3.指標指向的空間釋放
如下代碼
int* app()
{
int b = 3;
return &b;
}
int main()
{
int* ps = app();
return 0;
}
分析:
這段代碼中,app這個函式回傳了臨時變數b的地址,ps這個指標也接受到了b的地址,但是要知道在這個函式呼叫完之后所創建的b這塊記憶體空間就已經銷毀了(或者說還給作業系統了),那么ps再記住這塊空間的地址就沒有意義了(保存了一個非法的地址)
規避野指標
- 指標初始化
- 小心指標越界
- 指標指向空間釋放即使置NULL
- 指標使用之前檢查有效性
四、指標運算
1.指標+-整數
示例:(通過指標列印陣列元素)
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pa = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(pa + i));
}
return 0;
}
運行結果:

2.指標-指標
示例:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[0]);
return 0;
}
請問這段代碼的運行結果是什么呢? 是36?
需要知道的是 指標-指標:得到的是兩個指標之間元素的個數(前提:兩個指標指向同一塊空間)
運行結果:
3.指標的關系運算
代碼1:
#include <stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* ps = &arr;
for (ps = &arr[5]; ps > &arr[0];)
{
*--ps = 0;
}
return 0;
}
這段代碼將陣列中的元素改成了0
代碼2:
#include <stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* ps = &arr;
for (ps = &arr[4]; ps >= &arr[0];ps--)
{
*ps = 0;
}
return 0;
}
代碼2與代碼1相比更容易理解,但是要知道這樣寫是很大有問題的,C標準并不保證它可行
標準規定:
允許指向陣列元素的指標與指向陣列最后一個元素后面的那個記憶體位置的指標比較,但是不允許
與指向第一個元素之前的那個記憶體位置的指標進行比較
五、指標與陣列
再來啰嗦一下
1.陣列名表示首元素地址
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", &arr[0]);
printf("%p\n", arr);
return 0;
}

可以看到這種方式列印出來的結果是一樣的
陣列名表示陣列首元素的地址在絕大多數情況下都成立
兩個例外:
- sizeof(陣列名)
- &陣列名
可以通過指標來訪問陣列
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pa = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(pa + i));
}
return 0;
}

六、二級指標
我們都知道指標是用來存地址的,指標也是在記憶體中開辟了一塊空間然后將地址存盤在里面,所以指標本身也是有自己的地址的
那我們就可以取出指標的地址來進行存放
int main()
{
int a = 8;
int* ps = &a; //ps稱為一級指標
int** pps = &ps; //pps稱為二級指標 用來 存盤一級指標的地址
}


存盤地址就是為了后面能用得到的
現在對它進行解參考操作
#include <stdio.h>
int main()
{
int a = 8;
int b = 55;
int* ps = &a; //ps稱為一級指標
int** pps = &ps; //pps稱為二級指標 用來 存盤一級指標的地址
*pps = &b;
**pps = 666;
printf("%d\n", b);
printf("%d\n", **pps);
}
*pps 通過對ppa中的地址進行解參考,這樣找到的是ps,*pps 其實訪問的就是ps
**pps 先通過*pps 找到ps ,然后對ps 進行解參考操作
運行結果:

七、指標陣列
我們前面用過的整型陣列拿來存放整型,字符陣列里面存放字符,那么現在里面存放指標的陣列就叫做指標陣列
現在先簡單說下,后面在指標進階階段還會再詳細說明
通過以下代碼來體驗一下
#include <stdio.h>
int main()
{
int a = 0;
int b = 1;
int c = 2;
int* arr[3] = { &a,&b,&c };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *arr[i]);
}
}
這里是創建了一個整型指標陣列,并對它進行了初始化,里面存放有a,b,c的地址,再通過解參考去列印出a,b,c
運行結果:

如下圖:

這個陣列里面每個元素都是一個整型指標
-----------------------------------------------------------------
-----------------C語言運算子部分完結-------------------
關于C語言,每個知識點后面都會單獨寫博客更加詳細的介紹
歡迎大家關注!!!
一起學習交流 !!!
讓我們將編程進行到底!!!
--------------整理不易,請三連支持------------------
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/302189.html
標籤:其他
上一篇:程式人生 | 0基礎成功轉行IT軟體測驗,坐標上海薪資2w,分享下我本科畢業2年轉行的經歷!
下一篇:深入理解HDFS



