目錄
1.內存與地址
1.1 什么是內存
1.2 編址
2. 指針的變量和地址
2.1 取地址(&)
2.2 指針變量
2.3 解引用
2.4?指針變量大小
3. 指針變量類型存在的意義
3.1 不同類型指針的解引用
3.2 指針對整數的運算(+,-)
3.3 void* 指針
4. const 修飾
4.1 const 變量
?4.2 const(指針變量)
5. 指針的運算
?6. 野指針
7. assert斷言
8.指針的使用和傳址調用
舉例1:strlen 的模擬實現
傳址調用例如:寫?個函數,交換兩個整型變量的值
1.內存與地址
1.1 什么是內存
在這之前我們先說一下生活中的一些與內存非常像的例子
生活中有很多高樓,假設樓里有100個房間,你就住在其中一個,你的朋友要來找你,如果房間沒有編號,那么就要一間一間去找,這樣等你的朋友找到你,大概也天黑了,但是如果每個房間都有自己的編號,如:一樓:101,102.......? ? ? ? 二樓:201,202.....以此類推,這樣你的朋友就可以快速找到你。
?其實計算機中的內存也是通過這樣管理的,我們知道計算機CPU在處理數據的時候,需要的數據是在內存中讀取的,用完了在放回內存中。
計算機也是把內存劃分為一個個的內存單元,每個內存單元的大小是1字節。
補充:一個比特位可以儲存一個2進制的位1或0。
bit- 比特位 1byte = 8bit
byte- 字節 1KB = 1024byte
KB 1MB = 1024KB
MB 1GB = 1024MB
GB 1TB = 1024GB
TB 1PB = 1024TB
PB
把這些每一個內存單元想成一個房間,一個房間可以放8個比特位。
把每個內存單元的編號想成一個內存的地址。
總結就是:內存單元的編號==地址==指針
1.2 編址
計算機中的編址,不是把每個字節的地址記錄下來,而是通過硬件設計來完成的。就比如許多樂器的規則,都是設計者設計好的,所有人都遵循這個規則。其實本質上就是一直約定出來的共識。
2. 指針的變量和地址
2.1 取地址(&)
知道了內存和地址,其實在C語言中創建變量就是向內存申請一塊空間。
?向這樣的地址a我們應該如何得呢?
這時就要用到我們的一個操作符(&)——取地址操作符
#include <stdio.h>
//%p--打印地址
int main()
{int a = 10;printf("%p\n", &a);return 0;
}
?我們知道整型變量是占4個字節,這里我們只要知道第一個地址,就可以訪問到4個字節的數據了
2.2 指針變量
當我們通過&得到的地址也是一個數(0x0023DF32),這個數值有時候也要儲存起來,方便我們后面的使用。但是我們應該存放到哪呢?答案就是:指針變量。
#include <stdio.h>
int main()
{int a = 10;int* p = &a;//把a的地址儲存在p這個指針變量中。return 0;
}
注意:指針變量也是變量,這種變量是用來存地址的,只要存放在指針中的值都會理解為地址。
?指針的類型:
int a=10;
int* p=&a;
這里的* 是在說明平時指針變量,而int 是在說明p指向的是整型類型的對象。
2.3 解引用
我們現在知道怎么把地址保證起來了,但是要怎么使用呢?在現實生活中,我們每個房間都會有一把打開它的鑰匙,用了鑰匙我們就可以打開房間,找到里面的東西。C語言其實也是一樣的,我們拿到了地址,就可以通過地址找到地址指向的對象。這里就要用到解引用操作符 *()。
#include <stdio.h>
int main()
{int a = 10;int* p = &a;//把a的地址儲存在p這個指針變量中。*p = 0;//解引用p,找到a并且將它的值改為0printf("%d", *p);return 0;
}
2.4?指針變量大小
#include <stdio.h>
//指針變量的??取決于地址的??
//32位平臺下地址是32個bit位(即4個字節)
//64位平臺下地址是64個bit位(即8個字節)
int main()
{printf("%zd\n", sizeof(char*));printf("%zd\n", sizeof(short*));printf("%zd\n", sizeof(int*));printf("%zd\n", sizeof(double*));return 0;
}
3. 指針變量類型存在的意義
?其實指針類型是有特殊意義的,不同的類型有不同的意義。
3.1 不同類型指針的解引用
我們先看兩個程序:
#include <stdio.h>
int main()
{int n = 0x11223344;int* pi = &n;*pi = 0;return 0;
}
#include <stdio.h>
int main()
{int n = 0x11223344;char* pc = (char*)&n;*pc = 0;return 0;
}
通過調試我們發現第一個程序會把n的4個字節全部改為0,而第二個程序只會把第一個字節改為0
總結:指針的類型決定了,對指針解引?的時候有多?的權限(?次能操作?個字節)。
3.2 指針對整數的運算(+,-)
我們可以看到,char*類型的指針變量+1跳過1個字節,int* 類型的指針變量+1,跳過4次字節。
總結:指針的類型決定了指針向前或者向后走一步的距離。
3.3 void* 指針
在指針類型中有void*類型的,我們可以理解為沒有具體類型的指針(泛指針),這種指針可以接收任意類型的地址。但是,void* 不能用來直接進行指針的加減整數和解引用運算。
?注:?般 void* 類型的指針是使?在函數參數的部分,?來接收不同類型數據的地址,這樣的設計可以 實現泛型編程的效果。使得?個函數來處理多種類型的數據。
4. const 修飾
4.1 const 變量
只要是變量,就是可以修改的。如果我們要想要這個變量成為不可變的,那么就要用到 const 來修飾。
?4.2 const(指針變量)
舉例:
#include <stdio.h>
//代碼1
void test1()
{int n = 10;int m = 20;int* p = &n;*p = 20;p = &m;
}
void test2()
{//代碼2int n = 10;int m = 20;const int* p = &n;*p = 20;p = &m;
}
void test3()
{int n = 10;int m = 20;int* const p = &n;*p = 20;p = &m;
}
void test4()
{int n = 10;int m = 20;int const* const p = &n;*p = 20; p = &m;
}
int main()
{//測試?const修飾的情況test1();//測試const放在*的左邊情況test2();//測試const放在*的右邊情況test3();//測試*的左右兩邊都有consttest4();return 0;
}
結論:const修飾指針變量的時候
1.const如果放在*的左邊,修飾的是指針指向的內容,保證指針指向的內容不能通過指針來改變。 但是指針變量本?的內容可變。
2.const如果放在*的右邊,修飾的是指針變量本?,保證了指針變量的內容不能修改,但是指針指 向的內容,可以通過指針改變。
5. 指針的運算
指針的基本運算有三種,分別是:
?指針+- 整數——根據指針變量的類型,向前或向后移動
?指針-指針
?指針的關系運算
?6. 野指針
概念:野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)——沒有約束的指針。
產生野指針的原因:
1. 指針沒有初始化
2. 指針越界訪問 ——只有10個元素,但是訪問到第11個
3. 指針指向的空間被釋放——指針指向函數
避免野指針的方法:
1. 指針初始化:指針一定要指向一個地址,沒有就給指針賦值NULL(0)
2.小心指針越界:?個程序向內存申請了哪些空間,通過指針也就只能訪問哪些空間。
3. 指針變量不再使用時一定要及時置為NULL,指針使用之前檢查有效性。
4. 避免返回局部變量的地址
7. assert斷言
assert.h 頭?件定義了宏 assert() ,?于在運?時確保程序符合指定條件,如果不符合,就報錯終?運?。這個宏常常被稱為“斷?”。
assert (p!=NULL)
//驗證p是否等于NULL
//當p不等于NULL時,程序繼續運行,否則停止,并且給出報錯信息
assert 的實現:assert() 宏接受?個表達式作為參數。如果該表達式為真(返回值?零),assert() 不會產? 任何作?,程序繼續運?。如果該表達式為假(返回值為零), assert() 就會報錯,在標準錯誤流 stderr 中寫??條錯誤信息,顯示沒有通過的表達式,以及包含這個表達式的?件名和?號。
使用的優點:可以自動標識文件和出問題的行號,還有?種?需更改代碼就能開啟或關閉 assert() 的機制。
補充:如果已經確認程序沒有問題,不需要再做斷?,就在 #include 語句的前?,定義?個宏 NDEBUG 。
8.指針的使用和傳址調用
舉例1:strlen 的模擬實現
#include<assert.h>
int my_strlen(const char* str)
{int count = 0;assert(str);//斷言判的str是否為真(非0)while (*str){count++;str++;}return count;
}
int main()
{int len = my_strlen("abcdef");printf("%d\n", len);return 0;
}
傳址調用
例如:寫?個函數,交換兩個整型變量的值
我們一般的想法(傳值調用):
?注意:實參傳遞給形參的時候,形參會單獨創建?份臨時空間來接收實參,對形參的修改不影響實參。
如果把要交換值的地址傳給函數呢?
我們可以看到交換成功了。這種把變量的地址傳遞給了函數的方式叫:傳址調用。
補充:傳址調?,可以讓函數和主調函數之間建?真正的聯系,在函數內部可以修改主調函數中的變量;所以未來函數中只是需要主調函數中的變量值來實現計算,就可以采?傳值調?。如果函數內部要修改主調函數中的變量的值,就需要傳址調?。