C语言——指针(入门详解)
文章目錄
- 1.什么是指針?
- 1.1.理解指針的兩個要點:
- 1.2.指針變量:
- 1.3.內存是如何編址?
- 2.指針和指針類型
- 2.1指針的創建與初始化
- 2.2.指針類型
- 3.野指針
- 3.1.什么視野指針?
- 3.2.野指針成因
- 3.3.規避野指針
- 4.指針運算
- 4.1.指針+-整數
- 4.2.指針-指針
- 4.3指針的關系運算
- 5.指針與數組
- 6.二級指針
- 6.1.什么是二級指針
- 6.2.二級指針的運算
- 7.指針數組
- 總結
1.什么是指針?
1.1.理解指針的兩個要點:
1.指針是內存中最小單元的編號,也就是地址。
2.平時口語中的指針,通常指的是指針變量,指針變量是用來存放內存地址的變量。
總結:指針其實就是地址,口語中的指針通常指的是指針變量。
補充:一個內存單元占一個字節。
1.2.指針變量:
通過&取地址操作符取出變量在內存中的起始地址,將取出的地址存放在一個變量中,這個存放地址的變量就是指針變量。
下面通過代碼演示指針變量的創建與使用
int main() {int a = 10;int* pa = &a;//創建指針變量并初始化//打印驗證printf("%p\n", &a);printf("%p\n", pa);//使用指針變量需要用*解引用操作符*pa = 20;//此時*pa等價與aprintf("%d\n", *pa);printf("%d\n", a);return 0; }
補充:
1、a在內存中占4個字節,&a取出的是整型變量a的第一個字節的地址(最小的地址)。
2.當需要通過地址訪問來改變變量的值時,需要使用*解引用操作符對指針變量進行解引用操作。
1.3.內存是如何編址?
對于32位的機器,假設有32根地址線,那么假設每根地址線在尋址的時候產生高電平(高電壓)和低電平(低電壓)就是(1或者0)。
每個地址標識1個字節,操作系統就會分配2的32次方個字節,也就是4GB的空間進行編址。同理可得64位機器,每個地址標識2個字節,操作系統就會分配2的64次方個字節,也就是8GB的空間進行編址。
在32位的機器上,地址是32個0或者1組成二進制序列,那地址就得用4個字節的空間來存儲,所以一個指針變量的大小就應該是4個字節。那如果在64位機器上,如果有64個地址線,那一個指針變量的大小是8個字節,才能存放一個地址。
總結:
1、指針變量是用來存放地址的,地址是唯一標識一個內存單元的。
2、在32位平臺下,地址的大小是4個字節,指針變量的大小是4個字節。在64位平臺下,地址的大小是8個字節,指針變量的大小也是8個字節。
2.指針和指針類型
2.1指針的創建與初始化
我們都知道變量是分類型的,如整型、單精度浮點型、字符型等。那指針是否也分類型呢?答案是有的。
下面我通過代碼舉例
上例中,我將變量a的地址存放到指針變量pa中。pa的類型是int*(整形指針)。由此我們可以得到
指針的定義方式是:
type(類型) + * +prt_name(指針變量名字)
其實:
char* 類型的指針是為了存放 char 類型變量的地址。
short* 類型的指針是為了存放 short 類型變量的地址。
int* 類型的指針是為了存放 int 類型變量的地址。
讓我們來看看第二個例子
int main() {int a = 10;char* pc = &a;//char*類型的指針能否得存放int類型變量的地址呢?return 0; }答案是:能夠存放。因為一個指針變量在32位環境下都是占4個字節。這里不要門縫里看char*類型指針,把它給看遍了。這也是我們需要注意的一個小細節。
既然指針變量的大小在32位平臺下都是4個字節,那指針變量的類型存在的意義是什么呢?
2.2.指針類型
這里我依舊是通過兩段代碼進行舉例
//例一 #include <stdio.h>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; }
通過上面的例子我們可以發現,指針的類型決定了指針+1后訪問的步長。int類型指針+1向后移動了4個字節,char類型指針+1向后移動了1個字節。同理可得short類型指針+1向后移動2個字節,float類型指針+1向后移動4個字節。
總結
指針變量的類型決定了指針的步長(向前或向后走一步的距離)。
//例二int main() {int a = 0x11223344;//0x表示十六進制類型char* pc = (char*)&a; //a是整型,需要對&a進行強制類型轉換成char*后編譯器才不會報錯int* pi = &a;*pc = 0; *pi = 0; return 0; }先調試這段代碼
讓我們看看pc = 0的效果
接下來是pi = 0;這句代碼的效果
通過調試的內存窗口,可以發現當對char類型指針變量進行解引用操作后,訪問的權限是1個字節,而對int類型指針變量進行解引用訪問操作,訪問的權限是4個字節。所以指針變量的類型其實是由意義的。
總結:
指針變量的類型決定了解引用操作后的指針的訪問權限。int類型指針變量解引用操作訪問權限是4個字節,char類型指針變量解引用操作訪問權限是1個字節。
3.野指針
3.1.什么視野指針?
野指針的概念
野指針指的是指向不可知的指針變量(隨機的、不正確的、不可控的)。
3.2.野指針成因
1.指針變量未進行初始化
int main() {int* pa;*pa = 20;return 0; }上例中,指針變量pa未進行初始化,所以pa是一個野指針,對pa進行僅引用操作是極其危險的,因為pa的指向是不可知的。
指針越界訪問
int main() {int arr[5]={1,2,3,4,5};int *p = arr;int i = 0;for(i = 0; i <= 5; i++){*(p++) = i;}return 0; }當運行這段代碼后,編譯器會進行報錯
這里編譯器告訴我們因為指針訪問越界,數組arr被破壞了。所以當p超出數組的合理范圍后,p就是一個野指針,對野指針進行解引用操作是很危險的。
3.3.規避野指針
1.創建指針變量同時初始化指針變量。
2. 小心指針越界。
3. 指針指向空間釋放,及時置NULL。
4. 避免返回局部變量的地址。
5. 指針使用之前檢查有效性。
4.指針運算
4.1.指針±整數
代碼舉例如下
#include<stdio.h>int main() {int arr[5] = { 0 };int* pa = arr;int i = 0;//將數組內容賦值成1~5for (i = 0; i < 5; i++){*pa = i + 1;pa = pa + 1;//指針加整數}//打印數組內容for (i = 0; i < 5; i++){printf("%d ", arr[i]);}return 0; }4.2.指針-指針
指針-指針的前提條件是,兩個指針變量指向同一塊空間。
int main() {int arr[10] = { 0 };int* pa = &arr[9];int* pb = &arr[0];//此時pa和pb都是指向數組arr的內容printf("%d\n",pa - pb);return 0; }指針-指針得到的絕對值是,兩個指針之間相同類型的元素的個數。
下面我通過指針-指針的方式模擬實現庫函數strlen
#include<assert.h>size_t MyStrlen(const char* str) {assert(str != NULL);char* start = str;//記錄初始地址while (*str++){;}//通過指針-指針返回字符串長度return str - start - 1;}int main() {char str[] = "abcde";printf("%d\n",MyStrlen(str));return 0; }4.3指針的關系運算
#define N_VALUES 5 int main() {float values[N_VALUES];float *vp;// 指針的關系運算for(vp = &values[N_VALUES]; vp > &values[0];){*--vp = 0;}return 0; }
將上邊的代碼稍作修改
代碼修改后,看起來是更易讀了,可是我們應該避免這樣寫代碼。雖然上面的代碼在市面上絕大部分編譯器都可以正常運行,但是C語言標準中并不能保證它是對的。
C語言標準規定:
允許指向數組元素的指針與指向數組的最后一個元素后面那個內存空間的指針作比較,但是不允許與第一個元素前面那個內存空間的指針進行比較。
5.指針與數組
指針和數組是不同的對象。指針是一種用來存放地址的變量,大小是4/8個字節。數組是一組相同類型元素的集合,可以放多個元素,大小取決于元素個數和元素類型的。數組的數組名是數組首元素的地址,地址是可以存放在指針變量中的。可以通過指針來訪問數組。
#include<stdio.h> int main() {int arr[10] = {0};printf("%p\n",arr);printf("%p\n",&arr);return 0; }
由上例可知,數組名本質是首元素的地址(排除&數組名和sizeof(數組名)這兩種情況)。既然數組名是數組首元素的地址,我們不妨將數組名存入一個指針變量中,再通過這個指針變量來遍歷整個數組。
可以發現其實 p+i 是完全等價于 arr[ i ]的。接下來試試通過指針運算來便利整形數組并且打印數組
補充:
[ ]下標訪問操作符兩邊,arr 和 i 是下標訪問操作符的兩個操作數。[ ]下標訪問操作符是支持交換律的,所以 arr[ i ] 等價于 i[arr]。
6.二級指針
6.1.什么是二級指針
指針變量是用來存放地址的,那指針變量的地址該存放到哪里呢?這就要引出二級指針這個概念了。二級指針是用來存放指針變量的地址的。
int main() {int a = 10;int* pa = &a;int** ppa = &pa;return 0; }6.2.二級指針的運算
通過 * 解引用操作符對二級指針進行解引用操作。
int main() {int a = 10;int* pa = &a;int** ppa = &pa;**ppa = 20;//*(*ppa) =*(pa) = a; //通過對* *ppa進行解引用操作得到pa,再通過對pa進行解引用操作得到a//**ppa等價于aprintf("%d\n", a); }7.指針數組
指針數組是什么呢?是數組?還是指針?答案是:指針數組是用來存放指針的數組。通過前面的學習,我們了解到的數組類型有字符數組、整型數組、單精度浮點型數組等。
int arr[] = {1,2,3,4,5}; char ch[] = {'a', 'b', 'c'}; int* arrp[] = {0x0012ff40, 0x0012ff41, 0x0012ff42};下面我通過一個指針數組加三個一維數組來模擬實現二維數組
#include<stdio.h>int main() {int arr1[3] = { 1,2,3 };int arr2[3] = { 4,5,6 };int arr3[3] = { 7,8,9 };int* arrp[3] = { arr1,arr2,arr3 };int i = 0;int j = 0;for (i = 0; i < 3; i++){for (j = 0; j < 3; j++){printf("%d ",arrp[i][j]);//arr[i]相當于是arr[列號]。通過指針運算,就可以遍歷整個數組。}printf("\n");}return 0; }
通過了訪問指針數組arrp中存放的三個整形數組的首元素地址,然后再通過指針運算,就可以模擬實現一個二維數組了。
總結
總有人說指針多難多難,在我看來只要搞清楚指針的概念以及指針的用法,理解指針也不是特別難的。這只需要我們多去思考和總結。學習沒有捷徑,只有你真的認真學了才能夠真正的掌握知識。最后,也希望看完本篇文章的你有所收獲,有什么問題也歡迎在評論區進行討論。
總結
以上是生活随笔為你收集整理的C语言——指针(入门详解)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 继电器的常开常闭状态
- 下一篇: 简约大气中国风企业介绍动态PPT模板