目錄
1. 內存和地址
2. 指針變量和地址
2.1 取地址操作符(&)
2.2?指針變量
2.3?解引用操作符 (*)
3. 指針的解引用
3.1 指針 + - 整數
3.2?void* 指針
4. const修飾指針
4.1 const修飾變量
4.2?const修飾指針變量
5. 指針運算
5.1?指針 ± 整數
5.2指針 - 指針
5.3?指針的關系運算
6. 野指針
6.1 野指針成因
6.2 如何規避野指針
7. 指針的使用和傳址調用
7.1 strlen的模擬實現
7.2 傳值調用和傳址調用
1. 內存和地址
什么是內存,我們先舉個例子:假設有?棟宿舍樓,把你放在樓?,樓上有100個房間,但是房間沒有編號,你的?個朋友來找你玩,如果想找到你,就得挨個房?去找,這樣效率很低,但是我們如果根據樓層和樓層的房間的情況,給每個房間編上號
??
一樓:101 102 103... 二樓:201 202 203...以此類推...
有了房間號,如果你的朋友得到房間號,就可以快速的找房間找到你
??
將這個例子對應我們的計算機里面就是:
??
CPU在處理數據的時候,需要的數據是在內存中讀取的,處理后的數據也會放回內存中,那我們買電腦的時候,電腦上內存是8GB/16GB/32GB等,這些內存空間是把內存劃分為?個個的內存單元,每個內存單元的大小取1個字節(1個字節=8 個比特位)
? ??
每個內存單元就相當于每間酒店房間,每個房間能住 8 個比特位,房間(內存單元)都有一個門牌編號(地址),有了門牌號,就能快速找到相應的房間,即CPU能通過地址快速找到內存空間,在C語言中,給地址起了個名字叫指針
所以我們可以理解為:
??
內存單元的編號 == 地址 == 指針
??
1byte = 8bit 1KB = 1024byte 1MB = 1024KB 1GB = 1024MB 1TB = 1024GB 1PB = 1024TB
2. 指針變量和地址
2.1 取地址操作符(&)
創建變量其實就是向內存申請空間,調試下面代碼:
#include <stdio.h>int main()
{int a = 10;&a;//取出a的地址printf("%p\n", &a);return 0;
}
&a取出的是a所占4個字節中地址較?的字節的地址也就是第一個地址
??
雖然整型變量占用4個字節,我們只要知道了第?個字節地址,順藤摸?訪問到4個字節的數據也是可行的
2.2?指針變量
我們通過取地址操作符(&)拿到的地址是?個數值,這個數值有時候也是需要存儲起來,?便后期再使用的,那我們就可以把這樣的地址值存放在指針變量中
int main()
{int a = 10;int* pa = &a;//取出a的地址并存儲到指針變量pa中return 0;
}
指針變量就是用來存放地址的,存放在指針變量中的值都可以當成為地址
2.3?解引用操作符 (*)
我們把地址存儲在指針變量后要如何將存放在里面的東西取出使用呢?在知道地址的前提下,可以通過解引用操作符找到指針指向的對象
int main()
{int a = 100;int* p = &a;*p = 0;return 0;
}
上面這段代碼就是p?通過解引用(*)找到 a 并將其值改成 0 ,就像是通過門牌號找到特定酒店房間里的特定物品,并將其替換
3. 指針的解引用
?
#include <stdio.h>int main()
{int n = 0x11223344;int* pi = &n;*pi = 0;return 0;
}
調試一下這段代碼,代碼會將n的4個字節全部改為0,如果把指針類型改成 char ,那么代碼只是將n的第?個字節改為0
int main()
{int n = 0x11223344;char* pc = (char*)&n;*pc = 0;return 0;
}
結論:指針的類型決定了,對指針解引?的時候有多?的權限(?次能操作?個字節)
? ?
?如: char* 的指針解引?就只能訪問?個字節,? int* 的指針的解引?就能訪問四個字?
3.1 指針 + - 整數
#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;
}
char* 類型的指針變量+1跳過1個字節, int* 類型的指針變量+1跳過了4個字節
? ?
也就是說:指針類型決定了指針加減整數時一步走多大距離
3.2?void* 指針
在指針類型中有?種特殊的類型是 void* 類型的,可以理解為?具體類型的指針(或者叫泛型指針),這種類型的指針可以?來接受任意類型地址。但是也有局限性, void* 類型的指針不能直接進?指針的+-整數和解引?的運算
#include <stdio.h>int main()
{int a = 10;void* pa = &a;void* pc = &a;*pa = 10;*pc = 0;return 0;
}
這?我們可以看到, void* 類型的指針可以接收不同類型的地址,但是無法直接進?指針運算
? ?
那么 void* 類型的指針到底有什么?呢?? ?
?般 void* 類型的指針是使?在函數參數的部分,?來接收不同類型數據的地址,這樣的設計可以實現泛型編程的效果。使得?個函數來處理多種類型的數據
4. const修飾指針
4.1 const修飾變量
變量是可以修改的,如果把變量的地址交給?個指針變量,通過指針變量的也可以修改這個變量
? ?
當我們希望?個變量加上?些限制,不能被修改那么這個時候我們就可以加上一個const
#include <stdio.h>int main()
{int m = 0;m = 20;//m是可以修改的const int n = 0;n = 20;//n是不能被修改的return 0;
}
上述代碼中n是不能被修改的,其實n本質是變量,只不過被const修飾后,在語法上加了限制,只要我們在代碼中對n就?修改,就不符合語法規則,就報錯,致使沒法直接修改n
但是如果我們繞過n,使?n的地址,去修改n就能做到了
#include <stdio.h>int main()
{const int n = 0;printf("n = %d\n", n);int* p = &n;*p = 20;printf("n = %d\n", n);return 0;
}
4.2?const修飾指針變量
當const修飾指針變量的時候:
? ?
1. const如果放在*的左邊,修飾的是指針指向的內容,保證指針指向的內容不能通過指針來改變,但是指針變量本?的內容可變
int const * const p = &a;
?當const放在*的左邊時,限制的就是p所指向的內容,也就是&a
2. const如果放在*的右邊,修飾的是指針變量本?,保證了指針變量的內容不能修改,但是指針指向的內容,可以通過指針改變
當const放在*的右邊時,限制的就是p本身
5. 指針運算
??
5.1?指針 ± 整數
因為數組在內存中是連續存放的,只要知道第?個元素的地址,順藤摸?就能找到后?的所有元素
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", *(p + i));//p+i 這?就是指針+整數}return 0;
}
5.2指針 - 指針
代替 strlen 函數(計算字符或字符串長度),實現一個自定義的函數 my_strlen 來計算輸入字符串的長度
#include <stdio.h>int my_strlen(char* s)
{char* p = s;while (*p != '\0')p++;return p - s;
}int main()
{printf("%d\n", my_strlen("abc"));return 0;
}
5.3?指針的關系運算
//指針的關系運算
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);while (p < arr + sz) //指針的???較{printf("%d ", *p);p++;}return 0;
}
指針的關系運算,實際上就是:
6. 野指針
概念: 野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)
6.1 野指針成因
1. 指針未初始化
#include <stdio.h>int main()
{int* p;//局部變量指針未初始化,默認為隨機值*p = 20;return 0;
}
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;
}
當循環執行到 i = 10 及之后時,指針 p 已經超出了數組 arr 的范圍指向了數組 arr 所占用內存空間之外的未知區域,此時 p 就變成了野指針
3. 指針指向的空間釋放
6.2 如何規避野指針
1.對指針變量都進行初始化操作
? ?
2.注意數組等變量的范圍,小心指針越界? ?
3.指針不使用時,及時置之為NULL空指針? ??
4.不要返回局部變量的地址
?
7. 指針的使用和傳址調用
? ?
7.1 strlen的模擬實現
庫函數strlen的功能是求字符串?度,統計的是字符串中 \0 之前的字符的個數
size_t strlen ( const char * str );
參數str接收?個字符串的起始地址,然后開始統計字符串中 \0 之前的字符個數,最終返回?度
? ??
如果要模擬實現只要從起始地址開始向后逐個字符的遍歷,只要不是 \0 字符,計數器就+1,這樣直到 \0 就停??
strlen鏈接:
??
strlen - C++ Reference
https://legacy.cplusplus.com/reference/cstring/strlen/?kw=strlen
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);return 0;
}
7.2 傳值調用和傳址調用
先寫一個普通的代碼:?
#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函數在使?的時候,是把變量本?直接傳遞給了函數,這種調?函數的?式我們之前在函數的時候就知道了,值是會出了作用域就會自動銷毀,這種叫傳值調用
? ?
實參傳遞給形參的時候,形參會單獨創建?份臨時空間來接收實參,對形參的修改不影響實
參
我們使用指針的方法:
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);Swap1(&a, &b);printf("交換后:a=%d b=%d\n", a, b);return 0;
}
我們可以看到實現成Swap2的?式,順利完成了任務,這?調?Swap2函數的時候是將變量的地址傳遞給了函數,這種函數調??式叫:傳址調用
傳址調?,可以讓函數和主調函數之間建?真正的聯系,在函數內部可以修改主調函數中的變量
? ??
所以未來函數中只是需要主調函數中的變量值來實現計算,就可以采?傳值調?。如果函數內部要修改主調函數中的變量的值,就需要傳址調??
完結撒花~?