摘要:在語言中,允許用指針變量來存放指針,因此,一個指針變量的值就是某個內存單元的地址或稱為某內存單元的指針。指針詳解一基礎知識在語言中,定義變量時,如果在變量名前加上一個,那么這個變量就變成了對應變量類型的指針變量。
指針,是C語言中的一個重要概念及其特點,也是掌握C語言比較困難的部分。作為一個C語言初學者,我對指針也有了一定的了解,正好這幾天在做一個C語言指針的知識點匯總,于是就有了這篇文章,向大家分享一些我的見解,與大家一起共勉。
在計算機中,每一個數據都是存放在儲存器中的,而不同的數據類型所占的內存空間不同(例如:int類型占用4個字節,double類型占用8個字節等等),內存空間又是以字節為單位的,每一個字節又對應了一個編號,這個編號我們稱為這一個內存單元的地址。
而系統在內存中又為變量分配了存儲空間的首個字節單元的地址即變量的地址。為了方便用戶對存儲空間進行正確的訪問,指針便應運而生了。
指針相對于一個內存單元來說,指的是單元的地址,該單元的內容里面存放的是數據。在 C 語言中,允許用指針變量來存放指針,因此,一個指針變量的值就是某個內存單元的地址或稱為某內存單元的指針。
指針變量是存放一個內存地址的變量,不同于其他類型變量,它是專門用來存放內存地址的,也稱為地址變量。定義指針變量的一般形式為:類型說明符*變量名。
在C語言中,定義變量時,如果在變量名前加上一個 “ * ”,那么這個變量就變成了對應變量類型的指針變量。
對于一個指針變量,我們需要讓它來保存其他變量的地址的時候,就需要用 到 &運算符。
下面舉個例子:
例1
#include int main(){ int num = 10;//在內存中開辟一塊空間 int* p = #//這里我們對變量num取地址,使用了&運算符 //將num的地址存放在p變量中,p就是一個指針變量。 return 0;}
&num就取得了num的地址,指針p指向的num的地址,就形成了一個簡單的指針變量。
但是對于某些特殊情況,我們可以不需要用&運算符,例如:數組,函數等等,我們后邊會講到。
上面我們了解了如何取得一個數據的地址,那么接下來我們可以嘗試運用指針來解地址,從而得到這個變量的內存數據。
在指針前加一個“ * "即解引用地址,也就是從指針指向的內存中,取出這段內存地址對應的數據。
輸出例1中的指針p:
例2
#include int main(){ int num = 10; int* p = # printf("%d", *p); return 0;}
運行結果為:10
指針就是一個變量,用來存放地址的變量。(存放在指針中的值,均會被看作地址)
一個小的單元會有多大:(一個字節)
那么地址是如何編寫的呢?
目前經過仔細的技術及思考后,最合適的結論為:一個字節對應了一個地址。
對于32位機器,可以看作有32根地址線,每一根地址線在尋址的時候都會產生一個電信號(正電1/負電0),所以它的地址從000……000(32個0)到111……111(32個1)共有2的32次方個地址。
在32位機器上,地址是32個0或1組成的二進制序列,一個地址需要用4個字節的空間來存儲,所以一個指針變量的大小就是4個字節。
同樣,可以類比推理得到:64位機器有2的64次方個地址,一個指針變量的大小位8個字節。
我們來做一個練習:
例3
#include int main(){ printf("%d/n", sizeof(char*)); printf("%d/n", sizeof(short*)); printf("%d/n", sizeof(double*)); printf("%d/n", sizeof(int*)); return 0;}
運行結果為:
4
4
4
4
總結
(1)指針是一個變量,它存放的是地址。
(2)指針在32位機器中占4個字節,在64位機器中占8個字節,與類型無關。
我們前邊提到,指針的定義方式是:type + * :
char* 類型的指針是為了存放char類型變量的地址;
int* 類型的指針是為了存放int類型變量的地址;
short* 類型的指針是為了存放short類型變量的地址;
double* 類型的指針是為了存放double類型變量的地址。
指針的類型決定了指針向前或者向后的空間有多大。
指針類型決定了指針進行解引用操作的時候,能夠訪問空間的大小:
(1) int* p:* p能夠訪問4個字節。
(2)char* p:* p能夠訪問1個字節。
(3)double* p: *p能夠訪問8個字節。
指針類型的意義
先來看一個例子:
例4
#include int main(){ int a = 10; char* p1 = (char*)&a; int* p2 = &a; printf("%p/n", &a); printf("%p/n", p1); printf("%p/n", p1+1); printf("%p/n", p2); printf("%p/n", p2+1); return 0;}
輸出的結果為:
003CFC80
003CFC80
003CFC81
003CFC80
003CFC84
我們可以看到不同類型的指針p1和p2都指向了變量a的地址,但是p1+1指向的是003CFC81,而p2+1指向了003CFC84。一個地址向后移動了1,一個地址向后移動了4。
這是因為:指針的類型決定了指針向前或者向后移動一步有多大的距離。
(1)概念:
野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)。
(2)成因:
①指針未初始化
例5
#include int main(){ int* p;//局部變量指針未初始化,默認未隨機值。 *p = 10; return 0;}
②指針越界訪問
例6
#include int main(){ int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i <= 11; i++) { //當指針指向的范圍超出數組arr的范圍時,p就是野指針 *(p++) = i; } return 0;}
③指針指向的空間釋放
例7
#include int* func(){ int a = 10; return &a;}int main(){ int* p = func(); *p = 20; return 0;}
這里的func函數雖然確實返回了地址,而p也確實接受到了返回的地址,但是當返回的時候,已經來不及保存了,因為func函數一結束,函數申請的內存等等就返回給操作系統了,已經無法再通過指針p去訪問a的地址了,后邊再用*p=20去訪問的是已經被釋放的a的地址。
(3)如何避免野指針
①指針初始化
例8
#include int main(){ int a = 10; int* p1 = &a; int* p2 = NULL;//NULL:用來初始化指針的,給指針賦值。 return 0;}
②小心指針越界
③指針指向空間釋放即設置NULL
④指針使用之前檢查有效性
例9
#include int main(){ int* p = NULL; int a = 10; p = &a; if (p != NULL) { *p = 20; } return 0;}
(1)指針±整數
例10:輸出數組的每一個元素
#include 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 + i)); } return 0;}
運行結果為:1 2 3 4 5 6 7 8 9 10
(2)指針-指針
指針-指針得到的是中間的元素個數。(注意盡量大-小)
易錯點提示:兩個指針不能進行加法運算,這是非法的!!!兩個指針在進行減法運算時,類型要相同,否則結果不可預知!!!
例11
#include int main(){ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p1 = &arr[0]; int* p2 = &arr[9]; printf("%d/n", *p2 - *p1); return 0;}
運行結果為:9
我們在之前學過了用遞歸和迭代兩種方法來實現strlen函數,那么今天我們就學會了第三種方法——指針相減法:
例12:自己的strlen函數(指針相減法)
#include int my_strlen(char* str){ char* ret = str; while (*str != "/0") { str++; } return str - ret;}int main(){ char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d", len); return 0;}
(3)指針的關系運算——"<" “>” “<=” “>=” “==” “!=”
指針進行關系運算的前提是它們都指向同一個數組中的元素。
易錯點提示:指針的關系運算是相同類型的指針之間的關系運算,不同類型的指針之間的關系運算沒有意義,指針與非0整數的關系運算也沒有意義。
例13
#include int main(){ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = &arr[0]; int* q = &arr[9]; p < q; //當p所指的元素在q所指的元素之前時,表達式的值為1;反之為0。 p > q; //當p所指的元素在q所指的元素之后時,表達式的值為1;反之為0。 p == q; //當p和q指向同一元素時,表達式的值為1;反之為0。 p != q; //當p和q不指向同一元素時,表達式的值為1;反之為0。 printf("%d %d %d %d", p < q, p > q, p == q, p != q); return 0;}
運行結果為:1 0 0 1
例14
#include int main(){ int a = 10; int* p1 = &a; int** p2 = &p1;//p2就是二級指針。 printf("%d/n", **p2); **p2 = 20;//二級指針p2改變,其指向的a隨之改變。 printf("%d/n", **p2); printf("%d/n", a); return 0;}
運行結果為:
10
20
20
指向字符型數據的指針變量。每個字符串在內存中都占用一段連續的存儲空間,并有唯一確定的首地址。即將字符串的首地址賦值給字符指針,可讓字符指針指向一個字符串。
下面舉一個例子:
例15
#include int main(){ char arr[] = "abcdef"; char* p1 = arr; printf("%s/n", arr); printf("%s/n", p1); //這里的字符指針p1指向的是arr[]中首位的地址,所以在打印時不用解引用,它表示的是從arr[]的首位開始打印至"/0"停止。 char* p2 = "abcdef";//"abcdef"是一個常量字符串。 printf("%c/n", *p);//說明p存的只是首元素a的地址。 printf("%s/n", p);//同上 return 0;}
運行結果為:
abcdef
abcdef
a
abcdef
易錯點提示:在常量字符串前加一個“const”。
例16:常量字符串
#include int main(){ const char* p = "abcdef"; //*p = "w"; printf("%s/n", p); return 0;}
運行結果為:abcdef
有了const以后,我們就不能對指針p進行賦值修改了,這樣可以使其更加安全的儲存數據。
例17:經典易錯題
#include int main(){ char arr1[] = "abcdef"; char arr2[] = "abcdef"; char* p1 = "abcdef"; char* p2 = "abcdef"; if (arr1 == arr2) printf("1/n"); else printf("0/n"); if (p1 == p2) printf("1/n"); else printf("0/n"); return 0;}
運行結果為:
0
1
這是因為arr1和arr2是分別開辟的內存,雖然元素相同,但所處內存空間不同,所以arr1 != arr2;但是字符指針p1和p2都指向了字符串"abcdef",指向相同,所以p1 == p2。
數組名表示的是數組首元素的地址。
例18
#include int main(){ int arr[10] = { 0 }; printf("%p/n", arr);//arr:首元素的地址。 printf("%p/n", arr + 1); printf("%p/n", &arr[0]);//&arr[i]:數組中對應元素的地址。 printf("%p/n", &arr[0] + 1); printf("%p/n", &arr);//&arr:整個數組的地址。 printf("%p/n", &arr + 1); //1. &arr — &數組名:數組名不是首元素地址,數組名表示整個數組,&數組名-取出的是整個數組的地址。 //2.sizeof(arr) — sizeof(數組名):數組名表示的整個數組,sizeof(數組名)計算的是整個數組的大小。 return 0;}
運行結果為:
00B3F828
00B3F82C
00B3F828
00B3F82C
00B3F828
00B3F850
也就是說,對于上述代碼,arr與&arr[0]通過加1運算后發現地址加了4,而&arr通過加1運算后發現地址加了40,從而說明了&arr表示的是整個數組的地址。
概念:數組元素全為指針變量的數組稱為指針數組。
也就是指針數組是一個數組,用來存放指針的數組。
如:*p[10]是一個指針數組。
舉一個例子:
例19
#include int main(){ int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 2,3,4,5,6 }; int arr3[] = { 3,4,5,6,7 }; int* arr[] = { arr1,arr2,arr3 }; int i = 0; //分別遍歷出arr1,arr2,arr3。 for (i = 0; i < 3; i++) { int j = 0; //分別遍歷出arr1,arr2,arr3中的每個元素。 for (j = 0; j < 5; j++) { printf("%d ", *(arr[i] + j)); } printf("/n"); } return 0;}
運行結果為:
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
概念:指的是數組名的指針,即數組首元素地址的指針。
也就是數組指針是一個指針,指向數組的指針。
如:(*p)[10]是一個數組指針。
舉一個例子:
例20
#include void print1(int arr[3][5], int x, int y)//二維數組打印。{ int i, j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf("%d ", arr[i][j]); } printf("/n"); }}void print2(int (*p)[5], int x, int y)//數組指針打印{ int i = 0; for (i = 0; i < x; i++) { int j = 0; for (j = 0; j < y; j++) { printf("%d ", *(*(p+i)+j)); } printf("/n"); }}int main(){ int arr[3]
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/123067.html
目錄 前言 一、 什么是指針? 引例 計算機是怎么對內存單元編號的呢? 內存空間的地址如何得到 想存地址怎么辦? ? 本質目的不是為了存地址 ?二、指針和指針類型 為什么有不同類型的指針 1.指針的解引用 2.指針+-整數 三、野指針 造成野指針的原因 1.未主動初始化指針 ?2.指針越界訪問 3.指針指向的空間釋放 規避野指針 四、指針運算 1.指針+-整數 ?2.指針-指針 ?3.指針的關系運...
摘要:這里分塊講解六函數棧幀的銷毀過程一解析的作用是將棧頂的數據彈出,彈出數據儲存到相應寄存器中。 ?前言? 讀完這篇博客,你可以明白什么? ①局部變量到底是怎么在棧上創建的? ②為什么局部變量不初始化為隨機值? ③函數是怎么傳參的?傳參的先后順序是什么? ④形參和實參是什么關系? ⑤函數調用是怎...
目錄 一、什么是C語言? 二、第一個C語言程序 代碼 程序分析 ?程序運行 一個工程中出現兩個及以上的main函數 代碼 運行結果 分析 三、數據類型 數據各種類型 為什么會有這么多的數據類型? 計算機單位 ?各個數據類型的大小 ?注意事項 數據類型的使用 四、變量和常量 變量的分類 變量的使用 變量的作用域和生命周期 ?常量 五、字符串+轉義字符+注釋 字符串 ?轉義字符 注釋 六、選擇語句 ?...
閱讀 2546·2023-04-26 00:57
閱讀 917·2021-11-25 09:43
閱讀 2226·2021-11-11 16:55
閱讀 2223·2019-08-30 15:53
閱讀 3599·2019-08-30 15:52
閱讀 1463·2019-08-30 14:10
閱讀 3383·2019-08-30 13:22
閱讀 1216·2019-08-29 11:18