? ? ? ? 今天我們進入指針的復習了.這部分有很多知識,話不多說,讓我們進入指針的世界吧.
內存和地址
? ? ? ? 要想學指針就不能不學內存和地址.
??內存
????????其中,每個內存單元,相當于?個學?宿舍,? 個字節空間??能放8個?特位,就好?同學們 住的??間,每個?是?個?特位。 每個內存單元也都有?個編號(這個編號就相當于宿舍房間的?牌號),有了這個內存單元的編號,CPU就可以快速找到?個內存空間.在計算機中我們把內存單元的編號也稱為地址。C語?中給地址起 了新的名字叫:指針.
????????所以我們可以理解為: 內存單元的編號 == 地址 == 指針
? ?究竟該如何理解編址
指針變量和地址
? 取地址操作符(&)
? ? ? ??在C語?中創建變量其實就是向內存申請空間,下圖為例子:
????????我們如何能得到a的地址呢?這里就要用到取地址操作符了.(打印地址需要用%p)例子為下面代碼:
#include<stdio.h>int main()
{int a = 10;printf("%p", &a);return 0;
}
? ? ? ? 注:&a取出的是a所占4個字節中地址較?的字節的地址。雖然整型變量占?4個字節,我們只要知道了第?個字節地址,就可以順藤摸?訪問到4個字節的數據.
??指針變量和解引用操作符(*)
????????指針變量
? ? ? ? 我們通過取地址操作符取到地址,我們需要把這個地址存到指針變量中.指針變量也是?種變量,這種變量就是?來存放地址的,存放在指針變量中的值都會理解為地址.
int main()
{int a = 10;int* p = &a;printf("%p\n", p);return 0;
}
????????如何拆解指針類型
????????我們看到p的類型是 int* ,我們該如何理解指針的類型呢?
????????這?p左邊寫的是 int* , * 是在說明pa是指針變量,?前?的 int 是在說明pa指向的是整型(int) 類型的對象.同樣的char的地址就會放入char*的指針變量中.
????????解引用操作符?
? ? ? ? 我們拿到地址是為了使用,我們只要拿到了地址(指針),就可以通過地址(指針)找到地址(指針) 指向的對象,這?需要的就是解引?操作符(*)?.
int main()
{int a = 10;int* p = &a;printf("%p\n", p);printf("%d", a);*p = 0;printf("%d", a);return 0;
}
? ? ? ? 在上面的代碼中,*p的意思就是通過pa中存放的地址,找到指向的空間, *p其實就是a變量了;所以*p?= 0,這個操作符是把a改成了0.
??指針變量的大小
? ? ? ?根據前面的內容,32位機器假設有32根地址總線,每根地址線出來的電信號轉換成數字信號后 是1或者0,那我們把32根地址線產?的2進制序列當做?個地址,那么?個地址就是32個bit位,需要4 個字節才能存儲。 如果指針變量是?來存放地址的,那么指針變的??就得是4個字節的空間才可以。 同理64位機器,有64根地址線,?個地址就是64個?進制位組成的?進制序列,存儲起來就需要 8個字節的空間,指針變的大小就是8個字節.
//sizeof的返回值是size_t,打印最好用%zd來打印
int main()
{printf("%zd\n", sizeof(char*));printf("%zd\n", sizeof(short*));printf("%zd\n", sizeof(int*));printf("%zd\n", sizeof(double*));return 0;
}
? ? ? ? 輸出結果:?
????????結論:
????????32位平臺下地址是32個bit位,指針變量大小是4個字節
????????64位平臺下地址是64個bit位,指針變量大小是8個字節
????????注意:指針變量的大小和類型是無關的,只要指針類型的變量,在相同的平臺下,大小都是相同的.
指針變量類型的意義
? ? ? ? 既然所有指針的大小在同一平臺下都是一樣的,那么為什么要各種各樣的指針類型呢?
??指針的解引用
? ? ? ? 看下面兩端代碼,并且在執行時用調試來看他們的內存空間的變化.
????????調試我們可以看到,代碼1會將n的4個字節全部改為0,但是代碼2只是將n的第1個字節改為0.
????????結論:指針的類型決定了,對指針解引?的時候有多?的權限(?次能操作?個字節).(char* 的指針解引?就只能訪問?個字節,? int* 的指針的解引?就能訪問四個字節).
??指針+-整數
? ? ? ? 先看一下下面的代碼:
int main()
{int a = 10;int* p1 = &a;char* p2 = &a;printf("%p %p\n", p1, p2);p1 += 1; //p1加整數p2 += 1; //p2加整數printf("%p %p\n", p1, p2);return 0;
}
? ? ? ? 結果如下,可以看到int*的指針加1向后走了4個字節,而char*只走了1個字節.?
????????結論:指針的類型決定了指針向前或者向后??步有多?(距離).
??void* 指針
????????在指針類型中有?種特殊的類型是 void* 類型的,可以理解為?具體類型的指針(或者叫泛型指針),這種類型的指針可以?來接受任意類型地址.但是 void* 類型的指針不能直接進?指針的+-整數和解引?的運算.
int main()
{int a = 10;char b = 'n';void* p1 = &a;void* p2 = &b;printf("%p %p", p1, p2);//err//*p1=0;//p2++;return 0;
}
?????????般 void* 類型的指針是使?在函數參數的部分,?來接收不同類型數據的地址,這樣的設計可以 實現泛型編程的效果.在之后的復習中會了解到.
const修飾指針
??const修飾變量
????????變量是可以修改的,如果把變量的地址交給?個指針變量,通過指針變量的也可以修改這個變量。 但是如果我們希望?個變量加上?些限制,不能被修改,這就需要到const了.(例如下面的變量b.)
int main()
{//變量可以修改,指針也可以間接修改變量int a = 10;printf("%d\n", a);int* pa = &a;*pa = 0;printf("%d\n", a);//這里b是不可以修改的const int b = 10;//b = 0; //err//如果像下面的話,就意味著可以通過p2去改變b的值了,這是不合理的//int* pb1 = &b;//*pb1 = 0; 這里不會報錯并且會改變b的值//所以這里一定要用const來修飾指針const int* pb2 = &b;//*pb2 = 0; //errreturn 0;
}
??const修飾指針變量
? ? ? ? 在上面的代碼中我就用了const來修飾指針,下面讓我們來看下下面的代碼更加深入的學習.
void test1()
{int n = 10;int m = 20;int* p = &n;*p = 20;//ok?p = &m; //ok?
}
void test2()
{int n = 10;int m = 20;const int* p = &n;//*p = 20; //ok?no const在*前修飾*p不可以通過指針改變指針指向的變量p = &m; //ok?
}
void test3()
{int n = 10;int m = 20;int* const p = &n;*p = 20; //ok?//p = &m; //ok?no const在*后修飾p不可以改變p的指向
}
void test4()
{int n = 10;int m = 20;int const* const p = &n;//*p = 20; //ok?no//p = &m; //ok?no//*前后都有const,既不能改變指向也不能通過指針改變指針所指變量
}
int main()
{test1();test2();test3();test4();return 0;
}
????????結論:const修飾指針變量的時候?
????????1.const如果放在*的左邊,修飾的是指針指向的內容,保證指針指向的內容不能通過指針來改變。 但是指針變量本?的內容可變.
???????? 2. const如果放在*的右邊,修飾的是指針變量本?,保證了指針變量的內容不能修改,但是指針指向的內容,可以通過指針改變.
指針運算
指針的基本運算有三種,分別是:? 1.指針+- 整數? ? ?2.指針-指針? ? ?3.指針的關系運算
??指針+- 整數
? ? ? ? 這個可以用來便利內存中連續存放的類型,例如數組.代碼見下:
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };int* pa = &arr[0];for (int i = 0; i < 10; i++){printf("%d ", *(pa + i));}printf("\n");return 0;
}
??指針-指針
? ? ? ? 可以用來計算字符串的長度.下面是使用和注意事項:
int main()
{int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };char arr1[5] = { 'a','s','d','f','g' };int* p1 = &arr[0];int* p2 = &arr[5];char* p3 = &arr1[0];printf("%d\n", p2 - p1);//指針-指針,返回的是兩個指針之間的元素個數//printf("%d\n", p2 - p3);//err //因為兩個指針類型不同,不知到返回哪一個并且兩個數組不是連續空間,無法判斷兩個數組中間相隔多少return 0;
}
??指針的關系運算
? ? ? ? 可以提供另一種便利數組的方法:
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };int* pa = &arr[0];while (pa < (arr + 10))//指針的關系運算{printf("%d ", *pa);pa++;}printf("\n");return 0;
}
?野指針
????????概念: 野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)
? 野指針成因
????????1. 指針未初始化
????????2. 指針越界訪問(一般出現在數組的越界)
????????3. 指針指向的空間釋放(返回函數中局部變量的地址)
int* func()
{int a = 0;return &a;
}
int main()
{//err返回已經銷毀了的局部變量的地址//int* p = func();//可能有時候看不出錯誤,但是如果再調用函數破壞原來的函數棧幀,就會出現問題了return 0;
}
??如何規避野指針
? ? ? ? 1.指針初始化
????????如果明確知道指針指向哪?就直接賦值地址,如果不知道指針應該指向哪?,可以給指針賦值NULL. NULL 是C語?中定義的?個標識符常量,值是0,0也是地址,這個地址是?法使?的,讀寫該地址會報錯.
? ? ? ? 2.小心指針越界
????????3.指針變量不再使?時,及時置NULL,指針使?之前檢查有效性
檢查代碼大概為
if ( p != NULL )
{
????????//代碼
}
? ? ? ? 4.避免返回局部變量的地址
? ? ? ? 以上就是今天的復習啦,還有一個有用的斷言,我將新開一個小篇幅的博客簡單說一下~
C語言復習--assert斷言-CSDN博客