引言
? ? ? ? 介紹:const修飾指針,野指針
? ? ? ? 應用:指針的使用(strlen的模擬實現),傳值調用和傳指調用
一、const修飾指針
1.const修飾變量
????????簡單回顧一下前面學過的const修飾變量:在變量前面加上const,那么這個變量就變成了常量,下面就不能再對這個變量進行修改了,否則就會爆錯。
來看代碼:
#include<stdio.h>const int n = 10;int main()
{printf("n = %d\n",n);return 0;
}
如果想對n重新賦值,就會報錯。
錯誤案例:(編譯器不允許)
但是如果我們繞過n,使用 n 的地址,去修改 n 就能做到了,雖然這樣做是在打破語法規則。?
來看代碼:
#include<stdio.h>
int main()
{const int n = 10;printf("n = %d\n",n);int* p = &n;*p = 20;printf("n = %d\n", n);return 0;
}
運行結果:
?下面來學習const修飾指針,來解決這個問題。
2.const修飾指針變量
const修飾指針變量,可以放在 * 的左邊,也可以放在 * 的右邊,意義是不一樣的。
int* p; //正常指針,沒有被修飾int const* p; //const 放在*的左邊來修飾int* const p; // const 放在*的右邊來修飾
const放在*左邊:
#include<stdio.h>int main(){const int n = 10;printf("n = %d\n",n);int const * p = &n;*p = 20;printf("n = %d\n", n);return 0;}
將 *p給修飾了,下面改*p的內容,編譯器是不允許的。
const放在*右邊:
#include<stdio.h> int main() {const int n = 10;int m = 20;printf("n = %d\n", n);int *const p = &n;*p = 20;printf("n = %d\n", n);return 0; }
?運行結果:
*p是可以被修改了,但是指針變量p就不能被修改了:#include<stdio.h> int main() {const int n = 10;int m = 20;printf("n = %d\n", n);int *const p = &n;*p = 20;printf("n = %d\n", n);p = &m;return 0; }
const放在*的右邊,將p給修飾了,不能對p的內容(即p指向的地址)修改了。
二、野指針
概念:野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)
成因:
1. 指針未初始化
#include <stdio.h> int main() {int* p;//局部變量指針未初始化,默認為隨機值*p = 20;return 0; }
指針p指向的地址是隨機的
2. 指針越界訪問
#include <stdio.h> int main() {int arr[10] = { 0 };int* p = &arr[0];int i = 0;for (i = 0; i <= 11; i++){//當指針指向的范圍超出數組arr的范圍時,p就是野指針*(p++) = i;}return 0; }
3.指針指向的空間釋放
#include <stdio.h> int* test() {int n = 100;return &n; } int main() {int* p = test();printf("%d\n", *p);return 0; }
出test()函數的時候,n在內存中的就銷毀了,這時,p指向的地址就是隨機的了
。。。。。。
避免野指針:?
????????如果明確知道指針指向哪里就直接賦值地址,如果不知道指針應該指向哪里,可以給指針賦值NULL,?NULL 是C語言中定義的一個標識符常量,值是0,0也是地址,這個地址是無法使用的,讀寫該地址會報錯。
1.指針初始化?
#include <stdio.h> int main() {int num = 10;int* p1 = #int* p2 = NULL;return 0; }
2.小心指針越界
? ? ? ? 一個程序向內存申請了哪些空間,通過指針也就只能訪問哪些空間,不能超出范圍訪問,超出了就是越界訪問。(寫代碼的時候,自己注意一點,有時候自己都發現不了)
3.指針變量不再使用時,及時置NULL,指針使用之前檢查有效性?
????????當指針變量指向一塊區域的時候,我們可以通過指針訪問該區域,后期不再使用這個指針訪問空間的時候,我們可以把該指針置為NULL。因為約定俗成的?個規則就是:只要是NULL指針就不去訪問,同時使用指針之前可以判斷指針是否為NULL。
????????我們可以把野指針想象成野狗,野狗放任不管是非常危險的,所以我們可以找?棵樹把野狗拴起來,就相對安全了,給指針變量及時賦值為NULL,其實就類似把野狗栓起來,就是把野指針暫時管理起來。
????????不過野狗即使拴起來我們也要繞著走,不能去挑逗野狗,有點危險;對于指針也是,在使用之前,我 們也要判斷是否為NULL,看看是不是被拴起來起來的野狗,如果是不能直接使用,如果不是我們再去使用。
int main() {int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;for (i = 0; i < 10; i++){*(p++) = i;}//此時p已經越界了,可以把p置為NULLp = NULL;//下次使?的時候,判斷p不為NULL的時候再使?//...p = &arr[0];//重新讓p獲得地址if (p != NULL) //判斷{//...}return 0; }
4.避免返回局部變量的地址
三、指針的使用(strlen的模擬實現)
????????庫函數strlen的功能是求字符串長度,統計的是字符串中函數原型如下:
size_t strlen ( const char * str );
?????????參數str接收?個字符串的起始地址,然后開始統計字符串中 \0 之前的字符個數,最終返回?度。 如果要模擬實現只要從起始地址開始向后逐個字符的遍歷,只要不是 到 \0 就停?。
#include<assert.h>
int my_strlen(const char* str)
{int count = 0; //用于統計字符串的長度assert(str);while (*str){count++;str++; //指針向后走}return count;
}
int main()
{int len = my_strlen("abcdef"); printf("%d\n", len);
}
實現方式不止這一種,還可以最后一個地址的位置減去第一個地址的位置,等等。?
?四、傳值調用和傳址調用
下面通過一個案例來理解一下,什么是傳值調用,什么是傳址調用?
?寫一個交換兩個值的代碼:
#include <stdio.h>
void Swap1(int x, int y)
{int tmp = x;x = y;y = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交換前:a = % d b = % d\n", a, b);Swap1(a, b);printf("交換后:a = % d b = % d\n", a, b);return 0;
}
這段代碼,表面一看,沒啥問題,其實是不對的。運行結果:
為什么呢??
解釋:這里的swap1函數在傳參的時候,是把a,b的值賦值給了x和y,x和y有自己的地址,和a,b的地址沒關系,當x和y的值完成交換后,只是x和y的值交換了,a,b地址對應值沒有改變,所以不能完成a,b的交換。
Swap1函數在使用的時候,是把變量本身直接傳遞給了函數,這種調用函數的方式叫傳值調用。
結論:實參傳遞給形參的時候,形參會單獨創建一份臨時空間來接收實參,對形參的修改不影響實 參。?
怎么解決:
????????在main函數中將a和b的地址傳遞給Swap函數,Swap 函數里邊通過地址間接的操作main函數中的a和b,并達到交換的效果就好了。
#include <stdio.h>
void Swap2(int* px, int* py)
{int tmp = 0;tmp = *px;*px = *py;*py = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交換前:a = %d b = %d\n", a, b);Swap2(&a, &b);printf("交換后:a = %d b = %d\n", a, b);return 0;
}
運行結果:
????????這?調?Swap2函數的時候是將變量的地址傳 遞給了函數,這種函數調用方式叫:傳址調?。?
????????傳址調用,可以讓函數和主調函數之間建立真正的聯系,在函數內部可以修改主調函數中的變量;所以未來函數中只是需要主調函數中的變量值來實現計算,就可以采用傳值調用。如果函數內部要修改主調函數中的變量的值,就需要傳址調用。