指針也就是內存地址,指針變量是用來存放內存地址的變量,不同類型的指針變量所占用的存儲單元長度是相同的,而存放數據的變量因數據的類型不同,所占用的存儲空間長度也不同。有了指針以后,不僅可以對數據本身,也可以對存儲數據的變量地址進行操作。
計算機中所有的數據都必須放在內存中,不同類型的數據占用的字節數不一樣,例如 int 占用 4 個字節,char 占用 1 個字節。為了正確地訪問這些數據,必須為每個字節都編上號碼,就像門牌號、身份證號一樣,每個字節的編號是唯一的,根據編號可以準確地找到某個字節。
在計算機中, 所有的數據都是存放在存儲器中的, 不同的數據類型占有的內存空間的大小各不相同。內存是以字節為單位的連續編址空間, 每一個字節單元對應著一個唯一的編號, 這個編號被稱為內存單元的地址。比如: int類型占兩個字節, char類型占1個字節等。內存為變量分配存儲空間的首個字節單元的地址, 稱之為該變量的地址。地址用來標識每一個存儲單元, 方便用戶對存儲單元中的數據進行正確的訪問。在高級語言中地址形象地稱為指針, CPU 訪問內存時需要的是地址,而不是變量名和函數名!變量名和函數名只是地址的一種助記符,當源文件被編譯和鏈接成可執行程序后,它們都會被替換成地址。編譯和鏈接過程的一項重要任務就是找到這些名稱所對應的地址。
需要注意的是 變量名和函數名為我們提供了方便,讓我們在編寫代碼的過程中可以使用易于閱讀和理解的英文字符串,不用直接面對二進制地址,那場景簡直讓人崩潰。雖然變量名、函數名、字符串名和數組名在本質上是一樣的,它們都是地址的助記符,但在編寫代碼的過程中,我們認為變量名表示的是數據本身,而函數名、字符串名和數組名表示的是代碼塊或數據塊的首地址。
指針相對于一個內存單元來說,指的是單元的地址,該單元的內容里面存放的是數據。在C語言中,允許用指針變量來存放指針,因此,一個指針變量的值就是某個內存單元的地址或稱為某內存單元的指針。
指針的定義與使用
變量的指針與指針變量:
在C語言中,允許用一個變量來存放指針,這種變量稱為指針變量。指針變量的值就是某份數據的地址,這樣的一份數據可以是數組、字符串、函數,也可以是另外的一個普通變量或指針變量
變量的指針就是變量的存儲地址,指針變量就是存儲指針的變量。指針變量是存放一個變量地址的變量,不同于其他類型變量,它是專門用來存放內存地址的,也稱為地址變量。定義指針變量的一般形式為:類型說明符 * 變量名 *
類型說明符表示指針變量所指向變量的數據類型;* 表示這是一個指針變量;變量名表示定義的指針變量名,其值是一個地址,例如:char * p1;表示p1是一個指針變量,它的值是某個字符變量的地址
//定義指針變量與定義普通變量非常類似,不過要在變量名前面加星號*,格式為: int *fp;//*表示這是一個指針變量,fp是一個指向int數據類型的指針 float *a,*b; //表示a和b都是指針變量,都指向一個為float數據類型的指針
指針變量的使用:
取地址運算符&:單目運算符&是用來取操作對象的地址。
例:&i 為取變量 i 的地址。對于常量表達式、寄存器變量不能取地址(因為它們存儲在存儲器中,沒有地址)。
指針運算符 * (間接尋址符):與&為逆運算,作用是通過操作對象的地址,獲取存儲的內容。
例:x = &i x 為 i 的地址,*x 則為通過 i 的地址,獲取 i 的內容。
//賦值 int a = 100;//定義了一個a的整形變量 int *p_a = &a;//將一個int類型的指針p_a,p_a指向了a(也叫p_a指向了a的地址) 在定義指針變量 p_a 的同時對它進行初始化,并將變量 a 的地址賦予它,此時 p_a 就指向了 a。 值得注意的是,p_a 需要的一個地址,a 前面必須要加取地址符&,否則是不對的。
和普通變量一樣,指針變量也可以被多次寫入,只要你想,隨時都能夠改變指針變量的值:
//定義普通變量 float a = 99.5, b = 10.6; char c = '@', d = '#'; //定義指針變量 float *p1 = &a;//P1指向a的地址 char *p2 = &c;//p2指向c的地址 //修改指針變量的值 p1 = &b;//將p1改變指向為b p2 = &d;//將p2改變指向為a
* 是一個特殊符號,表明一個變量是指針變量,定義 p1、p2 時必須帶 *。而給 p1、p2 賦值時,因為已經知道了它是一個指針變量,就沒必要多此一舉再帶上 *,后邊可以像使用普通變量一樣來使用指針變量。也就是說,定義指針變量時必須帶 *,給指針變量賦值時不能帶 *。
//注意 不允許把一個數賦予指針變量 int *p; p = &a; *p = 100;//這樣是錯誤的 或者: int b=200; int *a; a=b; //這樣也錯誤,因為沒有加上取地址符&
指針變量存儲了數據的地址,通過指針變量能夠獲得該地址上的數據:
#includeint main(){ int a = 66;//定義整形變量 int *p = &a; //定義int的指針變量并指向a變量的地址 printf("%d, %d ", a, *p); //兩種方式都可以輸出a的值 return 0; } //假設 a 的地址是 0X1000,p 指向 a 后,p 本身的值也會變為 0X1000,*p 表示獲取地址 0X1000 上的數據, 也即變量 a 的值。所以從運行結果看,*p 和 a 是等價的。 CPU 讀寫數據必須要知道數據在內存中的地址,普通變量和指針變量都是地址的助記符,雖然通過 *p 和 a 獲取到的數據一樣, 但它們的運行過程稍有不同:a 只需要一次運算就能夠取得數據,而 *p 要經過兩次運算,多了一層“間接”。 //程序被編譯和鏈接后,a、p 被替換成相應的地址。使用 *p 的話,要先通過地址 0XF0A0 取得變量 p 本身的值, 這個值是變量 a 的地址,然后再通過這個值取得變量 a 的數據 也就是說,使用指針是間接獲取數據,使用變量名是直接獲取數據,前者比后者的代價要高。
可以用指針來改變被指向那個變量的值 如:
#includeint main(void){ int a = 1, b = 66, c = 2;//定義普通變量 int *p = &a; //定義指針變量并指向a的地址 *p = b; //通過指針變量將a的值改變了 (因為在這里,*p指向了a 就等于*p和a身處同一個內存空間了, 所以對*p修改 就相當于對a修改) c = *p; //把指針p的值的賦值給了C (根據前面說的,相當于將a賦值給了C) printf("%d, %d, %d, %d ", a, b, c, *p);//所以他們的值都是同一個了 return 0; } *在不同的場景下有不同的作用:*可以用在指針變量的定義中, 表明這是一個指針變量,以和普通變量區分開;使用指針變量時在前面加*表示獲取指針指向的數據,或者說表示的是指針指向的數據本身。
也就是說,定義指針變量時的*和使用指針變量時的*意義完全不同。以下面的語句為例:
int *p = &a;//這里表示指向a的地址 *p = 100; //這里表示獲取指針所指向的數據
其他一些騷操作:
int x, y, *px = &x, *py = &y; y = *px + 5; //表示把x的內容加5并賦給y,*px+5相當于(*px)+5 y = ++*px; //px的內容加上1之后賦給y,++*px相當于++(*px) y = *px++; //相當于y=(*px)++ py = px; //把一個指針的值賦給另一個指針
關于“&”和“*”
“&”和“ * ”都是右結合的。假設有變量 x = 10,則*&x 的含義是,先獲取變量 x 的地址,再獲取地址中的內容。因為“&”和“ * ”互為逆運算,所以 x = *&x。
假設有一個 int 類型的變量 a, pa 是指向a的指針,那么*&a和&*pa分別是什么意思呢?
*&a可以理解為*(&a),&a表示取變量 a 的地址(等價于 pa),*(&a)表示取這個地址上的數據(等價 * pa),繞來繞去,又回到了原點,*&a仍然等價于 a。
&*pa可以理解為&(*pa),*pa表示取得 pa 指向的數據(等價于 a),&(*pa)表示數據的地址(等價于 &a),所以&*pa等價于 pa。
野指針與空指針
空指針是說,這個指針沒有指向一塊有意義的內存,比如說:char* k; 這里這個k就叫空指針.我們并未讓它指向任意點.
又或者char* k = NULL; 這里這個k也叫空指針,因為它指向NULL 也就是0,注意是整數0,不是'?'
一個空指針我們也無法對它進行取內容操作,空指針只有在真正指向了一塊有意義的內存后,我們才能對它取內容.也就是說要這樣 k = "hello world!"; 這時k就不是空指針了.
對于空指針值,一般的文檔中傾向于用 NULL 表示,而沒有直接說成 0。但是我們應該清楚:對于指針類型來說,返回 NULL 和 返回 0 是完全等價的,因為 NULL 和 0 都表示 “null pointer”(空指針)。一句話, 空指針是什么,就是一個被賦值為0的指針,在沒有被具體初始化之前,其值為0.(百度解釋)
如 :
int *a;//定義一個指針a=NULL;//讓這個指針指向空a =0;//這樣也是讓一個指針指向空的方式
注意:void* 這不叫空指針,這叫無確切類型指針.這個指針指向一塊內存,卻沒有告訴程序該用何種方式來解釋這片內存.所以這種類型的指針不能直接進行取內容的操作.必須先轉成別的類型的指針才可以把內容解釋出來.
還有'?',這也不是空指針所指的內容. '?'是表示一個字符串的結尾而已,并不是NULL的意思
void*因為是表示不知道要指向什么東西的指針,計算時于char相同(但不相通)
野指針不同于空指針,空指針是指一個指針的值為null,而野指針的值并不為null,野指針會指向一段實際的內存,只是它指向哪里我們并不知情,或者是它所指向的內存空間已經被釋放,所以在實際使用的過程中,我們并不能通過指針判空去識別一個指針是否為野指針。避免野指針只能靠我們自己養成良好的編程習慣
野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)指針變量在定義時如果未初始化,其值是隨機的,指針變量的值是別的變量的地址,意味著指針指向了一個地址是不確定的變量,此時去解引用就是去訪問了一個不確定的地址,所以結果是不可知的。(百度解釋)
下面說說哪些情況下會產生野指針,以及怎樣避免。
1、指針變量的值未被初始化: 聲明一個指針的時候,沒有顯示的對其進行初始化,那么該指針所指向的地址空間是亂指一氣的。如果指針聲明在全局數據區,那么未初始化的指針缺省為空,如果指針聲明在棧區,那么該指針會隨意指向一個地址空間。所以良好的編程習慣就是在聲明指針的時候就對其進行初始化,如果暫時不知道該初始化成什么值,就先把指針置空。
int main(void){ int *a;//野指針 if(a!=NULL){ .... } /* int *a; a=NULL/0; 正確的引用 */ }
2、指針所指向的地址空間已經被free或delete:在堆上malloc或者new出來的地址空間,如果已經free或delete,那么此時堆上的內存已經被釋放,但是指向該內存的指針如果沒有人為的修改過,那么指針還會繼續指向這段堆上已經被釋放的內存,這時還通過該指針去訪問堆上的內存,就會造成不可預知的結果,給程序帶來隱患,所以良好的編程習慣是:內存被free或delete后,指向該內存的指針馬上置空。
void func() { int *ptr = new int[5]; delete [ ]ptr; // 執行完delete后,ptr野指針 //還應該這樣做:ptr=NULL; 正確 }
3、指針操作超越了作用域,如果在一個程序塊中讓一個指針指向那個塊中的某個變量,但是那個變量只是在塊中有效,出了那個程序塊,此變量就無效了,此時指向它的指針也就變成了野指針
void func() { int *ptr = nullptr; { int a = 10; ptr = &a; } // a的作用域到此結束 int b = *ptr; // ptr指向的a,但是a已經被回收,所以ptr變成野指針 //還應該這樣做:ptr=NULL; 正確 }
所以 使用指針時應當注意”規避“:初始化時置 NULL,釋放時置 NULL
3、指針的運算
C 指針是一個用數值表示的地址。因此,您可以對指針執行算術運算。可以對指針進行四種算術運算:++、--、+、-。遞增遞減加減,兩個指針相減
#includeint main(void) { int a=10; int *pa = &a,*pA=&a; double b = 22.2; double *pb = &b; char c = 'C'; char *pc = &c; //最初的值 printf("1- %#x %#x %#x ", &a, &b, &c);//%#x表示 轉換成十六進制帶格式輸出地址, //效果為在輸出前加0x printf("2- %#x %#x %#x ", pa, pb, pc); //指針加法 pa += 2; pb += 2; pc += 2; printf("3- %#x %#x %#x ", pa, pb, pc); //指針減法 pa -= 2; pb -= 2; pc -= 2; printf("4- %#x %#x %#x ", pa, pb, pc); //指針的比較 if (pa == pA) { printf("5=%d ", *pA); } else { printf("6=%d ", *pa); } return 0; } //從運算結果可以看出:pa、pb、pc 每次加 1,它們的地址分別增加 4、8、1,正好是 int、double、char 類型的長度; 減 2 時,地址分別減少 8、16、2,正好是 int、double、char 類型長度的 2 倍。 /*簡單的概括就是: 指針的每一次遞增,它其實會指向下一個元素的存儲單元。 指針的每一次遞減,它都會指向前一個元素的存儲單元。 指針在遞增和遞減時跳躍的字節數取決于指針所指向變量數據類型長度,比如 int 就是 4 個字節。 指針變量除了可以參與加減運算,還可以參與比較運算。當對指針變量進行比較運算時,比較的是指針變量本身的值,也就是數據的地址。 如果地址相等,那么兩個指針就指向同一份數據,否則就指向不同的數據。當然還有其他邏輯運算符 上面的代碼(第一個例子)在比較 pa 和 pA 的值時,pa 已經指向了 a 的上一份數據,所以它們不相等。而 a 的上一份數據又不知道是什么, 所以會導致 printf() 輸出一個沒有意義的數,這正好印證了上面的觀點,不要對指向普通變量的指針進行加減運算 注意:不能對指針變量進行乘法、除法、取余等其他運算,除了會發生語法錯誤,也沒有實際的含義。
#include//指針的加減法其實上的地址上的移動 int main(void) { char a[] = {2,3,4,5,6}; char *p = &a[0]; char *p1 = &a[10]; printf("p1-p=%d ", p1 - p); printf("p=%p ", p); printf("p1=%p ", p1); //這里如果運算為大于零,就是真 輸出 0 //如果運算為小于零,就是假 輸出 -1 int *t = a[0]; int *k = a[3]; printf("* %d ", t-k); int b[] = { 1,2,3,4,5,6 }; int *q = &b[0]; int *q1 = &b[6]; printf("q1-q=%d ", q1 - q); printf("%p ", q1); printf("%p ", q); return 0; }
常見的指針運算:
*(++p): 先移動指針,取下一個單元的值
*(p++): 先取出當前單元的值,再移動指針
( * p)++ : 先取出當前單元的值,再使當前單元的值加1 (指針不移動)
++( * p) : 先使當前單元的值加1,再取出當前單元的值 (指針不移動)
指針的類型轉換:
int *p=&i;
void *q=(void * )p;
這并沒有改變p所指向的變量的類型,而是讓后人用不同的眼光通過p看它所指的變量
意思是:這里的p我不再當你是int了,認為你就是個void類型的
注意 由于優先級的問題 *p++和 * (p++)是等價的
取地址符 &補充:
獲得變量的地址,它的操作必須是變量,
int i,printf("%x",&i); 取得i的地址并輸出。
int i,printf("%p",&i); 取得i的地址并輸出。
地址的大小是否于int相同取決于編譯器
注意 使用指針的時候的類型,無論指向什么類型,所有的指針的大小都是一樣的,因為都是地址,但是指向不同類型的指針是不能相互賦值的,這是為了避免用錯指針。
-
存儲
+關注
關注
13文章
4314瀏覽量
85851 -
C語言
+關注
關注
180文章
7604瀏覽量
136845 -
指針
+關注
關注
1文章
480瀏覽量
70564
原文標題:【零基礎學C語言】知識總結十:指針及其相關知識(一)
文章出處:【微信號:cyuyanxuexi,微信公眾號:C語言編程學習基地】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論